Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ dependencies {

// Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'

implementation 'org.springframework.boot:spring-boot-starter-validation'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ public class ApiController {

private final ApiService apiService;

// 헬스체크
// 헬스 체크
@GetMapping("/health")
public String healthCheck() {
return "OK";
}

// 문자열 반복
// 문자열 2개로 복사
@PostMapping("/string/repeat")
public RepeatResponse repeat(@RequestBody Map<String, String> request) {
return apiService.repeat(request.get("text"));
Expand Down
1 change: 1 addition & 0 deletions src/main/java/com/example/demo/api/service/ApiService.java
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
@Service
public class ApiService {

//문자열 복사
public RepeatResponse repeat(String text) {
return RepeatResponse.builder()
.result(text + text)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,18 @@
package com.example.demo.comment.controller;

import com.example.demo.comment.dto.*;
import com.example.demo.comment.dto.CommentCreateRequest;
import com.example.demo.comment.dto.CommentResponse;
import com.example.demo.comment.dto.CommentUpdateRequest;
import com.example.demo.comment.service.CommentService;
import com.example.demo.global.exception.ApiResponse;
import com.example.demo.global.exception.BaseCode;
import com.example.demo.global.exception.ResponseUtil;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.Map;

@RestController
@RequiredArgsConstructor
Expand All @@ -14,28 +21,54 @@ public class CommentController {

private final CommentService commentService;

// 댓글 생성
//댓글 생성
@PostMapping
public Long create(@RequestBody CommentCreateRequest request) {
return commentService.createComment(request);
public ApiResponse<Map<String, Long>> create(
@RequestBody @Valid CommentCreateRequest request) {

Long commentId = commentService.createComment(request);

return ResponseUtil.success(
BaseCode.COMMENT_CREATE_SUCCESS,
Map.of("commentId", commentId)
);
}

// 특정 게시글 댓글 조회
//특정 게시글 댓글 조회
@GetMapping("/post/{postId}")
public List<CommentResponse> getComments(@PathVariable Long postId) {
return commentService.getComments(postId);
public ApiResponse<List<CommentResponse>> getComments(
@PathVariable Long postId) {

return ResponseUtil.success(
BaseCode.SUCCESS,
commentService.getComments(postId)
);
}

// 댓글 수정
//댓글 수정
@PutMapping("/{commentId}")
public void update(@PathVariable Long commentId,
@RequestBody CommentUpdateRequest request) {
public ApiResponse<Void> update(
@PathVariable Long commentId,
@RequestBody @Valid CommentUpdateRequest request) {

commentService.updateComment(commentId, request);

return ResponseUtil.success(
BaseCode.COMMENT_UPDATE_SUCCESS,
null
);
}

// 댓글 삭제
//댓글 삭제
@DeleteMapping("/{commentId}")
public void delete(@PathVariable Long commentId) {
public ApiResponse<Map<String, Long>> delete(
@PathVariable Long commentId) {

commentService.deleteComment(commentId);

return ResponseUtil.success(
BaseCode.COMMENT_DELETE_SUCCESS,
Map.of("commentId", commentId)
);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,18 @@
package com.example.demo.comment.dto;

import lombok.Getter;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

@Getter
public class CommentCreateRequest {

@NotNull(message = "유저 ID는 필수입니다.")
private Long userId;

@NotNull(message = "게시글 ID는 필수입니다.")
private Long postId;

@NotBlank(message = "댓글 내용은 비어 있을 수 없습니다.")
private String content;
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package com.example.demo.comment.dto;

import lombok.Getter;
import jakarta.validation.constraints.NotBlank;

@Getter
public class CommentUpdateRequest {

@NotBlank(message = "댓글 내용은 비어 있을 수 없습니다.")
private String content;
}
13 changes: 13 additions & 0 deletions src/main/java/com/example/demo/global/exception/ApiResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.example.demo.global.exception;

import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class ApiResponse<T> {
private boolean isSuccess;
private String code;
private String message;
private T result;
}
30 changes: 30 additions & 0 deletions src/main/java/com/example/demo/global/exception/BaseCode.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.example.demo.global.exception;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@Getter
@RequiredArgsConstructor
public enum BaseCode {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

모든 API 응답을 ApiResponse로 통일하고 BaseCode를 enum으로 코드와 메시지를 관리한 부분 잘 설계하신 것 같습니다!👍


// 공통
SUCCESS("2000", "성공"),
INVALID_REQUEST("4002", "잘못된 요청입니다."),

// user
USER_CREATE_SUCCESS("2001", "유저 생성 성공"),

// post
POST_CREATE_SUCCESS("2010", "게시글 생성 성공"),
POST_UPDATE_SUCCESS("2002", "게시글 수정 성공"),
POST_DELETE_SUCCESS("2003", "게시글 삭제 성공"),
POST_NOT_FOUND("4040", "해당 게시글을 찾을 수 없습니다."),

// comment
COMMENT_CREATE_SUCCESS("2011", "댓글 생성 성공"),
COMMENT_UPDATE_SUCCESS("2004", "댓글 수정 성공"),
COMMENT_DELETE_SUCCESS("2005", "댓글 삭제 성공");

private final String code;
private final String message;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.example.demo.global.exception;

import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.*;

import java.util.HashMap;
import java.util.Map;

@RestControllerAdvice
public class GlobalExceptionHandler {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Vaild 로 컨트롤러에서 활성화하고 유효성 검증 어노테이션(@NotNull, @notblank 등)을 적절히 활용한 부분 좋은 설계인 것 같습니다! 👍


// 검증 만족 못한 내용들
@ExceptionHandler(MethodArgumentNotValidException.class)
public ResponseEntity<ApiResponse<Map<String, String>>> handleValidation(
MethodArgumentNotValidException e) {

Map<String, String> errors = new HashMap<>();

e.getBindingResult().getFieldErrors()
.forEach(error ->
errors.put(error.getField(), error.getDefaultMessage())
);

return ResponseEntity.badRequest()
.body(ResponseUtil.fail(BaseCode.INVALID_REQUEST, errors));
}

// 게시글 없음
@ExceptionHandler(IllegalArgumentException.class)
public ResponseEntity<ApiResponse<Map<String, Object>>> handleIllegal(
IllegalArgumentException e) {

if (e.getMessage().contains("게시글 없음")) {
return ResponseEntity.status(404)
.body(ResponseUtil.fail(
BaseCode.POST_NOT_FOUND,
Map.of("postId", -1)
));
}

return ResponseEntity.badRequest()
.body(ResponseUtil.fail(BaseCode.INVALID_REQUEST, null));
}

// 기타 예외상황
@ExceptionHandler(Exception.class)
public ResponseEntity<ApiResponse<Void>> handleException() {
return ResponseEntity.internalServerError()
.body(ResponseUtil.fail(BaseCode.INVALID_REQUEST, null));
}
}
22 changes: 22 additions & 0 deletions src/main/java/com/example/demo/global/exception/ResponseUtil.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package com.example.demo.global.exception;

public class ResponseUtil {

public static <T> ApiResponse<T> success(BaseCode code, T result) {
return ApiResponse.<T>builder()
.isSuccess(true)
.code(code.getCode())
.message(code.getMessage())
.result(result)
.build();
}

public static <T> ApiResponse<T> fail(BaseCode code, T result) {
return ApiResponse.<T>builder()
.isSuccess(false)
.code(code.getCode())
.message(code.getMessage())
.result(result)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public class MediaService {
private final MediaRepository mediaRepository;
private final String uploadDir = "C:/upload/";

//upload 폴더에 이미지 파일 저장
public MediaResponse upload(MultipartFile file) {
try {
String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename();
Expand Down
51 changes: 48 additions & 3 deletions src/main/java/com/example/demo/post/controller/PostController.java
Original file line number Diff line number Diff line change
@@ -1,21 +1,66 @@
package com.example.demo.post.controller;

import com.example.demo.global.exception.ApiResponse;
import com.example.demo.global.exception.BaseCode;
import com.example.demo.global.exception.ResponseUtil;
import com.example.demo.post.dto.PostCreateRequest;
import com.example.demo.post.dto.PostDetailResponse;
import com.example.demo.post.dto.PostResponse;
import com.example.demo.post.service.PostService;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.Map;

@RestController
@RequiredArgsConstructor
@RequestMapping("/posts")
public class PostController {

private final PostService postService;

//게시글 생성
@PostMapping
public Long create(@RequestBody PostCreateRequest request) {
return postService.createPost(request);
public ApiResponse<PostResponse> create(
@RequestBody @Valid PostCreateRequest request) {

return ResponseUtil.success(
BaseCode.POST_CREATE_SUCCESS,
postService.createPost(request)
);
}

//게시글 수정
@PutMapping("/{postId}")
public ApiResponse<PostResponse> update(
@PathVariable Long postId,
@RequestBody @Valid PostCreateRequest request) {

return ResponseUtil.success(
BaseCode.POST_UPDATE_SUCCESS,
postService.updatePost(postId, request)
);
}

//게시글 삭제
@DeleteMapping("/{postId}")
public ApiResponse<Map<String, Long>> delete(
@PathVariable Long postId) {

}
return ResponseUtil.success(
BaseCode.POST_DELETE_SUCCESS,
Map.of("postId", postService.deletePost(postId))
);
}

//게시글 조회
@GetMapping("/{postId}")
public ApiResponse<PostDetailResponse> getPost(@PathVariable Long postId) {

return ResponseUtil.success(
BaseCode.SUCCESS,
postService.getPostDetail(postId)
);
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/example/demo/post/dto/PostBlockResponse.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example.demo.post.dto;

import com.example.demo.post.entity.BlockType;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
public class PostBlockResponse {

private BlockType blockType;
private String textContent;
private String imageUrl;
}
12 changes: 12 additions & 0 deletions src/main/java/com/example/demo/post/dto/PostCreateRequest.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
package com.example.demo.post.dto;

import jakarta.validation.constraints.NotEmpty;
import lombok.Getter;
import java.util.List;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;


@Getter
public class PostCreateRequest {

@NotNull(message = "유저 ID는 필수입니다.")
private Long userId;

@NotBlank(message = "제목은 비어 있을 수 없습니다.")
private String title;

@NotBlank(message = "설명은 비어 있을 수 없습니다.")
private String description;

@NotEmpty(message = "블록은 최소 1개 이상이어야 합니다.")
private List<PostBlockDto> blocks;
}
Loading