Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
38 changes: 38 additions & 0 deletions src/controllers/alarm.controller.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { BadRequestError } from "../errors/custom.error.js";
import * as AlarmService from "../services/alarm.service.js";

// 알람 목록 조회
Expand All @@ -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, "알림 전체 삭제 성공");
};
25 changes: 25 additions & 0 deletions src/repositories/alarm.repository.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 },
});
};
11 changes: 10 additions & 1 deletion src/routes/alarm.route.js
Original file line number Diff line number Diff line change
@@ -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;
87 changes: 83 additions & 4 deletions src/services/alarm.service.js
Original file line number Diff line number Diff line change
@@ -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";
Expand All @@ -23,22 +28,22 @@ export const getAlarms = async (userId, cursor, limit, orderBy, order) => {
);
}

// 1. 알람 조회
// 알람 조회
const alarms = await findAlarmsByUserId(userId, {
cursor,
limit,
orderBy,
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 {
Expand All @@ -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
};