-
Notifications
You must be signed in to change notification settings - Fork 0
[feat] 게시판 도메인 구현 (게시글·댓글·멘션·검색) #117
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
03fe473
91051d7
c245aa9
0bc3fea
d9fcc66
3accc6e
c0dd15c
fa17b32
ccb3fa4
9c743e6
effc695
0e04469
094ed72
f377295
f02db3c
1da7e06
97e3c84
43fc174
952e483
a6c899c
2ccd465
a979fc8
18d2b1b
51fcb4c
691d404
039b29c
6ed95d5
7e59331
c7fda70
c9ed1c6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| package com.Coming.Backend.post.controller; | ||
|
|
||
| import com.Coming.Backend.post.dto.CommentLikeCountResponse; | ||
| import com.Coming.Backend.post.service.CommentService; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
| import org.springframework.web.bind.annotation.DeleteMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @Tag(name = "Comment") | ||
| @RestController | ||
| @RequestMapping("/api/comments") | ||
| @RequiredArgsConstructor | ||
| public class CommentController { | ||
|
|
||
| private final CommentService commentService; | ||
|
|
||
| @Operation(summary = "댓글 삭제") | ||
| @ApiResponse(responseCode = "403", description = "FORBIDDEN (작성자 본인 아님)") | ||
| @ApiResponse(responseCode = "404", description = "COMMENT_NOT_FOUND") | ||
| @DeleteMapping("/{commentId}") | ||
| public ResponseEntity<Void> delete( | ||
| @AuthenticationPrincipal Long userId, | ||
| @PathVariable Long commentId) { | ||
| commentService.delete(userId, commentId); | ||
| return ResponseEntity.ok().build(); | ||
| } | ||
|
|
||
| @Operation(summary = "댓글 좋아요") | ||
| @ApiResponse(responseCode = "404", description = "COMMENT_NOT_FOUND") | ||
| @ApiResponse(responseCode = "409", description = "ALREADY_LIKED") | ||
| @PostMapping("/{commentId}/like") | ||
| public ResponseEntity<CommentLikeCountResponse> like( | ||
| @AuthenticationPrincipal Long userId, | ||
| @PathVariable Long commentId) { | ||
| return ResponseEntity.ok(commentService.like(userId, commentId)); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Reject like mutations for soft-deleted comments.
When 🤖 Prompt for AI Agents |
||
| } | ||
|
|
||
| @Operation(summary = "댓글 좋아요 취소") | ||
| @ApiResponse(responseCode = "400", description = "NOT_LIKED") | ||
| @ApiResponse(responseCode = "404", description = "COMMENT_NOT_FOUND") | ||
| @DeleteMapping("/{commentId}/like") | ||
| public ResponseEntity<CommentLikeCountResponse> unlike( | ||
| @AuthenticationPrincipal Long userId, | ||
| @PathVariable Long commentId) { | ||
| return ResponseEntity.ok(commentService.unlike(userId, commentId)); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,41 @@ | ||
| package com.Coming.Backend.post.controller; | ||
|
|
||
| import com.Coming.Backend.common.response.PageResponse; | ||
| import com.Coming.Backend.post.dto.PostSummaryResponse; | ||
| import com.Coming.Backend.post.entity.EntityType; | ||
| import com.Coming.Backend.post.service.PostService; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.constraints.Max; | ||
| import jakarta.validation.constraints.Min; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.validation.annotation.Validated; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @Tag(name = "Post") | ||
| @Validated | ||
| @RestController | ||
| @RequestMapping("/api/entities") | ||
| @RequiredArgsConstructor | ||
| public class EntityPostController { | ||
|
|
||
| private final PostService postService; | ||
|
|
||
| @Operation(summary = "엔티티별 게시글 백링크 조회") | ||
| @ApiResponse(responseCode = "400", description = "INVALID_INPUT (허용되지 않은 sort 값)") | ||
| @GetMapping("/{type}/{id}/posts") | ||
| public ResponseEntity<PageResponse<PostSummaryResponse>> getBacklinks( | ||
| @PathVariable EntityType type, | ||
| @PathVariable Long id, | ||
| @RequestParam(defaultValue = "latest") String sort, | ||
| @RequestParam(defaultValue = "0") @Min(0) int page, | ||
| @RequestParam(defaultValue = "20") @Min(1) @Max(100) int size) { | ||
| return ResponseEntity.ok(postService.getBacklinks(type, id, sort, page, size)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,40 @@ | ||
| package com.Coming.Backend.post.controller; | ||
|
|
||
| import com.Coming.Backend.post.dto.EntityCardResponse; | ||
| import com.Coming.Backend.post.entity.EntityType; | ||
| import com.Coming.Backend.post.service.MentionService; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.constraints.Max; | ||
| import jakarta.validation.constraints.Min; | ||
| import jakarta.validation.constraints.NotBlank; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.validation.annotation.Validated; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Tag(name = "Post") | ||
| @Validated | ||
| @RestController | ||
| @RequestMapping("/api/mentions") | ||
| @RequiredArgsConstructor | ||
| public class MentionController { | ||
|
|
||
| private final MentionService mentionService; | ||
|
|
||
| @Operation(summary = "엔티티 검색 (게시글 본문 멘션 자동완성)") | ||
| @ApiResponse(responseCode = "400", description = "INVALID_INPUT (q 공백 또는 limit 범위 초과)") | ||
| @GetMapping("/search") | ||
| public ResponseEntity<List<EntityCardResponse>> search( | ||
| @RequestParam EntityType type, | ||
| @RequestParam @NotBlank String q, | ||
| @RequestParam(defaultValue = "10") @Min(1) @Max(20) int limit) { | ||
| return ResponseEntity.ok(mentionService.search(type, q, limit)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,57 @@ | ||
| package com.Coming.Backend.post.controller; | ||
|
|
||
| import com.Coming.Backend.common.response.PageResponse; | ||
| import com.Coming.Backend.post.dto.CommentCreateRequest; | ||
| import com.Coming.Backend.post.dto.CommentCreateResponse; | ||
| import com.Coming.Backend.post.dto.CommentResponse; | ||
| import com.Coming.Backend.post.service.CommentService; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.Max; | ||
| import jakarta.validation.constraints.Min; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
| import org.springframework.validation.annotation.Validated; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @Tag(name = "Comment") | ||
| @Validated | ||
| @RestController | ||
| @RequestMapping("/api/posts/{postId}/comments") | ||
| @RequiredArgsConstructor | ||
| public class PostCommentController { | ||
|
|
||
| private final CommentService commentService; | ||
|
|
||
| @Operation(summary = "게시글 댓글·답글 목록 조회") | ||
| @ApiResponse(responseCode = "404", description = "POST_NOT_FOUND") | ||
| @GetMapping | ||
| public ResponseEntity<PageResponse<CommentResponse>> getComments( | ||
| @PathVariable Long postId, | ||
| @AuthenticationPrincipal Long userId, | ||
| @RequestParam(defaultValue = "0") @Min(0) int page, | ||
| @RequestParam(defaultValue = "20") @Min(1) @Max(100) int size) { | ||
| return ResponseEntity.ok(commentService.getComments(postId, userId, page, size)); | ||
| } | ||
|
|
||
| @Operation(summary = "댓글 또는 답글 작성") | ||
| @ApiResponse(responseCode = "400", description = "INVALID_REPLY_DEPTH") | ||
| @ApiResponse(responseCode = "404", description = "POST_NOT_FOUND | COMMENT_NOT_FOUND") | ||
| @PostMapping | ||
| public ResponseEntity<CommentCreateResponse> create( | ||
| @PathVariable Long postId, | ||
| @AuthenticationPrincipal Long userId, | ||
| @Valid @RequestBody CommentCreateRequest request) { | ||
| return ResponseEntity.status(HttpStatus.CREATED).body(commentService.create(userId, postId, request)); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,129 @@ | ||
| package com.Coming.Backend.post.controller; | ||
|
|
||
| import com.Coming.Backend.common.response.PageResponse; | ||
| import com.Coming.Backend.post.dto.PostCreateRequest; | ||
| import com.Coming.Backend.post.dto.PostCreateResponse; | ||
| import com.Coming.Backend.post.dto.PostDetailResponse; | ||
| import com.Coming.Backend.post.dto.PostSummaryResponse; | ||
| import com.Coming.Backend.post.dto.PostUpdateRequest; | ||
| import com.Coming.Backend.post.dto.RecommendCountResponse; | ||
| import com.Coming.Backend.post.dto.TrendingTagResponse; | ||
| import com.Coming.Backend.post.entity.PostCategory; | ||
| import com.Coming.Backend.post.service.PostService; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponse; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.Valid; | ||
| import jakarta.validation.constraints.Max; | ||
| import jakarta.validation.constraints.Min; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.http.HttpStatus; | ||
| import org.springframework.http.ResponseEntity; | ||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
| import org.springframework.validation.annotation.Validated; | ||
| import org.springframework.web.bind.annotation.DeleteMapping; | ||
| import org.springframework.web.bind.annotation.GetMapping; | ||
| import org.springframework.web.bind.annotation.PatchMapping; | ||
| import org.springframework.web.bind.annotation.PathVariable; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RequestParam; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| import java.util.List; | ||
|
|
||
| @Tag(name = "Post") | ||
| @Validated | ||
| @RestController | ||
| @RequestMapping("/api/posts") | ||
| @RequiredArgsConstructor | ||
| public class PostController { | ||
|
|
||
| private final PostService postService; | ||
|
|
||
| @Operation(summary = "게시글 작성") | ||
| @PostMapping | ||
| public ResponseEntity<PostCreateResponse> create( | ||
| @AuthenticationPrincipal Long userId, | ||
| @Valid @RequestBody PostCreateRequest request) { | ||
| return ResponseEntity.status(HttpStatus.CREATED).body(postService.create(userId, request)); | ||
| } | ||
|
|
||
| @Operation(summary = "게시글 상세 조회") | ||
| @ApiResponse(responseCode = "404", description = "POST_NOT_FOUND") | ||
| @GetMapping("/{id}") | ||
| public ResponseEntity<PostDetailResponse> getDetail( | ||
| @PathVariable Long id, | ||
| @AuthenticationPrincipal Long userId) { | ||
| return ResponseEntity.ok(postService.getDetail(id, userId)); | ||
| } | ||
|
|
||
| @Operation(summary = "게시글 목록 조회") | ||
| @GetMapping | ||
| public ResponseEntity<PageResponse<PostSummaryResponse>> getList( | ||
| @RequestParam(required = false) PostCategory category, | ||
| @RequestParam(defaultValue = "0") @Min(0) int page, | ||
| @RequestParam(defaultValue = "20") @Min(1) @Max(100) int size) { | ||
| return ResponseEntity.ok(postService.getList(category, page, size)); | ||
| } | ||
|
|
||
| @Operation(summary = "이번 주 인기 게시글 조회") | ||
| @GetMapping("/popular") | ||
| public ResponseEntity<List<PostSummaryResponse>> getPopular( | ||
| @RequestParam(defaultValue = "7") @Min(1) int days, | ||
| @RequestParam(defaultValue = "5") @Min(1) @Max(100) int limit) { | ||
| return ResponseEntity.ok(postService.getPopular(days, limit)); | ||
| } | ||
|
|
||
| @Operation(summary = "최근 많이 언급된 태그 조회") | ||
| @GetMapping("/trending-tags") | ||
| public ResponseEntity<List<TrendingTagResponse>> getTrendingTags( | ||
| @RequestParam(defaultValue = "7") @Min(1) int days, | ||
| @RequestParam(defaultValue = "10") @Min(1) @Max(100) int limit) { | ||
| return ResponseEntity.ok(postService.getTrendingTags(days, limit)); | ||
| } | ||
|
|
||
| @Operation(summary = "게시글 수정") | ||
| @ApiResponse(responseCode = "403", description = "FORBIDDEN (작성자 본인 아님)") | ||
| @ApiResponse(responseCode = "404", description = "POST_NOT_FOUND") | ||
| @PatchMapping("/{id}") | ||
| public ResponseEntity<Void> update( | ||
| @AuthenticationPrincipal Long userId, | ||
| @PathVariable Long id, | ||
| @Valid @RequestBody PostUpdateRequest request) { | ||
| postService.update(userId, id, request); | ||
| return ResponseEntity.ok().build(); | ||
| } | ||
|
|
||
| @Operation(summary = "게시글 삭제") | ||
| @ApiResponse(responseCode = "403", description = "FORBIDDEN (작성자 본인 아님)") | ||
| @ApiResponse(responseCode = "404", description = "POST_NOT_FOUND") | ||
| @DeleteMapping("/{id}") | ||
| public ResponseEntity<Void> delete( | ||
| @AuthenticationPrincipal Long userId, | ||
| @PathVariable Long id) { | ||
| postService.delete(userId, id); | ||
| return ResponseEntity.ok().build(); | ||
| } | ||
|
|
||
| @Operation(summary = "게시글 추천") | ||
| @ApiResponse(responseCode = "404", description = "POST_NOT_FOUND") | ||
| @ApiResponse(responseCode = "409", description = "ALREADY_RECOMMENDED") | ||
| @PostMapping("/{id}/recommend") | ||
| public ResponseEntity<RecommendCountResponse> recommend( | ||
| @AuthenticationPrincipal Long userId, | ||
| @PathVariable Long id) { | ||
| return ResponseEntity.ok(postService.recommend(userId, id)); | ||
| } | ||
|
|
||
| @Operation(summary = "게시글 추천 취소") | ||
| @ApiResponse(responseCode = "400", description = "NOT_RECOMMENDED") | ||
| @ApiResponse(responseCode = "404", description = "POST_NOT_FOUND") | ||
| @DeleteMapping("/{id}/recommend") | ||
| public ResponseEntity<RecommendCountResponse> unrecommend( | ||
| @AuthenticationPrincipal Long userId, | ||
| @PathVariable Long id) { | ||
| return ResponseEntity.ok(postService.unrecommend(userId, id)); | ||
| } | ||
| } |
Uh oh!
There was an error while loading. Please reload this page.