민원 기능은 댓글보다 훨씬 복잡했다. 상태 관리(PENDING → IN_PROGRESS → COMPLETED), 비공개 처리, 알림 연동까지 고려할 게 많았다.
민원에는 3가지 상태가 있다.
PENDING(접수전) → IN_PROGRESS(처리중) → COMPLETED(처리완료)
상태에 따라 수정/삭제 가능 여부가 달라진다. 처리 중이거나 완료된 민원은 입주민이 수정하거나 삭제할 수 없다.
// 민원 수정 시 상태 체크
if (complaint.status !== 'PENDING')
throw new BadRequestError('처리 중이거나 완료된 민원은 수정할 수 없습니다.');
처음에는 이 로직을 controller에 넣을까 고민했는데, 비즈니스 규칙은 service에서 처리하는 게 맞다고 판단했다. controller는 요청을 받고 응답을 반환하는 역할이고, 실제 "언제 수정할 수 있는가"라는 규칙은 service 레이어가 알아야 할 내용이기 때문이다.
민원은 공개/비공개 설정이 가능하다. 비공개 민원은 작성자 본인과 관리자만 볼 수 있다.
목록 조회와 상세 조회 두 곳에서 처리가 달랐다.
목록 조회: DB 쿼리 단에서 필터링
// 입주민은 공개글 + 본인 글만 조회
if (!isAdmin) {
where.OR = [
{ isPublic: true },
{ authorId: requestUserId },
];
}
상세 조회: DB에서 가져온 후 서비스 레이어에서 체크
if (!complaint.isPublic && !isAdmin && complaint.authorId !== requestUserId) {
throw new ForbiddenError('비공개 민원은 작성자와 관리자만 열람 가능합니다.');
}
목록은 쿼리 레벨에서 막는 게 성능상 유리하고, 상세는 가져온 다음에 권한 체크를 하는 방식이 자연스럽다. 상황에 따라 어느 레이어에서 처리할지를 판단하는 것도 설계의 일부라는 걸 깨달았다.