diff --git a/src/controllers/alarm.controller.js b/src/controllers/alarm.controller.js index 449d65b..0b1d2f6 100644 --- a/src/controllers/alarm.controller.js +++ b/src/controllers/alarm.controller.js @@ -1,3 +1,4 @@ +import { BadRequestError } from "../errors/custom.error.js"; import * as AlarmService from "../services/alarm.service.js"; // 알람 목록 조회 @@ -16,3 +17,40 @@ export const handleAlarmList = async (req, res) => { return res.success(result, "알림 목록 조회 성공"); }; + +// 개별 알림 삭제 +export const handleAlarmDelete = async (req, res) => { + // userId (임시 - 로그인 미들웨어 생성 후 req.user.id 사용) + const userId = req.query.userId ? parseInt(req.query.userId) : 1; + + // alarmId 파라미터 파싱 및 검증 + const alarmId = req.params.alarmId; + + // 400 에러: 파라미터가 숫자가 아닌 경우 + if (!alarmId || isNaN(parseInt(alarmId))) { + throw new BadRequestError( + "INVALID_PARAMETER", + "params는 숫자로 보내야합니다." + ); + } + + const parsedAlarmId = parseInt(alarmId); + + // Service 호출 + await AlarmService.deleteAlarm(userId, parsedAlarmId); + + // 성공 응답 (data는 null) + return res.success(null, "개별 알림 삭제 성공"); +}; + +// 전체 알림 삭제 +export const handleAlarmDeleteAll = async (req, res) => { + // userId (임시 - 로그인 미들웨어 생성 후 req.user.id 사용) + const userId = req.query.userId ? parseInt(req.query.userId) : 1; + + // Service 호출 + await AlarmService.deleteAllAlarms(userId); + + // 성공 응답 (data는 null) + return res.success(null, "알림 전체 삭제 성공"); +}; diff --git a/src/repositories/alarm.repository.js b/src/repositories/alarm.repository.js index 6226fd5..82417a4 100644 --- a/src/repositories/alarm.repository.js +++ b/src/repositories/alarm.repository.js @@ -30,3 +30,28 @@ export const findAlarmsByUserId = async (userId, options = {}) => { return alarms; }; + +// 개별 알림 삭제 +export const deleteAlarmById = async (alarmId) => { + return await prisma.userAlarm.delete({ + where: { id: alarmId }, + }); +}; + +// 알림 존재 여부 및 소유자 확인 +export const findAlarmById = async (alarmId) => { + return await prisma.userAlarm.findUnique({ + where: { id: alarmId }, + select: { + id: true, + userId: true, + }, + }); +}; + +// 유저의 모든 알림 삭제 +export const deleteAllAlarmsByUserId = async (userId) => { + return await prisma.userAlarm.deleteMany({ + where: { userId }, + }); +}; diff --git a/src/routes/alarm.route.js b/src/routes/alarm.route.js index 60ff046..d9fc322 100644 --- a/src/routes/alarm.route.js +++ b/src/routes/alarm.route.js @@ -1,9 +1,18 @@ import express from "express"; -import { handleAlarmList } from "../controllers/alarm.controller.js"; +import { + handleAlarmDelete, + handleAlarmDeleteAll, + handleAlarmList, +} from "../controllers/alarm.controller.js"; const router = express.Router(); // GET /v1/api/alarm - 알람 목록 조회 router.get("/", handleAlarmList); +// DELETE /v1/api/alarm/:alarmId - 개별 알림 삭제 +router.delete("/:alarmId", handleAlarmDelete); +// DELETE /v1/api/alarm - 전체 알림 삭제 (모든 알림) +router.delete("/", handleAlarmDeleteAll); + export default router; diff --git a/src/services/alarm.service.js b/src/services/alarm.service.js index c23a1fc..85355ad 100644 --- a/src/services/alarm.service.js +++ b/src/services/alarm.service.js @@ -1,4 +1,9 @@ -import { findAlarmsByUserId } from "../repositories/alarm.repository.js"; +import { + deleteAlarmById, + findAlarmsByUserId, + findAlarmById, + deleteAllAlarmsByUserId, +} from "../repositories/alarm.repository.js"; import { alarmListResponseDTO } from "../dtos/alarm.dto.js"; import { NotFoundError, ForbiddenError } from "../errors/custom.error.js"; import prisma from "../db.config.js"; @@ -23,7 +28,7 @@ export const getAlarms = async (userId, cursor, limit, orderBy, order) => { ); } - // 1. 알람 조회 + // 알람 조회 const alarms = await findAlarmsByUserId(userId, { cursor, limit, @@ -31,14 +36,14 @@ export const getAlarms = async (userId, cursor, limit, orderBy, order) => { order, }); - // 2. 페이징 로직 + // 페이징 로직 const effectiveLimit = limit || 7; const hasNextPage = alarms.length > effectiveLimit; const data = hasNextPage ? alarms.slice(0, effectiveLimit) : alarms; const nextCursor = hasNextPage && data.length > 0 ? data[data.length - 1].id : null; - // 3. DTO 변환 + // DTO 변환 const responseData = alarmListResponseDTO(data); return { @@ -49,3 +54,77 @@ export const getAlarms = async (userId, cursor, limit, orderBy, order) => { }, }; }; + +// 개별 알림 삭제 +export const deleteAlarm = async (userId, alarmId) => { + // 유저 검증 (임시 - 로그인 미들웨어 생성 후 삭제 예정) + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { + id: true, + deletedAt: true, + }, + }); + + if (!user) { + throw new NotFoundError("USER_NOT_FOUND", "사용자를 찾을 수 없습니다."); + } + + if (user.deletedAt !== null) { + throw new ForbiddenError( + "USER_DELETED", + "탈퇴한 유저는 알림을 삭제할 수 없습니다." + ); + } + + // 알림 존재 여부 확인 + const alarm = await findAlarmById(alarmId); + + if (!alarm) { + throw new NotFoundError( + "ALARM_NOT_FOUND", + "요청하신 alarmID가 DB에 존재하지 않습니다." + ); + } + + // 알림 소유자 확인 (본인의 알림인지) + if (alarm.userId !== userId) { + throw new ForbiddenError( + "ALARM_ACCESS_DENIED", + "해당 알림에 접근할 권한이 없습니다." + ); + } + + // 알림 삭제 + await deleteAlarmById(alarmId); + + return null; // 성공 시 data는 null +}; + +// 전체 알림 삭제 +export const deleteAllAlarms = async (userId) => { + // 유저 검증 (임시 - 로그인 미들웨어 생성 후 삭제 예정) + const user = await prisma.user.findUnique({ + where: { id: userId }, + select: { + id: true, + deletedAt: true, + }, + }); + + if (!user) { + throw new NotFoundError("USER_NOT_FOUND", "사용자를 찾을 수 없습니다."); + } + + if (user.deletedAt !== null) { + throw new ForbiddenError( + "USER_DELETED", + "탈퇴한 유저는 알림을 삭제할 수 없습니다." + ); + } + + // 유저의 모든 알림 삭제 + await deleteAllAlarmsByUserId(userId); + + return null; // 성공 시 data는 null +};