개발자 도전기/[STUDY] JS || TS

ts-node | NodeBird | 라우터 만들기(3)

답수 2022. 5. 3. 02:02
728x90
반응형

 

 

※ 인프런 - Node.js에 TypeScript 적용하기(feat. NodeBire) by 조현영 강의를 기반으로 정리한 내용입니다.

 

routes/post.ts

저번에 이어서 계속 post라우터 만들어가자. image를 업로드 하는 라우터는 다음과 같다.

router.post('/images', upload.array('image'), (req, res) => {
    console.log(req.files);
    if (Array.isArray(req.files)) {
        res.json((req.files as Express.MulterS3.File[]).map((v) => v.location));
        /**
         * location이라는 메소드는 MulterS3 네임스페이스에만 존재하기 때문에 Multer를 MulterS3로 강제 형변환
         */
    }
});

 

해당 아이디의 게시글을 찾는 라우터

router.get('/:id', async (req, res, next) => {
    try {
        const post = await Post.findOne({
            where: { id: req.params.id },
            include: [{
                model: User,
                attributes: ['id', 'nickname'],
            }, {
                model: Image,
            }, {
                model: User,
                as: 'Likers',
                attributes: ['id'],
            }],
        });
        return res.json(post);
    } catch (err) {
        console.error(err);
        return next(err);
    }
});

 

게시글 삭제 라우터

outer.delete('/:id', isLoggedIn, async (req, res, next) => {       // 권한 체크는 미들웨어에서!
    try {
        // 작업 대상이 있는지 없는지 먼저 검사하는 습관 가지기
        const post = await Post.findOne({ where: { id: req.params.id } });
        if (!post) return res.status(404).send('포스트가 존재하지 않습니다.');
        await Post.destroy({ where: { id: req.params.id } });

        return res.send(req.params.id);
    } catch (err) {
        console.error(err);
        return next(err);
    }
});

 

다음은 댓글과 관련된 라우터들이다.

router.get('/:id/comments', async (req, res, next) => {
    try {
        const post = await Post.findOne({ where: { id: req.params.id } });
        if (!post) return res.status(404).send('포스트가 존재하지 않습니다.');
        const comments = await Comment.findAll({
            where: {
                PostId: req.params.id,
            },
            order: [['createdAt', 'ASC']],
            include: [{
                model: User,
                attributes: ['id', 'nickname'],
            }],
        });
        return res.json(comments);
    } catch (err) {
        console.error(err);
        return next(err);
    }
});
  
router.post('/:id/comment', isLoggedIn, async (req, res, next) => {
    try {
        const post = await Post.findOne({ where: { id: req.params.id } });
        if (!post) return res.status(404).send('포스트가 존재하지 않습니다.');
        const newComment = await Comment.create({
            PostId: post.id,
            UserId: req.user!.id,
            content: req.body.content,
        });
        const comment = await Comment.findOne({
            where: {
                id: newComment.id,
            },
            include: [{
                model: User,
                attributes: ['id', 'nickname'],
            }],
        });
        return res.json(comment);
    } catch (err) {
        console.error(err);
        return next(err);
    }
});

 

좋아요 기능 관련 라우터

router.post('/:id/like', isLoggedIn, async (req, res, next) => {
    try {
        const post = await Post.findOne({ where: { id: req.params.id } });
        if (!post) return res.status(404).send('포스트가 존재하지 않습니다.');
        await post.addLiker(req.user!.id);
        return res.json({ userId: req.user!.id });
    } catch (err) {
        console.error(err);
        next(err);
    }
});
  
router.delete('/:id/like', isLoggedIn, async (req, res, next) => {
    try {
        const post = await Post.findOne({ where: { id: req.params.id } });
        if (!post) return res.status(404).send('포스트가 존재하지 않습니다.');
        await post.removeLiker(req.user!.id);
        return res.json({ userId: req.user!.id });
    } catch (err) {
        console.error(err);
        next(err);
    }
});

 

리트윗 라우터

router.post('/:id/retweet', isLoggedIn, async (req, res, next) => {
    try {
        const post = await Post.findOne({
            where: { id: req.params.id },
            include: [{
                model: Post,
                as: 'Retweet',
            }],
        });
        if (!post) return res.status(404).send('포스트가 존재하지 않습니다.');
        if (req.user!.id === post.UserId || (post.Retweet && post.Retweet.UserId === req.user!.id)) {
            return res.status(403).send('자신의 글은 리트윗할 수 없습니다.');
        }
        const retweetTargetId = post.RetweetId || post.id;
        const exPost = await Post.findOne({
            where: {
                UserId: req.user!.id,
                RetweetId: retweetTargetId,
            },
        });
        if (exPost) return res.status(403).send('이미 리트윗했습니다.');
        const retweet = await Post.create({
            UserId: req.user!.id,
            RetweetId: retweetTargetId,
            content: 'retweet',
        });
        const retweetWithPrevPost = await Post.findOne({
            where: { id: retweet.id },
            include: [{
                model: User,
                attributes: ['id', 'nickname'],
            }, {
                model: Post,
                as: 'Retweet',
                include: [{
                    model: User,
                    attributes: ['id', 'nickname'],
                }, {
                    model: Image,
                }],
            }],
        });
        return res.json(retweetWithPrevPost);
    } catch (err) {
        console.error(err);
        next(err);
    }
});

 

routes/posts.ts

강사님은 단수, 복수를 구분하여 코드를 작성하신다고 한다. restAPI 디자인 가이드로는 단수를 사용하라고 하지만, 대부분 사람들은 restAPI를 정확히 지키지 못한다고 한다. 실제 현업에서도 이를 다 지키면서 하는 회사를 본 적이 없으시다고.. 그래서 완전히 규칙을 따르지 못할 바에는 내가 편한대로 쓰자! 라는 생각으로 단수와 복수를 나눈다고 하셨고, 데이터를 하나만 가져올 때와 여러 개를 가져올 때를 분리한다고 한다. 

* ref: https://meetup.toast.com/posts/92

 

import * as express from 'express';
import { Op, Sequelize } from 'sequelize';
import Image from '../models/image';
import Post from '../models/post';
import User from '../models/user';

const router = express.Router();

router.get('/', async (req, res, next) => {
    try {
        let where = {};
        if (parseInt(req.query.lastId as string, 10)) {
            where = {
                id: {
                    [Op.lt]: parseInt(req.query.lastId as string, 10),
                },
            };
        }
        const posts = await Post.findAll({
            where,
            include: [{
                model: User,
                attributes: ['id', 'nickname'], 
            }, {
                model: Image,
            }, {
                model: User,
                as: 'Likers',
                attributes: ['id'],
            }, {
                model: Post,
                as: 'Retweet',
                include: [{
                    model: User,
                    attributes: ['id', 'nickname'],
                }, {
                    model: Image,
                }]
            }],
            order: [['createAt', 'DESC']],  // 정렬 방법
            limit: parseInt(req.query.limit as string, 10),
        });
        return res.json(posts);
    } catch (err) {
        console.error(err);
        return next(err);
    }
});

export default router;

 

routes/hashtag.ts

다음은 해시태그와 관련된 라우터다.

import * as express from 'express';
import { Op, Sequelize } from 'sequelize';
import Hashtag from '../models/hashtag';
import Image from '../models/image';
import Post from '../models/post';
import User from '../models/user';

const router = express.Router();

router.get('/:tag', async (req, res, next) => {
    try {
        let where = {};
        if (parseInt(req.query.lastId as string, 10)) {
            where = {
                id: {
                    [Op.lt]: parseInt(req.query.lastId as string, 10),
                },
            };
        }
        const posts = await Post.findAll({
            where,
            include: [{
                model: Hashtag,
                where: { name: decodeURIComponent(req.params.tag) },
            }, {
                model: User,
                attributes: ['id', 'nickname'],
            }, {
                model: Image,
            }, {
                model: User,
                as: 'Likers',
                attributes: ['id'],
            }, {
                model: Post,
                as: 'Retweet',
                include: [{
                    model: User,
                    attributes: ['id', 'nickname'],
                }, {
                    model: Image,
                }]
            }],
            order: [['createdAt', 'DESC']],
            limit: parseInt(req.query.limit as string, 10),
        });
        res.json(posts);
    } catch (err) {
        console.error(err);
        return next(err);
    }
});

export default router;

 

여기서 보면 시퀄라이즈 모듈에 Op라는 것을 사용했다. 시퀄라이즈는 자바스크립트 Symbol 연산자를 사용하여 복잡한 비교 연산을 지원한다. 만약 [Op.lt]: 10 이면 10 미만의 자료들을 찾는다.

 

시퀄라이즈 버전 5까지는 Op가 시퀄라이즈의 메소드였지만, 버전 6부터는 하나의 변수로 따로 선언이 되어 있기 때문에 강의에서처럼 Sequelize.Op 이라고 하면 타입스크립트가 읽지 못한다.

 

* ref: https://sequelize.org/docs/v6/core-concepts/model-querying-basics/

 

Model Querying - Basics | Sequelize

Sequelize provides various methods to assist querying your database for data.

sequelize.org

 

 

728x90
반응형
LIST