본문 바로가기
프로그래밍/Node.js

[16] 댓글기능, 좋아요 기능을 구현해보자! (Node.js x mySQL x sequelize) -3탄-

by 제이스톨 2023. 7. 28.
728x90

들어가기 전에..

지난 시간까지는 개발환경 세팅하는 방법과 회원가입, 로그인, 게시글CRUD 구현까지 알아보았다.

이번 3탄에서는 댓글기능(Cmt), 좋아요(like)기능을 넣어보도록 하겠다.

 

[아직까지 like기능은 미구현입니다..ㅠ 이번 주말 안에 업로드 예정입니다.]

 

https://jh-healing-place.tistory.com/105

 

[14] 회원가입, 로그인, 게시판CRUD 구현해보자! (Node.js x mySQL x sequelize) 1탄

1. 들어가기 전에 개발자라고 하면 모두들 CRUD는 기본이라고한다. 하지만 이 CRUD를 구현한다는게 가장 어렵고 이를 자유자재로 구현하게 되면 그때서야 비로소 초급 개발자의 문턱에 들어갔다고

jh-healing-place.tistory.com

https://jh-healing-place.tistory.com/117

 

[15] 회원가입, 로그인, 게시판CRUD 구현해보자! (Node.js x mySQL x sequelize) 2탄

1. 들어가기 전에.. 여기서는 본격적으로 로그인, 로그아웃 기능을 위해 user라는 DB를 만들고 게시글과 댓글 기능을 위한 post와 cmt라는 DB를 만들어보도록 하겠다. ( 환경세팅 방법은 아래 1탄에 있

jh-healing-place.tistory.com


1. 댓글기능 (Cmt), 좋아요 기능 구현

# 1. migration 생성 및 수정 ( 댓글,  contentId라는 값을 가지고 있는 like table )

- npx sequelize model:generate --name Cmts --attributes userId:Integer,postId:Integer,cmtId:Integer,content:String

- npx sequelize model:generate --name like --attributes userId:Integer,contentId:Integer

 

# 2. DB 생성 및 Migrations에 정의된 테이블 MySQL에 생성

npx sequelize db:create
npx sequelize db:migrate

 

// cmt migration

'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('Cmts', {
      cmtId: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      postId: {
        allowNull: false,
        type: Sequelize.INTEGER,
        references: {
          model: "Posts",
          key: "postId",
        },
        onDelete: "CASCADE",
      },
      userId: {
        allowNull: false,
        type: Sequelize.INTEGER,
        references: {
          model: "Users",
          key: "userId",
        },
        onDelete: "CASCADE",
      },
      content: {
        type: Sequelize.STRING
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('Cmts');
  }
};
// like migrations

 

# 3. migration 파일 수정 및 model 폴더 내부 파일 세팅

// cmt models

'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class Cmts extends Model {
    static associate(models) {
      // Users와 Cmts는 일대다 관계
      this.belongsTo(models.Users, {
        targetKey: "userId",
        foreignKey: "userId",
      });

      // Posts와 Cmts는 일대다 관계
      this.belongsTo(models.Posts, {
        sourceKey: "cmtId",
        foreignKey: "postId",
      });
    }
  }
  Cmts.init({
    cmtId: {
      allowNull: false,
      autoIncrement: true,
      primaryKey: true,
      type: DataTypes.INTEGER,
    },
    postId: {
      allowNull: false,
      type: DataTypes.INTEGER,
    },
    userId: {
      allowNull: false,
      type: DataTypes.INTEGER,
    },
    content: {
      allowNull: false,
      type: DataTypes.STRING,
    },
    createdAt: {
      allowNull: false,
      type: DataTypes.DATE,
    },
    updatedAt: {
      allowNull: false,
      type: DataTypes.DATE,
    },
  }, {
    sequelize,
    modelName: 'Cmts',
  });
  return Cmts;
};
// like models

 

 

2. Route 설정

// cmtsRoute

const express = require('express');
const router = express.Router();

const authMiddleware = require('../middlewares/authMiddleware');
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)
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;
// likeRoute

 

3. app.js 수정

const cors = require('cors');
const express = require('express');
const app = express();
const port = 3000;

const path = require('path');

// cookie parser
const cookieParser = require('cookie-parser');

const usersRouter = require('./routes/usersRoute.js');
const postsRouter = require('./routes/postsRoute.js');
const cmtsRouter = require('./routes/cmtsRoute.js');

// Middleware ==================================================
app.use(express.json()); // req.body parser
app.use(cookieParser()); // cookie parser
app.use(cors()); // front-back connect

// localhost:3000/api/
app.use('/api', [usersRouter]);
app.use('/api', [postsRouter]);
app.use('/api', [cmtsRouter]);
// Middleware ==================================================

// HTML, CSS
app.use(express.static(path.join(__dirname, 'assets')));
app.get('/', (req, res) => {
    res.sendFile(path.join(__dirname, 'assets', 'index.html'));
});

// server start!!
app.listen(port, () => {
    console.log(port, '서버가 켜졌습니다.');
});

 


3탄에 걸쳐서 회원가입, 로그인, 게시글CRUD, 댓글CRUD, 좋아요까지 끝이 났다.

마지막 4탄에서는 이를 바탕으로 레이어드 아키텍쳐 패턴을 적용해보려한다.

 

3탄 끝~~

728x90