Skip to content

Commit fb1cafc

Browse files
Merge pull request #111 from DevKor-github/develop
#87 ~ #110 반영해 배포
2 parents 105dd87 + 44d3d41 commit fb1cafc

67 files changed

Lines changed: 4159 additions & 171 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

build.gradle

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ dependencies {
5858
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
5959

6060
implementation 'org.springframework.boot:spring-boot-starter-security'
61+
testImplementation 'org.springframework.security:spring-security-test' // 보안 테스트를 위한 의존성 추가
6162

6263
// AWS SDK BOM, S3 SDK + Presigner
6364
implementation platform('software.amazon.awssdk:bom:2.27.21')
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
package org.devkor.apu.saerok_server.domain.collection.api;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.media.Content;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
7+
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
8+
import io.swagger.v3.oas.annotations.tags.Tag;
9+
import jakarta.annotation.security.PermitAll;
10+
import lombok.RequiredArgsConstructor;
11+
import org.devkor.apu.saerok_server.domain.collection.api.dto.request.CreateCollectionCommentRequest;
12+
import org.devkor.apu.saerok_server.domain.collection.api.dto.request.UpdateCollectionCommentRequest;
13+
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.*;
14+
import org.devkor.apu.saerok_server.domain.collection.application.CollectionCommentCommandService;
15+
import org.devkor.apu.saerok_server.domain.collection.application.CollectionCommentQueryService;
16+
import org.devkor.apu.saerok_server.global.security.principal.UserPrincipal;
17+
import org.springframework.http.HttpStatus;
18+
import org.springframework.security.access.prepost.PreAuthorize;
19+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
20+
import org.springframework.web.bind.annotation.*;
21+
22+
@Tag(name = "Collection Comment API", description = "컬렉션 댓글 관련 API")
23+
@RestController
24+
@RequiredArgsConstructor
25+
@RequestMapping("${api_prefix}/collections")
26+
public class CollectionCommentController {
27+
28+
private final CollectionCommentCommandService commentCommandService;
29+
private final CollectionCommentQueryService commentQueryService;
30+
31+
/* 댓글 작성 */
32+
@PostMapping("/{collectionId}/comments")
33+
@PreAuthorize("hasRole('USER')")
34+
@Operation(
35+
summary = "컬렉션 댓글 작성",
36+
security = @SecurityRequirement(name = "bearerAuth"),
37+
responses = {
38+
@ApiResponse(responseCode = "201", description = "댓글 작성 성공",
39+
content = @Content(schema = @Schema(implementation = CreateCollectionCommentResponse.class))),
40+
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
41+
@ApiResponse(responseCode = "401", description = "사용자 인증 실패", content = @Content),
42+
@ApiResponse(responseCode = "404", description = "컬렉션이 존재하지 않음", content = @Content)
43+
}
44+
)
45+
@ResponseStatus(HttpStatus.CREATED)
46+
public CreateCollectionCommentResponse createComment(
47+
@AuthenticationPrincipal UserPrincipal userPrincipal,
48+
@PathVariable Long collectionId,
49+
@RequestBody CreateCollectionCommentRequest request
50+
) {
51+
return commentCommandService.createComment(userPrincipal.getId(), collectionId, request);
52+
}
53+
54+
/* 댓글 수정 */
55+
@PatchMapping("/{collectionId}/comments/{commentId}")
56+
@PreAuthorize("hasRole('USER')")
57+
@Operation(
58+
summary = "컬렉션 댓글 수정",
59+
security = @SecurityRequirement(name = "bearerAuth"),
60+
responses = {
61+
@ApiResponse(responseCode = "200", description = "댓글 수정 성공",
62+
content = @Content(schema = @Schema(implementation = UpdateCollectionCommentResponse.class))),
63+
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
64+
@ApiResponse(responseCode = "401", description = "사용자 인증 실패", content = @Content),
65+
@ApiResponse(responseCode = "403", description = "권한 없음", content = @Content),
66+
@ApiResponse(responseCode = "404", description = "댓글 또는 컬렉션이 존재하지 않음", content = @Content)
67+
}
68+
)
69+
public UpdateCollectionCommentResponse updateComment(
70+
@AuthenticationPrincipal UserPrincipal userPrincipal,
71+
@PathVariable Long collectionId,
72+
@PathVariable Long commentId,
73+
@RequestBody UpdateCollectionCommentRequest request
74+
) {
75+
return commentCommandService.updateComment(userPrincipal.getId(), collectionId, commentId, request);
76+
}
77+
78+
/* 댓글 삭제 */
79+
@DeleteMapping("/{collectionId}/comments/{commentId}")
80+
@PreAuthorize("hasRole('USER')")
81+
@Operation(
82+
summary = "컬렉션 댓글 삭제",
83+
security = @SecurityRequirement(name = "bearerAuth"),
84+
responses = {
85+
@ApiResponse(responseCode = "204", description = "댓글 삭제 성공"),
86+
@ApiResponse(responseCode = "401", description = "사용자 인증 실패", content = @Content),
87+
@ApiResponse(responseCode = "403", description = "권한 없음", content = @Content),
88+
@ApiResponse(responseCode = "404", description = "댓글 또는 컬렉션이 존재하지 않음", content = @Content)
89+
}
90+
)
91+
@ResponseStatus(HttpStatus.NO_CONTENT)
92+
public void deleteComment(
93+
@AuthenticationPrincipal UserPrincipal userPrincipal,
94+
@PathVariable Long collectionId,
95+
@PathVariable Long commentId
96+
) {
97+
commentCommandService.deleteComment(userPrincipal.getId(), collectionId, commentId);
98+
}
99+
100+
/* 댓글 목록 */
101+
@GetMapping("/{collectionId}/comments")
102+
@PermitAll
103+
@Operation(
104+
summary = "컬렉션 댓글 목록 조회 (인증: optional)",
105+
security = @SecurityRequirement(name = "bearerAuth"),
106+
responses = {
107+
@ApiResponse(responseCode = "200", description = "댓글 목록 조회 성공",
108+
content = @Content(schema = @Schema(implementation = GetCollectionCommentsResponse.class))),
109+
@ApiResponse(responseCode = "404", description = "컬렉션이 존재하지 않음", content = @Content)
110+
}
111+
)
112+
public GetCollectionCommentsResponse listComments(
113+
@PathVariable Long collectionId,
114+
@AuthenticationPrincipal UserPrincipal userPrincipal
115+
) {
116+
Long userId = userPrincipal == null ? null : userPrincipal.getId();
117+
return commentQueryService.getComments(collectionId, userId);
118+
}
119+
120+
/* 댓글 개수 */
121+
@GetMapping("/{collectionId}/comments/count")
122+
@PermitAll
123+
@Operation(
124+
summary = "컬렉션 댓글 수 조회",
125+
responses = {
126+
@ApiResponse(responseCode = "200", description = "댓글 수 조회 성공",
127+
content = @Content(schema = @Schema(implementation = GetCollectionCommentCountResponse.class))),
128+
@ApiResponse(responseCode = "404", description = "컬렉션이 존재하지 않음", content = @Content)
129+
}
130+
)
131+
public GetCollectionCommentCountResponse getCommentCount(
132+
@PathVariable Long collectionId
133+
) {
134+
return commentQueryService.getCommentCount(collectionId);
135+
}
136+
}
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
package org.devkor.apu.saerok_server.domain.collection.api;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.Parameter;
5+
import io.swagger.v3.oas.annotations.media.Content;
6+
import io.swagger.v3.oas.annotations.media.Schema;
7+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
8+
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
9+
import io.swagger.v3.oas.annotations.tags.Tag;
10+
import lombok.RequiredArgsConstructor;
11+
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.LikeStatusResponse;
12+
import org.devkor.apu.saerok_server.domain.collection.application.CollectionCommentLikeCommandService;
13+
import org.devkor.apu.saerok_server.domain.collection.application.CollectionCommentLikeQueryService;
14+
import org.devkor.apu.saerok_server.global.security.principal.UserPrincipal;
15+
import org.springframework.security.access.prepost.PreAuthorize;
16+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
17+
import org.springframework.web.bind.annotation.*;
18+
19+
@Tag(name = "Collection Comment Like API", description = "컬렉션 댓글 좋아요 관련 API")
20+
@RestController
21+
@RequiredArgsConstructor
22+
@RequestMapping("${api_prefix}/collections/comments")
23+
public class CollectionCommentLikeController {
24+
25+
private final CollectionCommentLikeCommandService collectionCommentLikeCommandService;
26+
private final CollectionCommentLikeQueryService collectionCommentLikeQueryService;
27+
28+
@PostMapping("/{commentId}/like")
29+
@PreAuthorize("hasRole('USER')")
30+
@Operation(
31+
summary = "댓글 좋아요 토글",
32+
security = @SecurityRequirement(name = "bearerAuth"),
33+
description = "댓글에 좋아요를 추가하거나 제거합니다.",
34+
responses = {
35+
@ApiResponse(responseCode = "200", description = "좋아요 토글 성공",
36+
content = @Content(schema = @Schema(implementation = LikeStatusResponse.class))),
37+
@ApiResponse(responseCode = "401", description = "사용자 인증 실패", content = @Content),
38+
@ApiResponse(responseCode = "404", description = "사용자 또는 댓글이 존재하지 않음", content = @Content)
39+
}
40+
)
41+
public LikeStatusResponse toggleLike(
42+
@AuthenticationPrincipal UserPrincipal userPrincipal,
43+
@Parameter(description = "댓글 ID", example = "1")
44+
@PathVariable Long commentId
45+
) {
46+
return collectionCommentLikeCommandService.toggleLikeResponse(userPrincipal.getId(), commentId);
47+
}
48+
49+
@GetMapping("/{commentId}/like/status")
50+
@PreAuthorize("hasRole('USER')")
51+
@Operation(
52+
summary = "댓글 좋아요 상태 조회",
53+
security = @SecurityRequirement(name = "bearerAuth"),
54+
description = "로그인한 사용자의 특정 댓글 좋아요 상태를 조회합니다.",
55+
responses = {
56+
@ApiResponse(responseCode = "200", description = "좋아요 상태 조회 성공",
57+
content = @Content(schema = @Schema(implementation = LikeStatusResponse.class))),
58+
@ApiResponse(responseCode = "401", description = "사용자 인증 실패", content = @Content),
59+
@ApiResponse(responseCode = "404", description = "사용자 또는 댓글이 존재하지 않음", content = @Content)
60+
}
61+
)
62+
public LikeStatusResponse getLikeStatus(
63+
@AuthenticationPrincipal UserPrincipal userPrincipal,
64+
@Parameter(description = "댓글 ID", example = "1")
65+
@PathVariable Long commentId
66+
) {
67+
return collectionCommentLikeQueryService.getLikeStatusResponse(userPrincipal.getId(), commentId);
68+
}
69+
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package org.devkor.apu.saerok_server.domain.collection.api;
2+
3+
import io.swagger.v3.oas.annotations.Operation;
4+
import io.swagger.v3.oas.annotations.media.Content;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import io.swagger.v3.oas.annotations.responses.ApiResponse;
7+
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
8+
import io.swagger.v3.oas.annotations.tags.Tag;
9+
import lombok.RequiredArgsConstructor;
10+
import org.devkor.apu.saerok_server.domain.collection.api.dto.response.ReportCollectionCommentResponse;
11+
import org.devkor.apu.saerok_server.domain.collection.application.CollectionCommentReportCommandService;
12+
import org.devkor.apu.saerok_server.global.security.principal.UserPrincipal;
13+
import org.springframework.http.HttpStatus;
14+
import org.springframework.security.access.prepost.PreAuthorize;
15+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
16+
import org.springframework.web.bind.annotation.*;
17+
18+
@Tag(name = "Collection Comment Report API", description = "컬렉션 댓글 신고 관련 API")
19+
@RestController
20+
@RequiredArgsConstructor
21+
@RequestMapping("${api_prefix}/collections")
22+
public class CollectionCommentReportController {
23+
24+
private final CollectionCommentReportCommandService collectionCommentReportCommandService;
25+
26+
@PostMapping("/{collectionId}/comments/{commentId}/report")
27+
@PreAuthorize("hasRole('USER')")
28+
@Operation(
29+
summary = "컬렉션 댓글 신고",
30+
security = @SecurityRequirement(name = "bearerAuth"),
31+
responses = {
32+
@ApiResponse(responseCode = "201", description = "신고 접수 성공",
33+
content = @Content(schema = @Schema(implementation = ReportCollectionCommentResponse.class))),
34+
@ApiResponse(responseCode = "400", description = "잘못된 요청", content = @Content),
35+
@ApiResponse(responseCode = "401", description = "사용자 인증 실패", content = @Content),
36+
@ApiResponse(responseCode = "404", description = "댓글 또는 컬렉션이 존재하지 않음 (또는 이미 신고한 댓글)", content = @Content)
37+
}
38+
)
39+
@ResponseStatus(HttpStatus.CREATED)
40+
public ReportCollectionCommentResponse reportComment(
41+
@AuthenticationPrincipal UserPrincipal userPrincipal,
42+
@PathVariable Long collectionId,
43+
@PathVariable Long commentId
44+
) {
45+
return collectionCommentReportCommandService.reportComment(userPrincipal.getId(), collectionId, commentId);
46+
}
47+
}

src/main/java/org/devkor/apu/saerok_server/domain/collection/api/CollectionController.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,8 @@ public GetCollectionDetailResponse getCollectionDetail(
238238
- collectionId
239239
- imageUrl
240240
- koreanName
241+
- likeCount
242+
- commentCount
241243
242244
""",
243245
responses = {
@@ -366,7 +368,7 @@ public void deleteCollectionImage(
366368
@GetMapping("/nearby")
367369
@PermitAll
368370
@Operation(
369-
summary = "주위의 컬렉션 조회",
371+
summary = "주위의 컬렉션 조회 (인증: optional)",
370372
security = @SecurityRequirement(name = "bearerAuth"),
371373
description = """
372374
주위의 컬렉션을 조회합니다.

0 commit comments

Comments
 (0)