웹 개발
나만의 뉴스피드 만들기
용찬
2023. 7. 11. 19:44
MYSQL이용해서 블로그 만들기(회원, 게시글, 댓글)
파일 구조
node_LV2
├─ app.js
├─ config
│ └─ config.json
├─ middlewares
│ └─ auth-middleware.js
├─ migrations
│ ├─ 20230623012409-create-users.js
│ ├─ 20230623012412-create-user-infos.js
│ ├─ 20230623012416-create-posts.js
│ └─ 20230623012419-create-comments.js
├─ models
│ ├─ comments.js
│ ├─ index.js
│ ├─ posts.js
│ ├─ userinfos.js
│ └─ users.js
├─ package-lock.json
├─ package.json
├─ README.md
├─ routes
│ ├─ comments.js
│ ├─ posts.js
│ └─ users.js
└─ seeders
mysql 설정
config/config.json에 설정, DB생성
=> npx sequelize db:create
migrations 폴더에 정의된 파일들과 mysql의 테이블 매핑
=> npx sequelize db:migrate
models안에 있는 파일들에 관계 설정
회원가입, 로그인, 사용자 정보 조회
const express = require("express");
const { Users, UserInfos } = require("../models");
const router = express.Router();
const jwt = require("jsonwebtoken");
// 회원가입
router.post("/users", async (req, res) => {
const { email, password, nickname } = req.body;
const isExistUser = await Users.findOne({ where: { email } });
// 이 정규식 표현이 의도한 대로 작동하지 않음
// 의도한 것 = 알파벳 + 숫자 + .@ 를 포함한 것
if (!/^[a-zA-Z0-9.@]{13,50}$/.test(email)) {
return res.status(410).json({
message: "이메일은 영어와 숫자로 13자 이상 50자 까지 가능합니다.",
});
}
if (isExistUser) {
return res.status(409).json({ message: "이미 존재하는 이메일입니다." });
}
// Users 테이블에 사용자를 추가합니다.
const user = await Users.create({ email, password });
// UserInfos 테이블에 사용자 정보를 추가합니다.
const userInfo = await UserInfos.create({
UserId: user.userId,
email: email, // 생성한 유저의 userId를 바탕으로 사용자 정보를 생성합니다.
nickname: nickname,
password: password,
userDesc: null,
});
return res.status(201).json({ message: "회원가입이 완료되었습니다." });
});
// routes/users.route.js
// 로그인
router.post("/login", async (req, res) => {
const { email, password } = req.body;
const user = await Users.findOne({ where: { email } });
if (!user) {
return res.status(401).json({ message: "존재하지 않는 이메일입니다." });
} else if (user.password !== password) {
return res.status(401).json({ message: "비밀번호가 일치하지 않습니다." });
}
const token = jwt.sign(
{
userId: user.userId,
},
"customized_secret_key"
);
res.cookie("authorization", `Bearer ${token}`);
return res.status(200).json({ message: "로그인 성공" });
});
// 사용자 조회 API (GET)
router.get("/users/:userId", async (req, res) => {
const { userId } = req.params;
const user = await Users.findOne({
attributes: ["userId", "email", "createdAt", "updatedAt"],
include: [
{
model: UserInfos, // 1:1 관계를 맺고있는 UserInfos 테이블을 조회합니다.
attributes: ["userDesc"],
},
],
where: { userId },
});
return res.status(200).json({ data: user });
});
module.exports = router;
게시글 CRUD
const express = require("express");
const { Op } = require("sequelize");
const { Posts } = require("../models");
const authMiddleware = require("../middleware/authMiddleware");
const router = express.Router();
// 게시글 목록 조회
router.get("/posts", async (req, res) => {
const posts = await Posts.findAll({
attributes: ["postId", "title", "content", "createdAt", "updatedAt"],
order: [["createdAt", "DESC"]],
});
return res.status(200).json({ data: posts });
});
// 내 게시글 조회 하기
router.get("/posts/:userId", authMiddleware, async (req, res) => {
const { userId } = req.params;
const { authenticatedUserId } = req.user;
const posts = await Posts.findAll({
attributes: [
"userId",
"postId",
"title",
"content",
"createdAt",
"updatedAt",
],
order: [["createdAt", "DESC"]],
where: { userId },
});
console.log(posts);
if (!posts) {
return res.status(404).json({ message: "게시글이 존재하지 않습니다" });
} else if (userId != authenticatedUserId) {
return res.status(401).json({ message: "권한이 없습니다" });
}
return res.status(200).json({ posts });
});
// 게시글 생성
router.post("/posts", authMiddleware, async (req, res) => {
const { userId } = res.locals.user;
const { title, content } = req.body;
const post = await Posts.create({
UserId: userId,
title,
content,
});
return res.status(201).json({ data: post });
});
// 게시글 수정
router.put("/posts/:postId", authMiddleware, async (req, res) => {
const { postId } = req.params;
const { userId } = res.locals.user;
const { title, content } = req.body;
const post = await Posts.findOne({ where: { postId } });
if (!post) {
return res.status(404).json({ message: "게시글이 존재하지 않습니다" });
} else if (post.UserId != userId) {
return res.status(401).json({ message: "권한이 없습니다" });
}
await Posts.update(
{ title, content },
{
where: {
[Op.and]: [{ postId }, { userId: userId }],
},
}
);
return res.status(200).json({ data: "게시글이 수정되었습니다." });
});
// 게시글 삭제
router.delete("/posts/:postId", authMiddleware, async (req, res) => {
const { postId } = req.params;
const { userId } = res.locals.user;
const { title } = res.req.body;
const post = await Posts.findOne({ where: { postId } });
if (!post) {
return res.status(404).json({ message: "게시글이 없습니다." });
} else if (post.UserId != userId) {
return res.status(404).json({ message: "권한이 없습니다" });
} else if (post.title !== title) {
return res.status(401).json({ message: "제목이 일치하지 않습니다." });
}
await Posts.destroy({
where: {
[Op.and]: [{ postId }, { UserId: userId }],
},
});
return res.status(200).json({ data: "게시글이 삭제되었습니다." });
});
module.exports = router;
댓글 CRUD
const express = require("express");
const router = express.Router();
// Middleware
const authMiddleware = require("../middleware/authMiddleware");
// Model
const { Cmts } = require("../models");
const { Op } = require("sequelize");
// 댓글 목록 조회 API (GET)
router.get("/posts/:postId/cmts", async (req, res) => {
const cmts = await Cmts.findAll({
attributes: ["cmtId", "postId", "content", "createdAt", "updatedAt"],
order: [["createdAt", "DESC"]],
});
return res.status(200).json({ data: cmts });
});
// 댓글 작성 API (POST)
router.post("/posts/:postId/cmts", authMiddleware, async (req, res) => {
const { userId } = res.locals.user;
const { postId } = req.params;
const { content } = req.body;
const cmt = await Cmts.create({
UserId: userId,
PostId: postId,
content: content,
});
return res.status(201).json({ data: cmt });
});
// 댓글 수정 API (PUT)
router.put("/posts/cmts/:cmtId", authMiddleware, async (req, res) => {
const { cmtId } = req.params;
const { userId } = res.locals.user;
const { content } = req.body;
const cmt = await Cmts.findOne({ where: { cmtId } });
if (!cmt) {
return res.status(404).json({ message: "해당 댓글이 존재하지 않습니다." });
} else if (cmt.userId !== userId) {
// 404 => 403
return res.status(403).json({ message: "권한이 없습니다." });
}
await Cmts.update(
{ content },
{
where: {
[Op.and]: [{ cmtId }, { userId }],
},
}
);
return res.status(200).json({ data: "댓글이 수정되었습니다." });
});
// 댓글 삭제 API (DELETE)
// /posts/:postId/cmts => /posts/cmts/:cmtId
router.delete("/posts/cmts/:cmtId", authMiddleware, async (req, res) => {
const { cmtId } = req.params;
const { userId } = res.locals.user;
const { content } = res.req.body;
const cmt = await Cmts.findOne({ where: { cmtId } });
if (!cmt) {
return res.status(404).json({ message: "해당 댓글이 존재하지 않습니다." });
} else if (cmt.userId !== userId) {
// 404 => 403
return res.status(403).json({ message: "권한이 없습니다." });
} else if (cmt.content !== content) {
return res.status(401).json({ message: "내용이 일치하지 않습니다." });
}
await Cmts.destroy({
where: {
[Op.and]: [{ cmtId }, { userId }],
},
});
return res.status(200).json({ data: "댓글이 삭제되었습니다." });
});
module.exports = router;
좋아요 기능
const express = require("express");
const router = express.Router();
const { Posts } = require("../models");
const { Likes } = require("../models");
const authMiddleware = require("../middleware/authMiddleware");
router.put("/like/:postId", authMiddleware, async (req, res) => {
const postId = req.params.postId;
const { userId } = res.locals.user;
try {
// Likes 모델에서 해당 postId와 userId의 좋아요 데이터 조회
let like = await Likes.findOne({
where: { postId: postId, userId: userId },
});
if (!like) {
// 좋아요 데이터가 없는 경우, 새로 생성
like = await Likes.create({ postId: postId, userId: userId, like: 1 });
} else {
// 좋아요 데이터가 있는 경우, toggle
like.like = like.like === 1 ? 0 : 1;
await like.save();
}
return res.json({
success: true,
message: like.like > 0 ? "좋아요 생성" : "좋아요 삭제",
likeCount: like.like,
});
} catch (error) {
console.error("오류 발생", error);
return res
.status(500)
.json({ success: false, message: "서버 오류가 발생했습니다." });
}
});
// 좋아요가 많은 순서대로 게시글 정렬
router.get("/like/post", authMiddleware, async (req, res) => {
const userId = res.locals.user;
const likePosts = await Posts.findAll({
attributes: ["title", "createdAt", "like"],
order: [["like", "DESC"]],
where: { userId },
});
console.log(likePosts);
res.status(200).json({ message: likePosts });
});
module.exports = router;
회원 인증 미들웨어
const jwt = require("jsonwebtoken");
const { Users } = require("../models");
module.exports = async (req, res, next) => {
try {
const { authorization } = req.cookies;
const [tokenType, token] = authorization.split(" ");
if (tokenType !== "Bearer") {
return res
.status(401)
.json({ message: "토큰 타입이 일치하지 않습니다." });
}
const decodedToken = jwt.verify(token, "customized_secret_key");
const userId = decodedToken.userId;
const user = await Users.findOne({ where: { userId } });
if (!user) {
res.clearCookie("authorization");
return res
.status(401)
.json({ message: "토큰 사용자가 존재하지 않습니다." });
}
res.locals.user = user;
next();
} catch (error) {
res.clearCookie("authorization");
return res.status(401).json({
message: "비정상적인 요청입니다.",
});
}
};