diff --git a/.gitignore b/.gitignore index c2065bc..e7c2888 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,10 @@ out/ ### VS Code ### .vscode/ + +# 데이터베이스 데이터 폴더 제외 +/mysql_data/ + +# OS 관련 임시 파일 +.DS_Store +Thumbs.db \ No newline at end of file diff --git a/build.gradle b/build.gradle index d62ae85..66fd456 100644 --- a/build.gradle +++ b/build.gradle @@ -1,6 +1,6 @@ plugins { id 'java' - id 'org.springframework.boot' version '4.0.3' + id 'org.springframework.boot' version '3.5.11' id 'io.spring.dependency-management' version '1.1.7' } @@ -26,13 +26,20 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-webmvc' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-validation' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' annotationProcessor 'org.projectlombok:lombok' - testImplementation 'org.springframework.boot:spring-boot-starter-data-jpa-test' - testImplementation 'org.springframework.boot:spring-boot-starter-webmvc-test' + testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + // Swagger + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.8.13' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-api:2.8.13' + // Validation + implementation 'org.springframework.boot:spring-boot-starter-validation' } tasks.named('test') { diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..7c0cf6e --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,12 @@ +version: '3.8' +services: + db: + image: mysql:8.0 + container_name: spring-study-db + ports: + - "3307:3306" + environment: + MYSQL_ROOT_PASSWORD: tngus3872 + MYSQL_DATABASE: spring-1team + volumes: + - ./mysql_data:/var/lib/mysql \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/Spring1teamApplication.java b/src/main/java/com/study/spring1team/Spring1teamApplication.java index f173fc6..c32833f 100644 --- a/src/main/java/com/study/spring1team/Spring1teamApplication.java +++ b/src/main/java/com/study/spring1team/Spring1teamApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@EnableJpaAuditing @SpringBootApplication public class Spring1teamApplication { diff --git a/src/main/java/com/study/spring1team/domain/comment/controller/CommentController.java b/src/main/java/com/study/spring1team/domain/comment/controller/CommentController.java new file mode 100644 index 0000000..5ea0e42 --- /dev/null +++ b/src/main/java/com/study/spring1team/domain/comment/controller/CommentController.java @@ -0,0 +1,31 @@ +package com.study.spring1team.domain.comment.controller; + +import com.study.spring1team.domain.comment.dto.CommentRequestDTO; +import com.study.spring1team.domain.comment.service.CommentService; +import com.study.spring1team.global.apiPayload.ApiResponse; +import com.study.spring1team.global.apiPayload.code.GeneralSuccessCode; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +public class CommentController { + + private final CommentService commentService; + + @PostMapping("/posts/{postId}/comments") + public ApiResponse createComment( + @PathVariable Long postId, + @Valid @RequestBody CommentRequestDTO request + ) { + commentService.createComment(postId, request); + return ApiResponse.onSuccess(GeneralSuccessCode.OK, "댓글이 성공적으로 작성되었습니다."); + } + + @DeleteMapping("/comments/{commentId}") + public ApiResponse deleteComment(@PathVariable Long commentId) { + commentService.deleteComment(commentId); + return ApiResponse.onSuccess(GeneralSuccessCode.OK, "댓글이 성공적으로 삭제되었습니다."); + } +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/domain/comment/dto/CommentRequestDTO.java b/src/main/java/com/study/spring1team/domain/comment/dto/CommentRequestDTO.java new file mode 100644 index 0000000..a5041db --- /dev/null +++ b/src/main/java/com/study/spring1team/domain/comment/dto/CommentRequestDTO.java @@ -0,0 +1,11 @@ +package com.study.spring1team.domain.comment.dto; + +import jakarta.validation.constraints.NotBlank; +import lombok.Getter; + +@Getter +public class CommentRequestDTO { + + @NotBlank(message = "댓글을 입력해주세요.") + private String content; +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/domain/comment/entity/Comment.java b/src/main/java/com/study/spring1team/domain/comment/entity/Comment.java new file mode 100644 index 0000000..fd4564b --- /dev/null +++ b/src/main/java/com/study/spring1team/domain/comment/entity/Comment.java @@ -0,0 +1,30 @@ +package com.study.spring1team.domain.comment.entity; + +import com.study.spring1team.domain.post.entity.Post; +import com.study.spring1team.domain.user.entity.User; +import com.study.spring1team.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Comment extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, columnDefinition = "TEXT") + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User author; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id") + private Post post; +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/domain/comment/repository/CommentRepository.java b/src/main/java/com/study/spring1team/domain/comment/repository/CommentRepository.java new file mode 100644 index 0000000..1caa718 --- /dev/null +++ b/src/main/java/com/study/spring1team/domain/comment/repository/CommentRepository.java @@ -0,0 +1,7 @@ +package com.study.spring1team.domain.comment.repository; + +import com.study.spring1team.domain.comment.entity.Comment; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommentRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/domain/comment/service/CommentService.java b/src/main/java/com/study/spring1team/domain/comment/service/CommentService.java new file mode 100644 index 0000000..9fab32b --- /dev/null +++ b/src/main/java/com/study/spring1team/domain/comment/service/CommentService.java @@ -0,0 +1,48 @@ +package com.study.spring1team.domain.comment.service; + +import com.study.spring1team.domain.comment.dto.CommentRequestDTO; +import com.study.spring1team.domain.comment.entity.Comment; +import com.study.spring1team.domain.comment.repository.CommentRepository; +import com.study.spring1team.domain.post.entity.Post; +import com.study.spring1team.domain.post.repository.PostRepository; +import com.study.spring1team.domain.user.entity.User; +import com.study.spring1team.domain.user.repository.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class CommentService { + + private final CommentRepository commentRepository; + private final PostRepository postRepository; + private final UserRepository userRepository; + + // 유저 고정값 설정 + private static final Long DEFAULT_USER_ID = 1L; + + public Long createComment(Long postId, CommentRequestDTO request) { + Post post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다.")); + + User user = userRepository.findById(DEFAULT_USER_ID) + .orElseThrow(() -> new IllegalArgumentException("해당 사용자가 없습니다.")); + + Comment comment = Comment.builder() + .content(request.getContent()) + .author(user) + .post(post) + .build(); + + return commentRepository.save(comment).getId(); + } + + public void deleteComment(Long commentId) { + Comment comment = commentRepository.findById(commentId) + .orElseThrow(() -> new IllegalArgumentException("해당 댓글이 없습니다.")); + + commentRepository.delete(comment); + } +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/controller/PingController.java b/src/main/java/com/study/spring1team/domain/ping/controller/PingController.java similarity index 50% rename from src/main/java/com/study/spring1team/controller/PingController.java rename to src/main/java/com/study/spring1team/domain/ping/controller/PingController.java index ce95df7..7cfcd92 100644 --- a/src/main/java/com/study/spring1team/controller/PingController.java +++ b/src/main/java/com/study/spring1team/domain/ping/controller/PingController.java @@ -1,5 +1,7 @@ -package com.study.spring1team.controller; +package com.study.spring1team.domain.ping.controller; +import com.study.spring1team.global.apiPayload.ApiResponse; +import com.study.spring1team.global.apiPayload.code.GeneralSuccessCode; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @@ -9,7 +11,7 @@ public class PingController { @GetMapping("/ping") - public String ping() { - return "pong"; + public ApiResponse ping() { + return ApiResponse.onSuccess(GeneralSuccessCode.OK,"pong"); } } \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/domain/post/controller/PostController.java b/src/main/java/com/study/spring1team/domain/post/controller/PostController.java new file mode 100644 index 0000000..73f7364 --- /dev/null +++ b/src/main/java/com/study/spring1team/domain/post/controller/PostController.java @@ -0,0 +1,50 @@ +package com.study.spring1team.domain.post.controller; + +import com.study.spring1team.domain.post.entity.Post; +import com.study.spring1team.domain.post.service.PostService; +import com.study.spring1team.global.apiPayload.ApiResponse; +import com.study.spring1team.domain.post.dto.PostRequestDTO; +import com.study.spring1team.global.apiPayload.code.GeneralSuccessCode; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/posts") +@RequiredArgsConstructor +public class PostController { + private final PostService postService; + + @PostMapping() + public ApiResponse createPost(@Valid @RequestBody PostRequestDTO request) { + postService.createPost(request); + return ApiResponse.onSuccess(GeneralSuccessCode.OK, "게시글이 성공적으로 생성되었습니다."); + } + + @GetMapping + public List getPosts() { + return postService.getPostList(); + } + + @GetMapping("/{postId}") + public ApiResponse getPost(@PathVariable Long postId) { + return ApiResponse.onSuccess(GeneralSuccessCode.OK, postService.getPost(postId)); + } + + @PutMapping("/{postId}") + public ApiResponse updatePost( + @PathVariable Long postId, + @Valid @RequestBody PostRequestDTO request + ) { + postService.updatePost(postId, request); + return ApiResponse.onSuccess(GeneralSuccessCode.OK, "게시글이 성공적으로 수정되었습니다."); + } + + @DeleteMapping("/{postId}") + public ApiResponse deletePost(@PathVariable Long postId) { + postService.deletePost(postId); + return ApiResponse.onSuccess(GeneralSuccessCode.OK, "게시글이 성공적으로 삭제되었습니다."); + } +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/domain/post/dto/PostRequestDTO.java b/src/main/java/com/study/spring1team/domain/post/dto/PostRequestDTO.java new file mode 100644 index 0000000..3458c48 --- /dev/null +++ b/src/main/java/com/study/spring1team/domain/post/dto/PostRequestDTO.java @@ -0,0 +1,18 @@ +package com.study.spring1team.domain.post.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.Size; +import lombok.Getter; + +@Getter +public class PostRequestDTO { + + @NotBlank(message = "제목은 필수 입력 항목입니다.") + @Size(min = 1, max = 50, message = "제목은 1자 이상 50자 이하로 입력해주세요.") + private String title; + + @NotBlank(message = "내용은 필수 입력 항목입니다.") + private String content; + + private Long categoryId; +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/domain/post/dto/PostResponseDTO.java b/src/main/java/com/study/spring1team/domain/post/dto/PostResponseDTO.java new file mode 100644 index 0000000..ce4ffbc --- /dev/null +++ b/src/main/java/com/study/spring1team/domain/post/dto/PostResponseDTO.java @@ -0,0 +1,4 @@ +package com.study.spring1team.domain.post.dto; + +public class PostResponseDTO { +} diff --git a/src/main/java/com/study/spring1team/domain/post/entity/Category.java b/src/main/java/com/study/spring1team/domain/post/entity/Category.java new file mode 100644 index 0000000..8257270 --- /dev/null +++ b/src/main/java/com/study/spring1team/domain/post/entity/Category.java @@ -0,0 +1,26 @@ +package com.study.spring1team.domain.post.entity; + +import com.study.spring1team.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Category extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 20) + private String name; + + @OneToMany(mappedBy = "category", cascade = CascadeType.ALL) + private List postList = new ArrayList<>(); +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/domain/post/entity/Post.java b/src/main/java/com/study/spring1team/domain/post/entity/Post.java new file mode 100644 index 0000000..21cd566 --- /dev/null +++ b/src/main/java/com/study/spring1team/domain/post/entity/Post.java @@ -0,0 +1,41 @@ +package com.study.spring1team.domain.post.entity; + +import com.study.spring1team.domain.comment.entity.Comment; +import com.study.spring1team.domain.user.entity.User; +import com.study.spring1team.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class Post extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Setter + @Column(nullable = false, length = 50) + private String title; + + @Setter + @Column(columnDefinition = "TEXT", nullable = false) + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "user_id") + private User author; // 작성자 매핑 + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "category_id") + private Category category; // 카테고리 매핑 + + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL, orphanRemoval = true) + private List commentList = new ArrayList<>(); +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/domain/post/repository/PostRepository.java b/src/main/java/com/study/spring1team/domain/post/repository/PostRepository.java new file mode 100644 index 0000000..4cc486f --- /dev/null +++ b/src/main/java/com/study/spring1team/domain/post/repository/PostRepository.java @@ -0,0 +1,7 @@ +package com.study.spring1team.domain.post.repository; + +import com.study.spring1team.domain.post.entity.Post; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PostRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/domain/post/service/PostService.java b/src/main/java/com/study/spring1team/domain/post/service/PostService.java new file mode 100644 index 0000000..d8e1e3b --- /dev/null +++ b/src/main/java/com/study/spring1team/domain/post/service/PostService.java @@ -0,0 +1,53 @@ +package com.study.spring1team.domain.post.service; + +import com.study.spring1team.domain.post.dto.PostRequestDTO; +import com.study.spring1team.domain.post.entity.Post; +import com.study.spring1team.domain.post.repository.PostRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@Transactional +@RequiredArgsConstructor +public class PostService { + + private final PostRepository postRepository; + + public Long createPost(PostRequestDTO request) { + Post post = Post.builder() + .title(request.getTitle()) + .content(request.getContent()) + .build(); + + return postRepository.save(post).getId(); + } + + @Transactional(readOnly = true) + public List getPostList() { + return postRepository.findAll(); + } + + @Transactional(readOnly = true) + public Post getPost(Long postId) { + return postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다.")); + } + + public void updatePost(Long postId, PostRequestDTO request) { + Post post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다.")); + + post.setTitle(request.getTitle()); + post.setContent(request.getContent()); + } + + public void deletePost(Long postId) { + Post post = postRepository.findById(postId) + .orElseThrow(() -> new IllegalArgumentException("해당 게시글이 없습니다.")); + + postRepository.delete(post); + } +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/domain/user/entity/User.java b/src/main/java/com/study/spring1team/domain/user/entity/User.java new file mode 100644 index 0000000..8f54fbe --- /dev/null +++ b/src/main/java/com/study/spring1team/domain/user/entity/User.java @@ -0,0 +1,36 @@ +package com.study.spring1team.domain.user.entity; + +import com.study.spring1team.domain.comment.entity.Comment; +import com.study.spring1team.domain.post.entity.Post; +import com.study.spring1team.global.entity.BaseEntity; +import jakarta.persistence.*; +import lombok.*; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Builder +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +public class User extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, length = 100) + private String password; + + @Column(nullable = false, length = 20) + private String name; + + @Column(nullable = false, unique = true, length = 50) + private String email; + + @OneToMany(mappedBy = "author", cascade = CascadeType.ALL) + private List postList = new ArrayList<>(); + + @OneToMany(mappedBy = "author") + private List commentList = new ArrayList<>(); +} diff --git a/src/main/java/com/study/spring1team/domain/user/repository/UserRepository.java b/src/main/java/com/study/spring1team/domain/user/repository/UserRepository.java new file mode 100644 index 0000000..281af8c --- /dev/null +++ b/src/main/java/com/study/spring1team/domain/user/repository/UserRepository.java @@ -0,0 +1,7 @@ +package com.study.spring1team.domain.user.repository; + +import com.study.spring1team.domain.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface UserRepository extends JpaRepository { +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/global/apiPayload/ApiResponse.java b/src/main/java/com/study/spring1team/global/apiPayload/ApiResponse.java new file mode 100644 index 0000000..365975c --- /dev/null +++ b/src/main/java/com/study/spring1team/global/apiPayload/ApiResponse.java @@ -0,0 +1,38 @@ +package com.study.spring1team.global.apiPayload; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; +import com.study.spring1team.global.apiPayload.code.BaseErrorCode; +import com.study.spring1team.global.apiPayload.code.BaseSuccessCode; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +@JsonPropertyOrder({"isSuccess", "code", "message", "result"}) +public class ApiResponse { + + @JsonProperty("isSuccess") + private final Boolean isSuccess; + + @JsonProperty("code") + private final String code; + + @JsonProperty("message") + private final String message; + + @JsonInclude(JsonInclude.Include.NON_NULL) // 결과가 null일 때 JSON에서 아예 안 보이게 함 + @JsonProperty("result") + private T result; + + // 성공한 경우 (result 포함) + public static ApiResponse onSuccess(BaseSuccessCode code, T result) { + return new ApiResponse<>(true, code.getCode(), code.getMessage(), result); + } + + // 실패한 경우 (result 포함) + public static ApiResponse onFailure(BaseErrorCode code, T result) { + return new ApiResponse<>(false, code.getCode(), code.getMessage(), result); + } +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/global/apiPayload/code/BaseErrorCode.java b/src/main/java/com/study/spring1team/global/apiPayload/code/BaseErrorCode.java new file mode 100644 index 0000000..886f201 --- /dev/null +++ b/src/main/java/com/study/spring1team/global/apiPayload/code/BaseErrorCode.java @@ -0,0 +1,9 @@ +package com.study.spring1team.global.apiPayload.code; + +import org.springframework.http.HttpStatus; + +public interface BaseErrorCode { + HttpStatus getStatus(); + String getCode(); + String getMessage(); +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/global/apiPayload/code/BaseSuccessCode.java b/src/main/java/com/study/spring1team/global/apiPayload/code/BaseSuccessCode.java new file mode 100644 index 0000000..1b8f966 --- /dev/null +++ b/src/main/java/com/study/spring1team/global/apiPayload/code/BaseSuccessCode.java @@ -0,0 +1,9 @@ +package com.study.spring1team.global.apiPayload.code; + +import org.springframework.http.HttpStatus; + +public interface BaseSuccessCode { + HttpStatus getStatus(); + String getCode(); + String getMessage(); +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/global/apiPayload/code/GeneralErrorCode.java b/src/main/java/com/study/spring1team/global/apiPayload/code/GeneralErrorCode.java new file mode 100644 index 0000000..8026618 --- /dev/null +++ b/src/main/java/com/study/spring1team/global/apiPayload/code/GeneralErrorCode.java @@ -0,0 +1,28 @@ +package com.study.spring1team.global.apiPayload.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum GeneralErrorCode implements BaseErrorCode{ + + BAD_REQUEST(HttpStatus.BAD_REQUEST, + "COMMON400_1", + "잘못된 요청입니다."), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED, + "AUTH401_1", + "인증이 필요합니다."), + FORBIDDEN(HttpStatus.FORBIDDEN, + "AUTH403_1", + "요청이 거부되었습니다."), + NOT_FOUND(HttpStatus.NOT_FOUND, + "COMMON404_1", + "요청한 리소스를 찾을 수 없습니다."), + ; + + private final HttpStatus status; + private final String code; + private final String message; +} diff --git a/src/main/java/com/study/spring1team/global/apiPayload/code/GeneralSuccessCode.java b/src/main/java/com/study/spring1team/global/apiPayload/code/GeneralSuccessCode.java new file mode 100644 index 0000000..0584cd8 --- /dev/null +++ b/src/main/java/com/study/spring1team/global/apiPayload/code/GeneralSuccessCode.java @@ -0,0 +1,15 @@ +package com.study.spring1team.global.apiPayload.code; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +@AllArgsConstructor +public enum GeneralSuccessCode implements BaseSuccessCode { + OK(HttpStatus.OK, "COMMON200", "요청에 성공하였습니다."); + + private final HttpStatus status; + private final String code; + private final String message; +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/global/apiPayload/exception/GeneralException.java b/src/main/java/com/study/spring1team/global/apiPayload/exception/GeneralException.java new file mode 100644 index 0000000..9798ebc --- /dev/null +++ b/src/main/java/com/study/spring1team/global/apiPayload/exception/GeneralException.java @@ -0,0 +1,14 @@ +package com.study.spring1team.global.apiPayload.exception; + +import com.study.spring1team.global.apiPayload.code.BaseErrorCode; +import lombok.Getter; + +@Getter +public class GeneralException extends RuntimeException { + private final BaseErrorCode code; + + public GeneralException(BaseErrorCode code) { + super(code.getMessage()); + this.code = code; + } +} diff --git a/src/main/java/com/study/spring1team/global/apiPayload/exception/GlobalExceptionHandler.java b/src/main/java/com/study/spring1team/global/apiPayload/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..22b5bd6 --- /dev/null +++ b/src/main/java/com/study/spring1team/global/apiPayload/exception/GlobalExceptionHandler.java @@ -0,0 +1,33 @@ +package com.study.spring1team.global.apiPayload.exception; + +import com.study.spring1team.global.apiPayload.ApiResponse; +import com.study.spring1team.global.apiPayload.code.BaseErrorCode; +import com.study.spring1team.global.apiPayload.code.GeneralErrorCode; +import jakarta.servlet.ServletException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseEntityExceptionHandler; + +@RestControllerAdvice +public class GlobalExceptionHandler extends ResponseEntityExceptionHandler { + + @ExceptionHandler(GeneralException.class) + public ResponseEntity> handleGeneralException(GeneralException e) { + BaseErrorCode code = e.getCode(); + return ResponseEntity + .status(code.getStatus()) + .body(ApiResponse.onFailure(code, null)); + } + + @ExceptionHandler(Exception.class) + public ResponseEntity> handleAllException(Exception e) throws ServletException { + if (e instanceof jakarta.servlet.ServletException) { + System.out.println("발생한 에러 클래스: " + e.getClass().getName()); + throw (jakarta.servlet.ServletException) e; + } + return ResponseEntity + .status(GeneralErrorCode.BAD_REQUEST.getStatus()) + .body(ApiResponse.onFailure(GeneralErrorCode.BAD_REQUEST, null)); + } +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/global/config/SwaggerConfig.java b/src/main/java/com/study/spring1team/global/config/SwaggerConfig.java new file mode 100644 index 0000000..0c66142 --- /dev/null +++ b/src/main/java/com/study/spring1team/global/config/SwaggerConfig.java @@ -0,0 +1,36 @@ +package com.study.spring1team.global.config; + +import io.swagger.v3.oas.models.Components; +import io.swagger.v3.oas.models.OpenAPI; +import io.swagger.v3.oas.models.info.Info; +import io.swagger.v3.oas.models.security.SecurityRequirement; +import io.swagger.v3.oas.models.security.SecurityScheme; +import io.swagger.v3.oas.models.servers.Server; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class SwaggerConfig { + + @Bean + public OpenAPI swagger() { + Info info = new Info().title("Project").description("Project Swagger").version("0.0.1"); + + // JWT 토큰 헤더 방식 + String securityScheme = "JWT TOKEN"; + SecurityRequirement securityRequirement = new SecurityRequirement().addList(securityScheme); + + Components components = new Components() + .addSecuritySchemes(securityScheme, new SecurityScheme() + .name(securityScheme) + .type(SecurityScheme.Type.HTTP) + .scheme("Bearer") + .bearerFormat("JWT")); + + return new OpenAPI() + .info(info) + .addServersItem(new Server().url("/")) + .addSecurityItem(securityRequirement) + .components(components); + } +} \ No newline at end of file diff --git a/src/main/java/com/study/spring1team/global/entity/BaseEntity.java b/src/main/java/com/study/spring1team/global/entity/BaseEntity.java new file mode 100644 index 0000000..5ec7a0b --- /dev/null +++ b/src/main/java/com/study/spring1team/global/entity/BaseEntity.java @@ -0,0 +1,25 @@ +package com.study.spring1team.global.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import lombok.Getter; +import java.time.LocalDateTime; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +@Getter +public abstract class BaseEntity { + + @CreatedDate + @Column(name= "created_at", nullable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(name= "updated_at", nullable = false) + private LocalDateTime updatedAt; +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties deleted file mode 100644 index ed2c030..0000000 --- a/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ -spring.application.name=spring-1team diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..c7d6dfc --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,30 @@ +spring: + application: + name: "spring-1team" + + datasource: + driver-class-name: com.mysql.cj.jdbc.Driver + url: ${DB_URL} + username: ${DB_USER} + password: ${DB_PW} + + jpa: + database: mysql + database-platform: org.hibernate.dialect.MySQLDialect + show-sql: true + hibernate: + ddl-auto: update + properties: + hibernate: + format_sql: true + +springdoc: + default-consume-media-type: application/json + api-docs: + path: /v3/api-docs + swagger-ui: + path: /swagger-ui.html + groups-order: DESC + operations-sorter: alpha + disable-swagger-default-url: true + display-request-duration: true \ No newline at end of file