const express = require("express");
const cors = require("cors");
const session = require("express-session");
const cookieParser = require("cookie-parser");
const passport = require("passport");
const morgan = require("morgan");
const dotenv = require("dotenv");
const postRouter = require("./routes/post");
const userRouter = require("./routes/user");
const db = require("./models");
const passportConfig = require("./passport");
dotenv.config();
const app = express();
db.sequelize
.sync()
.then(() => {
console.log("DB 연결 성공");
})
.catch(console.error);
passportConfig();
app.use(morgan("dev"));
app.use(
cors({
origin: "*",
credentials: false,
})
); //모든 요청에 설정을 넣어줌
//req.body 사용하기 위함
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cookieParser(process.env.COOKIE_SECRET));
app.use(
session({
saveUninitialized: false,
resave: false,
secret: process.env.COOKIE_SECRET,
})
);
app.use(passport.initialize());
app.use(passport.session());
app.get("/", (req, res) => {
res.send("hello express");
});
app.get("/", (req, res) => {
res.send("hello");
});
app.get("/posts", (req, res) => {
res.json([
{ id: 1, content: "hello" },
{ id: 2, content: "hello 2" },
{ id: 3, content: "hello 3" },
]);
});
app.use("/post", postRouter);
app.use("/user", userRouter);
app.listen(3065, () => {
console.log("서버 실행 중");
});
《 models(폴더) 》
┗ post.js
┗ user.js
《 passport(폴더) 》
┗ index.js
┗ local.js
《 routes(폴더) 》
┗ user.js
▶ db.User.belongsToMany에서 값 미입력 및 오타 있었음
module.exports = (sequelize, DataTypes) => {
const User = sequelize.define(
"User",
{
email: {
type: DataTypes.STRING(30),
allowNull: false, //필수
unique: true, //고유한 값
},
nickname: {
type: DataTypes.STRING(30),
allowNull: false, //필수
},
password: {
type: DataTypes.STRING(100),
allowNull: false, //필수
},
},
{
charset: "utf8",
collate: "utf8_general_ci", //한글 저장
}
);
User.associate = (db) => {
db.User.hasMany(db.Post);
db.User.hasMany(db.Comment);
db.User.belongsToMany(db.Post, { through: "Like", as: "Liked" });
db.User.belongsToMany(db.User, {
through: "Follow",
as: "Followers",
foreignKey: "FollowingId",
});
db.User.belongsToMany(db.User, {
through: "Follow",
as: "Followings",
foreignKey: "FollowerId",
});
};
return User;
};
const passport = require("passport");
const local = require("./local");
const { User } = require("../models");
module.exports = () => {
passport.serializeUser((user, done) => {
done(null, user.id);
});
passport.deserializeUser(async (id, done) => {
try {
const user = await User.findOne({ where: { id } });
done(null, user);
} catch (error) {
console.error(error);
done(error);
}
});
local();
};
const passport = require("passport");
const { Strategy: LocalStrategy } = require("passport-local");
const bcrypt = require("bcrypt");
const { User } = require("../models");
module.exports = () => {
passport.use(
new LocalStrategy(
{
usernameField: "email",
passwordField: "password",
},
async (email, password, done) => {
try {
const user = await User.findOne({
where: { email },
});
if (!user) {
done(null, false, { reason: "존재하지 않는 사용자입니다!" });
}
const result = await bcrypt.compare(password, user.password);
if (result) {
return done(null, user); //성공에 사용자 정보 넘김
}
return done(null, false, { resaon: "비밀번호가 틀렸습니다." });
} catch (error) {
console.error(error);
return done(error);
}
}
)
);
};
const express = require("express");
const bcrypt = require("bcrypt");
const passport = require("passport");
const { User, Post } = require("../models");
const { isLoggedIn, isNotLoggedIn } = require("./middlewares");
const router = express.Router();
//로그인
router.post("/login", isNotLoggedIn, (req, res, next) => {
passport.authenticate("local", (err, user, info) => {
if (err) {
console.error(err);
return next(err);
}
if (info) {
return res.status(401).send(info.reason);
}
return req.login(user, async (loginErr) => {
console.log("loginErr", loginErr);
if (loginErr) {
console.error(loginErr);
return next(loginErr);
}
const fullUserWithoutPassword = await User.findOne({
where: { id: user.id },
attributes: {
exclude: ["password"],
},
include: [
{
model: Post,
attributes: ["id"],
},
{
model: User,
as: "Followings",
attributes: ["id"],
},
{
model: User,
as: "Followers",
attributes: ["id"],
},
],
});
return res.status(200).json(fullUserWithoutPassword);
});
})(req, res, next);
});
//로그아웃
router.post("/logout", isLoggedIn, (req, res) => {
req.logout();
req.session.destroy();
res.send("ok");
});
//회원가입
router.post("/", isNotLoggedIn, async (req, res, next) => {
try {
const exUser = await User.findOne({
where: {
email: req.body.email,
},
});
if (exUser) {
//반드시 return 붙여야 함
return res.status(403).send("이미 사용 중인 아이디 입니다.");
}
const hashedPassword = await bcrypt.hash(req.body.password, 12);
await User.create({
email: req.body.email,
nickname: req.body.nickname,
password: hashedPassword,
});
res.status(201).send("ok");
} catch (error) {
console.error(error);
next(error);
}
});
module.exports = router;
《 components(폴더) 》
┗ LoginForm.js(로그인)
┗ UserProfile.js(로그인 후 나오는 회원정보)
《 pages(폴더) 》
┗ index.js(로그인)
┗ signup.js(회원가입)
《 reducer(폴더) 》
┗ index.js
┗ post.js
┗ user.js
《 store(폴더) 》
《 《 sagas(폴더) 》 》
┗ index.js
┗ post.js
┗ user.js
export const initialState = {
logInLoading: false, //로그인 시도 중
logInDone: false,
logInError: null,
logOutLoading: false, //로그아웃 시도 중
logOutDone: false,
logOutError: null,
signUpLoading: false, //회원가입 시도 중
signUpDone: false,
signUpError: false,
}
export const LOG_IN_REQUEST = "LOG_IN_REQUEST";
export const LOG_IN_SUCCESS = "LOG_IN_SUCCESS";
export const LOG_IN_FAILURE = "LOG_IN_FAILURE";
export const LOG_OUT_REQUEST = "LOG_OUT_REQUEST";
export const LOG_OUT_SUCCESS = "LOG_OUT_SUCCESS";
export const LOG_OUT_FAILURE = "LOG_OUT_FAILURE";
export const SIGN_UP_REQUEST = "SIGN_UP_REQUEST";
export const SIGN_UP_SUCCESS = "SIGN_UP_SUCCESS";
export const SIGN_UP_FAILURE = "SIGN_UP_FAILURE";
export const loginRequestAction = (data) => ({
type: LOG_IN_REQUEST,
data,
});
export const logoutRequestAction = () => ({
type: LOG_OUT_REQUEST,
});
const reducer = (state = initialState, action) => {
return produce(state, (draft) => {
switch (action.type) {
case LOG_IN_REQUEST:
console.log("reducer logIn");
draft.logInLoading = true;
draft.logInError = null;
draft.logInDone = false;
break;
case LOG_IN_SUCCESS: {
draft.logInLoading = false;
draft.logInDone = true;
draft.me = action.data;
break;
}
case LOG_IN_FAILURE: {
draft.logInLoading = false;
draft.logInError = action.error;
break;
}
case LOG_OUT_REQUEST:
draft.logOutLoading = true;
draft.logOutDone = false;
draft.logOutError = null;
break;
case LOG_OUT_SUCCESS:
draft.logOutLoading = false;
draft.logOutDone = true;
draft.me = null;
break;
case LOG_OUT_FAILURE:
draft.logOutLoading = false;
draft.logOutError = action.error;
break;
case LOG_OUT_REQUEST:
draft.logOutLoading = true;
draft.logOutDone = false;
draft.logOutError = null;
break;
case LOG_OUT_SUCCESS:
draft.logOutLoading = false;
draft.logOutDone = true;
draft.me = null;
break;
case LOG_OUT_FAILURE:
draft.logOutLoading = false;
draft.logOutError = action.error;
break;
// signup
case SIGN_UP_REQUEST:
draft.signUpLoading = true;
draft.signUpDone = false;
draft.signUpError = null;
break;
case SIGN_UP_SUCCESS:
draft.signUpLoading = false;
draft.signUpDone = true;
draft.me = null;
break;
case SIGN_UP_FAILURE:
draft.signUpLoading = false;
draft.signUpError = action.error;
break;
}
});
};
export default reducer;
▶ axios.defaults.baseURL 사용하기 위해서는 front에서도 axios 설치할 것
▶ baseURL을 한 번 만들면 sagas/user.js에서 자동으로 URL 주소가 붙어서 적용됨
import { all, fork } from "redux-saga/effects";
import axios from "axios";
import postSaga from "./post";
import userSaga from "./user";
axios.defaults.baseURL = "http://localhost:3065";
export default function* rootSaga() {
yield all([fork(postSaga), fork(userSaga)]);
}
import { all, takeLatest, delay, put, fork, call } from "redux-saga/effects";
import axios from "axios";
import {
LOG_IN_REQUEST,
LOG_IN_SUCCESS,
LOG_IN_FAILURE,
LOG_OUT_REQUEST,
LOG_OUT_SUCCESS,
LOG_OUT_FAILURE,
SIGN_UP_REQUEST,
SIGN_UP_SUCCESS,
SIGN_UP_FAILURE,
} from "../../reducers/user";
function logInAPI(data) {
return axios.post("/user/login", data);
}
function* logIn(action) {
try {
const result = yield call(logInAPI, action.data);
yield put({
type: LOG_IN_SUCCESS,
data: result.data,
});
} catch (err) {
console.log(err);
yield put({
type: LOG_IN_FAILURE,
error: err.response.data,
});
}
}
function logOutAPI() {
return axios.post("/user/logout");
}
function* logOut() {
try {
// const result = yield call(logOutAPI)
yield delay(1000);
yield put({
type: LOG_OUT_SUCCESS,
});
} catch (err) {
yield put({
type: LOG_OUT_FAILURE,
error: err.response.data,
});
}
}
function signUpAPI(data) {
return axios.post("/user", data);
}
function* signUp(action) {
try {
console.log("action", action);
console.log("action.data", action.data);
const result = yield call(signUpAPI, action.data);
console.log(result);
yield put({
type: SIGN_UP_SUCCESS,
});
} catch (err) {
console.error(err);
yield put({
type: SIGN_UP_FAILURE,
error: err.response.data,
});
}
}
function* watchLogin() {
yield takeLatest(LOG_IN_REQUEST, logIn);
}
function* watchLogOut() {
yield takeLatest(LOG_OUT_REQUEST, logOut);
}
function* watchSignUp() {
yield takeLatest(SIGN_UP_REQUEST, signUp);
}
export default function* userSaga() {
yield all([
fork(watchLogin),
fork(watchLogOut),
fork(watchSignUp),
]);
}