From d89ee417647a885176f8ca2ae6eac9ee1bdc4143 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EA=B2=BD=EB=AF=B8?= <109158497+kyum-q@users.noreply.github.com> Date: Wed, 23 Oct 2024 17:10:23 +0900 Subject: [PATCH] =?UTF-8?q?=EC=83=81=EC=84=B8=20=EC=A1=B0=ED=9A=8C?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=8B=A4=EB=A5=B8=20=EC=82=AC=EB=9E=8C?= =?UTF-8?q?=EC=9D=98=20=EB=B9=84=EA=B3=B5=EA=B0=9C=20=ED=85=9C=ED=94=8C?= =?UTF-8?q?=EB=A6=BF=20=ED=99=95=EC=9D=B8=20=EC=8B=9C=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC=20(#852)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * refactor(template): 기본 값 설정 * feat(template): 다른 사람의 비공개 템플릿 단건 조회 시 예외 발생 * feat(domain): 메서드명 변경 * refactor(domain): 주생성자 활용하도록 수정 * refactor(domain): 도메인에서 예외 발생시키지 않도록 수정 * docs: 실패 ErrorCode 문서 수정 * refactor(domain): 필드 사용으로 변경 * refactor(service): 함수형 인터페이스를 이용해 메서드 추출 및 재사용 --- .../SpringDocTemplateController.java | 3 + .../codezap/template/domain/Template.java | 20 +++--- .../template/service/TemplateService.java | 8 ++- .../facade/TemplateApplicationService.java | 31 +++++--- .../facade/TemplateOwnershipChecker.java | 9 +++ .../codezap/template/domain/TemplateTest.java | 71 +++++++++++++++++++ .../TemplateApplicationServiceTest.java | 51 ++++++++++++- 7 files changed, 170 insertions(+), 23 deletions(-) create mode 100644 backend/src/main/java/codezap/template/service/facade/TemplateOwnershipChecker.java create mode 100644 backend/src/test/java/codezap/template/domain/TemplateTest.java diff --git a/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java b/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java index 7076558e7..747ab737b 100644 --- a/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java +++ b/backend/src/main/java/codezap/template/controller/SpringDocTemplateController.java @@ -106,6 +106,9 @@ ResponseEntity findAllTemplates( @ApiErrorResponse(status = HttpStatus.BAD_REQUEST, instance = "/templates/1/login", errorCases = { @ErrorCase(description = "해당하는 ID 값인 템플릿이 없는 경우", exampleMessage = "식별자 1에 해당하는 템플릿이 존재하지 않습니다."), }) + @ApiErrorResponse(status = HttpStatus.FORBIDDEN, instance = "/templates/1", errorCases = { + @ErrorCase(description = "다른 사람의 private 템플릿인 경우", exampleMessage = "해당 템플릿은 비공개 템플릿입니다."), + }) ResponseEntity findTemplateById(Member member, Long id); @SecurityRequirement(name = "쿠키 인증 토큰") diff --git a/backend/src/main/java/codezap/template/domain/Template.java b/backend/src/main/java/codezap/template/domain/Template.java index 3d91767ef..4c4d590af 100644 --- a/backend/src/main/java/codezap/template/domain/Template.java +++ b/backend/src/main/java/codezap/template/domain/Template.java @@ -14,12 +14,11 @@ import jakarta.persistence.ManyToOne; import jakarta.persistence.OneToMany; +import org.hibernate.annotations.ColumnDefault; import org.hibernate.annotations.Formula; import codezap.category.domain.Category; import codezap.global.auditing.BaseTimeEntity; -import codezap.global.exception.CodeZapException; -import codezap.global.exception.ErrorCode; import codezap.member.domain.Member; import lombok.AccessLevel; import lombok.AllArgsConstructor; @@ -57,6 +56,7 @@ public class Template extends BaseTimeEntity { private Long likesCount; @Column(nullable = false) + @ColumnDefault("'PUBLIC'") @Enumerated(EnumType.STRING) private Visibility visibility; @@ -65,11 +65,7 @@ public Template(Member member, String title, String description, Category catego } public Template(Member member, String title, String description, Category category, Visibility visibility) { - this.member = member; - this.title = title; - this.description = description; - this.category = category; - this.visibility = visibility; + this(null, member, title, description, category, null, 0L, visibility); } public void updateTemplate(String title, String description, Category category, Visibility visibility) { @@ -79,9 +75,11 @@ public void updateTemplate(String title, String description, Category category, this.visibility = visibility; } - public void validateAuthorization(Member member) { - if (!getMember().equals(member)) { - throw new CodeZapException(ErrorCode.FORBIDDEN_ACCESS, "해당 템플릿에 대한 권한이 없습니다."); - } + public boolean matchMember(Member member) { + return this.member.equals(member); + } + + public boolean isPrivate() { + return visibility == Visibility.PRIVATE; } } diff --git a/backend/src/main/java/codezap/template/service/TemplateService.java b/backend/src/main/java/codezap/template/service/TemplateService.java index f6143784e..ed04667fd 100644 --- a/backend/src/main/java/codezap/template/service/TemplateService.java +++ b/backend/src/main/java/codezap/template/service/TemplateService.java @@ -66,7 +66,9 @@ public Template update( Category category ) { Template template = templateRepository.fetchById(templateId); - template.validateAuthorization(member); + if (!template.matchMember(member)) { + throw new CodeZapException(ErrorCode.FORBIDDEN_ACCESS, "해당 템플릿에 대한 권한이 없습니다."); + } template.updateTemplate( updateTemplateRequest.title(), updateTemplateRequest.description(), @@ -86,7 +88,9 @@ public void deleteByMemberAndIds(Member member, List ids) { private void deleteById(Member member, Long id) { Template template = templateRepository.fetchById(id); - template.validateAuthorization(member); + if (!template.matchMember(member)) { + throw new CodeZapException(ErrorCode.FORBIDDEN_ACCESS, "해당 템플릿에 대한 권한이 없습니다."); + } templateRepository.deleteById(id); } } diff --git a/backend/src/main/java/codezap/template/service/facade/TemplateApplicationService.java b/backend/src/main/java/codezap/template/service/facade/TemplateApplicationService.java index c2b380cbe..cb9234388 100644 --- a/backend/src/main/java/codezap/template/service/facade/TemplateApplicationService.java +++ b/backend/src/main/java/codezap/template/service/facade/TemplateApplicationService.java @@ -11,6 +11,8 @@ import codezap.category.domain.Category; import codezap.category.service.CategoryService; +import codezap.global.exception.CodeZapException; +import codezap.global.exception.ErrorCode; import codezap.likes.service.LikedChecker; import codezap.likes.service.LikesService; import codezap.member.domain.Member; @@ -58,18 +60,29 @@ public Long create(Member member, CreateTemplateRequest createTemplateRequest) { } public FindTemplateResponse findById(Long id) { - Template template = templateService.getById(id); - List tags = tagService.findAllByTemplate(template); - List sourceCodes = sourceCodeService.findAllByTemplate(template); - return FindTemplateResponse.of(template, sourceCodes, tags, false); + return makeTemplateResponse(id, template -> false, template -> false); } public FindTemplateResponse findById(Long id, Member loginMember) { + return makeTemplateResponse( + id, + template -> template.matchMember(loginMember), + template -> likesService.isLiked(loginMember, template) + ); + } + + private FindTemplateResponse makeTemplateResponse( + Long id, + TemplateOwnershipChecker templateOwnershipChecker, + LikedChecker likedChecker + ) { Template template = templateService.getById(id); + if (!templateOwnershipChecker.isOwner(template) && template.isPrivate()) { + throw new CodeZapException(ErrorCode.FORBIDDEN_ACCESS, "해당 템플릿은 비공개 템플릿입니다."); + } List tags = tagService.findAllByTemplate(template); List sourceCodes = sourceCodeService.findAllByTemplate(template); - boolean isLiked = likesService.isLiked(loginMember, template); - return FindTemplateResponse.of(template, sourceCodes, tags, isLiked); + return FindTemplateResponse.of(template, sourceCodes, tags, likedChecker.isLiked(template)); } public FindAllTemplatesResponse findAllBy( @@ -82,7 +95,7 @@ public FindAllTemplatesResponse findAllBy( Page