Skip to content

Commit b664e0f

Browse files
authored
Merge branch 'dev' into Refactor/142
2 parents 683e1f0 + 5c6666b commit b664e0f

File tree

6 files changed

+796
-4
lines changed

6 files changed

+796
-4
lines changed

src/main/java/com/back/domain/board/controller/PostController.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,4 +65,35 @@ public ResponseEntity<RsData<PostDetailResponse>> getPost(
6565
response
6666
));
6767
}
68+
69+
// 게시글 수정
70+
@PutMapping("/{postId}")
71+
public ResponseEntity<RsData<PostResponse>> updatePost(
72+
@PathVariable Long postId,
73+
@RequestBody @Valid PostRequest request,
74+
@AuthenticationPrincipal CustomUserDetails user
75+
) {
76+
PostResponse response = postService.updatePost(postId, request, user.getUserId());
77+
return ResponseEntity
78+
.status(HttpStatus.OK)
79+
.body(RsData.success(
80+
"게시글이 수정되었습니다.",
81+
response
82+
));
83+
}
84+
85+
// 게시글 삭제
86+
@DeleteMapping("/{postId}")
87+
public ResponseEntity<RsData<Void>> deletePost(
88+
@PathVariable Long postId,
89+
@AuthenticationPrincipal CustomUserDetails user
90+
) {
91+
postService.deletePost(postId, user.getUserId());
92+
return ResponseEntity
93+
.status(HttpStatus.OK)
94+
.body(RsData.success(
95+
"게시글이 삭제되었습니다.",
96+
null
97+
));
98+
}
6899
}

src/main/java/com/back/domain/board/controller/PostControllerDocs.java

Lines changed: 263 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,4 +294,267 @@ ResponseEntity<RsData<PageResponse<PostListResponse>>> getPosts(
294294
ResponseEntity<RsData<PostDetailResponse>> getPost(
295295
@PathVariable Long postId
296296
);
297+
298+
@Operation(
299+
summary = "게시글 수정",
300+
description = "로그인한 사용자가 자신의 게시글을 수정합니다."
301+
)
302+
@ApiResponses({
303+
@ApiResponse(
304+
responseCode = "200",
305+
description = "게시글 수정 성공",
306+
content = @Content(
307+
mediaType = "application/json",
308+
examples = @ExampleObject(value = """
309+
{
310+
"success": true,
311+
"code": "SUCCESS_200",
312+
"message": "게시글이 수정되었습니다.",
313+
"data": {
314+
"postId": 101,
315+
"author": {
316+
"id": 5,
317+
"nickname": "홍길동"
318+
},
319+
"title": "수정된 게시글",
320+
"content": "안녕하세요, 수정했습니다!",
321+
"categories": [
322+
{ "id": 1, "name": "공지사항" },
323+
{ "id": 2, "name": "자유게시판" }
324+
],
325+
"createdAt": "2025-09-22T10:30:00",
326+
"updatedAt": "2025-09-22T10:30:00"
327+
}
328+
}
329+
""")
330+
)
331+
),
332+
@ApiResponse(
333+
responseCode = "400",
334+
description = "잘못된 요청 (필드 누락 등)",
335+
content = @Content(
336+
mediaType = "application/json",
337+
examples = @ExampleObject(value = """
338+
{
339+
"success": false,
340+
"code": "COMMON_400",
341+
"message": "잘못된 요청입니다.",
342+
"data": null
343+
}
344+
""")
345+
)
346+
),
347+
@ApiResponse(
348+
responseCode = "401",
349+
description = "인증 실패 (토큰 없음/만료/잘못됨)",
350+
content = @Content(
351+
mediaType = "application/json",
352+
examples = {
353+
@ExampleObject(name = "토큰 없음", value = """
354+
{
355+
"success": false,
356+
"code": "AUTH_001",
357+
"message": "인증이 필요합니다.",
358+
"data": null
359+
}
360+
"""),
361+
@ExampleObject(name = "잘못된 토큰", value = """
362+
{
363+
"success": false,
364+
"code": "AUTH_002",
365+
"message": "유효하지 않은 액세스 토큰입니다.",
366+
"data": null
367+
}
368+
"""),
369+
@ExampleObject(name = "만료된 토큰", value = """
370+
{
371+
"success": false,
372+
"code": "AUTH_004",
373+
"message": "만료된 액세스 토큰입니다.",
374+
"data": null
375+
}
376+
""")
377+
}
378+
)
379+
),
380+
@ApiResponse(
381+
responseCode = "403",
382+
description = "권한 없음 (작성자 아님)",
383+
content = @Content(
384+
mediaType = "application/json",
385+
examples = @ExampleObject(value = """
386+
{
387+
"success": false,
388+
"code": "POST_002",
389+
"message": "게시글 작성자만 수정/삭제할 수 있습니다.",
390+
"data": null
391+
}
392+
""")
393+
)
394+
),
395+
@ApiResponse(
396+
responseCode = "404",
397+
description = "존재하지 않는 게시글 또는 카테고리",
398+
content = @Content(
399+
mediaType = "application/json",
400+
examples = {
401+
@ExampleObject(name = "존재하지 않는 게시글", value = """
402+
{
403+
"success": false,
404+
"code": "POST_001",
405+
"message": "존재하지 않는 게시글입니다.",
406+
"data": null
407+
}
408+
"""),
409+
@ExampleObject(name = "존재하지 않는 카테고리", value = """
410+
{
411+
"success": false,
412+
"code": "POST_003",
413+
"message": "존재하지 않는 카테고리입니다.",
414+
"data": null
415+
}
416+
""")
417+
}
418+
)
419+
),
420+
@ApiResponse(
421+
responseCode = "500",
422+
description = "서버 내부 오류",
423+
content = @Content(
424+
mediaType = "application/json",
425+
examples = @ExampleObject(value = """
426+
{
427+
"success": false,
428+
"code": "COMMON_500",
429+
"message": "서버 오류가 발생했습니다.",
430+
"data": null
431+
}
432+
""")
433+
)
434+
)
435+
})
436+
ResponseEntity<RsData<PostResponse>> updatePost(
437+
@PathVariable Long postId,
438+
@RequestBody PostRequest request,
439+
@AuthenticationPrincipal CustomUserDetails user
440+
);
441+
442+
@Operation(
443+
summary = "게시글 삭제",
444+
description = "로그인한 사용자가 자신의 게시글을 삭제합니다."
445+
)
446+
@ApiResponses({
447+
@ApiResponse(
448+
responseCode = "200",
449+
description = "게시글 삭제 성공",
450+
content = @Content(
451+
mediaType = "application/json",
452+
examples = @ExampleObject(value = """
453+
{
454+
"success": true,
455+
"code": "SUCCESS_200",
456+
"message": "게시글이 삭제되었습니다.",
457+
"data": null
458+
}
459+
""")
460+
)
461+
),
462+
@ApiResponse(
463+
responseCode = "400",
464+
description = "잘못된 요청 (필드 누락 등)",
465+
content = @Content(
466+
mediaType = "application/json",
467+
examples = @ExampleObject(value = """
468+
{
469+
"success": false,
470+
"code": "COMMON_400",
471+
"message": "잘못된 요청입니다.",
472+
"data": null
473+
}
474+
""")
475+
)
476+
),
477+
@ApiResponse(
478+
responseCode = "401",
479+
description = "인증 실패 (토큰 없음/만료/잘못됨)",
480+
content = @Content(
481+
mediaType = "application/json",
482+
examples = {
483+
@ExampleObject(name = "토큰 없음", value = """
484+
{
485+
"success": false,
486+
"code": "AUTH_001",
487+
"message": "인증이 필요합니다.",
488+
"data": null
489+
}
490+
"""),
491+
@ExampleObject(name = "잘못된 토큰", value = """
492+
{
493+
"success": false,
494+
"code": "AUTH_002",
495+
"message": "유효하지 않은 액세스 토큰입니다.",
496+
"data": null
497+
}
498+
"""),
499+
@ExampleObject(name = "만료된 토큰", value = """
500+
{
501+
"success": false,
502+
"code": "AUTH_004",
503+
"message": "만료된 액세스 토큰입니다.",
504+
"data": null
505+
}
506+
""")
507+
}
508+
)
509+
),
510+
@ApiResponse(
511+
responseCode = "403",
512+
description = "권한 없음 (작성자 아님)",
513+
content = @Content(
514+
mediaType = "application/json",
515+
examples = @ExampleObject(value = """
516+
{
517+
"success": false,
518+
"code": "POST_002",
519+
"message": "게시글 작성자만 수정/삭제할 수 있습니다.",
520+
"data": null
521+
}
522+
""")
523+
)
524+
),
525+
@ApiResponse(
526+
responseCode = "404",
527+
description = "존재하지 않는 게시글",
528+
content = @Content(
529+
mediaType = "application/json",
530+
examples = @ExampleObject(value = """
531+
{
532+
"success": false,
533+
"code": "POST_001",
534+
"message": "존재하지 않는 게시글입니다.",
535+
"data": null
536+
}
537+
""")
538+
)
539+
),
540+
@ApiResponse(
541+
responseCode = "500",
542+
description = "서버 내부 오류",
543+
content = @Content(
544+
mediaType = "application/json",
545+
examples = @ExampleObject(value = """
546+
{
547+
"success": false,
548+
"code": "COMMON_500",
549+
"message": "서버 오류가 발생했습니다.",
550+
"data": null
551+
}
552+
""")
553+
)
554+
)
555+
})
556+
ResponseEntity<RsData<Void>> deletePost(
557+
@PathVariable Long postId,
558+
@AuthenticationPrincipal CustomUserDetails user
559+
);
297560
}

src/main/java/com/back/domain/board/entity/Post.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ public Post(User user, String title, String content) {
4141
}
4242

4343
// -------------------- 비즈니스 메서드 --------------------
44+
// 게시글 업데이트
45+
public void update(String title, String content) {
46+
this.title = title;
47+
this.content = content;
48+
}
49+
4450
// 카테고리 업데이트
4551
public void updateCategories(List<PostCategory> categories) {
4652
this.postCategoryMappings.clear();

src/main/java/com/back/domain/board/service/PostService.java

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,4 +80,55 @@ public PostDetailResponse getPost(Long postId) {
8080
// 응답 반환
8181
return PostDetailResponse.from(post);
8282
}
83+
84+
/**
85+
* 게시글 수정 서비스
86+
* 1. Post 조회
87+
* 2. 작성자 검증
88+
* 3. Post 업데이트 (제목, 내용, 카테고리)
89+
* 4. PostResponse 반환
90+
*/
91+
public PostResponse updatePost(Long postId, PostRequest request, Long userId) {
92+
// Post 조회
93+
Post post = postRepository.findById(postId)
94+
.orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND));
95+
96+
// 작성자 검증
97+
if (!post.getUser().getId().equals(userId)) {
98+
throw new CustomException(ErrorCode.POST_NO_PERMISSION);
99+
}
100+
101+
// Post 업데이트
102+
post.update(request.title(), request.content());
103+
104+
// Category 매핑 업데이트
105+
List<PostCategory> categories = postCategoryRepository.findAllById(request.categoryIds());
106+
if (categories.size() != request.categoryIds().size()) {
107+
throw new CustomException(ErrorCode.CATEGORY_NOT_FOUND);
108+
}
109+
post.updateCategories(categories);
110+
111+
// 응답 반환
112+
return PostResponse.from(post);
113+
}
114+
115+
/**
116+
* 게시글 삭제 서비스
117+
* 1. Post 조회
118+
* 2. 작성자 검증
119+
* 3. Post 삭제
120+
*/
121+
public void deletePost(Long postId, Long userId) {
122+
// Post 조회
123+
Post post = postRepository.findById(postId)
124+
.orElseThrow(() -> new CustomException(ErrorCode.POST_NOT_FOUND));
125+
126+
// 작성자 검증
127+
if (!post.getUser().getId().equals(userId)) {
128+
throw new CustomException(ErrorCode.POST_NO_PERMISSION);
129+
}
130+
131+
// Post 삭제
132+
postRepository.delete(post);
133+
}
83134
}

0 commit comments

Comments
 (0)