diff --git a/services/article/api/exception/src/main/java/nettee/blolet/article/exception/DraftErrorCode.java b/services/article/api/exception/src/main/java/nettee/blolet/article/exception/DraftErrorCode.java index 4ca85613..e4ba4428 100644 --- a/services/article/api/exception/src/main/java/nettee/blolet/article/exception/DraftErrorCode.java +++ b/services/article/api/exception/src/main/java/nettee/blolet/article/exception/DraftErrorCode.java @@ -7,6 +7,7 @@ import java.util.function.Supplier; public enum DraftErrorCode implements ErrorCode { + // user input DRAFT_BLOG_ID_REQUIRED("블로그 ID를 반드시 제공해야 합니다.", HttpStatus.BAD_REQUEST), DRAFT_TITLE_MIN_LENGTH("블로그 제목은 반드시 3글자 이상입니다.", HttpStatus.BAD_REQUEST), @@ -19,7 +20,13 @@ public enum DraftErrorCode implements ErrorCode { DRAFT_GONE("더 이상 존재하지 않는 게시물입니다.", HttpStatus.GONE), DRAFT_FORBIDDEN("권한이 없습니다.", HttpStatus.FORBIDDEN), DRAFT_ALREADY_EXIST("임시글이 이미 존재합니다.", HttpStatus.CONFLICT), - DEFAULT("임시글 조작 오류", HttpStatus.INTERNAL_SERVER_ERROR); + DEFAULT("임시글 조작 오류", HttpStatus.INTERNAL_SERVER_ERROR), + BLOG_MISMATCH("드래프트와 시리즈가 같은 블로그에 속하지 않습니다.", HttpStatus.BAD_REQUEST), + + // series status + SERIES_ARTICLE_NOT_FOUND("시리즈 아티클을 찾을 수 없습니다.", HttpStatus.NOT_FOUND), + SERIES_NOT_FOUND("시리즈를 찾을 수 없습니다.", HttpStatus.NOT_FOUND), + SERIES_ALREADY_REGISTERED("이미 등록된 시리즈 아티클입니다.", HttpStatus.CONFLICT); private final String message; private final HttpStatus httpStatus; diff --git a/services/article/application/src/main/java/nettee/blolet/article/application/port/DraftCommandPort.java b/services/article/application/src/main/java/nettee/blolet/article/application/port/DraftCommandPort.java index b208253e..be6608d4 100644 --- a/services/article/application/src/main/java/nettee/blolet/article/application/port/DraftCommandPort.java +++ b/services/article/application/src/main/java/nettee/blolet/article/application/port/DraftCommandPort.java @@ -2,20 +2,28 @@ import nettee.blolet.article.domain.Draft; import nettee.blolet.article.domain.DraftImage; +import nettee.blolet.article.domain.SeriesArticle; import nettee.blolet.article.domain.sub.DraftStatus; import nettee.blolet.article.readmodel.DraftReadModels.DraftDetail; +import nettee.blolet.article.readmodel.SeriesQueryModels.SeriesDetail; import java.util.Optional; public interface DraftCommandPort { - Optional findById(String id); + Optional findDraftById(String id); + Optional findSeriesById(String seriesId); + + boolean existsSeriesArticle(String draftId, String seriesId); Draft save(Draft draft); + DraftImage save(DraftImage draftImage); + SeriesArticle createSeriesArticle(String draftId, String seriesId, String articleId); Draft update(Draft draft); - + Draft updateTitle(String draftId, String title); + Draft updatePath(String draftId, String path); + SeriesArticle updateSeriesArticle(String draftId, String seriesId, String articleId); + void updateDraftSeriesInfo(String draftId, String seriesId); void updateStatus(String id, DraftStatus draftStatus); - - DraftImage save(DraftImage draftImage); } diff --git a/services/article/application/src/main/java/nettee/blolet/article/application/service/DraftCommandService.java b/services/article/application/src/main/java/nettee/blolet/article/application/service/DraftCommandService.java index 3e5ba83f..466bf511 100644 --- a/services/article/application/src/main/java/nettee/blolet/article/application/service/DraftCommandService.java +++ b/services/article/application/src/main/java/nettee/blolet/article/application/service/DraftCommandService.java @@ -5,21 +5,32 @@ import nettee.blolet.article.application.usecase.DraftCreateUseCase; import nettee.blolet.article.application.usecase.DraftDeleteUseCase; import nettee.blolet.article.application.usecase.DraftImageCreateUseCase; +import nettee.blolet.article.application.usecase.DraftPatchUseCase; import nettee.blolet.article.application.usecase.DraftUpdateUseCase; import nettee.blolet.article.domain.Draft; import nettee.blolet.article.domain.DraftImage; +import nettee.blolet.article.domain.SeriesArticle; import nettee.blolet.article.domain.sub.DraftStatus; import nettee.blolet.blog.export.client.api.BlogClient; import nettee.upload.port.ImageStorage; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import static nettee.blolet.article.exception.DraftErrorCode.SERIES_ALREADY_REGISTERED; +import static nettee.blolet.article.exception.DraftErrorCode.BLOG_MISMATCH; import static nettee.blolet.article.exception.DraftErrorCode.DRAFT_FORBIDDEN; import static nettee.blolet.article.exception.DraftErrorCode.DRAFT_NOT_FOUND; +import static nettee.blolet.article.exception.DraftErrorCode.SERIES_NOT_FOUND; @Service @RequiredArgsConstructor -public class DraftCommandService implements DraftCreateUseCase, DraftUpdateUseCase, DraftDeleteUseCase, DraftImageCreateUseCase { +public class DraftCommandService implements + DraftCreateUseCase, + DraftUpdateUseCase, + DraftDeleteUseCase, + DraftImageCreateUseCase, + DraftPatchUseCase { + private final DraftCommandPort draftCommandPort; private final BlogClient blogClient; private final ImageStorage imageStorage; @@ -38,7 +49,7 @@ public Draft updateDraft(String userId, Draft draft) { @Override public void deleteDraft(String userId, String draftId) { - var blogId = draftCommandPort.findById(draftId) + var blogId = draftCommandPort.findDraftById(draftId) .orElseThrow(DRAFT_NOT_FOUND::exception) .blogId(); validateOwnership(userId, blogId); @@ -48,7 +59,7 @@ public void deleteDraft(String userId, String draftId) { @Override public DraftImage createDraftImage(String userId, String draftId, MultipartFile file, String targetName) { - var blogId = draftCommandPort.findById(draftId) + var blogId = draftCommandPort.findDraftById(draftId) .orElseThrow(DRAFT_NOT_FOUND::exception) .blogId(); validateOwnership(userId, blogId); @@ -64,6 +75,58 @@ public DraftImage createDraftImage(String userId, String draftId, MultipartFile return draftCommandPort.save(draftImage); } + @Override + public Draft patchTitle(String userId, String draftId, String title) { + var draft = draftCommandPort.findDraftById(draftId) + .orElseThrow(DRAFT_NOT_FOUND::exception); + + validateOwnership(userId, draft.blogId()); + + return draftCommandPort.updateTitle(draft.id(), title); + } + + @Override + public Draft patchPath(String userId, String draftId, String path) { + var draft = draftCommandPort.findDraftById(draftId) + .orElseThrow(DRAFT_NOT_FOUND::exception); + + validateOwnership(userId, draft.blogId()); + + return draftCommandPort.updatePath(draft.id(), path); + } + + @Override + public SeriesArticle registerSeriesArticle(String userId, String draftId, String seriesId, String articleId) { + // 드래프트, 시리즈 조회 + var draft = draftCommandPort.findDraftById(draftId) + .orElseThrow(DRAFT_NOT_FOUND::exception); + + var series = draftCommandPort.findSeriesById(seriesId) + .orElseThrow(SERIES_NOT_FOUND::exception); + + // 권한 검증 + validateOwnership(userId, draft.blogId()); + validateOwnership(userId, series.blogId()); + + // 블로그 동일 여부 확인 + if (!draft.blogId().equals(series.blogId())) { + throw BLOG_MISMATCH.exception(); + } + + // 시리즈 존재 여부 확인 + if (draftCommandPort.existsSeriesArticle(draftId, seriesId)) { + throw SERIES_ALREADY_REGISTERED.exception(); + } + + // 시리즈 등록 + var seriesArticle = draftCommandPort.createSeriesArticle(draftId, seriesId, articleId); + + // 드래프트에 시리즈 정보 업데이트 + draftCommandPort.updateDraftSeriesInfo(draftId, seriesId); + + return seriesArticle; + } + private void validateOwnership(String userId, String draftId) { var isOwner = blogClient.verifyOwnership(userId, draftId) .isOwner(); diff --git a/services/article/application/src/main/java/nettee/blolet/article/application/usecase/DraftPatchUseCase.java b/services/article/application/src/main/java/nettee/blolet/article/application/usecase/DraftPatchUseCase.java new file mode 100644 index 00000000..c0ba6461 --- /dev/null +++ b/services/article/application/src/main/java/nettee/blolet/article/application/usecase/DraftPatchUseCase.java @@ -0,0 +1,13 @@ +package nettee.blolet.article.application.usecase; + +import nettee.blolet.article.domain.Draft; +import nettee.blolet.article.domain.SeriesArticle; + +public interface DraftPatchUseCase { + + Draft patchTitle(String userId, String draftId, String title); + + Draft patchPath(String userId, String draftId, String path); + + SeriesArticle registerSeriesArticle(String userId, String draftId, String seriesId, String articleId); +} diff --git a/services/article/driven/rdb/src/main/java/nettee/blolet/article/rdb/persistence/DraftCommandAdapter.java b/services/article/driven/rdb/src/main/java/nettee/blolet/article/rdb/persistence/DraftCommandAdapter.java index 66e5f859..0360f501 100644 --- a/services/article/driven/rdb/src/main/java/nettee/blolet/article/rdb/persistence/DraftCommandAdapter.java +++ b/services/article/driven/rdb/src/main/java/nettee/blolet/article/rdb/persistence/DraftCommandAdapter.java @@ -27,7 +27,7 @@ public class DraftCommandAdapter implements DraftCommandPort { private final DraftEntityMapper mapper; @Override - public Optional findById(String id) { + public Optional findDraftById(String id) { var draft = draftJpaRepository.findById(id) .orElseThrow(DRAFT_NOT_FOUND::exception); return mapper.toOptionalDraftDetail(draft);