웹 개발

나만의 뉴스피드 만들기

용찬 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: "비정상적인 요청입니다.",
    });
  }
};