diff --git a/src/main/java/org/sopt/article/api/ArticleController.java b/src/main/java/org/sopt/article/api/ArticleController.java index 761463f..aa09c20 100644 --- a/src/main/java/org/sopt/article/api/ArticleController.java +++ b/src/main/java/org/sopt/article/api/ArticleController.java @@ -1,8 +1,8 @@ package org.sopt.article.api; -import static org.sopt.global.response.SuccessCode.ARTICLE_CREATED_SUCCESS; -import static org.sopt.global.response.SuccessCode.ARTICLE_LIST_RETRIEVED_SUCCESS; -import static org.sopt.global.response.SuccessCode.ARTICLE_RETRIEVED_SUCCESS; +import static org.sopt.article.api.code.ArticleSuccessCode.ARTICLE_CREATED_SUCCESS; +import static org.sopt.article.api.code.ArticleSuccessCode.ARTICLE_LIST_RETRIEVED_SUCCESS; +import static org.sopt.article.api.code.ArticleSuccessCode.ARTICLE_RETRIEVED_SUCCESS; import lombok.RequiredArgsConstructor; import org.sopt.article.api.dto.request.ArticleCreateRequest; @@ -18,7 +18,7 @@ import org.sopt.article.application.port.in.ArticleFindAllUsecase; import org.sopt.article.application.port.in.ArticleFindOneUsecase; import org.sopt.article.application.port.in.ArticleWriteUsecase; -import org.sopt.global.response.ApiResponseBody; +import org.sopt.global.response.dto.ApiResponseBody; import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.GetMapping; diff --git a/src/main/java/org/sopt/article/api/code/ArticleSuccessCode.java b/src/main/java/org/sopt/article/api/code/ArticleSuccessCode.java new file mode 100644 index 0000000..5fcf8a1 --- /dev/null +++ b/src/main/java/org/sopt/article/api/code/ArticleSuccessCode.java @@ -0,0 +1,23 @@ +package org.sopt.article.api.code; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.global.response.code.SuccessCode; + +@Getter +@RequiredArgsConstructor +public enum ArticleSuccessCode implements SuccessCode { + + // 200 OK + ARTICLE_RETRIEVED_SUCCESS(200, "ART_200_001", "특정 게시글 조회를 성공적으로 완료했습니다."), + ARTICLE_LIST_RETRIEVED_SUCCESS(200, "ART_200_002", "게시글 전체 조회가 성공적으로 완료되었습니다."), + + // 201 CREATED + ARTICLE_CREATED_SUCCESS(201, "ART_201_001", "게시물이 성공적으로 생성되었습니다."), + + ; + + private final int status; + private final String code; + private final String message; +} diff --git a/src/main/java/org/sopt/article/api/dto/request/ArticleUpdateRequest.java b/src/main/java/org/sopt/article/api/dto/request/ArticleUpdateRequest.java new file mode 100644 index 0000000..8763dfb --- /dev/null +++ b/src/main/java/org/sopt/article/api/dto/request/ArticleUpdateRequest.java @@ -0,0 +1,7 @@ +package org.sopt.article.api.dto.request; + +public record ArticleUpdateRequest( + String content +) { + +} diff --git a/src/main/java/org/sopt/article/application/service/ArticleFindOneService.java b/src/main/java/org/sopt/article/application/service/ArticleFindOneService.java index 67ee484..8595e95 100644 --- a/src/main/java/org/sopt/article/application/service/ArticleFindOneService.java +++ b/src/main/java/org/sopt/article/application/service/ArticleFindOneService.java @@ -1,6 +1,6 @@ package org.sopt.article.application.service; -import static org.sopt.global.exception.ErrorCode.ARTICLE_NOT_FOUND; +import static org.sopt.article.domain.exception.ArticleErrorCode.ARTICLE_NOT_FOUND; import lombok.RequiredArgsConstructor; import org.sopt.article.application.dto.result.ArticleFindOneResult; diff --git a/src/main/java/org/sopt/article/application/service/ArticleWriteService.java b/src/main/java/org/sopt/article/application/service/ArticleWriteService.java index 963206b..7a7df77 100644 --- a/src/main/java/org/sopt/article/application/service/ArticleWriteService.java +++ b/src/main/java/org/sopt/article/application/service/ArticleWriteService.java @@ -1,7 +1,7 @@ package org.sopt.article.application.service; -import static org.sopt.global.exception.ErrorCode.ARTICLE_BY_NAME_ALREADY_EXISTS; -import static org.sopt.global.exception.ErrorCode.MEMBER_NOT_FOUND; +import static org.sopt.article.domain.exception.ArticleErrorCode.ARTICLE_BY_NAME_ALREADY_EXISTS; +import static org.sopt.member.domain.exception.MemberErrorCode.MEMBER_NOT_FOUND; import lombok.RequiredArgsConstructor; import org.sopt.article.application.dto.command.ArticleWriteCommand; @@ -12,14 +12,14 @@ import org.sopt.article.domain.port.out.ArticleRepository; import org.sopt.member.domain.exception.MemberException; import org.sopt.member.domain.model.Member; -import org.sopt.member.domain.port.out.MemberRepositoryPort; +import org.sopt.member.domain.port.out.MemberRepository; import org.springframework.stereotype.Service; @RequiredArgsConstructor @Service public class ArticleWriteService implements ArticleWriteUsecase { - private final MemberRepositoryPort memberRepositoryPort; + private final MemberRepository memberRepository; private final ArticleRepository articleRepository; public ArticleCreateResult writeArticle(ArticleWriteCommand articleWriteCommand) { @@ -27,7 +27,7 @@ public ArticleCreateResult writeArticle(ArticleWriteCommand articleWriteCommand) throw new ArticleException(ARTICLE_BY_NAME_ALREADY_EXISTS); } - Member member = memberRepositoryPort.findById(articleWriteCommand.memberId()) + Member member = memberRepository.findById(articleWriteCommand.memberId()) .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); Article article = Article.create( member, diff --git a/src/main/java/org/sopt/article/domain/exception/ArticleErrorCode.java b/src/main/java/org/sopt/article/domain/exception/ArticleErrorCode.java new file mode 100644 index 0000000..507d693 --- /dev/null +++ b/src/main/java/org/sopt/article/domain/exception/ArticleErrorCode.java @@ -0,0 +1,21 @@ +package org.sopt.article.domain.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.global.exception.ErrorCode; + +@Getter +@RequiredArgsConstructor +public enum ArticleErrorCode implements ErrorCode { + + // 404 NOT FOUND + ARTICLE_NOT_FOUND(404, "ART_404_001", "존재하지 않는 게시물입니다."), + + // 409 CONFLICT + ARTICLE_BY_NAME_ALREADY_EXISTS(409, "ART_409_001", "해당 제목으로 작성된 게시글이 이미 존재합니다."); + + private final int status; + private final String code; + private final String message; + +} diff --git a/src/main/java/org/sopt/article/domain/model/Article.java b/src/main/java/org/sopt/article/domain/model/Article.java index db4f424..76068ba 100644 --- a/src/main/java/org/sopt/article/domain/model/Article.java +++ b/src/main/java/org/sopt/article/domain/model/Article.java @@ -29,7 +29,7 @@ public class Article extends BaseEntity { private Long id; @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "memberId") + @JoinColumn(name = "member_id") private Member member; @Enumerated(EnumType.STRING) diff --git a/src/main/java/org/sopt/article/domain/port/out/ArticleRepository.java b/src/main/java/org/sopt/article/domain/port/out/ArticleRepository.java index 5bc3701..865a317 100644 --- a/src/main/java/org/sopt/article/domain/port/out/ArticleRepository.java +++ b/src/main/java/org/sopt/article/domain/port/out/ArticleRepository.java @@ -1,11 +1,13 @@ package org.sopt.article.domain.port.out; +import java.util.List; +import java.util.Optional; import org.sopt.article.domain.model.Article; -import org.springframework.data.jpa.repository.JpaRepository; -import org.springframework.stereotype.Repository; -@Repository -public interface ArticleRepository extends JpaRepository { +public interface ArticleRepository { + Article save(Article article); boolean existsByTitle(String title); + Optional
findById(Long id); + List
findAll(); } diff --git a/src/main/java/org/sopt/article/infrastructure/jpa/ArticleJpaRepository.java b/src/main/java/org/sopt/article/infrastructure/jpa/ArticleJpaRepository.java new file mode 100644 index 0000000..326dd56 --- /dev/null +++ b/src/main/java/org/sopt/article/infrastructure/jpa/ArticleJpaRepository.java @@ -0,0 +1,10 @@ +package org.sopt.article.infrastructure.jpa; + +import org.sopt.article.domain.model.Article; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ArticleJpaRepository extends JpaRepository { + + boolean existsByTitle(String title); + +} diff --git a/src/main/java/org/sopt/article/infrastructure/jpa/ArticleRepositoryAdapter.java b/src/main/java/org/sopt/article/infrastructure/jpa/ArticleRepositoryAdapter.java new file mode 100644 index 0000000..df0ed2e --- /dev/null +++ b/src/main/java/org/sopt/article/infrastructure/jpa/ArticleRepositoryAdapter.java @@ -0,0 +1,36 @@ +package org.sopt.article.infrastructure.jpa; + +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.sopt.article.domain.model.Article; +import org.sopt.article.domain.port.out.ArticleRepository; +import org.springframework.stereotype.Repository; + +@RequiredArgsConstructor +@Repository +public class ArticleRepositoryAdapter implements ArticleRepository { + + private final ArticleJpaRepository jpa; + + @Override + public Article save(Article article) { + return jpa.save(article); + } + + @Override + public boolean existsByTitle(String title){ + return jpa.existsByTitle(title); + } + + @Override + public Optional
findById(Long id) { + return jpa.findById(id); + } + + @Override + public List
findAll() { + return jpa.findAll(); + } + +} diff --git a/src/main/java/org/sopt/comment/api/CommentController.java b/src/main/java/org/sopt/comment/api/CommentController.java new file mode 100644 index 0000000..eb8f520 --- /dev/null +++ b/src/main/java/org/sopt/comment/api/CommentController.java @@ -0,0 +1,119 @@ +package org.sopt.comment.api; + +import static org.sopt.comment.api.code.CommentSuccessCode.COMMENT_CREATED_SUCCESS; +import static org.sopt.comment.api.code.CommentSuccessCode.COMMENT_DELETE_SUCCESS; +import static org.sopt.comment.api.code.CommentSuccessCode.COMMENT_LIST_RETRIEVED_SUCCESS; +import static org.sopt.comment.api.code.CommentSuccessCode.COMMENT_UPDATED_SUCCESS; +import static org.springframework.http.HttpStatus.CREATED; +import static org.springframework.http.HttpStatus.OK; + +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; +import org.sopt.comment.api.dto.request.CreateCommentRequest; +import org.sopt.comment.api.dto.request.UpdateCommentRequest; +import org.sopt.comment.api.dto.response.CreateCommentResponse; +import org.sopt.comment.api.dto.response.GetCommentListResponse; +import org.sopt.comment.api.dto.response.GetCommentMetaResponse; +import org.sopt.comment.api.dto.response.UpdateCommentResponse; +import org.sopt.comment.api.mapper.CommentRequestMapper; +import org.sopt.comment.api.mapper.CommentResponseMapper; +import org.sopt.comment.application.dto.command.CreateCommentCommand; +import org.sopt.comment.application.dto.command.UpdateCommentCommand; +import org.sopt.comment.application.dto.result.CreateCommentResult; +import org.sopt.comment.application.dto.result.GetCommentListResult; +import org.sopt.comment.application.dto.result.UpdateCommentResult; +import org.sopt.comment.application.port.in.CreateCommentUsecase; +import org.sopt.comment.application.port.in.DeleteCommentUsecase; +import org.sopt.comment.application.port.in.GetCommentListUsecase; +import org.sopt.comment.application.port.in.UpdateCommentUsecase; +import org.sopt.global.response.dto.ApiResponseBody; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RequestMapping("/api/v1") +@RequiredArgsConstructor +@RestController +public class CommentController { + + private final CreateCommentUsecase createCommentUsecase; + private final UpdateCommentUsecase updateCommentUsecase; + private final GetCommentListUsecase getCommentListUsecase; + private final DeleteCommentUsecase deleteCommentUsecase; + + @PostMapping("/articles/{articleId}/comments") + public ResponseEntity> createComment( + @RequestHeader Long userId, + @PathVariable Long articleId, + @Valid @RequestBody CreateCommentRequest createCommentRequest + ) { + CreateCommentCommand createCommentCommand = CommentRequestMapper + .toCommentCreateCommand(userId, articleId, createCommentRequest); + CreateCommentResult commentCreateResult = createCommentUsecase + .writeComment(createCommentCommand); + CreateCommentResponse createCommentResponse = CommentResponseMapper + .toCreateResponse(commentCreateResult); + + return ResponseEntity.status(CREATED) + .body(ApiResponseBody.created(COMMENT_CREATED_SUCCESS, createCommentResponse)); + } + + @GetMapping("/articles/{articleId}/comments") + public ResponseEntity> getCommentList( + @PathVariable Long articleId, + @RequestParam(defaultValue = "0") int page, + @RequestParam(defaultValue = "10") int size + ) { + GetCommentListResult getCommentListResult = getCommentListUsecase + .findByArticleId(articleId, page, size); + GetCommentListResponse getCommentListResponse = CommentResponseMapper.toGetListResponse( + getCommentListResult + ); + GetCommentMetaResponse getCommentMetaResponse = GetCommentMetaResponse + .create(getCommentListResult.getCommentMetaResult()); + + return ResponseEntity.status(OK) + .body(ApiResponseBody.ok( + COMMENT_LIST_RETRIEVED_SUCCESS, + getCommentListResponse, + getCommentMetaResponse + )); + } + + @PatchMapping("/comments/{commentId}") + public ResponseEntity> updateComment( + @RequestHeader Long userId, + @PathVariable Long commentId, + @Valid @RequestBody UpdateCommentRequest updateCommentRequest + ) { + UpdateCommentCommand updateCommentCommand = CommentRequestMapper + .toCommentUpdateCommand(userId, commentId, updateCommentRequest); + UpdateCommentResult commentUpdateResult = updateCommentUsecase + .updateComment(updateCommentCommand); + UpdateCommentResponse updateCommentResponse = CommentResponseMapper + .toUpdateResponse(commentUpdateResult); + + return ResponseEntity.status(OK) + .body(ApiResponseBody.ok(COMMENT_UPDATED_SUCCESS, updateCommentResponse)); + } + + @DeleteMapping("/comments/{commentId}") + public ResponseEntity> deleteComment( + @RequestHeader Long userId, + @PathVariable Long commentId + ) { + deleteCommentUsecase.deleteComment(userId, commentId); + + return ResponseEntity.status(OK) + .body(ApiResponseBody.ok(COMMENT_DELETE_SUCCESS)); + } + +} diff --git a/src/main/java/org/sopt/comment/api/code/CommentSuccessCode.java b/src/main/java/org/sopt/comment/api/code/CommentSuccessCode.java new file mode 100644 index 0000000..fbfe927 --- /dev/null +++ b/src/main/java/org/sopt/comment/api/code/CommentSuccessCode.java @@ -0,0 +1,25 @@ +package org.sopt.comment.api.code; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.global.response.code.SuccessCode; + +@Getter +@RequiredArgsConstructor +public enum CommentSuccessCode implements SuccessCode { + + // 200 OK + COMMENT_UPDATED_SUCCESS(200, "COMMENT_200_001", "성공적으로 댓글을 수정했습니다."), + COMMENT_LIST_RETRIEVED_SUCCESS(200, "COMMENT_200_002", "성공적으로 해당 게시물의 댓글 목록을 조회했습니다."), + COMMENT_DELETE_SUCCESS(200, "COMMENT_200_003", "성공적으로 댓글을 삭제했습니다."), + + // 201 CREATED + COMMENT_CREATED_SUCCESS(201, "COMMENT_201_001", "성공적으로 댓글을 작성했습니다."), + + ; + + private final int status; + private final String code; + private final String message; + +} diff --git a/src/main/java/org/sopt/comment/api/dto/request/CreateCommentRequest.java b/src/main/java/org/sopt/comment/api/dto/request/CreateCommentRequest.java new file mode 100644 index 0000000..711800c --- /dev/null +++ b/src/main/java/org/sopt/comment/api/dto/request/CreateCommentRequest.java @@ -0,0 +1,12 @@ +package org.sopt.comment.api.dto.request; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.NotBlank; + +public record CreateCommentRequest( + @NotBlank + @Max(300) + String content +) { + +} diff --git a/src/main/java/org/sopt/comment/api/dto/request/UpdateCommentRequest.java b/src/main/java/org/sopt/comment/api/dto/request/UpdateCommentRequest.java new file mode 100644 index 0000000..a199797 --- /dev/null +++ b/src/main/java/org/sopt/comment/api/dto/request/UpdateCommentRequest.java @@ -0,0 +1,12 @@ +package org.sopt.comment.api.dto.request; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.NotBlank; + +public record UpdateCommentRequest( + @NotBlank + @Max(300) + String content +) { + +} diff --git a/src/main/java/org/sopt/comment/api/dto/response/CreateCommentResponse.java b/src/main/java/org/sopt/comment/api/dto/response/CreateCommentResponse.java new file mode 100644 index 0000000..31eb563 --- /dev/null +++ b/src/main/java/org/sopt/comment/api/dto/response/CreateCommentResponse.java @@ -0,0 +1,7 @@ +package org.sopt.comment.api.dto.response; + +public record CreateCommentResponse( + Long commentId +){ + +} diff --git a/src/main/java/org/sopt/comment/api/dto/response/GetCommentAuthorResponse.java b/src/main/java/org/sopt/comment/api/dto/response/GetCommentAuthorResponse.java new file mode 100644 index 0000000..e9c8118 --- /dev/null +++ b/src/main/java/org/sopt/comment/api/dto/response/GetCommentAuthorResponse.java @@ -0,0 +1,17 @@ +package org.sopt.comment.api.dto.response; + +import org.sopt.comment.application.dto.result.GetCommentAuthorResult; + +public record GetCommentAuthorResponse( + Long id, + String name +) { + + public static GetCommentAuthorResponse create(GetCommentAuthorResult getCommentAuthorResult) { + return new GetCommentAuthorResponse( + getCommentAuthorResult.id(), + getCommentAuthorResult.name() + ); + } + +} diff --git a/src/main/java/org/sopt/comment/api/dto/response/GetCommentContentResponse.java b/src/main/java/org/sopt/comment/api/dto/response/GetCommentContentResponse.java new file mode 100644 index 0000000..f21a915 --- /dev/null +++ b/src/main/java/org/sopt/comment/api/dto/response/GetCommentContentResponse.java @@ -0,0 +1,20 @@ +package org.sopt.comment.api.dto.response; + +import org.sopt.comment.application.dto.result.GetCommentContentResult; + +public record GetCommentContentResponse( + Long id, + GetCommentAuthorResponse author, + String content +) { + + public static GetCommentContentResponse create( + GetCommentContentResult getCommentContentResult + ) { + return new GetCommentContentResponse( + getCommentContentResult.id(), + GetCommentAuthorResponse.create(getCommentContentResult.author()), + getCommentContentResult.content() + ); + } +} diff --git a/src/main/java/org/sopt/comment/api/dto/response/GetCommentListResponse.java b/src/main/java/org/sopt/comment/api/dto/response/GetCommentListResponse.java new file mode 100644 index 0000000..43409fb --- /dev/null +++ b/src/main/java/org/sopt/comment/api/dto/response/GetCommentListResponse.java @@ -0,0 +1,9 @@ +package org.sopt.comment.api.dto.response; + +import java.util.List; + +public record GetCommentListResponse( + List comments +){ + +} diff --git a/src/main/java/org/sopt/comment/api/dto/response/GetCommentMetaResponse.java b/src/main/java/org/sopt/comment/api/dto/response/GetCommentMetaResponse.java new file mode 100644 index 0000000..430b9b7 --- /dev/null +++ b/src/main/java/org/sopt/comment/api/dto/response/GetCommentMetaResponse.java @@ -0,0 +1,24 @@ +package org.sopt.comment.api.dto.response; + +import org.sopt.comment.application.dto.result.GetCommentMetaResult; + +public record GetCommentMetaResponse( + int totalPages, + long totalElements, + int size, + int number, + boolean isFirst, + boolean isLast +) { + + public static GetCommentMetaResponse create(GetCommentMetaResult getCommentMetaResult) { + return new GetCommentMetaResponse( + getCommentMetaResult.totalPages(), + getCommentMetaResult.totalElements(), + getCommentMetaResult.size(), + getCommentMetaResult.number(), + getCommentMetaResult.isFirst(), + getCommentMetaResult.isLast() + ); + } +} diff --git a/src/main/java/org/sopt/comment/api/dto/response/UpdateCommentResponse.java b/src/main/java/org/sopt/comment/api/dto/response/UpdateCommentResponse.java new file mode 100644 index 0000000..6f4e21b --- /dev/null +++ b/src/main/java/org/sopt/comment/api/dto/response/UpdateCommentResponse.java @@ -0,0 +1,7 @@ +package org.sopt.comment.api.dto.response; + +public record UpdateCommentResponse( + Long commentId +) { + +} diff --git a/src/main/java/org/sopt/comment/api/mapper/CommentRequestMapper.java b/src/main/java/org/sopt/comment/api/mapper/CommentRequestMapper.java new file mode 100644 index 0000000..b2fc51e --- /dev/null +++ b/src/main/java/org/sopt/comment/api/mapper/CommentRequestMapper.java @@ -0,0 +1,34 @@ +package org.sopt.comment.api.mapper; + +import org.sopt.comment.api.dto.request.CreateCommentRequest; +import org.sopt.comment.api.dto.request.UpdateCommentRequest; +import org.sopt.comment.application.dto.command.CreateCommentCommand; +import org.sopt.comment.application.dto.command.UpdateCommentCommand; + +public class CommentRequestMapper { + + public static CreateCommentCommand toCommentCreateCommand( + Long userId, + Long articleId, + CreateCommentRequest createCommentRequest + ) { + return new CreateCommentCommand( + userId, + articleId, + createCommentRequest.content() + ); + } + + public static UpdateCommentCommand toCommentUpdateCommand( + Long userId, + Long articleId, + UpdateCommentRequest updateCommentRequest + ) { + return new UpdateCommentCommand( + userId, + articleId, + updateCommentRequest.content() + ); + } + +} diff --git a/src/main/java/org/sopt/comment/api/mapper/CommentResponseMapper.java b/src/main/java/org/sopt/comment/api/mapper/CommentResponseMapper.java new file mode 100644 index 0000000..4bf1d38 --- /dev/null +++ b/src/main/java/org/sopt/comment/api/mapper/CommentResponseMapper.java @@ -0,0 +1,34 @@ +package org.sopt.comment.api.mapper; + +import org.sopt.comment.api.dto.response.CreateCommentResponse; +import org.sopt.comment.api.dto.response.GetCommentContentResponse; +import org.sopt.comment.api.dto.response.GetCommentListResponse; +import org.sopt.comment.api.dto.response.UpdateCommentResponse; +import org.sopt.comment.application.dto.result.CreateCommentResult; +import org.sopt.comment.application.dto.result.GetCommentListResult; +import org.sopt.comment.application.dto.result.UpdateCommentResult; + +public class CommentResponseMapper { + + public static CreateCommentResponse toCreateResponse( + CreateCommentResult commentCreateResult + ) { + return new CreateCommentResponse(commentCreateResult.commentId()); + } + + public static UpdateCommentResponse toUpdateResponse( + UpdateCommentResult commentUpdateResult + ) { + return new UpdateCommentResponse(commentUpdateResult.commentId()); + } + + public static GetCommentListResponse toGetListResponse( + GetCommentListResult getCommentListResult + ) { + return new GetCommentListResponse( + getCommentListResult.getCommentContentResult().stream() + .map(GetCommentContentResponse::create) + .toList() + ); + } +} diff --git a/src/main/java/org/sopt/comment/application/dto/command/CreateCommentCommand.java b/src/main/java/org/sopt/comment/application/dto/command/CreateCommentCommand.java new file mode 100644 index 0000000..d336355 --- /dev/null +++ b/src/main/java/org/sopt/comment/application/dto/command/CreateCommentCommand.java @@ -0,0 +1,9 @@ +package org.sopt.comment.application.dto.command; + +public record CreateCommentCommand( + Long userId, + Long articleId, + String content +) { + +} diff --git a/src/main/java/org/sopt/comment/application/dto/command/UpdateCommentCommand.java b/src/main/java/org/sopt/comment/application/dto/command/UpdateCommentCommand.java new file mode 100644 index 0000000..c360633 --- /dev/null +++ b/src/main/java/org/sopt/comment/application/dto/command/UpdateCommentCommand.java @@ -0,0 +1,9 @@ +package org.sopt.comment.application.dto.command; + +public record UpdateCommentCommand( + Long userId, + Long commentId, + String content +) { + +} diff --git a/src/main/java/org/sopt/comment/application/dto/result/CreateCommentResult.java b/src/main/java/org/sopt/comment/application/dto/result/CreateCommentResult.java new file mode 100644 index 0000000..20456d3 --- /dev/null +++ b/src/main/java/org/sopt/comment/application/dto/result/CreateCommentResult.java @@ -0,0 +1,12 @@ +package org.sopt.comment.application.dto.result; + +import org.sopt.comment.domain.model.Comment; + +public record CreateCommentResult( + Long commentId +) { + + public static CreateCommentResult create(Comment comment) { + return new CreateCommentResult(comment.getId()); + } +} diff --git a/src/main/java/org/sopt/comment/application/dto/result/GetCommentAuthorResult.java b/src/main/java/org/sopt/comment/application/dto/result/GetCommentAuthorResult.java new file mode 100644 index 0000000..6febdfe --- /dev/null +++ b/src/main/java/org/sopt/comment/application/dto/result/GetCommentAuthorResult.java @@ -0,0 +1,16 @@ +package org.sopt.comment.application.dto.result; + +import org.sopt.member.domain.model.Member; + +public record GetCommentAuthorResult( + Long id, + String name +) { + + public static GetCommentAuthorResult create(Member member) { + return new GetCommentAuthorResult( + member.getId(), + member.getName() + ); + } +} diff --git a/src/main/java/org/sopt/comment/application/dto/result/GetCommentContentResult.java b/src/main/java/org/sopt/comment/application/dto/result/GetCommentContentResult.java new file mode 100644 index 0000000..c56dabf --- /dev/null +++ b/src/main/java/org/sopt/comment/application/dto/result/GetCommentContentResult.java @@ -0,0 +1,19 @@ +package org.sopt.comment.application.dto.result; + +import org.sopt.comment.domain.model.Comment; + +public record GetCommentContentResult( + Long id, + GetCommentAuthorResult author, + String content +) { + + public static GetCommentContentResult create(Comment comment) { + return new GetCommentContentResult( + comment.getId(), + GetCommentAuthorResult.create(comment.getAuthor()), + comment.getContent() + ); + } + +} diff --git a/src/main/java/org/sopt/comment/application/dto/result/GetCommentListResult.java b/src/main/java/org/sopt/comment/application/dto/result/GetCommentListResult.java new file mode 100644 index 0000000..9cd6b15 --- /dev/null +++ b/src/main/java/org/sopt/comment/application/dto/result/GetCommentListResult.java @@ -0,0 +1,20 @@ +package org.sopt.comment.application.dto.result; + +import java.util.List; +import org.sopt.comment.domain.model.Comment; +import org.springframework.data.domain.Page; + +public record GetCommentListResult( + List getCommentContentResult, + GetCommentMetaResult getCommentMetaResult +) { + + public static GetCommentListResult create(Page commentPage) { + return new GetCommentListResult( + commentPage.getContent().stream() + .map(GetCommentContentResult::create) + .toList(), + GetCommentMetaResult.create(commentPage) + ); + } +} diff --git a/src/main/java/org/sopt/comment/application/dto/result/GetCommentMetaResult.java b/src/main/java/org/sopt/comment/application/dto/result/GetCommentMetaResult.java new file mode 100644 index 0000000..7f3d17d --- /dev/null +++ b/src/main/java/org/sopt/comment/application/dto/result/GetCommentMetaResult.java @@ -0,0 +1,25 @@ +package org.sopt.comment.application.dto.result; + +import org.sopt.comment.domain.model.Comment; +import org.springframework.data.domain.Page; + +public record GetCommentMetaResult( + int totalPages, + long totalElements, + int size, + int number, + boolean isFirst, + boolean isLast +) { + + public static GetCommentMetaResult create(Page commentPage) { + return new GetCommentMetaResult( + commentPage.getTotalPages(), + commentPage.getTotalElements(), + commentPage.getSize(), + commentPage.getNumber(), + commentPage.isFirst(), + commentPage.isLast() + ); + } +} diff --git a/src/main/java/org/sopt/comment/application/dto/result/UpdateCommentResult.java b/src/main/java/org/sopt/comment/application/dto/result/UpdateCommentResult.java new file mode 100644 index 0000000..50192fe --- /dev/null +++ b/src/main/java/org/sopt/comment/application/dto/result/UpdateCommentResult.java @@ -0,0 +1,12 @@ +package org.sopt.comment.application.dto.result; + +import org.sopt.comment.domain.model.Comment; + +public record UpdateCommentResult( + Long commentId +) { + + public static UpdateCommentResult create(Comment comment) { + return new UpdateCommentResult(comment.getId()); + } +} diff --git a/src/main/java/org/sopt/comment/application/port/in/CreateCommentUsecase.java b/src/main/java/org/sopt/comment/application/port/in/CreateCommentUsecase.java new file mode 100644 index 0000000..3e27cbf --- /dev/null +++ b/src/main/java/org/sopt/comment/application/port/in/CreateCommentUsecase.java @@ -0,0 +1,9 @@ +package org.sopt.comment.application.port.in; + +import org.sopt.comment.application.dto.command.CreateCommentCommand; +import org.sopt.comment.application.dto.result.CreateCommentResult; + +public interface CreateCommentUsecase { + + CreateCommentResult writeComment(CreateCommentCommand createCommentCommand); +} diff --git a/src/main/java/org/sopt/comment/application/port/in/DeleteCommentUsecase.java b/src/main/java/org/sopt/comment/application/port/in/DeleteCommentUsecase.java new file mode 100644 index 0000000..489dd43 --- /dev/null +++ b/src/main/java/org/sopt/comment/application/port/in/DeleteCommentUsecase.java @@ -0,0 +1,6 @@ +package org.sopt.comment.application.port.in; + +public interface DeleteCommentUsecase { + + void deleteComment(Long userId, Long commentId); +} diff --git a/src/main/java/org/sopt/comment/application/port/in/GetCommentListUsecase.java b/src/main/java/org/sopt/comment/application/port/in/GetCommentListUsecase.java new file mode 100644 index 0000000..ba5bd83 --- /dev/null +++ b/src/main/java/org/sopt/comment/application/port/in/GetCommentListUsecase.java @@ -0,0 +1,8 @@ +package org.sopt.comment.application.port.in; + +import org.sopt.comment.application.dto.result.GetCommentListResult; + +public interface GetCommentListUsecase { + + GetCommentListResult findByArticleId(Long articleId, int page, int size); +} diff --git a/src/main/java/org/sopt/comment/application/port/in/UpdateCommentUsecase.java b/src/main/java/org/sopt/comment/application/port/in/UpdateCommentUsecase.java new file mode 100644 index 0000000..2513cf4 --- /dev/null +++ b/src/main/java/org/sopt/comment/application/port/in/UpdateCommentUsecase.java @@ -0,0 +1,9 @@ +package org.sopt.comment.application.port.in; + +import org.sopt.comment.application.dto.command.UpdateCommentCommand; +import org.sopt.comment.application.dto.result.UpdateCommentResult; + +public interface UpdateCommentUsecase { + + UpdateCommentResult updateComment(UpdateCommentCommand updateCommentCommand); +} diff --git a/src/main/java/org/sopt/comment/application/service/CreateCommentService.java b/src/main/java/org/sopt/comment/application/service/CreateCommentService.java new file mode 100644 index 0000000..6829ce9 --- /dev/null +++ b/src/main/java/org/sopt/comment/application/service/CreateCommentService.java @@ -0,0 +1,41 @@ +package org.sopt.comment.application.service; + +import static org.sopt.article.domain.exception.ArticleErrorCode.ARTICLE_NOT_FOUND; +import static org.sopt.member.domain.exception.MemberErrorCode.MEMBER_NOT_FOUND; + +import lombok.RequiredArgsConstructor; +import org.sopt.article.domain.exception.ArticleException; +import org.sopt.article.domain.model.Article; +import org.sopt.article.domain.port.out.ArticleRepository; +import org.sopt.comment.application.dto.command.CreateCommentCommand; +import org.sopt.comment.application.dto.result.CreateCommentResult; +import org.sopt.comment.application.port.in.CreateCommentUsecase; +import org.sopt.comment.domain.model.Comment; +import org.sopt.comment.domain.port.out.CommentRepository; +import org.sopt.member.domain.exception.MemberException; +import org.sopt.member.domain.model.Member; +import org.sopt.member.domain.port.out.MemberRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@RequiredArgsConstructor +@Service +public class CreateCommentService implements CreateCommentUsecase { + + private final MemberRepository memberRepository; + private final ArticleRepository articleRepository; + private final CommentRepository commentRepository; + + @Override + public CreateCommentResult writeComment(CreateCommentCommand createCommentCommand) { + Member member = memberRepository.findById(createCommentCommand.userId()) + .orElseThrow(() -> new MemberException(MEMBER_NOT_FOUND)); + Article article = articleRepository.findById(createCommentCommand.articleId()) + .orElseThrow(() -> new ArticleException(ARTICLE_NOT_FOUND)); + Comment comment = Comment.create(article, member, createCommentCommand.content()); + commentRepository.save(comment); + + return CreateCommentResult.create(comment); + } +} diff --git a/src/main/java/org/sopt/comment/application/service/DeleteCommentService.java b/src/main/java/org/sopt/comment/application/service/DeleteCommentService.java new file mode 100644 index 0000000..4eb4592 --- /dev/null +++ b/src/main/java/org/sopt/comment/application/service/DeleteCommentService.java @@ -0,0 +1,39 @@ +package org.sopt.comment.application.service; + +import static org.sopt.comment.domain.exception.CommentErrorCode.COMMENT_DELETE_FORBIDDEN; +import static org.sopt.comment.domain.exception.CommentErrorCode.COMMENT_NOT_FOUND; + +import lombok.RequiredArgsConstructor; +import org.sopt.comment.application.port.in.DeleteCommentUsecase; +import org.sopt.comment.domain.exception.CommentException; +import org.sopt.comment.domain.model.Comment; +import org.sopt.comment.domain.port.out.CommentRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@RequiredArgsConstructor +@Service +public class DeleteCommentService implements DeleteCommentUsecase { + + private final CommentRepository commentRepository; + + @Override + public void deleteComment(Long userId, Long commentId) { + Comment comment = getExistingComment(commentId); + validateCanDelete(userId, comment); + commentRepository.deleteById(commentId); + } + + private Comment getExistingComment(Long commentId) { + return commentRepository.findById(commentId) + .orElseThrow(() -> new CommentException(COMMENT_NOT_FOUND)); + } + + private void validateCanDelete(Long userId, Comment comment) { + if(!comment.isOwner(userId)) { + throw new CommentException(COMMENT_DELETE_FORBIDDEN); + } + } + +} diff --git a/src/main/java/org/sopt/comment/application/service/GetCommentListService.java b/src/main/java/org/sopt/comment/application/service/GetCommentListService.java new file mode 100644 index 0000000..0537d51 --- /dev/null +++ b/src/main/java/org/sopt/comment/application/service/GetCommentListService.java @@ -0,0 +1,41 @@ +package org.sopt.comment.application.service; + +import static org.sopt.article.domain.exception.ArticleErrorCode.ARTICLE_NOT_FOUND; + +import lombok.RequiredArgsConstructor; +import org.sopt.article.domain.exception.ArticleException; +import org.sopt.article.domain.model.Article; +import org.sopt.article.domain.port.out.ArticleRepository; +import org.sopt.comment.application.dto.result.GetCommentListResult; +import org.sopt.comment.application.port.in.GetCommentListUsecase; +import org.sopt.comment.domain.model.Comment; +import org.sopt.comment.domain.port.out.CommentRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Transactional(readOnly = true) +@RequiredArgsConstructor +@Service +public class GetCommentListService implements GetCommentListUsecase { + + private final ArticleRepository articleRepository; + private final CommentRepository commentRepository; + + @Override + public GetCommentListResult findByArticleId(Long articleId, int page, int size) { + Article article = getExistingArticle(articleId); + Pageable pageable = PageRequest.of(page, size); + Page commentPage = commentRepository.findByArticle(article, pageable); + + return GetCommentListResult.create(commentPage); + } + + private Article getExistingArticle(Long articleId) { + return articleRepository.findById(articleId) + .orElseThrow(() -> new ArticleException(ARTICLE_NOT_FOUND)); + } + +} diff --git a/src/main/java/org/sopt/comment/application/service/UpdateCommentService.java b/src/main/java/org/sopt/comment/application/service/UpdateCommentService.java new file mode 100644 index 0000000..68ce388 --- /dev/null +++ b/src/main/java/org/sopt/comment/application/service/UpdateCommentService.java @@ -0,0 +1,29 @@ +package org.sopt.comment.application.service; + +import static org.sopt.comment.domain.exception.CommentErrorCode.COMMENT_NOT_FOUND; + +import lombok.RequiredArgsConstructor; +import org.sopt.comment.application.dto.command.UpdateCommentCommand; +import org.sopt.comment.application.dto.result.UpdateCommentResult; +import org.sopt.comment.application.port.in.UpdateCommentUsecase; +import org.sopt.comment.domain.exception.CommentException; +import org.sopt.comment.domain.model.Comment; +import org.sopt.comment.domain.port.out.CommentRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Transactional +@RequiredArgsConstructor +@Service +public class UpdateCommentService implements UpdateCommentUsecase { + + private final CommentRepository commentRepository; + + public UpdateCommentResult updateComment(UpdateCommentCommand updateCommentCommand) { + Comment comment = commentRepository.findById(updateCommentCommand.commentId()) + .orElseThrow(() -> new CommentException(COMMENT_NOT_FOUND)); + comment.update(updateCommentCommand.userId(), updateCommentCommand.content()); + + return UpdateCommentResult.create(comment); + } +} diff --git a/src/main/java/org/sopt/comment/domain/exception/CommentErrorCode.java b/src/main/java/org/sopt/comment/domain/exception/CommentErrorCode.java new file mode 100644 index 0000000..6ba0f7f --- /dev/null +++ b/src/main/java/org/sopt/comment/domain/exception/CommentErrorCode.java @@ -0,0 +1,29 @@ +package org.sopt.comment.domain.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.global.exception.ErrorCode; + +@Getter +@RequiredArgsConstructor +public enum CommentErrorCode implements ErrorCode { + + // 400 BAD REQUEST + ARTICLE_REQUIRED(400, "COMMENT_400_001", "관련된 아티클이 필요합니다."), + MEMBER_REQUIRED(400, "COMMENT_400_002", "댓글 작성자가 필요합니다."), + CONTENT_REQUIRED(400, "COMMENT_400_003", "댓글 본문이 필요합니다."), + CONTENT_TOO_LONG(400, "COMMENT_400_004", "댓글 본문 길이는 300자 이하여야 합니다."), + + // 403 FORBIDDEN + COMMENT_EDIT_FORBIDDEN(403, "COMMENT_403_001", "댓글 수정 권한이 없습니다."), + COMMENT_DELETE_FORBIDDEN(403, "COMMENT_403_002", "댓글 삭제 권한이 없습니다."), + + // 404 NOT FOUND + COMMENT_NOT_FOUND(404, "COMMENT_404_001", "해당 댓글이 존재하지 않습니다."), + + ; + + private final int status; + private final String code; + private final String message; +} diff --git a/src/main/java/org/sopt/comment/domain/exception/CommentException.java b/src/main/java/org/sopt/comment/domain/exception/CommentException.java new file mode 100644 index 0000000..598c67a --- /dev/null +++ b/src/main/java/org/sopt/comment/domain/exception/CommentException.java @@ -0,0 +1,11 @@ +package org.sopt.comment.domain.exception; + +import org.sopt.global.exception.BusinessException; +import org.sopt.global.exception.ErrorCode; + +public class CommentException extends BusinessException { + + public CommentException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/org/sopt/comment/domain/model/Comment.java b/src/main/java/org/sopt/comment/domain/model/Comment.java new file mode 100644 index 0000000..2f99a5a --- /dev/null +++ b/src/main/java/org/sopt/comment/domain/model/Comment.java @@ -0,0 +1,115 @@ +package org.sopt.comment.domain.model; + +import static org.sopt.comment.domain.exception.CommentErrorCode.ARTICLE_REQUIRED; +import static org.sopt.comment.domain.exception.CommentErrorCode.COMMENT_EDIT_FORBIDDEN; +import static org.sopt.comment.domain.exception.CommentErrorCode.CONTENT_REQUIRED; +import static org.sopt.comment.domain.exception.CommentErrorCode.CONTENT_TOO_LONG; +import static org.sopt.comment.domain.exception.CommentErrorCode.MEMBER_REQUIRED; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.JoinColumn; +import jakarta.persistence.ManyToOne; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; +import org.sopt.article.domain.model.Article; +import org.sopt.comment.domain.exception.CommentException; +import org.sopt.global.model.BaseEntity; +import org.sopt.member.domain.model.Member; + +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@Entity +public class Comment extends BaseEntity { + + private static final int MAX_CONTENT_LENGTH = 300; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "article_id") + private Article article; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member author; + + @Column(nullable = false, length = MAX_CONTENT_LENGTH) + private String content; + + @Builder(access = AccessLevel.PRIVATE) + private Comment(Article article, Member author, String content) { + validateComment(article, author, content); + this.article = article; + this.author = author; + this.content = content; + } + + public static Comment create(Article article, Member author, String content) { + return Comment.builder() + .article(article) + .author(author) + .content(content) + .build(); + } + + public void update(Long userId, String content) { + validateCanEdit(userId); + validateContent(content); + this.content = content; + } + + public boolean isOwner(Long userId) { + return this.author.getId().equals(userId); + } + + private void validateCanEdit(Long userId) { + if (!this.author.getId().equals(userId)) { + throw new CommentException(COMMENT_EDIT_FORBIDDEN); + } + } + + private void validateComment(Article article, Member author, String content) { + validateArticle(article); + validateAuthor(author); + validateContent(content); + } + + private void validateArticle(Article article) { + if (article == null) { + throw new CommentException(ARTICLE_REQUIRED); + } + } + + private void validateAuthor(Member author) { + if (author == null) { + throw new CommentException(MEMBER_REQUIRED); + } + } + + private void validateContent(String content) { + validateContentNotEmpty(content); + validateContentLength(content); + } + + private void validateContentNotEmpty(String content) { + if (content == null || content.isBlank()) { + throw new CommentException(CONTENT_REQUIRED); + } + } + + private void validateContentLength(String content) { + if (content.length() > MAX_CONTENT_LENGTH) { + throw new CommentException(CONTENT_TOO_LONG); + } + } + +} diff --git a/src/main/java/org/sopt/comment/domain/port/out/CommentRepository.java b/src/main/java/org/sopt/comment/domain/port/out/CommentRepository.java new file mode 100644 index 0000000..53c18d1 --- /dev/null +++ b/src/main/java/org/sopt/comment/domain/port/out/CommentRepository.java @@ -0,0 +1,18 @@ +package org.sopt.comment.domain.port.out; + +import java.util.Optional; +import org.sopt.article.domain.model.Article; +import org.sopt.comment.domain.model.Comment; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +public interface CommentRepository { + + void save(Comment comment); + + Optional findById(Long id); + + Page findByArticle(Article article, Pageable pageable); + + void deleteById(Long commentId); +} diff --git a/src/main/java/org/sopt/comment/infrastructure/jpa/CommentJpaRepository.java b/src/main/java/org/sopt/comment/infrastructure/jpa/CommentJpaRepository.java new file mode 100644 index 0000000..be0c9d3 --- /dev/null +++ b/src/main/java/org/sopt/comment/infrastructure/jpa/CommentJpaRepository.java @@ -0,0 +1,12 @@ +package org.sopt.comment.infrastructure.jpa; + +import org.sopt.article.domain.model.Article; +import org.sopt.comment.domain.model.Comment; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommentJpaRepository extends JpaRepository { + + Page findByArticle(Article article, Pageable pageable); +} diff --git a/src/main/java/org/sopt/comment/infrastructure/jpa/CommentRepositoryAdapter.java b/src/main/java/org/sopt/comment/infrastructure/jpa/CommentRepositoryAdapter.java new file mode 100644 index 0000000..14051c8 --- /dev/null +++ b/src/main/java/org/sopt/comment/infrastructure/jpa/CommentRepositoryAdapter.java @@ -0,0 +1,38 @@ +package org.sopt.comment.infrastructure.jpa; + +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.sopt.article.domain.model.Article; +import org.sopt.comment.domain.model.Comment; +import org.sopt.comment.domain.port.out.CommentRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Repository; + +@RequiredArgsConstructor +@Repository +public class CommentRepositoryAdapter implements CommentRepository { + + private final CommentJpaRepository jpa; + + @Override + public void save(Comment comment) { + jpa.save(comment); + } + + @Override + public Optional findById(Long id) { + return jpa.findById(id); + } + + @Override + public Page findByArticle(Article article, Pageable pageable) { + return jpa.findByArticle(article, pageable); + } + + @Override + public void deleteById(Long id) { + jpa.deleteById(id); + } + +} diff --git a/src/main/java/org/sopt/global/exception/ErrorCode.java b/src/main/java/org/sopt/global/exception/ErrorCode.java index 2793133..d6550dc 100644 --- a/src/main/java/org/sopt/global/exception/ErrorCode.java +++ b/src/main/java/org/sopt/global/exception/ErrorCode.java @@ -1,44 +1,7 @@ package org.sopt.global.exception; -import lombok.Getter; +import org.sopt.global.response.code.ResponseCode; -// TODO 도메인 별 분리 => 다음 주에 할게요 -@Getter -public enum ErrorCode { +public interface ErrorCode extends ResponseCode { - // =================== COMMON =================== - // 400 BAD REQUEST - INVALID_NUMBER_FORMAT(400, "COM_001", "숫자만 입력해주세요."), - INVALID_EMAIL_FORMAT(400, "COM_002", "잘못된 이메일 형식입니다."), - INVALID_DATE_FORMAT(400, "COM_005", "잘못된 날짜 형식입니다."), - INVALID_NULL_DATA(400, "COM_006", "빈 값은 허용되지 않습니다."), - INVALID_MAPPING_PARAMETER(400, "COM_007", "매핑할 수 없는 값입니다."), - - // 404 NOT FOUND - RESOURCE_NOT_FOUND(404, "COM_003", "존재하지 않는 리소스입니다."), - - // 500 INTERNAL SERVER ERROR - INTERNAL_SERVER_ERROR(500, "COM_004", "서버 내부 오류가 발생했습니다."), - - // =================== COMMON =================== - // 400 BAD REQUEST - AGE_MUST_UPPER_THAN_20(400, "MEM_003", "20세 미만은 가입할 수 없습니다."), - - // 404 NOT FOUND - MEMBER_NOT_FOUND(404, "MEM_001", "존재하지 않는 회원입니다."), - ARTICLE_NOT_FOUND(404, "ART_001", "존재하지 않는 게시물입니다."), - - // 409 CONFLICT - MEMBER_BY_EMAIL_ALREADY_EXISTS(409, "MEM_002", "해당 이메일로 가입된 회원이 이미 존재합니다."), - ARTICLE_BY_NAME_ALREADY_EXISTS(409, "ART_002", "해당 제목으로 작성된 게시글이 이미 존재합니다."); - - private final int status; - private final String code; - private final String message; - - ErrorCode(int status, String code, String message) { - this.status = status; - this.code = code; - this.message = message; - } } diff --git a/src/main/java/org/sopt/global/exception/GlobalErrorCode.java b/src/main/java/org/sopt/global/exception/GlobalErrorCode.java new file mode 100644 index 0000000..099504f --- /dev/null +++ b/src/main/java/org/sopt/global/exception/GlobalErrorCode.java @@ -0,0 +1,28 @@ +package org.sopt.global.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum GlobalErrorCode implements ErrorCode { + + // 400 BAD REQUEST + INVALID_NUMBER_FORMAT(400, "COM_400_001", "숫자만 입력해주세요."), + INVALID_EMAIL_FORMAT(400, "COM_400_002", "잘못된 이메일 형식입니다."), + INVALID_DATE_FORMAT(400, "COM_400_003", "잘못된 날짜 형식입니다."), + INVALID_NULL_DATA(400, "COM_400_004", "빈 값은 허용되지 않습니다."), + INVALID_MAPPING_PARAMETER(400, "COM_400_005", "매핑할 수 없는 값입니다."), + + // 404 NOT FOUND + RESOURCE_NOT_FOUND(404, "COM_404_001", "존재하지 않는 리소스입니다."), + + // 500 INTERNAL SERVER ERROR + INTERNAL_SERVER_ERROR(500, "COM_500_001", "서버 내부 오류가 발생했습니다."), + + ; + + private final int status; + private final String code; + private final String message; +} diff --git a/src/main/java/org/sopt/global/exception/GlobalExceptionHandler.java b/src/main/java/org/sopt/global/exception/GlobalExceptionHandler.java index 46acf86..9f0a778 100644 --- a/src/main/java/org/sopt/global/exception/GlobalExceptionHandler.java +++ b/src/main/java/org/sopt/global/exception/GlobalExceptionHandler.java @@ -1,8 +1,11 @@ package org.sopt.global.exception; +import static org.sopt.global.exception.GlobalErrorCode.INTERNAL_SERVER_ERROR; +import static org.sopt.global.exception.GlobalErrorCode.INVALID_MAPPING_PARAMETER; + import jakarta.servlet.http.HttpServletRequest; -import org.sopt.global.response.ApiResponseBody; -import org.sopt.global.response.ErrorMeta; +import org.sopt.global.response.dto.ApiResponseBody; +import org.sopt.global.response.dto.ErrorMeta; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.http.converter.HttpMessageNotReadableException; @@ -39,7 +42,7 @@ public ResponseEntity> handleHttpMessageNotRead return ResponseEntity .status(HttpStatus.BAD_REQUEST) - .body(ApiResponseBody.onFailure(ErrorCode.INVALID_MAPPING_PARAMETER, null)); + .body(ApiResponseBody.onFailure(INVALID_MAPPING_PARAMETER, null)); } @ExceptionHandler(BusinessException.class) @@ -71,13 +74,12 @@ public ResponseEntity> handle( System.err.println(e.getClass().getName() + ": " + e.getMessage()); System.err.println("==============================="); - ErrorCode internalServerError = ErrorCode.INTERNAL_SERVER_ERROR; ErrorMeta meta = new ErrorMeta( request.getRequestURI(), System.currentTimeMillis() ); return ResponseEntity.status(500) - .body(ApiResponseBody.onFailure(internalServerError, e.getMessage(), meta)); + .body(ApiResponseBody.onFailure(INTERNAL_SERVER_ERROR, e.getMessage(), meta)); } } diff --git a/src/main/java/org/sopt/global/response/SuccessCode.java b/src/main/java/org/sopt/global/response/SuccessCode.java deleted file mode 100644 index 60091c5..0000000 --- a/src/main/java/org/sopt/global/response/SuccessCode.java +++ /dev/null @@ -1,28 +0,0 @@ -package org.sopt.global.response; - -import lombok.Getter; - -// TODO 도메인 별 분리 => 다음 주에 할게요 -@Getter -public enum SuccessCode { - - // 200 OK - MEMBER_RETRIEVED_SUCCESS(200, "특정 회원 조회가 성공적으로 완료되었습니다."), - MEMBER_LIST_RETRIEVED_SUCCESS(200, "회원 전체 조회가 성공적으로 완료되었습니다."), - MEMBER_DELETED_SUCCESS(200, "회원 삭제가 성공적으로 완료되었습니다."), - ARTICLE_RETRIEVED_SUCCESS(200, "특정 게시글 조회를 성공적으로 완료했습니다."), - ARTICLE_LIST_RETRIEVED_SUCCESS(200, "게시글 전체 조회가 성공적으로 완료되었습니다."), - - // 201 CREATED - MEMBER_CREATED_SUCCESS(201, "회원가입이 성공적으로 완료되었습니다."), - ARTICLE_CREATED_SUCCESS(201, "게시물이 성공적으로 생성되었습니다."); - - - private final int status; - private final String message; - - SuccessCode(int status, String message) { - this.status = status; - this.message = message; - } -} diff --git a/src/main/java/org/sopt/global/response/code/ResponseCode.java b/src/main/java/org/sopt/global/response/code/ResponseCode.java new file mode 100644 index 0000000..f6a015a --- /dev/null +++ b/src/main/java/org/sopt/global/response/code/ResponseCode.java @@ -0,0 +1,7 @@ +package org.sopt.global.response.code; + +public interface ResponseCode { + int getStatus(); + String getCode(); + String getMessage(); +} diff --git a/src/main/java/org/sopt/global/response/code/SuccessCode.java b/src/main/java/org/sopt/global/response/code/SuccessCode.java new file mode 100644 index 0000000..acffb35 --- /dev/null +++ b/src/main/java/org/sopt/global/response/code/SuccessCode.java @@ -0,0 +1,5 @@ +package org.sopt.global.response.code; + +public interface SuccessCode extends ResponseCode { + +} diff --git a/src/main/java/org/sopt/global/response/ApiResponseBody.java b/src/main/java/org/sopt/global/response/dto/ApiResponseBody.java similarity index 73% rename from src/main/java/org/sopt/global/response/ApiResponseBody.java rename to src/main/java/org/sopt/global/response/dto/ApiResponseBody.java index 646f2ec..fdee3bf 100644 --- a/src/main/java/org/sopt/global/response/ApiResponseBody.java +++ b/src/main/java/org/sopt/global/response/dto/ApiResponseBody.java @@ -1,15 +1,16 @@ -package org.sopt.global.response; +package org.sopt.global.response.dto; import com.fasterxml.jackson.annotation.JsonInclude; import org.sopt.global.exception.ErrorCode; +import org.sopt.global.response.code.SuccessCode; @JsonInclude(JsonInclude.Include.NON_NULL) public record ApiResponseBody( boolean success, int status, + String code, String message, T data, - String code, M meta ) { @@ -17,9 +18,9 @@ public static ApiResponseBody ok(SuccessCode successCode) { return new ApiResponseBody<>( true, successCode.getStatus(), + successCode.getCode(), successCode.getMessage(), null, - null, null ); } @@ -28,31 +29,45 @@ public static ApiResponseBody ok(SuccessCode successCode, T data) { return new ApiResponseBody<>( true, successCode.getStatus(), + successCode.getCode(), successCode.getMessage(), data, - null, null ); } + public static ApiResponseBody ok(SuccessCode successCode, T data, M meta) { + return new ApiResponseBody<>( + true, + successCode.getStatus(), + successCode.getCode(), + successCode.getMessage(), + data, + meta + ); + } + public static ApiResponseBody created(SuccessCode successCode, T data) { return new ApiResponseBody<>( true, successCode.getStatus(), + successCode.getCode(), successCode.getMessage(), data, - null, null ); } - public static ApiResponseBody onFailure(ErrorCode errorCode, ErrorMeta errorMeta) { + public static ApiResponseBody onFailure( + ErrorCode errorCode, + ErrorMeta errorMeta + ) { return new ApiResponseBody<>( false, errorCode.getStatus(), errorCode.getMessage(), - null, errorCode.getCode(), + null, errorMeta ); } @@ -65,9 +80,9 @@ public static ApiResponseBody onFailure( return new ApiResponseBody<>( false, errorCode.getStatus(), + errorCode.getCode(), message, null, - errorCode.getCode(), errorMeta ); } diff --git a/src/main/java/org/sopt/global/response/ErrorMeta.java b/src/main/java/org/sopt/global/response/dto/ErrorMeta.java similarity index 64% rename from src/main/java/org/sopt/global/response/ErrorMeta.java rename to src/main/java/org/sopt/global/response/dto/ErrorMeta.java index 82f54e1..665289c 100644 --- a/src/main/java/org/sopt/global/response/ErrorMeta.java +++ b/src/main/java/org/sopt/global/response/dto/ErrorMeta.java @@ -1,4 +1,4 @@ -package org.sopt.global.response; +package org.sopt.global.response.dto; public record ErrorMeta( String path, diff --git a/src/main/java/org/sopt/member/api/MemberController.java b/src/main/java/org/sopt/member/api/MemberController.java index 7656d9e..a975bb1 100644 --- a/src/main/java/org/sopt/member/api/MemberController.java +++ b/src/main/java/org/sopt/member/api/MemberController.java @@ -1,11 +1,11 @@ package org.sopt.member.api; -import static org.sopt.global.response.SuccessCode.MEMBER_CREATED_SUCCESS; -import static org.sopt.global.response.SuccessCode.MEMBER_DELETED_SUCCESS; -import static org.sopt.global.response.SuccessCode.MEMBER_LIST_RETRIEVED_SUCCESS; -import static org.sopt.global.response.SuccessCode.MEMBER_RETRIEVED_SUCCESS; +import static org.sopt.member.api.code.MemberSuccessCode.MEMBER_CREATED_SUCCESS; +import static org.sopt.member.api.code.MemberSuccessCode.MEMBER_DELETED_SUCCESS; +import static org.sopt.member.api.code.MemberSuccessCode.MEMBER_LIST_RETRIEVED_SUCCESS; +import static org.sopt.member.api.code.MemberSuccessCode.MEMBER_RETRIEVED_SUCCESS; -import org.sopt.global.response.ApiResponseBody; +import org.sopt.global.response.dto.ApiResponseBody; import org.sopt.member.api.dto.request.MemberCreateRequest; import org.sopt.member.api.dto.response.MemberCreateResponse; import org.sopt.member.api.dto.response.MemberFindOneResponse; diff --git a/src/main/java/org/sopt/member/api/code/MemberSuccessCode.java b/src/main/java/org/sopt/member/api/code/MemberSuccessCode.java new file mode 100644 index 0000000..35426cd --- /dev/null +++ b/src/main/java/org/sopt/member/api/code/MemberSuccessCode.java @@ -0,0 +1,23 @@ +package org.sopt.member.api.code; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.global.response.code.SuccessCode; + +@Getter +@RequiredArgsConstructor +public enum MemberSuccessCode implements SuccessCode { + + // 200 OK + MEMBER_RETRIEVED_SUCCESS(200, "MEM_200_001", "특정 회원 조회가 성공적으로 완료되었습니다."), + MEMBER_LIST_RETRIEVED_SUCCESS(200, "MEM_200_002","회원 전체 조회가 성공적으로 완료되었습니다."), + MEMBER_DELETED_SUCCESS(200, "MEM_200_003","회원 삭제가 성공적으로 완료되었습니다."), + + // 201 CREATED + MEMBER_CREATED_SUCCESS(201, "MEM_201_001", "회원가입이 성공적으로 완료되었습니다."), + ; + + private final int status; + private final String code; + private final String message; +} diff --git a/src/main/java/org/sopt/member/api/dto/request/MemberCreateRequest.java b/src/main/java/org/sopt/member/api/dto/request/MemberCreateRequest.java index bb77031..8fb408b 100644 --- a/src/main/java/org/sopt/member/api/dto/request/MemberCreateRequest.java +++ b/src/main/java/org/sopt/member/api/dto/request/MemberCreateRequest.java @@ -1,9 +1,9 @@ package org.sopt.member.api.dto.request; -import static org.sopt.global.exception.ErrorCode.INVALID_DATE_FORMAT; -import static org.sopt.global.exception.ErrorCode.INVALID_EMAIL_FORMAT; -import static org.sopt.global.exception.ErrorCode.INVALID_MAPPING_PARAMETER; -import static org.sopt.global.exception.ErrorCode.INVALID_NULL_DATA; +import static org.sopt.global.exception.GlobalErrorCode.INVALID_DATE_FORMAT; +import static org.sopt.global.exception.GlobalErrorCode.INVALID_EMAIL_FORMAT; +import static org.sopt.global.exception.GlobalErrorCode.INVALID_MAPPING_PARAMETER; +import static org.sopt.global.exception.GlobalErrorCode.INVALID_NULL_DATA; import java.util.regex.Pattern; import org.sopt.global.exception.BusinessException; diff --git a/src/main/java/org/sopt/member/application/service/MemberDeleteService.java b/src/main/java/org/sopt/member/application/service/MemberDeleteService.java index a202997..2165177 100644 --- a/src/main/java/org/sopt/member/application/service/MemberDeleteService.java +++ b/src/main/java/org/sopt/member/application/service/MemberDeleteService.java @@ -1,23 +1,22 @@ package org.sopt.member.application.service; -import static org.sopt.global.exception.ErrorCode.MEMBER_NOT_FOUND; +import static org.sopt.member.domain.exception.MemberErrorCode.MEMBER_NOT_FOUND; import java.util.NoSuchElementException; import org.sopt.member.application.port.in.MemberDeleteUseCase; import org.sopt.member.domain.exception.MemberException; -import org.sopt.member.domain.port.out.MemberRepositoryPort; -import org.springframework.beans.factory.annotation.Qualifier; +import org.sopt.member.domain.port.out.MemberRepository; import org.springframework.stereotype.Service; @Service public class MemberDeleteService implements MemberDeleteUseCase { - private final MemberRepositoryPort memberRepository; + private final MemberRepository memberRepository; public MemberDeleteService( - MemberRepositoryPort memberRepositoryPort + MemberRepository memberRepository ) { - this.memberRepository = memberRepositoryPort; + this.memberRepository = memberRepository; } @Override diff --git a/src/main/java/org/sopt/member/application/service/MemberFindAllService.java b/src/main/java/org/sopt/member/application/service/MemberFindAllService.java index b06726e..3a9015d 100644 --- a/src/main/java/org/sopt/member/application/service/MemberFindAllService.java +++ b/src/main/java/org/sopt/member/application/service/MemberFindAllService.java @@ -4,19 +4,18 @@ import org.sopt.member.application.dto.result.MemberListResult; import org.sopt.member.application.port.in.MemberFindAllUseCase; import org.sopt.member.domain.model.Member; -import org.sopt.member.domain.port.out.MemberRepositoryPort; -import org.springframework.beans.factory.annotation.Qualifier; +import org.sopt.member.domain.port.out.MemberRepository; import org.springframework.stereotype.Service; @Service public class MemberFindAllService implements MemberFindAllUseCase { - private final MemberRepositoryPort memberRepository; + private final MemberRepository memberRepository; public MemberFindAllService( - MemberRepositoryPort memberRepositoryPort + MemberRepository memberRepository ) { - this.memberRepository = memberRepositoryPort; + this.memberRepository = memberRepository; } @Override diff --git a/src/main/java/org/sopt/member/application/service/MemberFindOneService.java b/src/main/java/org/sopt/member/application/service/MemberFindOneService.java index b8c54d7..b5b7a62 100644 --- a/src/main/java/org/sopt/member/application/service/MemberFindOneService.java +++ b/src/main/java/org/sopt/member/application/service/MemberFindOneService.java @@ -1,20 +1,20 @@ package org.sopt.member.application.service; -import static org.sopt.global.exception.ErrorCode.MEMBER_NOT_FOUND; +import static org.sopt.member.domain.exception.MemberErrorCode.MEMBER_NOT_FOUND; import lombok.RequiredArgsConstructor; import org.sopt.member.application.dto.result.MemberFindOneResult; import org.sopt.member.application.port.in.MemberFindOneUseCase; import org.sopt.member.domain.exception.MemberException; import org.sopt.member.domain.model.Member; -import org.sopt.member.domain.port.out.MemberRepositoryPort; +import org.sopt.member.domain.port.out.MemberRepository; import org.springframework.stereotype.Service; @RequiredArgsConstructor @Service public class MemberFindOneService implements MemberFindOneUseCase { - private final MemberRepositoryPort memberRepository; + private final MemberRepository memberRepository; @Override public MemberFindOneResult findOne(Long id) { diff --git a/src/main/java/org/sopt/member/application/service/MemberJoinService.java b/src/main/java/org/sopt/member/application/service/MemberJoinService.java index b664ce6..17fb44b 100644 --- a/src/main/java/org/sopt/member/application/service/MemberJoinService.java +++ b/src/main/java/org/sopt/member/application/service/MemberJoinService.java @@ -1,26 +1,25 @@ package org.sopt.member.application.service; -import static org.sopt.global.exception.ErrorCode.INVALID_MAPPING_PARAMETER; -import static org.sopt.global.exception.ErrorCode.MEMBER_BY_EMAIL_ALREADY_EXISTS; +import static org.sopt.global.exception.GlobalErrorCode.INVALID_MAPPING_PARAMETER; +import static org.sopt.member.domain.exception.MemberErrorCode.MEMBER_BY_EMAIL_ALREADY_EXISTS; import org.sopt.member.application.dto.command.MemberJoinCommand; import org.sopt.member.application.dto.result.MemberJoinResult; import org.sopt.member.application.port.in.MemberJoinUseCase; import org.sopt.member.domain.exception.MemberException; import org.sopt.member.domain.model.Member; -import org.sopt.member.domain.port.out.MemberRepositoryPort; -import org.springframework.beans.factory.annotation.Qualifier; +import org.sopt.member.domain.port.out.MemberRepository; import org.springframework.stereotype.Service; @Service public class MemberJoinService implements MemberJoinUseCase { - private final MemberRepositoryPort memberRepository; + private final MemberRepository memberRepository; public MemberJoinService( - MemberRepositoryPort memberRepositoryPort + MemberRepository memberRepository ) { - this.memberRepository = memberRepositoryPort; + this.memberRepository = memberRepository; } @Override diff --git a/src/main/java/org/sopt/member/domain/exception/MemberErrorCode.java b/src/main/java/org/sopt/member/domain/exception/MemberErrorCode.java new file mode 100644 index 0000000..b235dda --- /dev/null +++ b/src/main/java/org/sopt/member/domain/exception/MemberErrorCode.java @@ -0,0 +1,25 @@ +package org.sopt.member.domain.exception; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.global.exception.ErrorCode; + +@Getter +@RequiredArgsConstructor +public enum MemberErrorCode implements ErrorCode { + + // 400 BAD REQUEST + AGE_MUST_UPPER_THAN_20(400, "MEM_400_001", "20세 미만은 가입할 수 없습니다."), + + // 404 NOT FOUND + MEMBER_NOT_FOUND(404, "MEM_404_001", "존재하지 않는 회원입니다."), + + // 409 CONFLICT + MEMBER_BY_EMAIL_ALREADY_EXISTS(409, "MEM_409_001", "해당 이메일로 가입된 회원이 이미 존재합니다."), + + ; + + private final int status; + private final String code; + private final String message; +} diff --git a/src/main/java/org/sopt/member/domain/model/Member.java b/src/main/java/org/sopt/member/domain/model/Member.java index 516f529..07680ae 100644 --- a/src/main/java/org/sopt/member/domain/model/Member.java +++ b/src/main/java/org/sopt/member/domain/model/Member.java @@ -1,21 +1,17 @@ package org.sopt.member.domain.model; -import static org.sopt.global.exception.ErrorCode.AGE_MUST_UPPER_THAN_20; +import static org.sopt.member.domain.exception.MemberErrorCode.AGE_MUST_UPPER_THAN_20; import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; -import jakarta.persistence.OneToMany; import java.time.LocalDate; -import java.util.ArrayList; -import java.util.List; import lombok.AccessLevel; import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; -import org.sopt.article.domain.model.Article; import org.sopt.global.model.BaseEntity; import org.sopt.member.domain.exception.MemberException; @@ -38,9 +34,6 @@ public class Member extends BaseEntity { private Gender gender; - @OneToMany - private List
articleList = new ArrayList<>(); - public static Member of( String name, LocalDate birthday, diff --git a/src/main/java/org/sopt/member/domain/port/out/MemberRepository.java b/src/main/java/org/sopt/member/domain/port/out/MemberRepository.java new file mode 100644 index 0000000..27b5eae --- /dev/null +++ b/src/main/java/org/sopt/member/domain/port/out/MemberRepository.java @@ -0,0 +1,14 @@ +package org.sopt.member.domain.port.out; + +import java.util.List; +import java.util.Optional; +import org.sopt.member.domain.model.Member; + +public interface MemberRepository { + + Member save(Member member); + boolean existsByEmail(String email); + List findAll(); + void deleteById(Long id); + Optional findById(Long id); +} diff --git a/src/main/java/org/sopt/member/domain/port/out/MemberRepositoryPort.java b/src/main/java/org/sopt/member/infrastructure/jpa/MemberJpaRepository.java similarity index 56% rename from src/main/java/org/sopt/member/domain/port/out/MemberRepositoryPort.java rename to src/main/java/org/sopt/member/infrastructure/jpa/MemberJpaRepository.java index 8deb0e4..f4db59c 100644 --- a/src/main/java/org/sopt/member/domain/port/out/MemberRepositoryPort.java +++ b/src/main/java/org/sopt/member/infrastructure/jpa/MemberJpaRepository.java @@ -1,9 +1,9 @@ -package org.sopt.member.domain.port.out; +package org.sopt.member.infrastructure.jpa; import org.sopt.member.domain.model.Member; import org.springframework.data.jpa.repository.JpaRepository; -public interface MemberRepositoryPort extends JpaRepository { +public interface MemberJpaRepository extends JpaRepository { boolean existsByEmail(String email); diff --git a/src/main/java/org/sopt/member/infrastructure/jpa/MemberRepositoryAdapter.java b/src/main/java/org/sopt/member/infrastructure/jpa/MemberRepositoryAdapter.java new file mode 100644 index 0000000..0989f3b --- /dev/null +++ b/src/main/java/org/sopt/member/infrastructure/jpa/MemberRepositoryAdapter.java @@ -0,0 +1,41 @@ +package org.sopt.member.infrastructure.jpa; + +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.sopt.member.domain.model.Member; +import org.sopt.member.domain.port.out.MemberRepository; +import org.springframework.stereotype.Repository; + +@RequiredArgsConstructor +@Repository +public class MemberRepositoryAdapter implements MemberRepository { + + private final MemberJpaRepository jpa; + + @Override + public Member save(Member member) { + return jpa.save(member); + } + + @Override + public boolean existsByEmail(String email) { + return jpa.existsByEmail(email); + } + + @Override + public List findAll() { + return jpa.findAll(); + } + + @Override + public void deleteById(Long id) { + jpa.deleteById(id); + } + + @Override + public Optional findById(Long id) { + return jpa.findById(id); + } + +}