diff --git a/backend/pcloud-api/build.gradle b/backend/pcloud-api/build.gradle index 7517517c..ced69529 100644 --- a/backend/pcloud-api/build.gradle +++ b/backend/pcloud-api/build.gradle @@ -39,6 +39,10 @@ dependencies { // actuator, prometheus implementation 'org.springframework.boot:spring-boot-starter-actuator' runtimeOnly 'io.micrometer:micrometer-registry-prometheus' + + // AWS S3 + implementation platform("software.amazon.awssdk:bom:2.20.143") + implementation "software.amazon.awssdk:s3" } def generated = 'src/main/generated' diff --git a/backend/pcloud-api/docs/asciidoc/auth.adoc b/backend/pcloud-api/src/docs/asciidoc/auth.adoc similarity index 100% rename from backend/pcloud-api/docs/asciidoc/auth.adoc rename to backend/pcloud-api/src/docs/asciidoc/auth.adoc diff --git a/backend/pcloud-api/docs/asciidoc/exhibition.adoc b/backend/pcloud-api/src/docs/asciidoc/exhibition.adoc similarity index 96% rename from backend/pcloud-api/docs/asciidoc/exhibition.adoc rename to backend/pcloud-api/src/docs/asciidoc/exhibition.adoc index 979c9bf2..a81aba35 100644 --- a/backend/pcloud-api/docs/asciidoc/exhibition.adoc +++ b/backend/pcloud-api/src/docs/asciidoc/exhibition.adoc @@ -1,11 +1,10 @@ -= exhibition API 문서 += Exhibition API 문서 :doctype: book :icons: font :source-highlighter: highlightjs :toc: left :toclevels: 3 -= Exhibiton == 개인전시회를 생성한다 (POST /api/exhibitions) @@ -45,7 +44,7 @@ include::{snippets}/exhibition-controller-web-mvc-test/patch_exhibition/request- include::{snippets}/exhibition-controller-web-mvc-test/patch_exhibition/http-response.adoc[] -== 개인전시회를 삭제한다 (DELETE /api/exhibitions) +== 개인전시회를 삭제한다 (DELETE /api/exhibitions/{exhibitionId}) === Request diff --git a/backend/pcloud-api/docs/asciidoc/index.adoc b/backend/pcloud-api/src/docs/asciidoc/index.adoc similarity index 100% rename from backend/pcloud-api/docs/asciidoc/index.adoc rename to backend/pcloud-api/src/docs/asciidoc/index.adoc diff --git a/backend/pcloud-api/docs/asciidoc/map.adoc b/backend/pcloud-api/src/docs/asciidoc/map.adoc similarity index 100% rename from backend/pcloud-api/docs/asciidoc/map.adoc rename to backend/pcloud-api/src/docs/asciidoc/map.adoc diff --git a/backend/pcloud-api/docs/asciidoc/popups.adoc b/backend/pcloud-api/src/docs/asciidoc/popups.adoc similarity index 84% rename from backend/pcloud-api/docs/asciidoc/popups.adoc rename to backend/pcloud-api/src/docs/asciidoc/popups.adoc index de3bcb27..ea68a071 100644 --- a/backend/pcloud-api/docs/asciidoc/popups.adoc +++ b/backend/pcloud-api/src/docs/asciidoc/popups.adoc @@ -65,6 +65,19 @@ include::{snippets}/popups-controller-web-mvc-test/patch_popups/http-request.ado include::{snippets}/popups-controller-web-mvc-test/patch_popups/http-response.adoc[] +== 팝업스토어를 삭제한다 (DELETE /api/popups/{popupsId}) + +=== Request + +include::{snippets}/popups-controller-web-mvc-test/delete_popups/http-request.adoc[] +include::{snippets}/popups-controller-web-mvc-test/delete_popups/path-parameters.adoc[] +include::{snippets}/popups-controller-web-mvc-test/delete_popups/request-headers.adoc[] + +=== Response + +include::{snippets}/popups-controller-web-mvc-test/delete_popups/http-response.adoc[] + + == 팝업스토어를 좋아요 처리 및 취소한다 (POST /api/popups/{popupsId}/likes) === Request diff --git a/backend/pcloud-api/docs/asciidoc/recommends.adoc b/backend/pcloud-api/src/docs/asciidoc/recommends.adoc similarity index 100% rename from backend/pcloud-api/docs/asciidoc/recommends.adoc rename to backend/pcloud-api/src/docs/asciidoc/recommends.adoc diff --git a/backend/pcloud-api/docs/asciidoc/show.adoc b/backend/pcloud-api/src/docs/asciidoc/show.adoc similarity index 100% rename from backend/pcloud-api/docs/asciidoc/show.adoc rename to backend/pcloud-api/src/docs/asciidoc/show.adoc diff --git a/backend/pcloud-api/src/main/java/com/api/auth/application/AuthService.java b/backend/pcloud-api/src/main/java/com/api/auth/application/AuthService.java index e79fb738..4fa1209e 100644 --- a/backend/pcloud-api/src/main/java/com/api/auth/application/AuthService.java +++ b/backend/pcloud-api/src/main/java/com/api/auth/application/AuthService.java @@ -32,4 +32,13 @@ public String loginWithOAuth(final OAuthPlatform platform, final String oAuthPer private Member signup(final Member member) { return memberRepository.save(member); } + + // ADMIN test용 로그인 기능입니다.(추후에 삭제 예정) + @Transactional + public String test() { + Member member = memberRepository.save(Member.createWithNormalRole("1234", "kakao", "email")); + member.changeMemberRoleToAdmin(); + + return tokenProvider.create(member.getId()); + } } diff --git a/backend/pcloud-api/src/main/java/com/api/auth/presentation/AuthController.java b/backend/pcloud-api/src/main/java/com/api/auth/presentation/AuthController.java index 69876b09..e312f753 100644 --- a/backend/pcloud-api/src/main/java/com/api/auth/presentation/AuthController.java +++ b/backend/pcloud-api/src/main/java/com/api/auth/presentation/AuthController.java @@ -26,4 +26,12 @@ public ResponseEntity loginWithOAuth( String token = authService.loginWithOAuth(platform, oAuthPermittedCode); return ResponseEntity.ok(new TokenResponse(token)); } + + + // test용 로그인 기능입니다.(추후에 삭제 예정) + @PostMapping("/login/test") + public ResponseEntity test() { + String token = authService.test(); + return ResponseEntity.ok(new TokenResponse(token)); + } } diff --git a/backend/pcloud-api/src/main/java/com/api/customtag/service/TagEventHandler.java b/backend/pcloud-api/src/main/java/com/api/customtag/service/TagEventHandler.java index 1e6ac329..c30a4088 100644 --- a/backend/pcloud-api/src/main/java/com/api/customtag/service/TagEventHandler.java +++ b/backend/pcloud-api/src/main/java/com/api/customtag/service/TagEventHandler.java @@ -4,8 +4,10 @@ import com.domain.customtag.domain.CustomTag; import com.domain.customtag.domain.CustomTagRepository; import com.domain.show.exhibition.event.ExhibitionTagsCreatedEvents; +import com.domain.show.exhibition.event.ExhibitionTagsDeletedEvent; import com.domain.show.exhibition.event.ExhibitionTagsUpdatedEvents; import com.domain.show.popups.event.PopupsTagsCreatedEvent; +import com.domain.show.popups.event.PopupsTagsDeletedEvent; import com.domain.show.popups.event.PopupsTagsUpdatedEvent; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; @@ -44,6 +46,11 @@ public void updatePopupsTags(final PopupsTagsUpdatedEvent event) { customTagRepository.saveAll(customTags); } + @EventListener(PopupsTagsDeletedEvent.class) + public void deletePopupsTags(final PopupsTagsDeletedEvent event) { + customTagRepository.deleteAllByTypeAndTargetId(event.type(), event.popupsId()); + } + @EventListener(ExhibitionTagsCreatedEvents.class) public void saveExhibitionTags(final ExhibitionTagsCreatedEvents event) { List customTags = getCustomTag(event.tags(), event.type(), event.exhibitionId()); @@ -56,4 +63,9 @@ public void updateExhibitionTags(final ExhibitionTagsUpdatedEvents event) { customTagRepository.deleteAllByTypeAndTargetId(event.type(), event.exhibitionId()); customTagRepository.saveAll(customTags); } + + @EventListener(ExhibitionTagsDeletedEvent.class) + public void deleteExhibitionTags(final ExhibitionTagsDeletedEvent event) { + customTagRepository.deleteAllByTypeAndTargetId(event.type(), event.exhibitionId()); + } } diff --git a/backend/pcloud-api/src/main/java/com/api/global/config/S3Config.java b/backend/pcloud-api/src/main/java/com/api/global/config/S3Config.java new file mode 100644 index 00000000..3d7d52c8 --- /dev/null +++ b/backend/pcloud-api/src/main/java/com/api/global/config/S3Config.java @@ -0,0 +1,30 @@ +package com.api.global.config; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import software.amazon.awssdk.auth.credentials.AwsBasicCredentials; +import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider; +import software.amazon.awssdk.regions.Region; +import software.amazon.awssdk.services.s3.S3Client; + +@Configuration +public class S3Config { + + @Value("${cloud.aws.credentials.accessKey}") + private String accessKey; + + @Value("${cloud.aws.credentials.secretKey}") + private String secretKey; + + @Value("${cloud.aws.region.static}") + private String region; + + @Bean + public S3Client getS3Client() { + return S3Client.builder() + .credentialsProvider(StaticCredentialsProvider.create(AwsBasicCredentials.create(accessKey, secretKey))) + .region(Region.of(region)) + .build(); + } +} diff --git a/backend/pcloud-api/src/main/java/com/api/show/common/event/ImageCreatedEvent.java b/backend/pcloud-api/src/main/java/com/api/show/common/event/ImageCreatedEvent.java new file mode 100644 index 00000000..276fea6b --- /dev/null +++ b/backend/pcloud-api/src/main/java/com/api/show/common/event/ImageCreatedEvent.java @@ -0,0 +1,19 @@ +package com.api.show.common.event; + +import com.domain.common.ShowType; +import java.util.List; + +public record ImageCreatedEvent( + ShowType showType, + Long targetId, + List imageNames +) { + + public static ImageCreatedEvent createdPopupsImages(final Long targetId, final List imageNames) { + return new ImageCreatedEvent(ShowType.POPUPS, targetId, imageNames); + } + + public static ImageCreatedEvent createdExhibitionImages(final Long targetId, final List imageNames) { + return new ImageCreatedEvent(ShowType.EXHIBITION, targetId, imageNames); + } +} diff --git a/backend/pcloud-api/src/main/java/com/api/show/common/event/ImageDeletedEvent.java b/backend/pcloud-api/src/main/java/com/api/show/common/event/ImageDeletedEvent.java new file mode 100644 index 00000000..8c57807e --- /dev/null +++ b/backend/pcloud-api/src/main/java/com/api/show/common/event/ImageDeletedEvent.java @@ -0,0 +1,17 @@ +package com.api.show.common.event; + +import com.domain.common.ShowType; + +public record ImageDeletedEvent( + ShowType showType, + Long targetId +) { + + public static ImageDeletedEvent deletedPopupsImages(final Long targetId) { + return new ImageDeletedEvent(ShowType.POPUPS, targetId); + } + + public static ImageDeletedEvent deletedExhibitionImages(final Long targetId) { + return new ImageDeletedEvent(ShowType.EXHIBITION, targetId); + } +} diff --git a/backend/pcloud-api/src/main/java/com/api/show/common/event/ImageUpdatedEvent.java b/backend/pcloud-api/src/main/java/com/api/show/common/event/ImageUpdatedEvent.java new file mode 100644 index 00000000..1e20054a --- /dev/null +++ b/backend/pcloud-api/src/main/java/com/api/show/common/event/ImageUpdatedEvent.java @@ -0,0 +1,33 @@ +package com.api.show.common.event; + +import com.domain.common.ShowType; +import java.util.List; + +public record ImageUpdatedEvent( + ShowType showType, + Long targetId, + List imageNames +) { + + public static ImageUpdatedEvent updatedPopupsImages( + final Long targetId, + final List imageNames + ) { + return new ImageUpdatedEvent( + ShowType.POPUPS, + targetId, + imageNames + ); + } + + public static ImageUpdatedEvent updatedExhibitionImages( + final Long targetId, + final List imageNames + ) { + return new ImageUpdatedEvent( + ShowType.EXHIBITION, + targetId, + imageNames + ); + } +} diff --git a/backend/pcloud-api/src/main/java/com/api/show/exhibition/application/ExhibitionService.java b/backend/pcloud-api/src/main/java/com/api/show/exhibition/application/ExhibitionService.java index 46a83245..dcec3c45 100644 --- a/backend/pcloud-api/src/main/java/com/api/show/exhibition/application/ExhibitionService.java +++ b/backend/pcloud-api/src/main/java/com/api/show/exhibition/application/ExhibitionService.java @@ -1,5 +1,8 @@ package com.api.show.exhibition.application; +import com.api.show.common.event.ImageCreatedEvent; +import com.api.show.common.event.ImageDeletedEvent; +import com.api.show.common.event.ImageUpdatedEvent; import com.api.show.exhibition.application.dto.ExhibitionCreateRequest; import com.api.show.exhibition.application.dto.ExhibitionUpdateRequest; import com.common.config.event.Events; @@ -8,6 +11,7 @@ import com.domain.show.exhibition.domain.ExhibitionRepository; import com.domain.show.exhibition.domain.LikedExhibition; import com.domain.show.exhibition.event.ExhibitionTagsCreatedEvents; +import com.domain.show.exhibition.event.ExhibitionTagsDeletedEvent; import com.domain.show.exhibition.event.ExhibitionTagsUpdatedEvents; import com.domain.show.exhibition.exception.ExhibitionException; import lombok.RequiredArgsConstructor; @@ -30,6 +34,7 @@ public Long create(final Long memberId, final ExhibitionCreateRequest request) { request.tags(), CustomTagType.PERSONAL_EXHIBITION )); + Events.raise(ImageCreatedEvent.createdExhibitionImages(savedExhibition.getId(), request.imageNames())); return savedExhibition.getId(); } @@ -40,13 +45,18 @@ public void patchById( final ExhibitionUpdateRequest request ) { Exhibition foundExhibition = findExhibition(exhibitionId); - Exhibition updateExhibition = request.toDomain(memberId); - foundExhibition.update(updateExhibition); + foundExhibition.validateOwnerWithOwnerId(memberId); + foundExhibition.update(request.toDomain(memberId)); Events.raise(new ExhibitionTagsUpdatedEvents( - foundExhibition.getId(), + exhibitionId, request.tags(), CustomTagType.PERSONAL_EXHIBITION) ); + + Events.raise(ImageUpdatedEvent.updatedExhibitionImages( + exhibitionId, + request.imageNames() + )); } private Exhibition findExhibition(final Long exhibitionId) { @@ -56,8 +66,10 @@ private Exhibition findExhibition(final Long exhibitionId) { public void deleteById(final Long memberId, final Long exhibitionId) { Exhibition foundExhibition = findExhibition(exhibitionId); - foundExhibition.validateOwnerEquals(memberId); + foundExhibition.validateOwnerWithOwnerId(memberId); exhibitionRepository.deleteById(foundExhibition.getId()); + Events.raise(new ExhibitionTagsDeletedEvent(exhibitionId, CustomTagType.PERSONAL_EXHIBITION)); + Events.raise(ImageDeletedEvent.deletedExhibitionImages(exhibitionId)); } public boolean toggleLike(final Long memberId, final Long exhibitionId) { diff --git a/backend/pcloud-api/src/main/java/com/api/show/exhibition/application/dto/ExhibitionCreateRequest.java b/backend/pcloud-api/src/main/java/com/api/show/exhibition/application/dto/ExhibitionCreateRequest.java index 909dfe9e..cdfe92de 100644 --- a/backend/pcloud-api/src/main/java/com/api/show/exhibition/application/dto/ExhibitionCreateRequest.java +++ b/backend/pcloud-api/src/main/java/com/api/show/exhibition/application/dto/ExhibitionCreateRequest.java @@ -2,12 +2,12 @@ import com.domain.show.exhibition.domain.Exhibition; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; import java.util.List; public record ExhibitionCreateRequest( - @NotBlank(message = "개인전시회 제목을 입력해주세요.") String title, @@ -53,7 +53,11 @@ public record ExhibitionCreateRequest( @NotBlank(message = "개인전시회 퍼블릭 태그를 붙여주세요.") String publicTag, - List tags + @NotEmpty(message = "개인전시회 커스텀 태그를 붙여주세요.") + List tags, + + @NotEmpty(message = "개인전시회 이미지명을 입력해주세요.") + List imageNames ) { public Exhibition toDomain(final Long memberId) { return Exhibition.of( diff --git a/backend/pcloud-api/src/main/java/com/api/show/exhibition/application/dto/ExhibitionUpdateRequest.java b/backend/pcloud-api/src/main/java/com/api/show/exhibition/application/dto/ExhibitionUpdateRequest.java index b82a6043..abf39e81 100644 --- a/backend/pcloud-api/src/main/java/com/api/show/exhibition/application/dto/ExhibitionUpdateRequest.java +++ b/backend/pcloud-api/src/main/java/com/api/show/exhibition/application/dto/ExhibitionUpdateRequest.java @@ -2,6 +2,7 @@ import com.domain.show.exhibition.domain.Exhibition; import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; import java.util.List; @@ -53,7 +54,10 @@ public record ExhibitionUpdateRequest( @NotBlank(message = "개인전시회 퍼블릭 태그를 붙여주세요.") String publicTag, - List tags + @NotEmpty(message = "팝업스토어 커스텀 태그를 붙여주세요.") + List tags, + + List imageNames ) { public Exhibition toDomain(final Long memberId) { diff --git a/backend/pcloud-api/src/main/java/com/api/show/exhibition/presentation/ExhibitionController.java b/backend/pcloud-api/src/main/java/com/api/show/exhibition/presentation/ExhibitionController.java index 0802ec15..28610156 100644 --- a/backend/pcloud-api/src/main/java/com/api/show/exhibition/presentation/ExhibitionController.java +++ b/backend/pcloud-api/src/main/java/com/api/show/exhibition/presentation/ExhibitionController.java @@ -9,6 +9,8 @@ import com.domain.annotation.AuthMember; import com.domain.annotation.AuthMembers; import com.domain.show.exhibition.domain.dto.ExhibitionSpecificResponse; +import jakarta.validation.Valid; +import java.net.URI; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -20,8 +22,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.net.URI; - import static com.domain.member.domain.vo.MemberRole.ADMIN; import static com.domain.member.domain.vo.MemberRole.MANAGER; @@ -35,13 +35,10 @@ public class ExhibitionController { private final ExhibitionService exhibitionService; private final ExhibitionQueryService exhibitionQueryService; - /** - * TODO: 이미지 처리 - */ @PostMapping public ResponseEntity create( @AuthMembers(permit = {MANAGER, ADMIN}) final Long memberId, - @RequestBody final ExhibitionCreateRequest request + @RequestBody @Valid final ExhibitionCreateRequest request ) { Long createdExhibitionId = exhibitionService.create(memberId, request); return ResponseEntity.created(URI.create(URI_PREFIX + createdExhibitionId)) @@ -51,7 +48,8 @@ public ResponseEntity create( @GetMapping("/{exhibitionId}") public ResponseEntity findById( @PathVariable final Long exhibitionId, - @ClientIpFinder final String clientIp) { + @ClientIpFinder final String clientIp + ) { return ResponseEntity.ok(exhibitionQueryService.findById(exhibitionId, clientIp)); } @@ -59,13 +57,9 @@ public ResponseEntity findById( public ResponseEntity patchById( @AuthMembers(permit = {ADMIN, MANAGER}) final Long memberId, @PathVariable final Long exhibitionId, - @RequestBody final ExhibitionUpdateRequest request + @RequestBody @Valid final ExhibitionUpdateRequest request ) { - exhibitionService.patchById( - memberId, - exhibitionId, - request - ); + exhibitionService.patchById(memberId, exhibitionId, request); return ResponseEntity.noContent() .build(); diff --git a/backend/pcloud-api/src/main/java/com/api/show/image/application/ImagePreProcessor.java b/backend/pcloud-api/src/main/java/com/api/show/image/application/ImagePreProcessor.java new file mode 100644 index 00000000..df84eef2 --- /dev/null +++ b/backend/pcloud-api/src/main/java/com/api/show/image/application/ImagePreProcessor.java @@ -0,0 +1,15 @@ +package com.api.show.image.application; + +import com.domain.common.ShowType; +import com.domain.show.common.image.domain.Image; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +public interface ImagePreProcessor { + + List convertMultipartFileToImage( + ShowType showType, + Long targetId, + List imageFiles); +} diff --git a/backend/pcloud-api/src/main/java/com/api/show/image/application/ImageProcessor.java b/backend/pcloud-api/src/main/java/com/api/show/image/application/ImageProcessor.java new file mode 100644 index 00000000..9707f80b --- /dev/null +++ b/backend/pcloud-api/src/main/java/com/api/show/image/application/ImageProcessor.java @@ -0,0 +1,8 @@ +package com.api.show.image.application; + +import java.util.List; + +public interface ImageProcessor { + + void deleteImagesByUniqueNames(List uniqueImageNames); +} diff --git a/backend/pcloud-api/src/main/java/com/api/show/image/application/ImageService.java b/backend/pcloud-api/src/main/java/com/api/show/image/application/ImageService.java new file mode 100644 index 00000000..c9ecede4 --- /dev/null +++ b/backend/pcloud-api/src/main/java/com/api/show/image/application/ImageService.java @@ -0,0 +1,82 @@ +package com.api.show.image.application; + +import com.api.show.common.event.ImageCreatedEvent; +import com.api.show.common.event.ImageDeletedEvent; +import com.api.show.common.event.ImageUpdatedEvent; +import com.domain.common.ShowType; +import com.domain.show.common.image.domain.Image; +import com.domain.show.common.image.domain.ImageRepository; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@Service +public class ImageService { + + private final ImageRepository imageRepository; + private final ImageProcessor imageProcessor; + + @Transactional + @EventListener(value = ImageCreatedEvent.class) + public void createImages(final ImageCreatedEvent event) { + List images = generateImages( + event.targetId(), + event.showType(), + event.imageNames() + ); + saveNewImages(images); + } + + private List generateImages( + final Long targetId, + final ShowType showType, + final List imageNames + ) { + return imageNames.stream() + .map(imageName -> Image.of(targetId, showType, imageName)).toList(); + } + + private void saveNewImages(final List images) { + if (images != null && !images.isEmpty()) { + imageRepository.saveAll(images); + } + } + + @Transactional + @EventListener(value = ImageUpdatedEvent.class) + public void updateImages(final ImageUpdatedEvent event) { + List images = generateImages( + event.targetId(), + event.showType(), + event.imageNames() + ); + deleteOldImages(event.targetId(), event.showType()); + saveNewImages(images); + } + + private void deleteOldImages(final Long targetId, final ShowType showType) { + deleteUploadedOldImages(targetId, showType); + deleteSavedOldImages(targetId, showType); + } + + private void deleteUploadedOldImages(final Long targetId, final ShowType showType) { + List uniqueImageNamesToDelete = imageRepository.findImageNamesByTargetIdAndShowType( + targetId, + showType + ); + imageProcessor.deleteImagesByUniqueNames(uniqueImageNamesToDelete); + } + + private void deleteSavedOldImages(final Long targetId, final ShowType showType) { + imageRepository.deleteAllByTargetIdAndShowType(targetId, showType); + } + + @Transactional + @EventListener(value = ImageDeletedEvent.class) + public void deleteOldImages(final ImageDeletedEvent event) { + deleteOldImages(event.targetId(), event.showType()); + } +} diff --git a/backend/pcloud-api/src/main/java/com/api/show/image/application/ImageUploader.java b/backend/pcloud-api/src/main/java/com/api/show/image/application/ImageUploader.java new file mode 100644 index 00000000..49761fa3 --- /dev/null +++ b/backend/pcloud-api/src/main/java/com/api/show/image/application/ImageUploader.java @@ -0,0 +1,13 @@ +//package com.api.show.image.application; +// +//import com.domain.show.common.image.domain.Image; +//import org.springframework.web.multipart.MultipartFile; +// +//import java.util.List; +// +//public interface ImageUploader { +// +// void uploadAll(List convertedImages, List originImageFiles); +// +// void deleteAll(List deletedImageUniqueNames); +//} diff --git a/backend/pcloud-api/src/main/java/com/api/show/image/infrastructure/ImageLocalUploader.java b/backend/pcloud-api/src/main/java/com/api/show/image/infrastructure/ImageLocalUploader.java new file mode 100644 index 00000000..f648e446 --- /dev/null +++ b/backend/pcloud-api/src/main/java/com/api/show/image/infrastructure/ImageLocalUploader.java @@ -0,0 +1,68 @@ +//package com.api.show.image.infrastructure; +// +//import com.api.show.image.application.ImageUploader; +//import com.domain.show.common.image.domain.Image; +//import com.domain.show.common.image.exception.ImageException; +//import com.domain.show.common.image.exception.ImageExceptionType; +//import jakarta.annotation.PostConstruct; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.context.annotation.Profile; +//import org.springframework.stereotype.Component; +//import org.springframework.web.multipart.MultipartFile; +// +//import java.io.File; +//import java.io.IOException; +//import java.util.List; +//import java.util.stream.IntStream; +// +//@Slf4j +//@Profile("local") +//@RequiredArgsConstructor +//@Component +//public class ImageLocalUploader implements ImageUploader { +// +// @Value("${file.show.upload.location}") +// private String location; +// +// @PostConstruct +// void createSavedDir() { +// File dir = new File(location); +// createDir(dir); +// } +// +// private void createDir(final File dir) { +// if (!dir.exists()) { +// dir.mkdir(); +// } +// } +// +// @Override +// public void uploadAll(final List convertedImages, final List originImageFiles) { +// IntStream.range(0, convertedImages.size()) +// .forEach(index -> saveFile( +// originImageFiles.get(index), +// convertedImages.get(index).getUniqueName() +// )); +// } +// +// private void saveFile(final MultipartFile file, final String fileUniqueName) { +// try { +// file.transferTo(new File(location + fileUniqueName)); +// log.info("파일 저장 성공 : " + location + fileUniqueName); +// } catch (IOException e) { +// log.error("파일 저장 실패 위치 {}, 로그 {} : ", location + fileUniqueName, e.getMessage()); +// throw new ImageException(ImageExceptionType.FILE_UPLOAD_FAILURE_EXCEPTION); +// } +// } +// +// @Override +// public void deleteAll(final List deletedImageUniqueNames) { +// deletedImageUniqueNames.forEach(this::deleteImage); +// } +// +// private void deleteImage(final String fileUniqueName) { +// new File(location + fileUniqueName).delete(); +// } +//} diff --git a/backend/pcloud-api/src/main/java/com/api/show/image/infrastructure/ImagePreProcessorImpl.java b/backend/pcloud-api/src/main/java/com/api/show/image/infrastructure/ImagePreProcessorImpl.java new file mode 100644 index 00000000..61e841a2 --- /dev/null +++ b/backend/pcloud-api/src/main/java/com/api/show/image/infrastructure/ImagePreProcessorImpl.java @@ -0,0 +1,35 @@ +//package com.api.show.image.infrastructure; +// +//import com.api.show.image.application.ImagePreProcessor; +//import com.domain.common.ShowType; +//import com.domain.show.common.image.domain.Image; +//import lombok.RequiredArgsConstructor; +//import org.springframework.stereotype.Component; +//import org.springframework.web.multipart.MultipartFile; +// +//import java.util.List; +// +//@RequiredArgsConstructor +//@Component +//public class ImagePreProcessorImpl implements ImagePreProcessor { +// +// @Override +// public List convertMultipartFileToImage( +// final ShowType showType, +// final Long targetId, +// final List imageMultipartFiles +// ) { +// return imageMultipartFiles.stream() +// .map(multipartFile -> convertToImage(showType, targetId, multipartFile)) +// .toList(); +// } +// +// private Image convertToImage( +// final ShowType showType, +// final Long targetId, +// final MultipartFile multipartImageFile +// ) { +// String originalFilename = multipartImageFile.getOriginalFilename(); +// return Image.of(showType, targetId, originalFilename); +// } +//} diff --git a/backend/pcloud-api/src/main/java/com/api/show/image/infrastructure/ImageProdUploader.java b/backend/pcloud-api/src/main/java/com/api/show/image/infrastructure/ImageProdUploader.java new file mode 100644 index 00000000..6335afe9 --- /dev/null +++ b/backend/pcloud-api/src/main/java/com/api/show/image/infrastructure/ImageProdUploader.java @@ -0,0 +1,80 @@ +//package com.api.show.image.infrastructure; +// +//import com.api.show.image.application.ImageUploader; +//import com.domain.show.common.image.domain.Image; +//import com.domain.show.common.image.exception.ImageException; +//import com.domain.show.common.image.exception.ImageExceptionType; +//import java.io.InputStream; +//import java.util.List; +//import java.util.stream.IntStream; +//import lombok.RequiredArgsConstructor; +//import lombok.extern.slf4j.Slf4j; +//import org.springframework.beans.factory.annotation.Value; +//import org.springframework.context.annotation.Profile; +//import org.springframework.scheduling.annotation.Async; +//import org.springframework.stereotype.Component; +//import org.springframework.web.multipart.MultipartFile; +//import software.amazon.awssdk.core.sync.RequestBody; +//import software.amazon.awssdk.services.s3.S3Client; +//import software.amazon.awssdk.services.s3.model.DeleteObjectsRequest; +//import software.amazon.awssdk.services.s3.model.ObjectIdentifier; +//import software.amazon.awssdk.services.s3.model.PutObjectRequest; +// +//@Slf4j +//@Profile("prod") +//@RequiredArgsConstructor +//@Component +//public class ImageProdUploader implements ImageUploader { +// +// private static final int START_INDEX = 0; +// private static final String IMAGE_PREFIX = "images/"; +// +// @Value("${file.show.upload.location}") +// private String location; +// +// private final S3Client s3Client; +// +// @Override +// @Async +// public void uploadAll(final List convertedImages, final List originImageFiles) { +// IntStream.range(START_INDEX, convertedImages.size()) +// .forEach(index -> uploadImage(convertedImages.get(index), originImageFiles.get(index))); +// } +// +// private void uploadImage(final Image image, final MultipartFile file) { +// try (InputStream inputStream = file.getInputStream()) { +// PutObjectRequest putObjectRequest = PutObjectRequest.builder() +// .bucket(location) +// .key(IMAGE_PREFIX + image.getUniqueName()) +// .contentType(file.getContentType()) +// .build(); +// +// s3Client.putObject(putObjectRequest, RequestBody.fromInputStream(inputStream, file.getSize())); +// log.info("파일 저장 성공 : {}", image.getUniqueName()); +// } catch (Exception e) { +// log.error("파일 저장 실패: {}, 로그 {} : ", image.getUniqueName(), e.getMessage()); +// throw new ImageException(ImageExceptionType.FILE_UPLOAD_FAILURE_EXCEPTION); +// } +// } +// +// @Override +// @Async +// public void deleteAll(final List deletedImageUniqueNames) { +// try { +// List objectsToDelete = deletedImageUniqueNames.stream() +// .map(name -> ObjectIdentifier.builder().key(IMAGE_PREFIX + name).build()) +// .toList(); +// +// DeleteObjectsRequest deleteObjectsRequest = DeleteObjectsRequest.builder() +// .bucket(location) +// .delete(builder -> builder.objects(objectsToDelete)) +// .build(); +// +// s3Client.deleteObjects(deleteObjectsRequest); +// log.info("파일 삭제 성공: {}", deletedImageUniqueNames); +// } catch (Exception e) { +// log.error("파일 삭제 실패: {}, 로그 {} : ", deletedImageUniqueNames, e.getMessage()); +// throw new ImageException(ImageExceptionType.FILE_DELETE_FAILURE_EXCEPTION); +// } +// } +//} diff --git a/backend/pcloud-api/src/main/java/com/api/show/image/infrastructure/ImageRepositoryImpl.java b/backend/pcloud-api/src/main/java/com/api/show/image/infrastructure/ImageRepositoryImpl.java new file mode 100644 index 00000000..fc0db19c --- /dev/null +++ b/backend/pcloud-api/src/main/java/com/api/show/image/infrastructure/ImageRepositoryImpl.java @@ -0,0 +1,34 @@ +package com.api.show.image.infrastructure; + +import com.domain.common.ShowType; +import com.domain.show.common.image.domain.Image; +import com.domain.show.common.image.domain.ImageRepository; +import com.domain.show.common.image.infrastructure.ImageJpaRepository; +import com.domain.show.common.image.infrastructure.ImageQueryRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@RequiredArgsConstructor +@Repository +public class ImageRepositoryImpl implements ImageRepository { + + private final ImageJpaRepository imageJpaRepository; + private final ImageQueryRepository imageQueryRepository; + + @Override + public List saveAll(final List images) { + return imageJpaRepository.saveAll(images); + } + + @Override + public List findImageNamesByTargetIdAndShowType(final Long targetId, final ShowType showType) { + return imageQueryRepository.findImageNamesByTargetIdAndShowType(targetId, showType); + } + + @Override + public void deleteAllByTargetIdAndShowType(final Long targetId, final ShowType showType) { + imageJpaRepository.deleteAllByTargetIdAndShowType(targetId, showType); + } +} diff --git a/backend/pcloud-api/src/main/java/com/api/show/image/infrastructure/S3ImageProcessor.java b/backend/pcloud-api/src/main/java/com/api/show/image/infrastructure/S3ImageProcessor.java new file mode 100644 index 00000000..1a087e26 --- /dev/null +++ b/backend/pcloud-api/src/main/java/com/api/show/image/infrastructure/S3ImageProcessor.java @@ -0,0 +1,56 @@ +package com.api.show.image.infrastructure; + +import com.api.show.image.application.ImageProcessor; +import com.domain.show.common.image.exception.ImageException; +import com.domain.show.common.image.exception.ImageExceptionType; +import java.util.ArrayList; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Component; +import software.amazon.awssdk.services.s3.S3Client; +import software.amazon.awssdk.services.s3.model.DeleteObjectsRequest; +import software.amazon.awssdk.services.s3.model.ObjectIdentifier; +import software.amazon.awssdk.services.s3.model.S3Exception; + +@Component +@RequiredArgsConstructor +public class S3ImageProcessor implements ImageProcessor { + + private static final String IMAGE_PREFIX = "images/"; + private static final String THUMBNAILS_PREFIX = "thumbnails/"; + + private final S3Client s3Client; + + @Value("${file.show.upload.location}") + private String location; + + @Override + @Async + public void deleteImagesByUniqueNames(final List uniqueImageNames) { + if (uniqueImageNames == null || uniqueImageNames.isEmpty()) { + return; + } + try { + List objectIdentifiers = new ArrayList<>(); + uniqueImageNames.forEach(uniqueImageName -> { + objectIdentifiers.add(createObjectIdentifier(IMAGE_PREFIX, uniqueImageName)); + objectIdentifiers.add(createObjectIdentifier(THUMBNAILS_PREFIX, uniqueImageName)); + }); + DeleteObjectsRequest deleteRequest = DeleteObjectsRequest.builder() + .bucket(location) + .delete(builder -> builder.objects(objectIdentifiers)) + .build(); + s3Client.deleteObjects(deleteRequest); + } catch (S3Exception e) { + throw new ImageException(ImageExceptionType.FILE_DELETE_FAILURE_EXCEPTION); + } + } + + private ObjectIdentifier createObjectIdentifier(final String prefix, final String uniqueImageName) { + return ObjectIdentifier.builder() + .key(prefix + uniqueImageName) + .build(); + } +} diff --git a/backend/pcloud-api/src/main/java/com/api/show/popups/application/PopupsQueryService.java b/backend/pcloud-api/src/main/java/com/api/show/popups/application/PopupsQueryService.java index e49a1210..0d347037 100644 --- a/backend/pcloud-api/src/main/java/com/api/show/popups/application/PopupsQueryService.java +++ b/backend/pcloud-api/src/main/java/com/api/show/popups/application/PopupsQueryService.java @@ -6,12 +6,11 @@ import com.domain.show.popups.domain.response.PopupsSpecificResponse; import com.domain.show.popups.event.PopupsFoundEvent; import com.domain.show.popups.exception.PopupsException; +import java.time.LocalDateTime; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; - import static com.domain.show.popups.exception.PopupsExceptionType.POPUPS_NOT_FOUND_EXCEPTION; @RequiredArgsConstructor @@ -39,7 +38,7 @@ private PopupsSpecificResponse cachePopupsWithoutConcurrencyIssue(final Long pop return foundPopups; } - private void cachePopupsIfExpiredEvictTtl(final Long popupsId, PopupsSpecificResponse foundPopups) { + private void cachePopupsIfExpiredEvictTtl(final Long popupsId, final PopupsSpecificResponse foundPopups) { LocalDateTime startFindTime = LocalDateTime.now(); LocalDateTime cacheEvictTime = popupsCacheRepository.findCacheEvictedTimeById(popupsId); diff --git a/backend/pcloud-api/src/main/java/com/api/show/popups/application/PopupsService.java b/backend/pcloud-api/src/main/java/com/api/show/popups/application/PopupsService.java index dce8fab7..95923d2d 100644 --- a/backend/pcloud-api/src/main/java/com/api/show/popups/application/PopupsService.java +++ b/backend/pcloud-api/src/main/java/com/api/show/popups/application/PopupsService.java @@ -1,5 +1,8 @@ package com.api.show.popups.application; +import com.api.show.common.event.ImageCreatedEvent; +import com.api.show.common.event.ImageDeletedEvent; +import com.api.show.common.event.ImageUpdatedEvent; import com.api.show.popups.application.request.PopupsCreateRequest; import com.api.show.popups.application.request.PopupsUpdateRequest; import com.common.config.event.Events; @@ -9,6 +12,7 @@ import com.domain.show.popups.domain.Popups; import com.domain.show.popups.domain.PopupsRepository; import com.domain.show.popups.event.PopupsTagsCreatedEvent; +import com.domain.show.popups.event.PopupsTagsDeletedEvent; import com.domain.show.popups.event.PopupsTagsUpdatedEvent; import com.domain.show.popups.exception.PopupsException; import lombok.RequiredArgsConstructor; @@ -26,9 +30,15 @@ public class PopupsService { private final PopupsCacheRepository popupsCacheRepository; public Long create(final Long memberId, final PopupsCreateRequest request) { - Popups popups = popupsRepository.save(request.toDomain(memberId)); - Events.raise(new PopupsTagsCreatedEvent(popups.getId(), request.tags(), CustomTagType.POPUPS)); - return popups.getId(); + Popups savedPopupsId = popupsRepository.save(request.toDomain(memberId)); + Events.raise(new PopupsTagsCreatedEvent( + savedPopupsId.getId(), + request.tags(), + CustomTagType.POPUPS) + ); + Events.raise(ImageCreatedEvent.createdPopupsImages(savedPopupsId.getId(), request.imageNames())); + + return savedPopupsId.getId(); } public void patchById( @@ -36,10 +46,16 @@ public void patchById( final Long popupsId, final PopupsUpdateRequest request ) { - Popups popups = findPopups(popupsId); - popups.update(request.toDomain(memberId)); + Popups foundPopups = findPopups(popupsId); + foundPopups.validateOwnerEquals(memberId); + foundPopups.update(request.toDomain(memberId)); popupsCacheRepository.evictCache(popupsId); - Events.raise(new PopupsTagsUpdatedEvent(popups.getId(), request.tags(), CustomTagType.POPUPS)); + Events.raise(new PopupsTagsUpdatedEvent( + foundPopups.getId(), + request.tags(), + CustomTagType.POPUPS) + ); + Events.raise(ImageUpdatedEvent.updatedPopupsImages(foundPopups.getId(), request.imageNames())); } private Popups findPopups(final Long popupsId) { @@ -47,10 +63,19 @@ private Popups findPopups(final Long popupsId) { .orElseThrow(() -> new PopupsException(POPUPS_NOT_FOUND_EXCEPTION)); } + public void deleteById(final Long memberId, final Long popupsId) { + Popups foundPopups = findPopups(popupsId); + foundPopups.validateOwnerEquals(memberId); + popupsRepository.deleteById(foundPopups.getId()); + Events.raise(new PopupsTagsDeletedEvent(popupsId, CustomTagType.POPUPS)); + Events.raise(ImageDeletedEvent.deletedPopupsImages(popupsId)); + } + public boolean likes(final Long memberId, final Long popupsId) { Popups popups = findPopupsWithLock(popupsId); boolean canAddLikes = handlePopupsLikes(popupsId, memberId); popups.addLikedCount(canAddLikes); + return canAddLikes; } diff --git a/backend/pcloud-api/src/main/java/com/api/show/popups/application/request/PopupsCreateRequest.java b/backend/pcloud-api/src/main/java/com/api/show/popups/application/request/PopupsCreateRequest.java index ac27fe2b..ab0174af 100644 --- a/backend/pcloud-api/src/main/java/com/api/show/popups/application/request/PopupsCreateRequest.java +++ b/backend/pcloud-api/src/main/java/com/api/show/popups/application/request/PopupsCreateRequest.java @@ -1,26 +1,63 @@ package com.api.show.popups.application.request; import com.domain.show.popups.domain.Popups; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; import java.util.List; public record PopupsCreateRequest( + @NotBlank(message = "팝업스토어 제목을 입력해주세요.") String title, + + @NotBlank(message = "팝업스토어 설명을 입력해주세요.") String description, + + @NotNull(message = "팝업스토어 시작일을 입력해주세요.") LocalDateTime startDate, + + @NotNull(message = "팝업스토어 종료일을 입력해주세요.") LocalDateTime endDate, + + @NotBlank(message = "팝업스토어 운영 시간을 입력해주세요.") String openTimes, + + @NotBlank(message = "팝업스토어 개최 장소를 입력해주세요.") String location, + + @NotBlank(message = "팝업스토어 위도 정보를 입력해세요.") String latitude, + + @NotBlank(message = "팝업스토어 경도 정보를 입력해주세요.") String longitude, + + @NotNull(message = "팝업스토어 주차 가능 여부를 입력해주세요.") Boolean isParkingAvailable, + + @NotNull(message = "팝업스토어 식음료 반입 여부를 입력해주세요.") Boolean isFoodAllowed, + + @NotNull(message = "팝업스토어 반려 동물 출입 가능 여부를 입력해주세요.") Boolean isPetAllowed, + + @NotNull(message = "팝업스토어 키즈존 유무 정보를 입력해주세요.") Boolean isKidsZone, + + @NotNull(message = "팝업스토어 와이파이 사용 가능 여부를 입력해주세요.") Boolean isWifiAvailable, + + @NotNull(message = "팝업스토어 요금 정보를 입력해주세요.") Integer fee, + + @NotBlank(message = "팝업스토어 퍼블릭 태그를 붙여주세요.") String publicTag, - List tags + + @NotEmpty(message = "팝업스토어 커스텀 태그를 붙여주세요.") + List tags, + + @NotEmpty(message = "팝업스토어 이미지명을 입력해주세요.") + List imageNames ) { public Popups toDomain(final Long memberId) { diff --git a/backend/pcloud-api/src/main/java/com/api/show/popups/application/request/PopupsUpdateRequest.java b/backend/pcloud-api/src/main/java/com/api/show/popups/application/request/PopupsUpdateRequest.java index 80120946..41589935 100644 --- a/backend/pcloud-api/src/main/java/com/api/show/popups/application/request/PopupsUpdateRequest.java +++ b/backend/pcloud-api/src/main/java/com/api/show/popups/application/request/PopupsUpdateRequest.java @@ -1,27 +1,62 @@ package com.api.show.popups.application.request; import com.domain.show.popups.domain.Popups; - +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; import java.util.List; public record PopupsUpdateRequest( + @NotBlank(message = "팝업스토어 제목을 입력해주세요.") String title, + + @NotBlank(message = "팝업스토어 설명을 입력해주세요.") String description, + + @NotNull(message = "팝업스토어 시작일을 입력해주세요.") LocalDateTime startDate, + + @NotNull(message = "팝업스토어 종료일을 입력해주세요.") LocalDateTime endDate, + + @NotBlank(message = "팝업스토어 운영 시간을 입력해주세요.") String openTimes, + + @NotBlank(message = "팝업스토어 개최 장소를 입력해주세요.") String location, + + @NotBlank(message = "팝업스토어 위도 정보를 입력해세요.") String latitude, + + @NotBlank(message = "팝업스토어 경도 정보를 입력해주세요.") String longitude, + + @NotNull(message = "팝업스토어 주차 가능 여부를 입력해주세요.") Boolean isParkingAvailable, + + @NotNull(message = "팝업스토어 식음료 반입 여부를 입력해주세요.") Boolean isFoodAllowed, + + @NotNull(message = "팝업스토어 반려 동물 출입 가능 여부를 입력해주세요.") Boolean isPetAllowed, + + @NotNull(message = "팝업스토어 키즈존 유무 정보를 입력해주세요.") Boolean isKidsZone, + + @NotNull(message = "팝업스토어 와이파이 사용 가능 여부를 입력해주세요.") Boolean isWifiAvailable, + + @NotNull(message = "팝업스토어 요금 정보를 입력해주세요.") Integer fee, + + @NotBlank(message = "팝업스토어 퍼블릭 태그를 붙여주세요.") String publicTag, - List tags + + @NotEmpty(message = "팝업스토어 커스텀 태그를 붙여주세요.") + List tags, + + List imageNames ) { public Popups toDomain(final Long memberId) { diff --git a/backend/pcloud-api/src/main/java/com/api/show/popups/infrastructure/PopupsRepositoryImpl.java b/backend/pcloud-api/src/main/java/com/api/show/popups/infrastructure/PopupsRepositoryImpl.java index 604530f4..84f979c0 100644 --- a/backend/pcloud-api/src/main/java/com/api/show/popups/infrastructure/PopupsRepositoryImpl.java +++ b/backend/pcloud-api/src/main/java/com/api/show/popups/infrastructure/PopupsRepositoryImpl.java @@ -54,4 +54,9 @@ public void deleteLikedPopupsByPopupsIdAndMemberId(final Long popupsId, final Lo public LikedPopups saveLikedPopups(final LikedPopups likedPopups) { return likedPopupsJpaRepository.save(likedPopups); } + + @Override + public void deleteById(final Long popupsId) { + popupsJpaRepository.deleteById(popupsId); + } } diff --git a/backend/pcloud-api/src/main/java/com/api/show/popups/presentation/PopupsController.java b/backend/pcloud-api/src/main/java/com/api/show/popups/presentation/PopupsController.java index 8768a776..871994f4 100644 --- a/backend/pcloud-api/src/main/java/com/api/show/popups/presentation/PopupsController.java +++ b/backend/pcloud-api/src/main/java/com/api/show/popups/presentation/PopupsController.java @@ -9,8 +9,11 @@ import com.domain.annotation.AuthMember; import com.domain.annotation.AuthMembers; import com.domain.show.popups.domain.response.PopupsSpecificResponse; +import jakarta.validation.Valid; +import java.net.URI; import lombok.RequiredArgsConstructor; 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; @@ -19,8 +22,6 @@ import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; -import java.net.URI; - import static com.domain.member.domain.vo.MemberRole.ADMIN; import static com.domain.member.domain.vo.MemberRole.MANAGER; @@ -32,13 +33,10 @@ public class PopupsController { private final PopupsService popupsService; private final PopupsQueryService popupsQueryService; - /** - * TODO : 이미지 처리 방식 회의 필요 - */ @PostMapping public ResponseEntity create( @AuthMembers(permit = {MANAGER, ADMIN}) final Long memberId, - @RequestBody final PopupsCreateRequest request + @RequestBody @Valid final PopupsCreateRequest request ) { Long createdPopupsId = popupsService.create(memberId, request); return ResponseEntity.created(URI.create("/popups/" + createdPopupsId)) @@ -57,13 +55,23 @@ public ResponseEntity findById( public ResponseEntity patchById( @AuthMember final Long memberId, @PathVariable final Long popupsId, - @RequestBody final PopupsUpdateRequest request + @RequestBody @Valid final PopupsUpdateRequest request ) { popupsService.patchById(memberId, popupsId, request); return ResponseEntity.noContent() .build(); } + @DeleteMapping("/{popupsId}") + public ResponseEntity deleteById( + @AuthMembers(permit = {ADMIN, MANAGER}) final Long memberId, + @PathVariable final Long popupsId + ) { + popupsService.deleteById(memberId, popupsId); + return ResponseEntity.noContent() + .build(); + } + @PostMapping("/{popupsId}/likes") public ResponseEntity likes( @AuthMember final Long memberId, diff --git a/backend/pcloud-api/src/main/java/com/api/show/show/presentation/ShowController.java b/backend/pcloud-api/src/main/java/com/api/show/show/presentation/ShowController.java index 46ca79c5..01ec2c77 100644 --- a/backend/pcloud-api/src/main/java/com/api/show/show/presentation/ShowController.java +++ b/backend/pcloud-api/src/main/java/com/api/show/show/presentation/ShowController.java @@ -20,7 +20,7 @@ public class ShowController { private final ShowQueryService showQueryService; @GetMapping - public ResponseEntity> findAll( + public ResponseEntity> findFilteredShowsWithPaging( @RequestParam(required = false) final Long showId, @RequestParam(required = false, defaultValue = "10") final Integer pageSize, @RequestParam(required = false, defaultValue = "popups") final String showType, diff --git a/backend/pcloud-api/src/main/resources/application.yml b/backend/pcloud-api/src/main/resources/application.yml index af3843a0..60ca1a23 100644 --- a/backend/pcloud-api/src/main/resources/application.yml +++ b/backend/pcloud-api/src/main/resources/application.yml @@ -1,6 +1,10 @@ spring: profiles: active: local + servlet: + multipart: + max-file-size: 10MB + max-request-size: 10MB server: servlet: diff --git a/backend/pcloud-api/src/test/java/com/api/show/exhibition/fixture/ExhibitionRequestFixtures.java b/backend/pcloud-api/src/test/java/com/api/show/exhibition/fixture/ExhibitionRequestFixtures.java index 9d7255a4..fe1a4689 100644 --- a/backend/pcloud-api/src/test/java/com/api/show/exhibition/fixture/ExhibitionRequestFixtures.java +++ b/backend/pcloud-api/src/test/java/com/api/show/exhibition/fixture/ExhibitionRequestFixtures.java @@ -17,8 +17,8 @@ public class ExhibitionRequestFixtures { LocalDateTime.now(), """ 평일 09:00 ~ 18:00, - 주말 12:00 ~ 21:00 - """, + 주말 12:00 ~ 21:00 + """, "서울 마포구 동교동 155-55", "37.556725", "126.9234952", @@ -29,7 +29,8 @@ public class ExhibitionRequestFixtures { true, 10000, PublicTag.EXHIBITION.getName(), - List.of("빵빵이", "만원", "가족", "데이트") + List.of("빵빵이", "만원", "가족", "데이트"), + List.of("image1.png", "image2.png", "image3.png") ); } @@ -41,8 +42,8 @@ public class ExhibitionRequestFixtures { LocalDateTime.now(), """ 평일 09:00 ~ 18:00, - 주말 12:00 ~ 21:00 - """, + 주말 12:00 ~ 21:00 + """, "서울 마포구 동교동 155-55", "37.556725", "126.9234952", @@ -53,7 +54,8 @@ public class ExhibitionRequestFixtures { true, 10000, PublicTag.EXHIBITION.getName(), - List.of("빵빵이", "만원", "가족", "데이트") + List.of("빵빵이", "만원", "가족", "데이트"), + List.of("image4.png", "image5.png", "image6.png") ); } } diff --git a/backend/pcloud-api/src/test/java/com/api/show/exhibition/presentation/ExhibitionControllerWebMvcTest.java b/backend/pcloud-api/src/test/java/com/api/show/exhibition/presentation/ExhibitionControllerWebMvcTest.java index ab2db318..8e985893 100644 --- a/backend/pcloud-api/src/test/java/com/api/show/exhibition/presentation/ExhibitionControllerWebMvcTest.java +++ b/backend/pcloud-api/src/test/java/com/api/show/exhibition/presentation/ExhibitionControllerWebMvcTest.java @@ -6,6 +6,7 @@ import com.api.show.exhibition.application.dto.ExhibitionUpdateRequest; import com.domain.show.exhibition.domain.dto.ExhibitionSpecificResponse; import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Optional; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Test; @@ -14,8 +15,6 @@ import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.test.web.servlet.MockMvc; -import java.util.Optional; - import static com.api.helper.RestDocsHelper.customDocument; import static com.api.show.exhibition.fixture.ExhibitionRequestFixtures.개인전시회_생성_요청_생성; import static com.api.show.exhibition.fixture.ExhibitionRequestFixtures.개인전시회_업데이트_요청_생성; @@ -93,7 +92,8 @@ class ExhibitionControllerWebMvcTest extends MockBeanInjection { fieldWithPath("isWifiAvailable").description("와이파이 사용가능 여부"), fieldWithPath("fee").description("입장 요금 (없다면 0)"), fieldWithPath("publicTag").description("큰 범주 안에서 퍼블릭 태그"), - fieldWithPath("tags").description("업로더가 설정하는 커스텀 태그") + fieldWithPath("tags").description("업로더가 설정하는 커스텀 태그"), + fieldWithPath("imageNames").description("업로더의 이미지명(프론트에서 UUID로 변환한 이미지명)") ), responseHeaders( headerWithName("location").description("생성된 개인전시회 redirection URL") @@ -136,7 +136,8 @@ class ExhibitionControllerWebMvcTest extends MockBeanInjection { fieldWithPath("publicTag").description("공용 퍼블릭 태그"), fieldWithPath("visitedCount").description("개인전시회 게시글 방문자 수"), fieldWithPath("likedCount").description("개인전시회 게시글 좋아요 수"), - fieldWithPath("tags[]").description("커스텀 태그") + fieldWithPath("tags[]").description("커스텀 태그"), + fieldWithPath("imageNames[]").description("이미지 이름") ) )); } @@ -177,10 +178,10 @@ class ExhibitionControllerWebMvcTest extends MockBeanInjection { fieldWithPath("isWifiAvailable").description("와이파이 사용가능 여부"), fieldWithPath("fee").description("입장 요금 (없다면 0)"), fieldWithPath("publicTag").description("큰 범주 안에서 퍼블릭 태그"), - fieldWithPath("tags").description("업로더가 설정하는 커스텀 태그") + fieldWithPath("tags").description("업로더가 설정하는 커스텀 태그"), + fieldWithPath("imageNames").description("업로더의 새로 저장할 이미지명(프론트에서 UUID로 변환한 이미지명)") ) )); - } @Test diff --git a/backend/pcloud-api/src/test/java/com/api/show/popups/application/PopupsServiceTest.java b/backend/pcloud-api/src/test/java/com/api/show/popups/application/PopupsServiceTest.java index e4d09f54..554dfec5 100644 --- a/backend/pcloud-api/src/test/java/com/api/show/popups/application/PopupsServiceTest.java +++ b/backend/pcloud-api/src/test/java/com/api/show/popups/application/PopupsServiceTest.java @@ -1,12 +1,11 @@ package com.api.show.popups.application; import com.api.show.popups.application.request.PopupsCreateRequest; -import com.common.exception.AuthException; -import com.common.exception.AuthExceptionType; import com.domain.show.popups.cache.PopupsCacheRepository; import com.domain.show.popups.domain.Popups; import com.domain.show.popups.domain.PopupsRepository; import com.domain.show.popups.exception.PopupsException; +import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; @@ -15,15 +14,12 @@ import show.popups.infrastructure.FakePopupsCacheRepository; import show.popups.infrastructure.FakePopupsRepository; -import java.util.Optional; - import static com.api.show.popups.fixture.request.PopupsRequestFixtures.팝업스토어_생성_요청; import static com.domain.show.popups.exception.PopupsExceptionType.POPUPS_NOT_FOUND_EXCEPTION; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.assertj.core.api.SoftAssertions.assertSoftly; import static show.popups.domain.PopupsFixture.일반_팝업_스토어_생성_뷰티; -import static show.popups.domain.PopupsFixture.일반_팝업_스토어_생성_뷰티_유효하지_않은_주인; import static show.popups.domain.PopupsFixture.일반_팝업_스토어_생성_펫샵; @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @@ -76,18 +72,6 @@ class 팝업스토어_업데이트 { .isEqualTo(updatedPopups); }); } - - @Test - void 유저가_다르면_업데이트하지_못한다() { - // given - Popups savedPopups = popupsRepository.save(일반_팝업_스토어_생성_뷰티()); - Popups updatedPopups = 일반_팝업_스토어_생성_뷰티_유효하지_않은_주인(); - - // when & then - assertThatThrownBy(() -> savedPopups.update(updatedPopups)) - .isInstanceOf(AuthException.class) - .hasMessageContaining(AuthExceptionType.AUTH_NOT_EQUALS_EXCEPTION.message()); - } } @Nested diff --git a/backend/pcloud-api/src/test/java/com/api/show/popups/fixture/request/PopupsRequestFixtures.java b/backend/pcloud-api/src/test/java/com/api/show/popups/fixture/request/PopupsRequestFixtures.java index b33e29a4..d146a3ee 100644 --- a/backend/pcloud-api/src/test/java/com/api/show/popups/fixture/request/PopupsRequestFixtures.java +++ b/backend/pcloud-api/src/test/java/com/api/show/popups/fixture/request/PopupsRequestFixtures.java @@ -3,7 +3,6 @@ import com.api.show.popups.application.request.PopupsCreateRequest; import com.api.show.popups.application.request.PopupsUpdateRequest; import com.domain.show.common.PublicTag; - import java.time.LocalDateTime; import java.util.List; @@ -19,7 +18,7 @@ public class PopupsRequestFixtures { """ 평일 09:00 ~ 18:00, 주말 12:00 ~ 21:00 - """, + """, "37.556725", "126.9234952", true, @@ -29,7 +28,8 @@ public class PopupsRequestFixtures { true, 10000, PublicTag.CHARACTER.getName(), - List.of("빵빵이", "만원", "가족", "데이트") + List.of("빵빵이", "만원", "가족", "데이트"), + List.of("image1.png", "image2.png", "image3.png") ); } @@ -43,7 +43,7 @@ public class PopupsRequestFixtures { """ 평일 09:00 ~ 18:00, 주말 12:00 ~ 21:00 - """, + """, "37.556725", "126.9234952", true, @@ -53,7 +53,8 @@ public class PopupsRequestFixtures { true, 10000, PublicTag.CHARACTER.getName(), - List.of("빵빵이", "만원", "가족", "데이트") + List.of("빵빵이", "만원", "가족", "데이트"), + List.of("image1.png", "image2.png", "image3.png") ); } } diff --git a/backend/pcloud-api/src/test/java/com/api/show/popups/presentation/PopupsControllerWebMvcTest.java b/backend/pcloud-api/src/test/java/com/api/show/popups/presentation/PopupsControllerWebMvcTest.java index 9d5e4351..8ceb1431 100644 --- a/backend/pcloud-api/src/test/java/com/api/show/popups/presentation/PopupsControllerWebMvcTest.java +++ b/backend/pcloud-api/src/test/java/com/api/show/popups/presentation/PopupsControllerWebMvcTest.java @@ -31,6 +31,7 @@ import static org.springframework.restdocs.headers.HeaderDocumentation.headerWithName; import static org.springframework.restdocs.headers.HeaderDocumentation.requestHeaders; import static org.springframework.restdocs.headers.HeaderDocumentation.responseHeaders; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; @@ -48,6 +49,8 @@ @WebMvcTest(PopupsController.class) class PopupsControllerWebMvcTest extends MockBeanInjection { + private static final String BEARER_TOKEN = "Bearer tokenInfo ~~"; + @Autowired private MockMvc mockMvc; @@ -66,7 +69,7 @@ class PopupsControllerWebMvcTest extends MockBeanInjection { // when & then mockMvc.perform(post("/popups") - .header(AUTHORIZATION, "Bearer tokenInfo ~~") + .header(AUTHORIZATION, BEARER_TOKEN) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request)) ).andExpect(status().isCreated()) @@ -90,7 +93,8 @@ class PopupsControllerWebMvcTest extends MockBeanInjection { fieldWithPath("latitude").description("latitude, 위도 정보 (String)"), fieldWithPath("longitude").description("longitude, 경도 정보 (String)"), fieldWithPath("publicTag").description("큰 범주 안에서 퍼블릭 태그"), - fieldWithPath("tags").description("업로더가 설정하는 커스텀 태그") + fieldWithPath("tags").description("업로더가 설정하는 커스텀 태그"), + fieldWithPath("imageNames").description("업로더의 이미지명(프론트에서 UUID로 변환한 이미지명)") ), responseHeaders( headerWithName("location").description("생성된 팝업스토어 redirection URL") @@ -133,7 +137,8 @@ class PopupsControllerWebMvcTest extends MockBeanInjection { fieldWithPath("publicTag").description("공용 퍼블릭 태그"), fieldWithPath("visitedCount").description("팝업스토어 게시글 방문자 수"), fieldWithPath("likedCount").description("팝업스토어 게시글 좋아요 수"), - fieldWithPath("tags[]").description("커스텀 태그") + fieldWithPath("tags[]").description("커스텀 태그"), + fieldWithPath("imageNames[]").description("이미지 이름") ) )); } @@ -146,7 +151,7 @@ class PopupsControllerWebMvcTest extends MockBeanInjection { // when & then mockMvc.perform(patch("/popups/{popupsId}", 1) - .header(AUTHORIZATION, "Bearer tokenInfo ~~") + .header(AUTHORIZATION, BEARER_TOKEN) .contentType(MediaType.APPLICATION_JSON) .content(objectMapper.writeValueAsString(request)) ).andExpect(status().isNoContent()) @@ -170,7 +175,28 @@ class PopupsControllerWebMvcTest extends MockBeanInjection { fieldWithPath("latitude").description("latitude, 위도 정보 (String)"), fieldWithPath("longitude").description("longitude, 경도 정보 (String)"), fieldWithPath("publicTag").description("큰 범주 안에서 퍼블릭 태그"), - fieldWithPath("tags").description("업로더가 설정하는 커스텀 태그") + fieldWithPath("tags").description("업로더가 설정하는 커스텀 태그"), + fieldWithPath("imageNames").description("업로더의 새로 저장할 이미지명(프론트에서 UUID로 변환한 이미지명)") + ) + )); + } + + @Test + void 팝업스토어를_삭제한다() throws Exception { + // given + when(memberRepository.findById(anyLong())).thenReturn(Optional.of(어드민_멤버_생성_id_없음_kakao_oauth_가입())); + doNothing().when(popupsService).deleteById(anyLong(), anyLong()); + + // when & then + mockMvc.perform(delete("/popups/{popupsId}", 1L) + .header(AUTHORIZATION, BEARER_TOKEN) + ).andExpect(status().isNoContent()) + .andDo(customDocument("delete_popups", + requestHeaders( + headerWithName(AUTHORIZATION).description("유저 토큰 정보") + ), + pathParameters( + parameterWithName("popupsId").description("팝업스토어 id") ) )); } @@ -194,7 +220,8 @@ class PopupsControllerWebMvcTest extends MockBeanInjection { ), responseFields( fieldWithPath("popupsId").description("팝업스토어 id"), - fieldWithPath("isStatusLiked").description("팝업스토어 좋아요 상태 (true면 좋아요 처리되고, false면 좋아요 취소 처리됨)") + fieldWithPath("isStatusLiked").description( + "팝업스토어 좋아요 상태 (true면 좋아요 처리되고, false면 좋아요 취소 처리됨)") ) )); } diff --git a/backend/pcloud-domain/src/main/java/com/domain/member/domain/Member.java b/backend/pcloud-domain/src/main/java/com/domain/member/domain/Member.java index 58529191..3ffb2859 100644 --- a/backend/pcloud-domain/src/main/java/com/domain/member/domain/Member.java +++ b/backend/pcloud-domain/src/main/java/com/domain/member/domain/Member.java @@ -54,4 +54,8 @@ public static Member createWithNormalRole( .memberRole(MemberRole.NORMAL) .build(); } + + public void changeMemberRoleToAdmin() { + this.memberRole = MemberRole.ADMIN; + } } diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/common/image/domain/Image.java b/backend/pcloud-domain/src/main/java/com/domain/show/common/image/domain/Image.java new file mode 100644 index 00000000..66178137 --- /dev/null +++ b/backend/pcloud-domain/src/main/java/com/domain/show/common/image/domain/Image.java @@ -0,0 +1,52 @@ +package com.domain.show.common.image.domain; + +import com.domain.common.ShowType; +import com.domain.global.domain.BaseEntity; +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@EqualsAndHashCode(of = "id", callSuper = false) +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor(access = AccessLevel.PRIVATE) +@Entity +public class Image extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false) + private Long targetId; + + @Column(nullable = false) + private String name; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private ShowType showType; + + public static Image of( + final Long targetId, + final ShowType showType, + final String name + ) { + return Image.builder() + .targetId(targetId) + .showType(showType) + .name(name) + .build(); + } +} diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/common/image/domain/ImageRepository.java b/backend/pcloud-domain/src/main/java/com/domain/show/common/image/domain/ImageRepository.java new file mode 100644 index 00000000..e5fa5fbe --- /dev/null +++ b/backend/pcloud-domain/src/main/java/com/domain/show/common/image/domain/ImageRepository.java @@ -0,0 +1,13 @@ +package com.domain.show.common.image.domain; + +import com.domain.common.ShowType; +import java.util.List; + +public interface ImageRepository { + + List saveAll(List images); + + List findImageNamesByTargetIdAndShowType(Long targetId, ShowType showType); + + void deleteAllByTargetIdAndShowType(Long targetId, ShowType showType); +} diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/common/image/exception/ImageException.java b/backend/pcloud-domain/src/main/java/com/domain/show/common/image/exception/ImageException.java new file mode 100644 index 00000000..100e1fc4 --- /dev/null +++ b/backend/pcloud-domain/src/main/java/com/domain/show/common/image/exception/ImageException.java @@ -0,0 +1,19 @@ +package com.domain.show.common.image.exception; + +import com.common.exception.CustomException; +import com.common.exception.CustomExceptionType; + +public class ImageException extends CustomException { + + private final ImageExceptionType imageExceptionType; + + public ImageException(final ImageExceptionType imageExceptionType) { + super(imageExceptionType); + this.imageExceptionType = imageExceptionType; + } + + @Override + public CustomExceptionType getExceptionType() { + return imageExceptionType; + } +} diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/common/image/exception/ImageExceptionType.java b/backend/pcloud-domain/src/main/java/com/domain/show/common/image/exception/ImageExceptionType.java new file mode 100644 index 00000000..5bb34755 --- /dev/null +++ b/backend/pcloud-domain/src/main/java/com/domain/show/common/image/exception/ImageExceptionType.java @@ -0,0 +1,39 @@ +package com.domain.show.common.image.exception; + +import com.common.exception.CustomExceptionType; + +public enum ImageExceptionType implements CustomExceptionType { + + UNSUPPORTED_IMAGE_FORMAT_EXCEPTION(400, "IMAGE001", "이미지 확장자 형식이 맞지 않습니다. jpg, jpeg, gif, bmp, png 확장자로 변경 후 재시도 해주세요."), + FILE_UPLOAD_FAILURE_EXCEPTION(500, "IMAGE002", "이미지 업로드에 실패했습니다."), + FILE_DELETE_FAILURE_EXCEPTION(500, "IMAGE003", "이미지 삭제에 실패했습니다."); + + private final int httpStatusCode; + private final String customCode; + private final String message; + + ImageExceptionType( + final int httpStatusCode, + final String customCode, + final String message + ) { + this.httpStatusCode = httpStatusCode; + this.customCode = customCode; + this.message = message; + } + + @Override + public String message() { + return this.message; + } + + @Override + public int httpStatusCode() { + return this.httpStatusCode; + } + + @Override + public String customCode() { + return this.customCode; + } +} diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/common/image/infrastructure/ImageJpaRepository.java b/backend/pcloud-domain/src/main/java/com/domain/show/common/image/infrastructure/ImageJpaRepository.java new file mode 100644 index 00000000..c73252cd --- /dev/null +++ b/backend/pcloud-domain/src/main/java/com/domain/show/common/image/infrastructure/ImageJpaRepository.java @@ -0,0 +1,17 @@ +package com.domain.show.common.image.infrastructure; + +import com.domain.common.ShowType; +import com.domain.show.common.image.domain.Image; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface ImageJpaRepository extends JpaRepository { + + @Override + List saveAll(Iterable images); + + @Override + void deleteAll(Iterable images); + + void deleteAllByTargetIdAndShowType(Long targetId, ShowType showType); +} diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/common/image/infrastructure/ImageQueryRepository.java b/backend/pcloud-domain/src/main/java/com/domain/show/common/image/infrastructure/ImageQueryRepository.java new file mode 100644 index 00000000..711791c2 --- /dev/null +++ b/backend/pcloud-domain/src/main/java/com/domain/show/common/image/infrastructure/ImageQueryRepository.java @@ -0,0 +1,23 @@ +package com.domain.show.common.image.infrastructure; + +import com.domain.common.ShowType; +import com.querydsl.jpa.impl.JPAQueryFactory; +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; + +import static com.domain.show.common.image.domain.QImage.image; + +@RequiredArgsConstructor +@Repository +public class ImageQueryRepository { + + private final JPAQueryFactory jpaQueryFactory; + + public List findImageNamesByTargetIdAndShowType(final Long targetId, final ShowType showType) { + return jpaQueryFactory.select(image.name) + .from(image) + .where(image.targetId.eq(targetId), image.showType.eq(showType)) + .fetch(); + } +} diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/exhibition/domain/Exhibition.java b/backend/pcloud-domain/src/main/java/com/domain/show/exhibition/domain/Exhibition.java index d04e8118..7ce66d20 100644 --- a/backend/pcloud-domain/src/main/java/com/domain/show/exhibition/domain/Exhibition.java +++ b/backend/pcloud-domain/src/main/java/com/domain/show/exhibition/domain/Exhibition.java @@ -110,7 +110,6 @@ public static Exhibition of( } public void update(final Exhibition updateExhibition) { - validateOwnerEquals(updateExhibition.getOwnerId()); this.showDetails = updateExhibition.getShowDetails(); this.showSchedule = updateExhibition.getShowSchedule(); this.position = updateExhibition.getPosition(); @@ -119,7 +118,7 @@ public void update(final Exhibition updateExhibition) { this.publicTag = updateExhibition.getPublicTag(); } - public void validateOwnerEquals(final Long ownerId) { + public void validateOwnerWithOwnerId(final Long ownerId) { if (!this.getOwnerId().equals(ownerId)) { throw new AuthException(AUTH_NOT_EQUALS_EXCEPTION); } diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/exhibition/domain/dto/ExhibitionSpecificResponse.java b/backend/pcloud-domain/src/main/java/com/domain/show/exhibition/domain/dto/ExhibitionSpecificResponse.java index f879f54f..c46a3074 100644 --- a/backend/pcloud-domain/src/main/java/com/domain/show/exhibition/domain/dto/ExhibitionSpecificResponse.java +++ b/backend/pcloud-domain/src/main/java/com/domain/show/exhibition/domain/dto/ExhibitionSpecificResponse.java @@ -3,7 +3,7 @@ import com.domain.show.common.PublicTag; import java.math.BigDecimal; import java.time.LocalDateTime; -import java.util.List; +import java.util.Set; public record ExhibitionSpecificResponse( Long exhibitionId, @@ -25,7 +25,8 @@ public record ExhibitionSpecificResponse( String publicTag, Integer visitedCount, Integer likedCount, - List tags + Set tags, + Set imageNames ) { public ExhibitionSpecificResponse( @@ -48,7 +49,8 @@ public ExhibitionSpecificResponse( PublicTag publicTag, Integer visitedCount, Integer likedCount, - List tags + Set tags, + Set imageNames ) { this( exhibitionId, @@ -70,7 +72,8 @@ public ExhibitionSpecificResponse( publicTag.getName(), visitedCount, likedCount, - tags + tags, + imageNames ); } } diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/exhibition/event/ExhibitionTagsDeletedEvent.java b/backend/pcloud-domain/src/main/java/com/domain/show/exhibition/event/ExhibitionTagsDeletedEvent.java new file mode 100644 index 00000000..803618c8 --- /dev/null +++ b/backend/pcloud-domain/src/main/java/com/domain/show/exhibition/event/ExhibitionTagsDeletedEvent.java @@ -0,0 +1,9 @@ +package com.domain.show.exhibition.event; + +import com.domain.common.CustomTagType; + +public record ExhibitionTagsDeletedEvent( + Long exhibitionId, + CustomTagType type +) { +} diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/exhibition/infrastructure/ExhibitionQueryRepository.java b/backend/pcloud-domain/src/main/java/com/domain/show/exhibition/infrastructure/ExhibitionQueryRepository.java index eec0cd31..b334be88 100644 --- a/backend/pcloud-domain/src/main/java/com/domain/show/exhibition/infrastructure/ExhibitionQueryRepository.java +++ b/backend/pcloud-domain/src/main/java/com/domain/show/exhibition/infrastructure/ExhibitionQueryRepository.java @@ -1,18 +1,19 @@ package com.domain.show.exhibition.infrastructure; import com.domain.common.CustomTagType; +import com.domain.common.ShowType; import com.domain.show.exhibition.domain.dto.ExhibitionSpecificResponse; import com.querydsl.jpa.impl.JPAQueryFactory; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - import java.util.List; import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; import static com.domain.customtag.domain.QCustomTag.customTag; +import static com.domain.show.common.image.domain.QImage.image; import static com.domain.show.exhibition.domain.QExhibition.exhibition; import static com.querydsl.core.group.GroupBy.groupBy; -import static com.querydsl.core.group.GroupBy.list; +import static com.querydsl.core.group.GroupBy.set; import static com.querydsl.core.types.Projections.constructor; @RequiredArgsConstructor @@ -27,7 +28,12 @@ public Optional findSpecificById(final Long exhibiti .leftJoin(customTag).on( customTag.targetId.eq(exhibitionId), customTag.type.eq(CustomTagType.PERSONAL_EXHIBITION) - ).transform(groupBy(exhibition.id) + ) + .leftJoin(image).on( + image.targetId.eq(exhibitionId), + image.showType.eq(ShowType.EXHIBITION) + ) + .transform(groupBy(exhibition.id) .list(constructor(ExhibitionSpecificResponse.class, exhibition.id, exhibition.ownerId, @@ -48,7 +54,8 @@ public Optional findSpecificById(final Long exhibiti exhibition.publicTag, exhibition.statistic.visitedCount, exhibition.statistic.likedCount, - list(customTag.name) + set(customTag.name), + set(image.name) )) ); diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/popups/domain/Popups.java b/backend/pcloud-domain/src/main/java/com/domain/show/popups/domain/Popups.java index 5bcc0641..1347e268 100644 --- a/backend/pcloud-domain/src/main/java/com/domain/show/popups/domain/Popups.java +++ b/backend/pcloud-domain/src/main/java/com/domain/show/popups/domain/Popups.java @@ -110,7 +110,6 @@ public static Popups of( } public void update(final Popups updatedPopups) { - validateOwnerEquals(updatedPopups.getOwnerId()); this.showDetails = updatedPopups.getShowDetails(); this.showSchedule = updatedPopups.getShowSchedule(); this.position = updatedPopups.getPosition(); @@ -119,7 +118,7 @@ public void update(final Popups updatedPopups) { this.publicTag = updatedPopups.getPublicTag(); } - private void validateOwnerEquals(final Long ownerId) { + public void validateOwnerEquals(final Long ownerId) { if (!this.getOwnerId().equals(ownerId)) { throw new AuthException(AUTH_NOT_EQUALS_EXCEPTION); } diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/popups/domain/PopupsRepository.java b/backend/pcloud-domain/src/main/java/com/domain/show/popups/domain/PopupsRepository.java index 6ed62b3c..5b3ba6c6 100644 --- a/backend/pcloud-domain/src/main/java/com/domain/show/popups/domain/PopupsRepository.java +++ b/backend/pcloud-domain/src/main/java/com/domain/show/popups/domain/PopupsRepository.java @@ -19,4 +19,6 @@ public interface PopupsRepository { void deleteLikedPopupsByPopupsIdAndMemberId(Long popupsId, Long memberId); LikedPopups saveLikedPopups(LikedPopups likedPopups); + + void deleteById(Long popupsId); } diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/popups/domain/response/PopupsSpecificResponse.java b/backend/pcloud-domain/src/main/java/com/domain/show/popups/domain/response/PopupsSpecificResponse.java index e661a283..22c76263 100644 --- a/backend/pcloud-domain/src/main/java/com/domain/show/popups/domain/response/PopupsSpecificResponse.java +++ b/backend/pcloud-domain/src/main/java/com/domain/show/popups/domain/response/PopupsSpecificResponse.java @@ -1,11 +1,10 @@ package com.domain.show.popups.domain.response; import com.domain.show.common.PublicTag; - import java.io.Serializable; import java.math.BigDecimal; import java.time.LocalDateTime; -import java.util.List; +import java.util.Set; public record PopupsSpecificResponse( Long id, @@ -27,7 +26,8 @@ public record PopupsSpecificResponse( String publicTag, Integer visitedCount, Integer likedCount, - List tags + Set tags, + Set imageNames ) implements Serializable { public PopupsSpecificResponse( @@ -50,7 +50,8 @@ public PopupsSpecificResponse( PublicTag publicTag, Integer visitedCount, Integer likedCount, - List tags + Set tags, + Set imageNames ) { this( id, @@ -72,7 +73,8 @@ public PopupsSpecificResponse( publicTag.getName(), visitedCount, likedCount, - tags + tags, + imageNames ); } } diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/popups/event/PopupsTagsDeletedEvent.java b/backend/pcloud-domain/src/main/java/com/domain/show/popups/event/PopupsTagsDeletedEvent.java new file mode 100644 index 00000000..deba55b2 --- /dev/null +++ b/backend/pcloud-domain/src/main/java/com/domain/show/popups/event/PopupsTagsDeletedEvent.java @@ -0,0 +1,9 @@ +package com.domain.show.popups.event; + +import com.domain.common.CustomTagType; + +public record PopupsTagsDeletedEvent ( + Long popupsId, + CustomTagType type +) { +} diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/popups/infrastructure/PopupsJpaRepository.java b/backend/pcloud-domain/src/main/java/com/domain/show/popups/infrastructure/PopupsJpaRepository.java index 1514e0fc..34eb4340 100644 --- a/backend/pcloud-domain/src/main/java/com/domain/show/popups/infrastructure/PopupsJpaRepository.java +++ b/backend/pcloud-domain/src/main/java/com/domain/show/popups/infrastructure/PopupsJpaRepository.java @@ -18,4 +18,6 @@ public interface PopupsJpaRepository extends JpaRepository { Optional findByIdWithOptimisticLock(@Param("id") Long id); Popups save(Popups popups); + + void deleteById(Long id); } diff --git a/backend/pcloud-domain/src/main/java/com/domain/show/popups/infrastructure/PopupsQueryRepository.java b/backend/pcloud-domain/src/main/java/com/domain/show/popups/infrastructure/PopupsQueryRepository.java index 74910b99..1b5121c4 100644 --- a/backend/pcloud-domain/src/main/java/com/domain/show/popups/infrastructure/PopupsQueryRepository.java +++ b/backend/pcloud-domain/src/main/java/com/domain/show/popups/infrastructure/PopupsQueryRepository.java @@ -1,19 +1,20 @@ package com.domain.show.popups.infrastructure; import com.domain.common.CustomTagType; +import com.domain.common.ShowType; import com.domain.show.popups.domain.response.PopupsSpecificResponse; import com.querydsl.core.types.Projections; import com.querydsl.jpa.impl.JPAQueryFactory; -import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Repository; - import java.util.List; import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Repository; import static com.domain.customtag.domain.QCustomTag.customTag; +import static com.domain.show.common.image.domain.QImage.image; import static com.domain.show.popups.domain.QPopups.popups; import static com.querydsl.core.group.GroupBy.groupBy; -import static com.querydsl.core.group.GroupBy.list; +import static com.querydsl.core.group.GroupBy.set; @RequiredArgsConstructor @Repository @@ -27,7 +28,12 @@ public Optional findSpecificById(final Long popupsId) { .leftJoin(customTag).on( customTag.targetId.eq(popupsId), customTag.type.eq(CustomTagType.POPUPS) - ).transform(groupBy(popups.id) + ) + .leftJoin(image).on( + image.targetId.eq(popupsId), + image.showType.eq(ShowType.POPUPS) + ) + .transform(groupBy(popups.id) .list(Projections.constructor(PopupsSpecificResponse.class, popups.id, popups.ownerId, @@ -48,7 +54,8 @@ public Optional findSpecificById(final Long popupsId) { popups.publicTag, popups.statistic.visitedCount, popups.statistic.likedCount, - list(customTag.name) + set(customTag.name), + set(image.name) ) )); diff --git a/backend/pcloud-domain/src/main/resources/application-domain-local.yml b/backend/pcloud-domain/src/main/resources/application-domain-local.yml index 933dcbdd..6793abfa 100644 --- a/backend/pcloud-domain/src/main/resources/application-domain-local.yml +++ b/backend/pcloud-domain/src/main/resources/application-domain-local.yml @@ -17,3 +17,18 @@ spring: hibernate: format_sql: true show_sql: true + +file: + show: + upload: + location: /Users/yoon/Desktop/images/ + +cloud: + aws: + stack: + auto: local + region: + static: local + credentials: + accessKey: local + secretKey: local diff --git a/backend/pcloud-domain/src/main/resources/application-domain-prod.yml b/backend/pcloud-domain/src/main/resources/application-domain-prod.yml index 6c4d8307..9dd64ec9 100644 --- a/backend/pcloud-domain/src/main/resources/application-domain-prod.yml +++ b/backend/pcloud-domain/src/main/resources/application-domain-prod.yml @@ -1,14 +1,30 @@ spring: datasource: - url: jdbc:mysql://prod:3306/pcloud?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&allowPublicKeyRetrieval=true - username: prod - password: prod + url: jdbc:mysql://localhost:3306/pcloud?useSSL=false&serverTimezone=UTC&useUnicode=true&characterEncoding=utf8&allowPublicKeyRetrieval=true + username: root + password: root driver-class-name: com.mysql.cj.jdbc.Driver flyway: - enabled: true - baseline-on-migrate: true + enabled: false + baseline-on-migrate: false jpa: hibernate: - ddl-auto: validate + ddl-auto: update + +file: + show: + upload: + location: ENC(BhTPOrqCYaDXNnQxelJwoKDldhvxBi0H4VgXUYjOXoM=) + + +cloud: + aws: + stack: + auto: false + region: + static: ENC(OqbCfhf9eS35s5y0MSOSkR6eOnCh04/Q) + credentials: + accessKey: ENC(GhaAryKVCSC5A17CEBaOSYaGIcafn3USgw+P61cp7R8=) + secretKey: ENC(WuJWPMVt3b3ezCBbpLvS1K44mqaMy1FXhaC4CjWdBhSh+PIQmt/SXMKTIodjPUV2jlySuZ7Qh/8=) diff --git a/backend/pcloud-domain/src/main/resources/application-domain-test.yml b/backend/pcloud-domain/src/main/resources/application-domain-test.yml index 51ee3e1f..c1fc297a 100644 --- a/backend/pcloud-domain/src/main/resources/application-domain-test.yml +++ b/backend/pcloud-domain/src/main/resources/application-domain-test.yml @@ -16,3 +16,19 @@ spring: hibernate: format_sql: true show_sql: true + +file: + show: + upload: + location: /test + + +cloud: + aws: + stack: + auto: test + region: + static: test + credentials: + accessKey: test + secretKey: test diff --git a/backend/pcloud-domain/src/test/java/com/domain/show/exhibition/domain/ExhibitionTest.java b/backend/pcloud-domain/src/test/java/com/domain/show/exhibition/domain/ExhibitionTest.java index 22945ef3..54d8bb61 100644 --- a/backend/pcloud-domain/src/test/java/com/domain/show/exhibition/domain/ExhibitionTest.java +++ b/backend/pcloud-domain/src/test/java/com/domain/show/exhibition/domain/ExhibitionTest.java @@ -1,17 +1,13 @@ package com.domain.show.exhibition.domain; -import com.common.exception.AuthException; -import com.common.exception.AuthExceptionType; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator.ReplaceUnderscores; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; import static show.exhibition.domain.ExhibitionFixture.개인전시회_생성_디자인_개인전; import static show.exhibition.domain.ExhibitionFixture.개인전시회_생성_사진_개인전; -import static show.exhibition.domain.ExhibitionFixture.개인전시회_생성_사진_개인전_작성자아이디; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; @DisplayNameGeneration(ReplaceUnderscores.class) @SuppressWarnings("NonAsciiCharacters") @@ -33,18 +29,5 @@ class 개인전시회_업데이트 { assertThat(exhibition).usingRecursiveComparison() .isEqualTo(newExhibition); } - - @Test - void 개인전시회_작성자와_업데이트_요청을_보낸_요청자의_정보가_다르면_예외가_발생한다() { - // given - Exhibition exhibition = 개인전시회_생성_사진_개인전(); - Long invalidOwnerId = -1L; - Exhibition newExhibition = 개인전시회_생성_사진_개인전_작성자아이디(invalidOwnerId); - - // when & then - assertThatThrownBy(() -> exhibition.update(newExhibition)) - .isInstanceOf(AuthException.class) - .hasMessageContaining(AuthExceptionType.AUTH_NOT_EQUALS_EXCEPTION.message()); - } } } diff --git a/backend/pcloud-domain/src/test/java/com/domain/show/exhibition/infrastructure/ExhibitionQueryRepositoryTest.java b/backend/pcloud-domain/src/test/java/com/domain/show/exhibition/infrastructure/ExhibitionQueryRepositoryTest.java index 7e960bee..3f7b6154 100644 --- a/backend/pcloud-domain/src/test/java/com/domain/show/exhibition/infrastructure/ExhibitionQueryRepositoryTest.java +++ b/backend/pcloud-domain/src/test/java/com/domain/show/exhibition/infrastructure/ExhibitionQueryRepositoryTest.java @@ -40,6 +40,7 @@ class ExhibitionQueryRepositoryTest extends IntegrationHelper { softly.assertThat(response.get()) .usingRecursiveComparison() .ignoringFields("tags") + .ignoringFields("imageNames") .withComparatorForType(BigDecimal::compareTo, BigDecimal.class) .isEqualTo(개인전시회_상세_조회_응답_생성_개인전시회(savedExhibition)); }); diff --git a/backend/pcloud-domain/src/test/java/com/domain/show/popups/domain/PopupsTest.java b/backend/pcloud-domain/src/test/java/com/domain/show/popups/domain/PopupsTest.java index 6d80e0ce..b1ff4a34 100644 --- a/backend/pcloud-domain/src/test/java/com/domain/show/popups/domain/PopupsTest.java +++ b/backend/pcloud-domain/src/test/java/com/domain/show/popups/domain/PopupsTest.java @@ -1,16 +1,12 @@ package com.domain.show.popups.domain; -import com.common.exception.AuthException; -import com.common.exception.AuthExceptionType; import org.junit.jupiter.api.DisplayNameGeneration; import org.junit.jupiter.api.DisplayNameGenerator; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static show.popups.domain.PopupsFixture.일반_팝업_스토어_생성_뷰티; -import static show.popups.domain.PopupsFixture.일반_팝업_스토어_생성_뷰티_유효하지_않은_주인; import static show.popups.domain.PopupsFixture.일반_팝업_스토어_생성_펫샵; @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class) @@ -34,17 +30,5 @@ class 팝업스토어_업데이트 { .usingRecursiveComparison() .isEqualTo(updatePopups); } - - @Test - void 자신의_계정이_아니면_업데이트_못한다() { - // given - Popups popups = 일반_팝업_스토어_생성_뷰티(); - Popups updatedPopups = 일반_팝업_스토어_생성_뷰티_유효하지_않은_주인(); - - // when & then - assertThatThrownBy(() -> popups.update(updatedPopups)) - .isInstanceOf(AuthException.class) - .hasMessageContaining(AuthExceptionType.AUTH_NOT_EQUALS_EXCEPTION.message()); - } } } diff --git a/backend/pcloud-domain/src/test/resources/application.yml b/backend/pcloud-domain/src/test/resources/application.yml index 51ee3e1f..ac42fa67 100644 --- a/backend/pcloud-domain/src/test/resources/application.yml +++ b/backend/pcloud-domain/src/test/resources/application.yml @@ -16,3 +16,8 @@ spring: hibernate: format_sql: true show_sql: true + +file: + show: + upload: + location: /test diff --git a/backend/pcloud-domain/src/testFixtures/java/show/exhibition/domain/ExhibitionSpecificResponseFixture.java b/backend/pcloud-domain/src/testFixtures/java/show/exhibition/domain/ExhibitionSpecificResponseFixture.java index 3af63a7f..10da0766 100644 --- a/backend/pcloud-domain/src/testFixtures/java/show/exhibition/domain/ExhibitionSpecificResponseFixture.java +++ b/backend/pcloud-domain/src/testFixtures/java/show/exhibition/domain/ExhibitionSpecificResponseFixture.java @@ -5,10 +5,9 @@ import com.domain.show.common.PublicTag; import com.domain.show.exhibition.domain.Exhibition; import com.domain.show.exhibition.domain.dto.ExhibitionSpecificResponse; - import java.math.BigDecimal; import java.time.LocalDateTime; -import java.util.List; +import java.util.Set; @SuppressWarnings("NonAsciiCharacters") public class ExhibitionSpecificResponseFixture { @@ -20,7 +19,7 @@ public class ExhibitionSpecificResponseFixture { "빵빵이 전시회", "빵빵이와 함께하는 전시회", LocalDateTime.now().minusMinutes(10), - LocalDateTime.now(), + LocalDateTime.now().plusMinutes(10), """ 평일 09:00 ~ 18:00, 주말 12:00 ~ 21:00 @@ -37,7 +36,8 @@ public class ExhibitionSpecificResponseFixture { PublicTag.EXHIBITION, 0, 0, - List.of("가족", "데이트") + Set.of("가족", "데이트"), + Set.of("image1.png", "image2.png", "image3.png") ); } @@ -62,7 +62,8 @@ public class ExhibitionSpecificResponseFixture { exhibition.getPublicTag(), exhibition.getStatistic().getVisitedCount(), exhibition.getStatistic().getLikedCount(), - List.of("가족", "데이트") + Set.of("가족", "데이트"), + Set.of("image1.png", "image2.png", "image3.png") ); } } diff --git a/backend/pcloud-domain/src/testFixtures/java/show/popups/domain/PopupsSpecificResponseFixture.java b/backend/pcloud-domain/src/testFixtures/java/show/popups/domain/PopupsSpecificResponseFixture.java index 62569efd..220b0924 100644 --- a/backend/pcloud-domain/src/testFixtures/java/show/popups/domain/PopupsSpecificResponseFixture.java +++ b/backend/pcloud-domain/src/testFixtures/java/show/popups/domain/PopupsSpecificResponseFixture.java @@ -5,10 +5,9 @@ import com.domain.show.common.PublicTag; import com.domain.show.popups.domain.Popups; import com.domain.show.popups.domain.response.PopupsSpecificResponse; - import java.math.BigDecimal; import java.time.LocalDateTime; -import java.util.List; +import java.util.Set; public class PopupsSpecificResponseFixture { @@ -19,7 +18,7 @@ public class PopupsSpecificResponseFixture { "빵빵이 팝업스토어", "빵빵이와 함께하는 팝업스토어", LocalDateTime.now().minusMinutes(10), - LocalDateTime.now(), + LocalDateTime.now().plusMinutes(10), """ 평일 09:00 ~ 18:00, 주말 12:00 ~ 21:00 @@ -36,7 +35,8 @@ public class PopupsSpecificResponseFixture { PublicTag.EXHIBITION, 0, 0, - List.of("가족", "데이트") + Set.of("가족", "데이트"), + Set.of("image1.png", "image2.png", "image3.png") ); } @@ -61,7 +61,8 @@ public class PopupsSpecificResponseFixture { popups.getPublicTag(), popups.getStatistic().getVisitedCount(), popups.getStatistic().getLikedCount(), - List.of("가족", "데이트") + Set.of("가족", "데이트"), + Set.of("image1.png", "image2.png", "image3.png") ); } } diff --git a/backend/pcloud-domain/src/testFixtures/java/show/popups/infrastructure/FakePopupsRepository.java b/backend/pcloud-domain/src/testFixtures/java/show/popups/infrastructure/FakePopupsRepository.java index d9aea28b..fe2c85b2 100644 --- a/backend/pcloud-domain/src/testFixtures/java/show/popups/infrastructure/FakePopupsRepository.java +++ b/backend/pcloud-domain/src/testFixtures/java/show/popups/infrastructure/FakePopupsRepository.java @@ -87,4 +87,9 @@ public LikedPopups saveLikedPopups(final LikedPopups likedPopups) { return savedPopups; } + + @Override + public void deleteById(Long popupsId) { + + } }