개발자 도전기/[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/
728x90
반응형
LIST