[박수훈] sprint5 리팩토링 및 sprint6 제출#9
Hidden character warning
[박수훈] sprint5 리팩토링 및 sprint6 제출#9mdeeno wants to merge 12 commits intocodeit-sprint-fullstack:express-박수훈from
Conversation
| this.name = this.constructor.name; | ||
| Error.captureStackTrace(this, this.constructor); | ||
| } | ||
| } |
There was a problem hiding this comment.
[Good] HttpException 계층 구조가 잘 설계되어 있습니다!
Error.captureStackTrace로 스택 트레이스를 깔끔하게 처리하고, statusCode, details 등 필요한 정보를 담을 수 있게 확장한 점이 좋습니다.
| CONFLICT: 409, | ||
|
|
||
| INTERNAL_SERVER_ERROR: 500, | ||
| }; |
There was a problem hiding this comment.
[Good] HTTP 상태 코드를 상수로 관리하는 좋은 패턴입니다!
매직 넘버 대신 HTTP_STATUS.CREATED, HTTP_STATUS.NOT_FOUND 등으로 사용하면 코드 가독성이 높아지고 오타로 인한 버그를 방지할 수 있습니다.
| return prisma.comment.findMany({ | ||
| where, | ||
| take: Number(take) || 10, | ||
| // Cursor 방식 페이지네이션 적용 |
There was a problem hiding this comment.
[Good] Cursor 기반 페이지네이션을 잘 구현했습니다!
skip: 1, cursor: { id: cursor }로 커서 이후 데이터를 가져오는 방식이 올바르게 적용되어 있습니다. 대량의 데이터에서 offset 방식보다 성능이 좋습니다.
| const pool = new Pool({ connectionString: process.env.DATABASE_URL }); | ||
| const adapter = new PrismaPg(pool); | ||
| const prisma = new PrismaClient({ adapter }); | ||
|
|
There was a problem hiding this comment.
[Good] 시딩 스크립트가 잘 작성되어 있습니다!
- 환경 체크로 프로덕션 실수 방지
- 트랜잭션으로 기존 데이터 초기화
- faker.js로 현실적인 더미 데이터 생성
- 적절한 에러 핸들링과 연결 해제
개발 환경 테스트에 매우 유용합니다.
|
|
||
| const adapter = new PrismaPg(pool); | ||
|
|
||
| export const prisma = new PrismaClient({ adapter }); |
There was a problem hiding this comment.
[Good] Prisma 설정이 깔끔합니다!
Pg Pool과 PrismaPg 어댑터를 활용한 PostgreSQL 연결 구성이 잘 되어 있습니다. 싱글톤 패턴으로 prisma 인스턴스를 export하여 앱 전체에서 재사용하는 것도 좋습니다.
| article Article? @relation(fields: [articleId], references: [id], onDelete: Cascade) | ||
|
|
||
| productId String? | ||
| product Product? @relation(fields: [productId], references: [id], onDelete: Cascade) |
There was a problem hiding this comment.
[Good] onDelete: Cascade 설정이 잘 되어 있습니다!
부모(Article/Product) 삭제 시 관련 댓글들이 자동으로 삭제되어 데이터 무결성이 유지됩니다. 고아 레코드 문제를 방지하는 좋은 설계입니다.
|
|
||
| // 게시글 등록 | ||
| function createArticle(data) { | ||
| return prisma.article.create({ data }); |
There was a problem hiding this comment.
[Good] Repository 패턴이 잘 적용되어 있습니다!
데이터베이스 접근 로직을 Repository로 분리하여:
- 라우터는 HTTP 요청/응답만 담당
- Repository는 데이터 조회/조작만 담당
이렇게 관심사를 분리하면 테스트와 유지보수가 쉬워집니다.
| PORT: z.coerce.number().min(1000).max(65535).default(5001), | ||
|
|
||
| DATABASE_URL: z.string().url(), | ||
| }); |
There was a problem hiding this comment.
[Good] Zod로 환경변수를 검증하는 좋은 패턴입니다!
서버 시작 시점에 환경변수 유효성을 체크하여, 런타임 에러를 방지할 수 있습니다. z.coerce.number()로 문자열을 숫자로 변환하는 것도 깔끔합니다.
| @@ -0,0 +1,53 @@ | |||
| import { prisma } from '#db/prisma.js'; | |||
There was a problem hiding this comment.
기존의 다른 파일들처럼 article.repository.ts와 같은 파일 네이밍을 유지하면 좋을 것 같습니다!
전체적으로 router, middleware, repo 들의 네이밍이 모두 컨벤션이 달라서 맞추면 좋을듯 합니다
|
|
||
| // 통과 | ||
| next(); | ||
| }; |
There was a problem hiding this comment.
[Suggestion] Zod를 활용한 유효성 검사 미들웨어로 개선할 수 있습니다!
현재 수동으로 각 필드를 검사하고 있는데, Zod 스키마를 활용하면 더 간결하고 재사용 가능합니다.
1. 스키마 정의:
// schemas/product.schema.js
import { z } from 'zod';
export const createProductSchema = z.object({
name: z.string().min(1, '이름은 필수입니다'),
description: z.string().min(1, '설명은 필수입니다'),
price: z.number().positive('가격은 0보다 커야 합니다'),
tags: z.array(z.string()).optional(),
});
// update는 모든 필드가 optional
export const updateProductSchema = createProductSchema.partial();2. 범용 validate 미들웨어:
// middleware/validate.js
import { z } from 'zod';
import { BadRequestException } from '../exceptions/BadRequsetException.js';
export const validate = (schema) => (req, res, next) => {
try {
req.body = schema.parse(req.body);
next();
} catch (error) {
if (error instanceof z.ZodError) {
throw new BadRequestException(
'Validation failed',
error.flatten().fieldErrors
);
}
next(error);
}
};3. 라우터에서 사용:
import { validate } from '../middleware/validate.js';
import { createProductSchema, updateProductSchema } from '../schemas/product.schema.js';
router.post('/', validate(createProductSchema), async (req, res) => {
// req.body는 이미 검증 및 타입 변환됨
const product = await productRepository.createProduct(req.body);
res.status(HTTP_STATUS.CREATED).json(product);
});
router.patch('/:id', validate(updateProductSchema), async (req, res) => {
// partial() 덕분에 일부 필드만 있어도 OK
});장점:
- 스키마 재사용 (
.partial(),.pick(),.extend()활용) - 타입 변환 자동화 (
z.coerce.number()등) - 상세한 에러 메시지 자동 생성
- 코드 중복 대폭 감소
| if (!exists) { | ||
| throw new NotFoundException(ERROR_MESSAGE.RESOURCE_NOT_FOUND); | ||
| } |
There was a problem hiding this comment.
인라인으로 if문을 작성하실때와 괄호를 사용하실때가 나뉘는 것 같은데 통일하면 코드 가독성이 더 오를 것 같습니다!
저는 항상 사용하는 편이 취향이긴합니다!
| @@ -0,0 +1,20 @@ | |||
| export default [ | |||
| js.configs.recommended, | |||
There was a problem hiding this comment.
[Critical] js가 import되지 않아 ReferenceError가 발생합니다.
// 상단에 추가
import js from '@eslint/js';
prisma/prisma.config.js
Outdated
| import { defineConfig, env } from 'prisma/config'; | ||
|
|
||
| export default defineConfig({ | ||
| schema: 'prisma/shema.prisma', |
There was a problem hiding this comment.
[Minor] 경로 오타
'prisma/shema.prisma' → 'prisma/schema.prisma'
| } | ||
|
|
||
| // 자유게시판 | ||
| model Article { |
There was a problem hiding this comment.
[Major] Article 스키마 필드 누락
체크리스트 요구사항에 따르면 image와 likeCount 필드가 필요합니다.
model Article {
id String @id @default(uuid())
title String
content String
image String? // 추가 필요
likeCount Int @default(0) // 좋아요순 정렬용
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
comments Comment[]
}
server.js
Outdated
| console.log(`\n${signal} 신호를 받았습니다. 서버를 종료합니다.`); | ||
| server.close(async () => { | ||
| console.log('HTTP 서버가 닫혔습니다.'); | ||
| await disconnectDB(); |
There was a problem hiding this comment.
[Critical] disconnectDB() 함수가 정의되지 않았습니다.
// 상단에 추가
import { prisma } from '#db/prisma.js';
// 여기서 수정
await prisma.$disconnect();
src/config/config.js
Outdated
| }); | ||
| } catch (error) { | ||
| if (error instanceof z.ZodError) { | ||
| const { fieldErrors } = flateenError(error); |
There was a problem hiding this comment.
[Critical] 함수명/변수명 오타
flateenError→ Zod에는 이런 함수가 없습니다filedErrors→fieldErrors
if (error instanceof z.ZodError) {
const { fieldErrors } = error.flatten();
console.error('환경 변수 검증 실패:', fieldErrors);
}| if (error instanceof HttpException) { | ||
| return res.status(error.statusCode).json({ | ||
| message: error.message, | ||
| stack: config.ENV === 'development' ? error.stack : undefined, |
There was a problem hiding this comment.
[Critical] config가 import되지 않아 ReferenceError가 발생합니다.
// 상단에 추가
import { config } from '../config/config.js';그리고 config.ENV가 아니라 config.NODE_ENV입니다.
|
|
||
| return prisma.product.findMany({ | ||
| where, | ||
| orderBy: { |
There was a problem hiding this comment.
[Critical] 오타 2개
'dest'→'desc'(Prisma는 desc만 인식)- Line 33:
take: Number(take) || 0→ 기본값 0이면 아무것도 반환 안됨
return prisma.product.findMany({
where,
orderBy: {
createdAt: orderBy === 'recent' ? 'desc' : 'asc',
},
skip: Number(skip) || 0,
take: Number(take) || 10, // 0 → 10
});
src/routes/index.js
Outdated
| // 라우터 연결 | ||
| router.use('/products', productRouter); | ||
| router.use('/articles', articleRouter); | ||
| router.use('/articles', commentRouter); |
There was a problem hiding this comment.
[Major] 댓글 라우터 경로 구조 문제
현재 중고마켓 댓글 경로가 /api/articles/products/:productId가 됩니다.
의도한 경로는 /api/products/:productId/comments일 것 같습니다.
현재:
router.use('/products', productRouter);
router.use('/articles', articleRouter);
router.use('/articles', commentRouter); // 모든 댓글이 /articles 하위해결 방안:
- 댓글 라우터를 별도 경로로 분리
- 또는 각 리소스 라우터 내에서 댓글 처리
| @@ -0,0 +1,31 @@ | |||
| // 유효성 검사 | |||
|
|
|||
| import { BadRequestException } from '../errors/httpException.js'; | |||
There was a problem hiding this comment.
[Critical] import 경로가 잘못되었습니다.
../errors/httpException.js 경로는 존재하지 않습니다.
// 수정
import { BadRequestException } from '../exceptions/BadRequsetException.js';
1. 스프린트 미션 요구사항
기본 요구사항
중고마켓
공통
자유게시판
댓글
2. 주요 변경사항
3. 멘토님에게 남길 메시지