Skip to content

Commit 798cd00

Browse files
committed
게시글 수정 및 게시글 삭제 기능 추가 후 third commit
1 parent e1fee85 commit 798cd00

10 files changed

Lines changed: 111 additions & 72 deletions

File tree

src/main/java/com/webservice/algorithmchef/config/GeminiConfig.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ public class GeminiConfig {
1313

1414
@Bean
1515
public Client geminiClient() {
16-
// Google Gen AI SDK V1 Client
16+
1717
return Client.builder()
1818
.apiKey(apiKey)
1919
.build();

src/main/java/com/webservice/algorithmchef/controller/BoardController.java

Lines changed: 55 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,7 @@ public class BoardController {
2424

2525
private final BoardService boardService;
2626

27-
/**
28-
* 1. 게시글 목록 조회
29-
* GET /board/posts
30-
*/
27+
// 게시글 목록 조회(게시판)
3128
@GetMapping("/posts")
3229
public ResponseEntity<BoardPostListResponse> getPostList(
3330
@RequestParam(defaultValue = "0") int page,
@@ -39,10 +36,7 @@ public ResponseEntity<BoardPostListResponse> getPostList(
3936
return ResponseEntity.ok(response);
4037
}
4138

42-
/**
43-
* 2. 새 게시글 작성
44-
* POST /board/post
45-
*/
39+
// 게시글 작성
4640
@PostMapping("/post")
4741
public ResponseEntity<Map<String, String>> createPost(
4842
@RequestBody BoardPostRequest requestDto,
@@ -55,13 +49,10 @@ public ResponseEntity<Map<String, String>> createPost(
5549
boardService.createPost(requestDto, userDetails.getUsername());
5650

5751
return ResponseEntity.status(HttpStatus.CREATED)
58-
.body(Map.of("message", "새 게시글 작성완료 되었습니다."));
52+
.body(Map.of("message", "게시글이 작성되었습니다."));
5953
}
6054

61-
/**
62-
* 3. 게시글 상세 조회
63-
* GET /board/post/{postId}
64-
*/
55+
// 게시글 조회
6556
@GetMapping("/post/{postId}")
6657
public ResponseEntity<BoardPostResponse> getPostDetail(
6758
@PathVariable Long postId,
@@ -77,10 +68,7 @@ public ResponseEntity<BoardPostResponse> getPostDetail(
7768
}
7869
}
7970

80-
/**
81-
* 6. 댓글 작성
82-
* POST /board/post/{postId}/comment
83-
*/
71+
// 댓글 작성
8472
@PostMapping("/post/{postId}/comment")
8573
public ResponseEntity<Map<String, String>> createComment(
8674
@PathVariable Long postId,
@@ -100,10 +88,7 @@ public ResponseEntity<Map<String, String>> createComment(
10088
}
10189
}
10290

103-
/**
104-
* 5. 대댓글 조회
105-
* GET /board/comments/{commentId}/replies
106-
*/
91+
// 대댓글 조회
10792
@GetMapping("/comments/{commentId}/replies")
10893
public ResponseEntity<CommentReplyListResponse> getReplies(
10994
@PathVariable Long commentId,
@@ -118,4 +103,53 @@ public ResponseEntity<CommentReplyListResponse> getReplies(
118103
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
119104
}
120105
}
106+
107+
// 게시글 수정
108+
@PutMapping("/post/{postId}")
109+
public ResponseEntity<Map<String, String>> updatePost(
110+
@PathVariable Long postId,
111+
@RequestBody BoardPostRequest requestDto,
112+
@AuthenticationPrincipal UserDetails userDetails
113+
) {
114+
if (userDetails == null) {
115+
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
116+
// 401 Unauthorized
117+
}
118+
119+
try {
120+
// userDetails.getUsername()은 JWT에서 추출한 userId(String)
121+
boardService.updatePost(postId, requestDto, userDetails.getUsername());
122+
123+
return ResponseEntity.ok(Map.of("message", "게시글이 수정되었습니다."));
124+
} catch (IllegalArgumentException e) {
125+
// 게시글이 없거나, 권한이 없는 경우
126+
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
127+
} catch (Exception e) {
128+
log.error("게시글 수정 오류", e);
129+
return ResponseEntity.internalServerError().body(Map.of("error", "서버 오류가 발생했습니다."));
130+
}
131+
}
132+
133+
// 게시글 삭제
134+
@DeleteMapping("/post/{postId}")
135+
public ResponseEntity<Map<String, String>> deletePost(
136+
@PathVariable Long postId,
137+
@AuthenticationPrincipal UserDetails userDetails
138+
) {
139+
if (userDetails == null) {
140+
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
141+
}
142+
143+
try {
144+
boardService.deletePost(postId, userDetails.getUsername());
145+
146+
return ResponseEntity.ok(Map.of("message", "게시글이 삭제되었습니다."));
147+
} catch (IllegalArgumentException e) {
148+
// 게시글이 없거나, 권한이 없는 경우
149+
return ResponseEntity.badRequest().body(Map.of("error", e.getMessage()));
150+
} catch (Exception e) {
151+
log.error("게시글 삭제 오류", e);
152+
return ResponseEntity.internalServerError().body(Map.of("error", "서버 오류가 발생했습니다."));
153+
}
154+
}
121155
}

src/main/java/com/webservice/algorithmchef/dto/board/BoardPostListResponse.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,9 @@
1313
@NoArgsConstructor
1414
@AllArgsConstructor
1515
@Builder
16+
// 게시글 목록 반환 객체
1617
public class BoardPostListResponse {
17-
// 목록 조회 시 반환할 래퍼 클래스
18+
// 목록 조회 시 반환할 Wrapper 클래스
1819
private List<BoardPostSimple> posts;
1920
private PageInfo pageInfo;
2021

src/main/java/com/webservice/algorithmchef/dto/board/BoardPostRequest.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,5 +20,4 @@ public class BoardPostRequest {
2020
// 게시글 내용 (DB에서는 BLOB이지만, JSON 전송 시 String으로 처리)
2121
private String content;
2222

23-
// 참고: user_id는 보통 Controller에서 @AuthenticationPrincipal을 통해 주입받으므로 DTO에서 제외합니다.
2423
}

src/main/java/com/webservice/algorithmchef/dto/board/CommentReplyListResponse.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
@NoArgsConstructor
1313
@AllArgsConstructor
1414
@Builder
15+
// 대댓글 목록 반환 객체
1516
public class CommentReplyListResponse {
1617
private List<CommentSimple> replies;
1718
private PageInfo pageInfo;

src/main/java/com/webservice/algorithmchef/model/BoardComment.java

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ public class BoardComment {
3131
@JoinColumn(name = "post_id", nullable = false)
3232
private BoardPost post;
3333

34-
// 댓글 작성자 (제공해주신 User 엔티티와 연결)
34+
// 댓글 작성자
3535
@ManyToOne(fetch = FetchType.LAZY)
3636
@JoinColumn(name = "user_id", nullable = false)
3737
private User user;
@@ -54,17 +54,15 @@ public class BoardComment {
5454
@Column(nullable = false, columnDefinition = "LONGTEXT")
5555
private String content;
5656

57-
// 삭제 여부 (Soft Delete)
57+
// 삭제 여부
5858
@Column(nullable = false)
5959
@ColumnDefault("false")
6060
private boolean isDeleted;
6161

62-
// 생성 시간 (Hibernate)
6362
@CreationTimestamp
6463
@Column(name = "created_at", updatable = false)
6564
private LocalDateTime createdAt;
6665

67-
// 수정 시간 (Hibernate)
6866
@UpdateTimestamp
6967
@Column(name = "modified_date")
7068
private LocalDateTime modifiedDate;
@@ -74,7 +72,7 @@ public void updateContent(String content) {
7472
this.content = content;
7573
}
7674

77-
// 댓글 삭제 처리 (상태값 변경)
75+
// 댓글 삭제 처리
7876
public void changeIsDeleted(boolean isDeleted) {
7977
this.isDeleted = isDeleted;
8078
}

src/main/java/com/webservice/algorithmchef/model/BoardPost.java

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -47,17 +47,15 @@ public class BoardPost {
4747
@Builder.Default
4848
private List<BoardComment> comments = new ArrayList<>();
4949

50-
// 생성 시간 (Hibernate)
5150
@CreationTimestamp
5251
@Column(name = "created_at", updatable = false)
5352
private LocalDateTime createdAt;
5453

55-
// 수정 시간 (Hibernate)
5654
@UpdateTimestamp
5755
@Column(name = "modified_date")
5856
private LocalDateTime modifiedDate;
5957

60-
// 게시글 수정 메서드 (Dirty Checking)
58+
// 게시글 수정 메서드 (Dirty Checking)
6159
public void update(String title, String content, String category) {
6260
this.title = title;
6361
this.content = content;

src/main/java/com/webservice/algorithmchef/repository/BoardCommentRepository.java

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -13,20 +13,10 @@
1313
@Repository
1414
public interface BoardCommentRepository extends JpaRepository<BoardComment, Long> {
1515

16-
/**
17-
* 특정 게시글(PostId)에 달린 모든 댓글을 조회합니다.
18-
* 대댓글 계층 구조를 서비스 로직에서 조립하기 위해,
19-
* 부모/자식 관계없이 해당 글의 모든 댓글을 작성일 순서(오래된 순)로 가져옵니다.
20-
*
21-
* @param postId 조회할 게시글의 ID
22-
* @return 해당 게시글의 댓글 리스트
23-
*/
16+
// 해당 postId에 달린 모든 댓글 조회
2417
List<BoardComment> findByPost_PostIdOrderByCreatedAtAsc(Long postId);
2518

26-
/**
27-
* 특정 게시글의 '최상위 댓글(부모가 없는 댓글)'만 페이징 조회합니다.
28-
* 게시글 상세 조회 시 댓글 목록 페이징에 사용됩니다.
29-
*/
19+
// 최상위 부모 댓글만 조회
3020
Page<BoardComment> findByPost_PostIdAndParentCommentIsNull(Long postId, Pageable pageable);
3121

3222
/**
@@ -36,13 +26,6 @@ public interface BoardCommentRepository extends JpaRepository<BoardComment, Long
3626
@Query("SELECT c FROM BoardComment c JOIN FETCH c.user WHERE c.post.postId = :postId ORDER BY c.createdAt ASC")
3727
List<BoardComment> findCommentsByPostIdWithUser(@Param("postId") Long postId);
3828

39-
/**
40-
* 특정 부모 댓글의 자식 댓글(대댓글)을 페이징하여 조회합니다.
41-
* 대댓글 더보기 기능 등에 사용됩니다.
42-
* * @param parentCommentId 부모 댓글 ID (CommentId)
43-
*
44-
* @param pageable 페이징 정보
45-
* @return 대댓글 Page 객체
46-
*/
29+
// 대댓글 페이징 및 조회
4730
Page<BoardComment> findByParentComment_CommentId(Long parentCommentId, Pageable pageable);
4831
}

src/main/java/com/webservice/algorithmchef/repository/BoardPostRepository.java

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,17 @@
33
import com.webservice.algorithmchef.model.BoardPost;
44
import org.springframework.data.domain.Page;
55
import org.springframework.data.domain.Pageable;
6+
import org.springframework.data.jpa.repository.EntityGraph;
67
import org.springframework.data.jpa.repository.JpaRepository;
78
import org.springframework.stereotype.Repository;
89

910
@Repository
1011
public interface BoardPostRepository extends JpaRepository<BoardPost, Long> {
11-
// 카테고리 필터링 + 페이징
12-
Page<BoardPost> findByCategory(String category, Pageable pageable);
1312

14-
// 전체 페이징 (필터 없을 때) - findAll(Pageable)은 JpaRepository에 기본 내장됨
13+
@EntityGraph(attributePaths = {"user"})
14+
Page<BoardPost> findAll(Pageable pageable);
15+
16+
// 카테고리별 조회
17+
@EntityGraph(attributePaths = {"user"})
18+
Page<BoardPost> findByCategory(String category, Pageable pageable);
1519
}

src/main/java/com/webservice/algorithmchef/service/BoardService.java

Lines changed: 38 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,7 @@ public class BoardService {
2828
private final BoardCommentRepository boardCommentRepository;
2929
private final UserRepository userRepository;
3030

31-
/**
32-
* 1. 게시글 목록 조회
33-
*/
31+
// 게시글 목록 조회(게시판)
3432
@Transactional(readOnly = true)
3533
public BoardPostListResponse getPostList(int page, int size, String sortStr, String filter) {
3634
Pageable pageable = createPageable(page, size, sortStr);
@@ -54,9 +52,7 @@ public BoardPostListResponse getPostList(int page, int size, String sortStr, Str
5452
.build();
5553
}
5654

57-
/**
58-
* 2. 새 게시글 작성
59-
*/
55+
// 게시글 작성
6056
@Transactional
6157
public void createPost(BoardPostRequest requestDto, String userId) {
6258
User user = userRepository.findByUserId(userId)
@@ -72,9 +68,7 @@ public void createPost(BoardPostRequest requestDto, String userId) {
7268
boardPostRepository.save(post);
7369
}
7470

75-
/**
76-
* 3. 게시글 상세 조회 (댓글 페이징 포함)
77-
*/
71+
// 게시글 조회
7872
@Transactional(readOnly = true)
7973
public BoardPostResponse getPostDetail(Long postId, int page, int size, String sortStr) {
8074
BoardPost post = boardPostRepository.findById(postId)
@@ -93,9 +87,7 @@ public BoardPostResponse getPostDetail(Long postId, int page, int size, String s
9387
return BoardPostResponse.of(post, commentDtos, pageInfo);
9488
}
9589

96-
/**
97-
* 6. 댓글 작성
98-
*/
90+
// 댓글 작성
9991
@Transactional
10092
public void createComment(Long postId, BoardCommentRequest requestDto, String userId) {
10193
User user = userRepository.findByUserId(userId)
@@ -126,20 +118,17 @@ public void createComment(Long postId, BoardCommentRequest requestDto, String us
126118
boardCommentRepository.save(comment);
127119
}
128120

129-
/**
130-
* 5. 대댓글 조회
131-
*/
121+
// 대댓글 조회
132122
@Transactional(readOnly = true)
133123
public CommentReplyListResponse getReplies(Long commentId, int page, int size, String sortStr) {
134-
// 부모 댓글 존재 여부 확인 (선택사항)
124+
// 조회 전, 부모 댓글 존재 여부 확인
135125
if (!boardCommentRepository.existsById(commentId)) {
136126
throw new IllegalArgumentException("해당 댓글이 존재하지 않습니다. commentId=" + commentId);
137127
}
138128

139129
Pageable pageable = createPageable(page, size, sortStr);
140130

141131
// 부모 댓글 ID를 기준으로 자식 댓글 페이징 조회
142-
// Repository에 findByParentComment_CommentId 메서드가 필요하다고 가정합니다.
143132
Page<BoardComment> replyPage = boardCommentRepository.findByParentComment_CommentId(commentId, pageable);
144133

145134
List<BoardPostResponse.CommentSimple> replyDtos = replyPage.getContent().stream()
@@ -154,6 +143,38 @@ public CommentReplyListResponse getReplies(Long commentId, int page, int size, S
154143
.build();
155144
}
156145

146+
// 게시글 수정
147+
@Transactional
148+
public void updatePost(Long postId, BoardPostRequest requestDto, String currentUserId) {
149+
// 1. 게시글 조회
150+
BoardPost post = boardPostRepository.findById(postId)
151+
.orElseThrow(() -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다. postId=" + postId));
152+
153+
// 2. 작성자 검증 (게시글 작성자 ID vs 현재 로그인한 유저 ID)
154+
if (!post.getUser().getUserId().equals(currentUserId)) {
155+
throw new IllegalArgumentException("게시글 수정 권한이 없습니다. 본인이 작성한 글만 수정 가능합니다.");
156+
}
157+
158+
// 3. 게시글 업데이트 (JPA Dirty Checking)
159+
post.update(requestDto.getTitle(), requestDto.getContent(), requestDto.getCategory());
160+
}
161+
162+
// 게시글 삭제
163+
@Transactional
164+
public void deletePost(Long postId, String currentUserId) {
165+
// 1. 게시글 조회
166+
BoardPost post = boardPostRepository.findById(postId)
167+
.orElseThrow(() -> new IllegalArgumentException("해당 게시글이 존재하지 않습니다. postId=" + postId));
168+
169+
// 2. 작성자 검증
170+
if (!post.getUser().getUserId().equals(currentUserId)) {
171+
throw new IllegalArgumentException("본인이 작성한 글만 삭제 가능합니다.");
172+
}
173+
174+
// 3. 게시글 삭제 (Cascade 옵션으로 인해 댓글도 자동 삭제됨)
175+
boardPostRepository.delete(post);
176+
}
177+
157178
// Pageable 생성 헬퍼 메서드
158179
private Pageable createPageable(int page, int size, String sortStr) {
159180
Sort sort = Sort.by(Sort.Direction.DESC, "createdAt"); // 기본값

0 commit comments

Comments
 (0)