본문 바로가기
개발/차근차근 개발일지 TIL

TIL 230429_sequelize 기반으로 게시판 api만들기2

by 코딩하는짱구 2023. 4. 29.
반응형

TIL 230429_sequelize 기반으로 게시판 api만들기2

✅오늘 학습 Keyword

Sequelize로 DB & Table 생성하기, 각 라우터 연결해주기, 좋아요기능 구현하기 

✅오늘 겪은 문제 

1. routes.comments.js와 app.js를 연결하였으나 계속 서버 연결에 오류가 남. 

2. sequelize를 이용해서 만든 table을 수정하고싶은데 migrations, models 모두 수정해야하는건가?

   그렇다면 순서는?

3. 좋아요 기능을 구현하는 원리는 무엇일까? 

✅해결 방법

1. 처음에 라우터를 지정해줄때 값을 const Comment= require('../modles/Comments.js') 로 줬던게 문제였다.  models에 가보면 아래처럼 mongoose스키마와는 다르게 Model이 객체로 선언되어있다. 

 

'use strict';
const { Model } = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class Comments extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here

      // 1. Comments 모델에서
      this.belongsTo(models.Users, {
        // 2. Users 모델에게 N:1 관계 설정을 합니다.
        targetKey: 'userId', // 3. Users 모델의 userId 컬럼을
        foreignKey: 'UserId', // 4. Comments 모델의 UserId 컬럼과 연결합니다.
      });

      // 1. Comments 모델에서
      this.belongsTo(models.Posts, {
        // 2. Posts 모델에게 N:1 관계 설정을 합니다.
        targetKey: 'postId', // 3. Posts 모델의 postId 컬럼을
        foreignKey: 'PostId', // 4. Comments 모델의 PostId 컬럼과 연결합니다.
      });
    }
  }

  Comments.init(
    {
      commentId: {
        allowNull: false, // NOT NULL
        autoIncrement: true, // AUTO_INCREMENT
        primaryKey: true, // Primary Key (기본키)
        type: DataTypes.INTEGER,
      },
      UserId: {
        allowNull: false, // NOT NULL
        type: DataTypes.INTEGER,
      },
      PostId: {
        allowNull: false, // NOT NULL
        type: DataTypes.INTEGER,
      },
      comment: {
        allowNull: false, // NOT NULL
        type: DataTypes.STRING,
      },
      createdAt: {
        allowNull: false, // NOT NULL
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
      },
      updatedAt: {
        allowNull: false, // NOT NULL
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
      },
    },
    {
      sequelize,
      modelName: 'Comments',
    }
  );
  return Comments;
};

 따라서 아래와 같이 model도 객체로 선언해주어야 함. 

const express = require('express');
const { Comments, Posts } = require('../models');
const { Users } = require('../models');
const authMiddleware = require('../middlewares/auth-middleware.js');
const router = express.Router();

//댓글 등록
router.post('/posts/:postId/comments', authMiddleware, async (req, res) => {
  try {
    const postId = req.params.postId;
    const { userId } = res.locals.user; //사용자 인증이 완료된
    const { content } = req.body;

    // 게시글이 존재하지 않을 경우
    const post = await Posts.findByPk(postId);
    if (!post) {
      return res.status(404).json({ message: '게시글이 존재하지 않습니다.' });
    }

    const comment = await Comments.create({
      //변수가 안쓰이는디
      UserId: userId,
      PostId: postId,
      comment: content,
    });

    return res.status(201).json({ message: '댓글을 작성하였습니다.' }); //작성한 데이터를 보려면?..
  } catch (err) {
    // 예외 처리되지 않은 모든 에러는 이곳에서 처리합니다.
    console.error(err);
    return res.status(400).json({ message: '댓글 작성에 실패하였습니다.' });
  }
});

//특정 게시물의 댓글 조회 //comments mig, model에 nickname 칼럼이 없음..
router.get('/posts/:postId/comments', authMiddleware, async (req, res) => {
  try {
    const postId = req.params.postId;
    const comments = await Comments.findAll({
      where: { PostId: postId },
      attributes: ['commentId', 'UserId', 'comment', 'createdAt', 'updatedAt'],
      order: [['createdAt', 'DESC']],
    });

    return res.status(200).json({ comments: comments });
  } catch (error) {
    console.error(error);
    return res.status(400).json({ message: '댓글 조회에 실패하였습니다.' });
  }
});

//댓글 수정
router.put(
  '/posts/:postId/comments/:commentId',
  authMiddleware,
  async (req, res) => {
    try {
      const { commentId } = req.params;
      const { content } = req.body;

      const existsComment = await Comments.findByPk(commentId);

      if (!existsComment) {
        return res.status(400).json({ message: '댓글을 찾을 수 없습니다.' });
      }

      existsComment.comment = content;
      await existsComment.save();

      res.status(200).json({ message: '댓글을 수정했습니다.', existsComment });
    } catch (error) {
      console.error(error);
      res.status(500).json({ message: '댓글 수정에 실패했습니다.' });
    }
  }
);

// //댓글삭제
router.delete(
  '/posts/:postId/comments/:commentId',
  authMiddleware,
  async (req, res) => {
    try {
      const { commentId } = req.params;

      const comment = await Comments.findByPk(commentId);
      if (comment) {
        await comment.destroy();
        res.json({ message: '댓글을 삭제하였습니다.' });
      } else {
        return res.status(400).json({ message: '해당 ID의 댓글이 없습니다.' });
      }
    } catch (error) {
      console.error(error);
      res.status(500).json({ errorMessage: '댓글 삭제에 실패하였습니다.' });
    }
  }
);

module.exports = router;

 

2. sequelize를 이용해서 만든 table을 수정하고싶은데 migrations, models 모두 수정해야하는건가?

   그렇다면 순서는?

-이부분은 아직도 조금 헷갈리긴 하지만 두가지 방법을 다 써봤다. 처음엔 migrations 파일을 수정하지않고, 

아래와 같이 models만을 사용해서 테이블을 생성하는 코드로 테이블을 다시 만들었다. 

이렇게 하면 undo와 같은 코드 없이도 자동으로 원래 있던 데이터들이 모두 삭제되고 다시 만들어져서, 

지금처럼 관리할 데이터가 별로 없는 상황에선 오히려 효율적인 것 같다. 

// model을 이용해 테이블 생성하는 코드

// const { sequelize } = require('./models/index.js');

// async function main() {
//   // model을 이용해 데이터베이스에 테이블을 삭제 후 생성합니다.
//   await sequelize.sync({ force: true });
// }

// main();

 

-두번째로는 migrations 을 수정,  model을 migrations와 동일하게 수정한 뒤 아래와 같은 sequelize 명령어를 사용하여 수정했다. 이전에는 수정할 것이 있으면 수정사항이 적용된 migration파일을 하나 만들어서 진행했는데 그럴 필요 없이 그냥 삭제하고 다시 만드는게 편할 것 같다. 

sequelize db:migrate
sequelize db:migrate:undo

https://crispypotato.tistory.com/156

 

3. 좋아요 기능을 구현하는 원리는 무엇일까? 

좋아요에 해당하는 테이블을 만든 뒤 그 테이블에 정보를 저장, 삭제하는 방식으로 구현했다.

좋아요를 누를 때 필요한 정보는 누른 사람, 그리고 누른 게시물이다. 즉 userId, postIdr값을 컬럼에 넣어준 후에 PUT을 이용하여 필요한 정보를 저장 / 삭제하는 방식이다. 

 

처음에는 DELETE로 좋아요 삭제 기능을 따로 추가했으나 과제에서는 DELETE방식이 아닌 PUT으로 두가지 과정을 모두 처리하라고 요구했으므로, 아래와 같이 구현했다. 

const express = require('express');
const authMiddleware = require('../middlewares/auth-middleware');
const { Likes, Users, Posts } = require('../models');
const router = express.Router();

//특정 게시물에 좋아요 추가
router.put('/posts/:postId/likes', authMiddleware, async (req, res) => {
  try {
    const { postId } = req.params; //좋아요를 누르고자 하는 게시물의 id
    const { userId } = res.locals.user;

    // 이미 좋아요를 누른 게시글인지 확인
    const existingLike = await Likes.findOne({
      where: { PostId: postId, UserId: userId },
    });

    if (existingLike) {
      await Likes.destroy({ where: { PostId: postId, UserId: userId } });
      return res.status(200).json({ message: '좋아요를 취소하였습니다.' });
    }

    // 좋아요 추가
    await Likes.create({ PostId: postId, UserId: userId }); //누가 뭘 좋아요했는지

    res.json({ message: '게시글에 좋아요를 등록하였습니다.' });
  } catch (error) {
    console.error(error);
    res.status(400).json({ message: '좋아요 등록에 실패하였습니다.' });
  }
});

router.get('/posts/likes', authMiddleware, async (req, res) => {
  try {
    const { userId } = res.locals.user;

    // 좋아요한 게시글의 id를 가져옴
    const likedPostIds = await Likes.findAll({
      attributes: ['PostId'],
      where: { UserId: userId },
    });

    // 좋아요한 게시글들을 가져옴
    const likedPosts = await Posts.findAll({
      where: { id: likedPostIds.map((like) => like.PostId) },
      include: { model: Users, attributes: ['id', 'nickname'] },
    });

    // 각 게시글에 대해 좋아요 수 계산
    const postsWithLikeCounts = likedPosts.map((post) => {
      const likeCount = likedPostIds.filter(
        (like) => like.PostId === post.id
      ).length;
      return { ...post.toJSON(), likeCount };
    });

    // 좋아요 수에 따라 정렬
    const sortedPosts = postsWithLikeCounts.sort(
      (a, b) => b.likeCount - a.likeCount
    );

    res.json({ likedPosts: sortedPosts });
  } catch (error) {
    console.error(error);
    res.status(400).json({ message: '게시글 조회에 실패하였습니다.' });
  }
});

// 특정 게시물에 좋아요 삭제
// router.put('/posts/:postId/likes', authMiddleware, async (req, res) => {
//   try {
//     const { postId } = req.params;
//     const { userId } = req.user;

//     // 좋아요 삭제

//     res.json({ message: '게시글의 좋아요를 취소하였습니다.' });
//   } catch (error) {
//     console.error(error);
//     res.status(400).json({ message: '좋아요 취소에 실패하였습니다.' });
//   }
// });

module.exports = router;

✅오늘 알게된 점 

과제의 완성 속도보다는 완성도에 더 신경써야될 것 같다. 기존에 있던 작업물에 단순히 코드들을 복붙하는 것이 아닌 데이터 생성부터 서버연결, 그리고 기능 구현까지 모든 단계의 개념을 이해하면서 진행해야겠다.

오늘 겪은 1번 문제처럼 코드들이 연결되고 실행되는 원리를 정확히 파악하지 못하는 부분에서 막히는 경향이 있다. 

나도 모르게 조금 조급한 마음이 들어 휙휙 지나갔던 부분들을 다시 한 번 새기면서 공부할 예정이다. 

반응형