Skip to content

Commit 771569d

Browse files
authored
Merge pull request #353 from TTORANG/dev
[Deploy][조이] 배포
2 parents a2e8f67 + 0aa9543 commit 771569d

5 files changed

Lines changed: 212 additions & 1 deletion

File tree

src/controllers/comment.controller.js

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,10 +7,12 @@ import {
77
videoCommentResponseDTO,
88
videoCommentListResponseDTO,
99
} from "../dtos/comment.dto.js";
10+
import { getShareCommentsResponseDTO } from "../dtos/shareLink.dto.js";
1011
import {
1112
createSlideComment,
1213
createVideoComment,
1314
deleteComment,
15+
getAllVideoComments,
1416
getSlideComments,
1517
getVideoCommentsByTimestamp,
1618
updateComment,
@@ -843,6 +845,120 @@ export const getVideoCommentsByTimestampController = async (req, res, next) => {
843845
}
844846
};
845847

848+
// 영상 전체 댓글 조회
849+
export const getAllVideoCommentsController = async (req, res, next) => {
850+
/**
851+
* @swagger
852+
* /videos/{videoId}/comments/all:
853+
* get:
854+
* summary: 영상 전체 댓글 목록 조회
855+
* description: |
856+
* 특정 영상에 달린 댓글/답글 전체를 조회합니다.
857+
* tags: [Comment]
858+
* security:
859+
* - bearerAuth: []
860+
* parameters:
861+
* - in: path
862+
* name: videoId
863+
* required: true
864+
* schema:
865+
* type: string
866+
* example: "456"
867+
* description: 영상 ID
868+
* responses:
869+
* 200:
870+
* description: 영상 전체 댓글 목록 조회 성공
871+
* content:
872+
* application/json:
873+
* schema:
874+
* $ref: "#/components/schemas/VideoAllCommentsResponse"
875+
* example:
876+
* resultType: "SUCCESS"
877+
* error: null
878+
* success:
879+
* comments:
880+
* - commentId: "1"
881+
* content: "이 부분 설명이 아주 좋네요!"
882+
* userId: "12"
883+
* isMine: true
884+
* writer: "가넷"
885+
* targetType: "video"
886+
* targetId: "456"
887+
* parentId: "2"
888+
* timestampMs: 12000
889+
* createdAt: "2026-02-10T15:00:00.000Z"
890+
* 400:
891+
* description: 잘못된 videoId
892+
* content:
893+
* application/json:
894+
* schema:
895+
* $ref: "#/components/schemas/ErrorResponse"
896+
* examples:
897+
* invalidVideoId:
898+
* value:
899+
* resultType: "FAILURE"
900+
* error:
901+
* errorCode: "P001"
902+
* reason: "videoId가 올바르지 않습니다."
903+
* data:
904+
* videoId: "abc"
905+
* success: null
906+
* 401:
907+
* description: 인증 실패 (JWT 누락/만료)
908+
* content:
909+
* application/json:
910+
* schema:
911+
* $ref: "#/components/schemas/ErrorResponse"
912+
* examples:
913+
* unauthorized:
914+
* value:
915+
* resultType: FAILURE
916+
* error:
917+
* errorCode: A004
918+
* reason: 인증 세션 정보가 없거나 유효하지 않습니다.
919+
* data: null
920+
* success: null
921+
* 404:
922+
* description: 영상 없음
923+
* content:
924+
* application/json:
925+
* schema:
926+
* $ref: "#/components/schemas/ErrorResponse"
927+
* examples:
928+
* videoNotFound:
929+
* value:
930+
* resultType: "FAILURE"
931+
* error:
932+
* errorCode: "V001"
933+
* reason: "영상을 찾을 수 없습니다."
934+
* data:
935+
* videoId: "456"
936+
* success: null
937+
* 500:
938+
* description: 서버 내부 오류
939+
* content:
940+
* application/json:
941+
* schema:
942+
* $ref: "#/components/schemas/ErrorResponse"
943+
*/
944+
try {
945+
const { videoId } = req.params;
946+
947+
const comments = await getAllVideoComments({ videoId });
948+
949+
res.status(200).json({
950+
resultType: "SUCCESS",
951+
error: null,
952+
success: getShareCommentsResponseDTO({
953+
comments,
954+
currentUserId: req.user?.id ?? null,
955+
}),
956+
});
957+
} catch (e) {
958+
next(e);
959+
}
960+
};
961+
846962
/**
847963
* @swagger
848964
* components:
@@ -1216,4 +1332,59 @@ export const getVideoCommentsByTimestampController = async (req, res, next) => {
12161332
* type: array
12171333
* items:
12181334
* $ref: "#/components/schemas/VideoCommentListItem"
1335+
*
1336+
* VideoAllCommentItem:
1337+
* type: object
1338+
* properties:
1339+
* commentId:
1340+
* type: string
1341+
* example: "1"
1342+
* content:
1343+
* type: string
1344+
* example: "이 부분 설명이 아주 좋네요!"
1345+
* userId:
1346+
* type: string
1347+
* example: "12"
1348+
* isMine:
1349+
* type: boolean
1350+
* example: true
1351+
* writer:
1352+
* type: string
1353+
* example: "가넷"
1354+
* targetType:
1355+
* type: string
1356+
* enum: [video]
1357+
* example: "video"
1358+
* targetId:
1359+
* type: string
1360+
* example: "456"
1361+
* parentId:
1362+
* type: string
1363+
* nullable: true
1364+
* example: "2"
1365+
* timestampMs:
1366+
* type: integer
1367+
* nullable: true
1368+
* example: 12000
1369+
* createdAt:
1370+
* type: string
1371+
* format: date-time
1372+
* example: "2026-02-10T15:00:00.000Z"
1373+
*
1374+
* VideoAllCommentsResponse:
1375+
* type: object
1376+
* properties:
1377+
* resultType:
1378+
* type: string
1379+
* example: SUCCESS
1380+
* error:
1381+
* nullable: true
1382+
* example: null
1383+
* success:
1384+
* type: object
1385+
* properties:
1386+
* comments:
1387+
* type: array
1388+
* items:
1389+
* $ref: "#/components/schemas/VideoAllCommentItem"
12191390
*/

src/dtos/shareLink.dto.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import { toPublicStorageUrl } from "../utils/storageUrl.util.js";
22

3-
const shareCommentItemDTO = (comment, currentUserId = null) => {
3+
export const shareCommentItemDTO = (comment, currentUserId = null) => {
44
const commentUserId = comment.userId.toString();
55
const currentUserIdStr = currentUserId ? currentUserId.toString() : null;
66

src/repositories/comment.repository.js

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,22 @@ export const findVideoCommentsByTimestamp = async ({ videoId, fromMs, toMs }) =>
108108
});
109109
};
110110

111+
export const findAllVideoComments = async ({ videoId }) => {
112+
return prisma.comment.findMany({
113+
where: {
114+
targetType: "video",
115+
targetId: videoId,
116+
isDeleted: false,
117+
},
118+
orderBy: { createdAt: "asc" },
119+
include: {
120+
user: {
121+
select: { id: true, nickName: true },
122+
},
123+
},
124+
});
125+
};
126+
111127
export const createVideoComment = async ({ projectId, userId, videoId, timestampMs, content }) => {
112128
return prisma.comment.create({
113129
data: {

src/routes/comment.route.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import express from "express";
22
import {
33
deleteCommentController,
4+
getAllVideoCommentsController,
45
getSlideCommentsController,
56
getVideoCommentsByTimestampController,
67
handleCreateVideoComment,
@@ -19,6 +20,7 @@ router.delete("/comments/:commentId", isLogin, deleteCommentController); // 댓
1920
router.get("/slides/:slideId/comments", isLogin, getSlideCommentsController); // 댓글 목록 조회
2021
router.post("/videos/:videoId/comments", isLogin, handleCreateVideoComment); // 영상 타임스탬프 댓글 생성
2122
router.get("/videos/:videoId/comments", isLogin, getVideoCommentsByTimestampController); // 시간대별 댓글 조회
23+
router.get("/videos/:videoId/comments/all", isLogin, getAllVideoCommentsController); // 영상 전체 댓글 조회
2224

2325
// 답글 관련 라우팅
2426
router.post("/comments/:commentId/replies", isLogin, postCommentReply); // 답글 작성

src/services/comment.service.js

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { InvalidParameterError, VideoNotFoundError } from "../errors/video.error
1111
import {
1212
createComment,
1313
createVideoComment as createVideoCommentRepo,
14+
findAllVideoComments,
1415
findCommentById,
1516
findCommentsBySlideId,
1617
findVideoCommentsByTimestamp,
@@ -206,3 +207,24 @@ export const getVideoCommentsByTimestamp = async ({
206207
toMs: Math.floor(ts / windowMs) * windowMs + windowMs,
207208
});
208209
};
210+
211+
// 영상 전체 댓글 조회
212+
export const getAllVideoComments = async ({ videoId }) => {
213+
let vid;
214+
try {
215+
vid = BigInt(videoId);
216+
} catch {
217+
throw new InvalidParameterError({ videoId }, "videoId가 올바르지 않습니다.");
218+
}
219+
220+
if (vid <= 0n) {
221+
throw new InvalidParameterError({ videoId }, "videoId가 올바르지 않습니다.");
222+
}
223+
224+
const videoExists = await findVideoByIdWithProject(vid);
225+
if (!videoExists) {
226+
throw new VideoNotFoundError({ videoId: String(videoId) });
227+
}
228+
229+
return findAllVideoComments({ videoId: vid });
230+
};

0 commit comments

Comments
 (0)