diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 12bbb95..cb29113 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: - name: ✔️ application.yml 파일을 생성합니다. run: | - cd ./src/main + cd ./api/src/main mkdir resources cd ./resources echo "$APPLICATION" > ./application.yml @@ -32,4 +32,4 @@ jobs: run: chmod +x gradlew - name: ✔️ gradle build 합니다. - run: ./gradlew build \ No newline at end of file + run: ./gradlew api:build \ No newline at end of file diff --git a/.gitignore b/.gitignore index e5cbe63..0debf03 100644 --- a/.gitignore +++ b/.gitignore @@ -40,4 +40,4 @@ out/ .DS_Store ### Yml ### -src/main/resources/application.yml \ No newline at end of file +api/src/main/resources/application.yml \ No newline at end of file diff --git a/api/build.gradle b/api/build.gradle new file mode 100644 index 0000000..3bd0525 --- /dev/null +++ b/api/build.gradle @@ -0,0 +1,16 @@ +dependencies { + // spring boot web + implementation 'org.springframework.boot:spring-boot-starter-web' + // spring data jpa + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + // h2 + runtimeOnly 'com.h2database:h2' + // domain dependency + implementation project(path: ':domain') + // common dependency + implementation project(path: ':common') +} + +jar { + enabled = false +} \ No newline at end of file diff --git a/src/main/java/org/sopt/seminar/SeminarApplication.java b/api/src/main/java/org/sopt/SeminarApplication.java similarity index 91% rename from src/main/java/org/sopt/seminar/SeminarApplication.java rename to api/src/main/java/org/sopt/SeminarApplication.java index 469f33c..0b347d0 100644 --- a/src/main/java/org/sopt/seminar/SeminarApplication.java +++ b/api/src/main/java/org/sopt/SeminarApplication.java @@ -1,4 +1,4 @@ -package org.sopt.seminar; +package org.sopt; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; diff --git a/src/main/java/org/sopt/seminar/global/common/ApiResponse.java b/api/src/main/java/org/sopt/api/common/ApiResponse.java similarity index 90% rename from src/main/java/org/sopt/seminar/global/common/ApiResponse.java rename to api/src/main/java/org/sopt/api/common/ApiResponse.java index 125501e..c785d3c 100644 --- a/src/main/java/org/sopt/seminar/global/common/ApiResponse.java +++ b/api/src/main/java/org/sopt/api/common/ApiResponse.java @@ -1,11 +1,11 @@ -package org.sopt.seminar.global.common; +package org.sopt.api.common; import com.fasterxml.jackson.annotation.JsonInclude; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; -import org.sopt.seminar.global.error.ErrorStatus; +import org.sopt.common.error.ErrorStatus; import org.springframework.http.ResponseEntity; @Builder(access = AccessLevel.PRIVATE) @@ -18,14 +18,14 @@ public class ApiResponse { private final T data; private static ApiResponse of(SuccessStatus successStatus) { - return ApiResponse.builder() + return builder() .status(successStatus.getHttpStatus().value()) .message(successStatus.getMessage()) .build(); } private static ApiResponse of(SuccessStatus successStatus, T data) { - return ApiResponse.builder() + return builder() .status(successStatus.getHttpStatus().value()) .message(successStatus.getMessage()) .data(data) @@ -33,7 +33,7 @@ private static ApiResponse of(SuccessStatus successStatus, T data) { } private static ApiResponse of(ErrorStatus errorStatus) { - return ApiResponse.builder() + return builder() .status(errorStatus.getHttpStatus().value()) .message(errorStatus.getMessage()) .build(); diff --git a/src/main/java/org/sopt/seminar/global/common/SuccessStatus.java b/api/src/main/java/org/sopt/api/common/SuccessStatus.java similarity index 91% rename from src/main/java/org/sopt/seminar/global/common/SuccessStatus.java rename to api/src/main/java/org/sopt/api/common/SuccessStatus.java index f8b1469..d5480f4 100644 --- a/src/main/java/org/sopt/seminar/global/common/SuccessStatus.java +++ b/api/src/main/java/org/sopt/api/common/SuccessStatus.java @@ -1,4 +1,4 @@ -package org.sopt.seminar.global.common; +package org.sopt.api.common; import lombok.AccessLevel; import lombok.Getter; diff --git a/src/main/java/org/sopt/seminar/global/error/GlobalExceptionHandler.java b/api/src/main/java/org/sopt/api/error/GlobalExceptionHandler.java similarity index 77% rename from src/main/java/org/sopt/seminar/global/error/GlobalExceptionHandler.java rename to api/src/main/java/org/sopt/api/error/GlobalExceptionHandler.java index 0be7bb7..931b85c 100644 --- a/src/main/java/org/sopt/seminar/global/error/GlobalExceptionHandler.java +++ b/api/src/main/java/org/sopt/api/error/GlobalExceptionHandler.java @@ -1,7 +1,9 @@ -package org.sopt.seminar.global.error; +package org.sopt.api.error; import lombok.extern.slf4j.Slf4j; -import org.sopt.seminar.global.common.ApiResponse; +import org.sopt.api.common.ApiResponse; +import org.sopt.common.error.BusinessException; +import org.sopt.common.error.ErrorStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.HttpRequestMethodNotSupportedException; import org.springframework.web.bind.annotation.ControllerAdvice; @@ -11,7 +13,7 @@ @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(HttpRequestMethodNotSupportedException.class) - protected ResponseEntity> handleHttpRequestMethodNotSupportedException(HttpRequestMethodNotSupportedException e) { + protected ResponseEntity> handleHttpRequestMethodNotSupportedException(final HttpRequestMethodNotSupportedException e) { log.error(">>> handle: HttpRequestMethodNotSupportedException ", e); return ApiResponse.failure(ErrorStatus.METHOD_NOT_ALLOWED); } @@ -23,7 +25,7 @@ protected ResponseEntity> handleBusinessException(final BusinessE } @ExceptionHandler(Exception.class) - protected ResponseEntity> handleException(Exception e) { + protected ResponseEntity> handleException(final Exception e) { log.error(">>> handle: Exception ", e); return ApiResponse.failure(ErrorStatus.INTERNAL_SERVER_ERROR); } diff --git a/src/main/java/org/sopt/seminar/domain/member/api/MemberApiController.java b/api/src/main/java/org/sopt/api/member/api/MemberApiController.java similarity index 79% rename from src/main/java/org/sopt/seminar/domain/member/api/MemberApiController.java rename to api/src/main/java/org/sopt/api/member/api/MemberApiController.java index f422f06..cf6ec07 100644 --- a/src/main/java/org/sopt/seminar/domain/member/api/MemberApiController.java +++ b/api/src/main/java/org/sopt/api/member/api/MemberApiController.java @@ -1,13 +1,13 @@ -package org.sopt.seminar.domain.member.api; +package org.sopt.api.member.api; import lombok.RequiredArgsConstructor; -import org.sopt.seminar.domain.member.dto.request.MemberSaveRequest; -import org.sopt.seminar.domain.member.dto.request.MemberUpdateRequest; -import org.sopt.seminar.domain.member.dto.response.MemberGetResponse; -import org.sopt.seminar.domain.member.dto.response.MemberSaveResponse; -import org.sopt.seminar.domain.member.service.MemberService; -import org.sopt.seminar.global.common.ApiResponse; -import org.sopt.seminar.global.common.SuccessStatus; +import org.sopt.api.common.ApiResponse; +import org.sopt.api.common.SuccessStatus; +import org.sopt.api.member.dto.request.MemberSaveRequest; +import org.sopt.api.member.dto.request.MemberUpdateRequest; +import org.sopt.api.member.dto.response.MemberGetResponse; +import org.sopt.api.member.dto.response.MemberSaveResponse; +import org.sopt.api.member.service.MemberService; import org.springframework.data.domain.Pageable; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; diff --git a/src/main/java/org/sopt/seminar/domain/member/dto/request/MemberSaveRequest.java b/api/src/main/java/org/sopt/api/member/dto/request/MemberSaveRequest.java similarity index 54% rename from src/main/java/org/sopt/seminar/domain/member/dto/request/MemberSaveRequest.java rename to api/src/main/java/org/sopt/api/member/dto/request/MemberSaveRequest.java index 2ecdc83..b891b19 100644 --- a/src/main/java/org/sopt/seminar/domain/member/dto/request/MemberSaveRequest.java +++ b/api/src/main/java/org/sopt/api/member/dto/request/MemberSaveRequest.java @@ -1,6 +1,6 @@ -package org.sopt.seminar.domain.member.dto.request; +package org.sopt.api.member.dto.request; -import org.sopt.seminar.domain.member.domain.Sopt; +import org.sopt.domain.member.domain.Sopt; public record MemberSaveRequest( String name, diff --git a/api/src/main/java/org/sopt/api/member/dto/request/MemberUpdateRequest.java b/api/src/main/java/org/sopt/api/member/dto/request/MemberUpdateRequest.java new file mode 100644 index 0000000..34123fd --- /dev/null +++ b/api/src/main/java/org/sopt/api/member/dto/request/MemberUpdateRequest.java @@ -0,0 +1,8 @@ +package org.sopt.api.member.dto.request; + +import org.sopt.domain.member.domain.Part; + +public record MemberUpdateRequest( + int generation, + Part part) { +} diff --git a/src/main/java/org/sopt/seminar/domain/member/dto/response/MemberGetResponse.java b/api/src/main/java/org/sopt/api/member/dto/response/MemberGetResponse.java similarity index 69% rename from src/main/java/org/sopt/seminar/domain/member/dto/response/MemberGetResponse.java rename to api/src/main/java/org/sopt/api/member/dto/response/MemberGetResponse.java index 032aac5..93578fe 100644 --- a/src/main/java/org/sopt/seminar/domain/member/dto/response/MemberGetResponse.java +++ b/api/src/main/java/org/sopt/api/member/dto/response/MemberGetResponse.java @@ -1,9 +1,9 @@ -package org.sopt.seminar.domain.member.dto.response; +package org.sopt.api.member.dto.response; import lombok.AccessLevel; import lombok.Builder; -import org.sopt.seminar.domain.member.domain.Member; -import org.sopt.seminar.domain.member.domain.Sopt; +import org.sopt.domain.member.domain.Member; +import org.sopt.domain.member.domain.Sopt; @Builder(access = AccessLevel.PRIVATE) public record MemberGetResponse( @@ -13,7 +13,7 @@ public record MemberGetResponse( Sopt sopt ) { public static MemberGetResponse of(Member member) { - return MemberGetResponse.builder() + return builder() .name(member.getName()) .nickname(member.getNickname()) .age(member.getAge()) diff --git a/src/main/java/org/sopt/seminar/domain/member/dto/response/MemberSaveResponse.java b/api/src/main/java/org/sopt/api/member/dto/response/MemberSaveResponse.java similarity index 63% rename from src/main/java/org/sopt/seminar/domain/member/dto/response/MemberSaveResponse.java rename to api/src/main/java/org/sopt/api/member/dto/response/MemberSaveResponse.java index d24c896..7137d00 100644 --- a/src/main/java/org/sopt/seminar/domain/member/dto/response/MemberSaveResponse.java +++ b/api/src/main/java/org/sopt/api/member/dto/response/MemberSaveResponse.java @@ -1,6 +1,6 @@ -package org.sopt.seminar.domain.member.dto.response; +package org.sopt.api.member.dto.response; -import org.sopt.seminar.domain.member.domain.Member; +import org.sopt.domain.member.domain.Member; public record MemberSaveResponse( Long memberId diff --git a/src/main/java/org/sopt/seminar/domain/member/service/MemberService.java b/api/src/main/java/org/sopt/api/member/service/MemberService.java similarity index 71% rename from src/main/java/org/sopt/seminar/domain/member/service/MemberService.java rename to api/src/main/java/org/sopt/api/member/service/MemberService.java index ad076f6..e8531b3 100644 --- a/src/main/java/org/sopt/seminar/domain/member/service/MemberService.java +++ b/api/src/main/java/org/sopt/api/member/service/MemberService.java @@ -1,23 +1,23 @@ -package org.sopt.seminar.domain.member.service; +package org.sopt.api.member.service; import lombok.RequiredArgsConstructor; -import org.sopt.seminar.domain.member.domain.Member; -import org.sopt.seminar.domain.member.domain.Sopt; -import org.sopt.seminar.domain.member.dto.request.MemberSaveRequest; -import org.sopt.seminar.domain.member.dto.request.MemberUpdateRequest; -import org.sopt.seminar.domain.member.dto.response.MemberGetResponse; -import org.sopt.seminar.domain.member.dto.response.MemberSaveResponse; -import org.sopt.seminar.domain.member.repository.MemberRepository; -import org.sopt.seminar.global.error.ConflictException; -import org.sopt.seminar.global.error.ErrorStatus; +import org.sopt.common.error.ConflictException; +import org.sopt.common.error.ErrorStatus; +import org.sopt.domain.member.domain.Member; +import org.sopt.domain.member.domain.Sopt; +import org.sopt.api.member.dto.request.MemberSaveRequest; +import org.sopt.api.member.dto.request.MemberUpdateRequest; +import org.sopt.api.member.dto.response.MemberGetResponse; +import org.sopt.api.member.dto.response.MemberSaveResponse; +import org.sopt.domain.member.repository.MemberRepository; import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; -import static org.sopt.seminar.domain.member.domain.Member.createMember; -import static org.sopt.seminar.domain.member.domain.Sopt.createSopt; +import static org.sopt.domain.member.domain.Member.createMember; +import static org.sopt.domain.member.domain.Sopt.createSopt; @RequiredArgsConstructor @Transactional(readOnly = true) diff --git a/api/src/main/java/org/sopt/api/post/api/PostApiController.java b/api/src/main/java/org/sopt/api/post/api/PostApiController.java new file mode 100644 index 0000000..c822e41 --- /dev/null +++ b/api/src/main/java/org/sopt/api/post/api/PostApiController.java @@ -0,0 +1,56 @@ +package org.sopt.api.post.api; + +import lombok.RequiredArgsConstructor; +import org.sopt.api.common.ApiResponse; +import org.sopt.api.common.SuccessStatus; +import org.sopt.api.post.dto.request.PostSaveOrUpdateRequest; +import org.sopt.api.post.dto.response.PostGetResponse; +import org.sopt.api.post.dto.response.PostSaveResponse; +import org.sopt.api.post.service.PostService; +import org.springframework.data.domain.Pageable; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RequiredArgsConstructor +@RequestMapping("/api/post") +@Controller +public class PostApiController { + private static final String CUSTOM_AUTHENTICATION = "X-Auth-Id"; + private final PostService postService; + + @PostMapping + public ResponseEntity> savePost(@RequestHeader(CUSTOM_AUTHENTICATION) final Long memberId, + @RequestBody final PostSaveOrUpdateRequest postSaveOrUpdateRequest) { + final PostSaveResponse postSaveResponse = postService.savePost(memberId, postSaveOrUpdateRequest); + return ApiResponse.success(SuccessStatus.CREATED, postSaveResponse); + } + + @GetMapping("/{postId}") + public ResponseEntity> getPost(@PathVariable final Long postId) { + final PostGetResponse post = postService.getPost(postId); + return ApiResponse.success(SuccessStatus.OK, post); + } + + @GetMapping + public ResponseEntity> getPosts(@RequestHeader(CUSTOM_AUTHENTICATION) final Long memberId, + final Pageable pageable) { + final List posts = postService.getPosts(memberId, pageable); + return ApiResponse.success(SuccessStatus.OK, posts); + } + + @PatchMapping("/{postId}") + public ResponseEntity> updatePost(@PathVariable final Long postId, + @RequestBody final PostSaveOrUpdateRequest postSaveOrUpdateRequest) { + postService.updatePost(postId, postSaveOrUpdateRequest); + return ApiResponse.success(SuccessStatus.OK); + } + + @DeleteMapping("/{postId}") + public ResponseEntity> deletePost(@PathVariable final Long postId) { + postService.deletePost(postId); + return ApiResponse.success(SuccessStatus.OK); + } +} diff --git a/api/src/main/java/org/sopt/api/post/dto/request/PostSaveOrUpdateRequest.java b/api/src/main/java/org/sopt/api/post/dto/request/PostSaveOrUpdateRequest.java new file mode 100644 index 0000000..e0c8474 --- /dev/null +++ b/api/src/main/java/org/sopt/api/post/dto/request/PostSaveOrUpdateRequest.java @@ -0,0 +1,8 @@ +package org.sopt.api.post.dto.request; + +public record PostSaveOrUpdateRequest( + String title, + String postContent, + String categoryContent +) { +} diff --git a/api/src/main/java/org/sopt/api/post/dto/response/PostGetResponse.java b/api/src/main/java/org/sopt/api/post/dto/response/PostGetResponse.java new file mode 100644 index 0000000..4263163 --- /dev/null +++ b/api/src/main/java/org/sopt/api/post/dto/response/PostGetResponse.java @@ -0,0 +1,22 @@ +package org.sopt.api.post.dto.response; + +import lombok.AccessLevel; +import lombok.Builder; +import org.sopt.domain.post.domain.Category; +import org.sopt.domain.post.domain.Post; + +@Builder(access = AccessLevel.PRIVATE) +public record PostGetResponse( + String title, + String postContent, + String categoryContent +) { + public static PostGetResponse of(Post post) { + Category category = post.getCategory(); + return builder() + .title(post.getTitle()) + .postContent(post.getContent()) + .categoryContent(category.getContent()) + .build(); + } +} diff --git a/api/src/main/java/org/sopt/api/post/dto/response/PostSaveResponse.java b/api/src/main/java/org/sopt/api/post/dto/response/PostSaveResponse.java new file mode 100644 index 0000000..b389831 --- /dev/null +++ b/api/src/main/java/org/sopt/api/post/dto/response/PostSaveResponse.java @@ -0,0 +1,11 @@ +package org.sopt.api.post.dto.response; + +import org.sopt.domain.post.domain.Post; + +public record PostSaveResponse( + Long postId +) { + public static PostSaveResponse of(Post post) { + return new PostSaveResponse(post.getId()); + } +} diff --git a/api/src/main/java/org/sopt/api/post/service/PostService.java b/api/src/main/java/org/sopt/api/post/service/PostService.java new file mode 100644 index 0000000..0b4ba2d --- /dev/null +++ b/api/src/main/java/org/sopt/api/post/service/PostService.java @@ -0,0 +1,93 @@ +package org.sopt.api.post.service; + +import lombok.RequiredArgsConstructor; +import org.sopt.api.post.dto.request.PostSaveOrUpdateRequest; +import org.sopt.api.post.dto.response.PostGetResponse; +import org.sopt.api.post.dto.response.PostSaveResponse; +import org.sopt.common.error.EntityNotFoundException; +import org.sopt.domain.member.domain.Member; +import org.sopt.domain.member.repository.MemberRepository; +import org.sopt.domain.post.domain.Category; +import org.sopt.domain.post.domain.Post; +import org.sopt.domain.post.repository.CategoryRepository; +import org.sopt.domain.post.repository.PostRepository; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static org.sopt.domain.post.domain.Category.createCategory; +import static org.sopt.domain.post.domain.Post.createPost; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class PostService { + private final MemberRepository memberRepository; + private final PostRepository postRepository; + private final CategoryRepository categoryRepository; + + @Transactional + public PostSaveResponse savePost(Long memberId, PostSaveOrUpdateRequest postSaveOrUpdateRequest) { + Member findMember = findMember(memberId); + Category savedCategory = createCategoryAndGetSavedCategoryOrGetCategory(postSaveOrUpdateRequest); + Post savedPost = createPostAndGetSavedPost(postSaveOrUpdateRequest, findMember, savedCategory); + return PostSaveResponse.of(savedPost); + } + + public PostGetResponse getPost(Long postId) { + Post findPost = findPost(postId); + return PostGetResponse.of(findPost); + } + + public List getPosts(Long memberId, Pageable pageable) { + return postRepository.findAllByMemberId(memberId, pageable) + .stream() + .map(PostGetResponse::of) + .toList(); + } + + @Transactional + public void updatePost(Long postId, PostSaveOrUpdateRequest postSaveOrUpdateRequest) { + Post findPost = findPost(postId); + Category category = findPost.getCategory(); + updatePostAndCategory(postSaveOrUpdateRequest, findPost, category); + } + + @Transactional + public void deletePost(Long postId) { + postRepository.deleteById(postId); + } + + private Member findMember(Long memberId) { + return memberRepository.findByIdOrThrow(memberId); + } + + private Category createCategoryAndGetSavedCategoryOrGetCategory(PostSaveOrUpdateRequest postSaveOrUpdateRequest) { + try { + return findCategory(postSaveOrUpdateRequest.categoryContent()); + } catch (EntityNotFoundException e) { + Category category = createCategory(postSaveOrUpdateRequest.categoryContent()); + return categoryRepository.save(category); + } + } + + private Post createPostAndGetSavedPost(PostSaveOrUpdateRequest postSaveOrUpdateRequest, Member member, Category category) { + Post post = createPost(postSaveOrUpdateRequest.title(), postSaveOrUpdateRequest.postContent(), category, member); + return postRepository.save(post); + } + + private Post findPost(Long postId) { + return postRepository.findByIdOrThrow(postId); + } + + private void updatePostAndCategory(PostSaveOrUpdateRequest postSaveOrUpdateRequest, Post post, Category category) { + post.updateTitleAndContent(postSaveOrUpdateRequest.title(), postSaveOrUpdateRequest.postContent()); + category.updateContent(postSaveOrUpdateRequest.categoryContent()); + } + + private Category findCategory(String content) { + return categoryRepository.findByContentOrThrow(content); + } +} diff --git a/build.gradle b/build.gradle index 4fc9ba0..514a72f 100644 --- a/build.gradle +++ b/build.gradle @@ -4,11 +4,14 @@ plugins { id 'io.spring.dependency-management' version '1.1.3' } -group = 'org.sopt' -version = '0.0.1-SNAPSHOT' - -java { +allprojects { + group = 'org.sopt' + version = '0.0.1-SNAPSHOT' sourceCompatibility = '17' + + repositories { + mavenCentral() + } } configurations { @@ -17,30 +20,21 @@ configurations { } } -repositories { - mavenCentral() -} - -dependencies { - // spring boot - implementation 'org.springframework.boot:spring-boot-starter-web' - implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - - // h2 - runtimeOnly 'com.h2database:h2' +subprojects { + apply plugin: 'java' + apply plugin: 'org.springframework.boot' + apply plugin: 'io.spring.dependency-management' - // lombok - compileOnly 'org.projectlombok:lombok' - annotationProcessor 'org.projectlombok:lombok' + dependencies { + // lombok + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' - // test - testImplementation 'org.springframework.boot:spring-boot-starter-test' -} - -tasks.named('test') { - useJUnitPlatform() -} + // test + testImplementation 'org.springframework.boot:spring-boot-starter-test' + } -jar { - enabled = false + tasks.named('test') { + useJUnitPlatform() + } } \ No newline at end of file diff --git a/common/build.gradle b/common/build.gradle new file mode 100644 index 0000000..7d81676 --- /dev/null +++ b/common/build.gradle @@ -0,0 +1,12 @@ +dependencies { + // spring boot web + implementation 'org.springframework.boot:spring-boot-starter-web' +} + +bootJar { + enabled = false +} + +jar { + enabled = true +} \ No newline at end of file diff --git a/src/main/java/org/sopt/seminar/global/error/BusinessException.java b/common/src/main/java/org/sopt/common/error/BusinessException.java similarity index 87% rename from src/main/java/org/sopt/seminar/global/error/BusinessException.java rename to common/src/main/java/org/sopt/common/error/BusinessException.java index b985f94..0f2da61 100644 --- a/src/main/java/org/sopt/seminar/global/error/BusinessException.java +++ b/common/src/main/java/org/sopt/common/error/BusinessException.java @@ -1,4 +1,4 @@ -package org.sopt.seminar.global.error; +package org.sopt.common.error; import lombok.Getter; diff --git a/src/main/java/org/sopt/seminar/global/error/ConflictException.java b/common/src/main/java/org/sopt/common/error/ConflictException.java similarity index 85% rename from src/main/java/org/sopt/seminar/global/error/ConflictException.java rename to common/src/main/java/org/sopt/common/error/ConflictException.java index 78f5242..274160c 100644 --- a/src/main/java/org/sopt/seminar/global/error/ConflictException.java +++ b/common/src/main/java/org/sopt/common/error/ConflictException.java @@ -1,4 +1,4 @@ -package org.sopt.seminar.global.error; +package org.sopt.common.error; public class ConflictException extends BusinessException { public ConflictException() { diff --git a/src/main/java/org/sopt/seminar/global/error/EntityNotFoundException.java b/common/src/main/java/org/sopt/common/error/EntityNotFoundException.java similarity index 86% rename from src/main/java/org/sopt/seminar/global/error/EntityNotFoundException.java rename to common/src/main/java/org/sopt/common/error/EntityNotFoundException.java index 7793473..5f11799 100644 --- a/src/main/java/org/sopt/seminar/global/error/EntityNotFoundException.java +++ b/common/src/main/java/org/sopt/common/error/EntityNotFoundException.java @@ -1,4 +1,4 @@ -package org.sopt.seminar.global.error; +package org.sopt.common.error; public class EntityNotFoundException extends BusinessException { public EntityNotFoundException() { diff --git a/src/main/java/org/sopt/seminar/global/error/ErrorStatus.java b/common/src/main/java/org/sopt/common/error/ErrorStatus.java similarity index 84% rename from src/main/java/org/sopt/seminar/global/error/ErrorStatus.java rename to common/src/main/java/org/sopt/common/error/ErrorStatus.java index 77083ab..b99f12f 100644 --- a/src/main/java/org/sopt/seminar/global/error/ErrorStatus.java +++ b/common/src/main/java/org/sopt/common/error/ErrorStatus.java @@ -1,4 +1,4 @@ -package org.sopt.seminar.global.error; +package org.sopt.common.error; import lombok.AccessLevel; import lombok.Getter; @@ -18,6 +18,8 @@ public enum ErrorStatus { */ ENTITY_NOT_FOUND(HttpStatus.NOT_FOUND, "대상을 찾을 수 없습니다."), MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "회원을 찾을 수 없습니다."), + POST_NOT_FOUND(HttpStatus.NOT_FOUND, "게시물을 찾을 수 없습니다."), + CATEGORY_NOT_FOUND(HttpStatus.NOT_FOUND, "카테고리를 찾을 수 없습니다."), /** * 405 Method Not Allowed diff --git a/src/main/java/org/sopt/seminar/global/error/InvalidValueException.java b/common/src/main/java/org/sopt/common/error/InvalidValueException.java similarity index 86% rename from src/main/java/org/sopt/seminar/global/error/InvalidValueException.java rename to common/src/main/java/org/sopt/common/error/InvalidValueException.java index 224adb9..ec7a515 100644 --- a/src/main/java/org/sopt/seminar/global/error/InvalidValueException.java +++ b/common/src/main/java/org/sopt/common/error/InvalidValueException.java @@ -1,4 +1,4 @@ -package org.sopt.seminar.global.error; +package org.sopt.common.error; public class InvalidValueException extends BusinessException { public InvalidValueException() { diff --git a/domain/build.gradle b/domain/build.gradle new file mode 100644 index 0000000..4b3bd02 --- /dev/null +++ b/domain/build.gradle @@ -0,0 +1,16 @@ +dependencies { + // spring data jpa + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + // h2 + runtimeOnly 'com.h2database:h2' + // common dependency + implementation project(path: ':common') +} + +bootJar { + enabled = false +} + +jar { + enabled = true +} \ No newline at end of file diff --git a/src/main/java/org/sopt/seminar/global/common/BaseTimeEntity.java b/domain/src/main/java/org/sopt/domain/common/BaseTimeEntity.java similarity index 94% rename from src/main/java/org/sopt/seminar/global/common/BaseTimeEntity.java rename to domain/src/main/java/org/sopt/domain/common/BaseTimeEntity.java index 002cf29..1a8475f 100644 --- a/src/main/java/org/sopt/seminar/global/common/BaseTimeEntity.java +++ b/domain/src/main/java/org/sopt/domain/common/BaseTimeEntity.java @@ -1,4 +1,4 @@ -package org.sopt.seminar.global.common; +package org.sopt.domain.common; import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; diff --git a/src/main/java/org/sopt/seminar/global/config/JpaConfig.java b/domain/src/main/java/org/sopt/domain/config/JpaConfig.java similarity index 83% rename from src/main/java/org/sopt/seminar/global/config/JpaConfig.java rename to domain/src/main/java/org/sopt/domain/config/JpaConfig.java index c9a9564..69facbd 100644 --- a/src/main/java/org/sopt/seminar/global/config/JpaConfig.java +++ b/domain/src/main/java/org/sopt/domain/config/JpaConfig.java @@ -1,4 +1,4 @@ -package org.sopt.seminar.global.config; +package org.sopt.domain.config; import org.springframework.context.annotation.Configuration; import org.springframework.data.jpa.repository.config.EnableJpaAuditing; diff --git a/src/main/java/org/sopt/seminar/domain/member/domain/Member.java b/domain/src/main/java/org/sopt/domain/member/domain/Member.java similarity index 66% rename from src/main/java/org/sopt/seminar/domain/member/domain/Member.java rename to domain/src/main/java/org/sopt/domain/member/domain/Member.java index 3cbd05f..e2d767c 100644 --- a/src/main/java/org/sopt/seminar/domain/member/domain/Member.java +++ b/domain/src/main/java/org/sopt/domain/member/domain/Member.java @@ -1,9 +1,13 @@ -package org.sopt.seminar.domain.member.domain; +package org.sopt.domain.member.domain; import jakarta.persistence.*; import lombok.*; import org.hibernate.annotations.DynamicUpdate; -import org.sopt.seminar.global.common.BaseTimeEntity; +import org.sopt.domain.common.BaseTimeEntity; +import org.sopt.domain.post.domain.Post; + +import java.util.ArrayList; +import java.util.List; @NoArgsConstructor(access = AccessLevel.PROTECTED) @AllArgsConstructor(access = AccessLevel.PRIVATE) @@ -21,6 +25,9 @@ public class Member extends BaseTimeEntity { private Integer age; @Embedded private Sopt sopt; + @OneToMany(mappedBy = "member", cascade = CascadeType.REMOVE) + @Builder.Default + private List posts = new ArrayList<>(); public static Member createMember(final String name, final String nickname, final Integer age, final Sopt sopt) { return Member.builder() @@ -31,6 +38,14 @@ public static Member createMember(final String name, final String nickname, fina .build(); } + public void addPost(Post post) { + posts.add(post); + } + + public void removePost(Post post) { + posts.remove(post); + } + public void updateSopt(Sopt sopt) { this.sopt = sopt; } diff --git a/src/main/java/org/sopt/seminar/domain/member/domain/Part.java b/domain/src/main/java/org/sopt/domain/member/domain/Part.java similarity index 86% rename from src/main/java/org/sopt/seminar/domain/member/domain/Part.java rename to domain/src/main/java/org/sopt/domain/member/domain/Part.java index e1e82d3..9ad1a88 100644 --- a/src/main/java/org/sopt/seminar/domain/member/domain/Part.java +++ b/domain/src/main/java/org/sopt/domain/member/domain/Part.java @@ -1,4 +1,4 @@ -package org.sopt.seminar.domain.member.domain; +package org.sopt.domain.member.domain; import lombok.AccessLevel; import lombok.Getter; diff --git a/src/main/java/org/sopt/seminar/domain/member/domain/Sopt.java b/domain/src/main/java/org/sopt/domain/member/domain/Sopt.java similarity index 92% rename from src/main/java/org/sopt/seminar/domain/member/domain/Sopt.java rename to domain/src/main/java/org/sopt/domain/member/domain/Sopt.java index d53c606..385afc1 100644 --- a/src/main/java/org/sopt/seminar/domain/member/domain/Sopt.java +++ b/domain/src/main/java/org/sopt/domain/member/domain/Sopt.java @@ -1,4 +1,4 @@ -package org.sopt.seminar.domain.member.domain; +package org.sopt.domain.member.domain; import jakarta.persistence.Embeddable; import jakarta.persistence.EnumType; diff --git a/src/main/java/org/sopt/seminar/domain/member/repository/MemberRepository.java b/domain/src/main/java/org/sopt/domain/member/repository/MemberRepository.java similarity index 63% rename from src/main/java/org/sopt/seminar/domain/member/repository/MemberRepository.java rename to domain/src/main/java/org/sopt/domain/member/repository/MemberRepository.java index 0e13e34..551173c 100644 --- a/src/main/java/org/sopt/seminar/domain/member/repository/MemberRepository.java +++ b/domain/src/main/java/org/sopt/domain/member/repository/MemberRepository.java @@ -1,9 +1,8 @@ -package org.sopt.seminar.domain.member.repository; +package org.sopt.domain.member.repository; - -import org.sopt.seminar.domain.member.domain.Member; -import org.sopt.seminar.global.error.EntityNotFoundException; -import org.sopt.seminar.global.error.ErrorStatus; +import org.sopt.common.error.EntityNotFoundException; +import org.sopt.common.error.ErrorStatus; +import org.sopt.domain.member.domain.Member; import org.springframework.data.jpa.repository.JpaRepository; public interface MemberRepository extends JpaRepository { diff --git a/domain/src/main/java/org/sopt/domain/post/domain/Category.java b/domain/src/main/java/org/sopt/domain/post/domain/Category.java new file mode 100644 index 0000000..a0c537e --- /dev/null +++ b/domain/src/main/java/org/sopt/domain/post/domain/Category.java @@ -0,0 +1,42 @@ +package org.sopt.domain.post.domain; + +import jakarta.persistence.*; +import lombok.*; +import org.sopt.domain.common.BaseTimeEntity; + +import java.util.ArrayList; +import java.util.List; + +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder(access = AccessLevel.PRIVATE) +@Getter +@Entity +public class Category extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "category_id") + private Short id; + private String content; + @OneToMany(mappedBy = "category") + @Builder.Default + private List posts = new ArrayList<>(); + + public static Category createCategory(final String content) { + return Category.builder() + .content(content) + .build(); + } + + public void addPost(Post post) { + posts.add(post); + } + + public void removePost(Post post) { + posts.remove(post); + } + + public void updateContent(String content) { + this.content = content; + } +} diff --git a/domain/src/main/java/org/sopt/domain/post/domain/Post.java b/domain/src/main/java/org/sopt/domain/post/domain/Post.java new file mode 100644 index 0000000..169c184 --- /dev/null +++ b/domain/src/main/java/org/sopt/domain/post/domain/Post.java @@ -0,0 +1,65 @@ +package org.sopt.domain.post.domain; + +import jakarta.persistence.*; +import lombok.*; +import org.sopt.domain.common.BaseTimeEntity; +import org.sopt.domain.member.domain.Member; + +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Builder(access = AccessLevel.PRIVATE) +@Getter +@Entity +public class Post extends BaseTimeEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "post_id") + private Long id; + private String title; + private String content; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "category_id") + private Category category; + + public static Post createPost(final String title, final String content, final Category category, final Member member) { + Post post = Post.builder() + .title(title) + .content(content) + .build(); + post.changeMember(member); + post.changeCategory(category); + return post; + } + + public void changeMember(Member member) { + if (this.member != null) { + this.member.removePost(this); + } + this.member = member; + member.addPost(this); + } + + public void changeCategory(Category category) { + if (this.category != null) { + this.category.removePost(this); + } + this.category = category; + category.addPost(this); + } + + public void updateTitleAndContent(String title, String content) { + updateTitle(title); + updateContent(content); + } + + public void updateTitle(String title) { + this.title = title; + } + + public void updateContent(String content) { + this.content = content; + } +} \ No newline at end of file diff --git a/domain/src/main/java/org/sopt/domain/post/repository/CategoryRepository.java b/domain/src/main/java/org/sopt/domain/post/repository/CategoryRepository.java new file mode 100644 index 0000000..7c53e57 --- /dev/null +++ b/domain/src/main/java/org/sopt/domain/post/repository/CategoryRepository.java @@ -0,0 +1,17 @@ +package org.sopt.domain.post.repository; + +import org.sopt.common.error.EntityNotFoundException; +import org.sopt.common.error.ErrorStatus; +import org.sopt.domain.post.domain.Category; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.Optional; + +public interface CategoryRepository extends JpaRepository { + Optional findByContent(String content); + + default Category findByContentOrThrow(String content) { + return findByContent(content) + .orElseThrow(() -> new EntityNotFoundException(ErrorStatus.CATEGORY_NOT_FOUND)); + } +} diff --git a/domain/src/main/java/org/sopt/domain/post/repository/PostRepository.java b/domain/src/main/java/org/sopt/domain/post/repository/PostRepository.java new file mode 100644 index 0000000..5e442db --- /dev/null +++ b/domain/src/main/java/org/sopt/domain/post/repository/PostRepository.java @@ -0,0 +1,17 @@ +package org.sopt.domain.post.repository; + +import org.sopt.common.error.EntityNotFoundException; +import org.sopt.common.error.ErrorStatus; +import org.sopt.domain.post.domain.Post; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PostRepository extends JpaRepository { + Page findAllByMemberId(Long memberId, Pageable pageable); + + default Post findByIdOrThrow(Long postId) { + return findById(postId) + .orElseThrow(() -> new EntityNotFoundException(ErrorStatus.POST_NOT_FOUND)); + } +} diff --git a/settings.gradle b/settings.gradle index 6b30e33..39131b8 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,4 @@ rootProject.name = 'seminar' +include 'api' +include 'domain' +include 'common' diff --git a/src/main/java/org/sopt/seminar/domain/member/dto/request/MemberUpdateRequest.java b/src/main/java/org/sopt/seminar/domain/member/dto/request/MemberUpdateRequest.java deleted file mode 100644 index d591d68..0000000 --- a/src/main/java/org/sopt/seminar/domain/member/dto/request/MemberUpdateRequest.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.sopt.seminar.domain.member.dto.request; - -import org.sopt.seminar.domain.member.domain.Part; - -public record MemberUpdateRequest( - int generation, - Part part) { -} diff --git a/src/test/java/org/sopt/seminar/SeminarApplicationTests.java b/src/test/java/org/sopt/seminar/SeminarApplicationTests.java deleted file mode 100644 index 63c422f..0000000 --- a/src/test/java/org/sopt/seminar/SeminarApplicationTests.java +++ /dev/null @@ -1,11 +0,0 @@ -package org.sopt.seminar; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class SeminarApplicationTests { - @Test - void contextLoads() { - } -}