diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index c4dddfa0..158c6808 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -3,21 +3,22 @@ name: CI/CD Github Action on: push: branches: [ "Master", "Weekly/*" ] - pull_request_target: - branches: [ "Master", "Weekly/*" ] permissions: contents: read env: + DATASOURCE_URL : ${{ secrets.ENV_DATASOURCE_URL }} + DATASOURCE_USERNAME : ${{ secrets.ENV_DATASOURCE_USERNAME }} + DATASOURCE_PASSWORD : ${{ secrets.ENV_DATASOURCE_PASSWORD }} GOOGLE_CLIENT_ID : ${{ secrets.ENV_GOOGLE_CLIENT_ID }} GOOGLE_CLIENT_SECRET: ${{ secrets.ENV_GOOGLE_CLIENT_SECRET }} - GOOGLE_REDIRECT_URI : ${{ secrets.ENV_GOOGLE_REDIRECT_URI }} + IMAGE_UPLOAD_PATH : ${{ secrets.ENV_IMAGE_UPLOAD_PATH }} JWT_SECRET : ${{ secrets.ENV_JWT_SECRET }} KAKAO_CLIENT_ID : ${{ secrets.ENV_KAKAO_CLIENT_ID }} KAKAO_CLIENT_SECRET : ${{ secrets.ENV_KAKAO_CLIENT_SECRET }} - KAKAO_REDIRECT_URI : ${{ secrets.ENV_KAKAO_REDIRECT_URI }} KAKAOPAY_SECRET_KEY : ${{ secrets.ENV_KAKAOPAY_SECRET_KEY }} + SERVER_DOMAIN_NAME : ${{ secrets.ENV_SERVER_DOMAIN_NAME }} jobs: ## 1단계: 프로젝트 빌드 @@ -137,14 +138,17 @@ jobs: key: ${{ secrets.SSH_PRIVATE_KEY }} port: ${{ secrets.SSH_PORT }} script: | - echo "GOOGLE_CLIENT_ID=${{ secrets.ENV_GOOGLE_CLIENT_ID }}" > ~/.env + echo "DATASOURCE_URL=${{ secrets.ENV_DATASOURCE_URL }}" > ~/.env + echo "DATASOURCE_USERNAME=${{ secrets.ENV_DATASOURCE_USERNAME }}" >> ~/.env + echo "DATASOURCE_PASSWORD=${{ secrets.ENV_DATASOURCE_PASSWORD }}" >> ~/.env + echo "GOOGLE_CLIENT_ID=${{ secrets.ENV_GOOGLE_CLIENT_ID }}" >> ~/.env echo "GOOGLE_CLIENT_SECRET=${{ secrets.ENV_GOOGLE_CLIENT_SECRET }}" >> ~/.env - echo "GOOGLE_REDIRECT_URI=${{ secrets.ENV_GOOGLE_REDIRECT_URI }}" >> ~/.env echo "JWT_SECRET=${{ secrets.ENV_JWT_SECRET }}" >> ~/.env + echo "IMAGE_UPLOAD_PATH=${{ secrets.ENV_IMAGE_UPLOAD_PATH }}" >> ~/.env echo "KAKAO_CLIENT_ID=${{ secrets.ENV_KAKAO_CLIENT_ID }}" >> ~/.env echo "KAKAO_CLIENT_SECRET=${{ secrets.ENV_KAKAO_CLIENT_SECRET }}" >> ~/.env - echo "KAKAO_REDIRECT_URI=${{ secrets.ENV_KAKAO_REDIRECT_URI }}" >> ~/.env echo "KAKAOPAY_SECRET_KEY=${{ secrets.ENV_KAKAOPAY_SECRET_KEY }}" >> ~/.env + echo "SERVER_DOMAIN_NAME=${{ secrets.ENV_SERVER_DOMAIN_NAME }}" >> ~/.env echo "Environment setup has been completed." - name: Pull New Docker Image @@ -175,7 +179,7 @@ jobs: key: ${{ secrets.SSH_PRIVATE_KEY }} port: ${{ secrets.SSH_PORT }} envs: GITHUB_SHA - script: sudo docker run --rm -d -p 80:8080 --env-file ~/.env --name would-you-in ${{ secrets.DOCKER_REPO_FULLNAME }} + script: sudo docker run --rm -d -p 80:8080 --env-file ~/.env --name would-you-in -v would-you-in-image-volume:${{ secrets.ENV_IMAGE_UPLOAD_PATH }} ${{ secrets.DOCKER_REPO_FULLNAME }} - name: Clean-Up Docker Image uses: appleboy/ssh-action@master diff --git a/.gitignore b/.gitignore index f4f1f4ac..c7e998cd 100644 --- a/.gitignore +++ b/.gitignore @@ -37,7 +37,7 @@ out/ .vscode/ ### static images -/src/main/resources/staticimages/ +/src/main/resources/static/images/ ### env file .env \ No newline at end of file diff --git a/build.gradle b/build.gradle index 000c37a2..3a5e9b40 100644 --- a/build.gradle +++ b/build.gradle @@ -29,13 +29,12 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - implementation 'com.h2database:h2' implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310' implementation 'com.fasterxml.jackson.core:jackson-databind' + implementation group: 'net.coobird', name: 'thumbnailator', version: '0.4.14' compileOnly 'org.projectlombok:lombok' developmentOnly 'org.springframework.boot:spring-boot-devtools' runtimeOnly 'com.mysql:mysql-connector-j' - runtimeOnly 'com.h2database:h2' annotationProcessor 'org.projectlombok:lombok' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' @@ -50,6 +49,8 @@ dependencies { //for test implementation 'org.springframework.boot:spring-boot-starter-thymeleaf' + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' //for Swagger implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0' diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/AdvertisementImageService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/AdvertisementImageService.java deleted file mode 100644 index 967b22a1..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/AdvertisementImageService.java +++ /dev/null @@ -1,61 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.Image.application; - -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin.Image.api.ImageDomain; -import org.ktc2.cokaen.wouldyouin.Image.api.dto.ImageRequest; -import org.ktc2.cokaen.wouldyouin.Image.persist.AdvertisementImage; -import org.ktc2.cokaen.wouldyouin.Image.persist.AdvertisementImageRepository; -import org.ktc2.cokaen.wouldyouin.Image.persist.ImageRepository; -import org.ktc2.cokaen.wouldyouin._common.exception.EntityParamIsNullException; -import org.ktc2.cokaen.wouldyouin.advertisement.persist.Advertisement; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -@Service -@RequiredArgsConstructor -public class AdvertisementImageService extends ImageService { - - private final ImageStorage imageStorage; - private final AdvertisementImageRepository adImageRepository; - @Value("${image.upload.ad.sub-path}") - private String subPath; - - @Override - protected ImageRepository getImageRepository() { - return adImageRepository; - } - - @Override - protected ImageDomain getImageDomain() { - return ImageDomain.ADVERTISEMENT; - } - - @Override - protected String getSubPath() { - return subPath; - } - - @Override - protected AdvertisementImage toEntity(ImageRequest imageRequest) { - return AdvertisementImage.builder() - .name(imageRequest.getUrl()) - .size(imageRequest.getSize()) - .build(); - } - - @Transactional - public AdvertisementImage saveAndCreateImage(MultipartFile image) { - String path = imageStorage.save(image, getSubPath()); - return adImageRepository.save(toEntity(ImageRequest.of(path, image.getSize(), ImageStorage.getExtension(image)))); - } - - @Transactional - public void setAd(AdvertisementImage image, Advertisement ad) { - Optional.ofNullable(image).orElseThrow(() -> new EntityParamIsNullException(getImageDomain().name() + " image")); - Optional.ofNullable(ad).orElseThrow(() -> new EntityParamIsNullException("advertisement")); - image.setAdvertisement(ad); - } -} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/CurationImageService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/CurationImageService.java deleted file mode 100644 index 518c2a58..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/CurationImageService.java +++ /dev/null @@ -1,54 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.Image.application; - -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin.Image.api.ImageDomain; -import org.ktc2.cokaen.wouldyouin.Image.api.dto.ImageRequest; -import org.ktc2.cokaen.wouldyouin.Image.persist.CurationImage; -import org.ktc2.cokaen.wouldyouin.Image.persist.CurationImageRepository; -import org.ktc2.cokaen.wouldyouin.Image.persist.ImageRepository; -import org.ktc2.cokaen.wouldyouin._common.exception.EntityParamIsNullException; -import org.ktc2.cokaen.wouldyouin.curation.persist.CurationCard; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -public class CurationImageService extends ImageService { - - private final CurationImageRepository curationImageRepository; - - @Value("${image.upload.curation.sub-path}") - private String subPath; - - @Override - public ImageRepository getImageRepository() { - return curationImageRepository; - } - - @Override - protected ImageDomain getImageDomain() { - return ImageDomain.CURATION; - } - - @Override - protected String getSubPath() { - return subPath; - } - - @Override - protected CurationImage toEntity(ImageRequest imageRequest) { - return CurationImage.builder() - .url(imageRequest.getUrl()) - .size(imageRequest.getSize()) - .build(); - } - - @Transactional - public void setCuration(CurationImage image, CurationCard curationCard) { - Optional.ofNullable(image).orElseThrow(() -> new EntityParamIsNullException(getImageDomain().name() + " image")); - Optional.ofNullable(curationCard).orElseThrow(() -> new EntityParamIsNullException("curationCard")); - image.setCurationCard(curationCard); - } -} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/EventImageService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/EventImageService.java deleted file mode 100644 index ecfc2a77..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/EventImageService.java +++ /dev/null @@ -1,51 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.Image.application; - -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin.Image.api.ImageDomain; -import org.ktc2.cokaen.wouldyouin.Image.api.dto.ImageRequest; -import org.ktc2.cokaen.wouldyouin.Image.persist.EventImage; -import org.ktc2.cokaen.wouldyouin.Image.persist.EventImageRepository; -import org.ktc2.cokaen.wouldyouin.Image.persist.ImageRepository; -import org.ktc2.cokaen.wouldyouin._common.exception.EntityParamIsNullException; -import org.ktc2.cokaen.wouldyouin.event.persist.Event; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@RequiredArgsConstructor -@Service -public class EventImageService extends ImageService { - - private final EventImageRepository eventImageRepository; - - @Value("${image.upload.event.sub-path}") - private String subPath; - - @Override - public ImageRepository getImageRepository() { - return eventImageRepository; - } - - @Override - protected ImageDomain getImageDomain() { - return ImageDomain.EVENT; - } - - @Override - protected String getSubPath() { - return subPath; - } - - @Override - protected EventImage toEntity(ImageRequest imageRequest) { - return EventImage.builder().url(imageRequest.getUrl()).size(imageRequest.getSize()).build(); - } - - @Transactional - public void setEvent(EventImage image, Event event) { - Optional.ofNullable(image).orElseThrow(() -> new EntityParamIsNullException(getImageDomain().name() + " image")); - Optional.ofNullable(event).orElseThrow(() -> new EntityParamIsNullException("event")); - image.setEvent(event); - } -} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/ImageService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/ImageService.java deleted file mode 100644 index aaac70b4..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/ImageService.java +++ /dev/null @@ -1,67 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.Image.application; - -import java.util.List; -import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin.Image.api.ImageDomain; -import org.ktc2.cokaen.wouldyouin.Image.api.dto.ImageRequest; -import org.ktc2.cokaen.wouldyouin.Image.api.dto.ImageResponse; -import org.ktc2.cokaen.wouldyouin.Image.persist.Image; -import org.ktc2.cokaen.wouldyouin.Image.persist.ImageRepository; -import org.ktc2.cokaen.wouldyouin._common.exception.EntityNotFoundException; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.multipart.MultipartFile; - -@Service -@RequiredArgsConstructor -public abstract class ImageService { - - @Autowired - protected ImageStorage imageStorage; - - @Value("${spring.wouldyouin-domain-name}") - private String domainName; - - protected abstract ImageRepository getImageRepository(); - - protected abstract ImageDomain getImageDomain(); - - protected abstract String getSubPath(); - - protected abstract T toEntity(ImageRequest imageRequest); - - public T getById(Long id) { - return getImageRepository().findById(id) - .orElseThrow(() -> new EntityNotFoundException(getImageDomain().name() + " Image")); - } - - protected ImageResponse create(ImageRequest imageRequest) { - return ImageResponse.from(getImageRepository().save(toEntity(imageRequest)), domainName); - } - - protected void delete(Long id) { - getImageRepository().findById(id) - .orElseThrow(() -> new EntityNotFoundException(getImageDomain().name() + " Image")); - getImageRepository().deleteById(id); - } - - @Transactional - public List saveAndCreateImages(List images) { - return images.stream() - .map(image -> { - String path = imageStorage.save(image, getSubPath()); - return create(ImageRequest.of(path, image.getSize(), ImageStorage.getExtension(image))); - }) - .toList(); - } - - @Transactional - public void deleteAndDelete(Long id) { - String url = getImageRepository().findById(id) - .orElseThrow(() -> new EntityNotFoundException(getImageDomain().name() + " Image")).getUrl(); - delete(id); - imageStorage.delete(url); - } -} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/ImageServiceFactory.java b/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/ImageServiceFactory.java deleted file mode 100644 index 329a411b..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/ImageServiceFactory.java +++ /dev/null @@ -1,23 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.Image.application; - -import java.util.List; -import java.util.concurrent.ConcurrentHashMap; -import java.util.stream.Collectors; -import org.ktc2.cokaen.wouldyouin.Image.api.ImageDomain; -import org.ktc2.cokaen.wouldyouin.Image.persist.Image; -import org.springframework.stereotype.Component; - -@Component -public class ImageServiceFactory { - - private final ConcurrentHashMap> imageTypeToImageServiceMap; - - public ImageServiceFactory(List> imageServices) { - imageTypeToImageServiceMap = new ConcurrentHashMap<>(imageServices.stream() - .collect(Collectors.toConcurrentMap(ImageService::getImageDomain, imageService -> imageService))); - } - - public ImageService getImageServiceByImageType(ImageDomain imageDomain) { - return imageTypeToImageServiceMap.get(imageDomain); - } -} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/ImageStorage.java b/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/ImageStorage.java deleted file mode 100644 index 046aea29..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/ImageStorage.java +++ /dev/null @@ -1,82 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.Image.application; - -import java.io.IOException; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.List; -import java.util.Objects; -import java.util.Optional; -import java.util.UUID; -import org.ktc2.cokaen.wouldyouin._common.exception.FailedToDeleteImageException; -import org.ktc2.cokaen.wouldyouin._common.exception.FailedToUploadImageException; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Component; -import org.springframework.web.client.RestClient; -import org.springframework.web.multipart.MultipartFile; - -@Component -public class ImageStorage { - - @Value("${image.upload.common-path}") - private String commonPath; - - public String save(MultipartFile image, String subPath) { - String fileName = ""; - try { - fileName = generateUuidName() + "." + getExtension(image); - Path path = Paths.get(commonPath, subPath, fileName); - Files.createDirectories(path.getParent()); - Files.write(path, image.getBytes()); - } catch (IOException ex) { - throw new FailedToUploadImageException(); - } - return subPath + "/" + fileName; - } - - // Todo: payment랑 이부분 restclient 유틸로 빼기 - public String save(String imageUrl, String subPath) { - RestClient client = RestClient.builder().build(); - String fileName = ""; - try { - ResponseEntity response = client.get() - .uri(imageUrl) - .retrieve() - .toEntity(byte[].class); - - if (Optional.ofNullable(response).isPresent() && response.getStatusCode().is2xxSuccessful()) { - byte[] imageBytes = response.getBody(); - - fileName = generateUuidName() + "." + getExtension(imageUrl); - Path path = Paths.get(commonPath, subPath, fileName); - Files.createDirectories(path.getParent()); - Files.write(path, response.getBody()); - } - } catch (IOException ex) { - throw new FailedToUploadImageException(); - } - return subPath + "/" + fileName; - } - - public void delete(String imagePath) { - try { - Files.deleteIfExists(Paths.get(imagePath)); - } catch (IOException ex) { - throw new FailedToDeleteImageException(); - } - } - - protected static String getExtension(MultipartFile image) { - return Objects.requireNonNull(image.getContentType()).split("/")[1]; - } - - protected static String getExtension(String imageUrl) { - String[] splitted = imageUrl.split("\\."); - return splitted[splitted.length - 1]; - } - - private static String generateUuidName() { - return UUID.randomUUID().toString().replace("-", ""); - } -} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/MemberImageService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/MemberImageService.java deleted file mode 100644 index 621679b3..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/application/MemberImageService.java +++ /dev/null @@ -1,69 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.Image.application; - -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.Paths; -import java.util.Optional; -import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin.Image.api.ImageDomain; -import org.ktc2.cokaen.wouldyouin.Image.api.dto.ImageRequest; -import org.ktc2.cokaen.wouldyouin.Image.persist.ImageRepository; -import org.ktc2.cokaen.wouldyouin.Image.persist.MemberImage; -import org.ktc2.cokaen.wouldyouin.Image.persist.MemberImageRepository; -import org.ktc2.cokaen.wouldyouin._common.exception.EntityParamIsNullException; -import org.ktc2.cokaen.wouldyouin.member.persist.BaseMember; -import org.ktc2.cokaen.wouldyouin.member.persist.BaseMemberRepository; -import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpStatus; -import org.springframework.http.ResponseEntity; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; -import org.springframework.web.client.RestClient; - -@Service -@RequiredArgsConstructor -public class MemberImageService extends ImageService { - - private final MemberImageRepository memberImageRepository; - private final BaseMemberRepository baseMemberRepository; - - @Value("${image.upload.member.sub-path}") - private String subPath; - - @Override - public ImageRepository getImageRepository() { - return memberImageRepository; - } - - @Override - protected ImageDomain getImageDomain() { - return ImageDomain.MEMBER; - } - - @Override - protected String getSubPath() { - return subPath; - } - - @Override - protected MemberImage toEntity(ImageRequest imageRequest) { - return MemberImage.builder() - .url(imageRequest.getUrl()) - .size(imageRequest.getSize()) - .build(); - } - - @Transactional - public void setBaseMember(MemberImage image, BaseMember member) { - Optional.ofNullable(image).orElseThrow(() -> new EntityParamIsNullException(getImageDomain().name() + " image")); - Optional.ofNullable(member).orElseThrow(() -> new EntityParamIsNullException("baseMember")); - image.setBaseMember(member); - } - - // TODO: imageUrl을 MemberImage로 변환하는 로직 추가 필요 - // Todo: extension과 size 불러오기 - public MemberImage convert(String imageUrl) { - var request = ImageRequest.of(imageStorage.save(imageUrl, subPath), 123123L, "jpg"); - return memberImageRepository.save(toEntity(request)); - } -} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/CurationImageRepository.java b/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/CurationImageRepository.java deleted file mode 100644 index 55c902fe..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/CurationImageRepository.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.Image.persist; - -import org.springframework.stereotype.Repository; - -@Repository -public interface CurationImageRepository extends ImageRepository { - -} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/aop/Logger.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/aop/Logger.java new file mode 100644 index 00000000..52d9e4e1 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/aop/Logger.java @@ -0,0 +1,59 @@ +package org.ktc2.cokaen.wouldyouin._common.aop; + +import jakarta.servlet.http.HttpServletRequest; +import java.net.URLDecoder; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.Map; +import lombok.extern.slf4j.Slf4j; +import net.minidev.json.JSONObject; +import org.aspectj.lang.ProceedingJoinPoint; +import org.aspectj.lang.annotation.Around; +import org.aspectj.lang.annotation.Aspect; +import org.aspectj.lang.annotation.Pointcut; +import org.springframework.stereotype.Component; +import org.springframework.web.context.request.RequestContextHolder; +import org.springframework.web.context.request.ServletRequestAttributes; + +@Slf4j +@Aspect +@Component +public class Logger { + + @Pointcut("execution(* org.ktc2.cokaen.wouldyouin.auth.application.JwtAuthFilter.*(..))") + public void springFilterExecution() { + } + + @Pointcut("execution(* org.ktc2.cokaen.wouldyouin..*.*(..)) && !springFilterExecution()") + public void all() { + } + + @Around("all()") + public Object logging(ProceedingJoinPoint joinPoint) throws Throwable { + String methodName = joinPoint.getSignature().toShortString(); + String packageName = joinPoint.getSignature().getDeclaringTypeName() + .replace("org.ktc2.cokaen.wouldyouin.", "") + .replaceAll("\\.[^\\.]+$", ""); + Object[] args = joinPoint.getArgs(); + long start = System.currentTimeMillis(); + long timeInMs; + Object result; + log.debug("{} : CALL {}", packageName, methodName); + log.debug("{} : with param = {}", packageName, args); + + try { + result = joinPoint.proceed(); + } catch(Throwable t) { + log.debug("{} : {} throws {}", packageName, methodName, t.getClass().getSimpleName()); + throw t; + } finally { + timeInMs = System.currentTimeMillis() - start; + log.debug("{} : EXIT {}", packageName, methodName); + log.debug("{} : with executeTime = {}ms", packageName, timeInMs); + } + + log.debug("{} : with return = {}", packageName, result); + + return result; + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/api/ApiResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/api/ApiResponse.java index f3872780..adec2578 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/api/ApiResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/api/ApiResponse.java @@ -1,6 +1,6 @@ package org.ktc2.cokaen.wouldyouin._common.api; -import org.ktc2.cokaen.wouldyouin._common.error.ErrorCode; +import org.ktc2.cokaen.wouldyouin._common.exception.ErrorCode; import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; @@ -18,10 +18,6 @@ public static ResponseEntity> noContent() { return ResponseEntity.status(HttpStatus.NO_CONTENT).body(new ApiResponseBody<>(true, null)); } - public static ResponseEntity> error(ErrorCode errorCode) { - return ResponseEntity.status(errorCode.getStatus()).body(new ApiResponseBody<>(false, errorCode.getCode(), errorCode.getMessage())); - } - public static ResponseEntity> error(ErrorCode errorCode, String errorMessage) { return ResponseEntity.status(errorCode.getStatus()).body(new ApiResponseBody<>(false, errorCode.getCode(), errorMessage)); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/api/ApiResponseBody.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/api/ApiResponseBody.java index 02e70fd9..7c4e8966 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/api/ApiResponseBody.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/api/ApiResponseBody.java @@ -1,10 +1,14 @@ package org.ktc2.cokaen.wouldyouin._common.api; import com.fasterxml.jackson.annotation.JsonInclude; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; @Getter @JsonInclude(JsonInclude.Include.NON_NULL) +@EqualsAndHashCode +@ToString public class ApiResponseBody { private final Boolean success; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/config/ParamDefaults.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/api/ParamDefaults.java similarity index 70% rename from src/main/java/org/ktc2/cokaen/wouldyouin/_common/config/ParamDefaults.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/_common/api/ParamDefaults.java index 5e4bfdd3..b1d8866d 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/config/ParamDefaults.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/api/ParamDefaults.java @@ -1,5 +1,10 @@ -package org.ktc2.cokaen.wouldyouin._common.config; +package org.ktc2.cokaen.wouldyouin._common.api; +import lombok.EqualsAndHashCode; +import lombok.ToString; + +@EqualsAndHashCode +@ToString public class ParamDefaults { public static final String PAGE = "0"; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/api/SliceInfo.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/api/SliceInfo.java index 303194f8..9aa58171 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/api/SliceInfo.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/api/SliceInfo.java @@ -1,12 +1,16 @@ package org.ktc2.cokaen.wouldyouin._common.api; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.Setter; +import lombok.ToString; @Setter @Getter @Builder +@EqualsAndHashCode +@ToString public class SliceInfo { private final Integer sliceSize; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/config/ObjectMapperConfig.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/config/ObjectMapperConfig.java new file mode 100644 index 00000000..630ab851 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/config/ObjectMapperConfig.java @@ -0,0 +1,17 @@ +package org.ktc2.cokaen.wouldyouin._common.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class ObjectMapperConfig { + + @Bean + ObjectMapper objectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(new JavaTimeModule()); + return mapper; + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/config/RestClientConfig.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/config/RestClientConfig.java new file mode 100644 index 00000000..a4655caa --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/config/RestClientConfig.java @@ -0,0 +1,14 @@ +package org.ktc2.cokaen.wouldyouin._common.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.client.RestClient; + +@Configuration +public class RestClientConfig { + + @Bean + RestClient restClient() { + return RestClient.builder().build(); + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/config/SecurityConfig.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/config/SecurityConfig.java index 139b214a..f3aff276 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/config/SecurityConfig.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/config/SecurityConfig.java @@ -32,7 +32,7 @@ public class SecurityConfig { private final CustomAuthenticationEntryPoint authenticationEntryPoint; private static final String[] AUTH_WHITELIST = { - "/**", //임시 설정, 나중에 제거필요 + "/**", "/h2-console/**", "/swagger-ui/**", "/swagger-ui/index.html", diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/error/ErrorCode.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/error/ErrorCode.java deleted file mode 100644 index 1e52d1d9..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/error/ErrorCode.java +++ /dev/null @@ -1,40 +0,0 @@ -package org.ktc2.cokaen.wouldyouin._common.error; - -import lombok.Getter; -import org.springframework.http.HttpStatus; - -@Getter -public enum ErrorCode { - - UNEXPECTED(HttpStatus.BAD_REQUEST.value(), "-1", "Unexpected exception occurred"), - - ENTITY_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "-10404", "Can not find a %s Entity"), - - NO_LEFT_SEAT(HttpStatus.BAD_REQUEST.value(), "-10400", "No left seat"), - - INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST.value(), "-20400", "Invalid Input Value"), - - FAIL_TO_READ_IMAGE(HttpStatus.CONFLICT.value(), "-20400", "Fail to read image"), - - FAIL_TO_PAY(HttpStatus.CONFLICT.value(), "-20400", "Fail to %s"), - - UNAUTHORIZED(HttpStatus.UNAUTHORIZED.value(), "-20400", "Unauthorized, %s."), - - ENTITY_PARAM_IS_NULL(HttpStatus.BAD_REQUEST.value(), "-20400", "%s is null"), - - FAIL_TO_UPLOAD_IMAGE(HttpStatus.CONFLICT.value(), "-20400", "Fail to upload image"), - - FAIL_TO_DELETE_IMAGE(HttpStatus.CONFLICT.value(), "-20400", "Fail to delete image"), - - CURRENT_LOCATION_EMPTY(HttpStatus.BAD_REQUEST.value(), "-20400", "현재 위치 정보를 찾을 수 없습니다."); - - private final Integer status; - private final String code; - private final String message; - - ErrorCode(int status, String code, String message) { - this.status = status; - this.code = code; - this.message = message; - } -} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/BusinessException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/BusinessException.java index 00993644..b2776adf 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/BusinessException.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/BusinessException.java @@ -2,7 +2,6 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin._common.error.ErrorCode; @Getter @RequiredArgsConstructor @@ -14,9 +13,4 @@ public BusinessException(String message, ErrorCode errorCode) { super(message); this.errorCode = errorCode; } - - public BusinessException(ErrorCode errorCode, Object... args) { - super(String.format(errorCode.getMessage(), args)); - this.errorCode = errorCode; - } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/CurrentLocationEmptyException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/CurrentLocationEmptyException.java deleted file mode 100644 index 28e1effd..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/CurrentLocationEmptyException.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.ktc2.cokaen.wouldyouin._common.exception; - -import org.ktc2.cokaen.wouldyouin._common.error.ErrorCode; - -public class CurrentLocationEmptyException extends BusinessException { - - public CurrentLocationEmptyException() { - super(ErrorCode.CURRENT_LOCATION_EMPTY, "현재 위치 정보를 찾을 수 없습니다."); - } -} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/EntityNotFoundException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/EntityNotFoundException.java index e242366d..1368a736 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/EntityNotFoundException.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/EntityNotFoundException.java @@ -1,10 +1,8 @@ package org.ktc2.cokaen.wouldyouin._common.exception; -import org.ktc2.cokaen.wouldyouin._common.error.ErrorCode; - public class EntityNotFoundException extends BusinessException { - public EntityNotFoundException(String notFoundedEntityName) { - super(ErrorCode.ENTITY_NOT_FOUND, notFoundedEntityName); + public EntityNotFoundException(String message) { + super(message, ErrorCode.ENTITY_NOT_FOUND); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/EntityParamIsNullException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/EntityParamIsNullException.java deleted file mode 100644 index 4df395ed..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/EntityParamIsNullException.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.ktc2.cokaen.wouldyouin._common.exception; - -import org.ktc2.cokaen.wouldyouin._common.error.ErrorCode; - -public class EntityParamIsNullException extends BusinessException{ - - public EntityParamIsNullException(String entityName) { - super(ErrorCode.ENTITY_PARAM_IS_NULL, entityName); - } -} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/ErrorCode.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/ErrorCode.java new file mode 100644 index 00000000..80eaa3d8 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/ErrorCode.java @@ -0,0 +1,45 @@ +package org.ktc2.cokaen.wouldyouin._common.exception; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public enum ErrorCode { + + EMAIL_ALREADY_EXISTS(HttpStatus.CONFLICT.value(), "-40904"), + FAIL_TO_DELETE_IMAGE(HttpStatus.CONFLICT.value(), "-40903"), + FAIL_TO_UPLOAD_IMAGE(HttpStatus.CONFLICT.value(), "-40902"), + FAIL_TO_PAY(HttpStatus.CONFLICT.value(), "-40901"), + FAIL_TO_READ_IMAGE(HttpStatus.CONFLICT.value(), "-40900"), + + RESERVATION_NOT_FOUND_FOR_REVIEW(HttpStatus.NOT_FOUND.value(), "40401"), + ENTITY_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "-40400"), + + SECURITY_FILTER_AUTHORIZATION_REQUIRED(HttpStatus.FORBIDDEN.value(), "-40300"), + + FAIL_SOCIAL_DATA_GET(HttpStatus.UNAUTHORIZED.value(), "-40106"), + FAIL_ACCESS_TOKEN_GET(HttpStatus.UNAUTHORIZED.value(), "-40105"), + AUTHENTICATION_EMPTY(HttpStatus.UNAUTHORIZED.value(), "-40104"), + JWT_TOKEN_EXCEPTION(HttpStatus.UNAUTHORIZED.value(), "-40103"), + ADDITIONAL_INFO_ILLEGAL_ACCESS(HttpStatus.UNAUTHORIZED.value(), "-40102"), + LOGIN_FAILED(HttpStatus.UNAUTHORIZED.value(), "-40101"), + UNAUTHORIZED(HttpStatus.UNAUTHORIZED.value(), "-40100"), + + INVALID_AUTHORIZATION_HEADER(HttpStatus.BAD_REQUEST.value(), "-40008"), + SECURITY_FILTER_FORBIDDEN_ACCESS(HttpStatus.BAD_REQUEST.value(), "-40007"), + NOT_LIKEABLE_MEMBER_TYPE(HttpStatus.BAD_REQUEST.value(), "-40006"), + CURRENT_LOCATION_EMPTY(HttpStatus.BAD_REQUEST.value(), "-40005"), + INVALID_IMAGE_DOMAIN(HttpStatus.BAD_REQUEST.value(), "-40004"), + NO_LEFT_SEAT(HttpStatus.BAD_REQUEST.value(), "-40003"), + URL_PARSING_FAILED(HttpStatus.BAD_REQUEST.value(), "-40002"), + INVALID_INPUT_VALUE(HttpStatus.BAD_REQUEST.value(), "-40001"), + UNEXPECTED(HttpStatus.BAD_REQUEST.value(), "-40000"); + + private final Integer status; + private final String code; + + ErrorCode(int status, String code) { + this.status = status; + this.code = code; + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/ExtensionParsingException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/ExtensionParsingException.java new file mode 100644 index 00000000..e6ad2b85 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/ExtensionParsingException.java @@ -0,0 +1,8 @@ +package org.ktc2.cokaen.wouldyouin._common.exception; + +public class ExtensionParsingException extends BusinessException { + + public ExtensionParsingException(String message) { + super(message, ErrorCode.URL_PARSING_FAILED); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/FailToReadImageException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/FailToReadImageException.java index 99c73fbd..71f4414b 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/FailToReadImageException.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/FailToReadImageException.java @@ -1,10 +1,8 @@ package org.ktc2.cokaen.wouldyouin._common.exception; -import org.ktc2.cokaen.wouldyouin._common.error.ErrorCode; - public class FailToReadImageException extends BusinessException { - public FailToReadImageException() { - super(ErrorCode.ENTITY_NOT_FOUND); + public FailToReadImageException(String message) { + super(message, ErrorCode.FAIL_TO_READ_IMAGE); } -} +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/FailedToDeleteImageException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/FailedToDeleteImageException.java deleted file mode 100644 index e470f6d1..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/FailedToDeleteImageException.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.ktc2.cokaen.wouldyouin._common.exception; - -import org.ktc2.cokaen.wouldyouin._common.error.ErrorCode; - -public class FailedToDeleteImageException extends BusinessException { - - public FailedToDeleteImageException() { - super(ErrorCode.FAIL_TO_DELETE_IMAGE, ErrorCode.FAIL_TO_DELETE_IMAGE.getMessage()); - } -} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/FailedToPayException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/FailedToPayException.java deleted file mode 100644 index 76aa3a3d..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/FailedToPayException.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.ktc2.cokaen.wouldyouin._common.exception; - -import org.ktc2.cokaen.wouldyouin._common.error.ErrorCode; - -public class FailedToPayException extends BusinessException { - - public FailedToPayException(String message) { - super(ErrorCode.FAIL_TO_PAY, message); - } -} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/FailedToUploadImageException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/FailedToUploadImageException.java index 813b211a..8bb6f4dd 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/FailedToUploadImageException.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/FailedToUploadImageException.java @@ -1,10 +1,8 @@ package org.ktc2.cokaen.wouldyouin._common.exception; -import org.ktc2.cokaen.wouldyouin._common.error.ErrorCode; - public class FailedToUploadImageException extends BusinessException { - public FailedToUploadImageException() { - super(ErrorCode.FAIL_TO_UPLOAD_IMAGE, ErrorCode.FAIL_TO_UPLOAD_IMAGE.getMessage()); + public FailedToUploadImageException(String message) { + super(message, ErrorCode.FAIL_TO_UPLOAD_IMAGE); } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/error/GlobalExceptionHandler.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/GlobalExceptionHandler.java similarity index 55% rename from src/main/java/org/ktc2/cokaen/wouldyouin/_common/error/GlobalExceptionHandler.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/GlobalExceptionHandler.java index 14949f8e..a08a2b98 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/error/GlobalExceptionHandler.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/GlobalExceptionHandler.java @@ -1,28 +1,38 @@ -package org.ktc2.cokaen.wouldyouin._common.error; +package org.ktc2.cokaen.wouldyouin._common.exception; import java.util.stream.Collectors; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponse; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponseBody; -import org.ktc2.cokaen.wouldyouin._common.exception.BusinessException; +import org.ktc2.cokaen.wouldyouin.image.api.ImageDomain; import org.springframework.context.support.DefaultMessageSourceResolvable; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.MethodArgumentNotValidException; import org.springframework.web.bind.annotation.ControllerAdvice; import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.method.annotation.MethodArgumentTypeMismatchException; @ControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(BusinessException.class) public ResponseEntity> handleBusinessException(BusinessException e) { - return ApiResponse.error(e.getErrorCode()); + return ApiResponse.error(e.getErrorCode(), e.getMessage()); + } + + @ExceptionHandler(MethodArgumentTypeMismatchException.class) + public ResponseEntity> handleMethodArgumentTypeMismatchException(MethodArgumentTypeMismatchException ex) { + if (ex.getRequiredType() == ImageDomain.class) { + return ApiResponse.error(ErrorCode.INVALID_IMAGE_DOMAIN, "해당 이미지 도메인에 대한 서비스가 존재하지 않습니다."); + } + return ApiResponse.error(ErrorCode.INVALID_INPUT_VALUE, "잘못된 요청 값입니다."); } @ExceptionHandler(MethodArgumentNotValidException.class) public ResponseEntity> handleMethodArgumentNotValidException(MethodArgumentNotValidException e) { return ApiResponse.error(ErrorCode.INVALID_INPUT_VALUE, e.getBindingResult().getFieldErrors().stream() .map(DefaultMessageSourceResolvable::getDefaultMessage) +// .map((reslover) -> "필드명: " + reslover.getField() + reslover.getDefaultMessage()) .collect(Collectors.joining(", "))); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/NoLeftSeatException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/NoLeftSeatException.java deleted file mode 100644 index e23d0ae5..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/NoLeftSeatException.java +++ /dev/null @@ -1,10 +0,0 @@ -package org.ktc2.cokaen.wouldyouin._common.exception; - -import org.ktc2.cokaen.wouldyouin._common.error.ErrorCode; - -public class NoLeftSeatException extends BusinessException { - - public NoLeftSeatException() { - super(ErrorCode.NO_LEFT_SEAT); - } -} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/UnauthorizedException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/UnauthorizedException.java index b7abfe7f..e03ba9fd 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/UnauthorizedException.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/exception/UnauthorizedException.java @@ -1,10 +1,8 @@ package org.ktc2.cokaen.wouldyouin._common.exception; -import org.ktc2.cokaen.wouldyouin._common.error.ErrorCode; - public class UnauthorizedException extends BusinessException { public UnauthorizedException(String message) { super(message, ErrorCode.UNAUTHORIZED); } -} +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/persist/Area.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/persist/Area.java deleted file mode 100644 index 7c9dcf98..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/persist/Area.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.ktc2.cokaen.wouldyouin._common.persist; - -public enum Area { - 전체, - 서울, - 인천, - 경기도, - 강원도, - 충청남도, - 충청북도, - 경상북도, - 대전, - 대구, - 울산, - 전라북도, - 경상남도, - 부산, - 광주, - 전라남도, - 제주도 -} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/persist/Category.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/persist/Category.java deleted file mode 100644 index 5d30389b..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/persist/Category.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.ktc2.cokaen.wouldyouin._common.persist; - -public enum Category { - 전체, - 밴드, - 연극, - 뮤지컬, - 원데이클래스, - 전시회, - 공예, - 축제 -} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/persist/Location.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/persist/Location.java deleted file mode 100644 index ab3cfe59..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/persist/Location.java +++ /dev/null @@ -1,17 +0,0 @@ -package org.ktc2.cokaen.wouldyouin._common.persist; - -import jakarta.persistence.Embeddable; -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -@Embeddable -public class Location { - Double longitude; - Double latitude; -} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/util/FileUtil.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/util/FileUtil.java new file mode 100644 index 00000000..c5987467 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/util/FileUtil.java @@ -0,0 +1,75 @@ +package org.ktc2.cokaen.wouldyouin._common.util; + +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.UUID; +import net.coobird.thumbnailator.Thumbnails; +import org.ktc2.cokaen.wouldyouin._common.exception.ExtensionParsingException; +import org.ktc2.cokaen.wouldyouin._common.exception.FailToReadImageException; +import org.ktc2.cokaen.wouldyouin._common.exception.FailedToUploadImageException; +import org.springframework.web.multipart.MultipartFile; + +public class FileUtil { + + public static byte[] readFile(Path path) { + try { + return Files.readAllBytes(path); + } catch (IOException e) { + throw new FailToReadImageException("파일을 읽어오는데 실패했습니다."); + } + } + + public static void saveFile(MultipartFile file, Path path) { + try { + Files.createDirectories(path.getParent()); + Files.write(path, file.getBytes()); + } catch (IOException e) { + throw new FailedToUploadImageException("디렉토리에 파일을 저장하는데 실패했습니다."); + } + } + + public static void saveFile(byte[] file, Path path) { + try { + Files.createDirectories(path.getParent()); + Files.write(path, file); + } catch (IOException e) { + throw new FailedToUploadImageException("디렉토리에 파일을 저장하는데 실패했습니다."); + } + } + + public static void createThumbnail(String originalPath, String originFileName, String thumbnailPath, int width, int height) { + try { + Files.createDirectories(Paths.get(thumbnailPath)); + Thumbnails.of(new File(originalPath)) + .size(height, width) + .toFile(new File(thumbnailPath, originFileName)); + } catch (IOException e) { + throw new FailedToUploadImageException("썸네일을 생성하는데 실패했습니다."); + } + } + + public static void deleteFile(Path path) { + try { + Files.deleteIfExists(path); + } catch (IOException e) { + throw new FailedToUploadImageException("해당 경로의 파일을 삭제하는데 실패했습니다."); + } + } + + public static String createRandomFileName(String extension) { + return UUID.randomUUID().toString().replace("-", "") + "." + extension; + } + + public static String getExtension(String contentType) { + try { + return contentType.split("/")[1]; + } catch (NullPointerException e) { + throw new ExtensionParsingException("파일이 존재하지 않거나 콘텐츠 타입이 없습니다."); + } catch (ArrayIndexOutOfBoundsException e) { + throw new ExtensionParsingException("파일의 확장자를 찾을 수 없습니다."); + } + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/util/KakaoPayUtil.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/util/KakaoPayUtil.java new file mode 100644 index 00000000..5d160970 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/util/KakaoPayUtil.java @@ -0,0 +1,33 @@ +package org.ktc2.cokaen.wouldyouin._common.util; + +import java.util.HashMap; +import java.util.Map; +import org.ktc2.cokaen.wouldyouin.payment.dto.KakaoPayRequest; +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; + +public class KakaoPayUtil { + + public static HttpHeaders createKakaoPayRequestHeaders(String kakaoPayRequestHost, String secretKey) { + HttpHeaders headers = new HttpHeaders(); + headers.add("Host", kakaoPayRequestHost); + headers.add("Authorization", "SECRET_KEY " + secretKey); + headers.setContentType(MediaType.APPLICATION_JSON); + return headers; + } + + public static Map createKakaoPayRequestBody(KakaoPayRequest kakaoPayRequest, String approvalUrl, String cancelUrl, String failUrl) { + Map body = new HashMap<>(); + body.put("cid", "TC0ONETIME"); + body.put("partner_order_id", kakaoPayRequest.getReservationId()); + body.put("partner_user_id", kakaoPayRequest.getHostId()); + body.put("item_name", kakaoPayRequest.getEventName()); + body.put("quantity", kakaoPayRequest.getQuantity()); + body.put("total_amount", kakaoPayRequest.getTotalAmount()); + body.put("tax_free_amount", kakaoPayRequest.getTaxFreeAmount()); + body.put("approval_url", approvalUrl); + body.put("cancel_url", cancelUrl); + body.put("fail_url", failUrl); + return body; + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/util/RestClientUtil.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/util/RestClientUtil.java new file mode 100644 index 00000000..9ed39e5f --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/util/RestClientUtil.java @@ -0,0 +1,54 @@ +package org.ktc2.cokaen.wouldyouin._common.util; + +import java.util.function.Predicate; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpHeaders; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestClient; +import org.springframework.web.client.RestClient.ResponseSpec.ErrorHandler; + +@Component +@RequiredArgsConstructor +public class RestClientUtil { + + private final RestClient client; + + public T get(Class classType, String url, HttpHeaders headers, ErrorHandler errorHandler) { + return client.get() + .uri(url) + .headers(httpHeaders -> httpHeaders.putAll(headers)) + .retrieve() + .onStatus(Predicate.not(HttpStatusCode::is2xxSuccessful), errorHandler) + .body(classType); + } + + public ResponseEntity getResponseEntity(Class classType, String url, HttpHeaders headers, ErrorHandler errorHandler) { + return client.get() + .uri(url) + .headers(httpHeaders -> httpHeaders.putAll(headers)) + .retrieve() + .onStatus(Predicate.not(HttpStatusCode::is2xxSuccessful), errorHandler) + .toEntity(classType); + } + + public T post( Class classType, String url, HttpHeaders headers, ErrorHandler errorHandler) { + return client.post() + .uri(url) + .headers(httpHeaders -> httpHeaders.addAll(headers)) + .retrieve() + .onStatus(Predicate.not(HttpStatusCode::is2xxSuccessful), errorHandler) + .body(classType); + } + + public T post(Class classType, String url, HttpHeaders headers, B body, ErrorHandler errorHandler) { + return client.post() + .uri(url) + .headers(httpHeaders -> httpHeaders.addAll(headers)) + .body(body) + .retrieve() + .onStatus(Predicate.not(HttpStatusCode::is2xxSuccessful), errorHandler) + .body(classType); + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/util/UriUtil.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/util/UriUtil.java new file mode 100644 index 00000000..75fbd2ab --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/util/UriUtil.java @@ -0,0 +1,34 @@ +package org.ktc2.cokaen.wouldyouin._common.util; + +import org.springframework.util.MultiValueMap; +import org.springframework.web.util.UriComponentsBuilder; + +public class UriUtil { + + public static String buildUrl(String scheme, String host, String path, MultiValueMap params) { + return UriComponentsBuilder.newInstance() + .scheme(scheme) + .host(host) + .path(path) + .queryParams(params) + .build() + .toString(); + } + + public static String buildUrl(String scheme, String host, String path) { + return UriComponentsBuilder.newInstance() + .scheme(scheme) + .host(host) + .path(path) + .build() + .toString(); + } + + public static String assembleFullUrl(String baseUrl, String... paths) { + String joinedPath = String.join("/", paths); + return UriComponentsBuilder.fromHttpUrl(baseUrl) + .path("/" + joinedPath) + .build() + .toString(); + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/vo/Category.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/vo/Category.java index 55593840..1a796347 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/vo/Category.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/vo/Category.java @@ -4,8 +4,8 @@ public enum Category { 전체, 밴드, 연극, - 뮤지컬, 원데이클래스, + 뮤지컬, 전시회, 공예, 축제 diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/vo/Location.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/vo/Location.java index 240d31d5..f3ea08a8 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/_common/vo/Location.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_common/vo/Location.java @@ -1,17 +1,36 @@ package org.ktc2.cokaen.wouldyouin._common.vo; import jakarta.persistence.Embeddable; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotNull; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.ToString; @Getter @Setter @NoArgsConstructor @AllArgsConstructor @Embeddable +@EqualsAndHashCode +@ToString public class Location { - Double longitude; - Double latitude; + + @NotNull(message = "위도 값은 필수입니다.") + @Min(value = -90, message = "위도는 -90 이상이어야 합니다.") + @Max(value = 90, message = "위도는 90 이하여야 합니다.") + private Double latitude; + + @NotNull(message = "경도 값은 필수입니다.") + @Min(value = -180, message = "경도는 -180 이상이어야 합니다.") + @Max(value = 180, message = "경도는 180 이하여야 합니다.") + private Double longitude; + + @NotEmpty(message = "상세 주소는 필수입니다.") + String detailAddress; } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/_develop/api/TempCreateController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_develop/api/TempCreateController.java new file mode 100644 index 00000000..5e5cc0ca --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_develop/api/TempCreateController.java @@ -0,0 +1,81 @@ +package org.ktc2.cokaen.wouldyouin._develop.api; + +import jakarta.validation.Valid; +import java.util.Map; +import lombok.RequiredArgsConstructor; +import org.ktc2.cokaen.wouldyouin._common.api.ApiResponse; +import org.ktc2.cokaen.wouldyouin._common.api.ApiResponseBody; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.auth.api.dto.TokenResponse; +import org.ktc2.cokaen.wouldyouin.auth.application.JwtService; +import org.ktc2.cokaen.wouldyouin.member.api.dto.MemberResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.create.HostCreateRequest; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.create.MemberCreateRequest; +import org.ktc2.cokaen.wouldyouin.member.application.BaseMemberService; +import org.ktc2.cokaen.wouldyouin.member.application.CuratorService; +import org.ktc2.cokaen.wouldyouin.member.application.HostService; +import org.ktc2.cokaen.wouldyouin.member.application.MemberService; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; +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.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/test") +public class TempCreateController { + + private final BaseMemberService baseMemberService; + private final MemberService memberService; + private final HostService hostService; + private final CuratorService curatorService; + private final JwtService jwtService; + + // 테스트: 사용자 생성 + @PostMapping("/create/member") + public ResponseEntity>> testCreateMember(@Valid @RequestBody MemberCreateRequest request) { + MemberResponse response = memberService.createMember(request); + MemberIdentifier identifier = new MemberIdentifier(response.getMemberId(), response.getMemberType()); + return ApiResponse.created(Map.of("response", response, "token", createToken(identifier))); + } + + // 테스트: 호스트 생성 + @PostMapping("/create/host") + public ResponseEntity>> testCreateHost(@RequestBody HostCreateRequest request) { + MemberResponse response = hostService.createHost(request); + MemberIdentifier identifier = new MemberIdentifier(response.getMemberId(), response.getMemberType()); + return ApiResponse.created(Map.of("response", response, "token", createToken(identifier))); + } + + // 테스트: 큐레이터 생성 + @PostMapping("/create/curator") + public ResponseEntity>> testCreateCurator(@RequestParam("memberId") Long id) { + MemberResponse response = curatorService.createCurator(id); + MemberIdentifier identifier = new MemberIdentifier(response.getMemberId(), response.getMemberType()); + return ApiResponse.created(Map.of("response", response, "token", createToken(identifier))); + } + + // 테스트: 사용자 삭제 + @DeleteMapping("/delete/{memberId}") + public ResponseEntity> testDeleteMember(@PathVariable("memberId") Long id) { + baseMemberService.deleteById(id); + return ApiResponse.noContent(); + } + + @GetMapping("create/token") + public ResponseEntity> testCreateToken( + @RequestParam("memberId") Long memberId, @RequestParam("type") MemberType memberType) { + return ApiResponse.ok(createToken(new MemberIdentifier(memberId, memberType))); + } + + private TokenResponse createToken(MemberIdentifier identifier) { + String accessToken = jwtService.createAccessToken(identifier.id(), identifier.type()); + return TokenResponse.of(accessToken, identifier.id(), identifier.type()); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/temp/TempLoginController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/_develop/api/TempLoginController.java similarity index 86% rename from src/main/java/org/ktc2/cokaen/wouldyouin/auth/temp/TempLoginController.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/_develop/api/TempLoginController.java index a3c15467..d3ccc541 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/temp/TempLoginController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/_develop/api/TempLoginController.java @@ -1,11 +1,13 @@ -package org.ktc2.cokaen.wouldyouin.auth.temp; +package org.ktc2.cokaen.wouldyouin._develop.api; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Controller; import org.springframework.ui.Model; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; -@Controller +@Controller() +@RequestMapping("/api/test") public class TempLoginController { @Value("${oauth.kakao.client.id}") @@ -20,7 +22,7 @@ public class TempLoginController { @Value("${oauth.google.redirect_uri}") private String googleRedirectUri; - @GetMapping("/social-login-test") + @GetMapping("/social-login") public String LoginPage(Model model) { String googleSocialLoginUri = "https://accounts.google.com/o/oauth2/auth" + diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/AdvertisementController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/AdvertisementController.java index e8beda9e..65f7ee62 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/AdvertisementController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/AdvertisementController.java @@ -38,31 +38,22 @@ public ResponseEntity>> getActiveAdv @GetMapping("/{adId}") public ResponseEntity> getAdvertisementByAdId( @PathVariable Long adId) { - return ApiResponse.ok(advertisementService.getAdvertisementByAdId(adId)); + return ApiResponse.ok(advertisementService.getById(adId)); } @PostMapping(consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}) public ResponseEntity> createAdvertisement( @Valid @RequestPart AdvertisementRequest advertisementRequest, @RequestPart(required = false) MultipartFile image, - @Authorize(MemberType.admin) MemberIdentifier memberIdentifier) { + @Authorize(MemberType.admin) MemberIdentifier identifier) { return ApiResponse.created(advertisementService.create(advertisementRequest, image)); } - @PutMapping(path = "/{adId}", consumes = {MediaType.APPLICATION_JSON_VALUE, MediaType.MULTIPART_FORM_DATA_VALUE}) - public ResponseEntity> updateAdvertisement( - @PathVariable Long adId, - @Valid @RequestPart AdvertisementRequest advertisementRequest, - @RequestPart(required = false) MultipartFile image, - @Authorize(MemberType.admin) MemberIdentifier memberIdentifier) { - return ApiResponse.ok(advertisementService.update(adId, advertisementRequest, image)); - } - @DeleteMapping("/{adId}") public ResponseEntity> deleteAdvertisement( @PathVariable Long adId, - @Authorize(MemberType.admin) MemberIdentifier memberIdentifier) { - advertisementService.delete(adId); + @Authorize(MemberType.admin) MemberIdentifier identifier) { + advertisementService.delete(identifier, adId); return ApiResponse.noContent(); } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/dto/AdvertisementRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/dto/AdvertisementRequest.java index ae62e765..27f58443 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/dto/AdvertisementRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/dto/AdvertisementRequest.java @@ -2,19 +2,23 @@ import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.FutureOrPresent; -import jakarta.validation.constraints.NotEmpty; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; -import org.ktc2.cokaen.wouldyouin.Image.persist.AdvertisementImage; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.advertisement.persist.Advertisement; +import org.ktc2.cokaen.wouldyouin.image.persist.AdvertisementImage; @Getter @Builder(toBuilder = true) +@EqualsAndHashCode +@ToString public class AdvertisementRequest { - @NotEmpty(message = "광고명은 필수입니다.") + @NotBlank(message = "광고명은 필수입니다.") private String title; @NotNull(message = "광고게시 시작 시간은 필수입니다.") @@ -26,7 +30,7 @@ public class AdvertisementRequest { private LocalDateTime endTime; @AssertTrue(message = "종료 시간은 시작 시간 이후여야 합니다.") - public boolean isEndTimeAfterStartTime() { + private boolean isEndTimeAfterStartTime() { return endTime.isAfter(startTime); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/dto/AdvertisementResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/dto/AdvertisementResponse.java index daf6a21c..5e9da066 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/dto/AdvertisementResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/api/dto/AdvertisementResponse.java @@ -1,10 +1,17 @@ package org.ktc2.cokaen.wouldyouin.advertisement.api.dto; import java.time.LocalDateTime; +import java.util.Optional; import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.advertisement.persist.Advertisement; +import org.ktc2.cokaen.wouldyouin.image.persist.AdvertisementImage; +import org.ktc2.cokaen.wouldyouin.image.persist.Image; @Builder +@EqualsAndHashCode +@ToString public class AdvertisementResponse { private Long id; @@ -17,7 +24,10 @@ public static AdvertisementResponse from(Advertisement advertisement) { return AdvertisementResponse.builder() .id(advertisement.getId()) .title(advertisement.getTitle()) - .imageUrl(advertisement.getAdvertisementImage().getUrl()) + .imageUrl(Optional.of(advertisement) + .map((ad) -> advertisement.getAdvertisementImage()) + .map(Image::getName) + .orElse("")) .startTime(advertisement.getStartTime()) .endTime(advertisement.getEndTime()) .build(); diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/application/AdvertisementService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/application/AdvertisementService.java index f0f63274..3a7fca03 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/application/AdvertisementService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/application/AdvertisementService.java @@ -4,13 +4,14 @@ import java.util.List; import java.util.Optional; import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin.Image.application.AdvertisementImageService; -import org.ktc2.cokaen.wouldyouin.Image.persist.AdvertisementImage; import org.ktc2.cokaen.wouldyouin._common.exception.EntityNotFoundException; import org.ktc2.cokaen.wouldyouin.advertisement.api.dto.AdvertisementRequest; import org.ktc2.cokaen.wouldyouin.advertisement.api.dto.AdvertisementResponse; import org.ktc2.cokaen.wouldyouin.advertisement.persist.Advertisement; import org.ktc2.cokaen.wouldyouin.advertisement.persist.AdvertisementRepository; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.image.application.AdvertisementImageService; +import org.ktc2.cokaen.wouldyouin.image.persist.AdvertisementImage; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -23,49 +24,33 @@ public class AdvertisementService { private final AdvertisementImageService adImageService; @Transactional(readOnly = true) - public List getAllActiveAdvertisements() { - return adRepository.findAllActiveAdvertisements(LocalDateTime.now()).stream() - .map(AdvertisementResponse::from).toList(); + public AdvertisementResponse getById(Long adId) { + return AdvertisementResponse.from(getByIdOrThrow(adId)); } @Transactional(readOnly = true) - public AdvertisementResponse getAdvertisementByAdId(Long adId) { - return AdvertisementResponse.from(adRepository.findById(adId).orElseThrow(() -> new EntityNotFoundException("Advertisement"))); + public List getAllActiveAdvertisements() { + return adRepository.findAllActiveAdvertisements(LocalDateTime.now()).stream() + .map(AdvertisementResponse::from).toList(); } - // Todo: 롤백될 경우, 저장한 이미지 삭제 @Transactional public AdvertisementResponse create(AdvertisementRequest adRequest, MultipartFile image) { - AdvertisementImage adImage = adImageService.saveAndCreateImage(image); + AdvertisementImage adImage = adImageService.saveImage(image); Advertisement ad = adRepository.save(adRequest.toEntity(adImage)); - adImage.setAdvertisement(ad); + adImageService.setAdvertisement(adImage, ad); return AdvertisementResponse.from(ad); } - // Todo: 수정될 때 이미지가 null인 경우 기존 이미지로 대체하는 로직 프론트와 협의 - // Todo: 롤백될 경우, 저장한 이미지 삭제 @Transactional - public AdvertisementResponse update(Long adId, AdvertisementRequest adRequest, MultipartFile multipartFile) { - Advertisement ad = adRepository.findById(adId).orElseThrow(() -> new EntityNotFoundException("Advertisement")); - Optional.ofNullable(multipartFile).ifPresentOrElse( - image -> { - adImageService.deleteAndDelete(ad.getAdvertisementImage().getId()); - AdvertisementImage adImage = adImageService.saveAndCreateImage(image); - ad.updateFrom(adRequest, adImage); - adImage.setAdvertisement(ad); - }, - () -> { - AdvertisementImage adImage = ad.getAdvertisementImage(); - ad.updateFrom(adRequest, adImage); - } - ); - return AdvertisementResponse.from(ad); + public void delete(MemberIdentifier identifier, Long adId) { + Advertisement ad = getByIdOrThrow(adId); + adImageService.deleteImage(identifier, ad.getAdvertisementImage().getId()); + adRepository.deleteById(adId); } - @Transactional - public void delete(Long adId) { - Advertisement ad = adRepository.findById(adId).orElseThrow(() -> new EntityNotFoundException("Advertisement")); - adImageService.deleteAndDelete(ad.getAdvertisementImage().getId()); - adRepository.deleteById(adId); + private Advertisement getByIdOrThrow(Long adId) { + return adRepository.findById(adId) + .orElseThrow(() -> new EntityNotFoundException("해당하는 광고를 찾을 수 없습니다.")); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/persist/Advertisement.java b/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/persist/Advertisement.java index a22b1bd5..2472cad0 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/persist/Advertisement.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/advertisement/persist/Advertisement.java @@ -7,29 +7,35 @@ import jakarta.persistence.Id; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; +import java.util.Optional; import lombok.AccessLevel; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.hibernate.annotations.Comment; -import org.ktc2.cokaen.wouldyouin.Image.persist.AdvertisementImage; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.advertisement.api.dto.AdvertisementRequest; +import org.ktc2.cokaen.wouldyouin.image.persist.AdvertisementImage; @Getter @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity +@EqualsAndHashCode +@ToString public class Advertisement { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Setter(AccessLevel.NONE) @Column(name = "advertisement_id") private Long id; - @NotNull + @NotBlank @Column(name = "title") private String title; @@ -47,7 +53,8 @@ public class Advertisement { private LocalDateTime endTime; @Builder - public Advertisement(String title, AdvertisementImage advertisementImage, LocalDateTime startTime, + public Advertisement(String title, AdvertisementImage advertisementImage, + LocalDateTime startTime, LocalDateTime endTime) { this.title = title; this.advertisementImage = advertisementImage; @@ -56,9 +63,9 @@ public Advertisement(String title, AdvertisementImage advertisementImage, LocalD } public void updateFrom(AdvertisementRequest advertisementRequest, AdvertisementImage adImage) { - this.title = advertisementRequest.getTitle(); - this.advertisementImage = adImage; - this.startTime = advertisementRequest.getStartTime(); - this.endTime = advertisementRequest.getEndTime(); + Optional.ofNullable(advertisementRequest.getTitle()).ifPresent(this::setTitle); + Optional.ofNullable(adImage).ifPresent(this::setAdvertisementImage); + Optional.ofNullable(advertisementRequest.getStartTime()).ifPresent(this::setStartTime); + Optional.ofNullable(advertisementRequest.getEndTime()).ifPresent(this::setEndTime); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/AuthorizeArgumentResolver.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/AuthorizeArgumentResolver.java index cef846b8..c6e8afa7 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/AuthorizeArgumentResolver.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/AuthorizeArgumentResolver.java @@ -4,6 +4,7 @@ import java.util.Objects; import lombok.RequiredArgsConstructor; import org.ktc2.cokaen.wouldyouin._common.exception.UnauthorizedException; +import org.ktc2.cokaen.wouldyouin.auth.exception.AuthenticationEmptyException; import org.ktc2.cokaen.wouldyouin.auth.persist.CustomUserDetails; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.springframework.core.MethodParameter; @@ -17,7 +18,6 @@ @Component @RequiredArgsConstructor -//TODO: 커스텀 예외 추가 필요 public class AuthorizeArgumentResolver implements HandlerMethodArgumentResolver { @Override @@ -37,12 +37,12 @@ public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer m // 토큰으로 인증된 사용자 정보 얻기 private MemberIdentifier getAuthorizedMemberIdFrom(Authentication authentication) { if (authentication == null) { - throw new RuntimeException("No authentication found"); + throw new AuthenticationEmptyException("시큐리티 컨텍스트 내 인증 정보가 없습니다."); } if (authentication.getPrincipal() instanceof CustomUserDetails userDetails) { return userDetails.getIdentifier(); } - throw new RuntimeException("No CustomUserDetails found"); + throw new RuntimeException("No CustomUserDetails found"); // 절대 도달하지 않음 } // @Authorize 애너테이션이 요구한 멤버 타입 추출 diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/AuthController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/AuthController.java index 3c886902..dd505208 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/AuthController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/AuthController.java @@ -5,12 +5,12 @@ import org.ktc2.cokaen.wouldyouin._common.api.ApiResponseBody; import org.ktc2.cokaen.wouldyouin.auth.Authorize; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.auth.api.dto.LocalLoginRequest; +import org.ktc2.cokaen.wouldyouin.auth.api.dto.LocalSignupRequest; +import org.ktc2.cokaen.wouldyouin.auth.api.dto.SocialTokenResponse; +import org.ktc2.cokaen.wouldyouin.auth.api.dto.TokenResponse; import org.ktc2.cokaen.wouldyouin.auth.application.AuthService; -import org.ktc2.cokaen.wouldyouin.auth.application.dto.LocalLoginRequest; -import org.ktc2.cokaen.wouldyouin.auth.application.dto.LocalSignupRequest; -import org.ktc2.cokaen.wouldyouin.auth.application.dto.SocialTokenResponse; -import org.ktc2.cokaen.wouldyouin.auth.application.dto.TokenResponse; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.MemberAdditionalInfoRequest; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.MemberAdditionalInfoRequest; import org.ktc2.cokaen.wouldyouin.member.persist.AccountType; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.springframework.http.ResponseEntity; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/dto/LocalLoginRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/dto/LocalLoginRequest.java similarity index 54% rename from src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/dto/LocalLoginRequest.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/dto/LocalLoginRequest.java index 1011fab3..6a18a892 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/dto/LocalLoginRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/dto/LocalLoginRequest.java @@ -1,4 +1,4 @@ -package org.ktc2.cokaen.wouldyouin.auth.application.dto; +package org.ktc2.cokaen.wouldyouin.auth.api.dto; public record LocalLoginRequest(String email, String password) { } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/dto/LocalSignupRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/dto/LocalSignupRequest.java new file mode 100644 index 00000000..14d436fc --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/dto/LocalSignupRequest.java @@ -0,0 +1,10 @@ +package org.ktc2.cokaen.wouldyouin.auth.api.dto; + +import lombok.EqualsAndHashCode; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.create.HostCreateRequest; + +@EqualsAndHashCode(callSuper = true) +@ToString +public class LocalSignupRequest extends HostCreateRequest { +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/dto/SocialTokenResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/dto/SocialTokenResponse.java new file mode 100644 index 00000000..ad4af168 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/dto/SocialTokenResponse.java @@ -0,0 +1,9 @@ +package org.ktc2.cokaen.wouldyouin.auth.api.dto; + +import lombok.Builder; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; + +@Builder +public record SocialTokenResponse(Boolean isWelcomeMember, String token, Long memberId, MemberType memberType) { + +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/dto/TokenResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/dto/TokenResponse.java new file mode 100644 index 00000000..c40ba589 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/api/dto/TokenResponse.java @@ -0,0 +1,16 @@ +package org.ktc2.cokaen.wouldyouin.auth.api.dto; + +import lombok.Builder; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; + +@Builder +public record TokenResponse(String token, Long memberId, MemberType memberType) { + + public static TokenResponse of(String token, Long memberId, MemberType memberType) { + return TokenResponse.builder() + .token(token) + .memberId(memberId) + .memberType(memberType) + .build(); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/AuthService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/AuthService.java index 127a50a1..817e4b65 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/AuthService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/AuthService.java @@ -3,18 +3,18 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; -import org.ktc2.cokaen.wouldyouin.auth.application.dto.LocalLoginRequest; -import org.ktc2.cokaen.wouldyouin.auth.application.dto.LocalSignupRequest; -import org.ktc2.cokaen.wouldyouin.auth.application.dto.SocialTokenResponse; -import org.ktc2.cokaen.wouldyouin.auth.application.dto.TokenResponse; +import org.ktc2.cokaen.wouldyouin.auth.api.dto.LocalLoginRequest; +import org.ktc2.cokaen.wouldyouin.auth.api.dto.LocalSignupRequest; +import org.ktc2.cokaen.wouldyouin.auth.api.dto.SocialTokenResponse; +import org.ktc2.cokaen.wouldyouin.auth.api.dto.TokenResponse; import org.ktc2.cokaen.wouldyouin.auth.application.oauth.OauthRequestServiceFactory; import org.ktc2.cokaen.wouldyouin.auth.application.oauth.dto.OauthResourcesResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.MemberResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.MemberAdditionalInfoRequest; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.create.MemberCreateRequest; import org.ktc2.cokaen.wouldyouin.member.application.BaseMemberService; import org.ktc2.cokaen.wouldyouin.member.application.HostService; import org.ktc2.cokaen.wouldyouin.member.application.MemberService; -import org.ktc2.cokaen.wouldyouin.member.application.dto.MemberResponse; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.MemberAdditionalInfoRequest; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.create.MemberCreateRequest; import org.ktc2.cokaen.wouldyouin.member.persist.AccountType; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.springframework.stereotype.Service; @@ -33,12 +33,14 @@ public class AuthService { @Transactional public TokenResponse localSignup(LocalSignupRequest request) { baseMemberService.checkUniqueEmailOrThrow(request.getEmail()); - return TokenResponse.from(createToken(hostService.createHost(request))); + MemberResponse response = hostService.createHost(request); + return TokenResponse.of(createToken(response), response.getMemberId(), response.getMemberType()); } @Transactional(readOnly = true) public TokenResponse localLogin(LocalLoginRequest request) { - return TokenResponse.from(createToken(hostService.getMemberResponseBy(request))); + MemberResponse response = hostService.getMemberResponseBy(request); + return TokenResponse.of(createToken(response), response.getMemberId(), response.getMemberType()); } @Transactional @@ -55,6 +57,8 @@ public SocialTokenResponse socialLogin(AccountType accountType, String code) { return SocialTokenResponse.builder() .isWelcomeMember(false) .token(createToken(id)) + .memberId(id.id()) + .memberType(id.type()) .build(); } // 소셜 계정이지만 아직 추가 정보 기입이 되지 않은 경우 @@ -62,6 +66,8 @@ public SocialTokenResponse socialLogin(AccountType accountType, String code) { return SocialTokenResponse.builder() .isWelcomeMember(true) .token(createToken(id)) + .memberId(id.id()) + .memberType(id.type()) .build(); } } @@ -78,12 +84,15 @@ public SocialTokenResponse socialLogin(AccountType accountType, String code) { return SocialTokenResponse.builder() .isWelcomeMember(true) .token(createToken(welcomeMemberResponse)) + .memberId(welcomeMemberResponse.getMemberId()) + .memberType(welcomeMemberResponse.getMemberType()) .build(); } @Transactional public TokenResponse acceptAdditionalInfo(Long welcomeMemberId, MemberAdditionalInfoRequest request) { - return TokenResponse.from(createToken(memberService.updateWelcomeMember(welcomeMemberId, request))); + MemberResponse response = memberService.updateWelcomeMember(welcomeMemberId, request); + return TokenResponse.of(createToken(response), response.getMemberId(), response.getMemberType()); } private String createToken(MemberIdentifier identifier) { diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/CustomAccessDeniedHandler.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/CustomAccessDeniedHandler.java index 1430f3dd..7ea85f5e 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/CustomAccessDeniedHandler.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/CustomAccessDeniedHandler.java @@ -6,8 +6,8 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin._common.api.ApiResponseBody; -import org.springframework.http.HttpStatus; +import org.ktc2.cokaen.wouldyouin._common.api.ApiResponse; +import org.ktc2.cokaen.wouldyouin._common.exception.ErrorCode; import org.springframework.http.MediaType; import org.springframework.security.access.AccessDeniedException; import org.springframework.security.web.access.AccessDeniedHandler; @@ -22,13 +22,12 @@ public class CustomAccessDeniedHandler implements AccessDeniedHandler { @Override public void handle(HttpServletRequest request, HttpServletResponse response, AccessDeniedException accessDeniedException) throws IOException, ServletException { - - // TODO: Fail ApiResponse로 변경필요 - ApiResponseBody body = new ApiResponseBody<>(false, "No Authorities(response by CustomAccessDeniedHandler.Class)"); - String responseBody = objectMapper.writeValueAsString(body); + String responseBody = objectMapper.writeValueAsString(ApiResponse.error(ErrorCode.SECURITY_FILTER_FORBIDDEN_ACCESS, + "권한이 부족하여 접근이 거부되었습니다.") + .getBody()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setStatus(HttpStatus.FORBIDDEN.value()); + response.setStatus(ErrorCode.SECURITY_FILTER_FORBIDDEN_ACCESS.getStatus()); response.setCharacterEncoding("UTF-8"); response.getWriter().write(responseBody); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/CustomAuthenticationEntryPoint.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/CustomAuthenticationEntryPoint.java index 3df329ea..8e1e004d 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/CustomAuthenticationEntryPoint.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/CustomAuthenticationEntryPoint.java @@ -6,8 +6,8 @@ import jakarta.servlet.http.HttpServletResponse; import java.io.IOException; import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin._common.api.ApiResponseBody; -import org.springframework.http.HttpStatus; +import org.ktc2.cokaen.wouldyouin._common.api.ApiResponse; +import org.ktc2.cokaen.wouldyouin._common.exception.ErrorCode; import org.springframework.http.MediaType; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; @@ -21,13 +21,12 @@ public class CustomAuthenticationEntryPoint implements AuthenticationEntryPoint @Override public void commence(HttpServletRequest request, HttpServletResponse response, AuthenticationException authException) throws IOException, ServletException { - - // TODO: Fail ApiResponse로 변경필요 - ApiResponseBody body = new ApiResponseBody<>(false, "Not Authenticated Request(response by CustomAuthenticationEntryPoint.Class)"); - String responseBody = objectMapper.writeValueAsString(body); + String responseBody = objectMapper.writeValueAsString(ApiResponse.error(ErrorCode.SECURITY_FILTER_AUTHORIZATION_REQUIRED, + "요청시 인증이 필요합니다. Authorization Token을 확인해주세요.") + .getBody()); response.setContentType(MediaType.APPLICATION_JSON_VALUE); - response.setStatus(HttpStatus.UNAUTHORIZED.value()); + response.setStatus(ErrorCode.SECURITY_FILTER_AUTHORIZATION_REQUIRED.getStatus()); response.setCharacterEncoding("UTF-8"); response.getWriter().write(responseBody); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/CustomUserDetailsService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/CustomUserDetailsService.java index 71cf280c..85cbe21c 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/CustomUserDetailsService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/CustomUserDetailsService.java @@ -1,6 +1,7 @@ package org.ktc2.cokaen.wouldyouin.auth.application; import lombok.RequiredArgsConstructor; +import org.ktc2.cokaen.wouldyouin._common.exception.EntityNotFoundException; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; import org.ktc2.cokaen.wouldyouin.auth.persist.CustomUserDetails; import org.ktc2.cokaen.wouldyouin.member.persist.BaseMember; @@ -20,8 +21,8 @@ public class CustomUserDetailsService implements UserDetailsService { @Override public UserDetails loadUserByUsername(String id) throws UsernameNotFoundException { - // TODO: 커스텀 예외 필요 - BaseMember member = baseMemberRepository.findById(Long.parseLong(id)).orElseThrow(RuntimeException::new); + BaseMember member = baseMemberRepository.findById(Long.parseLong(id)).orElseThrow(() -> + new EntityNotFoundException("아이디에 해당하는 사용자를 찾을 수 없습니다.")); return new CustomUserDetails(new MemberIdentifier(member.getId(), member.getMemberType())); } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/JwtService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/JwtService.java index c169acad..5da2c445 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/JwtService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/JwtService.java @@ -14,12 +14,13 @@ import java.time.ZoneOffset; import java.util.Date; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.auth.exception.InvalidAuthorizationHeaderException; +import org.ktc2.cokaen.wouldyouin.auth.exception.JwtTokenException; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; @Service -//TODO: 커스텀 예외 필요 public class JwtService { @Value("${jwt.access.expiration}") @@ -76,7 +77,7 @@ public boolean hasPrefix(String authorizationHeader) { // 토큰 접두사 제거 private String removePrefixFrom(String authorizationHeader) { if (!hasPrefix(authorizationHeader)) { - throw new RuntimeException("Authorization header is missing or invalid"); + throw new InvalidAuthorizationHeaderException("Authorization header is missing or invalid"); } // 'Bearer ' 제거 후 실제 JWT 토큰만 추출 및 토큰 검증 @@ -88,13 +89,13 @@ private void validateToken(String token) { try { Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); } catch (io.jsonwebtoken.security.SecurityException | MalformedJwtException e) { - throw new RuntimeException("Invalid JWT Token"); + throw new JwtTokenException("Invalid JWT Token"); } catch (ExpiredJwtException e) { - throw new RuntimeException("Expired JWT Token"); + throw new JwtTokenException("Expired JWT Token"); } catch (UnsupportedJwtException e) { - throw new RuntimeException("Unsupported JWT Token"); + throw new JwtTokenException("Unsupported JWT Token"); } catch (IllegalArgumentException e) { - throw new RuntimeException("JWT claims string is empty."); + throw new JwtTokenException("JWT claims string is empty."); } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/dto/LocalSignupRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/dto/LocalSignupRequest.java deleted file mode 100644 index 748ff493..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/dto/LocalSignupRequest.java +++ /dev/null @@ -1,6 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.auth.application.dto; - -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.create.HostCreateRequest; - -public class LocalSignupRequest extends HostCreateRequest { -} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/dto/SocialTokenResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/dto/SocialTokenResponse.java deleted file mode 100644 index 4dfce018..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/dto/SocialTokenResponse.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.auth.application.dto; - -import lombok.Builder; - -@Builder -public record SocialTokenResponse(Boolean isWelcomeMember, String token) { - -} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/dto/TokenResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/dto/TokenResponse.java deleted file mode 100644 index 37a4b667..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/dto/TokenResponse.java +++ /dev/null @@ -1,13 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.auth.application.dto; - -import lombok.Builder; - -@Builder -public record TokenResponse(String token) { - - public static TokenResponse from(String token) { - return TokenResponse.builder() - .token(token) - .build(); - } -} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/GoogleRequestService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/GoogleRequestService.java index c15e9e8b..bade50ee 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/GoogleRequestService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/GoogleRequestService.java @@ -2,19 +2,26 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.annotation.PostConstruct; import java.util.Objects; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.ktc2.cokaen.wouldyouin._common.util.RestClientUtil; +import org.ktc2.cokaen.wouldyouin._common.util.UriUtil; import org.ktc2.cokaen.wouldyouin.auth.application.oauth.dto.AccessTokenResponse; import org.ktc2.cokaen.wouldyouin.auth.application.oauth.dto.OauthRequest; import org.ktc2.cokaen.wouldyouin.auth.application.oauth.dto.OauthResourcesResponse; +import org.ktc2.cokaen.wouldyouin.auth.exception.FailAccessTokenGetException; +import org.ktc2.cokaen.wouldyouin.auth.exception.FailSocialDataGetException; import org.ktc2.cokaen.wouldyouin.member.persist.AccountType; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; -import org.springframework.web.client.RestClient; +@Slf4j @Service +@RequiredArgsConstructor public class GoogleRequestService extends OauthRequestService { @Value("${oauth.google.uri.login.host}") @@ -38,52 +45,56 @@ public class GoogleRequestService extends OauthRequestService { @Value("${oauth.google.redirect_uri}") private String redirectUri; + private final RestClientUtil client; + private String loginRequestUri; + private String accessRequestUri; + private HttpHeaders loginRequestHeaders; + + @PostConstruct + private void init() { + loginRequestUri = UriUtil.buildUrl("https", loginRequestHost, loginRequestPath); + accessRequestUri = UriUtil.buildUrl("https", accessRequestHost, accessRequestPath); + + loginRequestHeaders = new HttpHeaders(); + loginRequestHeaders.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE); + } + + protected HttpHeaders getAccessRequestHeaders(AccessTokenResponse authenticationResponse) { + HttpHeaders accessRequestHeaders = new HttpHeaders(); + accessRequestHeaders.add(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE); + accessRequestHeaders.add(HttpHeaders.AUTHORIZATION, "Bearer " + authenticationResponse.getAccessToken()); + return accessRequestHeaders; + } + @Override protected AccountType getAccountType() { return AccountType.google; } - protected OauthRequest getOauthRequestBase() { - return OauthRequest.builder() + @Override + public OauthResourcesResponse getOauthMemberResources(String code) { + return requestLoginAndAccessResources(OauthRequest.builder() .grantType("authorization_code") .clientId(clientId) .clientSecret(clientSecret) .redirectUri(redirectUri) - .build(); + .code(code) + .build()); } @Override protected OauthResourcesResponse requestLoginAndAccessResources(OauthRequest request) { - AccessTokenResponse authenticationResponse = RestClient.create() - .post() - .uri(uriBuilder -> uriBuilder - .scheme("https") - .host(loginRequestHost) - .path(loginRequestPath) - .build(true)) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) - .body(request) - .retrieve() - // TODO: 커스텀 예외 추가 - .onStatus(HttpStatusCode::is4xxClientError, (httpRequest, clientHttpResponse) -> { throw new RuntimeException("에러"); }) - .onStatus(HttpStatusCode::is5xxServerError, (httpRequest, clientHttpResponse) -> { throw new RuntimeException("에러"); }) - .body(AccessTokenResponse.class); + + AccessTokenResponse authenticationResponse = client.post( + AccessTokenResponse.class, loginRequestUri, loginRequestHeaders, request, + (req, response) -> { throw new FailAccessTokenGetException("구글 액세스 토큰을 가져오는데 실패했습니다."); }); Objects.requireNonNull(authenticationResponse); - GoogleAccessRequestResponse result = RestClient.create() - .get() - .uri(uriBuilder -> uriBuilder - .scheme("https") - .host(accessRequestHost) - .path(accessRequestPath) - .build(true)) - .header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + authenticationResponse.getAccessToken()) - .retrieve() - // TODO: 커스텀 예외 추가 - .onStatus(HttpStatusCode::is4xxClientError, (httpRequest, clientHttpResponse) -> { throw new RuntimeException("에러"); }) - .onStatus(HttpStatusCode::is5xxServerError, (httpRequest, clientHttpResponse) -> { throw new RuntimeException("에러"); }) - .body(GoogleAccessRequestResponse.class); + GoogleAccessRequestResponse result = client.get( + GoogleAccessRequestResponse.class, accessRequestUri, getAccessRequestHeaders(authenticationResponse), + (req, rsp) -> { throw new FailSocialDataGetException("구글 소셜 계정 정보를 가져오는데 실패했습니다."); }); + + log.debug("#### GoogleAccessRequestResponse result = {}", result); Objects.requireNonNull(result); return OauthResourcesResponse.builder() diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/KakaoRequestService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/KakaoRequestService.java index 1a8cb838..f92a690d 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/KakaoRequestService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/KakaoRequestService.java @@ -2,21 +2,29 @@ import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; +import jakarta.annotation.PostConstruct; import java.util.Objects; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.ToString; +import lombok.extern.slf4j.Slf4j; +import org.ktc2.cokaen.wouldyouin._common.util.RestClientUtil; +import org.ktc2.cokaen.wouldyouin._common.util.UriUtil; import org.ktc2.cokaen.wouldyouin.auth.application.oauth.dto.AccessTokenResponse; import org.ktc2.cokaen.wouldyouin.auth.application.oauth.dto.OauthRequest; import org.ktc2.cokaen.wouldyouin.auth.application.oauth.dto.OauthResourcesResponse; +import org.ktc2.cokaen.wouldyouin.auth.exception.FailAccessTokenGetException; +import org.ktc2.cokaen.wouldyouin.auth.exception.FailSocialDataGetException; import org.ktc2.cokaen.wouldyouin.member.persist.AccountType; import org.springframework.beans.factory.annotation.Value; import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpStatusCode; import org.springframework.http.MediaType; import org.springframework.stereotype.Service; -import org.springframework.web.client.RestClient; +import org.springframework.util.LinkedMultiValueMap; +@Slf4j @Service +@RequiredArgsConstructor public class KakaoRequestService extends OauthRequestService { @Value("${oauth.kakao.uri.login.host}") @@ -45,50 +53,57 @@ protected AccountType getAccountType() { return AccountType.kakao; } - protected OauthRequest getOauthRequestBase() { - return OauthRequest.builder() + private final RestClientUtil client; + private String accessRequestUri; + private HttpHeaders loginRequestHeaders; + + @PostConstruct + private void init() { + accessRequestUri = UriUtil.buildUrl("https", accessRequestHost, accessRequestPath); + loginRequestHeaders = new HttpHeaders(); + loginRequestHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); + } + + protected String getLoginRequestUri(OauthRequest request) { + var queries = new LinkedMultiValueMap(); + queries.add("grant_type", request.getGrantType()); + queries.add("client_id", request.getClientId()); + queries.add("client_secret", request.getClientSecret()); + queries.add("code", request.getCode()); + return UriUtil.buildUrl("https", loginRequestHost, loginRequestPath, queries); + } + + protected HttpHeaders getAccessRequestHeaders(AccessTokenResponse authenticationResponse) { + HttpHeaders accessRequestHeaders = new HttpHeaders(); + accessRequestHeaders.add(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE); + accessRequestHeaders.add(HttpHeaders.AUTHORIZATION, "Bearer " + authenticationResponse.getAccessToken()); + return accessRequestHeaders; + } + + @Override + public OauthResourcesResponse getOauthMemberResources(String code) { + return requestLoginAndAccessResources(OauthRequest.builder() .grantType("authorization_code") .clientId(clientId) .clientSecret(clientSecret) .redirectUri(redirectUri) - .build(); + .code(code) + .build()); } + @Override protected OauthResourcesResponse requestLoginAndAccessResources(OauthRequest request) { - AccessTokenResponse authenticationResponse = RestClient.create() - .post() - .uri(uriBuilder -> uriBuilder - .scheme("https") - .host(loginRequestHost) - .path(loginRequestPath) - .queryParam("grant_type", request.getGrantType()) - .queryParam("client_id", request.getClientId()) - .queryParam("client_secret", request.getClientSecret()) - .queryParam("code", request.getCode()) - .build(true)) - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) - .retrieve() - // TODO: 커스텀 예외 추가 - .onStatus(HttpStatusCode::is4xxClientError, (httpRequest, clientHttpResponse) -> { throw new RuntimeException("에러"); }) - .onStatus(HttpStatusCode::is5xxServerError, (httpRequest, clientHttpResponse) -> { throw new RuntimeException("에러"); }) - .body(AccessTokenResponse.class); + AccessTokenResponse authenticationResponse = client.post( + AccessTokenResponse.class, getLoginRequestUri(request), loginRequestHeaders, + (req, rsp) -> { throw new FailAccessTokenGetException("카카오 액세스 토큰을 가져오는데 실패했습니다."); }); Objects.requireNonNull(authenticationResponse); - KakaoAccessRequestResponse result = RestClient.create() - .get() - .uri(uriBuilder -> uriBuilder - .scheme("https") - .host(accessRequestHost) - .path(accessRequestPath) - .build(true)) - .header(HttpHeaders.CONTENT_TYPE, MediaType.APPLICATION_FORM_URLENCODED_VALUE) - .header(HttpHeaders.AUTHORIZATION, "Bearer " + authenticationResponse.getAccessToken()) - .retrieve() - // TODO: 커스텀 예외 추가 - .onStatus(HttpStatusCode::is4xxClientError, (httpRequest, clientHttpResponse) -> { throw new RuntimeException("에러"); }) - .onStatus(HttpStatusCode::is5xxServerError, (httpRequest, clientHttpResponse) -> { throw new RuntimeException("에러"); }) - .body(KakaoAccessRequestResponse.class); + KakaoAccessRequestResponse result = client.get( + KakaoAccessRequestResponse.class, accessRequestUri, getAccessRequestHeaders(authenticationResponse), + (req, rsp) -> { throw new FailSocialDataGetException("카카오 소셜 계정 정보를 가져오는데 실패했습니다."); }); + + log.debug("#### KakaoAccessRequestResponse result = {}", result); Objects.requireNonNull(result); return OauthResourcesResponse.builder() @@ -102,6 +117,7 @@ protected OauthResourcesResponse requestLoginAndAccessResources(OauthRequest req @JsonNaming(SnakeCaseStrategy.class) @RequiredArgsConstructor @Getter + @ToString static class KakaoAccessRequestResponse { private final Long id; @@ -111,6 +127,7 @@ static class KakaoAccessRequestResponse { @JsonNaming(SnakeCaseStrategy.class) @RequiredArgsConstructor @Getter + @ToString static class KakaoAccount { private final Profile profile; @@ -120,6 +137,7 @@ static class KakaoAccount { @JsonNaming(SnakeCaseStrategy.class) @RequiredArgsConstructor @Getter + @ToString static class Profile { private final String nickname; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/OauthRequestService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/OauthRequestService.java index 6e96036d..7b40e42a 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/OauthRequestService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/OauthRequestService.java @@ -9,22 +9,9 @@ @Service public abstract class OauthRequestService { - private OauthRequest oauthRequestBase; - protected abstract AccountType getAccountType(); - protected abstract OauthRequest getOauthRequestBase(); - protected abstract OauthResourcesResponse requestLoginAndAccessResources(OauthRequest oauthRequest); - @PostConstruct - private void init() { - oauthRequestBase = getOauthRequestBase(); - } - - final public OauthResourcesResponse getOauthMemberResources(String code) { - return requestLoginAndAccessResources(oauthRequestBase.toBuilder() - .code(code) - .build()); - } + public abstract OauthResourcesResponse getOauthMemberResources(String code); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/dto/OauthRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/dto/OauthRequest.java index ffcc3405..52cd236d 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/dto/OauthRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/dto/OauthRequest.java @@ -9,7 +9,7 @@ @Getter @RequiredArgsConstructor(access = AccessLevel.PROTECTED) -@Builder(toBuilder = true) +@Builder @JsonNaming(SnakeCaseStrategy.class) public class OauthRequest { diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/dto/OauthResourcesResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/dto/OauthResourcesResponse.java index 8dcdf841..2489472d 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/dto/OauthResourcesResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/application/oauth/dto/OauthResourcesResponse.java @@ -3,24 +3,16 @@ import lombok.Builder; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.ToString; @Getter @RequiredArgsConstructor @Builder +@ToString public class OauthResourcesResponse { private final String nickname; private final String email; private final String socialId; private final String profileImageUrl; - - @Override - public String toString() { - return "OauthResourcesResponse{" + - "nickname='" + nickname + '\'' + - ", email='" + email + '\'' + - ", socialId='" + socialId + '\'' + - ", profileImageUrl='" + profileImageUrl + '\'' + - '}'; - } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/exception/AuthenticationEmptyException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/exception/AuthenticationEmptyException.java new file mode 100644 index 00000000..86d24128 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/exception/AuthenticationEmptyException.java @@ -0,0 +1,12 @@ +package org.ktc2.cokaen.wouldyouin.auth.exception; + +import org.ktc2.cokaen.wouldyouin._common.exception.BusinessException; +import org.ktc2.cokaen.wouldyouin._common.exception.ErrorCode; + +public class AuthenticationEmptyException extends BusinessException { + + public AuthenticationEmptyException(String message) { + super(message, ErrorCode.AUTHENTICATION_EMPTY); + } + +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/exception/FailAccessTokenGetException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/exception/FailAccessTokenGetException.java new file mode 100644 index 00000000..480a7a4d --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/exception/FailAccessTokenGetException.java @@ -0,0 +1,12 @@ +package org.ktc2.cokaen.wouldyouin.auth.exception; + +import org.ktc2.cokaen.wouldyouin._common.exception.BusinessException; +import org.ktc2.cokaen.wouldyouin._common.exception.ErrorCode; + +public class FailAccessTokenGetException extends BusinessException { + + public FailAccessTokenGetException(String message) { + super(message, ErrorCode.FAIL_ACCESS_TOKEN_GET); + } + +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/exception/FailSocialDataGetException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/exception/FailSocialDataGetException.java new file mode 100644 index 00000000..a583984c --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/exception/FailSocialDataGetException.java @@ -0,0 +1,12 @@ +package org.ktc2.cokaen.wouldyouin.auth.exception; + +import org.ktc2.cokaen.wouldyouin._common.exception.BusinessException; +import org.ktc2.cokaen.wouldyouin._common.exception.ErrorCode; + +public class FailSocialDataGetException extends BusinessException { + + public FailSocialDataGetException(String message) { + super(message, ErrorCode.FAIL_SOCIAL_DATA_GET); + } + +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/exception/InvalidAuthorizationHeaderException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/exception/InvalidAuthorizationHeaderException.java new file mode 100644 index 00000000..f76717ae --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/exception/InvalidAuthorizationHeaderException.java @@ -0,0 +1,12 @@ +package org.ktc2.cokaen.wouldyouin.auth.exception; + +import org.ktc2.cokaen.wouldyouin._common.exception.BusinessException; +import org.ktc2.cokaen.wouldyouin._common.exception.ErrorCode; + +public class InvalidAuthorizationHeaderException extends BusinessException { + + public InvalidAuthorizationHeaderException(String message) { + super(message, ErrorCode.INVALID_AUTHORIZATION_HEADER); + } + +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/exception/JwtTokenException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/exception/JwtTokenException.java new file mode 100644 index 00000000..7e5ccf28 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/exception/JwtTokenException.java @@ -0,0 +1,11 @@ +package org.ktc2.cokaen.wouldyouin.auth.exception; + +import org.ktc2.cokaen.wouldyouin._common.exception.BusinessException; +import org.ktc2.cokaen.wouldyouin._common.exception.ErrorCode; + +public class JwtTokenException extends BusinessException { + + public JwtTokenException(String message) { + super(message, ErrorCode.JWT_TOKEN_EXCEPTION); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/persist/CustomUserDetails.java b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/persist/CustomUserDetails.java index 52470d9b..c3f22cd1 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/auth/persist/CustomUserDetails.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/auth/persist/CustomUserDetails.java @@ -4,8 +4,10 @@ import java.util.Collection; import java.util.List; import java.util.stream.Collectors; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; @@ -13,6 +15,8 @@ @Getter @RequiredArgsConstructor +@EqualsAndHashCode +@ToString public class CustomUserDetails implements UserDetails { private final MemberIdentifier identifier; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/CurationController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/CurationController.java index f411d948..b31035fa 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/CurationController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/CurationController.java @@ -4,7 +4,7 @@ import lombok.RequiredArgsConstructor; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponse; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponseBody; -import org.ktc2.cokaen.wouldyouin._common.config.ParamDefaults; +import org.ktc2.cokaen.wouldyouin._common.api.ParamDefaults; import org.ktc2.cokaen.wouldyouin._common.vo.Area; import org.ktc2.cokaen.wouldyouin.auth.Authorize; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; @@ -64,23 +64,23 @@ public ResponseEntity> getCurationByCurationId @PostMapping public ResponseEntity> createCuration( @Valid @RequestBody CurationCreateRequest curationCreateRequest, - @Authorize(MemberType.curator) MemberIdentifier curator) { - return ApiResponse.created(curationService.create(curator.id(), curationCreateRequest)); + @Authorize(MemberType.curator) MemberIdentifier identifier) { + return ApiResponse.created(curationService.create(identifier, curationCreateRequest)); } @PutMapping("/{curationId}") public ResponseEntity> updateCuration( @PathVariable Long curationId, @Valid @RequestBody CurationEditRequest curationEditRequest, - @Authorize(MemberType.curator) MemberIdentifier curator) { - return ApiResponse.ok(curationService.update(curator.id(), curationId, curationEditRequest)); + @Authorize(MemberType.curator) MemberIdentifier identifier) { + return ApiResponse.ok(curationService.update(identifier, curationId, curationEditRequest)); } @DeleteMapping("/{curationId}") public ResponseEntity> deleteCuration( @PathVariable Long curationId, - @Authorize(MemberType.curator) MemberIdentifier curator) { - curationService.delete(curator.id(), curationId); + @Authorize(MemberType.curator) MemberIdentifier identifier) { + curationService.delete(identifier, curationId); return ApiResponse.noContent(); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationCardRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationCardRequest.java index 80946d11..960fa524 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationCardRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationCardRequest.java @@ -8,12 +8,14 @@ import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; -import org.ktc2.cokaen.wouldyouin.Image.persist.CurationImage; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.curation.persist.CurationCard; +import org.ktc2.cokaen.wouldyouin.image.persist.CurationCardImage; @Getter @Builder(toBuilder = true) @EqualsAndHashCode +@ToString public class CurationCardRequest { @NotEmpty(message = "부제목은 필수입니다.") @@ -30,7 +32,7 @@ public boolean isImageSizeValid() { return imageIds == null || imageIds.size() <= 5; } - public CurationCard toEntity(List images) { + public CurationCard toEntity(List images) { return CurationCard.builder() .subtitle(this.subtitle) .content(this.content) diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationCardResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationCardResponse.java index 5dc27bb8..748631b4 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationCardResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationCardResponse.java @@ -2,25 +2,26 @@ import java.util.List; import lombok.Builder; -import org.ktc2.cokaen.wouldyouin.Image.persist.CurationImage; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.curation.persist.CurationCard; @Builder +@EqualsAndHashCode +@ToString +@Getter public class CurationCardResponse { private String subtitle; private String content; private List imageUrls; - public static CurationCardResponse from(CurationCard curationCard) { + public static CurationCardResponse from(CurationCard curationCard, List imageUrls) { return CurationCardResponse.builder() .subtitle(curationCard.getSubtitle()) .content(curationCard.getContent()) - .imageUrls( - curationCard.getCurationImages().stream() - .map(CurationImage::getUrl) - .toList() - ) + .imageUrls(imageUrls) .build(); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationCreateRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationCreateRequest.java index 4e584fa6..c21bdb79 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationCreateRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationCreateRequest.java @@ -2,12 +2,15 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import java.util.List; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.vo.Area; import org.ktc2.cokaen.wouldyouin.curation.persist.Curation; import org.ktc2.cokaen.wouldyouin.curation.persist.CurationCard; @@ -17,11 +20,14 @@ @Getter @Builder(toBuilder = true) @EqualsAndHashCode +@ToString public class CurationCreateRequest { @NotEmpty(message = "제목은 필수입니다.") private String title; + @NotBlank(message = "내용은 필수입니다.") + @Size(min = 20, max = 1000, message = "내용은 20자 이상 1000자 이하입니다.") private String content; @Valid @@ -30,27 +36,26 @@ public class CurationCreateRequest { @NotNull(message = "지역은 필수입니다.") private Area area; - private List hashTag; + private List hashtags; private List eventIds; @AssertTrue(message = "큐레이션 카드의 개수는 1개 이상 10개 이하이어야 합니다.") - public boolean isCurationCardsSizeValid() { - if (this.curationCards == null) { - return false; - } - return 1 <= this.curationCards.size() && this.curationCards.size() <= 10; + private boolean isCurationCardsSizeValid() { + return curationCards != null && 1 <= this.curationCards.size() && this.curationCards.size() <= 10; } - public Curation toEntity(Curator curator, List curationCards, List events) { + public Curation toEntity(Curator curator, List curationCards, + List events, String thumbnailUrl) { return Curation.builder() .curator(curator) .title(this.title) .content(this.content) .curationCards(curationCards) .area(this.area) - .hashTag(this.hashTag) + .hashtags(this.hashtags) .events(events) + .thumbnailUrl(thumbnailUrl) .build(); } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationEditRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationEditRequest.java index 2a691007..6b405317 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationEditRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationEditRequest.java @@ -2,22 +2,28 @@ import jakarta.validation.Valid; import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.NotBlank; import jakarta.validation.constraints.NotEmpty; import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; import java.util.List; import lombok.Builder; import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.vo.Area; @Getter @Builder(toBuilder = true) @EqualsAndHashCode +@ToString public class CurationEditRequest { @NotEmpty(message = "제목은 필수입니다.") private String title; + @NotBlank(message = "내용은 필수입니다.") + @Size(min = 20, max = 1000, message = "내용은 20자 이상 1000자 이하입니다.") private String content; @Valid @@ -26,7 +32,7 @@ public class CurationEditRequest { @NotNull(message = "지역은 필수입니다.") private Area area; - private List hashTag; + private List hashtags; private List eventIds; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationResponse.java index 65489c61..954a8f72 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationResponse.java @@ -3,14 +3,18 @@ import java.time.LocalDateTime; import java.util.List; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.vo.Area; import org.ktc2.cokaen.wouldyouin.curation.persist.Curation; import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.CurationEventResponse; -import org.ktc2.cokaen.wouldyouin.member.application.dto.relationResponse.CurationCuratorResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse.CurationCuratorResponse; @Builder @Getter +@EqualsAndHashCode +@ToString public class CurationResponse { private final Long id; @@ -19,23 +23,24 @@ public class CurationResponse { private final String content; private final List curationCards; private final Area area; - private final List hashTag; + private final List hashtags; private final List eventsInfo; - private LocalDateTime modifiedDate; + private final String thumbnailUrl; private final LocalDateTime createdTime; + private final LocalDateTime modifiedDate; - public static CurationResponse from(Curation curation) { + public static CurationResponse from(Curation curation, List curationCards) { return CurationResponse.builder() .id(curation.getId()) .curator(CurationCuratorResponse.from(curation.getCurator())) .title(curation.getTitle()) .content(curation.getContent()) - .curationCards(curation.getCurationCards().stream() - .map(CurationCardResponse::from).toList()) + .curationCards(curationCards) .area(curation.getArea()) - .hashTag(curation.getHashTag()) + .hashtags(curation.getHashtags()) .eventsInfo(curation.getEvents().stream() .map(CurationEventResponse::from).toList()) + .thumbnailUrl(curation.getThumbnailUrl()) .createdTime(curation.getCreatedDate()) .modifiedDate(curation.getModifiedDate()) .build(); diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationSliceResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationSliceResponse.java index 1e426d05..f8787cf5 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationSliceResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/CurationSliceResponse.java @@ -2,21 +2,25 @@ import java.util.List; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.api.SliceInfo; @Getter @Builder +@EqualsAndHashCode +@ToString public class CurationSliceResponse { private final List curations; - private final SliceInfo slice; + private final SliceInfo sliceInfo; - public static CurationSliceResponse of(List curationResponses, int sliceSize, long lastId) { + public static CurationSliceResponse from(List curations, int size, Long lastId) { return CurationSliceResponse.builder() - .curations(curationResponses) - .slice(SliceInfo.builder() - .sliceSize(sliceSize) + .curations(curations) + .sliceInfo(SliceInfo.builder() + .sliceSize(size) .lastId(lastId) .build()) .build(); diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/LocationFilter.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/LocationFilter.java deleted file mode 100644 index aec28478..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/api/dto/LocationFilter.java +++ /dev/null @@ -1,18 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.curation.api.dto; - -import lombok.AllArgsConstructor; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; - -@Getter -@Setter -@NoArgsConstructor -@AllArgsConstructor -public class LocationFilter { - - private Double startLatitude = -90.0; - private Double startLongitude = -180.0; - private Double endLatitude = 90.0; - private Double endLongitude = 180.0; -} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/application/CurationCardService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/application/CurationCardService.java index de13ad0e..3817177f 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/application/CurationCardService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/application/CurationCardService.java @@ -1,17 +1,16 @@ package org.ktc2.cokaen.wouldyouin.curation.application; import java.util.List; -import java.util.Optional; import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin.Image.application.CurationImageService; -import org.ktc2.cokaen.wouldyouin.Image.persist.CurationImage; import org.ktc2.cokaen.wouldyouin._common.exception.EntityNotFoundException; -import org.ktc2.cokaen.wouldyouin._common.exception.EntityParamIsNullException; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationCardRequest; import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationCardResponse; import org.ktc2.cokaen.wouldyouin.curation.persist.Curation; import org.ktc2.cokaen.wouldyouin.curation.persist.CurationCard; import org.ktc2.cokaen.wouldyouin.curation.persist.CurationCardRepository; +import org.ktc2.cokaen.wouldyouin.image.application.CurationImageService; +import org.ktc2.cokaen.wouldyouin.image.persist.CurationCardImage; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -24,40 +23,38 @@ public class CurationCardService { @Transactional(readOnly = true) public CurationCardResponse getById(Long id) { - return CurationCardResponse.from( - curationCardRepository.findById(id).orElseThrow(() -> new EntityNotFoundException("CurationCard")) - ); - } - - @Transactional(readOnly = true) - public List getByCurationId(Curation curation) { - Optional.ofNullable(curation).orElseThrow(() -> new EntityParamIsNullException("Curation")); - return curationCardRepository.findAllByCurationOrderByIdAsc(curation).stream() - .map(CurationCardResponse::from).toList(); - } - - @Transactional - public void setCuration(CurationCard curationCard, Curation curation) { - Optional.ofNullable(curationCard).orElseThrow(() -> new EntityParamIsNullException("CurationCard")); - Optional.ofNullable(curation).orElseThrow(() -> new EntityParamIsNullException("Curation")); - curationCard.setCuration(curation); + CurationCard curationCard = getByIdOrThrow(id); + return CurationCardResponse.from(curationCard, getImageUrls(curationCard)); } @Transactional public CurationCard create(CurationCardRequest request) { - List images = request.getImageIds().stream() - .map(curationImageService::getById) - .toList(); + List images = request.getImageIds().stream() + .map(curationImageService::getById).toList(); CurationCard curationCard = curationCardRepository.save(request.toEntity(images)); images.forEach(image -> curationImageService.setCuration(image, curationCard)); return curationCard; } @Transactional - public void delete(Long id) { - CurationCard target = curationCardRepository.findById(id) - .orElseThrow(() -> new EntityNotFoundException("CurationCard")); - target.getCurationImages().forEach(image -> curationImageService.deleteAndDelete(image.getId())); + public void delete(MemberIdentifier identifier, Long id) { + CurationCard target = getByIdOrThrow(id); + target.getCurationCardImages().forEach(image -> curationImageService.deleteImage(identifier, image.getId())); curationCardRepository.deleteById(id); } + + @Transactional + public void setCuration(CurationCard curationCard, Curation curation) { + curationCard.setCuration(curation); + } + + private CurationCard getByIdOrThrow(Long id) { + return curationCardRepository.findById(id).orElseThrow(() -> new EntityNotFoundException("해당하는 큐레이션 카드를 찾을 수 없습니다.")); + } + + private List getImageUrls(CurationCard curationCard) { + return curationCard.getCurationCardImages().stream() + .map(curationImageService::getImageUrl) + .toList(); + } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/application/CurationService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/application/CurationService.java index 162b1cf9..9d35dff6 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/application/CurationService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/application/CurationService.java @@ -1,10 +1,13 @@ package org.ktc2.cokaen.wouldyouin.curation.application; import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; import org.ktc2.cokaen.wouldyouin._common.exception.EntityNotFoundException; import org.ktc2.cokaen.wouldyouin._common.exception.UnauthorizedException; import org.ktc2.cokaen.wouldyouin._common.vo.Area; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationCardResponse; import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationCreateRequest; import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationEditRequest; import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationResponse; @@ -14,8 +17,11 @@ import org.ktc2.cokaen.wouldyouin.curation.persist.CurationRepository; import org.ktc2.cokaen.wouldyouin.event.application.EventService; import org.ktc2.cokaen.wouldyouin.event.persist.Event; +import org.ktc2.cokaen.wouldyouin.image.application.CurationImageService; +import org.ktc2.cokaen.wouldyouin.image.persist.CurationCardImage; import org.ktc2.cokaen.wouldyouin.member.application.CuratorService; import org.ktc2.cokaen.wouldyouin.member.persist.Curator; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; @@ -29,81 +35,105 @@ public class CurationService { private final CuratorService curatorService; private final EventService eventService; private final CurationCardService curationCardService; - - @Transactional(readOnly = true) - public Curation getByIdOrThrow(Long id) throws EntityNotFoundException { - return curationRepository.findById(id).orElseThrow(() -> new EntityNotFoundException("Curation")); - } + private final CurationImageService curationImageService; @Transactional(readOnly = true) public CurationResponse getById(Long curationId) { - return CurationResponse.from(getByIdOrThrow(curationId)); + Curation curation = getByIdOrThrow(curationId); + return CurationResponse.from(curation, getCurationCardResponses(curation)); } @Transactional(readOnly = true) - public CurationSliceResponse getAllByAreaOrderByCreatedDateDesc(Area area, Pageable pageable, Long lastId) { - return getCurationSliceResponse( - curationRepository.findAllByAreaOrderByCreatedDateDesc(area, lastId, pageable), lastId); + public CurationSliceResponse getAllByAreaOrderByCreatedDateDesc(Area area, Pageable pageable, Long oldLastId) { + Slice curations = curationRepository.findAllByAreaOrderByCreatedDateDesc(area, oldLastId, pageable); + Long newLastId = getLastId(curations, oldLastId); + return CurationSliceResponse.from(getCurationResponses(curations), curations.getSize(), newLastId); } @Transactional(readOnly = true) public CurationSliceResponse getAllByCuratorIdOrderByCreatedDateDesc(Long curatorId, Pageable pageable, Long lastId) { - return getCurationSliceResponse( - curationRepository.findAllByCuratorOrderByCreatedDateDesc( - curatorService.getByIdOrThrow(curatorId), lastId, pageable), lastId); - } - - private CurationSliceResponse getCurationSliceResponse(Slice curationSlice, Long lastId) { - List curations = curationSlice.stream().map(CurationResponse::from).toList(); - if (!curationSlice.hasContent()) { - Long id = curationSlice.getContent().getLast().getId(); - return CurationSliceResponse.of(curations, curationSlice.getSize(), id); - } - return CurationSliceResponse.of(curations, curationSlice.getSize(), lastId); + Slice curations = curationRepository.findAllByCuratorOrderByCreatedDateDesc( + curatorId, lastId, pageable); + Long newLastId = getLastId(curations, lastId); + return CurationSliceResponse.from(getCurationResponses(curations), curations.getSize(), newLastId); } @Transactional - public CurationResponse create(Long curatorId, CurationCreateRequest curationCreateRequest) { - Curator curator = curatorService.getByIdOrThrow(curatorId); + public CurationResponse create(MemberIdentifier identifier, CurationCreateRequest curationCreateRequest) { + Curator curator = curatorService.getByIdOrThrow(identifier.id()); List curationCards = curationCreateRequest.getCurationCards().stream() .map(curationCardService::create) .toList(); List events = curationCreateRequest.getEventIds().stream() .map(eventService::getByIdOrThrow) .toList(); - Curation curation = curationRepository.save(curationCreateRequest.toEntity(curator, curationCards, events)); + Curation curation = curationRepository.save( + curationCreateRequest.toEntity(curator, curationCards, events, getThumbnailUrl(curationCards))); curationCards.forEach(curationCard -> curationCardService.setCuration(curationCard, curation)); - return CurationResponse.from(curation); - } - - public void validateCuratorId(Long curatorId, Curation curation) { - if (!curatorId.equals(curation.getCurator().getId())) { - throw new UnauthorizedException("큐레이터 ID가 큐레이션의 큐레이터 ID와 일치하지 않습니다."); - } + return CurationResponse.from(curation, getCurationCardResponses(curation)); } @Transactional - public CurationResponse update(Long curatorId, Long curationId, CurationEditRequest curationEditRequest) { + public CurationResponse update(MemberIdentifier identifier, Long curationId, CurationEditRequest curationEditRequest) { Curation curation = getByIdOrThrow(curationId); - validateCuratorId(curatorId, curation); + validateCuratorId(identifier, curation); List curationCards = curationEditRequest.getCurationCards().stream() - .map(curationCardService::create) - .toList(); + .map(curationCardService::create).toList(); List events = curationEditRequest.getEventIds().stream() - .map(eventService::getByIdOrThrow) - .toList(); - curation.getCurationCards().forEach(card -> curationCardService.delete(card.getId())); - curation.updateFrom(curationEditRequest, curationCards, events); + .map(eventService::getByIdOrThrow).toList(); + curation.getCurationCards().forEach(card -> curationCardService.delete(identifier, card.getId())); + curation.updateFrom(curationEditRequest, curationCards, events, getThumbnailUrl(curationCards)); curationCards.forEach(curationCard -> curationCardService.setCuration(curationCard, curation)); - return CurationResponse.from(curation); + return CurationResponse.from(curation, getCurationCardResponses(curation)); } @Transactional - public void delete(Long curatorId, Long curationId) { + public void delete(MemberIdentifier identifier, Long curationId) { Curation curation = getByIdOrThrow(curationId); - validateCuratorId(curatorId, curation); + validateCuratorId(identifier, curation); curation.getCurationCards() - .forEach(curationCard -> curationCardService.delete(curationCard.getId())); + .forEach(curationCard -> curationCardService.delete(identifier, curationCard.getId())); curationRepository.deleteById(curationId); } + + private Long getLastId(Slice curations, Long oldLastId) { + if (curations.hasContent()) { + return curations.getContent().getLast().getId(); + } + return oldLastId; + } + + private void validateCuratorId(MemberIdentifier identifier, Curation curation) { + if (!identifier.type().equals(MemberType.admin) && !identifier.id().equals(curation.getCurator().getId())) { + throw new UnauthorizedException("큐레이션에 접근할 권한이 없습니다."); + } + } + + private Curation getByIdOrThrow(Long id) throws EntityNotFoundException { + return curationRepository.findById(id).orElseThrow(() -> new EntityNotFoundException("해당하는 큐레이션을 찾을 수 없습니다.")); + } + + private List getCurationResponses(Slice curations) { + return curations.getContent().stream() + .map(curation -> CurationResponse.from(curation, getCurationCardResponses(curation))) + .toList(); + } + + private List getCurationCardResponses(Curation curation) { + return curation.getCurationCards().stream() + .map(curationCard -> CurationCardResponse.from( + curationCard, curationCard.getCurationCardImages().stream() + .map(curationImageService::getImageUrl).toList())) + .toList(); + } + + private String getThumbnailUrl(List curationCards) { + return Optional.ofNullable(curationCards) + .map(List::getFirst) + .map(CurationCard::getCurationCardImages) + .map(List::getFirst) + .map(CurationCardImage::getName) + .map(curationImageService::createThumbnail) + .orElse(""); + } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/Curation.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/Curation.java index 90462f05..dab2245f 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/Curation.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/Curation.java @@ -22,9 +22,11 @@ import java.util.Optional; import lombok.AccessLevel; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.converter.HashtagConverter; import org.ktc2.cokaen.wouldyouin._common.vo.Area; import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationEditRequest; @@ -37,6 +39,8 @@ @Getter @Setter @EntityListeners(AuditingEntityListener.class) +@EqualsAndHashCode +@ToString @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity public class Curation { @@ -44,6 +48,7 @@ public class Curation { @Id @Column(name = "curation_id") @GeneratedValue(strategy = GenerationType.IDENTITY) + @Setter(AccessLevel.NONE) private Long id; @JoinColumn(name = "curator_id") @@ -66,9 +71,9 @@ public class Curation { @Column(name = "area") private Area area; - @Column(name = "hashtag") + @Column(name = "hashtags") @Convert(converter = HashtagConverter.class) - private List hashTag = new ArrayList<>(); + private List hashtags = new ArrayList<>(); @ManyToMany(fetch = FetchType.LAZY) @JoinTable( @@ -76,7 +81,10 @@ public class Curation { joinColumns = @JoinColumn(name = "curation_id"), inverseJoinColumns = @JoinColumn(name = "event_id") ) - private List events = new ArrayList<>(); + private List events; + + @Column(name = "thumbnail_url") + private String thumbnailUrl; @CreatedDate @Column(name = "created_date") @@ -87,23 +95,27 @@ public class Curation { private LocalDateTime modifiedDate; @Builder - public Curation(Curator curator, String title, String content, List curationCards, Area area, List hashTag, - List events) { - this.curator = curator; - this.title = title; - this.content = content; - this.curationCards = curationCards; - this.area = area; - this.hashTag = hashTag; - this.events = events; + public Curation(Curator curator, String title, String content, List curationCards, + Area area, List hashtags, + List events, String thumbnailUrl) { + Optional.ofNullable(curator).ifPresent(this::setCurator); + Optional.ofNullable(title).ifPresent(this::setTitle); + Optional.ofNullable(content).ifPresent(this::setContent); + Optional.ofNullable(curationCards).ifPresent(this::setCurationCards); + Optional.ofNullable(area).ifPresent(this::setArea); + Optional.ofNullable(hashtags).ifPresent(this::setHashtags); + Optional.ofNullable(events).ifPresent(this::setEvents); + Optional.ofNullable(thumbnailUrl).ifPresent(this::setThumbnailUrl); } - public void updateFrom(CurationEditRequest curationEditRequest, List curationCards, List events) { + public void updateFrom(CurationEditRequest curationEditRequest, + List curationCards, List events, String thumbnailUrl) { Optional.ofNullable(curationEditRequest.getTitle()).ifPresent(this::setTitle); Optional.ofNullable(curationEditRequest.getContent()).ifPresent(this::setContent); Optional.ofNullable(curationEditRequest.getArea()).ifPresent(this::setArea); - Optional.ofNullable(curationEditRequest.getHashTag()).ifPresent(this::setHashTag); + Optional.ofNullable(curationEditRequest.getHashtags()).ifPresent(this::setHashtags); Optional.ofNullable(curationCards).ifPresent(this::setCurationCards); Optional.ofNullable(events).ifPresent(this::setEvents); + Optional.ofNullable(thumbnailUrl).ifPresent(this::setThumbnailUrl); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/CurationCard.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/CurationCard.java index ba78a2d7..257f8bda 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/CurationCard.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/CurationCard.java @@ -11,22 +11,28 @@ import jakarta.persistence.OneToMany; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import lombok.AccessLevel; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.ktc2.cokaen.wouldyouin.Image.persist.CurationImage; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin.image.persist.CurationCardImage; @Getter @Setter @Entity +@EqualsAndHashCode +@ToString @NoArgsConstructor(access = AccessLevel.PROTECTED) public class CurationCard { @Id @Column(name = "curation_card_id") @GeneratedValue(strategy = GenerationType.IDENTITY) + @Setter(AccessLevel.NONE) private Long id; @Column(name = "subtitle") @@ -40,13 +46,13 @@ public class CurationCard { private Curation curation; @OneToMany(mappedBy = "curationCard", fetch = FetchType.LAZY) - private List curationImages = new ArrayList<>(); + private List curationCardImages = new ArrayList<>(); @Builder - public CurationCard(String subtitle, String content, Curation curation, List images) { - this.subtitle = subtitle; - this.content = content; - this.curation = curation; - this.curationImages = images; + public CurationCard(String subtitle, String content, Curation curation, List images) { + Optional.ofNullable(subtitle).ifPresent(this::setSubtitle); + Optional.ofNullable(content).ifPresent(this::setContent); + Optional.ofNullable(curation).ifPresent(this::setCuration); + Optional.ofNullable(images).ifPresent(this::setCurationCardImages); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/CurationCardRepository.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/CurationCardRepository.java index d312e320..80f1b01d 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/CurationCardRepository.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/CurationCardRepository.java @@ -7,7 +7,7 @@ @Repository public interface CurationCardRepository extends JpaRepository { - + @Query("SELECT CC FROM CurationCard CC JOIN FETCH CC.curation WHERE CC.curation = :curation ORDER BY CC.id ASC") List findAllByCurationOrderByIdAsc(Curation curation); } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/CurationRepository.java b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/CurationRepository.java index 95ce7854..a7165267 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/CurationRepository.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/curation/persist/CurationRepository.java @@ -12,12 +12,12 @@ public interface CurationRepository extends JpaRepository { @Query("SELECT C FROM Curation C JOIN FETCH C.curator " - + "WHERE C.area = :area AND C.id > :lastId " + + "WHERE C.area = :area AND C.id < :lastId " + "ORDER BY C.id DESC") Slice findAllByAreaOrderByCreatedDateDesc(Area area, Long lastId, Pageable pageable); @Query("SELECT C FROM Curation C JOIN FETCH C.curator " - + "WHERE C.curator = :curator AND C.id > :lastId " + + "WHERE C.curator.id = :curatorId AND C.id < :lastId " + "ORDER BY C.id DESC") - Slice findAllByCuratorOrderByCreatedDateDesc(Curator curator, Long lastId, Pageable pageable); + Slice findAllByCuratorOrderByCreatedDateDesc(Long curatorId, Long lastId, Pageable pageable); } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/EventController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/EventController.java index abf24bd9..00db8492 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/EventController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/EventController.java @@ -4,17 +4,17 @@ import lombok.RequiredArgsConstructor; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponse; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponseBody; -import org.ktc2.cokaen.wouldyouin._common.config.ParamDefaults; +import org.ktc2.cokaen.wouldyouin._common.api.ParamDefaults; import org.ktc2.cokaen.wouldyouin._common.vo.Area; import org.ktc2.cokaen.wouldyouin._common.vo.Category; -import org.ktc2.cokaen.wouldyouin._common.vo.Location; import org.ktc2.cokaen.wouldyouin.auth.Authorize; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; -import org.ktc2.cokaen.wouldyouin.curation.api.dto.LocationFilter; import org.ktc2.cokaen.wouldyouin.event.api.dto.EventCreateRequest; import org.ktc2.cokaen.wouldyouin.event.api.dto.EventEditRequest; import org.ktc2.cokaen.wouldyouin.event.api.dto.EventResponse; import org.ktc2.cokaen.wouldyouin.event.api.dto.EventSliceResponse; +import org.ktc2.cokaen.wouldyouin.event.api.dto.LocationFilter; +import org.ktc2.cokaen.wouldyouin.event.api.dto.LocationRequest; import org.ktc2.cokaen.wouldyouin.event.application.EventService; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.springframework.data.domain.PageRequest; @@ -37,10 +37,10 @@ public class EventController { private final EventService eventService; - @GetMapping + @GetMapping("/filter") public ResponseEntity> getEventsByFilterOrderByDistanceAsc( - @ModelAttribute LocationFilter locationFilter, - @ModelAttribute Location currentLocation, + @Valid @ModelAttribute LocationFilter locationFilter, + @Valid @ModelAttribute LocationRequest currentLocation, @RequestParam(defaultValue = ParamDefaults.TITLE) String title, @RequestParam(defaultValue = ParamDefaults.CATEGORY) Category category, @RequestParam(defaultValue = ParamDefaults.AREA) Area area, @@ -49,7 +49,18 @@ public ResponseEntity> getEventsByFilterOrde @RequestParam(defaultValue = ParamDefaults.LAST_ID) Long lastId ) { return ApiResponse.ok(eventService.getAllByFilterOrderByDistanceAsc( - locationFilter, currentLocation, title, category, area, PageRequest.of(page, size), lastId)); + locationFilter, currentLocation, title, category, area, PageRequest.of(page, size), + lastId)); + } + + @GetMapping + public ResponseEntity> getEvents( + @RequestParam(defaultValue = ParamDefaults.PAGE) Integer page, + @RequestParam(defaultValue = ParamDefaults.PAGE_SIZE) Integer size, + @RequestParam(defaultValue = ParamDefaults.LAST_ID) Long lastId + ) { + return ApiResponse.ok(eventService.getAllByCreatedDateDesc( + PageRequest.of(page, size), lastId)); } @GetMapping("/hosts/{hostId}") @@ -72,22 +83,22 @@ public ResponseEntity> getEventByEventId( @PostMapping public ResponseEntity> createEvent( @Valid @RequestBody EventCreateRequest eventCreateRequest, - @Authorize(MemberType.host) MemberIdentifier host) { - return ApiResponse.created(eventService.create(host.id(), eventCreateRequest)); + @Authorize(MemberType.host) MemberIdentifier identifier) { + return ApiResponse.created(eventService.create(identifier, eventCreateRequest)); } @PutMapping("/{eventId}") public ResponseEntity> updateEvent(@PathVariable Long eventId, @Valid @RequestBody EventEditRequest eventEditRequest, - @Authorize(MemberType.host) MemberIdentifier host) { - return ApiResponse.ok(eventService.update(host.id(), eventId, eventEditRequest)); + @Authorize(MemberType.host) MemberIdentifier identifier) { + return ApiResponse.ok(eventService.update(identifier, eventId, eventEditRequest)); } @DeleteMapping("/{eventId}") public ResponseEntity> deleteEvent( @PathVariable("eventId") Long eventId, - @Authorize(MemberType.host) MemberIdentifier host) { - eventService.delete(host.id(), eventId); + @Authorize(MemberType.host) MemberIdentifier identifier) { + eventService.delete(identifier, eventId); return ApiResponse.noContent(); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventCreateRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventCreateRequest.java index e276a6e2..75db1e48 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventCreateRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventCreateRequest.java @@ -1,5 +1,6 @@ package org.ktc2.cokaen.wouldyouin.event.api.dto; +import jakarta.validation.Valid; import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.FutureOrPresent; import jakarta.validation.constraints.Max; @@ -10,16 +11,20 @@ import java.time.LocalDateTime; import java.util.List; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; -import org.ktc2.cokaen.wouldyouin.Image.persist.EventImage; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.vo.Area; import org.ktc2.cokaen.wouldyouin._common.vo.Category; import org.ktc2.cokaen.wouldyouin._common.vo.Location; import org.ktc2.cokaen.wouldyouin.event.persist.Event; +import org.ktc2.cokaen.wouldyouin.image.persist.EventImage; import org.ktc2.cokaen.wouldyouin.member.persist.Host; @Getter -@Builder +@Builder(toBuilder = true) +@EqualsAndHashCode +@ToString public class EventCreateRequest { @NotBlank(message = "제목은 필수입니다.") @@ -32,12 +37,15 @@ public class EventCreateRequest { @NotNull(message = "지역는 필수입니다.") private Area area; + @Valid @NotNull(message = "장소는 필수입니다.") private Location location; + @NotNull(message = "시작시간은 필수입니다.") @FutureOrPresent(message = "시작 시간은 현재 시간 이후여야 합니다.") private LocalDateTime startTime; + @NotNull(message = "종료시간은 필수입니다.") @FutureOrPresent(message = "종료 시간은 현재 시간 이후여야 합니다.") private LocalDateTime endTime; @@ -46,6 +54,7 @@ public class EventCreateRequest { @Max(value = 1000000, message = "가격은 1,000,000원 이하입니다.") private Integer price; + @NotNull(message = "좌석입력은 필수입니다.") @Min(value = 0, message = "총 좌석은 0석 이상입니다.") @Max(value = 1000, message = "총 좌석은 1,000석 이하입니다.") private Integer totalSeat; @@ -56,7 +65,7 @@ public class EventCreateRequest { private List imageIds; @AssertTrue(message = "종료 시간은 시작 시간 이후여야 합니다.") - public boolean isEndTimeAfterStartTime() { + private boolean isEndTimeAfterStartTime() { if (startTime == null || endTime == null) { return true; } @@ -64,11 +73,11 @@ public boolean isEndTimeAfterStartTime() { } @AssertTrue(message = "이미지는 최대 5개까지 등록할 수 있습니다.") - public boolean isImageSizeValid() { + private boolean isImageSizeValid() { return imageIds == null || imageIds.size() <= 5; } - public Event toEntity(Host host, List images) { + public Event toEntity(Host host, List images, String thumbnailUrl) { return Event.builder() .title(title) .content(content) @@ -81,6 +90,7 @@ public Event toEntity(Host host, List images) { .category(category) .host(host) .images(images) + .thumbnailUrl(thumbnailUrl) .build(); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventEditRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventEditRequest.java index f46afcab..ffd67f77 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventEditRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventEditRequest.java @@ -1,5 +1,6 @@ package org.ktc2.cokaen.wouldyouin.event.api.dto; +import jakarta.validation.Valid; import jakarta.validation.constraints.AssertTrue; import jakarta.validation.constraints.FutureOrPresent; import jakarta.validation.constraints.Max; @@ -10,14 +11,17 @@ import java.time.LocalDateTime; import java.util.List; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.vo.Area; import org.ktc2.cokaen.wouldyouin._common.vo.Category; import org.ktc2.cokaen.wouldyouin._common.vo.Location; -import org.ktc2.cokaen.wouldyouin.event.persist.Event; @Getter @Builder(toBuilder = true) +@EqualsAndHashCode +@ToString public class EventEditRequest { @NotBlank(message = "제목은 필수입니다.") @@ -30,12 +34,15 @@ public class EventEditRequest { @NotNull(message = "지역는 필수입니다.") private Area area; + @Valid @NotNull(message = "장소는 필수입니다.") private Location location; + @NotNull(message = "시작 시간은 필수입니다.") @FutureOrPresent(message = "시작 시간은 현재 시간 이후여야 합니다.") private LocalDateTime startTime; + @NotNull(message = "종료 시간은 필수입니다.") @FutureOrPresent(message = "종료 시간은 현재 시간 이후여야 합니다.") private LocalDateTime endTime; @@ -44,6 +51,7 @@ public class EventEditRequest { @Max(value = 1000000, message = "가격은 1,000,000원 이하입니다.") private Integer price; + @NotNull(message = "좌석은 필수입니다.") @Min(value = 0, message = "총 좌석은 0석 이상입니다.") @Max(value = 1000, message = "총 좌석은 1,000석 이하입니다.") private Integer totalSeat; @@ -54,7 +62,7 @@ public class EventEditRequest { private List imageIds; @AssertTrue(message = "종료 시간은 시작 시간 이후여야 합니다.") - public boolean isEndTimeAfterStartTime() { + private boolean isEndTimeAfterStartTime() { if (startTime == null || endTime == null) { return true; } @@ -62,21 +70,7 @@ public boolean isEndTimeAfterStartTime() { } @AssertTrue(message = "이미지는 최대 5개까지 등록할 수 있습니다.") - public boolean isImageSizeValid() { - return imageIds.size() <= 5; - } - - public Event toEntity() { - return Event.builder() - .title(title) - .content(content) - .area(area) - .location(location) - .startTime(startTime) - .endTime(endTime) - .price(price) - .totalSeat(totalSeat) - .category(category) - .build(); + private boolean isImageSizeValid() { + return imageIds == null || imageIds.size() <= 5; } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventResponse.java index 7ff20f85..a8a1dbe6 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventResponse.java @@ -1,17 +1,22 @@ package org.ktc2.cokaen.wouldyouin.event.api.dto; import java.time.LocalDateTime; +import java.util.List; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.vo.Area; import org.ktc2.cokaen.wouldyouin._common.vo.Category; import org.ktc2.cokaen.wouldyouin._common.vo.Location; import org.ktc2.cokaen.wouldyouin.event.persist.Event; -import org.ktc2.cokaen.wouldyouin.member.application.dto.relation.EventHostResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse.EventHostResponse; import org.ktc2.cokaen.wouldyouin.member.persist.Host; @Builder @Getter +@EqualsAndHashCode +@ToString public class EventResponse { private Long id; @@ -26,9 +31,11 @@ public class EventResponse { private Integer totalSeat; private Integer leftSeat; private Category category; + private String thumbnailUrl; + private List images; private Boolean expired; - public static EventResponse from(Event event) { + public static EventResponse from(Event event, List imageUrls) { Host host = event.getHost(); return EventResponse.builder() .id(event.getId()) @@ -43,6 +50,8 @@ public static EventResponse from(Event event) { .totalSeat(event.getTotalSeat()) .leftSeat(event.getLeftSeat()) .category(event.getCategory()) + .thumbnailUrl(event.getThumbnailUrl()) + .images(imageUrls) .expired(event.getStartTime().isAfter(LocalDateTime.now())) .build(); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventSliceResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventSliceResponse.java index 51889b9a..5a9d92a8 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventSliceResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/EventSliceResponse.java @@ -2,21 +2,25 @@ import java.util.List; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.api.SliceInfo; @Getter @Builder +@EqualsAndHashCode +@ToString public class EventSliceResponse { private List events; - private SliceInfo slice; + private SliceInfo sliceInfo; - public static EventSliceResponse of(List eventResponses, int sliceSize, long lastId) { + public static EventSliceResponse from(List events, int size, Long lastId) { return EventSliceResponse.builder() - .events(eventResponses) - .slice(SliceInfo.builder() - .sliceSize(sliceSize) + .events(events) + .sliceInfo(SliceInfo.builder() + .sliceSize(size) .lastId(lastId) .build()) .build(); diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/LocationFilter.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/LocationFilter.java new file mode 100644 index 00000000..2f96787b --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/LocationFilter.java @@ -0,0 +1,46 @@ +package org.ktc2.cokaen.wouldyouin.event.api.dto; + +import jakarta.validation.constraints.AssertTrue; +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode +@ToString +public class LocationFilter { + + @Min(value = -90, message = "시작 위도는 -90 이상이어야 합니다.") + @Max(value = 90, message = "시작 위도는 90 이하여야 합니다.") + private Double startLatitude = -90.0; + + @Min(value = -180, message = "시작 경도는 -180 이상이어야 합니다.") + @Max(value = 180, message = "시작 경도는 180 이하여야 합니다.") + private Double startLongitude = -180.0; + + @Min(value = -90, message = "끝 위도는 -90 이상이어야 합니다.") + @Max(value = 90, message = "끝 위도는 90 이하여야 합니다.") + private Double endLatitude = 90.0; + + @Min(value = -180, message = "끝 경도는 -180 이상이어야 합니다.") + @Max(value = 180, message = "끝 경도는 180 이하여야 합니다.") + private Double endLongitude = 180.0; + + @AssertTrue(message = "시작 위도는 끝 위도보다 작아야 합니다.") + public boolean isStartLatitudeLessThanEndLatitude() { + return startLatitude <= endLatitude; + } + + @AssertTrue(message = "시작 경도는 끝 경도보다 작아야 합니다.") + public boolean isStartLongitudeLessThanEndLongitude() { + return startLongitude <= endLongitude; + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/LocationRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/LocationRequest.java new file mode 100644 index 00000000..44ac37cc --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/LocationRequest.java @@ -0,0 +1,28 @@ +package org.ktc2.cokaen.wouldyouin.event.api.dto; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; +import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.ToString; + +@Getter +@Setter +@AllArgsConstructor +@NoArgsConstructor +@EqualsAndHashCode +@ToString +public class LocationRequest { + + @Min(value = -90, message = "위도는 -90 이상이어야 합니다.") + @Max(value = 90, message = "위도는 90 이하여야 합니다.") + private Double latitude = 37.0; + + @Min(value = -180, message = "경도는 -180 이상이어야 합니다.") + @Max(value = 180, message = "경도는 180 이하여야 합니다.") + private Double longitude = 127.1; +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/relationResonse/CurationEventResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/relationResonse/CurationEventResponse.java index 6073a499..242e1235 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/relationResonse/CurationEventResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/relationResonse/CurationEventResponse.java @@ -1,16 +1,22 @@ package org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse; import java.time.LocalDateTime; +import java.util.Optional; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.vo.Location; import org.ktc2.cokaen.wouldyouin.event.persist.Event; +import org.ktc2.cokaen.wouldyouin.member.persist.BaseMember; @Getter @Builder +@EqualsAndHashCode +@ToString public class CurationEventResponse { - private Long id; + private Long eventId; private String title; private Location location; private LocalDateTime startTime; @@ -20,12 +26,15 @@ public class CurationEventResponse { public static CurationEventResponse from(Event event) { return CurationEventResponse.builder() - .id(event.getId()) + .eventId(event.getId()) .title(event.getTitle()) .location(event.getLocation()) .startTime(event.getStartTime()) .thumbnailImageUrl(event.getThumbnailUrl()) - .hostProfileImageUrl(event.getHost().getProfileImageUrl()) + .hostProfileImageUrl(Optional.of(event) + .map(Event::getHost) + .map(BaseMember::getProfileImageUrl) + .orElse("")) .hostNickname(event.getHost().getNickname()) .build(); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/relationResonse/ReservationEventResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/relationResonse/ReservationEventResponse.java index 801b0cb3..db34ffe0 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/relationResonse/ReservationEventResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/relationResonse/ReservationEventResponse.java @@ -1,13 +1,34 @@ package org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse; +import java.time.LocalDateTime; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin._common.vo.Location; +import org.ktc2.cokaen.wouldyouin.event.persist.Event; @Getter @Builder +@EqualsAndHashCode +@ToString public class ReservationEventResponse { - private Long id; + private Long eventId; private String title; private Integer price; + private Location location; + private LocalDateTime startTime; + private String thumbnailUrl; + + public static ReservationEventResponse from(Event event) { + return ReservationEventResponse.builder() + .eventId(event.getId()) + .title(event.getTitle()) + .price(event.getPrice()) + .location(event.getLocation()) + .startTime(event.getStartTime()) + .thumbnailUrl(event.getThumbnailUrl()) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/relationResonse/ReviewEventResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/relationResonse/ReviewEventResponse.java index 9db9e214..85666b5b 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/relationResonse/ReviewEventResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/relationResonse/ReviewEventResponse.java @@ -1,13 +1,29 @@ package org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse; +import java.time.LocalDateTime; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin.event.persist.Event; @Getter @Builder +@EqualsAndHashCode +@ToString public class ReviewEventResponse { - private Long id; + private Long eventId; private String title; - private String mainImage; + private LocalDateTime startTime; + private String thumbnailUrl; + + public static ReviewEventResponse from(Event event){ + return builder() + .eventId(event.getId()) + .title(event.getTitle()) + .startTime(event.getStartTime()) + .thumbnailUrl(event.getThumbnailUrl()) + .build(); + } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/relationResonse/ReviewEventSliceResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/relationResonse/ReviewEventSliceResponse.java new file mode 100644 index 00000000..fee9e011 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/api/dto/relationResonse/ReviewEventSliceResponse.java @@ -0,0 +1,29 @@ +package org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse; + +import java.util.List; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin._common.api.SliceInfo; + +@Getter +@Builder +@EqualsAndHashCode +@ToString +public class ReviewEventSliceResponse { + + private List reviewEvents; + private SliceInfo sliceInfo; + + public static ReviewEventSliceResponse from(List reviewEvents, int size, + Long lastId) { + return ReviewEventSliceResponse.builder() + .reviewEvents(reviewEvents) + .sliceInfo(SliceInfo.builder() + .sliceSize(size) + .lastId(lastId) + .build()) + .build(); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/application/EventService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/application/EventService.java index 8a31d149..2d0c25d4 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/event/application/EventService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/application/EventService.java @@ -1,22 +1,28 @@ package org.ktc2.cokaen.wouldyouin.event.application; import java.util.List; +import java.util.Optional; import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin.Image.application.EventImageService; -import org.ktc2.cokaen.wouldyouin._common.exception.CurrentLocationEmptyException; import org.ktc2.cokaen.wouldyouin._common.exception.EntityNotFoundException; +import org.ktc2.cokaen.wouldyouin.event.exception.NoLeftSeatException; import org.ktc2.cokaen.wouldyouin._common.exception.UnauthorizedException; import org.ktc2.cokaen.wouldyouin._common.vo.Area; import org.ktc2.cokaen.wouldyouin._common.vo.Category; -import org.ktc2.cokaen.wouldyouin._common.vo.Location; -import org.ktc2.cokaen.wouldyouin.curation.api.dto.LocationFilter; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; import org.ktc2.cokaen.wouldyouin.event.api.dto.EventCreateRequest; import org.ktc2.cokaen.wouldyouin.event.api.dto.EventEditRequest; import org.ktc2.cokaen.wouldyouin.event.api.dto.EventResponse; import org.ktc2.cokaen.wouldyouin.event.api.dto.EventSliceResponse; +import org.ktc2.cokaen.wouldyouin.event.api.dto.LocationFilter; +import org.ktc2.cokaen.wouldyouin.event.api.dto.LocationRequest; import org.ktc2.cokaen.wouldyouin.event.persist.Event; import org.ktc2.cokaen.wouldyouin.event.persist.EventRepository; +import org.ktc2.cokaen.wouldyouin.image.application.EventImageService; +import org.ktc2.cokaen.wouldyouin.image.application.MemberImageService; +import org.ktc2.cokaen.wouldyouin.image.persist.EventImage; import org.ktc2.cokaen.wouldyouin.member.application.HostService; +import org.ktc2.cokaen.wouldyouin.member.persist.Host; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; @@ -29,82 +35,116 @@ public class EventService { private final EventRepository eventRepository; private final HostService hostService; private final EventImageService eventImageService; + private final MemberImageService memberImageService; - @Transactional(readOnly = true) - public EventSliceResponse getAllByFilterOrderByDistanceAsc(LocationFilter location, Location currentLocation, String title, - Category category, Area area, Pageable pageable, Long lastId) { - validateCurrentLocation(currentLocation); - return getEventSliceResponse( - eventRepository.findAllByFilterOrderByDistance(location.getStartLatitude(), location.getStartLongitude(), - location.getEndLatitude(), location.getEndLongitude(), currentLocation.getLatitude(), currentLocation.getLongitude(), - title, category, area, pageable), lastId); - } - - private void validateCurrentLocation(Location currentLocation) { - if (currentLocation.getLatitude() == null || currentLocation.getLongitude() == null) { - throw new CurrentLocationEmptyException(); - } + @Transactional + public Event getByIdOrThrow(Long id) throws EntityNotFoundException { + return eventRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("해당하는 이벤트를 찾을 수 없습니다.")); } @Transactional(readOnly = true) - public EventSliceResponse getAllByHostIdOrderByCreatedDateDesc(Long hostId, Pageable pageable, Long lastId) { - return getEventSliceResponse( - eventRepository.findAllByHostIdOrderByEventIdDesc(hostId, lastId, pageable), lastId); + public EventResponse getById(Long id) { + Event event = getByIdOrThrow(id); + return EventResponse.from(event, getImageUrl(event)); } - private EventSliceResponse getEventSliceResponse(Slice eventSlice, Long lastId) { - List events = eventSlice.stream().map(EventResponse::from).toList(); - if (eventSlice.hasContent()) { - Long id = eventSlice.getContent().getLast().getId(); - return EventSliceResponse.of(events, eventSlice.getSize(), id); - } - return EventSliceResponse.of(events, eventSlice.getSize(), lastId); + @Transactional(readOnly = true) + public EventSliceResponse getAllByFilterOrderByDistanceAsc(LocationFilter location, LocationRequest currentLocation, String title, + Category category, Area area, Pageable pageable, Long beforeLastId) { + Slice events = eventRepository.findAllByFilterOrderByDistance( + location.getStartLatitude(), location.getStartLongitude(), location.getEndLatitude(), + location.getEndLongitude(), currentLocation.getLatitude(), currentLocation.getLongitude(), + title, category, area, pageable + ); + Long newLastId = getLastId(events, beforeLastId); + List responses = events.stream().map(this::getEventResponse).toList(); + return EventSliceResponse.from(responses, events.getSize(), newLastId); } @Transactional(readOnly = true) - public EventResponse getById(Long id) { - return EventResponse.from(getByIdOrThrow(id)); + public EventSliceResponse getAllByHostIdOrderByCreatedDateDesc(Long hostId, Pageable pageable, Long beforeLastId) { + Slice events = eventRepository.findAllByHostIdOrderByEventIdDesc(hostId, beforeLastId, pageable); + Long newLastId = getLastId(events, beforeLastId); + List responses = events.stream().map(this::getEventResponse).toList(); + return EventSliceResponse.from(responses, events.getSize(), newLastId); } - public Event getByIdOrThrow(Long id) throws EntityNotFoundException { - return eventRepository.findById(id).orElseThrow(() -> new EntityNotFoundException("Event")); + @Transactional(readOnly = true) + public EventSliceResponse getAllByCreatedDateDesc(Pageable pageable, Long beforeLastId) { + Slice events = eventRepository.findAllByEventIdDesc(beforeLastId, pageable); + Long newLastId = getLastId(events, beforeLastId); + List responses = events.stream().map(this::getEventResponse).toList(); + return EventSliceResponse.from(responses, events.getSize(), newLastId); } @Transactional - public EventResponse create(Long hostId, EventCreateRequest eventCreateRequest) { - return EventResponse.from( - eventRepository.save(eventCreateRequest.toEntity( - hostService.getByIdOrThrow(hostId), - eventCreateRequest.getImageIds().stream() - .map(eventImageService::getById) - .toList()))); + public EventResponse create(MemberIdentifier identifier, EventCreateRequest eventCreateRequest) { + Host host = hostService.getByIdOrThrow(identifier.id()); + List images = eventCreateRequest.getImageIds().stream() + .map(eventImageService::getById).toList(); + Event event = eventRepository.save(eventCreateRequest.toEntity(host, images, getThumbnailUrl(images))); + images.forEach(image -> eventImageService.setEvent(image, event)); + return getEventResponse(event); } - private void validateHostId(Long hostId, Event event) { - if (!hostId.equals(event.getHost().getId())) { - throw new UnauthorizedException("호스트 ID가 행사의 호스트 ID와 일치하지 않습니다."); - } + private String getThumbnailUrl(List images) { + return Optional.ofNullable(images) + .map(List::getFirst) + .map(EventImage::getName) + .map(eventImageService::createThumbnail) + .orElse(""); } @Transactional - public EventResponse update(Long hostId, Long eventId, EventEditRequest eventEditRequest) { + public EventResponse update(MemberIdentifier identifier, Long eventId, EventEditRequest eventEditRequest) { Event event = getByIdOrThrow(eventId); - validateHostId(hostId, event); - event.updateFrom(eventEditRequest, eventEditRequest.getImageIds().stream() - .map(eventImageService::getById) - .toList()); - return EventResponse.from(event); + validateHostId(identifier, event); + event.getImages().forEach(image -> eventImageService.deleteImage(identifier, image.getId())); + List images = eventEditRequest.getImageIds().stream() + .map(eventImageService::getById).toList(); + event.updateFrom(eventEditRequest, images, getThumbnailUrl(images)); + images.forEach(image -> eventImageService.setEvent(image, event)); + return getEventResponse(event); } @Transactional - public void delete(Long hostId, Long eventId) { + public void delete(MemberIdentifier identifier, Long eventId) { Event event = getByIdOrThrow(eventId); - validateHostId(hostId, event); + validateHostId(identifier, event); + event.getImages().forEach(eventImage -> eventImageService.deleteImage(identifier, eventImage.getId())); eventRepository.deleteById(eventId); } @Transactional public void decreaseLeftSeat(Long eventId, Integer count) { - getByIdOrThrow(eventId).decreaseLeftSeat(count); + Event event = getByIdOrThrow(eventId); + if (event.getLeftSeat() < count) { + throw new NoLeftSeatException("남은 좌석이 부족합니다."); + } + event.decreaseLeftSeat(count); + } + + public static Long getLastId(Slice events, Long oldLastId) { + if (events.hasContent()) { + return events.getContent().getLast().getId(); + } + return oldLastId; + } + + public void validateHostId(MemberIdentifier identifier, Event event) { + if (!identifier.type().equals(MemberType.admin) && !identifier.id().equals(event.getHost().getId())) { + throw new UnauthorizedException("해당 이벤트에 접근할 권한이 없습니다."); + } + } + + private EventResponse getEventResponse(Event event) { + return EventResponse.from(event, getImageUrl(event)); + } + + private List getImageUrl(Event event) { + return event.getImages().stream() + .map(eventImageService::getImageUrl) + .toList(); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/exception/NoLeftSeatException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/exception/NoLeftSeatException.java new file mode 100644 index 00000000..ce133983 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/exception/NoLeftSeatException.java @@ -0,0 +1,11 @@ +package org.ktc2.cokaen.wouldyouin.event.exception; + +import org.ktc2.cokaen.wouldyouin._common.exception.BusinessException; +import org.ktc2.cokaen.wouldyouin._common.exception.ErrorCode; + +public class NoLeftSeatException extends BusinessException { + + public NoLeftSeatException(String message) { + super(message, ErrorCode.NO_LEFT_SEAT); + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/persist/Event.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/persist/Event.java index 98efe2a3..37e2301c 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/event/persist/Event.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/persist/Event.java @@ -19,23 +19,27 @@ import java.time.LocalDateTime; import java.util.ArrayList; import java.util.List; +import java.util.Optional; import lombok.AccessLevel; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.ktc2.cokaen.wouldyouin.Image.persist.EventImage; -import org.ktc2.cokaen.wouldyouin._common.exception.NoLeftSeatException; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.vo.Area; import org.ktc2.cokaen.wouldyouin._common.vo.Category; import org.ktc2.cokaen.wouldyouin._common.vo.Location; import org.ktc2.cokaen.wouldyouin.event.api.dto.EventEditRequest; +import org.ktc2.cokaen.wouldyouin.image.persist.EventImage; import org.ktc2.cokaen.wouldyouin.member.persist.Host; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @Getter @Setter +@EqualsAndHashCode +@ToString @NoArgsConstructor(access = AccessLevel.PROTECTED) @EntityListeners(AuditingEntityListener.class) @Entity @@ -43,10 +47,11 @@ public class Event { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Setter(AccessLevel.NONE) @Column(name = "event_id") private Long id; - @NotNull + @NotBlank @Column(name = "title") private String title; @@ -55,7 +60,7 @@ public class Event { private String content; @NotNull - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "host_id") private Host host; @@ -69,9 +74,11 @@ public class Event { @Column(name = "location") private Location location; + @NotNull @Column(name = "start_time") private LocalDateTime startTime; + @NotNull @Column(name = "end_time") private LocalDateTime endTime; @@ -80,10 +87,12 @@ public class Event { @Column(name = "price") private Integer price; + @NotNull @Min(1) @Column(name = "total_seat") private Integer totalSeat; + @NotNull @Min(0) @Column(name = "left_seat") private Integer leftSeat; @@ -93,51 +102,51 @@ public class Event { @Enumerated(EnumType.STRING) private Category category; - // Todo: 이미지를 사용하는 모든 엔티티 thumnail 설정 @Column(name = "thumbnail_url") private String thumbnailUrl; @OneToMany(mappedBy = "event", fetch = FetchType.LAZY) private List images = new ArrayList<>(); + @NotNull @CreatedDate @Column(name = "created_date") private LocalDateTime createdDate; @Builder protected Event(String title, String content, Host host, Area area, Location location, - LocalDateTime startTime, LocalDateTime endTime, Integer price, Integer totalSeat, Category category, List images) { - this.title = title; - this.content = content; - this.host = host; - this.area = area; - this.location = location; - this.startTime = startTime; - this.endTime = endTime; - this.price = price; - this.totalSeat = totalSeat; - this.leftSeat = totalSeat; - this.category = category; - this.images = images; + LocalDateTime startTime, LocalDateTime endTime, Integer price, Integer totalSeat, + Category category, List images, String thumbnailUrl) { + Optional.ofNullable(title).ifPresent(this::setTitle); + Optional.ofNullable(content).ifPresent(this::setContent); + Optional.ofNullable(host).ifPresent(this::setHost); + Optional.ofNullable(area).ifPresent(this::setArea); + Optional.ofNullable(location).ifPresent(this::setLocation); + Optional.ofNullable(startTime).ifPresent(this::setStartTime); + Optional.ofNullable(endTime).ifPresent(this::setEndTime); + Optional.ofNullable(price).ifPresent(this::setPrice); + Optional.ofNullable(totalSeat).ifPresent(this::setTotalSeat); + Optional.ofNullable(leftSeat).ifPresent(this::setLeftSeat); + Optional.ofNullable(category).ifPresent(this::setCategory); + Optional.ofNullable(images).ifPresent(this::setImages); + Optional.of(thumbnailUrl).ifPresent(this::setThumbnailUrl); } - public void updateFrom(EventEditRequest eventEditRequest, List images) { - this.title = eventEditRequest.getTitle(); - this.content = eventEditRequest.getContent(); - this.area = eventEditRequest.getArea(); - this.location = eventEditRequest.getLocation(); - this.startTime = eventEditRequest.getStartTime(); - this.endTime = eventEditRequest.getEndTime(); - this.price = eventEditRequest.getPrice(); - this.totalSeat = eventEditRequest.getTotalSeat(); - this.category = eventEditRequest.getCategory(); - this.images = images; + public void updateFrom(EventEditRequest eventEditRequest, List images, String thumbnailUrl) { + Optional.ofNullable(eventEditRequest.getTitle()).ifPresent(this::setTitle); + Optional.ofNullable(eventEditRequest.getContent()).ifPresent(this::setContent); + Optional.ofNullable(eventEditRequest.getArea()).ifPresent(this::setArea); + Optional.ofNullable(eventEditRequest.getLocation()).ifPresent(this::setLocation); + Optional.ofNullable(eventEditRequest.getStartTime()).ifPresent(this::setStartTime); + Optional.ofNullable(eventEditRequest.getEndTime()).ifPresent(this::setEndTime); + Optional.ofNullable(eventEditRequest.getPrice()).ifPresent(this::setPrice); + Optional.ofNullable(eventEditRequest.getTotalSeat()).ifPresent(this::setTotalSeat); + Optional.ofNullable(eventEditRequest.getCategory()).ifPresent(this::setCategory); + Optional.ofNullable(images).ifPresent(this::setImages); + Optional.ofNullable(thumbnailUrl).ifPresent(this::setThumbnailUrl); } public void decreaseLeftSeat(Integer count) { - if (this.leftSeat < count) { - throw new NoLeftSeatException(); - } this.leftSeat -= count; } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/event/persist/EventRepository.java b/src/main/java/org/ktc2/cokaen/wouldyouin/event/persist/EventRepository.java index 837f5463..2aaa6fb2 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/event/persist/EventRepository.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/event/persist/EventRepository.java @@ -12,14 +12,20 @@ public interface EventRepository extends JpaRepository { @Query("SELECT E FROM Event E JOIN FETCH E.host " - + "WHERE E.host.Id = :hostId " - + "AND E.host.Id > :lastId " + + "WHERE E.host.id = :hostId " + + "AND E.id < :lastId " + "ORDER BY E.id DESC") Slice findAllByHostIdOrderByEventIdDesc(Long hostId, Long lastId, Pageable pageable); @Query("SELECT E FROM Event E JOIN FETCH E.host " - + "WHERE (E.location.latitude between :startLatitude AND :endLatitude) " - + "AND (E.location.longitude between :startLongitude AND :endLongitude) " + + "WHERE E.id < :lastId " + + "ORDER BY E.id DESC") + Slice findAllByEventIdDesc(Long lastId, Pageable pageable); + + @Query("SELECT E FROM Event E JOIN FETCH E.host " + + "WHERE ((:currentLatitude IS NULL Or :currentLongitude IS NULL) OR " + + "((E.location.latitude between :startLatitude AND :endLatitude) " + + "AND (E.location.longitude between :startLongitude AND :endLongitude))) " + "AND (E.title LIKE %:title%) " + "AND (:category = '전체' OR E.category = :category) " + "AND (:area = '전체' OR E.area = :area) " @@ -27,5 +33,6 @@ public interface EventRepository extends JpaRepository { + "(:currentLongitude - E.location.longitude) * (:currentLongitude - E.location.longitude) ASC") Slice findAllByFilterOrderByDistance( Double startLatitude, Double startLongitude, Double endLatitude, Double endLongitude, - Double currentLatitude, Double currentLongitude, String title, Category category, Area area, Pageable pageable); + Double currentLatitude, Double currentLongitude, String title, Category category, Area area, + Pageable pageable); } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/api/ImageController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/api/ImageController.java similarity index 52% rename from src/main/java/org/ktc2/cokaen/wouldyouin/Image/api/ImageController.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/image/api/ImageController.java index cc4fa869..3c4b4f79 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/api/ImageController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/api/ImageController.java @@ -1,18 +1,15 @@ -package org.ktc2.cokaen.wouldyouin.Image.api; +package org.ktc2.cokaen.wouldyouin.image.api; -import java.io.IOException; -import java.nio.file.Files; import java.nio.file.Paths; import java.util.List; import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin.Image.api.dto.ImageResponse; -import org.ktc2.cokaen.wouldyouin.Image.application.ImageServiceFactory; -import org.ktc2.cokaen.wouldyouin.Image.application.ImageStorage; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponse; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponseBody; -import org.ktc2.cokaen.wouldyouin._common.exception.FailToReadImageException; import org.ktc2.cokaen.wouldyouin.auth.Authorize; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.image.api.dto.ImageResponse; +import org.ktc2.cokaen.wouldyouin.image.application.ImageServiceFactory; +import org.ktc2.cokaen.wouldyouin.image.application.ImageStorageService; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; @@ -28,34 +25,36 @@ @RestController @RequiredArgsConstructor -@RequestMapping("/api") +@RequestMapping("/api/images") public class ImageController { private final ImageServiceFactory imageServiceFactory; + private final ImageStorageService imageStorageService; - // Todo: authorize - @PostMapping("/images") + @GetMapping(value = "/{directory}/{file}", produces = {MediaType.IMAGE_PNG_VALUE, MediaType.IMAGE_JPEG_VALUE}) + public ResponseEntity getImage(@PathVariable String directory, @PathVariable String file) { + return ResponseEntity.status(HttpStatus.OK).body(imageStorageService.readFromDirectory(Paths.get(directory, file))); + } + + @GetMapping(value = "/{directory}/{thumbnail}/{file}", produces = {MediaType.IMAGE_PNG_VALUE, MediaType.IMAGE_JPEG_VALUE}) + public ResponseEntity getThumnailImage(@PathVariable String directory, @PathVariable String thumbnail, + @PathVariable String file) { + return ResponseEntity.status(HttpStatus.OK).body(imageStorageService.readFromDirectory(Paths.get(directory, thumbnail, file))); + } + + @PostMapping public ResponseEntity>> uploadImages( @RequestParam List images, @RequestParam(value = "type") ImageDomain imageDomain) { - return ApiResponse.ok(imageServiceFactory.getImageServiceByImageType(imageDomain).saveAndCreateImages(images)); + return ApiResponse.ok(imageServiceFactory.getImageService(imageDomain).saveImages(images)); } - @GetMapping(value = "{path}", produces = {MediaType.IMAGE_PNG_VALUE, MediaType.IMAGE_JPEG_VALUE}) - public ResponseEntity getImage(@PathVariable String path) { - try { - return ResponseEntity.status(HttpStatus.OK).body(Files.readAllBytes(Paths.get(path))); - } catch (IOException e) { - throw new FailToReadImageException(); - } - } - - // Todo: authorize - @DeleteMapping("/images/{id}") + @DeleteMapping("/{imageId}") public ResponseEntity> deleteImage( - @PathVariable Long id, - @RequestParam(value = "type") ImageDomain imageDomain) { - imageServiceFactory.getImageServiceByImageType(imageDomain).deleteAndDelete(id); + @PathVariable Long imageId, + @RequestParam(value = "type") ImageDomain imageDomain, + @Authorize({MemberType.normal, MemberType.curator, MemberType.host}) MemberIdentifier identifier) { + imageServiceFactory.getImageService(imageDomain).deleteImage(identifier, imageId); return ApiResponse.noContent(); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/api/ImageDomain.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/api/ImageDomain.java similarity index 64% rename from src/main/java/org/ktc2/cokaen/wouldyouin/Image/api/ImageDomain.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/image/api/ImageDomain.java index 4a572368..5e69f835 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/api/ImageDomain.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/api/ImageDomain.java @@ -1,4 +1,4 @@ -package org.ktc2.cokaen.wouldyouin.Image.api; +package org.ktc2.cokaen.wouldyouin.image.api; public enum ImageDomain { MEMBER, diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/api/dto/ImageRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/api/dto/ImageRequest.java similarity index 50% rename from src/main/java/org/ktc2/cokaen/wouldyouin/Image/api/dto/ImageRequest.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/image/api/dto/ImageRequest.java index 2825c2dd..c14280d2 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/api/dto/ImageRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/api/dto/ImageRequest.java @@ -1,19 +1,23 @@ -package org.ktc2.cokaen.wouldyouin.Image.api.dto; +package org.ktc2.cokaen.wouldyouin.image.api.dto; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; @Getter @Builder +@EqualsAndHashCode +@ToString public class ImageRequest { - private String url; + private String name; private Long size; private String extension; - public static ImageRequest of(String url, Long size, String extension) { + public static ImageRequest of(String name, Long size, String extension) { return ImageRequest.builder() - .url(url) + .name(name) .size(size) .extension(extension) .build(); diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/api/dto/ImageResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/api/dto/ImageResponse.java similarity index 54% rename from src/main/java/org/ktc2/cokaen/wouldyouin/Image/api/dto/ImageResponse.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/image/api/dto/ImageResponse.java index 5c219943..7a90bfd6 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/api/dto/ImageResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/api/dto/ImageResponse.java @@ -1,25 +1,30 @@ -package org.ktc2.cokaen.wouldyouin.Image.api.dto; +package org.ktc2.cokaen.wouldyouin.image.api.dto; import java.time.LocalDateTime; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; -import org.ktc2.cokaen.wouldyouin.Image.persist.Image; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin.image.persist.Image; @Getter @Builder +@EqualsAndHashCode +@ToString public class ImageResponse { private Long id; private String url; - private LocalDateTime createdDate; private Long size; + private String extension; + private LocalDateTime createdDate; - public static ImageResponse from(Image image, String domainName) { - System.out.println("도메인" + domainName); + public static ImageResponse from(Image image, String url) { return ImageResponse.builder() .id(image.getId()) - .url(domainName + image.getUrl()) + .url(url) .size(image.getSize()) + .extension(image.getExtension()) .createdDate(image.getCreatedDate()) .build(); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/AdvertisementImageService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/AdvertisementImageService.java new file mode 100644 index 00000000..69531a6d --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/AdvertisementImageService.java @@ -0,0 +1,70 @@ +package org.ktc2.cokaen.wouldyouin.image.application; + +import lombok.RequiredArgsConstructor; +import org.ktc2.cokaen.wouldyouin._common.exception.UnauthorizedException; +import org.ktc2.cokaen.wouldyouin.advertisement.persist.Advertisement; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.image.api.ImageDomain; +import org.ktc2.cokaen.wouldyouin.image.api.dto.ImageRequest; +import org.ktc2.cokaen.wouldyouin.image.persist.AdvertisementImage; +import org.ktc2.cokaen.wouldyouin.image.persist.AdvertisementImageRepository; +import org.ktc2.cokaen.wouldyouin.image.persist.ImageRepository; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +@Service +public class AdvertisementImageService extends ImageService { + + @Value("${image.upload.ad.child-path}") + private String childPath; + private final AdvertisementImageRepository adImageRepository; + + public AdvertisementImageService(ImageStorageService imageStorageService, AdvertisementImageRepository adImageRepository) { + this.imageStorageService = imageStorageService; + this.adImageRepository = adImageRepository; + } + + @Override + protected ImageRepository getImageRepository() { + return adImageRepository; + } + + @Override + protected ImageDomain getImageDomain() { + return ImageDomain.ADVERTISEMENT; + } + + @Override + protected String getChildPath() { + return childPath; + } + + @Override + protected AdvertisementImage mapToEntityFrom(ImageRequest imageRequest) { + return AdvertisementImage.builder() + .name(imageRequest.getName()) + .size(imageRequest.getSize()) + .extension(imageRequest.getExtension()) + .build(); + } + + @Override + protected void validateMemberId(MemberIdentifier identifier, AdvertisementImage image) { + if (!identifier.type().equals(MemberType.admin)) { + throw new UnauthorizedException("광고 이미지에 접근할 권한이 없습니다."); + } + } + + @Transactional + public AdvertisementImage saveImage(MultipartFile image) { + return adImageRepository.save(mapToEntityFrom(imageStorageService.saveToDirectory(image, getChildPath()))); + } + + @Transactional + public void setAdvertisement(AdvertisementImage image, Advertisement ad) { + image.setAdvertisement(ad); + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/CurationImageService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/CurationImageService.java new file mode 100644 index 00000000..ae946a1f --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/CurationImageService.java @@ -0,0 +1,64 @@ +package org.ktc2.cokaen.wouldyouin.image.application; + +import org.ktc2.cokaen.wouldyouin._common.exception.UnauthorizedException; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.curation.persist.CurationCard; +import org.ktc2.cokaen.wouldyouin.image.api.ImageDomain; +import org.ktc2.cokaen.wouldyouin.image.api.dto.ImageRequest; +import org.ktc2.cokaen.wouldyouin.image.persist.CurationCardImage; +import org.ktc2.cokaen.wouldyouin.image.persist.CurationCardImageRepository; +import org.ktc2.cokaen.wouldyouin.image.persist.ImageRepository; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class CurationImageService extends ImageService { + + @Value("${image.upload.curation.child-path}") + private String childPath; + private final CurationCardImageRepository curationCardImageRepository; + + public CurationImageService(ImageStorageService imageStorageService, CurationCardImageRepository curationCardImageRepository) { + this.imageStorageService = imageStorageService; + this.curationCardImageRepository = curationCardImageRepository; + } + + @Override + public ImageRepository getImageRepository() { + return curationCardImageRepository; + } + + @Override + protected ImageDomain getImageDomain() { + return ImageDomain.CURATION; + } + + @Override + protected String getChildPath() { + return childPath; + } + + @Override + protected CurationCardImage mapToEntityFrom(ImageRequest imageRequest) { + return CurationCardImage.builder() + .name(imageRequest.getName()) + .size(imageRequest.getSize()) + .extension(imageRequest.getExtension()) + .build(); + } + + @Override + protected void validateMemberId(MemberIdentifier identifier, CurationCardImage image) { + if (!identifier.type().equals(MemberType.admin) && + !identifier.id().equals(image.getCurationCard().getCuration().getCurator().getId())) { + throw new UnauthorizedException("해당 큐레이션 이미지에 접근할 권한이 없습니다."); + } + } + + @Transactional + public void setCuration(CurationCardImage image, CurationCard curationCard) { + image.setCurationCard(curationCard); + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/EventImageService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/EventImageService.java new file mode 100644 index 00000000..5a57e580 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/EventImageService.java @@ -0,0 +1,65 @@ +package org.ktc2.cokaen.wouldyouin.image.application; + +import lombok.RequiredArgsConstructor; +import org.ktc2.cokaen.wouldyouin._common.exception.UnauthorizedException; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.event.persist.Event; +import org.ktc2.cokaen.wouldyouin.image.api.ImageDomain; +import org.ktc2.cokaen.wouldyouin.image.api.dto.ImageRequest; +import org.ktc2.cokaen.wouldyouin.image.persist.EventImage; +import org.ktc2.cokaen.wouldyouin.image.persist.EventImageRepository; +import org.ktc2.cokaen.wouldyouin.image.persist.ImageRepository; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class EventImageService extends ImageService { + + @Value("${image.upload.event.child-path}") + private String childPath; + private final EventImageRepository eventImageRepository; + + public EventImageService(ImageStorageService imageStorageService, EventImageRepository eventImageRepository) { + this.imageStorageService = imageStorageService; + this.eventImageRepository = eventImageRepository; + } + + @Override + public ImageRepository getImageRepository() { + return eventImageRepository; + } + + @Override + protected ImageDomain getImageDomain() { + return ImageDomain.EVENT; + } + + @Override + protected String getChildPath() { + return childPath; + } + + @Override + protected EventImage mapToEntityFrom(ImageRequest imageRequest) { + return EventImage.builder() + .name(imageRequest.getName()) + .size(imageRequest.getSize()) + .extension(imageRequest.getExtension()) + .build(); + } + + @Override + protected void validateMemberId(MemberIdentifier identifier, EventImage image) { + if (!identifier.type().equals(MemberType.admin) && + !identifier.id().equals(image.getEvent().getHost().getId())) { + throw new UnauthorizedException("해당 이벤트 이미지에 접근할 권한이 없습니다."); + } + } + + @Transactional + public void setEvent(EventImage image, Event event) { + image.setEvent(event); + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/ImageService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/ImageService.java new file mode 100644 index 00000000..bee01f80 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/ImageService.java @@ -0,0 +1,75 @@ +package org.ktc2.cokaen.wouldyouin.image.application; + +import java.util.List; +import lombok.RequiredArgsConstructor; +import org.ktc2.cokaen.wouldyouin._common.exception.EntityNotFoundException; +import org.ktc2.cokaen.wouldyouin._common.util.UriUtil; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.image.api.ImageDomain; +import org.ktc2.cokaen.wouldyouin.image.api.dto.ImageRequest; +import org.ktc2.cokaen.wouldyouin.image.api.dto.ImageResponse; +import org.ktc2.cokaen.wouldyouin.image.persist.Image; +import org.ktc2.cokaen.wouldyouin.image.persist.ImageRepository; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; + +@Service +@RequiredArgsConstructor +public abstract class ImageService { + + protected ImageStorageService imageStorageService; + + @Value("${image.api-url}") + private String imageApiHeader; + + protected abstract ImageRepository getImageRepository(); + + protected abstract ImageDomain getImageDomain(); + + protected abstract String getChildPath(); + + protected abstract T mapToEntityFrom(ImageRequest imageRequest); + + protected abstract void validateMemberId(MemberIdentifier identifier, T image); + + @Transactional(readOnly = true) + public T getById(Long id) { + return getImageRepository().findById(id) + .orElseThrow(() -> new EntityNotFoundException(getImageDomain().name() + " 이미지를 찾을 수 없습니다.")); + } + + @Transactional + public List saveImages(List images) { + return images.stream() + .map(image -> create(imageStorageService.saveToDirectory(image, getChildPath()))) + .toList(); + } + + @Transactional + public void deleteImage(MemberIdentifier identifier, Long imageId) { + T image = getById(imageId); + validateMemberId(identifier, image); + delete(imageId); + imageStorageService.delete(getChildPath(), image.getName()); + } + + protected ImageResponse create(ImageRequest imageRequest) { + T image = mapToEntityFrom(imageRequest); + return ImageResponse.from(getImageRepository().save(image), getImageUrl(image)); + } + + protected void delete(Long id) { + getById(id); + getImageRepository().deleteById(id); + } + + public String createThumbnail(String fileName) { + return imageStorageService.createThumbnailImage(imageApiHeader, getChildPath(), fileName); + } + + public String getImageUrl(T image) { + return UriUtil.assembleFullUrl(imageApiHeader, getChildPath(), image.getName()); + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/ImageServiceFactory.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/ImageServiceFactory.java new file mode 100644 index 00000000..331b19f6 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/ImageServiceFactory.java @@ -0,0 +1,24 @@ +package org.ktc2.cokaen.wouldyouin.image.application; + +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; +import java.util.function.Function; +import java.util.stream.Collectors; +import org.ktc2.cokaen.wouldyouin.image.api.ImageDomain; +import org.ktc2.cokaen.wouldyouin.image.persist.Image; +import org.springframework.stereotype.Component; + +@Component +public class ImageServiceFactory { + + private final ConcurrentHashMap> imageServiceMap; + + public ImageServiceFactory(List> imageServices) { + imageServiceMap = new ConcurrentHashMap<>(imageServices.stream() + .collect(Collectors.toConcurrentMap(ImageService::getImageDomain, Function.identity()))); + } + + public ImageService getImageService(ImageDomain imageDomain) { + return imageServiceMap.get(imageDomain); + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/ImageStorageService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/ImageStorageService.java new file mode 100644 index 00000000..425adf3e --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/ImageStorageService.java @@ -0,0 +1,81 @@ +package org.ktc2.cokaen.wouldyouin.image.application; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import net.coobird.thumbnailator.Thumbnails; +import org.ktc2.cokaen.wouldyouin._common.exception.FailedToUploadImageException; +import org.ktc2.cokaen.wouldyouin._common.util.FileUtil; +import org.ktc2.cokaen.wouldyouin._common.util.RestClientUtil; +import org.ktc2.cokaen.wouldyouin._common.util.UriUtil; +import org.ktc2.cokaen.wouldyouin.image.api.dto.ImageRequest; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.HttpHeaders; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Component; +import org.springframework.web.multipart.MultipartFile; + +@Component +@RequiredArgsConstructor +public class ImageStorageService { + + @Value("${image.upload.parent-path}") + private String parentPath; + + @Value("${image.upload.thumbnail.child-path}") + private String thumbnailChildPath; + + @Value("${image.upload.thumbnail.height}") + private Integer thumbnailHeight; + + @Value("${image.upload.thumbnail.width}") + private Integer thumbnailWidth; + + private final RestClientUtil client; + + public byte[] readFromDirectory(Path childPath) { + return FileUtil.readFile(Paths.get(parentPath).resolve(childPath)); + } + + public ImageRequest saveToDirectory(MultipartFile image, String childPath) { + String extension = FileUtil.getExtension(image.getContentType()); + String fileName = FileUtil.createRandomFileName(extension); + FileUtil.saveFile(image, Path.of(parentPath, childPath, fileName)); + return ImageRequest.of(fileName, image.getSize(), extension); + } + + public ImageRequest saveToDirectory(String imageUrl, String childPath) { + ResponseEntity response = client.getResponseEntity(byte[].class, imageUrl, new HttpHeaders(), + (req, rsp) -> { + throw new FailedToUploadImageException("이미지 URL에 대한 요청을 실패하였습니다."); + } + ); + String contentType = Optional.ofNullable(response.getHeaders().getContentType()).orElseThrow( + () -> new FailedToUploadImageException("응답 헤더에 콘텐츠 타입이 없어 이미지를 가져올 수 없습니다.") + ).toString(); + byte[] body = Optional.ofNullable(response.getBody()).orElseThrow( + () -> new FailedToUploadImageException("응답 본문이 비어있어 이미지를 가져올 수 없습니다.") + ); + + String extension = FileUtil.getExtension(contentType); + String fileName = FileUtil.createRandomFileName(extension); + Path path = Path.of(parentPath, childPath, fileName); + FileUtil.saveFile(response.getBody(), path); + return ImageRequest.of(fileName, (long)body.length, extension); + } + + public String createThumbnailImage(String apiHeader, String childPath, String originFileName) { + String originImagePath = Path.of(parentPath, childPath, originFileName).toString(); + String thumbnailImagePath = Path.of(parentPath, childPath, thumbnailChildPath).toString(); + FileUtil.createThumbnail(originImagePath, originFileName, thumbnailImagePath, thumbnailWidth, thumbnailHeight); + return UriUtil.assembleFullUrl(apiHeader,childPath, thumbnailChildPath, originFileName); + } + + public void delete(String childPath, String fileName) { + FileUtil.deleteFile(Path.of(parentPath, childPath, fileName)); + FileUtil.deleteFile(Path.of(parentPath, childPath, thumbnailChildPath, fileName)); + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/MemberImageService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/MemberImageService.java new file mode 100644 index 00000000..d2656222 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/application/MemberImageService.java @@ -0,0 +1,70 @@ +package org.ktc2.cokaen.wouldyouin.image.application; + +import lombok.RequiredArgsConstructor; +import org.ktc2.cokaen.wouldyouin._common.exception.UnauthorizedException; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.image.api.ImageDomain; +import org.ktc2.cokaen.wouldyouin.image.api.dto.ImageRequest; +import org.ktc2.cokaen.wouldyouin.image.persist.ImageRepository; +import org.ktc2.cokaen.wouldyouin.image.persist.MemberImage; +import org.ktc2.cokaen.wouldyouin.image.persist.MemberImageRepository; +import org.ktc2.cokaen.wouldyouin.member.persist.BaseMember; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; +import org.springframework.beans.factory.annotation.Value; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +public class MemberImageService extends ImageService { + + @Value("${image.upload.member.child-path}") + private String childPath; + private final MemberImageRepository memberImageRepository; + + public MemberImageService(ImageStorageService imageStorageService, MemberImageRepository memberImageRepository) { + this.imageStorageService = imageStorageService; + this.memberImageRepository = memberImageRepository; + } + + @Override + public ImageRepository getImageRepository() { + return memberImageRepository; + } + + @Override + protected ImageDomain getImageDomain() { + return ImageDomain.MEMBER; + } + + @Override + protected String getChildPath() { + return childPath; + } + + @Override + protected MemberImage mapToEntityFrom(ImageRequest imageRequest) { + return MemberImage.builder() + .name(imageRequest.getName()) + .size(imageRequest.getSize()) + .extension(imageRequest.getExtension()) + .build(); + } + + @Override + protected void validateMemberId(MemberIdentifier identifier, MemberImage image) { + if (!identifier.type().equals(MemberType.admin) && + !identifier.id().equals(image.getBaseMember().getId())) { + throw new UnauthorizedException("해당 프로필 이미지에 접근할 권한이 없습니다."); + } + } + + @Transactional + public void setBaseMember(MemberImage image, BaseMember member) { + image.setBaseMember(member); + } + + public MemberImage convert(String imageUrl) { + ImageRequest imageRequest = imageStorageService.saveToDirectory(imageUrl, childPath); + return memberImageRepository.save(mapToEntityFrom(imageRequest)); + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/AdvertisementImage.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/AdvertisementImage.java similarity index 79% rename from src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/AdvertisementImage.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/AdvertisementImage.java index 2fd5c836..37d94bc2 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/AdvertisementImage.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/AdvertisementImage.java @@ -1,16 +1,22 @@ -package org.ktc2.cokaen.wouldyouin.Image.persist; +package org.ktc2.cokaen.wouldyouin.image.persist; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.advertisement.persist.Advertisement; @Entity +@Getter @Setter +@EqualsAndHashCode(callSuper = true) +@ToString @NoArgsConstructor public class AdvertisementImage extends Image { diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/AdvertisementImageRepository.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/AdvertisementImageRepository.java similarity index 75% rename from src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/AdvertisementImageRepository.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/AdvertisementImageRepository.java index 25fe1910..1bd3709b 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/AdvertisementImageRepository.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/AdvertisementImageRepository.java @@ -1,4 +1,4 @@ -package org.ktc2.cokaen.wouldyouin.Image.persist; +package org.ktc2.cokaen.wouldyouin.image.persist; import org.springframework.stereotype.Repository; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/CurationImage.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/CurationCardImage.java similarity index 58% rename from src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/CurationImage.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/CurationCardImage.java index aee7d07d..48eec470 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/CurationImage.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/CurationCardImage.java @@ -1,25 +1,31 @@ -package org.ktc2.cokaen.wouldyouin.Image.persist; +package org.ktc2.cokaen.wouldyouin.image.persist; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.curation.persist.CurationCard; @Entity +@Getter @Setter +@EqualsAndHashCode(callSuper = true) +@ToString @NoArgsConstructor -public class CurationImage extends Image { +public class CurationCardImage extends Image { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "curation_card_id") private CurationCard curationCard; @Builder - public CurationImage(String url, Long size, String extension) { - super(url, size, extension); + public CurationCardImage(String name, Long size, String extension) { + super(name, size, extension); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/CurationCardImageRepository.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/CurationCardImageRepository.java new file mode 100644 index 00000000..bbe22bd1 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/CurationCardImageRepository.java @@ -0,0 +1,8 @@ +package org.ktc2.cokaen.wouldyouin.image.persist; + +import org.springframework.stereotype.Repository; + +@Repository +public interface CurationCardImageRepository extends ImageRepository { + +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/EventImage.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/EventImage.java similarity index 60% rename from src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/EventImage.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/EventImage.java index ce8d1265..f8ffe173 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/EventImage.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/EventImage.java @@ -1,25 +1,32 @@ -package org.ktc2.cokaen.wouldyouin.Image.persist; +package org.ktc2.cokaen.wouldyouin.image.persist; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.event.persist.Event; @Entity +@Getter @Setter +@EqualsAndHashCode(callSuper = true) +@ToString @NoArgsConstructor public class EventImage extends Image { + @ToString.Exclude @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_id") private Event event; @Builder - public EventImage(String url, Long size, String extension) { - super(url, size, extension); + public EventImage(String name, Long size, String extension) { + super(name, size, extension); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/EventImageRepository.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/EventImageRepository.java similarity index 55% rename from src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/EventImageRepository.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/EventImageRepository.java index 20476445..32a562bd 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/EventImageRepository.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/EventImageRepository.java @@ -1,6 +1,5 @@ -package org.ktc2.cokaen.wouldyouin.Image.persist; +package org.ktc2.cokaen.wouldyouin.image.persist; -import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/Image.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/Image.java similarity index 71% rename from src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/Image.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/Image.java index 73fd421f..d07a94bf 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/Image.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/Image.java @@ -1,34 +1,40 @@ -package org.ktc2.cokaen.wouldyouin.Image.persist; +package org.ktc2.cokaen.wouldyouin.image.persist; import jakarta.persistence.Column; import jakarta.persistence.EntityListeners; import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.MappedSuperclass; import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.ToString; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.jpa.domain.support.AuditingEntityListener; @Getter @Setter +@EqualsAndHashCode +@ToString @NoArgsConstructor(access = AccessLevel.PROTECTED) @MappedSuperclass @EntityListeners(AuditingEntityListener.class) public abstract class Image { @Id - @GeneratedValue + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Setter(AccessLevel.NONE) @Column(name = "image_id") protected Long id; @NotNull - @Column(name = "url") - protected String url; + @Column(name = "name") + protected String name; @Column(name = "size") private Long size; @@ -40,8 +46,8 @@ public abstract class Image { @Column(name = "created_date") private LocalDateTime createdDate; - protected Image(String url, Long size, String extension) { - this.url = url; + protected Image(String name, Long size, String extension) { + this.name = name; this.size = size; this.extension = extension; } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/ImageRepository.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/ImageRepository.java similarity index 82% rename from src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/ImageRepository.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/ImageRepository.java index 94ff3576..19cbfae9 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/ImageRepository.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/ImageRepository.java @@ -1,4 +1,4 @@ -package org.ktc2.cokaen.wouldyouin.Image.persist; +package org.ktc2.cokaen.wouldyouin.image.persist; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.repository.NoRepositoryBean; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/MemberImage.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/MemberImage.java similarity index 63% rename from src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/MemberImage.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/MemberImage.java index 30158439..b780d3f1 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/MemberImage.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/MemberImage.java @@ -1,16 +1,22 @@ -package org.ktc2.cokaen.wouldyouin.Image.persist; +package org.ktc2.cokaen.wouldyouin.image.persist; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.JoinColumn; import jakarta.persistence.OneToOne; import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.member.persist.BaseMember; @Entity @Setter +@Getter +@EqualsAndHashCode(callSuper = true) +@ToString @NoArgsConstructor public class MemberImage extends Image { @@ -19,7 +25,7 @@ public class MemberImage extends Image { private BaseMember baseMember; @Builder - public MemberImage(String url, Long size, String extension) { - super(url, size, extension); + public MemberImage(String name, Long size, String extension) { + super(name, size, extension); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/MemberImageRepository.java b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/MemberImageRepository.java similarity index 56% rename from src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/MemberImageRepository.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/MemberImageRepository.java index a0af1261..38d393de 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/Image/persist/MemberImageRepository.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/image/persist/MemberImageRepository.java @@ -1,6 +1,5 @@ -package org.ktc2.cokaen.wouldyouin.Image.persist; +package org.ktc2.cokaen.wouldyouin.image.persist; -import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.stereotype.Repository; @Repository diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/like/api/LikeController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/like/api/LikeController.java index 02a434bc..9a2d52f4 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/like/api/LikeController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/like/api/LikeController.java @@ -1,17 +1,17 @@ package org.ktc2.cokaen.wouldyouin.like.api; -import java.util.List; import lombok.RequiredArgsConstructor; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponse; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponseBody; +import org.ktc2.cokaen.wouldyouin._common.api.ParamDefaults; import org.ktc2.cokaen.wouldyouin.auth.Authorize; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; -import org.ktc2.cokaen.wouldyouin.like.application.LikeResponse; +import org.ktc2.cokaen.wouldyouin.like.api.dto.LikeSliceResponse; +import org.ktc2.cokaen.wouldyouin.like.api.dto.LikeToggleResponse; import org.ktc2.cokaen.wouldyouin.like.application.LikeServiceFactory; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; -import org.springframework.http.HttpStatus; +import org.springframework.data.domain.PageRequest; 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.PathVariable; import org.springframework.web.bind.annotation.PostMapping; @@ -26,22 +26,30 @@ public class LikeController { private final LikeServiceFactory likeServiceFactory; - // Todo: 와일드카드 수정 - // Todo: api 요청시 create, delete를 분리하지말고 토글방식으로 하면 어떨지 - @GetMapping - public ResponseEntity>> getLikes(@Authorize(MemberType.normal) MemberIdentifier identifier, @RequestParam("type") MemberType memberType) { - return ApiResponse.ok(likeServiceFactory.getLikeServiceFrom(memberType).getLikes(identifier.id())); + public ResponseEntity> getLikes( + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier identifier, + @RequestParam("type") MemberType memberType, + @RequestParam(defaultValue = ParamDefaults.PAGE) Integer page, + @RequestParam(defaultValue = ParamDefaults.PAGE_SIZE) Integer size, + @RequestParam(defaultValue = ParamDefaults.LAST_ID) Long lastId + ) { + return ApiResponse.ok( + likeServiceFactory + .getLikeServiceFrom(memberType) + .getLikes(identifier, PageRequest.of(page, size), lastId)); } @PostMapping("/{targetMemberId}") - public ResponseEntity> createLike(@Authorize(MemberType.normal) MemberIdentifier identifier, @PathVariable("targetMemberId") Long targetId) { - return ApiResponse.created(likeServiceFactory.getLikeServiceFrom(targetId).create(identifier.id(), targetId)); - } - - @DeleteMapping("/{targetMemberId}") - public ResponseEntity> deleteLike(@Authorize(MemberType.normal) MemberIdentifier identifier, @PathVariable("targetMemberId") Long targetId) { - likeServiceFactory.getLikeServiceFrom(targetId).delete(identifier.id(), targetId); - return ApiResponse.noContent(); + public ResponseEntity> createOrDeleteLike( + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier identifier, + @RequestParam("type") MemberType memberType, + @PathVariable("targetMemberId") Long targetId) { + return ApiResponse.created( + likeServiceFactory.getLikeServiceFrom(memberType) + .toggleLike(identifier, targetId)); } } + +// TODO: Like service에 Identifier 넘겨주기 +// TODO: Review service에 Identifier 넘겨주기 \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/like/application/LikeResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/like/api/dto/LikeResponse.java similarity index 81% rename from src/main/java/org/ktc2/cokaen/wouldyouin/like/application/LikeResponse.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/like/api/dto/LikeResponse.java index c4150bb6..fb79c426 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/like/application/LikeResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/like/api/dto/LikeResponse.java @@ -1,14 +1,18 @@ -package org.ktc2.cokaen.wouldyouin.like.application; +package org.ktc2.cokaen.wouldyouin.like.api.dto; import java.util.List; import lombok.AccessLevel; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.member.persist.LikeableMember; @Getter @RequiredArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode +@ToString @Builder public class LikeResponse { @@ -23,7 +27,7 @@ public static LikeResponse from(LikeableMember member) { .memberId(member.getId()) .nickname(member.getNickname()) .intro(member.getIntro()) - .hashtags(member.getHashTagList()) + .hashtags(member.getHashtags()) .profileImageUrl(member.getProfileImageUrl()) .build(); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/like/api/dto/LikeSliceResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/like/api/dto/LikeSliceResponse.java new file mode 100644 index 00000000..170ab9a2 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/like/api/dto/LikeSliceResponse.java @@ -0,0 +1,28 @@ +package org.ktc2.cokaen.wouldyouin.like.api.dto; + +import java.util.List; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin._common.api.SliceInfo; + +@Getter +@Builder +@EqualsAndHashCode +@ToString +public class LikeSliceResponse { + + private List likes; + private SliceInfo sliceInfo; + + public static LikeSliceResponse from(List likes, int size, Long lastId) { + return LikeSliceResponse.builder() + .likes(likes) + .sliceInfo(SliceInfo.builder() + .sliceSize(size) + .lastId(lastId) + .build()) + .build(); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/like/api/dto/LikeToggleResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/like/api/dto/LikeToggleResponse.java new file mode 100644 index 00000000..d12d9568 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/like/api/dto/LikeToggleResponse.java @@ -0,0 +1,23 @@ +package org.ktc2.cokaen.wouldyouin.like.api.dto; + +import lombok.AccessLevel; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PROTECTED) +@EqualsAndHashCode +@ToString +@Builder +public class LikeToggleResponse { + private final boolean isLiked; + + public static LikeToggleResponse from(boolean state) { + return LikeToggleResponse.builder() + .isLiked(state) + .build(); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/like/application/LikeService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/like/application/LikeService.java index 01e2b32d..49e6d043 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/like/application/LikeService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/like/application/LikeService.java @@ -2,6 +2,10 @@ import java.util.List; import lombok.RequiredArgsConstructor; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.like.api.dto.LikeResponse; +import org.ktc2.cokaen.wouldyouin.like.api.dto.LikeSliceResponse; +import org.ktc2.cokaen.wouldyouin.like.api.dto.LikeToggleResponse; import org.ktc2.cokaen.wouldyouin.like.persist.Like; import org.ktc2.cokaen.wouldyouin.like.persist.LikeRepository; import org.ktc2.cokaen.wouldyouin.member.application.LikeableMemberGetterFactory; @@ -9,6 +13,8 @@ import org.ktc2.cokaen.wouldyouin.member.persist.LikeableMember; import org.ktc2.cokaen.wouldyouin.member.persist.Member; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,44 +26,48 @@ public abstract class LikeService getLikeRepository(); + protected abstract LikeType toEntity(Member member, LikeableMember targetLikableMember); + public abstract MemberType getTargetLikeableMemberType(); @Transactional(readOnly = true) - public List getLikes(Long memberId) { - return getLikeRepository().findAllByMember(memberService.getByIdOrThrow(memberId)) - .stream() - .map(Like::getLikeableMember) - .map(LikeResponse::from) - .toList(); - } - - @Transactional - public LikeResponse create(Long memberId, Long targetMemberId) { - Member member = memberService.getByIdOrThrow(memberId); - LikeableMember targetLikeableMember = getLikeableMemberByIdOrThrow(targetMemberId); - getLikeRepository().findByMemberAndLikeableMember(member, targetLikeableMember) - .ifPresent(x -> { throw new RuntimeException("이미 좋아요한 사용자입니다."); }); - - targetLikeableMember.increaseLikes(); - return LikeResponse.from(getLikeRepository() - .save(toEntity(member, targetLikeableMember)) - .getLikeableMember()); + public LikeSliceResponse getLikes(MemberIdentifier identifier, Pageable pageable, Long beforeLastId) { + Slice likes = getLikeRepository().findAllByMember( + memberService.getByIdOrThrow(identifier.id()), beforeLastId, pageable); + Long newLastId = getLastId(likes, beforeLastId); + List responses = likes.stream() + .map(like -> LikeResponse.from(like.getLikeableMember())).toList(); + return LikeSliceResponse.from(responses, likes.getSize(), newLastId); } @Transactional - public void delete(Long memberId, Long targetMemberId) { - Member member = memberService.getByIdOrThrow(memberId); + public LikeToggleResponse toggleLike(MemberIdentifier identifier, Long targetMemberId) { + Member member = memberService.getByIdOrThrow(identifier.id()); LikeableMember targetLikeableMember = getLikeableMemberByIdOrThrow(targetMemberId); - LikeType like = getLikeRepository().findByMemberAndLikeableMember(member, targetLikeableMember) - .orElseThrow(() -> new RuntimeException("해당 사용자를 좋아요하지 않았습니다.")); - - targetLikeableMember.decreaseLikes(); - getLikeRepository().delete(like); + return getLikeRepository().findByMemberAndLikeableMember(member, targetLikeableMember) + .map(like -> { + targetLikeableMember.decreaseLikes(); + getLikeRepository().delete(like); + return LikeToggleResponse.from(false); + }) + .orElseGet(() -> { + targetLikeableMember.increaseLikes(); + getLikeRepository().save(toEntity(member, targetLikeableMember)); + return LikeToggleResponse.from(true); + }); } @Transactional(readOnly = true) protected LikeableMember getLikeableMemberByIdOrThrow(Long likeableMemberId) { - return likeableMemberGetterFactory.get(getTargetLikeableMemberType()).getByIdOrThrow(likeableMemberId); + return likeableMemberGetterFactory.get(getTargetLikeableMemberType()) + .getByIdOrThrow(likeableMemberId); + } + + private Long getLastId(Slice likes, Long oldLastId) { + if (likes.hasContent()) { + return likes.getContent().getLast().getId(); + } + return oldLastId; } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/like/application/LikeServiceFactory.java b/src/main/java/org/ktc2/cokaen/wouldyouin/like/application/LikeServiceFactory.java index dd4bb176..85d2bf73 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/like/application/LikeServiceFactory.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/like/application/LikeServiceFactory.java @@ -6,6 +6,7 @@ import java.util.stream.Collectors; import org.ktc2.cokaen.wouldyouin.like.persist.Like; import org.ktc2.cokaen.wouldyouin.member.application.BaseMemberService; +import org.ktc2.cokaen.wouldyouin.member.exception.NotLikeableMemberException; import org.ktc2.cokaen.wouldyouin.member.persist.LikeableMember; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.springframework.beans.factory.annotation.Autowired; @@ -28,7 +29,7 @@ public LikeServiceFactory( public LikeService> getLikeServiceFrom(MemberType targetLikeableMemberType) { if (!LikeableMember.getLikeableMemberTypes().contains(targetLikeableMemberType)) { - throw new RuntimeException("해당 사용자는 좋아요할 수 없는 사용자 유형입니다."); + throw new NotLikeableMemberException("해당 사용자는 좋아요할 수 없는 사용자 유형입니다."); } return map.get(targetLikeableMemberType); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/CuratorLike.java b/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/CuratorLike.java index 7cbf9283..d3309ef9 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/CuratorLike.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/CuratorLike.java @@ -3,12 +3,16 @@ import jakarta.persistence.Entity; import lombok.AccessLevel; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.member.persist.Curator; import org.ktc2.cokaen.wouldyouin.member.persist.Member; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; @Entity +@EqualsAndHashCode(callSuper = true) +@ToString @NoArgsConstructor(access = AccessLevel.PROTECTED) public class CuratorLike extends Like{ diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/HostLike.java b/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/HostLike.java index f8e43b09..6e19a3f6 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/HostLike.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/HostLike.java @@ -3,12 +3,16 @@ import jakarta.persistence.Entity; import lombok.AccessLevel; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.member.persist.Host; import org.ktc2.cokaen.wouldyouin.member.persist.Member; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; @Entity +@EqualsAndHashCode(callSuper = true) +@ToString @NoArgsConstructor(access = AccessLevel.PROTECTED) public class HostLike extends Like { diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/Like.java b/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/Like.java index 31ae8ff1..9e1afcb4 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/Like.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/Like.java @@ -1,6 +1,9 @@ package org.ktc2.cokaen.wouldyouin.like.persist; import jakarta.persistence.Column; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -9,34 +12,40 @@ import jakarta.persistence.MappedSuperclass; import jakarta.validation.constraints.NotNull; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.member.persist.LikeableMember; import org.ktc2.cokaen.wouldyouin.member.persist.Member; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; @Getter @Setter +@EqualsAndHashCode +@ToString @MappedSuperclass @NoArgsConstructor(access = AccessLevel.PROTECTED) public abstract class Like { @Id + @Setter(AccessLevel.NONE) @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @NotNull - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn private T likeableMember; @NotNull @Column + @Enumerated(EnumType.STRING) private MemberType likeableMemberType; @NotNull - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn private Member member; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/LikeRepository.java b/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/LikeRepository.java index 3bb61ce8..2bfe6c14 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/LikeRepository.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/like/persist/LikeRepository.java @@ -1,15 +1,24 @@ package org.ktc2.cokaen.wouldyouin.like.persist; -import java.util.List; + import java.util.Optional; import org.ktc2.cokaen.wouldyouin.member.persist.LikeableMember; import org.ktc2.cokaen.wouldyouin.member.persist.Member; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.data.repository.NoRepositoryBean; @NoRepositoryBean public interface LikeRepository > extends JpaRepository { - Optional findByMemberAndLikeableMember(Member member, LikeableMember likeableMember); - List findAllByMember(Member member); + + @Query("SELECT l FROM #{#entityName} l " + + "JOIN FETCH l.member m " + + "JOIN FETCH l.likeableMember lm " + + "WHERE l.member = :member " + + "AND l.id < :lastId " + + "ORDER BY l.id DESC") + Slice findAllByMember(Member member, Long lastId, Pageable pageable); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/CuratorController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/CuratorController.java index 2500403c..86588d77 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/CuratorController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/CuratorController.java @@ -5,12 +5,11 @@ import org.ktc2.cokaen.wouldyouin._common.api.ApiResponseBody; import org.ktc2.cokaen.wouldyouin.auth.Authorize; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.member.api.dto.MemberResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.edit.CuratorEditRequest; import org.ktc2.cokaen.wouldyouin.member.application.CuratorService; -import org.ktc2.cokaen.wouldyouin.member.application.dto.MemberResponse; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.edit.CuratorEditRequest; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.springframework.http.ResponseEntity; -import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -24,12 +23,6 @@ public class CuratorController { private final CuratorService curatorService; - // 테스트: 큐레이터 생성 - @PostMapping("/test-create/{memberId}") - public ResponseEntity> testCreateCurator(@PathVariable("memberId") Long id) { - return ApiResponse.created(curatorService.createCurator(id)); - } - @PostMapping public ResponseEntity> createCurator(@Authorize(MemberType.normal) MemberIdentifier identifier) { return ApiResponse.created(curatorService.createCurator(identifier.id())); diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/HostController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/HostController.java index 17cac9d6..f375d0f6 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/HostController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/HostController.java @@ -5,9 +5,9 @@ import org.ktc2.cokaen.wouldyouin._common.api.ApiResponseBody; import org.ktc2.cokaen.wouldyouin.auth.Authorize; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.member.api.dto.MemberResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.edit.HostEditRequest; import org.ktc2.cokaen.wouldyouin.member.application.HostService; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.edit.HostEditRequest; -import org.ktc2.cokaen.wouldyouin.member.application.dto.MemberResponse; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.PutMapping; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/MemberController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/MemberController.java index 07425641..77702fcb 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/MemberController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/MemberController.java @@ -1,24 +1,19 @@ package org.ktc2.cokaen.wouldyouin.member.api; -import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponse; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponseBody; import org.ktc2.cokaen.wouldyouin.auth.Authorize; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.member.api.dto.MemberResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.edit.MemberEditRequest; import org.ktc2.cokaen.wouldyouin.member.application.BaseMemberService; -import org.ktc2.cokaen.wouldyouin.member.application.HostService; import org.ktc2.cokaen.wouldyouin.member.application.MemberService; -import org.ktc2.cokaen.wouldyouin.member.application.dto.MemberResponse; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.create.HostCreateRequest; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.create.MemberCreateRequest; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.edit.MemberEditRequest; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; 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.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -31,26 +26,6 @@ public class MemberController { private final BaseMemberService baseMemberService; private final MemberService memberService; - private final HostService hostService; - - // 테스트: 사용자 생성 - @PostMapping("/test-create/member") - public ResponseEntity> testCreateMember(@Valid @RequestBody MemberCreateRequest request) { - return ApiResponse.created(memberService.createMember(request)); - } - - // 테스트: 호스트 생성 - @PostMapping("/test-create/host") - public ResponseEntity> testCreateHost(@RequestBody HostCreateRequest hostCreateRequest) { - return ApiResponse.created(hostService.createHost(hostCreateRequest)); - } - - // 테스트: 사용자 삭제 - @DeleteMapping("/test-delete/{memberId}") - public void testDeleteMember(@PathVariable("memberId") Long id) { - // TODO: 204 NO CONTENT 반환하게 수정필요 - baseMemberService.deleteById(id); - } // 사용자 수정 @PutMapping @@ -66,8 +41,8 @@ public ResponseEntity> findMember(@PathVariable( // 사용자 삭제 @DeleteMapping - public void deleteMember(@Authorize({MemberType.normal, MemberType.host, MemberType.curator}) MemberIdentifier identifier) { - // TODO: 204 NO CONTENT 반환하게 수정필요 + public ResponseEntity> deleteMember(@Authorize({MemberType.normal, MemberType.host, MemberType.curator}) MemberIdentifier identifier) { baseMemberService.deleteById(identifier.id()); + return ApiResponse.noContent(); } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/MemberResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/MemberResponse.java similarity index 65% rename from src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/MemberResponse.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/MemberResponse.java index ed56592b..e64715b4 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/MemberResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/MemberResponse.java @@ -1,17 +1,21 @@ -package org.ktc2.cokaen.wouldyouin.member.application.dto; +package org.ktc2.cokaen.wouldyouin.member.api.dto; +import java.util.List; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.vo.Area; -import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.ktc2.cokaen.wouldyouin.member.persist.BaseMember; import org.ktc2.cokaen.wouldyouin.member.persist.Curator; +import org.ktc2.cokaen.wouldyouin.member.persist.Gender; import org.ktc2.cokaen.wouldyouin.member.persist.Host; import org.ktc2.cokaen.wouldyouin.member.persist.Member; - -import java.util.List; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; @Getter +@EqualsAndHashCode +@ToString @Builder public class MemberResponse { @@ -19,44 +23,47 @@ public class MemberResponse { private String nickname; private String phoneNumber; private String profileUrl; + private String profileThumbnailUrl; private MemberType memberType; private Area area; - private String gender; + private Gender gender; private String intro; private Integer likes; private List hashtag; - private static MemberResponseBuilder responseBase(BaseMember baseMember) { + private static MemberResponseBuilder responseBase(BaseMember baseMember, String profileUrl) { return MemberResponse.builder() .memberId(baseMember.getId()) .nickname(baseMember.getNickname()) .phoneNumber(baseMember.getPhone()) - .profileUrl(baseMember.getProfileImage().getUrl()); + .profileUrl(profileUrl) + .profileThumbnailUrl(baseMember.getProfileImageThumbnailUrl()); + } // TODO: normal member임에도 불구, curator 형식이 호출되는 현상 수정필요 - public static MemberResponse from(final Member member) { - return responseBase(member) + public static MemberResponse from(final Member member, String profileUrl) { + return responseBase(member, profileUrl) .memberType(member.getMemberType()) .area(member.getArea()) .gender(member.getGender()) .build(); } - public static MemberResponse from(final Host host) { - return responseBase(host) + public static MemberResponse from(final Host host, String profileUrl) { + return responseBase(host, profileUrl) .memberType(host.getMemberType()) .intro(host.getIntro()) .likes(host.getLikes()) - .hashtag(host.getHashTagList()) + .hashtag(host.getHashtags()) .build(); } - public static MemberResponse from(final Curator curator) { - return responseBase(curator) + public static MemberResponse from(final Curator curator, String profileUrl) { + return responseBase(curator, profileUrl) .memberType(curator.getMemberType()) .area(curator.getArea()) .gender(curator.getGender()) diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/relationResponse/CurationCuratorResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/relationResponse/CurationCuratorResponse.java similarity index 75% rename from src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/relationResponse/CurationCuratorResponse.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/relationResponse/CurationCuratorResponse.java index 2516823c..4a678b35 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/relationResponse/CurationCuratorResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/relationResponse/CurationCuratorResponse.java @@ -1,14 +1,19 @@ -package org.ktc2.cokaen.wouldyouin.member.application.dto.relationResponse; +package org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse; import java.util.List; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.member.persist.Curator; @Builder @Getter +@EqualsAndHashCode +@ToString public class CurationCuratorResponse { + private Long curatorId; private String nickname; private String email; private String phone; @@ -20,13 +25,14 @@ public class CurationCuratorResponse { public static CurationCuratorResponse from(Curator curator) { return CurationCuratorResponse.builder() + .curatorId(curator.getId()) .nickname(curator.getNickname()) .email(curator.getEmail()) .phone(curator.getPhone()) .profileImageUrl(curator.getProfileImageUrl()) .intro(curator.getIntro()) .likes(curator.getLikes()) - .hashtags(curator.getHashTagList()) + .hashtags(curator.getHashtags()) .build(); } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/relation/EventHostResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/relationResponse/EventHostResponse.java similarity index 54% rename from src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/relation/EventHostResponse.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/relationResponse/EventHostResponse.java index 2430622f..430a24f0 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/relation/EventHostResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/relationResponse/EventHostResponse.java @@ -1,14 +1,22 @@ -package org.ktc2.cokaen.wouldyouin.member.application.dto.relation; +package org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse; import java.util.List; +import java.util.Optional; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin.image.persist.Image; +import org.ktc2.cokaen.wouldyouin.member.persist.BaseMember; import org.ktc2.cokaen.wouldyouin.member.persist.Host; @Builder @Getter +@EqualsAndHashCode +@ToString public class EventHostResponse { + private Long hostId; private String nickname; private String email; private String phone; @@ -19,13 +27,18 @@ public class EventHostResponse { public static EventHostResponse from(Host host) { return EventHostResponse.builder() + .hostId(host.getId()) .nickname(host.getNickname()) .email(host.getEmail()) .phone(host.getPhone()) - .profileImageUrl(host.getProfileImage().getUrl()) + .profileImageUrl(Optional.of(host) + .map(BaseMember::getProfileImage) + .map(Image::getName) + .orElse("") + ) .intro(host.getIntro()) .likes(host.getLikes()) - .hashtags(host.getHashTagList()) + .hashtags(host.getHashtags()) .build(); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/relationResponse/ReservationMemberResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/relationResponse/ReservationMemberResponse.java new file mode 100644 index 00000000..5e495ba4 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/relationResponse/ReservationMemberResponse.java @@ -0,0 +1,31 @@ +package org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse; + +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin.member.persist.Gender; +import org.ktc2.cokaen.wouldyouin.member.persist.Member; + +@Getter +@Builder +@EqualsAndHashCode +@ToString +public class ReservationMemberResponse { + + private Long memberId; + private String email; + private String nickname; + private String phone; + private Gender gender; + + public static ReservationMemberResponse from(Member member) { + return ReservationMemberResponse.builder() + .memberId(member.getId()) + .email(member.getEmail()) + .nickname(member.getNickname()) + .phone(member.getPhone()) + .gender(member.getGender()) + .build(); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/relationResponse/ReviewMemberResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/relationResponse/ReviewMemberResponse.java new file mode 100644 index 00000000..dfdb5b92 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/relationResponse/ReviewMemberResponse.java @@ -0,0 +1,24 @@ +package org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse; + +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin.member.persist.Member; + +@Getter +@Builder +@EqualsAndHashCode +@ToString +public class ReviewMemberResponse { + + private Long memberId; + private String nickname; + + public static ReviewMemberResponse from(Member member) { + return builder() + .memberId(member.getId()) + .nickname(member.getNickname()) + .build(); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/MemberAdditionalInfoRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/MemberAdditionalInfoRequest.java new file mode 100644 index 00000000..ec1c4b17 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/MemberAdditionalInfoRequest.java @@ -0,0 +1,19 @@ +package org.ktc2.cokaen.wouldyouin.member.api.dto.request; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin._common.vo.Area; +import org.ktc2.cokaen.wouldyouin.member.persist.Gender; + +@Getter +@EqualsAndHashCode +@ToString +@RequiredArgsConstructor +public class MemberAdditionalInfoRequest { + + private String phone; + private Area area; + private Gender gender; +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/create/HostCreateRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/create/HostCreateRequest.java similarity index 72% rename from src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/create/HostCreateRequest.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/create/HostCreateRequest.java index 5233ddc3..d087ca67 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/create/HostCreateRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/create/HostCreateRequest.java @@ -1,12 +1,15 @@ -package org.ktc2.cokaen.wouldyouin.member.application.dto.request.create; +package org.ktc2.cokaen.wouldyouin.member.api.dto.request.create; -import java.util.List; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin.Image.persist.MemberImage; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin.image.persist.MemberImage; import org.ktc2.cokaen.wouldyouin.member.persist.Host; @Getter +@EqualsAndHashCode(callSuper = true) +@ToString @RequiredArgsConstructor public class HostCreateRequest extends MemberCreateRequestBase { @@ -21,7 +24,7 @@ public HostCreateRequest(String nickname, String email, String phone, String pas this.profileImageId = profileImageId; } - public Host toEntity(String hashedPassword, MemberImage profileImage) { + public Host toEntity(String hashedPassword, MemberImage profileImage, String thumbnailImageUrl) { return Host.builder() .nickname(this.nickname) @@ -29,6 +32,7 @@ public Host toEntity(String hashedPassword, MemberImage profileImage) { .email(this.email) .phone(this.phone) .hashedPassword(hashedPassword) + .profileImageThumbnailUrl(thumbnailImageUrl) .build(); } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/create/MemberCreateRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/create/MemberCreateRequest.java similarity index 70% rename from src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/create/MemberCreateRequest.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/create/MemberCreateRequest.java index f021e163..2d3c6a71 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/create/MemberCreateRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/create/MemberCreateRequest.java @@ -1,14 +1,19 @@ -package org.ktc2.cokaen.wouldyouin.member.application.dto.request.create; +package org.ktc2.cokaen.wouldyouin.member.api.dto.request.create; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin.Image.persist.MemberImage; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.vo.Area; +import org.ktc2.cokaen.wouldyouin.image.persist.MemberImage; import org.ktc2.cokaen.wouldyouin.member.persist.AccountType; +import org.ktc2.cokaen.wouldyouin.member.persist.Gender; import org.ktc2.cokaen.wouldyouin.member.persist.Member; @Getter +@EqualsAndHashCode(callSuper = true) +@ToString @RequiredArgsConstructor public class MemberCreateRequest extends MemberCreateRequestBase { @@ -24,7 +29,7 @@ protected MemberCreateRequest(String nickname, String email, AccountType account this.profileImageUrl = profileImageUrl; } - public Member toEntity(MemberImage profileImage) { + public Member toEntity(MemberImage profileImage, String thumbnailImageUrl) { return Member.builder() .nickname(this.nickname) .email(this.email) @@ -32,8 +37,9 @@ public Member toEntity(MemberImage profileImage) { .accountType(this.accountType) .socialId(this.socialId) .area(Area.서울) - .gender("") + .gender(Gender.MAN) .profileImage(profileImage) + .profileImageThumbnailUrl(thumbnailImageUrl) .build(); } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/create/MemberCreateRequestBase.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/create/MemberCreateRequestBase.java similarity index 69% rename from src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/create/MemberCreateRequestBase.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/create/MemberCreateRequestBase.java index 428664a7..7594f2ff 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/create/MemberCreateRequestBase.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/create/MemberCreateRequestBase.java @@ -1,10 +1,14 @@ -package org.ktc2.cokaen.wouldyouin.member.application.dto.request.create; +package org.ktc2.cokaen.wouldyouin.member.api.dto.request.create; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.ToString; @Getter @RequiredArgsConstructor +@EqualsAndHashCode +@ToString public abstract class MemberCreateRequestBase { protected String nickname; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/edit/CuratorEditRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/edit/CuratorEditRequest.java similarity index 77% rename from src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/edit/CuratorEditRequest.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/edit/CuratorEditRequest.java index bef7ce24..35fc76a2 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/edit/CuratorEditRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/edit/CuratorEditRequest.java @@ -1,11 +1,15 @@ -package org.ktc2.cokaen.wouldyouin.member.application.dto.request.edit; +package org.ktc2.cokaen.wouldyouin.member.api.dto.request.edit; import io.micrometer.common.lang.Nullable; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.vo.Area; @Getter +@EqualsAndHashCode(callSuper = true) +@ToString public class CuratorEditRequest extends MemberEditRequest { @Nullable private final String intro; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/edit/HostEditRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/edit/HostEditRequest.java similarity index 54% rename from src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/edit/HostEditRequest.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/edit/HostEditRequest.java index 618fe72c..3336125e 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/edit/HostEditRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/edit/HostEditRequest.java @@ -1,21 +1,25 @@ -package org.ktc2.cokaen.wouldyouin.member.application.dto.request.edit; - +package org.ktc2.cokaen.wouldyouin.member.api.dto.request.edit; import jakarta.annotation.Nullable; +import java.util.List; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; @Getter +@EqualsAndHashCode(callSuper = true) +@ToString public class HostEditRequest extends MemberEditRequestBase { @Nullable private final String intro; - @Nullable private final String hashtag; + @Nullable private final List hashtags; @Builder public HostEditRequest(@Nullable String nickname, @Nullable String phoneNumber, @Nullable Long profileImageId, - @Nullable String intro, @Nullable String hashtag) { + @Nullable String intro, @Nullable List hashtags) { super(nickname, phoneNumber, profileImageId); this.intro = intro; - this.hashtag = hashtag; + this.hashtags = hashtags; } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/edit/MemberEditRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/edit/MemberEditRequest.java similarity index 74% rename from src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/edit/MemberEditRequest.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/edit/MemberEditRequest.java index 46e591a8..d9189dcd 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/edit/MemberEditRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/edit/MemberEditRequest.java @@ -1,11 +1,15 @@ -package org.ktc2.cokaen.wouldyouin.member.application.dto.request.edit; +package org.ktc2.cokaen.wouldyouin.member.api.dto.request.edit; import jakarta.annotation.Nullable; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.vo.Area; @Getter +@EqualsAndHashCode(callSuper = true) +@ToString public class MemberEditRequest extends MemberEditRequestBase { @Nullable private final Area area; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/edit/MemberEditRequestBase.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/edit/MemberEditRequestBase.java similarity index 68% rename from src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/edit/MemberEditRequestBase.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/edit/MemberEditRequestBase.java index 45e8da30..1fbbf155 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/edit/MemberEditRequestBase.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/api/dto/request/edit/MemberEditRequestBase.java @@ -1,10 +1,14 @@ -package org.ktc2.cokaen.wouldyouin.member.application.dto.request.edit; +package org.ktc2.cokaen.wouldyouin.member.api.dto.request.edit; import jakarta.annotation.Nullable; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.RequiredArgsConstructor; +import lombok.ToString; @Getter +@EqualsAndHashCode +@ToString @RequiredArgsConstructor public abstract class MemberEditRequestBase { diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/BaseMemberService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/BaseMemberService.java index 3f11940a..ec5c7191 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/BaseMemberService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/BaseMemberService.java @@ -1,7 +1,9 @@ package org.ktc2.cokaen.wouldyouin.member.application; import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin.member.application.dto.MemberResponse; +import org.ktc2.cokaen.wouldyouin._common.exception.EntityNotFoundException; +import org.ktc2.cokaen.wouldyouin.member.api.dto.MemberResponse; +import org.ktc2.cokaen.wouldyouin.member.exception.EmailAlreadyExistsException; import org.ktc2.cokaen.wouldyouin.member.persist.BaseMember; import org.ktc2.cokaen.wouldyouin.member.persist.BaseMemberRepository; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; @@ -15,13 +17,14 @@ public class BaseMemberService { private final BaseMemberRepository baseMemberRepository; private final DerivedMemberServiceFactory derivedMemberServiceFactory; - public BaseMember getByIdOrThrow(Long id) throws RuntimeException { - // TODO: 커스텀 예외 작성필요 - return baseMemberRepository.findById(id).orElseThrow(RuntimeException::new); + public BaseMember getByIdOrThrow(Long id) throws EntityNotFoundException { + return baseMemberRepository.findById(id).orElseThrow(() -> + new EntityNotFoundException("아이디에 해당하는 사용자를 찾을 수 없습니다.") + ); } @Transactional(readOnly = true) - public MemberType getMemberType(Long id) throws RuntimeException { + public MemberType getMemberType(Long id) throws EntityNotFoundException { return getByIdOrThrow(id).getMemberType(); } @@ -32,9 +35,8 @@ public MemberResponse findById(Long id) { @Transactional(readOnly = true) public void checkUniqueEmailOrThrow(String email) { - // TODO: 커스텀 예외 추가필요 baseMemberRepository.findByEmail(email).ifPresent(p -> { - throw new RuntimeException("Email already exists"); + throw new EmailAlreadyExistsException("이미 사용중인 이메일입니다."); }); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/CuratorService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/CuratorService.java index c98a6f34..36493e07 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/CuratorService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/CuratorService.java @@ -1,10 +1,11 @@ package org.ktc2.cokaen.wouldyouin.member.application; -import java.util.Optional; import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin.Image.application.MemberImageService; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.edit.CuratorEditRequest; -import org.ktc2.cokaen.wouldyouin.member.application.dto.MemberResponse; +import org.ktc2.cokaen.wouldyouin._common.exception.EntityNotFoundException; +import org.ktc2.cokaen.wouldyouin.image.application.MemberImageService; +import org.ktc2.cokaen.wouldyouin.image.persist.MemberImage; +import org.ktc2.cokaen.wouldyouin.member.api.dto.MemberResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.edit.CuratorEditRequest; import org.ktc2.cokaen.wouldyouin.member.persist.BaseMemberRepository; import org.ktc2.cokaen.wouldyouin.member.persist.Curator; import org.ktc2.cokaen.wouldyouin.member.persist.CuratorRepository; @@ -23,10 +24,33 @@ public class CuratorService implements MemberServiceCommonBehavior, LikeableMemb private final BaseMemberRepository baseMemberRepository; private final MemberImageService memberImageService; + @Override + public LikeableMemberService getLikeableMemberService() { + return this; + } + + @Override + @Transactional(readOnly = true) + public MemberResponse getMemberResponseById(Long id) { + Curator curator = getByIdOrThrow(id); + return MemberResponse.from(curator, memberImageService.getImageUrl(curator.getProfileImage())); + } + + @Override + public MemberType getTargetMemberType() { + return MemberType.curator; + } + + @Transactional(readOnly = true) + public Curator getByIdOrThrow(Long id) { + return curatorRepository.findById(id). + orElseThrow(() -> new EntityNotFoundException("해당하는 큐레이터 정보를 찾을 수 없습니다.")); + } + @Transactional public MemberResponse createCurator(Long normalMemberId) { - //TODO: 커스텀 예외 필요 - Member member = memberRepository.findById(normalMemberId).orElseThrow(RuntimeException::new); + Member member = memberRepository.findById(normalMemberId) + .orElseThrow(() -> new EntityNotFoundException("해당하는 멤버 정보를 찾을 수 없습니다.")); // 일반 멤버 정보로 큐레이터 생성 후, 기존 일반멤버 및 BaseMember 정보는 데이터베이스에서 제거 Curator curator = Curator.curatorBuilder() @@ -35,36 +59,34 @@ public MemberResponse createCurator(Long normalMemberId) { .nickname(member.getNickname()) .phone(member.getPhone()) .profileImage(member.getProfileImage()) + .profileImageThumbnailUrl(member.getProfileImageThumbnailUrl()) .area(member.getArea()) .gender(member.getGender()) .socialId(member.getSocialId()) .build(); Long toDeleteId = member.getId(); + memberImageService.setBaseMember(member.getProfileImage(), null); memberRepository.deleteById(toDeleteId); baseMemberRepository.deleteById(toDeleteId); //삭제 후 플러시를 사용해 즉시 데이터베이스에 반영 - memberRepository.flush(); baseMemberRepository.flush(); + memberRepository.flush(); curatorRepository.save(curator); - return MemberResponse.from(curator); + memberImageService.setBaseMember(curator.getProfileImage(), curator); + return MemberResponse.from(curator, memberImageService.getImageUrl(curator.getProfileImage())); } @Transactional public MemberResponse updateCurator(Long curatorId, CuratorEditRequest request) { Curator curator = getByIdOrThrow(curatorId); - - Optional.ofNullable(request.getPhoneNumber()).ifPresent(curator::setPhone); - Optional.ofNullable(request.getNickname()).ifPresent(curator::setNickname); - Optional.ofNullable(request.getArea()).ifPresent(curator::setArea); - Optional.ofNullable(request.getIntro()).ifPresent(curator::setIntro); - Optional.ofNullable(request.getProfileImageId()) - .map(memberImageService::getById) - .ifPresent(curator::setProfileImage); - - return MemberResponse.from(curator); + MemberImage image = memberImageService.getById(request.getProfileImageId()); + String thumbnailImageUrl = memberImageService.createThumbnail(image.getName()); + curator.updateFrom(request, image, thumbnailImageUrl); + memberImageService.setBaseMember(image, curator); + return MemberResponse.from(curator, memberImageService.getImageUrl(curator.getProfileImage())); } @Override @@ -72,26 +94,4 @@ public MemberResponse updateCurator(Long curatorId, CuratorEditRequest request) public void deleteById(Long id) { curatorRepository.delete(getByIdOrThrow(id)); } - - @Override - @Transactional(readOnly = true) - public MemberResponse getMemberResponseById(Long id) { - return MemberResponse.from(getByIdOrThrow(id)); - } - - @Transactional(readOnly = true) - public Curator getByIdOrThrow(Long id) { - //TODO: 커스텀 예외 필요 - return curatorRepository.findById(id).orElseThrow(RuntimeException::new); - } - - @Override - public MemberType getTargetMemberType() { - return MemberType.curator; - } - - @Override - public LikeableMemberService getLikeableMemberService() { - return this; - } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/HostService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/HostService.java index f2cceed8..e1e32343 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/HostService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/HostService.java @@ -2,12 +2,14 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin.Image.application.MemberImageService; -import org.ktc2.cokaen.wouldyouin.Image.persist.MemberImage; -import org.ktc2.cokaen.wouldyouin.auth.application.dto.LocalLoginRequest; -import org.ktc2.cokaen.wouldyouin.member.application.dto.MemberResponse; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.create.HostCreateRequest; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.edit.HostEditRequest; +import org.ktc2.cokaen.wouldyouin._common.exception.EntityNotFoundException; +import org.ktc2.cokaen.wouldyouin.auth.api.dto.LocalLoginRequest; +import org.ktc2.cokaen.wouldyouin.image.application.MemberImageService; +import org.ktc2.cokaen.wouldyouin.image.persist.MemberImage; +import org.ktc2.cokaen.wouldyouin.member.api.dto.MemberResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.create.HostCreateRequest; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.edit.HostEditRequest; +import org.ktc2.cokaen.wouldyouin.member.exception.LoginFailedException; import org.ktc2.cokaen.wouldyouin.member.persist.Host; import org.ktc2.cokaen.wouldyouin.member.persist.HostRepository; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; @@ -27,21 +29,29 @@ public class HostService implements MemberServiceCommonBehavior, LikeableMemberS public MemberResponse createHost(HostCreateRequest request) { String hashedPassword = passwordEncoder.encode(request.getPassword()); MemberImage profileImage = memberImageService.getById(request.getProfileImageId()); - Host createdHost = hostRepository.save(request.toEntity(hashedPassword, profileImage)); + String profileImageThumbnailUrl = memberImageService.createThumbnail(profileImage.getName()); + Host createdHost = hostRepository.save(request.toEntity(hashedPassword, profileImage, profileImageThumbnailUrl)); memberImageService.setBaseMember(profileImage, createdHost); - return MemberResponse.from(createdHost); + return MemberResponse.from(createdHost, memberImageService.getImageUrl(profileImage)); } + // TODO: 리팩토링할것 @Transactional public MemberResponse updateHost(Long hostId, HostEditRequest request) { Host host = getByIdOrThrow(hostId); Optional.ofNullable(request.getNickname()).ifPresent(host::setNickname); Optional.ofNullable(request.getPhoneNumber()).ifPresent(host::setPhone); - Optional.ofNullable(request.getProfileImageId()).map(memberImageService::getById).ifPresent(host::setProfileImage); Optional.ofNullable(request.getIntro()).ifPresent(host::setIntro); - Optional.ofNullable(request.getHashtag()).ifPresent(host::setHashtag); + Optional.ofNullable(request.getHashtags()).ifPresent(host::setHashtags); + Optional.ofNullable(request.getProfileImageId()) + .map(memberImageService::getById) + .ifPresent((image) -> { + host.setProfileImage(image); + String url = memberImageService.createThumbnail(memberImageService.createThumbnail(image.getName())); + host.setProfileImageThumbnailUrl(url); + }); - return MemberResponse.from(host); + return MemberResponse.from(host, memberImageService.getImageUrl(host.getProfileImage())); } @Override @@ -53,22 +63,22 @@ public void deleteById(Long id) { @Override @Transactional(readOnly = true) public MemberResponse getMemberResponseById(Long id) { - return MemberResponse.from(getByIdOrThrow(id)); + Host host = getByIdOrThrow(id); + return MemberResponse.from(host, memberImageService.getImageUrl(host.getProfileImage())); } @Transactional(readOnly = true) public MemberResponse getMemberResponseBy(LocalLoginRequest loginRequest) { - return MemberResponse.from(hostRepository - .findByEmailAndHashedPassword( - loginRequest.email(), - passwordEncoder.encode(loginRequest.password())) - .orElseThrow(RuntimeException::new)); + Host host = hostRepository.findByEmailAndHashedPassword(loginRequest.email(), passwordEncoder.encode(loginRequest.password())) + .orElseThrow(() -> new LoginFailedException("이메일 또는 비밀번호가 일치하지 않습니다.")); + return MemberResponse.from(host, memberImageService.getImageUrl(host.getProfileImage())); } @Transactional(readOnly = true) public Host getByIdOrThrow(Long id) { - //TODO: 커스텀 예외 필요 - return hostRepository.findById(id).orElseThrow(RuntimeException::new); + return hostRepository.findById(id).orElseThrow(() -> + new EntityNotFoundException("사용자가 주최자가 아니거나 없습니다.") + ); } @Override diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/MemberService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/MemberService.java index ea657969..92a64656 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/MemberService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/MemberService.java @@ -2,13 +2,15 @@ import java.util.Optional; import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin.Image.application.MemberImageService; -import org.ktc2.cokaen.wouldyouin.Image.persist.MemberImage; +import org.ktc2.cokaen.wouldyouin._common.exception.EntityNotFoundException; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; -import org.ktc2.cokaen.wouldyouin.member.application.dto.MemberResponse; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.MemberAdditionalInfoRequest; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.create.MemberCreateRequest; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.edit.MemberEditRequest; +import org.ktc2.cokaen.wouldyouin.image.application.MemberImageService; +import org.ktc2.cokaen.wouldyouin.image.persist.MemberImage; +import org.ktc2.cokaen.wouldyouin.member.api.dto.MemberResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.MemberAdditionalInfoRequest; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.create.MemberCreateRequest; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.edit.MemberEditRequest; +import org.ktc2.cokaen.wouldyouin.member.exception.AdditionalInfoIllegalAccessException; import org.ktc2.cokaen.wouldyouin.member.persist.Member; import org.ktc2.cokaen.wouldyouin.member.persist.MemberRepository; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; @@ -25,9 +27,13 @@ public class MemberService implements MemberServiceCommonBehavior { @Transactional public MemberResponse createMember(MemberCreateRequest request) { MemberImage profileImage = memberImageService.convert(request.getProfileImageUrl()); - return MemberResponse.from(memberRepository.save(request.toEntity(profileImage))); + String thumbnailImageUrl = memberImageService.createThumbnail(profileImage.getName()); + Member member = memberRepository.save(request.toEntity(profileImage, thumbnailImageUrl)); + memberImageService.setBaseMember(profileImage, member); + return MemberResponse.from(member, memberImageService.getImageUrl(profileImage)); } + // TODO : 리팩토링 꼭 할 것, 멤버 전체에 대해 연관관계 설정할 것 @Transactional public MemberResponse updateMember(Long memberId, MemberEditRequest editRequest) { Member member = getByIdOrThrow(memberId); @@ -36,19 +42,23 @@ public MemberResponse updateMember(Long memberId, MemberEditRequest editRequest) Optional.ofNullable(editRequest.getPhoneNumber()).ifPresent(member::setPhone); Optional.ofNullable(editRequest.getProfileImageId()) .map(memberImageService::getById) - .ifPresent(member::setProfileImage); - return MemberResponse.from(member); + .ifPresent((image) -> { + member.setProfileImage(image); + String url = memberImageService.createThumbnail(memberImageService.createThumbnail(image.getName())); + member.setProfileImageThumbnailUrl(url); + }); + return MemberResponse.from(member, memberImageService.getImageUrl(member.getProfileImage())); } @Transactional public MemberResponse updateWelcomeMember(Long welcomeMemberId, MemberAdditionalInfoRequest additionalInfoRequest) { Member member = getByIdOrThrow(welcomeMemberId); + // TODO : validate if (member.getMemberType() != MemberType.welcome) { - // TODO: 커스텀 예외 필요 - throw new RuntimeException("Welcome Member가 아닙니다."); + throw new AdditionalInfoIllegalAccessException("최초 소셜로그인 후 추가정보를 기입하지 않은 사용자만 접근 가능합니다."); } member.updateFrom(additionalInfoRequest); - return MemberResponse.from(member); + return MemberResponse.from(member, memberImageService.getImageUrl(member.getProfileImage())); } @Override @@ -60,13 +70,15 @@ public void deleteById(Long id) { @Override @Transactional(readOnly = true) public MemberResponse getMemberResponseById(Long id) { - return MemberResponse.from(getByIdOrThrow(id)); + Member member = getByIdOrThrow(id); + return MemberResponse.from(member, memberImageService.getImageUrl(member.getProfileImage())); } @Transactional(readOnly = true) public Member getByIdOrThrow(Long id) { - //TODO: 커스텀 예외 필요 - return memberRepository.findById(id).orElseThrow(RuntimeException::new); + return memberRepository.findById(id).orElseThrow(() -> + new EntityNotFoundException("해당 사용자를 찾을 수 없습니다.") + ); } @Transactional(readOnly = true) diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/MemberServiceCommonBehavior.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/MemberServiceCommonBehavior.java index 2dabecef..ed70871e 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/MemberServiceCommonBehavior.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/MemberServiceCommonBehavior.java @@ -1,6 +1,6 @@ package org.ktc2.cokaen.wouldyouin.member.application; -import org.ktc2.cokaen.wouldyouin.member.application.dto.MemberResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.MemberResponse; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.springframework.transaction.annotation.Transactional; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/relation/ReservationMemberResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/relation/ReservationMemberResponse.java deleted file mode 100644 index 0ebfe7f9..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/relation/ReservationMemberResponse.java +++ /dev/null @@ -1,15 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.member.application.dto.relation; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class ReservationMemberResponse { - - private Long id; - private String email; - private String nickname; - private String phone; - private String gender; -} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/relationResponse/ReviewMemberResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/relationResponse/ReviewMemberResponse.java deleted file mode 100644 index 21a57579..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/relationResponse/ReviewMemberResponse.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.member.application.dto.relationResponse; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder -public class ReviewMemberResponse { - - private Long id; - private String nickname; -} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/MemberAdditionalInfoRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/MemberAdditionalInfoRequest.java deleted file mode 100644 index 64bfff22..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/application/dto/request/MemberAdditionalInfoRequest.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.member.application.dto.request; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.ktc2.cokaen.wouldyouin._common.vo.Area; - -@Getter -@RequiredArgsConstructor -public class MemberAdditionalInfoRequest { - - private final String phone; - private final Area area; - private final String gender; -} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/exception/AdditionalInfoIllegalAccessException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/exception/AdditionalInfoIllegalAccessException.java new file mode 100644 index 00000000..1358bef4 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/exception/AdditionalInfoIllegalAccessException.java @@ -0,0 +1,10 @@ +package org.ktc2.cokaen.wouldyouin.member.exception; + +import org.ktc2.cokaen.wouldyouin._common.exception.BusinessException; +import org.ktc2.cokaen.wouldyouin._common.exception.ErrorCode; + +public class AdditionalInfoIllegalAccessException extends BusinessException { + public AdditionalInfoIllegalAccessException(String message) { + super(message, ErrorCode.ADDITIONAL_INFO_ILLEGAL_ACCESS); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/exception/EmailAlreadyExistsException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/exception/EmailAlreadyExistsException.java new file mode 100644 index 00000000..1f010c0d --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/exception/EmailAlreadyExistsException.java @@ -0,0 +1,11 @@ +package org.ktc2.cokaen.wouldyouin.member.exception; + +import org.ktc2.cokaen.wouldyouin._common.exception.BusinessException; +import org.ktc2.cokaen.wouldyouin._common.exception.ErrorCode; + +public class EmailAlreadyExistsException extends BusinessException { + + public EmailAlreadyExistsException(String message) { + super(message, ErrorCode.EMAIL_ALREADY_EXISTS); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/exception/LoginFailedException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/exception/LoginFailedException.java new file mode 100644 index 00000000..b7d9608f --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/exception/LoginFailedException.java @@ -0,0 +1,11 @@ +package org.ktc2.cokaen.wouldyouin.member.exception; + +import org.ktc2.cokaen.wouldyouin._common.exception.BusinessException; +import org.ktc2.cokaen.wouldyouin._common.exception.ErrorCode; + +public class LoginFailedException extends BusinessException { + public LoginFailedException(String message) { + super(message, ErrorCode.LOGIN_FAILED); + } + +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/exception/NotLikeableMemberException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/exception/NotLikeableMemberException.java new file mode 100644 index 00000000..28225cb2 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/exception/NotLikeableMemberException.java @@ -0,0 +1,11 @@ +package org.ktc2.cokaen.wouldyouin.member.exception; + +import org.ktc2.cokaen.wouldyouin._common.exception.BusinessException; +import org.ktc2.cokaen.wouldyouin._common.exception.ErrorCode; + +public class NotLikeableMemberException extends BusinessException { + public NotLikeableMemberException(String message) { + super(message, ErrorCode.NOT_LIKEABLE_MEMBER_TYPE); + } + +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/BaseMember.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/BaseMember.java index 2fb3ad56..f89cc912 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/BaseMember.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/BaseMember.java @@ -5,64 +5,69 @@ import jakarta.persistence.Entity; import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; -import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; import jakarta.persistence.Inheritance; import jakarta.persistence.InheritanceType; import jakarta.persistence.OneToOne; -import jakarta.persistence.PrimaryKeyJoinColumn; import lombok.AccessLevel; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.ktc2.cokaen.wouldyouin.Image.persist.MemberImage; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin.image.persist.MemberImage; @Getter @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Inheritance(strategy = InheritanceType.JOINED) @DiscriminatorColumn +@EqualsAndHashCode +@ToString @Entity public abstract class BaseMember { @Id + @Setter(AccessLevel.NONE) @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long Id; + private Long id; - @Column(nullable = false) + @Column(nullable = false, name = "account_type") @Enumerated(EnumType.STRING) private AccountType accountType; - @Column(nullable = false) + @Column(nullable = false, name = "member_type") @Enumerated(EnumType.STRING) private MemberType memberType; - @Column(nullable = false, unique = true) + @Column(nullable = false, unique = true, name = "email") private String email; - @Column(nullable = false, unique = true) + @Column(nullable = false, unique = true, name = "nickname") private String nickname; - @Column(nullable = false) + @Column(nullable = false, name = "phone") private String phone; - @PrimaryKeyJoinColumn @OneToOne(mappedBy = "baseMember") private MemberImage profileImage; + private String profileImageThumbnailUrl; + protected BaseMember(AccountType accountType, MemberType memberType, String email, String nickname, String phone, - MemberImage profileImage) { + MemberImage profileImage, String profileImageThumbnailUrl) { this.accountType = accountType; this.memberType = memberType; this.email = email; this.nickname = nickname; this.phone = phone; this.profileImage = profileImage; + this.profileImageThumbnailUrl = profileImageThumbnailUrl; } public String getProfileImageUrl() { - return profileImage.getUrl(); + return profileImage.getName(); } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/Curator.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/Curator.java index 25431476..3db77301 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/Curator.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/Curator.java @@ -1,45 +1,64 @@ package org.ktc2.cokaen.wouldyouin.member.persist; import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.OneToMany; +import java.util.ArrayList; import java.util.List; +import java.util.Optional; import lombok.AccessLevel; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.ktc2.cokaen.wouldyouin.Image.persist.MemberImage; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin._common.converter.HashtagConverter; import org.ktc2.cokaen.wouldyouin._common.vo.Area; import org.ktc2.cokaen.wouldyouin.curation.persist.Curation; +import org.ktc2.cokaen.wouldyouin.image.persist.MemberImage; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.edit.CuratorEditRequest; @Getter @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @DiscriminatorValue("Curator") +@EqualsAndHashCode(callSuper = true) +@ToString @Entity public class Curator extends Member implements LikeableMember { - @Column(nullable = false) + @Column(nullable = false, name = "intro") private String intro; - @Column(nullable = false) + @Column(nullable = false, name = "likes") private Integer likes; - @Column(nullable = false) - private String hashtag; + @Column(nullable = false, name = "hashtags") + @Convert(converter = HashtagConverter.class) + private List hashtags; @OneToMany(mappedBy = "curator", fetch = FetchType.LAZY) - private List curations; + private List curations = new ArrayList<>(); @Builder(builderMethodName = "curatorBuilder") - public Curator(AccountType accountType, String email, String nickname, String phone, MemberImage profileImage, Area area, String gender, + public Curator(AccountType accountType, String email, String nickname, String phone, MemberImage profileImage, String profileImageThumbnailUrl, Area area, Gender gender, String socialId) { - super(accountType, MemberType.curator, email, nickname, phone, profileImage, area, gender, socialId); + super(accountType, MemberType.curator, email, nickname, phone, profileImage, profileImageThumbnailUrl, area, gender, socialId); this.intro = ""; this.likes = 0; - this.hashtag = ""; + hashtags = new ArrayList<>(); + } + + public void updateFrom(CuratorEditRequest request, MemberImage image, String profileImageThumbnailUrl) { + Optional.ofNullable(request.getPhoneNumber()).ifPresent(this::setPhone); + Optional.ofNullable(request.getNickname()).ifPresent(this::setNickname); + Optional.ofNullable(request.getArea()).ifPresent(this::setArea); + Optional.ofNullable(request.getIntro()).ifPresent(this::setIntro); + Optional.ofNullable(image).ifPresent(this::setProfileImage); + Optional.ofNullable(profileImageThumbnailUrl).ifPresent(this::setProfileImageThumbnailUrl); } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/Gender.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/Gender.java new file mode 100644 index 00000000..311b1652 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/Gender.java @@ -0,0 +1,8 @@ +package org.ktc2.cokaen.wouldyouin.member.persist; + +import lombok.Getter; + +@Getter +public enum Gender { + MAN, WOMAN +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/Host.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/Host.java index 1ef6efee..4ff93ba2 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/Host.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/Host.java @@ -1,47 +1,56 @@ package org.ktc2.cokaen.wouldyouin.member.persist; import jakarta.persistence.Column; +import jakarta.persistence.Convert; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; import jakarta.persistence.FetchType; import jakarta.persistence.OneToMany; +import java.util.ArrayList; import java.util.List; import lombok.AccessLevel; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.ktc2.cokaen.wouldyouin.Image.persist.MemberImage; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin._common.converter.HashtagConverter; import org.ktc2.cokaen.wouldyouin.event.persist.Event; +import org.ktc2.cokaen.wouldyouin.image.persist.MemberImage; @Getter @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @DiscriminatorValue("Host") +@EqualsAndHashCode(callSuper = true) +@ToString @Entity public class Host extends BaseMember implements LikeableMember { - @Column(nullable = false) + @Column(nullable = false, name = "hashed_password") private String hashedPassword; - @Column(nullable = false) + @Column(nullable = false, name = "intro") private String intro; - @Column(nullable = false) + @Column(nullable = false, name = "likes") private Integer likes; - @Column(nullable = false) - private String hashtag; + @Column(nullable = false, name = "hashtags") + @Convert(converter = HashtagConverter.class) + private List hashtags; + @ToString.Exclude @OneToMany(mappedBy = "host", fetch = FetchType.LAZY) - private List events; + private List events = new ArrayList<>(); @Builder - protected Host(String email, String nickname, String phone, String hashedPassword, MemberImage profileImage) { - super(AccountType.local, MemberType.host, email, nickname, phone, profileImage); + protected Host(String email, String nickname, String phone, String hashedPassword, MemberImage profileImage, String profileImageThumbnailUrl) { + super(AccountType.local, MemberType.host, email, nickname, phone, profileImage, profileImageThumbnailUrl); this.hashedPassword = hashedPassword; this.intro = ""; this.likes = 0; - this.hashtag = ""; + this.hashtags = new ArrayList<>(); } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/LikeableMember.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/LikeableMember.java index 5fdc4608..0409d64d 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/LikeableMember.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/LikeableMember.java @@ -1,6 +1,5 @@ package org.ktc2.cokaen.wouldyouin.member.persist; -import java.util.Arrays; import java.util.List; public interface LikeableMember { @@ -17,7 +16,7 @@ public interface LikeableMember { void setLikes(Integer likes); - String getHashtag(); + List getHashtags(); default void increaseLikes() { setLikes(getLikes() + 1); @@ -27,10 +26,6 @@ default void decreaseLikes() { setLikes(getLikes() - 1); } - default List getHashTagList() { - return Arrays.stream(getHashtag().split("#")).toList(); - } - static List getLikeableMemberTypes() { return List.of(MemberType.host, MemberType.curator); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/Member.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/Member.java index d5b4fe5c..1c4f8345 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/Member.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/Member.java @@ -3,19 +3,24 @@ import jakarta.persistence.Column; import jakarta.persistence.DiscriminatorValue; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.OneToMany; +import java.util.ArrayList; import java.util.List; import lombok.AccessLevel; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; -import org.ktc2.cokaen.wouldyouin.Image.persist.MemberImage; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.vo.Area; +import org.ktc2.cokaen.wouldyouin.image.persist.MemberImage; import org.ktc2.cokaen.wouldyouin.like.persist.CuratorLike; import org.ktc2.cokaen.wouldyouin.like.persist.HostLike; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.MemberAdditionalInfoRequest; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.MemberAdditionalInfoRequest; import org.ktc2.cokaen.wouldyouin.reservation.persist.Reservation; import org.ktc2.cokaen.wouldyouin.review.persist.Review; @@ -23,37 +28,41 @@ @Setter @NoArgsConstructor(access = AccessLevel.PROTECTED) @DiscriminatorValue("Member") +@EqualsAndHashCode(callSuper = true) +@ToString @Entity public class Member extends BaseMember { - @Column(nullable = false) + @Column(nullable = false, name = "area") + @Enumerated(EnumType.STRING) private Area area; - @Column(nullable = false) - private String gender; + @Column(nullable = false, name = "gender") + @Enumerated(EnumType.STRING) + private Gender gender; - @Column + @Column(name = "social_id") private String socialId; //소셜 타입 식별자 값 //for JWT refresh token - @Column(length = 1000) + @Column(length = 1000, name = "refresh_token") private String refreshToken; @OneToMany(mappedBy = "member", fetch = FetchType.LAZY) - private List curatorLikes; + private List curatorLikes = new ArrayList<>(); @OneToMany(mappedBy = "member", fetch = FetchType.LAZY) - private List hostLikes; + private List hostLikes = new ArrayList<>(); @OneToMany(mappedBy = "member", fetch = FetchType.LAZY) - private List reservations; + private List reservations = new ArrayList<>(); @OneToMany(mappedBy = "member", fetch = FetchType.LAZY) - private List reviews; + private List reviews = new ArrayList<>(); // for Curator - protected Member(AccountType accountType, MemberType memberType, String email, String nickname, String phone, MemberImage profileImage, Area area, String gender, String socialId) { - super(accountType, memberType, email, nickname, phone, profileImage); + protected Member(AccountType accountType, MemberType memberType, String email, String nickname, String phone, MemberImage profileImage, String profileImageThumbNailUrl, Area area, Gender gender, String socialId) { + super(accountType, memberType, email, nickname, phone, profileImage, profileImageThumbNailUrl); this.area = area; this.gender = gender; this.socialId = socialId; @@ -61,8 +70,8 @@ protected Member(AccountType accountType, MemberType memberType, String email, S @Builder // for public builder - protected Member(AccountType accountType, String email, String nickname, String phone, MemberImage profileImage, Area area, String gender, String socialId) { - this(accountType, MemberType.welcome, email, nickname, phone, profileImage, area, gender, socialId); + protected Member(AccountType accountType, String email, String nickname, String phone, MemberImage profileImage, String profileImageThumbnailUrl, Area area, Gender gender, String socialId) { + this(accountType, MemberType.welcome, email, nickname, phone, profileImage, profileImageThumbnailUrl, area, gender, socialId); } public void updateFrom(MemberAdditionalInfoRequest request) { diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/MemberType.java b/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/MemberType.java index e77abba5..8e60abbd 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/MemberType.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/member/persist/MemberType.java @@ -9,5 +9,4 @@ public enum MemberType { curator, host, admin; - } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/payment/application/PaymentService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/payment/application/PaymentService.java index 70cb9ca3..0de71a1f 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/payment/application/PaymentService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/payment/application/PaymentService.java @@ -1,74 +1,41 @@ package org.ktc2.cokaen.wouldyouin.payment.application; -import java.net.URI; -import java.util.HashMap; -import java.util.Map; -import org.ktc2.cokaen.wouldyouin._common.exception.FailedToPayException; +import lombok.RequiredArgsConstructor; +import org.ktc2.cokaen.wouldyouin.payment.exception.FailedToPayException; +import org.ktc2.cokaen.wouldyouin._common.util.KakaoPayUtil; +import org.ktc2.cokaen.wouldyouin._common.util.RestClientUtil; +import org.ktc2.cokaen.wouldyouin._common.util.UriUtil; import org.ktc2.cokaen.wouldyouin.payment.dto.KakaoPayRequest; import org.ktc2.cokaen.wouldyouin.payment.dto.KakaoPayResponse; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; import org.springframework.stereotype.Service; -import org.springframework.web.client.RestClient; @Service +@RequiredArgsConstructor public class PaymentService { - // Todo: util로 이동하여 재사용 - private final RestClient client = RestClient.builder().build(); + private final RestClientUtil client; - @Value("${oauth.payment.kakao_pay_request_host}") + @Value("${oauth.payment.kakao-pay-request-host}") private String kakaoPayRequestHost; - @Value("${oauth.payment.kakao_pay_single_payment_url}") private String kakaoPaySinglePaymentUrl; - @Value("${oauth.payment.approval_url}") private String approvalUrl; - @Value("${oauth.payment.cancel_url}") private String cancelUrl; - @Value("${oauth.payment.fail_url}") private String failUrl; - @Value("${oauth.payment.secret_key}") private String secretKey; public KakaoPayResponse createPayment(KakaoPayRequest kakaoPayRequest) { - try { - return client.post() - .uri(URI.create("https://" + kakaoPayRequestHost + kakaoPaySinglePaymentUrl)) - .headers(httpHeaders -> httpHeaders.addAll(createKakaoPayRequestHeaders())) - .body(createKakaoPayRequestBody(kakaoPayRequest)) - .retrieve() - .body(KakaoPayResponse.class); - } catch (Exception ex) { - throw new FailedToPayException("Kakao Pay"); - } - } - - private HttpHeaders createKakaoPayRequestHeaders() { - HttpHeaders headers = new HttpHeaders(); - headers.add("Host", kakaoPayRequestHost); - headers.add("Authorization", "SECRET_KEY " + secretKey); - headers.setContentType(MediaType.APPLICATION_JSON); - return headers; - } - - private Map createKakaoPayRequestBody(KakaoPayRequest kakaoPayRequest) { - Map body = new HashMap<>(); - body.put("cid", "TC0ONETIME"); - body.put("partner_order_id", kakaoPayRequest.getReservationId()); - body.put("partner_user_id", kakaoPayRequest.getHostId()); - body.put("item_name", kakaoPayRequest.getEventName()); - body.put("quantity", kakaoPayRequest.getQuantity()); - body.put("total_amount", kakaoPayRequest.getTotalAmount()); - body.put("tax_free_amount", kakaoPayRequest.getTaxFreeAmount()); - body.put("approval_url", approvalUrl); - body.put("cancel_url", cancelUrl); - body.put("fail_url", failUrl); - return body; + return client.post( + KakaoPayResponse.class, + UriUtil.assembleFullUrl(kakaoPayRequestHost, kakaoPaySinglePaymentUrl), + KakaoPayUtil.createKakaoPayRequestHeaders(kakaoPayRequestHost, secretKey), + KakaoPayUtil.createKakaoPayRequestBody(kakaoPayRequest, approvalUrl, cancelUrl, failUrl), + (req, rsp) -> { throw new FailedToPayException("카카오페이 API 요청을 실패하였습니다."); } + ); } -} +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/payment/dto/KakaoPayRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/payment/dto/KakaoPayRequest.java index b2a8cfa1..3fb2a226 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/payment/dto/KakaoPayRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/payment/dto/KakaoPayRequest.java @@ -1,11 +1,15 @@ package org.ktc2.cokaen.wouldyouin.payment.dto; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.reservation.persist.Reservation; @Getter @Builder +@EqualsAndHashCode +@ToString public class KakaoPayRequest { private String reservationId; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/payment/dto/KakaoPayResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/payment/dto/KakaoPayResponse.java index b17b9053..f3c2833e 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/payment/dto/KakaoPayResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/payment/dto/KakaoPayResponse.java @@ -1,14 +1,17 @@ package org.ktc2.cokaen.wouldyouin.payment.dto; -import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.databind.PropertyNamingStrategies.SnakeCaseStrategy; import com.fasterxml.jackson.databind.annotation.JsonNaming; import java.time.LocalDateTime; import lombok.AllArgsConstructor; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; @Getter @AllArgsConstructor +@EqualsAndHashCode +@ToString @JsonNaming(value = SnakeCaseStrategy.class) public class KakaoPayResponse { diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/payment/exception/FailedToPayException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/payment/exception/FailedToPayException.java new file mode 100644 index 00000000..351612aa --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/payment/exception/FailedToPayException.java @@ -0,0 +1,11 @@ +package org.ktc2.cokaen.wouldyouin.payment.exception; + +import org.ktc2.cokaen.wouldyouin._common.exception.BusinessException; +import org.ktc2.cokaen.wouldyouin._common.exception.ErrorCode; + +public class FailedToPayException extends BusinessException { + + public FailedToPayException(String message) { + super(message, ErrorCode.FAIL_TO_PAY); + } +} \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/ReservationController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/ReservationController.java index 8bdc91ea..462c9db4 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/ReservationController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/ReservationController.java @@ -4,15 +4,16 @@ import lombok.RequiredArgsConstructor; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponse; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponseBody; -import org.ktc2.cokaen.wouldyouin._common.config.ParamDefaults; +import org.ktc2.cokaen.wouldyouin._common.api.ParamDefaults; import org.ktc2.cokaen.wouldyouin.auth.Authorize; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.ktc2.cokaen.wouldyouin.payment.dto.KakaoPayResponse; -import org.ktc2.cokaen.wouldyouin.reservation.application.ReservationService; +import org.ktc2.cokaen.wouldyouin.reservation.api.dto.KakaoPayReservationResponse; import org.ktc2.cokaen.wouldyouin.reservation.api.dto.ReservationRequest; import org.ktc2.cokaen.wouldyouin.reservation.api.dto.ReservationResponse; import org.ktc2.cokaen.wouldyouin.reservation.api.dto.ReservationSliceResponse; +import org.ktc2.cokaen.wouldyouin.reservation.application.ReservationService; import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; @@ -33,20 +34,21 @@ public class ReservationController { @GetMapping public ResponseEntity> getReservationsByMemberId( - @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier member, + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier identifier, @RequestParam(defaultValue = ParamDefaults.PAGE) Integer page, @RequestParam(defaultValue = ParamDefaults.PAGE_SIZE) Integer size, @RequestParam(defaultValue = ParamDefaults.LAST_ID) Long lastId) { - return ApiResponse.ok(reservationService.getAllByMemberId(member.id(), PageRequest.of(page, size), lastId)); + return ApiResponse.ok(reservationService.getAllByMemberId(identifier, PageRequest.of(page, size), lastId)); } @GetMapping("/events/{eventId}") public ResponseEntity> getReservationsByEventId( + @Authorize(MemberType.host) MemberIdentifier identifier, @PathVariable Long eventId, @RequestParam(defaultValue = ParamDefaults.PAGE) Integer page, @RequestParam(defaultValue = ParamDefaults.PAGE_SIZE) Integer size, @RequestParam(defaultValue = ParamDefaults.LAST_ID) Long lastId) { - return ApiResponse.ok(reservationService.getAllByEventId(eventId, PageRequest.of(page, size), lastId)); + return ApiResponse.ok(reservationService.getAllByEventId(identifier, eventId, PageRequest.of(page, size), lastId)); } @GetMapping("/{reservationId}") @@ -56,17 +58,24 @@ public ResponseEntity> getReservationById( } @PostMapping - public ResponseEntity> createReservation( + public ResponseEntity> createReservation( + @Valid @RequestBody ReservationRequest reservationRequest, + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier identifier) { + return ApiResponse.created(reservationService.create(identifier, reservationRequest)); + } + + @PostMapping("/test") + public ResponseEntity> createTestReservation( @Valid @RequestBody ReservationRequest reservationRequest, - @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier member) { - return ApiResponse.created(reservationService.create(member.id(), reservationRequest)); + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier identifier) { + return ApiResponse.created(reservationService.createTest(identifier, reservationRequest)); } @DeleteMapping("/{reservationId}") public ResponseEntity> deleteReservation( @PathVariable Long reservationId, - @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier member) { - reservationService.delete(member.id(), reservationId); + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier identifier) { + reservationService.delete(identifier, reservationId); return ApiResponse.noContent(); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/dto/KakaoPayReservationResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/dto/KakaoPayReservationResponse.java new file mode 100644 index 00000000..599368aa --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/dto/KakaoPayReservationResponse.java @@ -0,0 +1,24 @@ +package org.ktc2.cokaen.wouldyouin.reservation.api.dto; + +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin.payment.dto.KakaoPayResponse; + +@Builder +@Getter +@EqualsAndHashCode +@ToString +public class KakaoPayReservationResponse { + + private ReservationResponse reservationResponse; + private KakaoPayResponse kakaoPayResponse; + + public static KakaoPayReservationResponse from(ReservationResponse reservationResponse, KakaoPayResponse kakaoPayResponse) { + return KakaoPayReservationResponse.builder() + .reservationResponse(reservationResponse) + .kakaoPayResponse(kakaoPayResponse) + .build(); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/dto/ReservationRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/dto/ReservationRequest.java index aa7c11e1..a1d89c2b 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/dto/ReservationRequest.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/dto/ReservationRequest.java @@ -1,34 +1,33 @@ package org.ktc2.cokaen.wouldyouin.reservation.api.dto; -import jakarta.validation.constraints.Max; import jakarta.validation.constraints.Min; import jakarta.validation.constraints.NotNull; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.event.persist.Event; import org.ktc2.cokaen.wouldyouin.member.persist.Member; import org.ktc2.cokaen.wouldyouin.reservation.persist.Reservation; @Getter @Builder(toBuilder = true) +@EqualsAndHashCode +@ToString public class ReservationRequest { @NotNull(message = "이벤트 ID는 필수입니다.") private Long eventId; - @NotNull(message = "가격은 필수입니다.") - @Min(value = 0, message = "가격은 0 이상이어야 합니다.") - @Max(value = 1000000, message = "가격은 1,000,000원 이하이어야 합니다.") - private Integer price; - @NotNull(message = "수량은 필수입니다.") + @Min(value = 1, message = "수량은 1개 이상이어야 합니다.") private Integer quantity; public Reservation toEntity(Member member, Event event) { return Reservation.builder() .member(member) .event(event) - .price(price) + .price(event.getPrice() * quantity) .quantity(quantity) .build(); } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/dto/ReservationResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/dto/ReservationResponse.java index ba63a297..a9acaf40 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/dto/ReservationResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/dto/ReservationResponse.java @@ -2,13 +2,17 @@ import java.time.LocalDateTime; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.ReservationEventResponse; -import org.ktc2.cokaen.wouldyouin.member.application.dto.relation.ReservationMemberResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse.ReservationMemberResponse; import org.ktc2.cokaen.wouldyouin.reservation.persist.Reservation; @Builder @Getter +@EqualsAndHashCode +@ToString public class ReservationResponse { private Long id; @@ -21,22 +25,8 @@ public class ReservationResponse { public static ReservationResponse from(Reservation reservation) { return ReservationResponse.builder() .id(reservation.getId()) - .member( - ReservationMemberResponse.builder() - .id(reservation.getMember().getId()) - .email(reservation.getMember().getEmail()) - .nickname(reservation.getMember().getNickname()) - .phone(reservation.getMember().getPhone()) - .gender(reservation.getMember().getGender()) - .build() - ) - .event( - ReservationEventResponse.builder() - .id(reservation.getEvent().getId()) - .title(reservation.getEvent().getTitle()) - .price(reservation.getEvent().getPrice()) - .build() - ) + .member(ReservationMemberResponse.from(reservation.getMember())) + .event(ReservationEventResponse.from(reservation.getEvent())) .price(reservation.getPrice()) .quantity(reservation.getQuantity()) .reservationDate(reservation.getReservationDate()) diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/dto/ReservationSliceResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/dto/ReservationSliceResponse.java index 61c076dc..2469e5d0 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/dto/ReservationSliceResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/api/dto/ReservationSliceResponse.java @@ -2,19 +2,26 @@ import java.util.List; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin._common.api.SliceInfo; +import org.ktc2.cokaen.wouldyouin.reservation.persist.Reservation; +import org.springframework.data.domain.Slice; @Getter @Builder +@EqualsAndHashCode +@ToString public class ReservationSliceResponse { private List reservations; private SliceInfo sliceInfo; - public static ReservationSliceResponse of(List reservations, int size, Long lastId) { + public static ReservationSliceResponse from(Slice reservations, int size, Long lastId) { return ReservationSliceResponse.builder() - .reservations(reservations) + .reservations(reservations.stream() + .map(ReservationResponse::from).toList()) .sliceInfo(SliceInfo.builder() .sliceSize(size) .lastId(lastId) diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/application/ReservationService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/application/ReservationService.java index c94af33a..391e9ac8 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/application/ReservationService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/application/ReservationService.java @@ -1,14 +1,16 @@ package org.ktc2.cokaen.wouldyouin.reservation.application; -import java.util.List; import lombok.RequiredArgsConstructor; import org.ktc2.cokaen.wouldyouin._common.exception.EntityNotFoundException; +import org.ktc2.cokaen.wouldyouin.reservation.exception.ReservationNotFoundForReviewException; import org.ktc2.cokaen.wouldyouin._common.exception.UnauthorizedException; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; import org.ktc2.cokaen.wouldyouin.event.application.EventService; import org.ktc2.cokaen.wouldyouin.member.application.MemberService; import org.ktc2.cokaen.wouldyouin.payment.application.PaymentService; import org.ktc2.cokaen.wouldyouin.payment.dto.KakaoPayRequest; import org.ktc2.cokaen.wouldyouin.payment.dto.KakaoPayResponse; +import org.ktc2.cokaen.wouldyouin.reservation.api.dto.KakaoPayReservationResponse; import org.ktc2.cokaen.wouldyouin.reservation.api.dto.ReservationRequest; import org.ktc2.cokaen.wouldyouin.reservation.api.dto.ReservationResponse; import org.ktc2.cokaen.wouldyouin.reservation.api.dto.ReservationSliceResponse; @@ -29,52 +31,75 @@ public class ReservationService { private final EventService eventService; @Transactional(readOnly = true) - public ReservationSliceResponse getAllByMemberId(Long memberId, Pageable pageable, Long lastId) { - return getReservationSliceResponse( - reservationRepository.findByMemberIdOrderByReservationIdDesc(memberId, lastId, pageable), lastId); + public ReservationResponse getById(Long id) { + return ReservationResponse.from(getByIdOrThrow(id)); } @Transactional(readOnly = true) - public ReservationSliceResponse getAllByEventId(Long eventId, Pageable pageable, Long lastId) { - return getReservationSliceResponse( - reservationRepository.findByEventIdOrderByReservationIdDesc(eventId, lastId, pageable), lastId); + public ReservationSliceResponse getAllByMemberId(MemberIdentifier identifier, Pageable pageable, Long oldLastId) { + Slice reservations = + reservationRepository.findByMemberIdOrderByReservationIdDesc(identifier.id(), oldLastId, pageable); + Long newLastId = getLastId(reservations, oldLastId); + return ReservationSliceResponse.from(reservations, reservations.getSize(), newLastId); } - private ReservationSliceResponse getReservationSliceResponse(Slice reservationSlice, Long lastId) { - List reservations = reservationSlice.stream().map(ReservationResponse::from).toList(); - if (!reservationSlice.hasContent()) { - Long id = reservationSlice.getContent().getLast().getId(); - return ReservationSliceResponse.of(reservations, reservationSlice.getSize(), id); - } - return ReservationSliceResponse.of(reservations, reservationSlice.getSize(), lastId); + @Transactional(readOnly = true) + public ReservationSliceResponse getAllByEventId(MemberIdentifier identifier, Long eventId, Pageable pageable, Long oldLastId) { + eventService.validateHostId(identifier, eventService.getByIdOrThrow(eventId)); + Slice reservations = reservationRepository.findByEventIdOrderByReservationIdDesc(eventId, oldLastId, pageable); + Long newLastId = getLastId(reservations, oldLastId); + return ReservationSliceResponse.from(reservations, reservations.getSize(), newLastId); } - @Transactional(readOnly = true) - public ReservationResponse getById(Long id) { - Reservation target = reservationRepository.findById(id).orElseThrow(() -> new EntityNotFoundException("reservation ")); - return ReservationResponse.from(target); + @Transactional + public KakaoPayReservationResponse create(MemberIdentifier identifier, ReservationRequest reservationRequest) { + Reservation reservation = reservationRepository.save(reservationRequest.toEntity( + memberService.getByIdOrThrow(identifier.id()), + eventService.getByIdOrThrow(reservationRequest.getEventId())) + ); + eventService.decreaseLeftSeat(reservation.getEvent().getId(), reservationRequest.getQuantity()); + KakaoPayResponse kakaoPayResponse = paymentService.createPayment(KakaoPayRequest.from(reservation)); + ReservationResponse reservationResponse = ReservationResponse.from(reservation); + return KakaoPayReservationResponse.from(reservationResponse, kakaoPayResponse); } @Transactional - public KakaoPayResponse create(Long memberId, ReservationRequest reservationRequest) { + public ReservationResponse createTest(MemberIdentifier identifier, ReservationRequest reservationRequest) { Reservation reservation = reservationRepository.save(reservationRequest.toEntity( - memberService.getByIdOrThrow(memberId), + memberService.getByIdOrThrow(identifier.id()), eventService.getByIdOrThrow(reservationRequest.getEventId()))); eventService.decreaseLeftSeat(reservation.getEvent().getId(), reservationRequest.getQuantity()); - return paymentService.createPayment(KakaoPayRequest.from(reservation)); + return ReservationResponse.from(reservation); } @Transactional - public void delete(Long memberId, Long reservationId) { - Reservation reservation = reservationRepository.findById(reservationId) - .orElseThrow(() -> new EntityNotFoundException("reservation ")); - validateMemberId(memberId, reservation); + public void delete(MemberIdentifier identifier, Long reservationId) { + validateMemberId(identifier.id(), getByIdOrThrow(reservationId)); reservationRepository.deleteById(reservationId); } - public void validateMemberId(Long memberId, Reservation reservation) { + @Transactional(readOnly = true) + public void validateByMemberIdAndEventId(Long memberId, Long eventId) { + if (reservationRepository.findByMemberIdAndEventId(memberId, eventId).isEmpty()) { + throw new ReservationNotFoundForReviewException("해당 이벤트에 대한 리뷰를 작성할 수 없습니다."); + } + } + + private Reservation getByIdOrThrow(Long id) { + return reservationRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("해당하는 예약을 찾을 수 없습니다.")); + } + + private Long getLastId(Slice reservations, Long oldLastId) { + if (reservations.hasContent()) { + return reservations.getContent().getLast().getId(); + } + return oldLastId; + } + + private void validateMemberId(Long memberId, Reservation reservation) { if (!memberId.equals(reservation.getMember().getId())) { - throw new UnauthorizedException("member ID가 예약의 member ID와 일치하지 않습니다."); + throw new UnauthorizedException("사용자 ID가 예약한 사용자 ID와 일치하지 않습니다."); } } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/exception/ReservationNotFoundForReviewException.java b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/exception/ReservationNotFoundForReviewException.java new file mode 100644 index 00000000..a4cb9d16 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/exception/ReservationNotFoundForReviewException.java @@ -0,0 +1,10 @@ +package org.ktc2.cokaen.wouldyouin.reservation.exception; + +import org.ktc2.cokaen.wouldyouin._common.exception.BusinessException; +import org.ktc2.cokaen.wouldyouin._common.exception.ErrorCode; + +public class ReservationNotFoundForReviewException extends BusinessException { + public ReservationNotFoundForReviewException(String message) { + super(message, ErrorCode.RESERVATION_NOT_FOUND_FOR_REVIEW); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/persist/Reservation.java b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/persist/Reservation.java index fc6ae657..0e2ba328 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/persist/Reservation.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/persist/Reservation.java @@ -1,6 +1,5 @@ package org.ktc2.cokaen.wouldyouin.reservation.persist; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; import jakarta.persistence.EntityListeners; @@ -13,11 +12,14 @@ import jakarta.persistence.OneToOne; import jakarta.validation.constraints.NotNull; import java.time.LocalDateTime; +import java.util.Optional; import lombok.AccessLevel; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.event.persist.Event; import org.ktc2.cokaen.wouldyouin.member.persist.Member; import org.springframework.data.annotation.CreatedDate; @@ -25,6 +27,8 @@ @Getter @Setter +@EqualsAndHashCode +@ToString @NoArgsConstructor(access = AccessLevel.PROTECTED) @EntityListeners(AuditingEntityListener.class) @Entity @@ -32,6 +36,7 @@ public class Reservation { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Setter(AccessLevel.NONE) @Column(name = "reservation_id") private Long id; @@ -57,9 +62,9 @@ public class Reservation { @Builder protected Reservation(Member member, Event event, Integer price, Integer quantity) { - this.member = member; - this.event = event; - this.price = price; - this.quantity = quantity; + Optional.ofNullable(member).ifPresent(this::setMember); + Optional.ofNullable(event).ifPresent(this::setEvent); + Optional.ofNullable(price).ifPresent(this::setPrice); + Optional.ofNullable(quantity).ifPresent(this::setQuantity); } } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/persist/ReservationRepository.java b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/persist/ReservationRepository.java index bddac660..810e248a 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/persist/ReservationRepository.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/reservation/persist/ReservationRepository.java @@ -1,5 +1,6 @@ package org.ktc2.cokaen.wouldyouin.reservation.persist; +import java.util.List; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; @@ -10,12 +11,18 @@ public interface ReservationRepository extends JpaRepository { @Query("SELECT R FROM Reservation R JOIN FETCH R.member JOIN FETCH R.event " - + "WHERE R.member.Id = :memberId AND R.id > :lastId " + + "WHERE R.member.id = :memberId AND R.id < :lastId " + "ORDER BY R.id DESC") Slice findByMemberIdOrderByReservationIdDesc(Long memberId, Long lastId, Pageable pageable); @Query("SELECT R FROM Reservation R JOIN FETCH R.member JOIN FETCH R.event " - + "WHERE R.event.id = :eventId AND R.id > :lastId " + + "WHERE R.event.id = :eventId AND R.id < :lastId " + "ORDER BY R.id DESC") Slice findByEventIdOrderByReservationIdDesc(Long eventId, Long lastId, Pageable pageable); + + @Query("SELECT R FROM Reservation R JOIN FETCH R.member JOIN FETCH R.event " + + "WHERE R.member.id = :memberId " + + "AND R.event.id = :eventId " + + "AND R.event.endTime > CURRENT_TIMESTAMP") + List findByMemberIdAndEventId(Long memberId, Long eventId); } \ No newline at end of file diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/ReviewController.java b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/ReviewController.java index ea036dc1..98f03c50 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/ReviewController.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/ReviewController.java @@ -1,13 +1,20 @@ package org.ktc2.cokaen.wouldyouin.review.api; -import java.util.List; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; +import org.ktc2.cokaen.wouldyouin._common.api.ApiResponse; import org.ktc2.cokaen.wouldyouin._common.api.ApiResponseBody; +import org.ktc2.cokaen.wouldyouin._common.api.ParamDefaults; +import org.ktc2.cokaen.wouldyouin.auth.Authorize; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.ReviewEventSliceResponse; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewCreateRequest; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewEditRequest; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewResponse; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewSliceResponse; import org.ktc2.cokaen.wouldyouin.review.application.ReviewService; -import org.ktc2.cokaen.wouldyouin.review.application.dto.ReviewCreateRequest; -import org.ktc2.cokaen.wouldyouin.review.application.dto.ReviewEditRequest; -import org.ktc2.cokaen.wouldyouin.review.application.dto.ReviewResponse; -import org.springframework.http.HttpStatus; +import org.springframework.data.domain.PageRequest; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; @@ -16,6 +23,7 @@ import org.springframework.web.bind.annotation.PutMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @@ -25,51 +33,63 @@ public class ReviewController { private final ReviewService reviewService; - // ToDo: ApiResponse 정적메서드 활용 - - @GetMapping("/{memberId}") - public ResponseEntity>> getReviewsByMemberId( - @PathVariable("memberId") Long memberId) { - return ResponseEntity.status(HttpStatus.OK) - .body(new ApiResponseBody<>(true, reviewService.getAllByMemberId(memberId))); + @GetMapping + public ResponseEntity> getReviewsByMemberId( + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier member, + @RequestParam(defaultValue = ParamDefaults.PAGE) Integer page, + @RequestParam(defaultValue = ParamDefaults.PAGE_SIZE) Integer size, + @RequestParam(defaultValue = ParamDefaults.LAST_ID) Long lastId) { + return ApiResponse.ok( + reviewService.getAllByMemberId(member.id(), PageRequest.of(page, size), lastId)); } - @GetMapping("/{eventId}") - public ResponseEntity>> getReviewsByEventId( - @PathVariable("eventId") Long eventId) { - return ResponseEntity.status(HttpStatus.OK) - .body(new ApiResponseBody<>(true, reviewService.getAllByEventId(eventId))); + @GetMapping("/events/{eventId}") + public ResponseEntity> getReviewsByEventId( + @PathVariable("eventId") Long eventId, + @RequestParam(defaultValue = ParamDefaults.PAGE) Integer page, + @RequestParam(defaultValue = ParamDefaults.PAGE_SIZE) Integer size, + @RequestParam(defaultValue = ParamDefaults.LAST_ID) Long lastId) { + return ApiResponse.ok( + reviewService.getAllByEventId(eventId, PageRequest.of(page, size), lastId)); } @GetMapping("/{reviewId}") public ResponseEntity> getReviewByReviewId( @PathVariable("reviewId") Long reviewId) { - return ResponseEntity.status(HttpStatus.OK) - .body(new ApiResponseBody<>(true, reviewService.getById(reviewId))); + return ApiResponse.ok(reviewService.getById(reviewId)); + } + + @GetMapping("events") + public ResponseEntity> getUnreviewedEventsByMemberId( + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier identifier, + @RequestParam(defaultValue = ParamDefaults.PAGE) Integer page, + @RequestParam(defaultValue = ParamDefaults.PAGE_SIZE) Integer size, + @RequestParam(defaultValue = ParamDefaults.LAST_ID) Long lastId) { + return ApiResponse.ok( + reviewService.getUnreviewedEventsByMemberId(identifier.id(), PageRequest.of(page, size), + lastId)); } - @PostMapping("/{eventId}") + @PostMapping public ResponseEntity> createReview( - @PathVariable("eventId") Long eventId, - @RequestBody ReviewCreateRequest reviewCreateRequest) { - return ResponseEntity.status(HttpStatus.OK) - .body(new ApiResponseBody<>(true, reviewService.create(eventId, reviewCreateRequest))); + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier member, + @Valid @RequestBody ReviewCreateRequest reviewCreateRequest) { + return ApiResponse.created(reviewService.create(member.id(), reviewCreateRequest)); } @PutMapping("/{reviewId}") public ResponseEntity> updateReview( @PathVariable("reviewId") Long reviewId, - @RequestBody ReviewEditRequest reviewEditRequest) { - return ResponseEntity.status(HttpStatus.OK) - .body(new ApiResponseBody<>(true, reviewService.update(reviewId, reviewEditRequest))); + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier member, + @Valid @RequestBody ReviewEditRequest reviewEditRequest) { + return ApiResponse.ok(reviewService.update(member.id(), reviewId, reviewEditRequest)); } @DeleteMapping("/{reviewId}") public ResponseEntity> deleteReview( - @PathVariable("reviewId") Long reviewId) { - reviewService.delete(reviewId); - return ResponseEntity.status(HttpStatus.NO_CONTENT) - .body(new ApiResponseBody<>(true, null)); + @PathVariable("reviewId") Long reviewId, + @Authorize({MemberType.normal, MemberType.curator}) MemberIdentifier member) { + reviewService.delete(member.id(), reviewId); + return ApiResponse.noContent(); } - } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewCreateRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewCreateRequest.java new file mode 100644 index 00000000..61fcb95c --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewCreateRequest.java @@ -0,0 +1,40 @@ +package org.ktc2.cokaen.wouldyouin.review.api.dto; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin.event.persist.Event; +import org.ktc2.cokaen.wouldyouin.member.persist.Member; +import org.ktc2.cokaen.wouldyouin.review.persist.Review; + +@Getter +@Builder(toBuilder = true) +@EqualsAndHashCode +@ToString +public class ReviewCreateRequest { + + @NotNull(message = "이벤트 ID는 필수입니다.") + private Long eventId; + + @NotNull(message = "별점은 필수입니다.") + @Min(value = 0, message = "별점은 0점 이상입니다.") + @Max(value = 5, message = "별점은 5점 이하입니다.") + private Integer score; + + @NotBlank(message = "내용은 필수입니다.") + @Size(min = 5, max = 50, message = "내용은 5자 이상 50자 이하입니다.") + private String content; + + public Review toEntity(Member member, Event event) { + return Review.builder() + .score(this.score) + .content(this.content) + .build(); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewEditRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewEditRequest.java new file mode 100644 index 00000000..d0069102 --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewEditRequest.java @@ -0,0 +1,27 @@ +package org.ktc2.cokaen.wouldyouin.review.api.dto; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Size; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; + +@Getter +@Builder(toBuilder = true) +@EqualsAndHashCode +@ToString +public class ReviewEditRequest { + + @NotNull(message = "별점은 필수입니다.") + @Min(value = 0, message = "별점은 0점 이상입니다.") + @Max(value = 5, message = "별점은 5점 이하입니다.") + private Integer score; + + @NotBlank(message = "내용은 필수입니다.") + @Size(min = 5, max = 50, message = "내용은 5자 이상 50자 이하입니다.") + private String content; +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/dto/ReviewResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewResponse.java similarity index 57% rename from src/main/java/org/ktc2/cokaen/wouldyouin/review/application/dto/ReviewResponse.java rename to src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewResponse.java index 08a2e764..7ddd44cb 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/dto/ReviewResponse.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewResponse.java @@ -1,15 +1,19 @@ -package org.ktc2.cokaen.wouldyouin.review.application.dto; +package org.ktc2.cokaen.wouldyouin.review.api.dto; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.ReviewEventResponse; import org.ktc2.cokaen.wouldyouin.event.persist.Event; -import org.ktc2.cokaen.wouldyouin.member.application.dto.relationResponse.ReviewMemberResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse.ReviewMemberResponse; import org.ktc2.cokaen.wouldyouin.member.persist.Member; import org.ktc2.cokaen.wouldyouin.review.persist.Review; @Builder @Getter +@EqualsAndHashCode +@ToString public class ReviewResponse { private Long id; @@ -20,18 +24,11 @@ public class ReviewResponse { public static ReviewResponse from(final Review review) { Member member = review.getMember(); - Event eventInReview = review.getEvent(); + Event event = review.getEvent(); return ReviewResponse.builder() .id(review.getId()) - .member(ReviewMemberResponse.builder() - .id(member.getId()) - .nickname(member.getNickname()) - .build()) - .event(ReviewEventResponse.builder() - .id(eventInReview.getId()) - .title(eventInReview.getTitle()) -// .mainImage(eventInCuration.getMainImage()) - .build()) + .member(ReviewMemberResponse.from(member)) + .event(ReviewEventResponse.from(event)) .score(review.getScore()) .content(review.getContent()) .build(); diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewSliceResponse.java b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewSliceResponse.java new file mode 100644 index 00000000..0ee0155f --- /dev/null +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/review/api/dto/ReviewSliceResponse.java @@ -0,0 +1,31 @@ +package org.ktc2.cokaen.wouldyouin.review.api.dto; + +import java.util.List; +import lombok.Builder; +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.ToString; +import org.ktc2.cokaen.wouldyouin._common.api.SliceInfo; +import org.ktc2.cokaen.wouldyouin.review.persist.Review; +import org.springframework.data.domain.Slice; + +@Getter +@Builder +@EqualsAndHashCode +@ToString +public class ReviewSliceResponse { + + private List reviews; + private SliceInfo sliceInfo; + + public static ReviewSliceResponse from(Slice reviews, int size, Long lastId) { + return ReviewSliceResponse.builder() + .reviews(reviews.stream() + .map(ReviewResponse::from).toList()) + .sliceInfo(SliceInfo.builder() + .sliceSize(size) + .lastId(lastId) + .build()) + .build(); + } +} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/ReviewService.java b/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/ReviewService.java index adb7b604..f542eced 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/ReviewService.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/ReviewService.java @@ -3,13 +3,22 @@ import java.util.List; import lombok.AccessLevel; import lombok.RequiredArgsConstructor; +import org.ktc2.cokaen.wouldyouin._common.exception.EntityNotFoundException; +import org.ktc2.cokaen.wouldyouin._common.exception.UnauthorizedException; +import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.ReviewEventResponse; +import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.ReviewEventSliceResponse; import org.ktc2.cokaen.wouldyouin.event.application.EventService; +import org.ktc2.cokaen.wouldyouin.event.persist.Event; import org.ktc2.cokaen.wouldyouin.member.application.MemberService; -import org.ktc2.cokaen.wouldyouin.review.application.dto.ReviewCreateRequest; -import org.ktc2.cokaen.wouldyouin.review.application.dto.ReviewEditRequest; -import org.ktc2.cokaen.wouldyouin.review.application.dto.ReviewResponse; +import org.ktc2.cokaen.wouldyouin.reservation.application.ReservationService; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewCreateRequest; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewEditRequest; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewResponse; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewSliceResponse; import org.ktc2.cokaen.wouldyouin.review.persist.Review; import org.ktc2.cokaen.wouldyouin.review.persist.ReviewRepository; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -20,43 +29,80 @@ public class ReviewService { private final ReviewRepository reviewRepository; private final EventService eventService; private final MemberService memberService; + private final ReservationService reservationService; @Transactional(readOnly = true) public ReviewResponse getById(Long reviewId) { - Review target = reviewRepository.findById(reviewId).orElseThrow(RuntimeException::new); - return ReviewResponse.from(target); + return ReviewResponse.from(getByIdOrThrow(reviewId)); } @Transactional(readOnly = true) - public List getAllByMemberId(Long memberId) { - return reviewRepository.findByMemberId(memberId).stream().map(ReviewResponse::from) - .toList(); + public ReviewSliceResponse getAllByMemberId(Long memberId, Pageable pageable, Long oldLastId) { + Slice reviews = reviewRepository.findByMemberIdOrderByReviewIdDesc(memberId, + oldLastId, pageable); + Long newLastId = getLastId(reviews, oldLastId); + return ReviewSliceResponse.from(reviews, reviews.getSize(), newLastId); + } + + @Transactional(readOnly = true) + public ReviewSliceResponse getAllByEventId(Long eventId, Pageable pageable, Long oldLastId) { + Slice reviews = reviewRepository.findByEventIdOrderByReviewIdDesc(eventId, + oldLastId, pageable); + Long newLastId = getLastId(reviews, oldLastId); + return ReviewSliceResponse.from(reviews, reviews.getSize(), newLastId); + } + + private Long getLastId(Slice reviews, Long oldLastId) { + if (reviews.hasContent()) { + return reviews.getContent().getLast().getId(); + } + return oldLastId; } @Transactional(readOnly = true) - public List getAllByEventId(Long eventId) { - return reviewRepository.findByEventId(eventId).stream().map(ReviewResponse::from) - .toList(); + public ReviewEventSliceResponse getUnreviewedEventsByMemberId(Long memberId, Pageable pageable, + Long beforeLastId) { + Slice unreviewedEvents = reviewRepository.findUnreviewedEventsByMemberId(memberId, + beforeLastId, pageable); + Long newLastId = EventService.getLastId(unreviewedEvents, beforeLastId); + List responses = unreviewedEvents.stream() + .map(ReviewEventResponse::from).toList(); + return ReviewEventSliceResponse.from(responses, unreviewedEvents.getSize(), newLastId); } @Transactional - public ReviewResponse create(Long eventId, ReviewCreateRequest reviewCreateRequest) { - Review review = reviewRepository.save(reviewCreateRequest.toEntity()); - review.setMember(memberService.getByIdOrThrow(reviewCreateRequest.getMemberId())); - review.setEvent(eventService.getByIdOrThrow(eventId)); + public ReviewResponse create(Long memberId, ReviewCreateRequest reviewCreateRequest) { + reservationService.validateByMemberIdAndEventId(memberId, reviewCreateRequest.getEventId()); + Review review = reviewRepository.save( + reviewCreateRequest.toEntity( + memberService.getByIdOrThrow(memberId), + eventService.getByIdOrThrow(reviewCreateRequest.getEventId()))); return ReviewResponse.from(review); } @Transactional - public ReviewResponse update(Long reviewId, ReviewEditRequest reviewEditRequest) { - Review target = reviewRepository.findById(reviewId).orElseThrow(RuntimeException::new); + public ReviewResponse update(Long memberId, Long reviewId, + ReviewEditRequest reviewEditRequest) { + Review target = getByIdOrThrow(reviewId); + validateMemberId(memberId, target); target.updateFrom(reviewEditRequest); return ReviewResponse.from(target); } @Transactional - public void delete(Long reviewId) { - reviewRepository.findById(reviewId).orElseThrow(RuntimeException::new); + public void delete(Long memberId, Long reviewId) { + validateMemberId(memberId, getByIdOrThrow(reviewId)); reviewRepository.deleteById(reviewId); } + + private Review getByIdOrThrow(Long id) { + return reviewRepository.findById(id) + .orElseThrow(() -> new EntityNotFoundException("해당하는 리뷰를 찾을 수 없습니다.")); + } + + private void validateMemberId(Long memberId, Review review) { + if (!memberId.equals(review.getMember().getId())) { + throw new UnauthorizedException("member ID가 리뷰의 member ID와 일치하지 않습니다."); + } + } } diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/dto/ReviewCreateRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/dto/ReviewCreateRequest.java deleted file mode 100644 index b90571ce..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/dto/ReviewCreateRequest.java +++ /dev/null @@ -1,21 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.review.application.dto; - -import lombok.Builder; -import lombok.Getter; -import org.ktc2.cokaen.wouldyouin.review.persist.Review; - -@Getter -@Builder -public class ReviewCreateRequest { - - private Long memberId; - private int score; - private String content; - - public Review toEntity() { - return Review.builder() - .score(this.score) - .content(this.content) - .build(); - } -} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/dto/ReviewEditRequest.java b/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/dto/ReviewEditRequest.java deleted file mode 100644 index f850e0b2..00000000 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/review/application/dto/ReviewEditRequest.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.ktc2.cokaen.wouldyouin.review.application.dto; - -import lombok.Builder; -import lombok.Getter; - -@Getter -@Builder(toBuilder = true) -public class ReviewEditRequest { - - private Integer score; - private String content; -} diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/review/persist/Review.java b/src/main/java/org/ktc2/cokaen/wouldyouin/review/persist/Review.java index d61b0c86..a3db8902 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/review/persist/Review.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/review/persist/Review.java @@ -1,8 +1,8 @@ package org.ktc2.cokaen.wouldyouin.review.persist; -import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -13,29 +13,34 @@ import java.util.Optional; import lombok.AccessLevel; import lombok.Builder; +import lombok.EqualsAndHashCode; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; +import lombok.ToString; import org.ktc2.cokaen.wouldyouin.event.persist.Event; import org.ktc2.cokaen.wouldyouin.member.persist.Member; -import org.ktc2.cokaen.wouldyouin.review.application.dto.ReviewEditRequest; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewEditRequest; @Getter @Setter +@EqualsAndHashCode +@ToString @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity public class Review { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) + @Setter(AccessLevel.NONE) @Column(name = "review_id") private Long id; - @ManyToOne(cascade = CascadeType.ALL) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "member_id") private Member member; - @ManyToOne(cascade = CascadeType.ALL) + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "event_id") private Event event; diff --git a/src/main/java/org/ktc2/cokaen/wouldyouin/review/persist/ReviewRepository.java b/src/main/java/org/ktc2/cokaen/wouldyouin/review/persist/ReviewRepository.java index bd430639..af631e54 100644 --- a/src/main/java/org/ktc2/cokaen/wouldyouin/review/persist/ReviewRepository.java +++ b/src/main/java/org/ktc2/cokaen/wouldyouin/review/persist/ReviewRepository.java @@ -1,13 +1,28 @@ package org.ktc2.cokaen.wouldyouin.review.persist; -import java.util.List; +import org.ktc2.cokaen.wouldyouin.event.persist.Event; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; import org.springframework.stereotype.Repository; @Repository public interface ReviewRepository extends JpaRepository { - List findByMemberId(Long memberId); + @Query("SELECT R FROM Review R JOIN FETCH R.member JOIN FETCH R.event " + + "WHERE R.member.id = :memberId AND R.id < :lastId " + + "ORDER BY R.id DESC") + Slice findByMemberIdOrderByReviewIdDesc(Long memberId, Long lastId, Pageable pageable); - List findByEventId(Long eventId); + @Query("SELECT R FROM Review R JOIN FETCH R.member JOIN FETCH R.event " + + "WHERE R.event.id = :eventId AND R.id < :lastId " + + "ORDER BY R.id DESC") + Slice findByEventIdOrderByReviewIdDesc(Long eventId, Long lastId, Pageable pageable); + + @Query("select r, e from Review r Join fetch r.event e " + + "where r.member.id = :memberId " + + "And e.id < :lastId " + + "And e.id not in (select rv.event.id from Review rv where rv.member.id = :memberId) ") + Slice findUnreviewedEventsByMemberId(Long memberId, Long lastId, Pageable pageable); } diff --git a/src/main/resources/application-create.yml b/src/main/resources/application-create.yml index 72e6a118..477bcadd 100644 --- a/src/main/resources/application-create.yml +++ b/src/main/resources/application-create.yml @@ -7,6 +7,5 @@ spring: activate: on-profile: create jpa: - show-sql: true hibernate: - ddl-auto: update + ddl-auto: create diff --git a/src/main/resources/application-deploy.yml b/src/main/resources/application-deploy.yml index 99a9f9e6..329c63a0 100644 --- a/src/main/resources/application-deploy.yml +++ b/src/main/resources/application-deploy.yml @@ -7,9 +7,7 @@ spring: activate: on-profile: deploy jpa: - show-sql: false hibernate: - ddl-auto: none - -# log4j log level setting -logging.level.org.ktc2.cokaen.wouldyouin: info \ No newline at end of file + ddl-auto: update + datasource: + url: ${DATASOURCE_URL}?useSSL=true&useUnicode=true&serverTimezone=Asia/Seoul \ No newline at end of file diff --git a/src/main/resources/application-dev.yml b/src/main/resources/application-dev.yml index d0202301..36fa911f 100644 --- a/src/main/resources/application-dev.yml +++ b/src/main/resources/application-dev.yml @@ -7,6 +7,5 @@ spring: activate: on-profile: dev jpa: - show-sql: true hibernate: - ddl-auto: update + ddl-auto: create-drop \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index e76ba79b..258d67c9 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -4,8 +4,11 @@ spring: application.name: WouldYouIn - # 임시 프로젝트 url - wouldyouin-domain-name: http://52.78.71.136/ + servlet: + multipart: + max-file-size: 10MB + max-request-size: 10MB + profiles: default: dev @@ -18,38 +21,41 @@ spring: restart: enabled: false - # h2 database settings - h2: - console: - enabled: true - datasource: - url: jdbc:h2:mem:test - driver-class-name: org.h2.Driver + driver-class-name: com.mysql.cj.jdbc.Driver + url: ${DATASOURCE_URL}?useSSL=false&allowPublicKeyRetrieval=true&useUnicode=true&serverTimezone=Asia/Seoul + username: ${DATASOURCE_USERNAME} + password: ${DATASOURCE_PASSWORD} jpa: + database: mysql + database-platform: org.hibernate.dialect.MySQLDialect properties: hibernate: format_sql: true default_batch_fetch_size: 100 show-sql: true - hibernate.ddl-auto: create # log4j log level setting logging.level.org.ktc2.cokaen.wouldyouin: debug # for image storage path image: + api-url: ${SERVER_DOMAIN_NAME}/api/images upload: - common-path: src/main/resources/static + parent-path: ${IMAGE_UPLOAD_PATH} curation: - sub-path: images/curation + child-path: curation member: - sub-path: images/member + child-path: member event: - sub-path: images/event + child-path: event ad: - sub-path: images/ad + child-path: advertisement + thumbnail: + child-path: thumbnail + width: 130 + height: 90 # for jwt jwt: @@ -70,7 +76,7 @@ oauth: client: id: ${KAKAO_CLIENT_ID} secret: ${KAKAO_CLIENT_SECRET} - redirect_uri: ${KAKAO_REDIRECT_URI} + redirect_uri: ${SERVER_DOMAIN_NAME}/api/auth/social/redirect/kakao google: uri: login: @@ -82,14 +88,14 @@ oauth: client: id: ${GOOGLE_CLIENT_ID} secret: ${GOOGLE_CLIENT_SECRET} - redirect_uri: ${GOOGLE_REDIRECT_URI} + redirect_uri: ${SERVER_DOMAIN_NAME}/api/auth/social/redirect/google scope: - email - profile payment: - "approval_url": "http://localhost:8080/api/events" - "cancel_url": "http://localhost:8080/api/events" - "fail_url": "http://localhost:8080/api/events" + "approval_url": ${SERVER_DOMAIN_NAME}/api/events + "cancel_url": ${SERVER_DOMAIN_NAME}/api/events + "fail_url": ${SERVER_DOMAIN_NAME}/api/events "secret_key": ${KAKAOPAY_SECRET_KEY} - "kakao_pay_single_payment_url": "/online/v1/payment/ready" - "kakao_pay_request_host": "open-api.kakaopay.com" \ No newline at end of file + "kakao_pay_single_payment_url": online/v1/payment/ready + "kakao-pay-request-host": https://open-api.kakaopay.com \ No newline at end of file diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/WouldYouInApplicationTests.java b/src/test/java/org/ktc2/cokaen/wouldyouin/WouldYouInApplicationTests.java index b4ff92e9..188f65e8 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/WouldYouInApplicationTests.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/WouldYouInApplicationTests.java @@ -6,8 +6,7 @@ @SpringBootTest class WouldYouInApplicationTests { - @Test - void contextLoads() { - } - +// @Test +// void contextLoads() { +// } } diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/TestData.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/TestData.java deleted file mode 100644 index d1bdbf13..00000000 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/TestData.java +++ /dev/null @@ -1,375 +0,0 @@ -package org.ktc2.cokaen.wouldyouin._global; - -import java.time.LocalDateTime; -import java.util.List; -import org.ktc2.cokaen.wouldyouin.Image.persist.CurationImage; -import org.ktc2.cokaen.wouldyouin.Image.persist.EventImage; -import org.ktc2.cokaen.wouldyouin.Image.persist.MemberImage; -import org.ktc2.cokaen.wouldyouin._common.api.SliceInfo; -import org.ktc2.cokaen.wouldyouin._common.vo.Area; -import org.ktc2.cokaen.wouldyouin._common.vo.Category; -import org.ktc2.cokaen.wouldyouin._common.vo.Location; -import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationCardRequest; -import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationCardResponse; -import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationCreateRequest; -import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationEditRequest; -import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationResponse; -import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationSliceResponse; -import org.ktc2.cokaen.wouldyouin.curation.persist.Curation; -import org.ktc2.cokaen.wouldyouin.event.api.dto.EventCreateRequest; -import org.ktc2.cokaen.wouldyouin.event.api.dto.EventEditRequest; -import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.CurationEventResponse; -import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.ReservationEventResponse; -import org.ktc2.cokaen.wouldyouin.event.persist.Event; -import org.ktc2.cokaen.wouldyouin.member.application.dto.relation.ReservationMemberResponse; -import org.ktc2.cokaen.wouldyouin.member.application.dto.relationResponse.CurationCuratorResponse; -import org.ktc2.cokaen.wouldyouin.member.persist.AccountType; -import org.ktc2.cokaen.wouldyouin.member.persist.Curator; -import org.ktc2.cokaen.wouldyouin.member.persist.Host; -import org.ktc2.cokaen.wouldyouin.member.persist.Member; -import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; -import org.ktc2.cokaen.wouldyouin.reservation.api.dto.ReservationRequest; -import org.ktc2.cokaen.wouldyouin.reservation.api.dto.ReservationResponse; -import org.ktc2.cokaen.wouldyouin.reservation.api.dto.ReservationSliceResponse; -import org.ktc2.cokaen.wouldyouin.reservation.persist.Reservation; -import org.springframework.test.util.ReflectionTestUtils; - -public class TestData { - - public static SliceInfo createSliceInfo() { - return SliceInfo.builder() - .sliceSize(10) - .lastId(100L) - .build(); - } - - public static class ImageDomain { - - public static MemberImage createValidMemberImage(Long id) { - MemberImage ret = MemberImage.builder() - .url("memberImageUrl") - .size(10L) - .extension(".jpg") - .build(); - ReflectionTestUtils.setField(ret, "id", id); - return ret; - } - - public static EventImage createValidEventImage(Long id) { - EventImage ret = EventImage.builder() - .url("eventImageUrl") - .size(10L) - .extension(".jpg") - .build(); - ReflectionTestUtils.setField(ret, "id", id); - return ret; - } - - public static CurationImage createValidCurationImage(Long id) { - CurationImage ret = CurationImage.builder() - .url("curationImageUrl") - .size(10L) - .extension(".jpg") - .build(); - ReflectionTestUtils.setField(ret, "id", id); - return ret; - } - } - - public static class MemberDomain { - - public static final long validMemberId = 1L; - public static final long validCuratorId = 2L; - public static final long validHostId = 3L; - public static final long validWelcomeMemberId = 4L; - - public static Member createValidMember() { - MemberImage memberImage = ImageDomain.createValidMemberImage(validMemberId); - Member ret = Member.builder() - .accountType(AccountType.kakao) - .email("member1@example.com") - .nickname("nick_normal_123") - .phone("010-1112-2233") - .profileImage(memberImage) - .area(Area.광주) - .gender("Men") - .socialId("100100100100100") - .build(); - ReflectionTestUtils.setField(ret, "Id", validMemberId); - ReflectionTestUtils.setField(ret, "memberType", MemberType.normal); - ReflectionTestUtils.setField(memberImage, "baseMember", ret); - return ret; - } - - public static Curator createValidCurator() { - MemberImage memberImage = ImageDomain.createValidMemberImage(validCuratorId); - Curator ret = Curator.curatorBuilder() - .accountType(AccountType.google) - .email("curator1@example.com") - .nickname("nick_curator_12") - .phone("010-4545-6767") - .profileImage(memberImage) - .area(Area.광주) - .gender("Women") - .socialId("200200200200200") - .build(); - ReflectionTestUtils.setField(ret, "Id", validCuratorId); - ReflectionTestUtils.setField(ret, "intro", "큐레이터 자기소개입니다."); - ReflectionTestUtils.setField(ret, "hashtag", "#큐레이터#해시태그#입니다"); - ReflectionTestUtils.setField(memberImage, "baseMember", ret); - return ret; - } - - public static Host createValidHost() { - MemberImage memberImage = ImageDomain.createValidMemberImage(validHostId); - Host ret = Host.builder() - .email("curator1@example.com") - .nickname("nick_curator_12") - .phone("010-4545-6767") - .hashedPassword("hashed_password") - .profileImage(memberImage) - .build(); - ReflectionTestUtils.setField(ret, "Id", validHostId); - ReflectionTestUtils.setField(ret, "intro", "주최자 자기소개입니다."); - ReflectionTestUtils.setField(ret, "hashtag", "#주최자#해시태그#입니다"); - ReflectionTestUtils.setField(memberImage, "baseMember", ret); - return ret; - } - - public static Member createValidWelcomeMember() { - MemberImage memberImage = ImageDomain.createValidMemberImage(validWelcomeMemberId); - Member ret = Member.builder() - .accountType(AccountType.kakao) - .email("member2@example.com") - .nickname("nick_normal_333") - .phone("010-4414-1144") - .profileImage(memberImage) - .area(Area.서울) - .gender("Men") - .socialId("456456456456") - .build(); - ReflectionTestUtils.setField(ret, "Id", validWelcomeMemberId); - ReflectionTestUtils.setField(memberImage, "baseMember", ret); - return ret; - } - - public static ReservationMemberResponse createValidReservationMemberResponse() { - return ReservationMemberResponse.builder() - .id(validMemberId) - .email("member1@example.com") - .nickname("nick_normal_123") - .phone("010-1112-2233") - .gender("Men") - .build(); - } - } - - public static class EventDomain { - - public static Event createValidEvent() { - Event validEvent = Event.builder() - .title("title") - .content("content") - .area(Area.전체) - .location(new Location(132.0, 43.0)) - .startTime(LocalDateTime.now()) - .endTime(LocalDateTime.now()) - .price(10000) - .totalSeat(100) - .category(Category.밴드) - .build(); - validEvent.setHost(MemberDomain.createValidHost()); - return validEvent; - } - - public static EventCreateRequest createValidEventCreateRequest() { - return EventCreateRequest.builder() - .title("title") - .content("content 조홍식씨 최소글자 20자라고 해놓고 안 지켰어요.") - .area(Area.전체) - .location(new Location(132.0, 43.0)) - .startTime(LocalDateTime.of(2025, 10, 1, 9, 0)) - .endTime(LocalDateTime.of(2025, 10, 1, 10, 0)) - .price(10000) - .totalSeat(100) - .category(Category.밴드) - .imageIds(List.of()) - .build(); - } - - public static EventEditRequest createValidEventEditRequest() { - return EventEditRequest.builder() - .title("modifiedTitle") - .content("modifiedContent 조홍식씨 최소글자 20자라고 해놓고 안 지켰어요. ") - .area(Area.광주) - .location(new Location(232.0, 143.0)) - .startTime(LocalDateTime.of(2024, 10, 2, 17, 0)) - .endTime(LocalDateTime.of(2024, 10, 2, 18, 0)) - .price(20000) - .totalSeat(200) - .category(Category.뮤지컬) - .imageIds(List.of()) - .build(); - } - - public static CurationEventResponse createValidCurationEventResponse() { - return CurationEventResponse.builder() - .id(1L) - .title("title") - .location(new Location(132.0, 43.0)) - .thumbnailImageUrl("thumbnailImageUrl") - .hostProfileImageUrl("hostProfileImageUrl") - .hostNickname("nick_curator_12") - .build(); - } - - public static ReservationEventResponse createValidReservationEventResponse() { - return ReservationEventResponse.builder() - .id(1L) - .title("title") - .price(15000) - .build(); - } - } - - public static class ReservationDomain { - - public static ReservationRequest createValidReservationRequest() { - return ReservationRequest.builder() - .eventId(1L) - .price(15000) - .quantity(2) - .build(); - } - - public static Reservation createValidReservation() { - Reservation reservation = Reservation.builder() - .member(MemberDomain.createValidMember()) - .event(EventDomain.createValidEvent()) - .price(15000) - .quantity(2) - .build(); - ReflectionTestUtils.setField(reservation, "id", 1L); - return reservation; - } - - public static ReservationResponse createValidReservationResponse() { - return ReservationResponse.builder() - .id(1L) - .member(MemberDomain.createValidReservationMemberResponse()) - .event(EventDomain.createValidReservationEventResponse()) - .price(15000) - .quantity(2) - .reservationDate(LocalDateTime.of(2024, 3, 23, 0, 0)) - .build(); - } - - public static ReservationSliceResponse createValidReservationSliceResponse() { - return ReservationSliceResponse.builder() - .reservations(List.of(createValidReservationResponse())) - .sliceInfo(TestData.createSliceInfo()) - .build(); - } - } - - public static class CurationDomain { - - public static Curation createValidCuration() { - return Curation.builder() - .curator(MemberDomain.createValidCurator()) - .title("title") - .content("content") - .curationCards(List.of()) - .area(Area.전체) - .hashTag(List.of("#해시태그1", "#해시태그2")) - .events(List.of(EventDomain.createValidEvent())) - .build(); - } - - public static CurationCardRequest createValidCurationCardRequest1() { - return CurationCardRequest.builder() - .subtitle("부제목1") - .content("큐레이션 카드 내용1 입니다. 큐레이션 카드의 내용은 최소 20자 최대 1000자 입니다.") - .imageIds(List.of(1L, 2L)) - .build(); - } - - public static CurationCardRequest createValidCurationCardRequest2() { - return CurationCardRequest.builder() - .subtitle("부제목2") - .content("큐레이션 카드 내용2 입니다. 큐레이션 카드의 내용은 최소 20자 최대 1000자 입니다.") - .imageIds(List.of(3L, 4L)) - .build(); - } - - public static CurationCardResponse createCurationCardResponse1() { - return CurationCardResponse.builder() - .subtitle("부제목1") - .content("큐레이션 카드 내용1 입니다. 큐레이션 카드의 내용은 최소 20자 최대 1000자 입니다.") - .imageUrls(List.of("image1.com", "image2.com")) - .build(); - } - - public static CurationCardResponse createCurationCardResponse2() { - return CurationCardResponse.builder() - .subtitle("부제목2") - .content("큐레이션 카드 내용2 입니다. 큐레이션 카드의 내용은 최소 20자 최대 1000자 입니다.") - .imageUrls(List.of("image3.com", "image4.com")) - .build(); - } - - public static CurationCreateRequest createValidCurationCreateRequest() { - return CurationCreateRequest.builder() - .title("큐레이션 제목1") - .content("큐레이션 카드 내용1 입니다. 큐레이션 카드의 내용은 최소 20자 최대 1000자 입니다.") - .curationCards(List.of(createValidCurationCardRequest1())) - .area(Area.광주) - .hashTag(List.of("#광주밴드", "#전남대")) - .eventIds(List.of(1L, 2L)) - .build(); - } - - public static CurationEditRequest createValidCurationEditRequest() { - return CurationEditRequest.builder() - .title("큐레이션 제목2") - .content("큐레이션 내용2 입니다.") - .curationCards(List.of(createValidCurationCardRequest2())) - .area(Area.서울) - .hashTag(List.of("#서울밴드", "#서울대")) - .eventIds(List.of(3L, 4L)) - .build(); - } - - public static CurationCuratorResponse createCurationCuratorResponse() { - Curator curator = MemberDomain.createValidCurator(); - return CurationCuratorResponse.builder() - .nickname(curator.getNickname()) - .email(curator.getEmail()) - .phone(curator.getPhone()) - .profileImageUrl(curator.getProfileImageUrl()) - .intro(curator.getIntro()) - .likes(curator.getLikes()) - .hashtags(curator.getHashTagList()) - .build(); - } - - public static CurationResponse createValidCurationResponse() { - return CurationResponse.builder() - .curator(createCurationCuratorResponse()) - .title("title") - .content("content") - .curationCards(List.of(createCurationCardResponse1())) - .area(Area.전체) - .hashTag(List.of("#해시태그1", "#해시태그2")) - .eventsInfo(List.of(EventDomain.createValidCurationEventResponse())) - .build(); - } - - public static CurationSliceResponse createValidCurationSliceResponse() { - return CurationSliceResponse.builder() - .curations(List.of(createValidCurationResponse())) - .slice(TestData.createSliceInfo()) - .build(); - } - } -} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/TestUtil.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/TestUtil.java index 50cbb345..f6f35839 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/TestUtil.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/TestUtil.java @@ -9,5 +9,4 @@ public static T getLeftOrRight(T left, T right) { public static T getOrNull(T object) { return getLeftOrRight(object, null); } - } diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockCurator.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockCurator.java deleted file mode 100644 index 58488b23..00000000 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockCurator.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.ktc2.cokaen.wouldyouin._global.mockMember; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain; -import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; - -@Retention(RetentionPolicy.RUNTIME) -@WithMockCustomUser(memberId = MemberDomain.validCuratorId, memberType = MemberType.curator) -public @interface WithMockCurator { - -} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockHost.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockHost.java deleted file mode 100644 index 1805cbf8..00000000 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockHost.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.ktc2.cokaen.wouldyouin._global.mockMember; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain; -import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; - -@Retention(RetentionPolicy.RUNTIME) -@WithMockCustomUser(memberId = MemberDomain.validHostId, memberType = MemberType.host) -public @interface WithMockHost { - -} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockMember.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockMember.java deleted file mode 100644 index 0bbaf38c..00000000 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockMember.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.ktc2.cokaen.wouldyouin._global.mockMember; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain; -import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; - -@Retention(RetentionPolicy.RUNTIME) -@WithMockCustomUser(memberId = MemberDomain.validMemberId, memberType = MemberType.normal) -public @interface WithMockMember { - -} \ No newline at end of file diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockWelcomeMember.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockWelcomeMember.java deleted file mode 100644 index 1f8e877b..00000000 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockWelcomeMember.java +++ /dev/null @@ -1,12 +0,0 @@ -package org.ktc2.cokaen.wouldyouin._global.mockMember; - -import java.lang.annotation.Retention; -import java.lang.annotation.RetentionPolicy; -import org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain; -import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; - -@Retention(RetentionPolicy.RUNTIME) -@WithMockCustomUser(memberId = MemberDomain.validWelcomeMemberId, memberType = MemberType.welcome) -public @interface WithMockWelcomeMember { - -} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockCurator1.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockCurator1.java new file mode 100644 index 00000000..b1ee3cc4 --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockCurator1.java @@ -0,0 +1,12 @@ +package org.ktc2.cokaen.wouldyouin._global.mockmember; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.curator1; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; + +@Retention(RetentionPolicy.RUNTIME) +@WithMockCustomUser(memberId = curator1.id, memberType = MemberType.curator) +public @interface WithMockCurator1 { + // TODO: mockMember 패키지명 변경 +} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockCustomUser.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockCustomUser.java similarity index 88% rename from src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockCustomUser.java rename to src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockCustomUser.java index 742a7324..ae26e6e5 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockCustomUser.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockCustomUser.java @@ -1,4 +1,4 @@ -package org.ktc2.cokaen.wouldyouin._global.mockMember; +package org.ktc2.cokaen.wouldyouin._global.mockmember; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockCustomUserSecurityContextFactory.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockCustomUserSecurityContextFactory.java similarity index 95% rename from src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockCustomUserSecurityContextFactory.java rename to src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockCustomUserSecurityContextFactory.java index 4c164a2b..0e7e0773 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockMember/WithMockCustomUserSecurityContextFactory.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockCustomUserSecurityContextFactory.java @@ -1,4 +1,4 @@ -package org.ktc2.cokaen.wouldyouin._global.mockMember; +package org.ktc2.cokaen.wouldyouin._global.mockmember; import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; import org.ktc2.cokaen.wouldyouin.auth.persist.CustomUserDetails; diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockHost1.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockHost1.java new file mode 100644 index 00000000..783da542 --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockHost1.java @@ -0,0 +1,12 @@ +package org.ktc2.cokaen.wouldyouin._global.mockmember; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.host1; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; + +@Retention(RetentionPolicy.RUNTIME) +@WithMockCustomUser(memberId = host1.id, memberType = MemberType.host) +public @interface WithMockHost1 { + +} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockMember1.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockMember1.java new file mode 100644 index 00000000..62c0957b --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockMember1.java @@ -0,0 +1,12 @@ +package org.ktc2.cokaen.wouldyouin._global.mockmember; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.normal1; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; + +@Retention(RetentionPolicy.RUNTIME) +@WithMockCustomUser(memberId = normal1.id, memberType = MemberType.normal) +public @interface WithMockMember1 { + +} \ No newline at end of file diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockWelcomeMember1.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockWelcomeMember1.java new file mode 100644 index 00000000..ec241e3f --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/mockmember/WithMockWelcomeMember1.java @@ -0,0 +1,12 @@ +package org.ktc2.cokaen.wouldyouin._global.mockmember; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.welcome1; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; + +@Retention(RetentionPolicy.RUNTIME) +@WithMockCustomUser(memberId = welcome1.id, memberType = MemberType.welcome) +public @interface WithMockWelcomeMember1 { + +} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/AdvertisementData.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/AdvertisementData.java new file mode 100644 index 00000000..7a7583d7 --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/AdvertisementData.java @@ -0,0 +1,5 @@ +package org.ktc2.cokaen.wouldyouin._global.testdata; + +public class AdvertisementData { + +} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/CommonData.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/CommonData.java new file mode 100644 index 00000000..7698524b --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/CommonData.java @@ -0,0 +1,92 @@ +package org.ktc2.cokaen.wouldyouin._global.testdata; + +import org.ktc2.cokaen.wouldyouin._common.api.SliceInfo; +import org.ktc2.cokaen.wouldyouin._global.testdata.CurationData.R.curation1; +import org.ktc2.cokaen.wouldyouin._global.testdata.EventData.R.event1; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.curator1; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.host1; +import org.ktc2.cokaen.wouldyouin._global.testdata.ReservationData.R.reservation1; +import org.ktc2.cokaen.wouldyouin._global.testdata.ReviewData.R.review1; + +public class CommonData { + + public static class path { + + public static final String domainUrl = "https://wouldyouin.store"; + } + + public static class sliceInfo { + public static class event{ + public static SliceInfo get(){ + return SliceInfo.builder() + .sliceSize(10) + .lastId(event1.id) + .build(); + } + } + + public static class curation { + + public static SliceInfo get() { + return SliceInfo.builder() + .sliceSize(10) + .lastId(curation1.id) + .build(); + } + } + + public static class like { + public static class normal1 { + public static class hostLikes { + public static SliceInfo get() { + return SliceInfo.builder() + .sliceSize(1) + .lastId(host1.id) + .build(); + } + } + public static class curatorLikes { + public static SliceInfo get() { + return SliceInfo.builder() + .sliceSize(1) + .lastId(R.curator1.id) + .build(); + } + } + } + public static class curator1 { + public static class hostLikes { + public static SliceInfo get() { + return SliceInfo.builder() + .sliceSize(1) + .lastId(host1.id) + .build(); + } + } + } + + + } + + public static class reservation { + + public static SliceInfo get() { + return SliceInfo.builder() + .sliceSize(10) + .lastId(reservation1.id) + .build(); + } + } + + public static class review { + + public static SliceInfo get() { + return SliceInfo.builder() + .sliceSize(10) + .lastId(review1.id) + .build(); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/CurationData.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/CurationData.java new file mode 100644 index 00000000..99fa3240 --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/CurationData.java @@ -0,0 +1,401 @@ +package org.ktc2.cokaen.wouldyouin._global.testdata; + +import static org.ktc2.cokaen.wouldyouin._global.testdata.EventData.response.curationEvent.createValidCurationEventResponse; + +import java.time.LocalDateTime; +import java.util.List; +import org.ktc2.cokaen.wouldyouin._common.vo.Area; +import org.ktc2.cokaen.wouldyouin._global.testdata.CurationData.curation1.entity; +import org.ktc2.cokaen.wouldyouin._global.testdata.EventData.R.event1; +import org.ktc2.cokaen.wouldyouin._global.testdata.EventData.R.event2; +import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationCardRequest; +import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationCardResponse; +import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationCreateRequest; +import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationEditRequest; +import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationResponse; +import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationSliceResponse; +import org.ktc2.cokaen.wouldyouin.curation.persist.Curation; +import org.ktc2.cokaen.wouldyouin.curation.persist.CurationCard; +import org.ktc2.cokaen.wouldyouin.event.persist.Event; +import org.ktc2.cokaen.wouldyouin.image.persist.CurationCardImage; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.test.util.ReflectionTestUtils; + +public class CurationData { + + public static class R { + + public static class curationCard1 { + + public static final Long id = 351L; + public static final String subtitle = "큐레이션 카드 부제목1"; + public static final String content = "큐레이션 카드 내용1 입니다. 큐레이션 카드의 내용은 최소 20자 최대 1000자 입니다."; + public static final List images = List.of(ImageData.curation1.entity.get()); + public static final List imageIds = List.of(ImageData.R.curation1.id); + public static final List imageUrls = List.of(ImageData.R.curation1.url); + } + + public static class curationCard2 { + + public static final Long id = 352L; + public static final String subtitle = "큐레이션 카드 부제목2"; + public static final String content = "큐레이션 카드 내용2 입니다. 큐레이션 카드의 내용은 최소 20자 최대 1000자 입니다."; + public static final List images = List.of(ImageData.curation2.entity.get()); + public static final List imageIds = List.of(ImageData.R.curation2.id); + public static final List imageUrls = List.of(ImageData.R.curation2.url); + } + + public static class curation1 { + + public static final Long id = 301L; + public static final String title = "큐레이션 제목1"; + public static final String content = "큐레이션 본문1 입니다. 큐레이션의 본문은 최소 20자 최대 1000자 입니다."; + public static final List curationCards = List.of( + CurationData.curationCard1.entity.get() + ); + public static final List curationCardRequests = List.of( + CurationData.curationCard1.request.get() + ); + public static final List curationCardResponses = List.of( + CurationData.curationCard1.response.get() + ); + public static final int page = 0; + public static final int pageSize = 10; + public static final Long lastId = 100L; + public static final PageRequest pageable = PageRequest.of(0, 10); + public static final Area area = Area.전체; + public static final List hashtags = List.of("#큐레이션", "#해시태그"); + public static final List eventIds = List.of(event1.id); + public static final List events = List.of(EventData.event1.entity.get()); + public static final LocalDateTime createdDate = LocalDateTime.of(2023, 3, 23, 0, 0); + public static final LocalDateTime modifiedDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + + public static class curation2 { + + public static final Long id = 301L; + public static final String title = "큐레이션 제목 수정"; + public static final String content = "수정된 큐레이션 본문 입니다. 큐레이션의 본문은 최소 20자 최대 1000자 입니다."; + public static final List curationCards = List.of( + CurationData.curationCard2.entity.get() + ); + public static final List curationCardRequests = List.of( + CurationData.curationCard2.request.get() + ); + public static final List curationCardResponses = List.of( + CurationData.curationCard2.response.get() + ); + public static final int page = 0; + public static final int pageSize = 20; + public static final Long lastId = 50L; + public static final PageRequest pageable = PageRequest.of(0, 10); + public static final Area area = Area.광주; + public static final List hashtags = List.of("수정 해시태그"); + public static final List eventIds = List.of(event2.id); + public static final List events = List.of(EventData.event1.entity.get()); + public static final LocalDateTime createdDate = LocalDateTime.of(2023, 3, 23, 0, 0); + public static final LocalDateTime modifiedDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + } + + public static class curationCard1 { + + public static class entity { + + public static CurationCard get() { + CurationCard validCurationCard1 = CurationCard.builder() + .subtitle(R.curationCard1.subtitle) + .content(R.curationCard1.content) + .curation(CurationData.curation1.entity.get()) + .images(R.curationCard1.images) + .build(); + ReflectionTestUtils.setField(validCurationCard1, "id", R.curationCard1.id); + return validCurationCard1; + } + } + + public static class entityWithNoId { + + public static CurationCard get() { + return CurationCard.builder() + .subtitle(R.curationCard1.subtitle) + .content(R.curationCard1.content) + .images(R.curationCard1.images) + .build(); + } + } + + public static class entityWithNoCuration { + + public static CurationCard get() { + CurationCard validCurationCard1 = CurationCard.builder() + .subtitle(R.curationCard1.subtitle) + .content(R.curationCard1.content) + .images(R.curationCard1.images) + .build(); + ReflectionTestUtils.setField(validCurationCard1, "id", R.curationCard1.id); + return validCurationCard1; + } + } + + public static class request { + + public static CurationCardRequest get() { + return CurationCardRequest.builder() + .subtitle(R.curationCard1.subtitle) + .content(R.curationCard1.content) + .imageIds(R.curationCard1.imageIds) + .build(); + } + } + + public static class response { + + public static CurationCardResponse get() { + return CurationCardResponse.builder() + .subtitle(R.curationCard1.subtitle) + .content(R.curationCard1.content) + .imageUrls(R.curationCard1.imageUrls) + .build(); + } + } + } + + public static class curationCard2 { + + public static class entity { + + public static CurationCard get() { + CurationCard validCurationCard2 = CurationCard.builder() + .subtitle(R.curationCard2.subtitle) + .content(R.curationCard2.content) + .curation(CurationData.curation1.entityWithNoCurationCard.get()) + .images(List.of(ImageData.curation2.entity.get())) + .build(); + ReflectionTestUtils.setField(validCurationCard2, "id", R.curationCard2.id); + return validCurationCard2; + } + } + + public static class entityWithNoCuration { + + public static CurationCard get() { + CurationCard validCurationCard2 = CurationCard.builder() + .subtitle(R.curationCard2.subtitle) + .content(R.curationCard2.content) + .images(List.of(ImageData.curation2.entity.get())) + .build(); + ReflectionTestUtils.setField(validCurationCard2, "id", R.curationCard2.id); + return validCurationCard2; + } + } + + public static class request { + + public static CurationCardRequest get() { + return CurationCardRequest.builder() + .subtitle(R.curationCard2.subtitle) + .content(R.curationCard2.content) + .imageIds(R.curationCard2.imageIds) + .build(); + } + } + + public static class response { + + public static CurationCardResponse get() { + return CurationCardResponse.builder() + .subtitle(R.curationCard2.subtitle) + .content(R.curationCard2.content) + .imageUrls(R.curationCard2.imageUrls) + .build(); + } + } + } + + public static class curation1 { + + public static class entity { + + public static Curation get() { + Curation validCuration = Curation.builder() + .curator(MemberData.curator1.entity.get()) + .title(R.curation1.title) + .content(R.curation1.content) + .curationCards(R.curation1.curationCards) + .area(R.curation1.area) + .hashtags(R.curation1.hashtags) + .events(R.curation1.events) + .build(); + ReflectionTestUtils.setField(validCuration, "id", R.curation1.id); + ReflectionTestUtils.setField(validCuration, "createdDate", R.curation1.createdDate); + ReflectionTestUtils.setField(validCuration, "modifiedDate", R.curation1.modifiedDate); + return validCuration; + } + } + + public static class entityWithNoCurator { + + public static Curation get() { + Curation validCuration = Curation.builder() + .title(R.curation1.title) + .content(R.curation1.content) + .curationCards(R.curation1.curationCards) + .area(R.curation1.area) + .hashtags(R.curation1.hashtags) + .events(R.curation1.events) + .build(); + ReflectionTestUtils.setField(validCuration, "id", R.curation1.id); + ReflectionTestUtils.setField(validCuration, "createdDate", R.curation1.createdDate); + ReflectionTestUtils.setField(validCuration, "modifiedDate", R.curation1.modifiedDate); + return validCuration; + } + } + + public static class entityWithNoCurationCard { + + public static Curation get() { + Curation validCuration = Curation.builder() + .curator(MemberData.curator1.entity.get()) + .title(R.curation1.title) + .content(R.curation1.content) + .area(R.curation1.area) + .hashtags(R.curation1.hashtags) + .events(R.curation1.events) + .build(); + ReflectionTestUtils.setField(validCuration, "id", R.curation1.id); + ReflectionTestUtils.setField(validCuration, "createdDate", R.curation1.createdDate); + ReflectionTestUtils.setField(validCuration, "modifiedDate", R.curation1.modifiedDate); + return validCuration; + } + } + + public static class entityWithNoEvent { + + public static Curation get() { + Curation validCuration = Curation.builder() + .curator(MemberData.curator1.entity.get()) + .title(R.curation1.title) + .content(R.curation1.content) + .curationCards(R.curation1.curationCards) + .area(R.curation1.area) + .hashtags(R.curation1.hashtags) + .events(List.of()) + .build(); + ReflectionTestUtils.setField(validCuration, "id", R.curation1.id); + ReflectionTestUtils.setField(validCuration, "createdDate", R.curation1.createdDate); + ReflectionTestUtils.setField(validCuration, "modifiedDate", R.curation1.modifiedDate); + return validCuration; + } + } + + public static class request { + + public static class create { + + public static CurationCreateRequest get() { + return CurationCreateRequest.builder() + .title(R.curation1.title) + .content(R.curation1.content) + .curationCards(R.curation1.curationCardRequests) + .area(R.curation1.area) + .hashtags(R.curation1.hashtags) + .eventIds(R.curation1.eventIds) + .build(); + } + } + + public static class edit { + + public static CurationEditRequest get() { + return CurationEditRequest.builder() + .title(R.curation2.title) + .content(R.curation2.content) + .curationCards(R.curation2.curationCardRequests) + .area(R.curation2.area) + .hashtags(R.curation2.hashtags) + .eventIds(R.curation2.eventIds) + .build(); + } + } + } + + public static class response { + + public static CurationResponse get() { + return CurationResponse.builder() + .id(R.curation1.id) + .curator(MemberData.response.curation1Curator1.get()) + .title(R.curation1.title) + .content(R.curation1.content) + .curationCards(R.curation1.curationCardResponses) + .area(R.curation1.area) + .hashtags(R.curation1.hashtags) + .eventsInfo(List.of(createValidCurationEventResponse())) + .createdTime(R.curation1.createdDate) + .modifiedDate(R.curation1.modifiedDate) + .build(); + } + + public static class slice { + + public static CurationSliceResponse get() { + return CurationSliceResponse.builder() + .curations(List.of(response.get())) + .sliceInfo(CommonData.sliceInfo.curation.get()) + .build(); + } + } + } + } + + public static class curation2 { + + public static class entity { + + public static Curation get() { + Curation validCuration = Curation.builder() + .curator(MemberData.curator1.entity.get()) + .title(R.curation2.title) + .content(R.curation2.content) + .curationCards(List.of(curationCard2.entity.get())) + .area(R.curation2.area) + .hashtags(R.curation2.hashtags) + .events(R.curation1.events) + .build(); + ReflectionTestUtils.setField(validCuration, "id", R.curation2.id); + ReflectionTestUtils.setField(validCuration, "createdDate", R.curation2.createdDate); + ReflectionTestUtils.setField(validCuration, "modifiedDate", R.curation2.modifiedDate); + return validCuration; + } + } + + public static class response { + + public static CurationResponse get() { + return CurationResponse.builder() + .id(R.curation2.id) + .curator(MemberData.response.curation1Curator1.get()) + .title(R.curation2.title) + .content(R.curation2.content) + .curationCards(R.curation2.curationCardResponses) + .area(R.curation2.area) + .hashtags(R.curation2.hashtags) + .eventsInfo(List.of(createValidCurationEventResponse())) + .createdTime(R.curation2.createdDate) + .modifiedDate(R.curation2.modifiedDate) + .thumbnailUrl(ImageData.R.curation2.url) + .build(); + } + } + } + + public static class CurationSlice { + + public static Slice get() { + return new SliceImpl<>(List.of(entity.get()), R.curation1.pageable, true); + } + } +} \ No newline at end of file diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/EventData.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/EventData.java new file mode 100644 index 00000000..cd689051 --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/EventData.java @@ -0,0 +1,353 @@ +package org.ktc2.cokaen.wouldyouin._global.testdata; + +import java.time.LocalDateTime; +import java.util.List; +import org.ktc2.cokaen.wouldyouin._common.vo.Area; +import org.ktc2.cokaen.wouldyouin._common.vo.Category; +import org.ktc2.cokaen.wouldyouin._common.vo.Location; +import org.ktc2.cokaen.wouldyouin._global.testdata.EventData.R.event1; +import org.ktc2.cokaen.wouldyouin._global.testdata.EventData.R.event1._Dto.editRequest1; +import org.ktc2.cokaen.wouldyouin._global.testdata.EventData.R.event1._Relation; +import org.ktc2.cokaen.wouldyouin._global.testdata.EventData.R.updatedEvent1; +import org.ktc2.cokaen.wouldyouin._global.testdata.EventData.event1.entity; +import org.ktc2.cokaen.wouldyouin._global.testdata.EventData.event1.response; +import org.ktc2.cokaen.wouldyouin.event.api.dto.EventCreateRequest; +import org.ktc2.cokaen.wouldyouin.event.api.dto.EventEditRequest; +import org.ktc2.cokaen.wouldyouin.event.api.dto.EventResponse; +import org.ktc2.cokaen.wouldyouin.event.api.dto.EventSliceResponse; +import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.CurationEventResponse; +import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.ReservationEventResponse; +import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.ReviewEventResponse; +import org.ktc2.cokaen.wouldyouin.event.persist.Event; +import org.ktc2.cokaen.wouldyouin.image.persist.EventImage; +import org.ktc2.cokaen.wouldyouin.member.persist.Host; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.test.util.ReflectionTestUtils; + +public class EventData { + + public static class R { + + public static class event1 { + + public static final long id = 201L; + public static final String title = "행사 제목"; + public static final String content = "행사 내용입니다. 행사의 내용은 최소 20자 최대 1000자 입니다."; + public static final Area area = Area.전체; + public static final Location location = new Location(55.0, 43.0, "광주 북구 용봉로 77"); + public static final LocalDateTime startTime = LocalDateTime.now().plusDays(3) + .plusHours(11).plusMinutes(47); + public static final LocalDateTime endTime = startTime.plusWeeks(2); + public static final Integer price = 15000; + public static final Integer totalSeat = 100; + public static final Integer leftSeat = 10; + public static final Category category = Category.밴드; + public static final Long lastId = 100L; + public static final PageRequest pageable = PageRequest.of(0, 10); + public static final String thumbnailUrl = ImageData.getThumbnailUrl( + _Relation.images().getFirst()); + public static final LocalDateTime createdDate = LocalDateTime.now().minusDays(1); + + public static class _Relation { + + public static Host host() { + return MemberData.host1.entity.get(); + } + + public static List images() { + return List.of( + ImageData.event1.entity.get(), + ImageData.event2.entity.get(), + ImageData.event3.entity.get()); + } + + public static List imageUrls() { + return List.of( + ImageData.R.event1.url, + ImageData.R.event2.url, + ImageData.R.event3.url); + } + } + + public static class _Dto { + + public static class editRequest1 { + + public static final String title = "modifiedTitle"; + public static final String content = "modifiedContent 조홍식씨 최소글자 20자라고 해놓고 안 지켰어요."; + public static final Area area = Area.광주; + public static final Location location = new Location(45.0, 143.0, + "광주 북구 용봉로 77"); + public static final LocalDateTime startTime = event1.startTime.plusDays(2); + public static final LocalDateTime endTime = startTime.plusWeeks(3); + public static final int price = 20000; + public static final int totalSeat = 200; + public static final Category category = Category.뮤지컬; + public static final List imageIds = List.of( + ImageData.R.event4.id, + ImageData.R.event5.id + ); + } + } + } + + public static class updatedEvent1{ + public static final long id = event1.id; + public static final String title = editRequest1.title; + public static final String content = editRequest1.content; + public static final Area area = editRequest1.area; + public static final Location location = editRequest1.location; + public static final LocalDateTime startTime = editRequest1.startTime; + public static final LocalDateTime endTime = editRequest1.endTime; + public static final Integer price = editRequest1.price; + public static final Integer totalSeat = editRequest1.totalSeat; + public static final Integer leftSeat = 10; + public static final Category category = editRequest1.category; + public static final Long lastId = 100L; + public static final List images = List.of(ImageData.event4.entity.get(), ImageData.event5.entity.get()); + public static final String thumbnailUrl = ImageData.getThumbnailUrl( + images.getFirst()); + public static final LocalDateTime createdDate = LocalDateTime.now().minusDays(1); + } + + public static class event2 { + + public static final long id = 202L; + public static final String title = "행사 제목"; + public static final String content = "행사 내용입니다. 행사의 내용은 최소 20자 최대 1000자 입니다."; + public static final Area area = Area.전체; + public static final Location location = new Location(55.0, 43.0, "광주 북구 용봉로 77"); + public static final LocalDateTime startTime = LocalDateTime.now().plusDays(3) + .plusHours(11).plusMinutes(47); + public static final LocalDateTime endTime = startTime.plusWeeks(2); + public static final Integer price = 15000; + public static final Integer totalSeat = 100; + public static final Integer leftSeat = 10; + public static final Category category = Category.밴드; + public static final String thumbnailUrl = ImageData.getThumbnailUrl( + _Relation.images().getFirst()); + public static final LocalDateTime createdDate = LocalDateTime.now().minusDays(1); + + public static class _Relation { + + public static Host host() { + return MemberData.host1.entity.get(); + } + + public static List images() { + return List.of( + ImageData.event1.entity.get(), + ImageData.event2.entity.get(), + ImageData.event3.entity.get()); + } + + public static List imageUrls() { + return List.of( + ImageData.R.event1.url, + ImageData.R.event2.url, + ImageData.R.event3.url); + } + } + + public static class _Dto { + + public static class editRequest1 { + + public static final String title = "modifiedTitle"; + public static final String content = "modifiedContent 조홍식씨 최소글자 20자라고 해놓고 안 지켰어요. "; + public static final Area area = Area.광주; + public static final Location location = new Location(45.0, 143.0, + "광주 북구 용봉로 77"); + public static final LocalDateTime startTime = event1.startTime.plusDays(2); + public static final LocalDateTime endTime = startTime.plusWeeks(3); + public static final int price = 20000; + public static final int totalSeat = 200; + public static final Category category = Category.뮤지컬; + public static final List imageIds = List.of( + ImageData.R.event4.id, + ImageData.R.event5.id + ); + } + } + } + } + + public static class event1 { + + public static class entity { + + public static Event get() { + Event ret = Event.builder() + .title(R.event1.title) + .content(R.event1.content) + .host(R.event1._Relation.host()) + .area(R.event1.area) + .location(R.event1.location) + .startTime(R.event1.startTime) + .endTime(R.event1.endTime) + .price(R.event1.price) + .totalSeat(R.event1.totalSeat) + .category(R.event1.category) + .images(R.event1._Relation.images()) + .thumbnailUrl(R.event1.thumbnailUrl) + .build(); + + ReflectionTestUtils.setField(ret, "id", R.event1.id); + ReflectionTestUtils.setField(ret, "leftSeat", R.event1.leftSeat); + return ret; + } + } + + public static class entityWithNoHost { + + public static Event get() { + Event ret = Event.builder() + .title(R.event1.title) + .content(R.event1.content) + .area(R.event1.area) + .location(R.event1.location) + .startTime(R.event1.startTime) + .endTime(R.event1.endTime) + .price(R.event1.price) + .totalSeat(R.event1.totalSeat) + .category(R.event1.category) + .images(R.event1._Relation.images()) + .thumbnailUrl(R.event1.thumbnailUrl) + .build(); + + ReflectionTestUtils.setField(ret, "id", R.event1.id); + ReflectionTestUtils.setField(ret, "leftSeat", R.event1.leftSeat); + ret.getHost().setEvents(List.of(ret)); + ret.getImages().forEach(image -> image.setEvent(ret)); + return ret; + } + } + + public static class request { + + public static class create { + + public static EventCreateRequest get() { + return EventCreateRequest.builder() + .title(R.event1.title) + .content(R.event1.content) + .area(R.event1.area) + .location(R.event1.location) + .startTime(R.event1.startTime) + .endTime(R.event1.endTime) + .price(R.event1.price) + .totalSeat(R.event1.totalSeat) + .category(R.event1.category) + .imageIds( + R.event1._Relation.images().stream().map(EventImage::getId).toList()) + .build(); + } + } + + public static class edit1 { + + public static EventEditRequest get() { + return EventEditRequest.builder() + .title(R.event1._Dto.editRequest1.title) + .content(R.event1._Dto.editRequest1.content) + .area(R.event1._Dto.editRequest1.area) + .location(R.event1._Dto.editRequest1.location) + .startTime(R.event1._Dto.editRequest1.startTime) + .endTime(R.event1._Dto.editRequest1.endTime) + .price(R.event1._Dto.editRequest1.price) + .totalSeat(R.event1._Dto.editRequest1.totalSeat) + .category(R.event1._Dto.editRequest1.category) + .imageIds(R.event1._Dto.editRequest1.imageIds) + .build(); + } + } + } + + public static class response { + + public static EventResponse get() { + return EventResponse.from(EventData.event1.entity.get(), + R.event1._Relation.imageUrls()); + } + + public static class slice{ + public static EventSliceResponse get(){ + return EventSliceResponse.builder() + .events(List.of(response.get())) + .sliceInfo(CommonData.sliceInfo.event.get()) + .build(); + } + } + } + } + + public static class updatedEvent1{ + public static class entity { + + public static Event get() { + Event ret = Event.builder() + .title(R.updatedEvent1.title) + .content(R.updatedEvent1.content) + .host(R.event1._Relation.host()) + .area(R.updatedEvent1.area) + .location(R.updatedEvent1.location) + .startTime(R.updatedEvent1.startTime) + .endTime(R.updatedEvent1.endTime) + .price(R.updatedEvent1.price) + .totalSeat(R.updatedEvent1.totalSeat) + .category(R.updatedEvent1.category) + .images(R.updatedEvent1.images) + .thumbnailUrl(R.updatedEvent1.thumbnailUrl) + .build(); + + ReflectionTestUtils.setField(ret, "id", R.updatedEvent1.id); + ReflectionTestUtils.setField(ret, "leftSeat", R.updatedEvent1.leftSeat); + return ret; + } + } + public static class response { + + public static EventResponse get() { + List imageUrls= List.of( + ImageData.R.event4.url, + ImageData.R.event5.url); + + return EventResponse.from(EventData.updatedEvent1.entity.get(), + imageUrls); + } + } + } + + public static class response { + + public static class reservationEvent { + + public static ReservationEventResponse createValidReservationEventResponse() { + return ReservationEventResponse.from(EventData.event1.entity.get()); + } + } + + public static class reviewEvent { + + public static ReviewEventResponse createValidReviewEventResponse() { + return ReviewEventResponse.from(EventData.event1.entity.get()); + } + } + + public static class curationEvent { + + public static CurationEventResponse createValidCurationEventResponse() { + return CurationEventResponse.from(EventData.event1.entity.get()); + } + } + } + + public static class EventSlice{ + + public static Slice get(){ + return new SliceImpl<>(List.of(entity.get()), R.event1.pageable, true); + } + } +} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ImageData.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ImageData.java new file mode 100644 index 00000000..3cf0e590 --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ImageData.java @@ -0,0 +1,819 @@ +package org.ktc2.cokaen.wouldyouin._global.testdata; + +import java.time.LocalDateTime; +import org.ktc2.cokaen.wouldyouin.image.api.dto.ImageRequest; +import org.ktc2.cokaen.wouldyouin.image.api.dto.ImageResponse; +import org.ktc2.cokaen.wouldyouin.image.persist.AdvertisementImage; +import org.ktc2.cokaen.wouldyouin.image.persist.CurationCardImage; +import org.ktc2.cokaen.wouldyouin.image.persist.EventImage; +import org.ktc2.cokaen.wouldyouin.image.persist.Image; +import org.ktc2.cokaen.wouldyouin.image.persist.MemberImage; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.util.ReflectionTestUtils; + +public class ImageData { + + // name: 파일 이름만 있어야 함 => entity, request + // 예: memberImage1.png + // url: 도메인 그거 => response + // 예: https://localhost:8080/images/member/Image1.png + + public static class childPath { + public static final String member = "/api/images/member"; + public static final String event = "/api/images/events"; + public static final String curation = "/api/images/curation"; + public static final String advertisement = "/api/images/advertisement"; + } + + public static String getThumbnailUrl(AdvertisementImage image) { + return CommonData.path.domainUrl + childPath.advertisement + "/thumbnails/" + image.getName(); + } + public static String getThumbnailUrl(CurationCardImage image) { + return CommonData.path.domainUrl + childPath.curation + "/thumbnails/" + image.getName(); + } + public static String getThumbnailUrl(EventImage image) { + return CommonData.path.domainUrl + childPath.event + "/thumbnails/" + image.getName(); + } + public static String getThumbnailUrl(MemberImage image) { + return CommonData.path.domainUrl + childPath.member + "/thumbnails/" + image.getName(); + } + + public static class R { + public static class member { + public static class normal { + public static final Long id = 1101L; + public static final String name = "memberImage1.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.member + name; + public static final Long size = 11000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class curator { + public static final Long id = 1102L; + public static final String name = "curatorImage1.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.member + name; + public static final Long size = 12000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class host { + public static final Long id = 1103L; + public static final String name = "hostImage1.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.member + name; + public static final Long size = 13000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class welcome { + public static final Long id = 1104L; + public static final String name = "welcomeImage1.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.member + name; + public static final Long size = 14000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + } + + public static class event1 { + public static final Long id = 1201L; + public static final String name = "eventImage1.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.event + name; + public static final Long size = 21000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class event2 { + public static final Long id = 1202L; + public static final String name = "eventImage2.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.event + name; + public static final Long size = 22000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class event3 { + public static final Long id = 1203L; + public static final String name = "eventImage3.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.event + name; + public static final Long size = 23000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class event4 { + public static final Long id = 1204L; + public static final String name = "eventImage4.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.event + name; + public static final Long size = 24000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class event5 { + public static final Long id = 1205L; + public static final String name = "eventImage5.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.event + name; + public static final Long size = 25000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + + public static class curation1 { + public static final Long id = 1301L; + public static final String name = "curationImage1.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.curation + name; + public static final Long size = 31000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class curation2 { + public static final Long id = 1302L; + public static final String name = "curationImage2.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.curation + name; + public static final Long size = 32000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class curation3 { + public static final Long id = 1303L; + public static final String name = "curationImage3.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.curation + name; + public static final Long size = 33000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class curation4 { + public static final Long id = 1304L; + public static final String name = "curationImage4.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.curation + name; + public static final Long size = 34000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class curation5 { + public static final Long id = 1305L; + public static final String name = "curationImage5.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.curation + name; + public static final Long size = 35000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class curation6 { + public static final Long id = 1306L; + public static final String name = "curationImage6.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.curation + name; + public static final Long size = 36000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class curation7 { + public static final Long id = 1307L; + public static final String name = "curationImage7.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.curation + name; + public static final Long size = 37000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class curation8 { + public static final Long id = 1308L; + public static final String name = "curationImage8.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.curation + name; + public static final Long size = 38000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + + public static class advertisement1 { + public static final Long id = 1401L; + public static final String name = "adImage1.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.advertisement + name; + public static final Long size = 41000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class advertisement2 { + public static final Long id = 1402L; + public static final String name = "adImage2.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.advertisement + name; + public static final Long size = 42000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + public static class advertisement3 { + public static final Long id = 1403L; + public static final String name = "adImage3.jpg"; + public static final String url = CommonData.path.domainUrl + childPath.advertisement + name; + public static final Long size = 43000L; + public static final String extension = ".jpg"; + public static final LocalDateTime createdDate = LocalDateTime.of(2024, 3, 23, 0, 0); + } + + public static class mockMultipartFile1 { + public static final String name = "images"; + public static final String originalFileName = "image1.jpg"; + public static final String contentType = MediaType.IMAGE_JPEG_VALUE; + public static final byte[] content = "imageData1".getBytes(); + } + public static class mockMultipartFile2 { + public static final String name = "images"; + public static final String originalFileName = "image2.png"; + public static final String contentType = MediaType.IMAGE_JPEG_VALUE; + public static final byte[] content = "imageData2".getBytes(); + } + } + + private static void setImageFields(Image image, Long id, LocalDateTime createdDate) { + ReflectionTestUtils.setField(image, "id", id); + ReflectionTestUtils.setField(image, "createdDate", createdDate); + } + + public static class member { + public static class normal { + public static class entity { + public static MemberImage get() { + MemberImage ret = MemberImage.builder() + .name(R.member.normal.name) + .size(R.member.normal.size) + .extension(R.member.normal.extension) + .build(); + setImageFields(ret, R.member.normal.id, R.member.normal.createdDate); + // ReflectionTestUtils.setField(ret, "baseMember", createValidMember()); + return ret; + } + } + public static class request { + public static ImageRequest get() { + return ImageRequest.builder() + .name(R.member.normal.url) + .size(R.member.normal.size) + .extension(R.member.normal.extension) + .build(); + } + } + public static class response { + + } + } + public static class curator { + public static class entity { + public static MemberImage get() { + MemberImage ret = MemberImage.builder() + .name(R.member.curator.name) + .size(R.member.curator.size) + .extension(R.member.curator.extension) + .build(); + setImageFields(ret, R.member.curator.id, R.member.curator.createdDate); + // ReflectionTestUtils.setField(ret, "baseMember", createValidCurator()); + return ret; + } + } + public static class request { + + } + public static class response { + + } + } + public static class host { + public static class entity { + public static MemberImage get() { + MemberImage ret = MemberImage.builder() + .name(R.member.host.name) + .size(R.member.host.size) + .extension(R.member.host.extension) + .build(); + setImageFields(ret, R.member.host.id, R.member.host.createdDate); + // ReflectionTestUtils.setField(ret, "baseMember", createValidHost()); + return ret; + } + } + public static class request { + + } + public static class response { + + } + } + public static class welcome { + public static class entity { + public static MemberImage get() { + MemberImage ret = MemberImage.builder() + .name(R.member.welcome.name) + .size(R.member.welcome.size) + .extension(R.member.welcome.extension) + .build(); + setImageFields(ret, R.member.welcome.id, R.member.welcome.createdDate); + // ReflectionTestUtils.setField(validMemberImage, "baseMember", createValidWelcome()); + return ret; + } + } + public static class request { + + } + public static class response { + + } + } + } + + public static class event1 { + public static class entity { + public static EventImage get() { + EventImage ret = EventImage.builder() + .name(R.event1.name) + .size(R.event1.size) + .extension(R.event1.extension) + .build(); + setImageFields(ret, R.event1.id, R.event1.createdDate); +// ReflectionTestUtils.setField(ret, "event", createValidEvent()); + return ret; + } + } + public static class request { + public static ImageRequest get() { + return ImageRequest.builder() + .name(R.event1.name) + .size(R.event1.size) + .extension(R.event1.extension) + .build(); + } + } + public static class response { + + } + } + public static class event2 { + public static class entity { + public static EventImage get() { + EventImage ret = EventImage.builder() + .name(R.event2.name) + .size(R.event2.size) + .extension(R.event2.extension) + .build(); + setImageFields(ret, R.event2.id, R.event2.createdDate); + // ReflectionTestUtils.setField(ret, "event", createValidEvent()); + return ret; + } + } + public static class request { + + } + public static class response { + + } + } + public static class event3 { + public static class entity { + public static EventImage get() { + EventImage ret = EventImage.builder() + .name(R.event3.name) + .size(R.event3.size) + .extension(R.event3.extension) + .build(); + setImageFields(ret, R.event3.id, R.event3.createdDate); + // ReflectionTestUtils.setField(ret, "event", createValidEvent()); + return ret; + } + } + public static class request { + + } + public static class response { + + } + } + public static class event4 { + public static class entity { + public static EventImage get() { + EventImage ret = EventImage.builder() + .name(R.event4.name) + .size(R.event4.size) + .extension(R.event4.extension) + .build(); + setImageFields(ret, R.event4.id, R.event4.createdDate); + // ReflectionTestUtils.setField(ret, "event", createValidEvent()); + return ret; + } + } + public static class request { + + } + public static class response { + + } + } + public static class event5 { + public static class entity { + public static EventImage get() { + EventImage ret = EventImage.builder() + .name(R.event5.name) + .size(R.event5.size) + .extension(R.event5.extension) + .build(); + setImageFields(ret, R.event5.id, R.event5.createdDate); + // ReflectionTestUtils.setField(ret, "event", createValidEvent()); + return ret; + } + } + public static class request { + + } + public static class response { + + } + } + + public static class curation1 { + public static class entity { + public static CurationCardImage get() { + CurationCardImage ret = CurationCardImage.builder() + .name(R.curation1.name) + .size(R.curation1.size) + .extension(R.curation1.extension) + .build(); + setImageFields(ret, R.curation1.id, R.curation1.createdDate); + // ReflectionTestUtils.setField(ret, "curationCard", createValidCurator()); + return ret; + } + } + public static class request { + public static ImageRequest get() { + return ImageRequest.builder() + .name(R.curation1.name) + .size(R.curation1.size) + .extension(R.curation1.extension) + .build(); + } + } + public static class response { + public static ImageResponse get() { + return ImageResponse.builder() + .id(R.curation1.id) + .url(R.curation1.url) + .size(R.curation1.size) + .extension(R.curation1.extension) + .createdDate(R.curation1.createdDate) + .build(); + } + } + } + public static class curation2 { + public static class entity { + public static CurationCardImage get() { + CurationCardImage ret = CurationCardImage.builder() + .name(R.curation2.name) + .size(R.curation2.size) + .extension(R.curation2.extension) + .build(); + setImageFields(ret, R.curation2.id, R.curation2.createdDate); + // ReflectionTestUtils.setField(ret, "curationCard", createValidCurator()); + return ret; + } + } + public static class request { + public static ImageRequest get() { + return ImageRequest.builder() + .name(R.curation2.name) + .size(R.curation2.size) + .extension(R.curation2.extension) + .build(); + } + } + public static class response { + public static ImageResponse get() { + return ImageResponse.builder() + .id(R.curation2.id) + .url(R.curation2.url) + .size(R.curation2.size) + .extension(R.curation2.extension) + .createdDate(R.curation2.createdDate) + .build(); + } + } + } + public static class curation3 { + public static class entity { + public static CurationCardImage get() { + CurationCardImage ret = CurationCardImage.builder() + .name(R.curation3.name) + .size(R.curation3.size) + .extension(R.curation3.extension) + .build(); + setImageFields(ret, R.curation3.id, R.curation3.createdDate); + // ReflectionTestUtils.setField(ret, "curationCard", createValidCurator()); + return ret; + } + } + public static class request { + public static ImageRequest get() { + return ImageRequest.builder() + .name(R.curation3.name) + .size(R.curation3.size) + .extension(R.curation3.extension) + .build(); + } + } + public static class response { + public static ImageResponse get() { + return ImageResponse.builder() + .id(R.curation3.id) + .url(R.curation3.url) + .size(R.curation3.size) + .extension(R.curation3.extension) + .createdDate(R.curation3.createdDate) + .build(); + } + } + } + public static class curation4 { + public static class entity { + public static CurationCardImage get() { + CurationCardImage ret = CurationCardImage.builder() + .name(R.curation4.name) + .size(R.curation4.size) + .extension(R.curation4.extension) + .build(); + setImageFields(ret, R.curation4.id, R.curation4.createdDate); + // ReflectionTestUtils.setField(ret, "curationCard", createValidCurator()); + return ret; + } + } + public static class request { + public static ImageRequest get() { + return ImageRequest.builder() + .name(R.curation4.name) + .size(R.curation4.size) + .extension(R.curation4.extension) + .build(); + } + } + public static class response { + public static ImageResponse get() { + return ImageResponse.builder() + .id(R.curation4.id) + .url(R.curation4.url) + .size(R.curation4.size) + .extension(R.curation4.extension) + .createdDate(R.curation4.createdDate) + .build(); + } + } + } + public static class curation5 { + public static class entity { + public static CurationCardImage get() { + CurationCardImage ret = CurationCardImage.builder() + .name(R.curation5.name) + .size(R.curation5.size) + .extension(R.curation5.extension) + .build(); + setImageFields(ret, R.curation5.id, R.curation5.createdDate); + // ReflectionTestUtils.setField(ret, "curationCard", createValidCurator()); + return ret; + } + } + public static class request { + public static ImageRequest get() { + return ImageRequest.builder() + .name(R.curation5.name) + .size(R.curation5.size) + .extension(R.curation5.extension) + .build(); + } + } + public static class response { + public static ImageResponse get() { + return ImageResponse.builder() + .id(R.curation5.id) + .url(R.curation5.url) + .size(R.curation5.size) + .extension(R.curation5.extension) + .createdDate(R.curation5.createdDate) + .build(); + } + } + } + public static class curation6 { + public static class entity { + public static CurationCardImage get() { + CurationCardImage ret = CurationCardImage.builder() + .name(R.curation6.name) + .size(R.curation6.size) + .extension(R.curation6.extension) + .build(); + setImageFields(ret, R.curation6.id, R.curation6.createdDate); + // ReflectionTestUtils.setField(ret, "curationCard", createValidCurator()); + return ret; + } + } + public static class request { + public static ImageRequest get() { + return ImageRequest.builder() + .name(R.curation6.name) + .size(R.curation6.size) + .extension(R.curation6.extension) + .build(); + } + } + public static class response { + public static ImageResponse get() { + return ImageResponse.builder() + .id(R.curation6.id) + .url(R.curation6.url) + .size(R.curation6.size) + .extension(R.curation6.extension) + .createdDate(R.curation6.createdDate) + .build(); + } + } + } + public static class curation7 { + public static class entity { + public static CurationCardImage get() { + CurationCardImage ret = CurationCardImage.builder() + .name(R.curation7.name) + .size(R.curation7.size) + .extension(R.curation7.extension) + .build(); + setImageFields(ret, R.curation7.id, R.curation7.createdDate); + // ReflectionTestUtils.setField(ret, "curationCard", createValidCurator()); + return ret; + } + } + public static class request { + public static ImageRequest get() { + return ImageRequest.builder() + .name(R.curation7.name) + .size(R.curation7.size) + .extension(R.curation7.extension) + .build(); + } + } + public static class response { + public static ImageResponse get() { + return ImageResponse.builder() + .id(R.curation7.id) + .url(R.curation7.url) + .size(R.curation7.size) + .extension(R.curation7.extension) + .createdDate(R.curation7.createdDate) + .build(); + } + } + } + public static class curation8 { + public static class entity { + public static CurationCardImage get() { + CurationCardImage ret = CurationCardImage.builder() + .name(R.curation8.name) + .size(R.curation8.size) + .extension(R.curation8.extension) + .build(); + setImageFields(ret, R.curation8.id, R.curation8.createdDate); + // ReflectionTestUtils.setField(ret, "curationCard", createValidCurator()); + return ret; + } + } + public static class request { + public static ImageRequest get() { + return ImageRequest.builder() + .name(R.curation8.name) + .size(R.curation8.size) + .extension(R.curation8.extension) + .build(); + } + } + public static class response { + public static ImageResponse get() { + return ImageResponse.builder() + .id(R.curation8.id) + .url(R.curation8.url) + .size(R.curation8.size) + .extension(R.curation8.extension) + .createdDate(R.curation8.createdDate) + .build(); + } + } + } + + public static class advertisement1 { + public static class entity { + public static AdvertisementImage get() { + AdvertisementImage ret = AdvertisementImage.builder() + .name(R.advertisement1.name) + .size(R.advertisement1.size) + .extension(R.advertisement1.extension) + .build(); + setImageFields(ret, R.advertisement1.id, R.advertisement1.createdDate); + // ReflectionTestUtils.setField(ret, "advertisement", createValidMember()); + return ret; + } + } + public static class request { + public static ImageRequest get() { + return ImageRequest.builder() + .name(R.advertisement1.name) + .size(R.advertisement1.size) + .extension(R.advertisement1.extension) + .build(); + } + } + public static class response { + public static ImageResponse get() { + return ImageResponse.builder() + .id(R.advertisement1.id) + .url(R.advertisement1.url) + .size(R.advertisement1.size) + .extension(R.advertisement1.extension) + .createdDate(R.advertisement1.createdDate) + .build(); + } + } + } + public static class advertisement2 { + public static class entity { + public static AdvertisementImage get() { + AdvertisementImage ret = AdvertisementImage.builder() + .name(R.advertisement2.name) + .size(R.advertisement2.size) + .extension(R.advertisement2.extension) + .build(); + setImageFields(ret, R.advertisement2.id, R.advertisement2.createdDate); + // ReflectionTestUtils.setField(ret, "advertisement", createValidMember()); + return ret; + } + } + public static class request { + public static ImageRequest get() { + return ImageRequest.builder() + .name(R.advertisement2.name) + .size(R.advertisement2.size) + .extension(R.advertisement2.extension) + .build(); + } + } + public static class response { + public static ImageResponse get() { + return ImageResponse.builder() + .id(R.advertisement2.id) + .url(R.advertisement2.url) + .size(R.advertisement2.size) + .extension(R.advertisement2.extension) + .createdDate(R.advertisement2.createdDate) + .build(); + } + } + } + public static class advertisement3 { + public static class entity { + public static AdvertisementImage get() { + AdvertisementImage ret = AdvertisementImage.builder() + .name(R.advertisement3.name) + .size(R.advertisement3.size) + .extension(R.advertisement3.extension) + .build(); + setImageFields(ret, R.advertisement3.id, R.advertisement3.createdDate); + // ReflectionTestUtils.setField(ret, "advertisement", createValidMember()); + return ret; + } + } + public static class request { + public static ImageRequest get() { + return ImageRequest.builder() + .name(R.advertisement3.name) + .size(R.advertisement3.size) + .extension(R.advertisement3.extension) + .build(); + } + } + public static class response { + public static ImageResponse get() { + return ImageResponse.builder() + .id(R.advertisement3.id) + .url(R.advertisement3.url) + .size(R.advertisement3.size) + .extension(R.advertisement3.extension) + .createdDate(R.advertisement3.createdDate) + .build(); + } + } + } + + public static class mockMultipartFile1 { + public static MockMultipartFile get() { + return new MockMultipartFile( + R.mockMultipartFile1.name, + R.mockMultipartFile1.originalFileName, + R.mockMultipartFile1.contentType, + R.mockMultipartFile1.content); + } + } + public static class mockMultipartFile2 { + public static MockMultipartFile get() { + return new MockMultipartFile( + R.mockMultipartFile2.name, + R.mockMultipartFile2.originalFileName, + R.mockMultipartFile2.contentType, + R.mockMultipartFile2.content); + } + } + +} \ No newline at end of file diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/LikeData.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/LikeData.java new file mode 100644 index 00000000..bc0a7664 --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/LikeData.java @@ -0,0 +1,218 @@ +package org.ktc2.cokaen.wouldyouin._global.testdata; + +import java.util.List; +import org.ktc2.cokaen.wouldyouin._global.testdata.LikeData.R.likeHost1.byNormal1; +import org.ktc2.cokaen.wouldyouin._global.testdata.LikeData.R.likeHost1.byNormal1.create; +import org.ktc2.cokaen.wouldyouin.like.api.dto.LikeResponse; +import org.ktc2.cokaen.wouldyouin.like.api.dto.LikeSliceResponse; +import org.ktc2.cokaen.wouldyouin.like.api.dto.LikeToggleResponse; +import org.ktc2.cokaen.wouldyouin.like.persist.CuratorLike; +import org.ktc2.cokaen.wouldyouin.like.persist.HostLike; +import org.ktc2.cokaen.wouldyouin.member.persist.Curator; +import org.ktc2.cokaen.wouldyouin.member.persist.Host; +import org.ktc2.cokaen.wouldyouin.member.persist.Member; + +public class LikeData { + + public static class R { + public static class likeHost1 { + public static class byNormal1 { + public static class _Relation { + public static Host targetMember() { + return MemberData.host1.entity.get(); + } + public static Member member() { + return MemberData.normal1.entity.get(); + } + } + public static class response { + public static final Long id = MemberData.R.host1.id; + public static final String nickname = MemberData.R.host1.nickname; + public static final String intro = MemberData.R.host1.intro; + public static final List hashtags = MemberData.R.host1.hashtags; + public static final String profileImageUrl = MemberData.R.host1.profileImageUrl; + } + public static class create{ + public static final Boolean created = true; + public static final Boolean deleted = false; + } + } + public static class byCurator1 { + public static class _Relation { + public static Host targetMember() { + return MemberData.host1.entity.get(); + } + public static Curator member() { + return MemberData.curator1.entity.get(); + } + } + public static class response { + public static final Long id = MemberData.R.host1.id; + public static final String nickname = MemberData.R.host1.nickname; + public static final String intro = MemberData.R.host1.intro; + public static final List hashtags = MemberData.R.host1.hashtags; + public static final String profileImageUrl = MemberData.R.host1.profileImageUrl; + } + public static class create{ + public static final Boolean created = true; + public static final Boolean deleted = false; + } + } + } + public static class likeCurator1 { + public static class byNormal1 { + public static class _Relation { + public static Curator targetMember() { + return MemberData.curator1.entity.get(); + } + public static Member member() { + return MemberData.normal1.entity.get(); + } + } + public static class response { + public static final Long id = MemberData.R.curator1.id; + public static final String nickname = MemberData.R.curator1.nickname; + public static final String intro = MemberData.R.curator1.intro; + public static final List hashtags = MemberData.R.curator1.hashtags; + public static final String profileImageUrl = MemberData.R.curator1.profileImageUrl; + } + } + } + } + + public static class likeHost1 { + public static class byNormal1 { + public static class entity { + public static HostLike get() { + return HostLike.builder() + .targetMember(R.likeHost1.byNormal1._Relation.targetMember()) + .member(R.likeHost1.byNormal1._Relation.member()) + .build(); + } + } + public static class response { + public static LikeResponse get() { + return LikeResponse.builder() + .memberId(R.likeHost1.byNormal1.response.id) + .nickname(R.likeHost1.byNormal1.response.nickname) + .intro(R.likeHost1.byNormal1.response.intro) + .hashtags(R.likeHost1.byNormal1.response.hashtags) + .profileImageUrl(R.likeHost1.byNormal1.response.profileImageUrl) + .build(); + } + } + } + public static class byCurator1 { + public static class entity { + public static HostLike get() { + return HostLike.builder() + .targetMember(R.likeHost1.byCurator1._Relation.targetMember()) + .member(R.likeHost1.byCurator1._Relation.member()) + .build(); + } + } + public static class response { + public static LikeResponse get() { + return LikeResponse.builder() + .memberId(R.likeHost1.byCurator1.response.id) + .nickname(R.likeHost1.byCurator1.response.nickname) + .intro(R.likeHost1.byCurator1.response.intro) + .hashtags(R.likeHost1.byCurator1.response.hashtags) + .profileImageUrl(R.likeHost1.byCurator1.response.profileImageUrl) + .build(); + } + } + } + } + + public static class likeCurator1 { + public static class byNormal1 { + public static class entity { + public static CuratorLike get() { + return CuratorLike.builder() + .targetMember(R.likeCurator1.byNormal1._Relation.targetMember()) + .member(R.likeCurator1.byNormal1._Relation.member()) + .build(); + } + } + public static class response { + public static LikeResponse get() { + return LikeResponse.builder() + .memberId(R.likeCurator1.byNormal1.response.id) + .nickname(R.likeCurator1.byNormal1.response.nickname) + .intro(R.likeCurator1.byNormal1.response.intro) + .hashtags(R.likeCurator1.byNormal1.response.hashtags) + .profileImageUrl(R.likeCurator1.byNormal1.response.profileImageUrl) + .build(); + } + } + } + } + public static class toggleResponse{ + public static class hostLikes { + public static class create { + + public static LikeToggleResponse get() { + return LikeToggleResponse.builder().isLiked(byNormal1.create.created).build(); + } + } + public static class delete { + + public static LikeToggleResponse get() { + return LikeToggleResponse.builder().isLiked(byNormal1.create.deleted).build(); + } + } + } + } + public static class sliceResponse { + public static class normal1 { + public static class hostLikes { + public static LikeSliceResponse get() { + return LikeSliceResponse.builder() + .likes(List.of( + LikeResponse.builder() + .memberId(R.likeHost1.byNormal1.response.id) + .nickname(R.likeHost1.byNormal1.response.nickname) + .intro(R.likeHost1.byNormal1.response.intro) + .hashtags(R.likeHost1.byNormal1.response.hashtags) + .profileImageUrl(R.likeHost1.byNormal1.response.profileImageUrl) + .build())) + .sliceInfo(CommonData.sliceInfo.like.normal1.hostLikes.get()) + .build(); + } + } + public static class curatorLikes { + public static LikeSliceResponse get() { + return LikeSliceResponse.builder() + .likes(List.of( + LikeResponse.builder() + .memberId(R.likeCurator1.byNormal1.response.id) + .nickname(R.likeCurator1.byNormal1.response.nickname) + .intro(R.likeCurator1.byNormal1.response.intro) + .hashtags(R.likeCurator1.byNormal1.response.hashtags) + .profileImageUrl(R.likeCurator1.byNormal1.response.profileImageUrl) + .build())) + .sliceInfo(CommonData.sliceInfo.like.normal1.curatorLikes.get()) + .build(); + } + } + } + public static class curator1 { + public static class hostLikes { + public static LikeSliceResponse get() { + return LikeSliceResponse.builder() + .likes(List.of( + LikeResponse.builder() + .memberId(R.likeHost1.byCurator1.response.id) + .nickname(R.likeHost1.byCurator1.response.nickname) + .intro(R.likeHost1.byCurator1.response.intro) + .hashtags(R.likeHost1.byCurator1.response.hashtags) + .profileImageUrl(R.likeHost1.byCurator1.response.profileImageUrl) + .build())) + .sliceInfo(CommonData.sliceInfo.like.curator1.hostLikes.get()) + .build(); + } + } + } + } +} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/MemberData.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/MemberData.java new file mode 100644 index 00000000..e9418187 --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/MemberData.java @@ -0,0 +1,312 @@ +package org.ktc2.cokaen.wouldyouin._global.testdata; + +import java.util.List; +import org.ktc2.cokaen.wouldyouin._common.vo.Area; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.curator1; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.curation.persist.Curation; +import org.ktc2.cokaen.wouldyouin.event.persist.Event; +import org.ktc2.cokaen.wouldyouin.image.persist.MemberImage; +import org.ktc2.cokaen.wouldyouin.like.persist.CuratorLike; +import org.ktc2.cokaen.wouldyouin.like.persist.HostLike; +import org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse.CurationCuratorResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse.ReviewMemberResponse; +import org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse.ReservationMemberResponse; +import org.ktc2.cokaen.wouldyouin.member.persist.AccountType; +import org.ktc2.cokaen.wouldyouin.member.persist.Curator; +import org.ktc2.cokaen.wouldyouin.member.persist.Gender; +import org.ktc2.cokaen.wouldyouin.member.persist.Host; +import org.ktc2.cokaen.wouldyouin.member.persist.Member; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; +import org.ktc2.cokaen.wouldyouin.reservation.persist.Reservation; +import org.ktc2.cokaen.wouldyouin.review.persist.Review; +import org.springframework.test.util.ReflectionTestUtils; + +public class MemberData { + + public static class R { + + public static class normal1 { + + public static class _Relation { + + public static List curatorLikes() { + return null; // TODO: Implement + } + + public static List hostLikes() { + return null; // TODO: Implement + } + + public static List reservations() { + return null; // TODO: Implement + } + + public static List reviews() { + return null; // TODO: Implement + } + } + + public static final long id = 101L; + public static final MemberIdentifier memberIdentifier = new MemberIdentifier(id, MemberType.normal); + public static final AccountType accountType = AccountType.kakao; + public static final String email = "member1@example.com"; + public static final String nickname = "nick_normal_123"; + public static final String phone = "010-1112-2233"; + public static final MemberImage profileImage = ImageData.member.normal.entity.get(); + public static final String profileImageUrl = ImageData.R.member.normal.url; + public static final String profileImageThumbnailUrl = ImageData.getThumbnailUrl(profileImage); + public static final Area area = Area.광주; + public static final Gender gender = Gender.MAN; + public static final String socialId = "100100100100100"; + public static final MemberType memberType = MemberType.normal; + } + + public static class curator1 { + + public static class _Relation { + + public static List curations() { + return null; // TODO: Implement + } + } + + public static final long id = 201L; + public static final MemberIdentifier memberIdentifier = new MemberIdentifier(id, MemberType.curator); + public static final AccountType accountType = AccountType.google; + public static final String email = "curator1@example.com"; + public static final String nickname = "nick_curator_12"; + public static final String phone = "010-4545-6767"; + public static final Integer likes = 10; + public static final MemberImage profileImage = ImageData.member.curator.entity.get(); + public static final String profileImageUrl = ImageData.R.member.curator.url; + public static final String profileImageThumbnailUrl = ImageData.getThumbnailUrl(profileImage); + public static final Area area = Area.광주; + public static final Gender gender = Gender.WOMAN; + public static final String socialId = "200200200200200"; + public static final String intro = "큐레이터 자기소개입니다."; + public static final List hashtags = List.of("#큐레이터", "#해시태그", "#입니다"); + } + + public static class host1 { + + public static class _Relation { + + public static List events() { + return null; // TODO: Implement + } + } + + public static final long id = 301; + public static final MemberIdentifier memberIdentifier = new MemberIdentifier(id, MemberType.host); + public static final String email = "host1@example.com"; + public static final String nickname = "nick_host_12"; + public static final String phone = "010-4545-6767"; + public static final String hashedPassword = "hashed_password"; + public static final MemberImage profileImage = ImageData.member.host.entity.get(); + public static final String profileImageUrl = ImageData.R.member.host.url; + public static final String profileImageThumbnailUrl = ImageData.getThumbnailUrl(profileImage); + public static final String intro = "주최자 자기소개입니다."; + public static final List hashtags = List.of("#주최자", "#해시태그", "#입니다"); + + } + + public static class welcome1 { + + public static final long id = 401; + public static final MemberIdentifier memberIdentifier = new MemberIdentifier(id, MemberType.welcome); + public static final AccountType accountType = AccountType.kakao; + public static final String email = "welcome2@example.com"; + public static final String nickname = "nick_normal_333"; + public static final String phone = "010-4414-1144"; + public static final MemberImage profileImage = ImageData.member.welcome.entity.get(); + public static final String profileImageUrl = ImageData.R.member.welcome.url; + public static final String profileImageThumbnailUrl = ImageData.getThumbnailUrl(profileImage); + public static final Area area = Area.서울; + public static final Gender gender = Gender.MAN; + public static final String socialId = "456456456456"; + + } + + public static class admin1 { + + public static final long id = 501L; + public static final MemberIdentifier memberIdentifier = new MemberIdentifier(id, MemberType.admin); + } + } + + public static class normal1 { + + public static class entity { + + public static Member get() { + Member ret = Member.builder() + .accountType(R.normal1.accountType) + .email(R.normal1.email) + .nickname(R.normal1.nickname) + .phone(R.normal1.phone) + .profileImage(R.normal1.profileImage) + .profileImageThumbnailUrl(R.normal1.profileImageThumbnailUrl) + .area(R.normal1.area) + .gender(R.normal1.gender) + .socialId(R.normal1.socialId) + .build(); + ReflectionTestUtils.setField(ret, "id", R.normal1.id); + ReflectionTestUtils.setField(ret, "memberType", R.normal1.memberType); + ReflectionTestUtils.setField(ret.getProfileImage(), "baseMember", ret); + return ret; + } + } + + public static class request { + + } + + public static class response { + + } + } + + public static class curator1 { + + public static class entity { + + public static Curator get() { + Curator ret = Curator.curatorBuilder() + .accountType(R.curator1.accountType) + .email(R.curator1.email) + .nickname(R.curator1.nickname) + .phone(R.curator1.phone) + .profileImage(R.curator1.profileImage) + .profileImageThumbnailUrl(R.curator1.profileImageThumbnailUrl) + .area(R.curator1.area) + .gender(R.curator1.gender) + .socialId(R.curator1.socialId) + .build(); + ReflectionTestUtils.setField(ret, "id", R.curator1.id); + ReflectionTestUtils.setField(ret, "intro", R.curator1.intro); + ReflectionTestUtils.setField(ret, "hashtags", R.curator1.hashtags); + ReflectionTestUtils.setField(ret, "likes", R.curator1.likes); + + ReflectionTestUtils.setField(ret.getProfileImage(), "baseMember", ret); + return ret; + } + } + public static class request { + + } + + public static class response { + + public static CurationCuratorResponse getCurationCuratorResponse() { + return CurationCuratorResponse.builder() + .nickname(R.curator1.nickname) + .email(R.curator1.email) + .phone(R.curator1.phone) + .profileImageUrl(R.curator1.profileImageUrl) + .intro(R.curator1.intro) + .likes(R.curator1.likes) + .hashtags(R.curator1.hashtags) + .build(); + } + } + } + + public static class host1 { + + public static class entity { + + public static Host get() { + Host ret = Host.builder() + .email(R.host1.email) + .nickname(R.host1.nickname) + .phone(R.host1.phone) + .hashedPassword(R.host1.hashedPassword) + .profileImage(R.host1.profileImage) + .profileImageThumbnailUrl(R.host1.profileImageThumbnailUrl) + .build(); + ReflectionTestUtils.setField(ret, "id", R.host1.id); + ReflectionTestUtils.setField(ret, "intro", R.host1.intro); + ReflectionTestUtils.setField(ret, "hashtags", R.host1.hashtags); +// ReflectionTestUtils.setField(ret.getProfileImage(), "baseMember", ret); + return ret; + } + } + + public static class request { + + } + + public static class response { + + } + } + + public static class welcome1 { + + public static class entity { + + public static Member get() { + Member ret = Member.builder() + .accountType(R.welcome1.accountType) + .email(R.welcome1.email) + .nickname(R.welcome1.nickname) + .phone(R.welcome1.phone) + .profileImage(R.welcome1.profileImage) + .profileImageThumbnailUrl(R.welcome1.profileImageThumbnailUrl) + .area(R.welcome1.area) + .gender(R.welcome1.gender) + .socialId(R.welcome1.socialId) + .build(); + ReflectionTestUtils.setField(ret, "id", R.welcome1.id); +// ReflectionTestUtils.setField(ret.getProfileImage(), "baseMember", ret); + return ret; + } + } + + public static class request { + + } + + public static class response { + + } + } + + public static class admin1 { + + public static class entity { + + public static final long validAdminId = 105L; + } + + public static class request { + + } + + public static class response { + + } + } + + public static class response { + + public static class reservation1Member1 { + + public static ReservationMemberResponse get() { + return ReservationMemberResponse.from(normal1.entity.get()); + } + } + public static class review1Member1 { + public static ReviewMemberResponse get(){ + return ReviewMemberResponse.from(normal1.entity.get()); + } + } + public static class curation1Curator1 { + + public static CurationCuratorResponse get() { + return CurationCuratorResponse.from(curator1.entity.get()); + } + } + } +} \ No newline at end of file diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ReservationData.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ReservationData.java new file mode 100644 index 00000000..2933e1e9 --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ReservationData.java @@ -0,0 +1,130 @@ +package org.ktc2.cokaen.wouldyouin._global.testdata; + +import java.time.LocalDateTime; +import java.util.List; +import org.ktc2.cokaen.wouldyouin._global.testdata.CurationData.R; +import org.ktc2.cokaen.wouldyouin._global.testdata.CurationData.curation1.entity; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.response.reservation1Member1; +import org.ktc2.cokaen.wouldyouin.curation.persist.Curation; +import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.ReservationEventResponse; +import org.ktc2.cokaen.wouldyouin.event.persist.Event; +import org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse.ReservationMemberResponse; +import org.ktc2.cokaen.wouldyouin.member.persist.Member; +import org.ktc2.cokaen.wouldyouin.payment.dto.KakaoPayResponse; +import org.ktc2.cokaen.wouldyouin.reservation.api.dto.KakaoPayReservationResponse; +import org.ktc2.cokaen.wouldyouin.reservation.api.dto.ReservationRequest; +import org.ktc2.cokaen.wouldyouin.reservation.api.dto.ReservationResponse; +import org.ktc2.cokaen.wouldyouin.reservation.api.dto.ReservationSliceResponse; +import org.ktc2.cokaen.wouldyouin.reservation.persist.Reservation; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Slice; +import org.springframework.data.domain.SliceImpl; +import org.springframework.test.util.ReflectionTestUtils; + +public class ReservationData { + + public static class R { + + public static final int page = 0; + public static final int pageSize = 10; + public static final Long lastId = 1L; + public static final Pageable pageable = PageRequest.of(0, 10); + + public static class reservation1 { + + public static final Long id = 1L; + public static final Integer price = _Relation.event().getPrice(); + public static final Integer quantity = 2; + public static final LocalDateTime reservationDate = LocalDateTime.of(2024, 3, 23, 0, 0); + public static final ReservationMemberResponse memberResponse = reservation1Member1.get(); + public static final ReservationEventResponse eventResponse = EventData.response.reservationEvent.createValidReservationEventResponse(); + + public static class _Relation { + + public static Member member() { + return MemberData.normal1.entity.get(); + } + + public static Event event() { + return EventData.event1.entity.get(); + } + } + } + } + + public static class reservation1 { + + public static class entity { + + public static Reservation get() { + Reservation ret = Reservation.builder() + .member(R.reservation1._Relation.member()) + .event(R.reservation1._Relation.event()) + .price(R.reservation1.price) + .quantity(R.reservation1.quantity) + .build(); + ReflectionTestUtils.setField(ret, "id", R.reservation1.id); + ReflectionTestUtils.setField(ret, "reservationDate", + R.reservation1.reservationDate); + return ret; + } + } + + public static class request { + + public static ReservationRequest get() { + return ReservationRequest.builder() + .eventId(R.reservation1._Relation.event().getId()) + .quantity(R.reservation1.quantity) + .build(); + } + } + + public static class response { + + public static ReservationResponse get() { + return ReservationResponse.from(reservation1.entity.get()); + } + + } + + public static class kakaoPayResponse { + + public static KakaoPayResponse get() { + return new KakaoPayResponse( + "10L", "nextRedirectAppUrl", "nextRedirectMobileUrl", + "nextRedirectPcUrl", "androidAppScheme", "ios_app_scheme", + LocalDateTime.of(2024, 3, 23, 0, 0)); + } + } + + public static class kakaoPayReservationResponse { + + public static ReservationResponse reservationResponse = ReservationResponse.from(reservation1.entity.get()); + public static KakaoPayResponse kakaoPayResponse = ReservationData.reservation1.kakaoPayResponse.get(); + + public static KakaoPayReservationResponse get() { + return KakaoPayReservationResponse.from(reservationResponse, kakaoPayResponse); + } + } + } + + public static class ReservationSlice { + + public static Slice get() { + return new SliceImpl<>(List.of(ReservationData.reservation1.entity.get()), PageRequest.of(0, 10), true); + } + } + + public static class sliceResponse { + + public static ReservationSliceResponse get() { + return ReservationSliceResponse.builder() + .reservations(List.of( + ReservationData.reservation1.response.get())) + .sliceInfo(CommonData.sliceInfo.reservation.get()) + .build(); + } + } +} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ReviewData.java b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ReviewData.java new file mode 100644 index 00000000..26abee5e --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/_global/testdata/ReviewData.java @@ -0,0 +1,109 @@ +package org.ktc2.cokaen.wouldyouin._global.testdata; + +import java.util.List; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.response.review1Member1; +import org.ktc2.cokaen.wouldyouin._global.testdata.ReviewData.R.review1._Dto.editRequest1; +import org.ktc2.cokaen.wouldyouin.event.api.dto.relationResonse.ReviewEventResponse; +import org.ktc2.cokaen.wouldyouin.event.persist.Event; +import org.ktc2.cokaen.wouldyouin.member.api.dto.relationResponse.ReviewMemberResponse; +import org.ktc2.cokaen.wouldyouin.member.persist.Member; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewCreateRequest; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewEditRequest; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewResponse; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewSliceResponse; +import org.ktc2.cokaen.wouldyouin.review.persist.Review; +import org.springframework.test.util.ReflectionTestUtils; + +public class ReviewData { + + public static class R { + + public static class review1 { + + public static final Long id = 1L; + public static final Integer score = 3; + public static final ReviewMemberResponse memberResponse = review1Member1.get(); + public static final ReviewEventResponse eventResponse = EventData.response.reviewEvent.createValidReviewEventResponse(); + private static final String content = "리뷰 내용입니다. 리뷰 내용은 5자 이상 50자 이하입니다"; + + public static class _Relation { + + public static Member member() { + return MemberData.normal1.entity.get(); + } + + public static Event event() { + return EventData.event1.entity.get(); + } + } + + public static class _Dto { + + public static class editRequest1 { + + public static final Integer score = 5; + public static final String content = "수정된 리뷰 내용입니다. 리뷰 내용은 5자 이상 50자 이하입니다"; + } + } + } + } + + public static class review1 { + + public static class entity { + + public static Review get() { + Review ret = Review.builder() + .member(R.review1._Relation.member()) + .event(R.review1._Relation.event()) + .score(R.review1.score) + .content(R.review1.content) + .build(); + ReflectionTestUtils.setField(ret, "id", R.review1.id); + return ret; + } + } + + public static class request { + + public static class create { + + public static ReviewCreateRequest get() { + return ReviewCreateRequest.builder() + .eventId(R.review1._Relation.event().getId()) + .score(R.review1.score) + .content(R.review1.content) + .build(); + } + } + + public static class edit1 { + + public static ReviewEditRequest get() { + return ReviewEditRequest.builder() + .score(editRequest1.score) + .content(editRequest1.content) + .build(); + } + } + } + + public static class response { + + public static ReviewResponse get() { + return ReviewResponse.from(review1.entity.get()); + } + } + } + + public static class sliceResponse { + + public static ReviewSliceResponse get() { + return ReviewSliceResponse.builder() + .reviews(List.of( + ReviewData.review1.response.get())) + .sliceInfo(CommonData.sliceInfo.review.get()) + .build(); + } + } +} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/curation/CurationCardServiceTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/curation/CurationCardServiceTest.java new file mode 100644 index 00000000..8bd2efda --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/curation/CurationCardServiceTest.java @@ -0,0 +1,85 @@ +package org.ktc2.cokaen.wouldyouin.curation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.times; + +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.ktc2.cokaen.wouldyouin._global.testdata.CurationData; +import org.ktc2.cokaen.wouldyouin._global.testdata.CurationData.R.curationCard1; +import org.ktc2.cokaen.wouldyouin._global.testdata.ImageData; +import org.ktc2.cokaen.wouldyouin._global.testdata.ImageData.R.curation1; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.curator1; +import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationCardResponse; +import org.ktc2.cokaen.wouldyouin.curation.application.CurationCardService; +import org.ktc2.cokaen.wouldyouin.curation.persist.CurationCard; +import org.ktc2.cokaen.wouldyouin.curation.persist.CurationCardRepository; +import org.ktc2.cokaen.wouldyouin.image.application.CurationImageService; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class CurationCardServiceTest { + + private CurationCardService curationCardService; + + @Mock + private CurationCardRepository curationCardRepository; + + @Mock + private CurationImageService curationImageService; + + @BeforeEach + void setUp() { + curationCardService = new CurationCardService(curationCardRepository, curationImageService); + } + + @Test + @DisplayName("큐레이션 ID를 통해 해당 하는 큐레이션을 반환한다.") + void getById() { + // given + given(curationCardRepository.findById(curationCard1.id)).willReturn(Optional.of(CurationData.curationCard1.entity.get())); + given(curationImageService.getImageUrl(ImageData.curation1.entity.get())).willReturn(ImageData.R.curation1.url); + + // when + CurationCardResponse response = curationCardService.getById(curationCard1.id); + + // then + assertThat(response).isEqualTo(CurationData.curationCard1.response.get()); + } + + @Test + @DisplayName("큐레이션 카드 DTO를 통해 큐레이션 카드를 생성한다.") + void create() { + // given + given(curationImageService.getById(curation1.id)).willReturn(ImageData.curation1.entity.get()); + given(curationCardRepository.save(CurationData.curationCard1.entityWithNoId.get())) + .willReturn(CurationData.curationCard1.entity.get()); + + // when + CurationCard response = curationCardService.create(CurationData.curationCard1.request.get()); + + // then + assertThat(response).isEqualTo(CurationData.curationCard1.entity.get()); + } + + @Test + @DisplayName("큐레이션 카 ID를 통해 해당 하는 큐레이션 카드를 삭제한다.") + void delete() { + // given + given(curationCardRepository.findById(curationCard1.id)).willReturn(Optional.of(CurationData.curationCard1.entity.get())); + + // when + curationCardService.delete(curator1.memberIdentifier, curationCard1.id); + + // then + then(curationImageService).should(times(curationCard1.images.size())).deleteImage(any(), any()); + then(curationCardRepository).should(times(1)).deleteById(curationCard1.id); + } +} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/curation/CurationControllerUnitTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/curation/CurationControllerUnitTest.java index d6fcb308..1343a924 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/curation/CurationControllerUnitTest.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/curation/CurationControllerUnitTest.java @@ -16,22 +16,20 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; import java.util.List; import java.util.Random; -import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.ktc2.cokaen.wouldyouin._common.vo.Area; -import org.ktc2.cokaen.wouldyouin._global.TestData.CurationDomain; -import org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain; -import org.ktc2.cokaen.wouldyouin._global.mockMember.WithMockCurator; -import org.ktc2.cokaen.wouldyouin._global.mockMember.WithMockHost; -import org.ktc2.cokaen.wouldyouin._global.mockMember.WithMockMember; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockCurator1; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockHost1; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockMember1; +import org.ktc2.cokaen.wouldyouin._global.testdata.CurationData; +import org.ktc2.cokaen.wouldyouin._global.testdata.CurationData.curation1; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.curator1; import org.ktc2.cokaen.wouldyouin.auth.application.JwtAuthFilter; import org.ktc2.cokaen.wouldyouin.curation.api.CurationController; -import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationCardRequest; import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationCreateRequest; import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationEditRequest; import org.ktc2.cokaen.wouldyouin.curation.application.CurationService; @@ -48,27 +46,17 @@ @WebMvcTest(CurationController.class) class CurationControllerUnitTest { - private static ObjectMapper objectMapper; - - @MockBean - private CurationService curationService; - - @MockBean - private JwtAuthFilter jwtAuthFilter; - + private final long randomId = abs(new Random().nextLong()); @Autowired private MockMvc mockMvc; - + @Autowired + private ObjectMapper objectMapper; @Autowired private WebApplicationContext context; - private static final long randomId = abs(new Random().nextLong()); - - - @BeforeAll - public static void init() { - objectMapper = new ObjectMapper(); - objectMapper.registerModule(new JavaTimeModule()); - } + @MockBean + private CurationService curationService; + @MockBean + private JwtAuthFilter jwtAuthFilter; @BeforeEach public void setup() throws Exception { @@ -80,7 +68,7 @@ public void setup() throws Exception { @Test @DisplayName("RequestParam을 통해 전달받은 지역의 큐레이션 목록을 조회한다.") - @WithMockMember + @WithMockMember1 void getCurationsByAreaOrderByCreatedDateDesc1() throws Exception { // given, when mockMvc.perform(get("/api/curations") @@ -97,7 +85,7 @@ void getCurationsByAreaOrderByCreatedDateDesc1() throws Exception { @Test @DisplayName("RequestParam을 통해 지역을 지정하지 않은 경우, 전체 지역의 큐레이션 목록을 조회한다.") - @WithMockMember + @WithMockMember1 void getCurationsByAreaOrderByCreatedDateDesc2() throws Exception { // given, when mockMvc.perform(get("/api/curations")).andDo(print()) @@ -109,8 +97,8 @@ void getCurationsByAreaOrderByCreatedDateDesc2() throws Exception { } @Test - @DisplayName("ReqeustParam을 통해 요청할 페이지에 대한 정보를 전달받아, 해당하는 호스트의 큐레이션 목록을 조회한다.") - @WithMockMember + @DisplayName("ReqeustParam을 통해 요청할 페이지에 대한 정보를 전달받아, 해당하는 큐레이터의 큐레이션 목록을 조회한다.") + @WithMockMember1 void getCurationsByCuratorIdOrderByCreatedDateDesc1() throws Exception { // given, when mockMvc.perform(get("/api/curations/curators/" + randomId) @@ -126,7 +114,7 @@ void getCurationsByCuratorIdOrderByCreatedDateDesc1() throws Exception { @Test @DisplayName("RequestParam을 통해 페이지 정보를 지정하지 않은 경우, 디폴트 값으로 해당하는 호스트의 큐레이션 목록을 조회한다.") - @WithMockMember + @WithMockMember1 void getCurationsByCuratorIdOrderByCreatedDateDesc2() throws Exception { // given, when mockMvc.perform(get("/api/curations/curators/" + randomId)).andDo(print()) @@ -139,7 +127,7 @@ void getCurationsByCuratorIdOrderByCreatedDateDesc2() throws Exception { @Test @DisplayName("큐레이션 ID를 통해 해당하는 큐레이션을 조회한다.") - @WithMockMember + @WithMockMember1 void getCurationByCurationId() throws Exception { // given, when mockMvc.perform(get("/api/curations/" + randomId)).andDo(print()) @@ -151,11 +139,12 @@ void getCurationByCurationId() throws Exception { @Test @DisplayName("RequestBody로 전달받은 정보를 통해 큐레이션을 생성한다.") - @WithMockCurator + @WithMockCurator1 void createCuration1() throws Exception { // given - ArgumentCaptor captor = ArgumentCaptor.forClass(CurationCreateRequest.class); - CurationCreateRequest request = CurationDomain.createValidCurationCreateRequest(); + ArgumentCaptor captor = ArgumentCaptor.forClass( + CurationCreateRequest.class); + CurationCreateRequest request = curation1.request.create.get(); // when mockMvc.perform(post("/api/curations") @@ -166,21 +155,23 @@ void createCuration1() throws Exception { .andExpect(status().isCreated()); // then - then(curationService).should(times(1)).create(eq(MemberDomain.validCuratorId), captor.capture()); + then(curationService).should(times(1)) + .create(eq(curator1.memberIdentifier), captor.capture()); assertThat(captor.getValue()).isEqualTo(request); } @Test @DisplayName("호스트의 권한으로는 큐레이션을 생성할 수 없다.") - @WithMockHost + @WithMockHost1 void createCuration2() throws Exception { // given, when mockMvc.perform(post("/api/curations") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(CurationDomain.createValidCurationCreateRequest()))) + .content(objectMapper.writeValueAsString(curation1.request.create.get()))) .andDo(print()) - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); // then then(curationService).shouldHaveNoInteractions(); @@ -188,15 +179,16 @@ void createCuration2() throws Exception { @Test @DisplayName("멤버의 권한으로는 큐레이션을 생성할 수 없다.") - @WithMockMember + @WithMockMember1 void createCuration3() throws Exception { // given, when mockMvc.perform(post("/api/curations") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(CurationDomain.createValidCurationCreateRequest()))) + .content(objectMapper.writeValueAsString(curation1.request.create.get()))) .andDo(print()) - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); // then then(curationService).shouldHaveNoInteractions(); @@ -204,10 +196,10 @@ void createCuration3() throws Exception { @Test @DisplayName("큐레이션 생성 시, 제목에는 빈 값이 들어갈 수 없다.") - @WithMockCurator + @WithMockCurator1 void createCuration4() throws Exception { // given - CurationCreateRequest request = CurationDomain.createValidCurationCreateRequest().toBuilder() + CurationCreateRequest request = curation1.request.create.get().toBuilder() .title("").build(); // when @@ -225,11 +217,12 @@ void createCuration4() throws Exception { @Test @DisplayName("큐레이션 생성 시, 큐레이션 카드의 부제목에는 빈 값이 들어갈 수 없다.") - @WithMockCurator + @WithMockCurator1 void createCuration5() throws Exception { // given - CurationCreateRequest request = CurationDomain.createValidCurationCreateRequest().toBuilder() - .curationCards(List.of(CurationDomain.createValidCurationCardRequest1().toBuilder().subtitle("").build())) + CurationCreateRequest request = curation1.request.create.get().toBuilder() + .curationCards( + List.of(CurationData.curationCard1.request.get().toBuilder().subtitle("").build())) .build(); // when @@ -247,11 +240,12 @@ void createCuration5() throws Exception { @Test @DisplayName("큐레이션 생성 시, 큐레이션 카드의 내용에는 빈 값이 들어갈 수 없다.") - @WithMockCurator + @WithMockCurator1 void createCuration6() throws Exception { // given - CurationCreateRequest request = CurationDomain.createValidCurationCreateRequest().toBuilder() - .curationCards(List.of(CurationDomain.createValidCurationCardRequest1().toBuilder().content(null).build())) + CurationCreateRequest request = curation1.request.create.get().toBuilder() + .curationCards( + List.of(CurationData.curationCard1.request.get().toBuilder().content(null).build())) .build(); // when @@ -269,11 +263,12 @@ void createCuration6() throws Exception { @Test @DisplayName("큐레이션 생성 시, 큐레이션 카드의 내용에는 빈 값이 들어갈 수 없다.") - @WithMockCurator + @WithMockCurator1 void createCuration7() throws Exception { // given - CurationCreateRequest request = CurationDomain.createValidCurationCreateRequest().toBuilder() - .curationCards(List.of(CurationDomain.createValidCurationCardRequest1().toBuilder().content(null).build())) + CurationCreateRequest request = curation1.request.create.get().toBuilder() + .curationCards( + List.of(CurationData.curationCard1.request.get().toBuilder().content(null).build())) .build(); // when @@ -291,11 +286,12 @@ void createCuration7() throws Exception { @Test @DisplayName("큐레이션 생성 시, 큐레이션 카드의 내용은 20자 이상 1000자 이하이어야 한다.") - @WithMockCurator + @WithMockCurator1 void createCuration8() throws Exception { // given - CurationCreateRequest request = CurationDomain.createValidCurationCreateRequest().toBuilder() - .curationCards(List.of(CurationDomain.createValidCurationCardRequest1().toBuilder().content("짧은 내용").build())) + CurationCreateRequest request = curation1.request.create.get().toBuilder() + .curationCards(List.of( + CurationData.curationCard1.request.get().toBuilder().content("짧은 내용").build())) .build(); // when @@ -313,11 +309,11 @@ void createCuration8() throws Exception { @Test @DisplayName("큐레이션 생성 시, 각 큐레이션 카드에는 이미지를 최대 5개까지 등록할 수 있다.") - @WithMockCurator + @WithMockCurator1 void createCuration9() throws Exception { // given - CurationCreateRequest request = CurationDomain.createValidCurationCreateRequest().toBuilder() - .curationCards(List.of(CurationDomain.createValidCurationCardRequest1().toBuilder() + CurationCreateRequest request = curation1.request.create.get().toBuilder() + .curationCards(List.of(CurationData.curationCard1.request.get().toBuilder() .imageIds(List.of(1L, 2L, 3L, 4L, 5L, 6L)).build())) .build(); @@ -336,10 +332,10 @@ void createCuration9() throws Exception { @Test @DisplayName("큐레이션 생성 시, 지역에는 빈 값이 들어갈 수 없다.") - @WithMockCurator + @WithMockCurator1 void createCuration10() throws Exception { // given - CurationCreateRequest request = CurationDomain.createValidCurationCreateRequest().toBuilder() + CurationCreateRequest request = curation1.request.create.get().toBuilder() .area(null).build(); // when @@ -357,10 +353,10 @@ void createCuration10() throws Exception { @Test @DisplayName("큐레이션 생성 시, 큐레이션 카드의 개수는 1개 이상 10개 이하이어야 한다.") - @WithMockCurator + @WithMockCurator1 void createCuration11() throws Exception { // given - CurationCreateRequest request = CurationDomain.createValidCurationCreateRequest().toBuilder(). + CurationCreateRequest request = curation1.request.create.get().toBuilder(). curationCards(List.of()).build(); // when @@ -378,11 +374,12 @@ void createCuration11() throws Exception { @Test @DisplayName("RequestBody로 전달받은 정보를 통해 큐레이션을 수정한다.") - @WithMockCurator + @WithMockCurator1 void updateCuration1() throws Exception { // given - ArgumentCaptor captor = ArgumentCaptor.forClass(CurationEditRequest.class); - CurationEditRequest request = CurationDomain.createValidCurationEditRequest(); + ArgumentCaptor captor = ArgumentCaptor.forClass( + CurationEditRequest.class); + CurationEditRequest request = curation1.request.edit.get(); // when mockMvc.perform(put("/api/curations/" + randomId) @@ -393,21 +390,23 @@ void updateCuration1() throws Exception { .andExpect(status().isOk()); // then - then(curationService).should(times(1)).update(eq(MemberDomain.validCuratorId), eq(randomId), captor.capture()); + then(curationService).should(times(1)) + .update(eq(curator1.memberIdentifier), eq(randomId), captor.capture()); assertThat(captor.getValue()).isEqualTo(request); } @Test @DisplayName("Host의 권한으로는 큐레이션을 수정할 수 없다.") - @WithMockHost + @WithMockHost1 void updateCuration2() throws Exception { // given, when mockMvc.perform(put("/api/curations/" + randomId) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(CurationDomain.createValidCurationEditRequest()))) + .content(objectMapper.writeValueAsString(curation1.request.edit.get()))) .andDo(print()) - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); // then then(curationService).shouldHaveNoInteractions(); @@ -415,15 +414,16 @@ void updateCuration2() throws Exception { @Test @DisplayName("Member의 권한으로는 큐레이션을 수정할 수 없다.") - @WithMockMember + @WithMockMember1 void updateCuration3() throws Exception { // given, when mockMvc.perform(put("/api/curations/" + randomId) .with(csrf()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(CurationDomain.createValidCurationEditRequest()))) + .content(objectMapper.writeValueAsString(curation1.request.edit.get()))) .andDo(print()) - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); // then then(curationService).shouldHaveNoInteractions(); @@ -431,10 +431,10 @@ void updateCuration3() throws Exception { @Test @DisplayName("큐레이션 수정 시, 제목에는 빈 값이 들어갈 수 없다.") - @WithMockCurator + @WithMockCurator1 void updateCuration4() throws Exception { // given - CurationEditRequest request = CurationDomain.createValidCurationEditRequest().toBuilder() + CurationEditRequest request = curation1.request.edit.get().toBuilder() .title(null).build(); // when @@ -452,11 +452,11 @@ void updateCuration4() throws Exception { @Test @DisplayName("큐레이션 수정 시, 큐레이션 카드의 부제목에는 빈 값이 들어갈 수 없다.") - @WithMockCurator + @WithMockCurator1 void updateCuration5() throws Exception { // given - CurationEditRequest request = CurationDomain.createValidCurationEditRequest().toBuilder() - .curationCards(List.of(CurationDomain.createValidCurationCardRequest1().toBuilder() + CurationEditRequest request = curation1.request.edit.get().toBuilder() + .curationCards(List.of(CurationData.curationCard1.request.get().toBuilder() .subtitle("").build())) .build(); @@ -475,11 +475,11 @@ void updateCuration5() throws Exception { @Test @DisplayName("큐레이션 수정 시, 큐레이션 카드의 내용에는 빈 값이 들어갈 수 없다.") - @WithMockCurator + @WithMockCurator1 void updateCuration6() throws Exception { // given - CurationEditRequest request = CurationDomain.createValidCurationEditRequest().toBuilder() - .curationCards(List.of(CurationDomain.createValidCurationCardRequest1().toBuilder() + CurationEditRequest request = curation1.request.edit.get().toBuilder() + .curationCards(List.of(CurationData.curationCard1.request.get().toBuilder() .content(null).build())) .build(); @@ -498,11 +498,11 @@ void updateCuration6() throws Exception { @Test @DisplayName("큐레이션 수정 시, 큐레이션 카드의 내용의 길이는 20자 이상 1000자 이하이어야 한다.") - @WithMockCurator + @WithMockCurator1 void updateCuration7() throws Exception { // given - CurationEditRequest request = CurationDomain.createValidCurationEditRequest().toBuilder() - .curationCards(List.of(CurationDomain.createValidCurationCardRequest1().toBuilder() + CurationEditRequest request = curation1.request.edit.get().toBuilder() + .curationCards(List.of(CurationData.curationCard1.request.get().toBuilder() .content("짧은 내용").build())) .build(); @@ -521,11 +521,11 @@ void updateCuration7() throws Exception { @Test @DisplayName("큐레이션 수정 시, 각 큐레이션 카드에는 이미지를 최대 5개 등록할 수 있다.") - @WithMockCurator + @WithMockCurator1 void updateCuration8() throws Exception { // given - CurationEditRequest request = CurationDomain.createValidCurationEditRequest().toBuilder() - .curationCards(List.of(CurationDomain.createValidCurationCardRequest1().toBuilder() + CurationEditRequest request = curation1.request.edit.get().toBuilder() + .curationCards(List.of(CurationData.curationCard1.request.get().toBuilder() .imageIds(List.of(1L, 2L, 3L, 4L, 5L, 6L)).build())) .build(); @@ -544,10 +544,10 @@ void updateCuration8() throws Exception { @Test @DisplayName("큐레이션 수정 시, 지역에는 빈 값이 들어갈 수 없다.") - @WithMockCurator + @WithMockCurator1 void updateCuration9() throws Exception { // given - CurationEditRequest request = CurationDomain.createValidCurationEditRequest().toBuilder() + CurationEditRequest request = curation1.request.edit.get().toBuilder() .area(null).build(); // when @@ -565,10 +565,10 @@ void updateCuration9() throws Exception { @Test @DisplayName("큐레이션 수정 시, 큐레이션 카드는 1개 이상 10개 이하이어야 한다.") - @WithMockCurator + @WithMockCurator1 void updateCuration10() throws Exception { // given - CurationEditRequest request = CurationDomain.createValidCurationEditRequest().toBuilder() + CurationEditRequest request = curation1.request.edit.get().toBuilder() .curationCards(List.of()).build(); // when @@ -586,7 +586,7 @@ void updateCuration10() throws Exception { @Test @DisplayName("PathVariable로 전달받은 큐레이션 ID에 해당하는 큐레이션을 삭제한다.") - @WithMockCurator + @WithMockCurator1 void deleteCuration1() throws Exception { // given, when mockMvc.perform(delete("/api/curations/" + randomId) @@ -595,18 +595,19 @@ void deleteCuration1() throws Exception { .andExpect(status().isNoContent()); // then - then(curationService).should(times(1)).delete(eq(MemberDomain.validCuratorId), eq(randomId)); + then(curationService).should(times(1)).delete(eq(curator1.memberIdentifier), eq(randomId)); } @Test @DisplayName("Host의 권한으로는 큐레이션을 삭제할 수 없다.") - @WithMockHost + @WithMockHost1 void deleteCuration2() throws Exception { // given, when mockMvc.perform(delete("/api/curations/" + randomId) .with(csrf())) .andDo(print()) - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); // then then(curationService).shouldHaveNoInteractions(); @@ -614,13 +615,14 @@ void deleteCuration2() throws Exception { @Test @DisplayName("Member의 권한으로는 큐레이션을 삭제할 수 없다.") - @WithMockMember + @WithMockMember1 void deleteCuration3() throws Exception { // given, when mockMvc.perform(delete("/api/curations/" + randomId) .with(csrf())) .andDo(print()) - .andExpect(status().isUnauthorized()); + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); // then then(curationService).shouldHaveNoInteractions(); diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/curation/CurationServiceTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/curation/CurationServiceTest.java new file mode 100644 index 00000000..5ddc8fc0 --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/curation/CurationServiceTest.java @@ -0,0 +1,221 @@ +package org.ktc2.cokaen.wouldyouin.curation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.times; + +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.ktc2.cokaen.wouldyouin._common.exception.UnauthorizedException; +import org.ktc2.cokaen.wouldyouin._global.testdata.CurationData; +import org.ktc2.cokaen.wouldyouin._global.testdata.CurationData.R.curation1; +import org.ktc2.cokaen.wouldyouin._global.testdata.EventData; +import org.ktc2.cokaen.wouldyouin._global.testdata.EventData.R.event2; +import org.ktc2.cokaen.wouldyouin._global.testdata.ImageData; +import org.ktc2.cokaen.wouldyouin._global.testdata.ImageData.R.curation2; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.curator1; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationResponse; +import org.ktc2.cokaen.wouldyouin.curation.api.dto.CurationSliceResponse; +import org.ktc2.cokaen.wouldyouin.curation.application.CurationCardService; +import org.ktc2.cokaen.wouldyouin.curation.application.CurationService; +import org.ktc2.cokaen.wouldyouin.curation.persist.Curation; +import org.ktc2.cokaen.wouldyouin.curation.persist.CurationRepository; +import org.ktc2.cokaen.wouldyouin.event.application.EventService; +import org.ktc2.cokaen.wouldyouin.image.application.CurationImageService; +import org.ktc2.cokaen.wouldyouin.member.application.CuratorService; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +class CurationServiceTest { + + private CurationService curationService; + + @Mock + private CurationRepository curationRepository; + + @Mock + private CuratorService curatorService; + + @Mock + private EventService eventService; + + @Mock + private CurationCardService curationCardService; + + @Mock + private CurationImageService curationImageService; + + @BeforeEach + void setUp() { + curationService = new CurationService(curationRepository, curatorService, eventService, curationCardService, curationImageService); + } + + @Test + @DisplayName("큐레이션 ID를 통해 해당 하는 큐레이션을 반환한다.") + void getById() { + // given + given(curationRepository.findById(curation1.id)).willReturn(Optional.of(CurationData.curation1.entity.get())); + given(curationImageService.getImageUrl(ImageData.curation1.entity.get())).willReturn(ImageData.R.curation1.url); + + // when + CurationResponse response = curationService.getById(curation1.id); + + // then + assertThat(response).isEqualTo(CurationData.curation1.response.get()); + } + + @Test + @DisplayName("해당하는 지역의 모든 큐레이션을 반환한다.") + void getAllByAreaOrderByCreatedDateDesc() { + // given + given(curationRepository.findAllByAreaOrderByCreatedDateDesc(curation1.area, curation1.lastId, curation1.pageable)) + .willReturn(CurationData.CurationSlice.get()); + given(curationImageService.getImageUrl(ImageData.curation1.entity.get())).willReturn(ImageData.R.curation1.url); + + // when + CurationSliceResponse response = + curationService.getAllByAreaOrderByCreatedDateDesc(curation1.area, curation1.pageable, curation1.lastId); + + // then + assertThat(response).isEqualTo(CurationData.curation1.response.slice.get()); + } + + @Test + @DisplayName("큐레이터 ID를 통해 해당하는 모든 큐레이션을 반환한다.") + void getAllByCuratorIdOrderByCreatedDateDesc() { + // given + given(curationRepository.findAllByCuratorOrderByCreatedDateDesc(curator1.id, curation1.lastId, curation1.pageable)) + .willReturn(CurationData.CurationSlice.get()); + given(curationImageService.getImageUrl(ImageData.curation1.entity.get())).willReturn(ImageData.R.curation1.url); + + // when + CurationSliceResponse response = + curationService.getAllByCuratorIdOrderByCreatedDateDesc(curator1.id, curation1.pageable, curation1.lastId); + + // then + assertThat(response).isEqualTo(CurationData.curation1.response.slice.get()); + } + + @Test + @DisplayName("큐레이션 생성 DTO를 통해 큐레이션을 생성한다.") + void create() { + // given + given(curatorService.getByIdOrThrow(curator1.id)).willReturn(MemberData.curator1.entity.get()); + given(curationCardService.create(CurationData.curationCard1.request.get())) + .willReturn(CurationData.curationCard1.entity.get()); + given(eventService.getByIdOrThrow(curation1.eventIds.getFirst())).willReturn(EventData.event1.entity.get()); + given(curationRepository.save(any(Curation.class))).willReturn(CurationData.curation1.entity.get()); + given(curationImageService.getImageUrl(ImageData.curation1.entity.get())).willReturn(ImageData.R.curation1.url); + + // when + CurationResponse response = curationService.create(curator1.memberIdentifier, CurationData.curation1.request.create.get()); + + // then + assertThat(response).isEqualTo(CurationData.curation1.response.get()); + } + + @Test + @DisplayName("CurationCreateReqeust를 통해 큐레이션을 수정한다.") + void update1() { + // given + given(curationRepository.findById(curation1.id)).willReturn(Optional.of(CurationData.curation1.entity.get())); + given(curationCardService.create(CurationData.curationCard2.request.get())) + .willReturn(CurationData.curationCard2.entity.get()); + given(eventService.getByIdOrThrow(event2.id)).willReturn(EventData.event1.entity.get()); + given(curationImageService.getImageUrl(ImageData.curation2.entity.get())).willReturn(curation2.url); + given(curationImageService.createThumbnail(curation2.name)).willReturn(curation2.url); + + // when + CurationResponse response = curationService.update(curator1.memberIdentifier, curation1.id, + CurationData.curation1.request.edit.get()); + + // then + assertThat(response).isEqualTo(CurationData.curation2.response.get()); + } + + @Test + @DisplayName("멤버 ID가 다르면 큐레이션을 수정할 수 없다.") + void update2() { + // given + MemberIdentifier differentMember = new MemberIdentifier(100L, MemberType.curator); + given(curationRepository.findById(curation1.id)).willReturn(Optional.of(CurationData.curation1.entity.get())); + + // when, then + UnauthorizedException exception = assertThrows( + UnauthorizedException.class, () -> curationService.update(differentMember, curation1.id, CurationData.curation1.request.edit.get())); + assertThat(exception.getMessage()).isEqualTo("큐레이션에 접근할 권한이 없습니다."); + } + + @Test + @DisplayName("멤버 ID가 달라도 ADMIN은 큐레이션을 수정할 수 있다.") + void update3() { + // given + Long invalidCuratorId = 100L; + given(curationRepository.findById(curation1.id)).willReturn(Optional.of(CurationData.curation1.entity.get())); + given(curationCardService.create(CurationData.curationCard2.request.get())) + .willReturn(CurationData.curationCard2.entity.get()); + given(eventService.getByIdOrThrow(event2.id)).willReturn(EventData.event1.entity.get()); + given(curationImageService.getImageUrl(ImageData.curation2.entity.get())).willReturn(curation2.url); + given(curationImageService.createThumbnail(curation2.name)).willReturn(curation2.url); + + // when + CurationResponse response = curationService.update(new MemberIdentifier(invalidCuratorId, MemberType.admin), curation1.id, + CurationData.curation1.request.edit.get()); + + // then + assertThat(response).isEqualTo(CurationData.curation2.response.get()); + } + + @Test + @DisplayName("큐레이션 ID를 통해 해당하는 큐레이션을 삭제한다.") + void delete1() { + // given + given(curationRepository.findById(curation1.id)).willReturn(Optional.of(CurationData.curation1.entity.get())); + + // when + curationService.delete(curator1.memberIdentifier, curation1.id); + + // then + then(curationCardService).should(times(curation1.curationCards.size())).delete(eq(curator1.memberIdentifier), any()); + then(curationRepository).should(times(1)).deleteById(curation1.id); + } + + @Test + @DisplayName("멤버 ID가 다르면 큐레이션을 삭제할 수 없다.") + void delete2() { + // given + MemberIdentifier differentMember = new MemberIdentifier(100L, MemberType.curator); + given(curationRepository.findById(curation1.id)).willReturn(Optional.of(CurationData.curation1.entity.get())); + + // when, then + UnauthorizedException exception = assertThrows( + UnauthorizedException.class, () -> curationService.delete(differentMember, curation1.id)); + assertThat(exception.getMessage()).isEqualTo("큐레이션에 접근할 권한이 없습니다."); + } + + @Test + @DisplayName("멤버 ID가 달라도 ADMIN은 큐레이션을 삭제할 수 있다.") + void delete3() { + // given + MemberIdentifier admin = new MemberIdentifier(100L, MemberType.admin); + given(curationRepository.findById(curation1.id)).willReturn(Optional.of(CurationData.curation1.entity.get())); + + // when + curationService.delete(admin, curation1.id); + + // then + then(curationCardService).should(times(curation1.curationCards.size())).delete(eq(admin), any()); + then(curationRepository).should(times(1)).deleteById(curation1.id); + } +} \ No newline at end of file diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/event/EventControllerUnitTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/event/EventControllerUnitTest.java index e9267358..1727dead 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/event/EventControllerUnitTest.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/event/EventControllerUnitTest.java @@ -1,10 +1,9 @@ package org.ktc2.cokaen.wouldyouin.event; - -import static org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain.createValidHost; +import static java.lang.Math.abs; +import static org.assertj.core.api.Assertions.assertThat; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; -import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.times; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; @@ -14,35 +13,34 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import com.fasterxml.jackson.databind.ObjectMapper; -import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -import org.junit.jupiter.api.BeforeAll; +import java.time.LocalDateTime; +import java.util.Random; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; - import org.ktc2.cokaen.wouldyouin._common.vo.Area; import org.ktc2.cokaen.wouldyouin._common.vo.Category; -import org.ktc2.cokaen.wouldyouin._common.vo.Location; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockCurator1; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockHost1; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockMember1; +import org.ktc2.cokaen.wouldyouin._global.testdata.EventData; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.host1; import org.ktc2.cokaen.wouldyouin.auth.application.JwtAuthFilter; -import org.ktc2.cokaen.wouldyouin.auth.application.JwtService; -import org.ktc2.cokaen.wouldyouin.curation.api.dto.LocationFilter; import org.ktc2.cokaen.wouldyouin.event.api.EventController; import org.ktc2.cokaen.wouldyouin.event.api.dto.EventCreateRequest; import org.ktc2.cokaen.wouldyouin.event.api.dto.EventEditRequest; -import org.ktc2.cokaen.wouldyouin.event.api.dto.EventSliceResponse; +import org.ktc2.cokaen.wouldyouin.event.api.dto.LocationFilter; +import org.ktc2.cokaen.wouldyouin.event.api.dto.LocationRequest; import org.ktc2.cokaen.wouldyouin.event.application.EventService; -import org.ktc2.cokaen.wouldyouin._global.TestData.EventDomain; -import org.ktc2.cokaen.wouldyouin._global.mockMember.WithMockHost; -import org.ktc2.cokaen.wouldyouin.member.application.MemberService; -import org.ktc2.cokaen.wouldyouin.member.persist.Host; +import org.mockito.ArgumentCaptor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -51,30 +49,17 @@ @WebMvcTest(EventController.class) class EventControllerUnitTest { - private static Long id; - private static Host validHost; - private static ObjectMapper objectMapper; - @MockBean - private EventService eventService; - @MockBean - private MemberService memberService; - @MockBean - private JwtService jwtService; - @MockBean - private JwtAuthFilter jwtAuthFilter; - private EventSliceResponse eventSliceResponse; + private final Long randomId = abs(new Random().nextLong()); @Autowired private MockMvc mockMvc; @Autowired + private ObjectMapper objectMapper; + @Autowired private WebApplicationContext context; - - @BeforeAll - public static void init() { - id = 3L; - validHost = createValidHost(); - objectMapper = new ObjectMapper(); - objectMapper.registerModule(new JavaTimeModule()); - } + @MockBean + private EventService eventService; + @MockBean + private JwtAuthFilter jwtAuthFilter; @BeforeEach public void setup() throws Exception { @@ -85,139 +70,822 @@ public void setup() throws Exception { } @Test - @DisplayName("모든 행사 조회 - 성공") - @WithMockHost + @DisplayName("RequestParam을 통해 전달받은 위치를 기반으로 이벤트 목록을 조회한다.") + @WithMockMember1 void getEventsByFilterOrderByDistanceAsc() throws Exception { - // given - LocationFilter locationFilter = new LocationFilter(0.0, 0.0, 10.0, 10.0); - Location currentLocation = new Location(3.0, 2.0); - Category category = Category.공예; - Area area = Area.광주; - int pageNumber = 1; // 교수님 - int pageSize = 10; // - Long lastId = 1L; - Pageable pageable = PageRequest.of(pageNumber, pageSize); - - given(eventService.getAllByFilterOrderByDistanceAsc( - locationFilter, - currentLocation, - category, - area, - pageable, - lastId)) - .willReturn(eventSliceResponse); - - // when - mockMvc.perform(get("/api/events") - .param("startLatitude", locationFilter.getStartLatitude().toString()) - .param("startLongitude", locationFilter.getStartLongitude().toString()) - .param("endLatitude", locationFilter.getEndLatitude().toString()) - .param("endLongitude", locationFilter.getEndLongitude().toString()) - .param("latitude", currentLocation.getLatitude().toString()) - .param("longitude", currentLocation.getLongitude().toString()) - .param("category", category.toString()) // enum을 문자열로 변환하여 설정 - .param("area", area.toString()) // enum을 문자열로 변환하여 설정 - .param("page", String.valueOf(pageNumber)) - .param("size", String.valueOf(pageSize)) - .param("lastId", String.valueOf(lastId)) - ).andDo(print()) + // given, when + mockMvc.perform(get("/api/events/filter") + .param("startLatitude", "0.0") + .param("startLongitude", "0.0") + .param("endLatitude", "10.0") + .param("endLongitude", "10.0") + .param("latitude", "3.0") + .param("longitude", "2.0") + .param("title", "testTitle") + .param("category", Category.밴드.name()) + .param("area", Area.광주.name()) + .param("page", "5") + .param("size", "20") + .param("lastId", "100")).andDo(print()) .andExpect(status().isOk()); // then then(eventService).should(times(1)).getAllByFilterOrderByDistanceAsc( any(LocationFilter.class), - any(Location.class), - eq(category), - eq(area), - eq(pageable), - eq(lastId) + any(LocationRequest.class), + eq("testTitle"), + eq(Category.밴드), + eq(Area.광주), + eq(PageRequest.of(5, 20)), eq(100L) ); } @Test - @DisplayName("주최자 id를 통한 모든 행사 조회 - 성공") - @WithMockHost - void getEventsByLocationByHostId() throws Exception { - // given - int pageNumber = 1; - int pageSize = 10; - Pageable pageable = PageRequest.of(pageNumber, pageSize); - Long lastId = 1L; + @DisplayName("모든 이벤트 목록을 조회한다.") + @WithMockMember1 + void getEvents1() throws Exception { + // given, when + mockMvc.perform(get("/api/events") + .param("page", "5") + .param("size", "20") + .param("lastId", "100")).andDo(print()) + .andExpect(status().isOk()); - // when - mockMvc.perform(get("/api/events/hosts/" + id) - .param("page", String.valueOf(pageNumber)) - .param("size", String.valueOf(pageSize)) - .param("lastId", String.valueOf(lastId)) - ) + // then + then(eventService).should(times(1)).getAllByCreatedDateDesc( + eq(PageRequest.of(5, 20)), eq(100L)); + } + + @Test + @DisplayName("RequestParam을 통해 페이지 정보를 지정하지 않은 경우, 디폴트 값으로 해당하는 호스트의 이벤트 목록을 조회한다.") + @WithMockMember1 + void getEvents2() throws Exception { + // given, when + mockMvc.perform(get("/api/events")).andDo(print()) .andExpect(status().isOk()); - //then + // then + then(eventService).should(times(1)).getAllByCreatedDateDesc( + eq(PageRequest.of(0, 10)), eq(Long.MAX_VALUE)); + } + + @Test + @DisplayName("호스트 ID를 통해 해당하는 호스트의 이벤트 목록을 조회한다.") + @WithMockMember1 + void getEventsByHostId() throws Exception { + // given, when + mockMvc.perform(get("/api/events/hosts/" + randomId) + .param("page", "5") + .param("size", "20") + .param("lastId", "100")).andDo(print()) + .andExpect(status().isOk()); + + // then then(eventService).should(times(1)).getAllByHostIdOrderByCreatedDateDesc( - eq(id), - eq(pageable), - eq(lastId) - ); + eq(randomId), eq(PageRequest.of(5, 20)), eq(100L)); } @Test - @DisplayName("행사 id를 통한 행사 조회 - 성공") - @WithMockHost + @DisplayName("이벤트 ID를 통해 해당하는 이벤트를 조회한다.") + @WithMockMember1 void getEventByEventId() throws Exception { - //given - //when - mockMvc.perform(get("/api/events/" + id)) - .andDo(print()) + // given, when + mockMvc.perform(get("/api/events/" + randomId)).andDo(print()) .andExpect(status().isOk()); - //then - then(eventService).should(times(1)).getById(eq(id)); + // then + then(eventService).should(times(1)).getById(eq(randomId)); } @Test - @DisplayName("행사 생성 - 성공") - @WithMockHost - void createEvent() throws Exception { - //given - //when + @DisplayName("RequestBody로 전달받은 정보를 통해 이벤트를 생성한다.") + @WithMockHost1 + void createEvent1() throws Exception { + // given + ArgumentCaptor captor = ArgumentCaptor.forClass( + EventCreateRequest.class); + EventCreateRequest request = EventData.event1.request.create.get(); + + // when mockMvc.perform(post("/api/events") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(EventDomain.createValidEventCreateRequest()))) + .content(objectMapper.writeValueAsString(request))) .andDo(print()) .andExpect(status().isCreated()); - //then - then(eventService).should(times(1)).create(eq(id), any(EventCreateRequest.class)); + // then + then(eventService).should(times(1)).create(eq(host1.memberIdentifier), captor.capture()); + assertThat(captor.getValue()).isEqualTo(request); + } + + @Test + @DisplayName("Curator의 권한으로는 이벤트를 생성할 수 없다.") + @WithMockCurator1 + void createEvent2() throws Exception { + // given, when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(EventData.event1.request.create.get()))) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("Member의 권한으로는 이벤트를 생성할 수 없다.") + @WithMockMember1 + void createEvent3() throws Exception { + // given, when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(EventData.event1.request.create.get()))) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 생성 시, 제목에는 빈 값이 들어갈 수 없다.") + @WithMockHost1 + void createEvent4() throws Exception { + // given + EventCreateRequest request = EventData.event1.request.create.get().toBuilder() + .title("").build(); + + // when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("제목은 필수입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 생성 시, 내용에는 빈 값이 들어갈 수 없다.") + @WithMockHost1 + void createEvent5() throws Exception { + // given + EventCreateRequest request = EventData.event1.request.create.get().toBuilder() + .content(null).build(); + + // when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("내용은 필수입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 생성 시, 내용은 20자 이상 1000자 이하이어야 한다.") + @WithMockHost1 + void createEvent6() throws Exception { + // given + EventCreateRequest request = EventData.event1.request.create.get().toBuilder() + .content("짧은 내용").build(); + + // when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("내용은 20자 이상 1000자 이하입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 생성 시, 지역은 필수 입력값이다.") + @WithMockHost1 + void createEvent7() throws Exception { + // given + EventCreateRequest request = EventData.event1.request.create.get().toBuilder() + .area(null).build(); + + // when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("지역는 필수입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 생성 시, 장소는 필수 입력값이다.") + @WithMockHost1 + void createEvent8() throws Exception { + // given + EventCreateRequest request = EventData.event1.request.create.get().toBuilder() + .location(null).build(); + + // when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("장소는 필수입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 생성 시, 시작 시간은 필수 입력값이다.") + @WithMockHost1 + void createEvent9() throws Exception { + // given + EventCreateRequest request = EventData.event1.request.create.get().toBuilder() + .startTime(null).build(); + + // when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("시작시간은 필수입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 생성 시, 종료 시간은 필수 입력값이다.") + @WithMockHost1 + void createEvent10() throws Exception { + // given + EventCreateRequest request = EventData.event1.request.create.get().toBuilder() + .endTime(null).build(); + + // when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("종료시간은 필수입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 생성 시, 시작 시간은 현재 또는 이후 시간이어야 한다.") + @WithMockHost1 + void createEvent11() throws Exception { + // given + EventCreateRequest request = EventData.event1.request.create.get().toBuilder() + .startTime(LocalDateTime.now().minusDays(1)).build(); + + // when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("시작 시간은 현재 시간 이후여야 합니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 생성 시, 종료 시간은 현재 또는 이후 시간이어야 한다.") + @WithMockHost1 + void createEvent12() throws Exception { + // given + EventCreateRequest request = EventData.event1.request.create.get().toBuilder() + .startTime(LocalDateTime.now().minusDays(2)) // startTime을 미래로 설정 + .endTime(LocalDateTime.now().minusDays(1)) // endTime을 과거로 설정 + .build(); + + // when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value( + org.hamcrest.Matchers.containsString("종료 시간은 현재 시간 이후여야 합니다."))); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 생성 시, 종료 시간은 시작 시간 이후 시간이어야 한다.") + @WithMockHost1 + void createEvent13() throws Exception { + // given + EventCreateRequest request = EventData.event1.request.create.get().toBuilder() + .endTime(LocalDateTime.now().plusDays(2)).build(); + + // when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("종료 시간은 시작 시간 이후여야 합니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 생성 시, 가격은 0원 이상이어야 한다.") + @WithMockHost1 + void createEvent14() throws Exception { + // given + EventCreateRequest request = EventData.event1.request.create.get().toBuilder() + .price(-1).build(); + + // when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("가격은 0원 이상입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 생성 시, 가격은 1,000,000원 이하이어야 한다.") + @WithMockHost1 + void createEvent15() throws Exception { + // given + EventCreateRequest request = EventData.event1.request.create.get().toBuilder() + .price(1000001).build(); + + // when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("가격은 1,000,000원 이하입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 생성 시, 총 좌석은 0석 이상이어야 한다.") + @WithMockHost1 + void createEvent16() throws Exception { + // given + EventCreateRequest request = EventData.event1.request.create.get().toBuilder() + .totalSeat(-1).build(); + + // when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("총 좌석은 0석 이상입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); } @Test - @DisplayName("행사 수정 - 성공") - @WithMockHost - void updateEvent() throws Exception { - //given - //when - mockMvc.perform(put("/api/events/" + id) + @DisplayName("이벤트 생성 시, 총 좌석은 1,000석 이하이어야 한다.") + @WithMockHost1 + void createEvent17() throws Exception { + // given + EventCreateRequest request = EventData.event1.request.create.get().toBuilder() + .totalSeat(1001).build(); + + // when + mockMvc.perform(post("/api/events") .with(csrf()) .contentType(MediaType.APPLICATION_JSON) - .content(objectMapper.writeValueAsString(EventDomain.createValidEventCreateRequest()))) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("총 좌석은 1,000석 이하입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 생성 시, 카테고리는 필수 입력값이다.") + @WithMockHost1 + void createEvent18() throws Exception { + // given + EventCreateRequest request = EventData.event1.request.create.get().toBuilder() + .category(null).build(); + + // when + mockMvc.perform(post("/api/events") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("카테고리는 필수입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("RequestBody로 전달받은 정보를 통해 이벤트를 수정한다.") + @WithMockHost1 + void updateEvent1() throws Exception { + // given + ArgumentCaptor captor = ArgumentCaptor.forClass(EventEditRequest.class); + EventEditRequest request = EventData.event1.request.edit1.get(); + + // when + mockMvc.perform(put("/api/events/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) .andExpect(status().isOk()); - //then - then(eventService).should(times(1)).update(eq(id), eq(id), any(EventEditRequest.class)); + // then + then(eventService).should(times(1)) + .update(eq(host1.memberIdentifier), eq(randomId), captor.capture()); + assertThat(captor.getValue()).isEqualTo(request); + } + + @Test + @DisplayName("Curator의 권한으로는 이벤트를 수정할 수 없다.") + @WithMockCurator1 + void updateEvent2() throws Exception { + // given, when + mockMvc.perform(put("/api/events/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(EventData.event1.request.edit1.get()))) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); } @Test - @DisplayName("행사 삭제 - 성공") - @WithMockHost + @DisplayName("Member의 권한으로는 이벤트를 수정할 수 없다.") + @WithMockMember1 + void updateEvent3() throws Exception { + // given, when + mockMvc.perform(put("/api/events/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(EventData.event1.request.edit1.get()))) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 수정 시, 제목에는 빈 값이 들어갈 수 없다.") + @WithMockMember1 + void updateEvent4() throws Exception { + // given + EventEditRequest request = EventData.event1.request.edit1.get().toBuilder().title(null) + .build(); + + // when + mockMvc.perform(put("/api/events/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("제목은 필수입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 수정 시, 내용은 빈 값이 들어갈 수 없다.") + @WithMockMember1 + void updateEvent5() throws Exception { + // given + EventEditRequest request = EventData.event1.request.edit1.get().toBuilder().content(null) + .build(); + + // when + mockMvc.perform(put("/api/events/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("내용은 필수입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 수정 시, 내용은 20자 이상 1000자 이하이어야 한다.") + @WithMockMember1 + void updateEvent6() throws Exception { + // given + EventEditRequest request = EventData.event1.request.edit1.get().toBuilder().content("짧은 내용") + .build(); + + // when + mockMvc.perform(put("/api/events/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("내용은 20자 이상 1000자 이하입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 수정 시, 지역은 필수이다.") + @WithMockMember1 + void updateEvent7() throws Exception { + // given + EventEditRequest request = EventData.event1.request.edit1.get().toBuilder().area(null) + .build(); + + // when + mockMvc.perform(put("/api/events/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("지역는 필수입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 수정 시, 장소는 필수이다.") + @WithMockMember1 + void updateEvent8() throws Exception { + // given + EventEditRequest request = EventData.event1.request.edit1.get().toBuilder().location(null) + .build(); + + // when + mockMvc.perform(put("/api/events/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("장소는 필수입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 수정 시, 시작 시간은 필수이다") + @WithMockMember1 + void updateEvent9() throws Exception { + // given + EventEditRequest request = EventData.event1.request.edit1.get().toBuilder() + .startTime(null).build(); + + // when + mockMvc.perform(put("/api/events/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("시작 시간은 필수입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 수정 시, 시작 시간은 현재 시간 이후여야 한다.") + @WithMockMember1 + void updateEvent10() throws Exception { + // given + EventEditRequest request = EventData.event1.request.edit1.get().toBuilder() + .startTime(LocalDateTime.now().minusDays(1)) + .build(); + + // when + mockMvc.perform(put("/api/events/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("시작 시간은 현재 시간 이후여야 합니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 수정 시, 종료 시간은 필수이다") + @WithMockMember1 + void updateEvent11() throws Exception { + // given + EventEditRequest request = EventData.event1.request.edit1.get().toBuilder() + .endTime(null).build(); + + // when + mockMvc.perform(put("/api/events/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("종료 시간은 필수입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 수정 시, 종료 시간은 현재 시간 이후여야 한다.") + @WithMockMember1 + void updateEvent12() throws Exception { + // given + EventEditRequest request = EventData.event1.request.edit1.get().toBuilder() + .endTime(LocalDateTime.now().minusDays(1)) + .build(); + + // when + mockMvc.perform(put("/api/events/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value( + org.hamcrest.Matchers.containsString("종료 시간은 현재 시간 이후여야 합니다."))); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 수정 시, 종료 시간은 시작 시간 이후여야 한다.") + @WithMockMember1 + void updateEvent13() throws Exception { + // given + EventEditRequest request = EventData.event1.request.edit1.get().toBuilder() + .endTime(LocalDateTime.now().plusDays(2)) + .build(); + + // when + mockMvc.perform(put("/api/events/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("종료 시간은 시작 시간 이후여야 합니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 수정 시, 가격은 0원 이상 1,000,000원 이하이어야 한다.") + @WithMockMember1 + void updateEvent14() throws Exception { + // given + EventEditRequest request = EventData.event1.request.edit1.get().toBuilder() + .price(1000001) // 1,000,001원 설정 + .build(); + + // when + mockMvc.perform(put("/api/events/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("가격은 1,000,000원 이하입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("이벤트 수정 시, 총 좌석은 0석 이상 1,000석 이하이어야 한다.") + @WithMockMember1 + void updateEvent15() throws Exception { + // given + EventEditRequest request = EventData.event1.request.edit1.get().toBuilder() + .totalSeat(1001) // 1,001석 설정 + .build(); + + // when + mockMvc.perform(put("/api/events/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("총 좌석은 1,000석 이하입니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("PathVariable로 전달받은 이벤트 ID에 해당하는 이벤트를 삭제한다.") + @WithMockHost1 void deleteEvent() throws Exception { - //given - //when - mockMvc.perform(delete("/api/events/" + id) - .with(csrf()) - ).andExpect(status().isNoContent()); - - //then - then(eventService).should(times(1)).delete(eq(id), eq(id)); + // given, when + mockMvc.perform(delete("/api/events/" + randomId) + .with(csrf())) + .andDo(print()) + .andExpect(status().isNoContent()); + + // then + then(eventService).should(times(1)).delete(eq(host1.memberIdentifier), eq(randomId)); + } + + @Test + @DisplayName("Curator의 권한으로는 큐레이션을 삭제할 수 없다.") + @WithMockCurator1 + void deleteEvent2() throws Exception { + // given, when + mockMvc.perform(delete("/api/events/" + randomId) + .with(csrf())) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("Member의 권한으로는 큐레이션을 삭제할 수 없다.") + @WithMockMember1 + void deleteEvent3() throws Exception { + // given, when + mockMvc.perform(delete("/api/events/" + randomId) + .with(csrf())) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(eventService).shouldHaveNoInteractions(); } } \ No newline at end of file diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/event/EventServiceUnitTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/event/EventServiceUnitTest.java index a5e5eaa7..87c554b0 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/event/EventServiceUnitTest.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/event/EventServiceUnitTest.java @@ -1,38 +1,41 @@ package org.ktc2.cokaen.wouldyouin.event; -import static org.junit.jupiter.api.Assertions.assertAll; -import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; -import static org.mockito.BDDMockito.willDoNothing; import static org.mockito.Mockito.times; -import java.util.List; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.ktc2.cokaen.wouldyouin.Image.application.EventImageService; -import org.ktc2.cokaen.wouldyouin._common.vo.Area; -import org.ktc2.cokaen.wouldyouin._common.vo.Category; -import org.ktc2.cokaen.wouldyouin._common.vo.Location; -import org.ktc2.cokaen.wouldyouin.curation.api.dto.LocationFilter; -import org.ktc2.cokaen.wouldyouin.event.api.dto.EventCreateRequest; -import org.ktc2.cokaen.wouldyouin.event.api.dto.EventEditRequest; +import org.ktc2.cokaen.wouldyouin._common.exception.UnauthorizedException; +import org.ktc2.cokaen.wouldyouin._global.testdata.EventData; +import org.ktc2.cokaen.wouldyouin._global.testdata.EventData.R.event1; +import org.ktc2.cokaen.wouldyouin._global.testdata.EventData.R.updatedEvent1; +import org.ktc2.cokaen.wouldyouin._global.testdata.ImageData; +import org.ktc2.cokaen.wouldyouin._global.testdata.ImageData.R; +import org.ktc2.cokaen.wouldyouin._global.testdata.ImageData.R.event4; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.host1; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.event.api.dto.EventResponse; +import org.ktc2.cokaen.wouldyouin.event.api.dto.EventSliceResponse; +import org.ktc2.cokaen.wouldyouin.event.api.dto.LocationFilter; +import org.ktc2.cokaen.wouldyouin.event.api.dto.LocationRequest; import org.ktc2.cokaen.wouldyouin.event.application.EventService; import org.ktc2.cokaen.wouldyouin.event.persist.Event; import org.ktc2.cokaen.wouldyouin.event.persist.EventRepository; -import org.ktc2.cokaen.wouldyouin._global.TestData.EventDomain; +import org.ktc2.cokaen.wouldyouin.image.application.EventImageService; +import org.ktc2.cokaen.wouldyouin.image.application.MemberImageService; +import org.ktc2.cokaen.wouldyouin.image.persist.EventImage; import org.ktc2.cokaen.wouldyouin.member.application.HostService; -import org.ktc2.cokaen.wouldyouin.member.application.MemberService; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; -import org.springframework.data.domain.PageRequest; -import org.springframework.data.domain.Pageable; -import org.springframework.data.domain.SliceImpl; @ExtendWith(MockitoExtension.class) class EventServiceUnitTest { @@ -48,170 +51,196 @@ class EventServiceUnitTest { @Mock private EventImageService eventImageService; - private Event validEvent; + @Mock + private MemberImageService memberImageService; @BeforeEach void setUp() { - eventService = new EventService(eventRepository, hostService, eventImageService); - validEvent = EventDomain.createValidEvent(); + eventService = new EventService(eventRepository, hostService, eventImageService, + memberImageService); } @Test - @DisplayName("모든 행사 조회 - 성공") - void getAllByFilterOrderByDistanceAsc() { + @DisplayName("행사 ID를 통해 해당하는 행사를 반환한다.") + void getById() { // given - LocationFilter location = new LocationFilter(); - Location currentLocation = new Location(3.0, 2.0); - Category category = Category.공예; - Area area = Area.광주; - int pageNumber = 1; - int pageSize = 10; - Pageable pageable = PageRequest.of(pageNumber, pageSize); - Long lastId = 1L; - given(eventRepository.findAllByFilterOrderByDistance(location.getStartLatitude(), - location.getStartLongitude(), - location.getEndLatitude(), location.getEndLongitude(), currentLocation.getLatitude(), - currentLocation.getLongitude(), - category, area, pageable)).willReturn(new SliceImpl<>(List.of())); - + given(eventRepository.findById(event1.id)).willReturn( + Optional.of(EventData.event1.entity.get())); + EventImage eventImage1 = EventData.event1.entity.get().getImages().get(0); + EventImage eventImage2 = EventData.event1.entity.get().getImages().get(1); + EventImage eventImage3 = EventData.event1.entity.get().getImages().get(2); + given(eventImageService.getImageUrl(eventImage1)).willReturn(R.event1.url); + given(eventImageService.getImageUrl(eventImage2)).willReturn(R.event2.url); + given(eventImageService.getImageUrl(eventImage3)).willReturn(R.event3.url); // when - eventService.getAllByFilterOrderByDistanceAsc(location, currentLocation, category, area, - pageable, lastId); + EventResponse response = eventService.getById(event1.id); // then - then(eventRepository).should(times(1)) - .findAllByFilterOrderByDistance(any(Double.class), any(Double.class), any(Double.class), - any(Double.class), any(Double.class), any(Double.class), - any(Category.class), any(Area.class), any(Pageable.class)); + assertThat(response).isEqualTo(EventData.event1.response.get()); } @Test - @DisplayName("주최자 id를 통한 모든 행사 조회 - 성공") - void getAllByHostIdOrderByCreatedDateDesc() { + @DisplayName("거리에 따른 이벤트들을 반환한다. ") + void getAllByFilterOrderByDistanceAsc() { // given - Long hostId = validEvent.getHost().getId(); - int pageNumber = 1; - int pageSize = 10; - Pageable pageable = PageRequest.of(pageNumber, pageSize); - Long lastId = 1L; - given(eventRepository.findAllByHostIdOrderByEventIdDesc(hostId, lastId, pageable)) - .willReturn(new SliceImpl<>(List.of())); + LocationFilter location = new LocationFilter(0.0, 0.0, 10.0, 10.0); + LocationRequest currentLocation = new LocationRequest(3.0, 2.0); + given(eventRepository.findAllByFilterOrderByDistance( + location.getStartLatitude(), location.getStartLongitude(), + location.getEndLatitude(), location.getEndLongitude(), + currentLocation.getLatitude(), currentLocation.getLongitude(), + event1.title, event1.category, event1.area, event1.pageable)) + .willReturn(EventData.EventSlice.get()); + EventImage eventImage1 = EventData.event1.entity.get().getImages().get(0); + EventImage eventImage2 = EventData.event1.entity.get().getImages().get(1); + EventImage eventImage3 = EventData.event1.entity.get().getImages().get(2); + given(eventImageService.getImageUrl(eventImage1)).willReturn(R.event1.url); + given(eventImageService.getImageUrl(eventImage2)).willReturn(R.event2.url); + given(eventImageService.getImageUrl(eventImage3)).willReturn(R.event3.url); // when - eventService.getAllByHostIdOrderByCreatedDateDesc(hostId, pageable, lastId); + EventSliceResponse responses = eventService.getAllByFilterOrderByDistanceAsc( + location, currentLocation, event1.title, event1.category, event1.area, event1.pageable, + event1.lastId); // then - then(eventRepository).should(times(1)). - findAllByHostIdOrderByEventIdDesc(any(Long.class), any(Long.class), - any(Pageable.class)); + assertThat(responses).isEqualTo(EventData.event1.response.slice.get()); } @Test - @DisplayName("행사 id를 통한 행사 조회 - 성공") - void getById() { + @DisplayName("Host ID를 통해 해당하는 모든 이벤트를 반환한다.") + void getAllByHostIdOrderByCreatedDateDesc() { // given - given(eventRepository.findById(validEvent.getId())).willReturn(Optional.of(validEvent)); + given(eventRepository.findAllByHostIdOrderByEventIdDesc(host1.id, event1.lastId, + event1.pageable)) + .willReturn(EventData.EventSlice.get()); + EventImage eventImage1 = EventData.event1.entity.get().getImages().get(0); + EventImage eventImage2 = EventData.event1.entity.get().getImages().get(1); + EventImage eventImage3 = EventData.event1.entity.get().getImages().get(2); + given(eventImageService.getImageUrl(eventImage1)).willReturn(R.event1.url); + given(eventImageService.getImageUrl(eventImage2)).willReturn(R.event2.url); + given(eventImageService.getImageUrl(eventImage3)).willReturn(R.event3.url); // when - eventService.getById(validEvent.getId()); + EventSliceResponse responses = eventService.getAllByHostIdOrderByCreatedDateDesc(host1.id, + event1.pageable, event1.lastId); // then - then(eventRepository).should(times(1)).findById(validEvent.getId()); + assertThat(responses).isEqualTo(EventData.event1.response.slice.get()); } @Test - @DisplayName("유효하지 않은 행사 id를 통한 행사 조회 - 실패") - void getByInvalidId() { + @DisplayName("모든 이벤트를 반환한다.") + void getAllByCreatedDateDesc() { // given - Long eventId = validEvent.getId(); - given(eventRepository.findById(eventId)).willThrow(RuntimeException.class); + given(eventRepository.findAllByEventIdDesc(event1.lastId, event1.pageable)) + .willReturn(EventData.EventSlice.get()); + EventImage eventImage1 = EventData.event1.entity.get().getImages().get(0); + EventImage eventImage2 = EventData.event1.entity.get().getImages().get(1); + EventImage eventImage3 = EventData.event1.entity.get().getImages().get(2); + given(eventImageService.getImageUrl(eventImage1)).willReturn(R.event1.url); + given(eventImageService.getImageUrl(eventImage2)).willReturn(R.event2.url); + given(eventImageService.getImageUrl(eventImage3)).willReturn(R.event3.url); + + // when + EventSliceResponse responses = eventService.getAllByCreatedDateDesc(event1.pageable, + event1.lastId); - // when & then - assertThrows(RuntimeException.class, () -> eventService.getById(eventId)); + // then + assertThat(responses).isEqualTo(EventData.event1.response.slice.get()); } @Test - @DisplayName("행사 생성 - 성공") + @DisplayName("EventCreateRequest를 통해 이벤트를 생성한다.") void create() { // given - Long hostId = validEvent.getHost().getId(); - EventCreateRequest validEventCreateRequest = EventDomain.createValidEventCreateRequest(); - given(eventRepository.save(any())).willReturn(validEvent); + given(hostService.getByIdOrThrow(host1.id)).willReturn(MemberData.host1.entity.get()); + given(eventImageService.getById(R.event1.id)).willReturn(ImageData.event1.entity.get()); + given(eventImageService.getById(R.event2.id)).willReturn(ImageData.event2.entity.get()); + given(eventImageService.getById(R.event3.id)).willReturn(ImageData.event3.entity.get()); + EventImage eventImage1 = EventData.event1.entity.get().getImages().get(0); + EventImage eventImage2 = EventData.event1.entity.get().getImages().get(1); + EventImage eventImage3 = EventData.event1.entity.get().getImages().get(2); + given(eventImageService.getImageUrl(eventImage1)).willReturn(R.event1.url); + given(eventImageService.getImageUrl(eventImage2)).willReturn(R.event2.url); + given(eventImageService.getImageUrl(eventImage3)).willReturn(R.event3.url); + given(eventRepository.save(any(Event.class))).willReturn(EventData.event1.entity.get()); // when - eventService.create(hostId, validEventCreateRequest); + EventResponse response = eventService.create(host1.memberIdentifier, + EventData.event1.request.create.get()); // then - then(eventRepository).should(times(1)).save(any(Event.class)); + assertThat(response).isEqualTo(EventData.event1.response.get()); } @Test - @DisplayName("행사 id를 통한 행사 수정 - 성공") - void update() { + @DisplayName("EventEditRequest를 통해 이벤트를 수정한다.") + void update1() { // given - Long eventId = validEvent.getId(); - Long hostId = validEvent.getHost().getId(); - EventEditRequest validEventEditRequest = EventDomain.createValidEventEditRequest(); - given(eventRepository.findById(eventId)).willReturn(Optional.of(validEvent)); + given(eventRepository.findById(event1.id)).willReturn( + Optional.of(EventData.event1.entity.get())); + given(eventImageService.getById(R.event4.id)).willReturn(ImageData.event4.entity.get()); + given(eventImageService.getById(R.event5.id)).willReturn(ImageData.event5.entity.get()); + given(eventImageService.createThumbnail(event4.name)).willReturn( + updatedEvent1.thumbnailUrl); + EventImage eventImage4 = EventData.updatedEvent1.entity.get().getImages().get(0); + EventImage eventImage5 = EventData.updatedEvent1.entity.get().getImages().get(1); + given(eventImageService.getImageUrl(eventImage4)).willReturn(R.event4.url); + given(eventImageService.getImageUrl(eventImage5)).willReturn(R.event5.url); // when - eventService.update(hostId, eventId, validEventEditRequest); + EventResponse response = eventService.update(host1.memberIdentifier, event1.id, + EventData.event1.request.edit1.get()); // then - then(eventRepository).should(times(1)).findById(eventId); - assertAll( - () -> assertEquals(validEvent.getTitle(), validEventEditRequest.getTitle()), - () -> assertEquals(validEvent.getContent(), validEventEditRequest.getContent()), - () -> assertEquals(validEvent.getArea(), validEventEditRequest.getArea()), - () -> assertEquals(validEvent.getLocation(), validEventEditRequest.getLocation()), - () -> assertEquals(validEvent.getStartTime(), validEventEditRequest.getStartTime()), - () -> assertEquals(validEvent.getEndTime(), validEventEditRequest.getEndTime()), - () -> assertEquals(validEvent.getPrice(), validEventEditRequest.getPrice()), - () -> assertEquals(validEvent.getTotalSeat(), validEventEditRequest.getTotalSeat()), - () -> assertEquals(validEvent.getCategory(), validEventEditRequest.getCategory()) - ); + then(eventImageService).should(times(3)) + .deleteImage(any(MemberIdentifier.class), any(Long.class)); + assertThat(response).isEqualTo(EventData.updatedEvent1.response.get()); } @Test - @DisplayName("유효하지 않은 행사 id를 통한 행사 수정 - 실패") - void updateByInvalidId() { + @DisplayName("Host ID가 다르면 큐레이션을 수정할 수 없다.") + void update2() { // given - EventEditRequest request = EventDomain.createValidEventEditRequest(); - Long invalidHostId = 999L; - given(eventRepository.findById(invalidHostId)).willThrow(RuntimeException.class); - - // when & then - assertThrows(RuntimeException.class, - () -> eventService.update(invalidHostId, validEvent.getId(), - EventDomain.createValidEventEditRequest())); + MemberIdentifier differentMember = new MemberIdentifier(100L, MemberType.host); + given(eventRepository.findById(event1.id)).willReturn( + Optional.of(EventData.event1.entity.get())); + + // when, then + UnauthorizedException exception = assertThrows( + UnauthorizedException.class, () -> eventService.update(differentMember, event1.id, + EventData.event1.request.edit1.get())); + assertThat(exception.getMessage()).isEqualTo("해당 이벤트에 접근할 권한이 없습니다."); } @Test - @DisplayName("행사 삭제 - 성공") + @DisplayName("이벤트 ID를 통해 해당하는 이벤트를 삭제한다.") void delete() { // given - Long eventId = validEvent.getId(); - Long hostId = validEvent.getHost().getId(); + given(eventRepository.findById(event1.id)).willReturn( + Optional.of(EventData.event1.entity.get())); // when - given(eventRepository.findById(eventId)).willReturn(Optional.of(validEvent)); - willDoNothing().given(eventRepository).deleteById(eventId); - eventService.delete(hostId, eventId); + eventService.delete(host1.memberIdentifier, event1.id); // then - then(eventRepository).should(times(1)).findById(eventId); - then(eventRepository).should(times(1)).deleteById(eventId); + then(eventImageService).should(times(3)) + .deleteImage(any(MemberIdentifier.class), any(Long.class)); + then(eventRepository).should(times(1)).deleteById(event1.id); } @Test - @DisplayName("유효하지 않은 행사 id를 통한 행사 삭제 - 실패") + @DisplayName("Host ID가 다르면 큐레이션을 삭제할 수 없다.") void deleteByInvalidId() { // given - Long eventId = validEvent.getId(); - Long hostId = validEvent.getHost().getId(); - given(eventRepository.findById(eventId)).willThrow(RuntimeException.class); - - // when & then - assertThrows(RuntimeException.class, () -> eventService.delete(hostId, eventId)); + MemberIdentifier differentMember = new MemberIdentifier(100L, MemberType.host); + given(eventRepository.findById(event1.id)).willReturn( + Optional.of(EventData.event1.entity.get())); + + // when, then + UnauthorizedException exception = assertThrows( + UnauthorizedException.class, () -> eventService.delete(differentMember, event1.id)); + assertThat(exception.getMessage()).isEqualTo("해당 이벤트에 접근할 권한이 없습니다."); } } \ No newline at end of file diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/image/CurationCardImageServiceTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/image/CurationCardImageServiceTest.java new file mode 100644 index 00000000..5d57121a --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/image/CurationCardImageServiceTest.java @@ -0,0 +1,63 @@ +package org.ktc2.cokaen.wouldyouin.image; + + +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; + +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.ktc2.cokaen.wouldyouin._global.testdata.CurationData.R.curation1; +import org.ktc2.cokaen.wouldyouin._global.testdata.ImageData; +import org.ktc2.cokaen.wouldyouin.image.application.CurationImageService; +import org.ktc2.cokaen.wouldyouin.image.application.ImageStorageService; +import org.ktc2.cokaen.wouldyouin.image.persist.CurationCardImage; +import org.ktc2.cokaen.wouldyouin.image.persist.CurationCardImageRepository; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith(MockitoExtension.class) +public class CurationCardImageServiceTest { + + private CurationImageService curationImageService; + + @Mock + private ImageStorageService imageStorageService; + + @Mock + private CurationCardImageRepository curationCardImageRepository; + + @BeforeEach + void setUp() { + curationImageService = new CurationImageService(imageStorageService, + curationCardImageRepository); + } + + @Test + @DisplayName("이미지 ID를 통해 큐레이션 이미지를 반환한다.") + void getById() { + // given + given(curationCardImageRepository.findById(curation1.id)).willReturn(Optional.of(ImageData.curation1.entity.get())); + + // when + CurationCardImage response = curationImageService.getById(curation1.id); + + // then + assertThat(response).isEqualTo(ImageData.curation1.entity.get()); + } + +// @Test +// @DisplayName("이미지 ID를 통해 해당 이미지를 삭제한다.") +// void deleteImage() { +// // given +// CurationCardImage image = ImageData.curation1.entity.get(); +// given(curationCardImageRepository.findById(curation1.id)).willReturn(Optional.of(image)); +// +// // when, then +// then(imageStorageService).should(times(1)).delete(eq(image.getName()), any()); +// } +} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/image/ImageControllerTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/image/ImageControllerTest.java new file mode 100644 index 00000000..4b381670 --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/image/ImageControllerTest.java @@ -0,0 +1,201 @@ +package org.ktc2.cokaen.wouldyouin.image; + +import static java.lang.Math.abs; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.times; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.multipart; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.nio.file.Paths; +import java.util.List; +import java.util.Random; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockCurator1; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockMember1; +import org.ktc2.cokaen.wouldyouin._global.testdata.ImageData; +import org.ktc2.cokaen.wouldyouin._global.testdata.ImageData.mockMultipartFile1; +import org.ktc2.cokaen.wouldyouin._global.testdata.ImageData.mockMultipartFile2; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.curator1; +import org.ktc2.cokaen.wouldyouin.auth.application.JwtAuthFilter; +import org.ktc2.cokaen.wouldyouin.image.api.ImageController; +import org.ktc2.cokaen.wouldyouin.image.api.ImageDomain; +import org.ktc2.cokaen.wouldyouin.image.application.CurationImageService; +import org.ktc2.cokaen.wouldyouin.image.application.ImageServiceFactory; +import org.ktc2.cokaen.wouldyouin.image.application.ImageStorageService; +import org.ktc2.cokaen.wouldyouin.payment.application.PaymentService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.http.MediaType; +import org.springframework.mock.web.MockMultipartFile; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@WebMvcTest(ImageController.class) +class ImageControllerTest { + + private final long randomId = abs(new Random().nextLong()); + @Autowired + private MockMvc mockMvc; + @Autowired + private WebApplicationContext context; + @MockBean + private ImageServiceFactory imageServiceFactory; + @MockBean + private CurationImageService curationImageService; + @MockBean + private ImageStorageService imageStorageService; + @MockBean + private PaymentService paymentService; + @MockBean + private JwtAuthFilter jwtAuthFilter; + + @BeforeEach + public void setup() throws Exception { + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @Test + @DisplayName("이미지 경로를 통해 이미지를 조회한다.") + @WithMockMember1 + void getImage() throws Exception { + // given + String directory = "member"; + String file = "image.jpg"; + + // when + mockMvc.perform(get("/api/images/{directory}/{file}", directory, file)) + .andDo(print()) + .andExpect(status().isOk()); + + // then + then(imageStorageService).should(times(1)) + .readFromDirectory(eq(Paths.get(directory, file))); + } + + @Test + @DisplayName("이미지 경로를 통해 썸네일 이미지를 조회한다.") + @WithMockMember1 + void getThumnailImage() throws Exception { + // given + String directory = "member"; + String thumbnail = "thumbnail"; + String file = "image.jpg"; + + // when + mockMvc.perform(get("/api/images/{directory}/{thumbnail}/{file}", directory, thumbnail, file)) + .andDo(print()) + .andExpect(status().isOk()); + + // then + then(imageStorageService).should(times(1)).readFromDirectory(eq(Paths.get(directory, thumbnail, file))); + } + + @Test + @DisplayName("RequestParam으로 이미지 도메인을 받아 첨부된 이미지를 업로드한다.") + @WithMockCurator1 + void uploadImages1() throws Exception { + // given + MockMultipartFile image1 = mockMultipartFile1.get(); + MockMultipartFile image2 = mockMultipartFile2.get(); + given((CurationImageService) imageServiceFactory.getImageService( + ImageDomain.CURATION)).willReturn(curationImageService); + given(curationImageService.saveImages(List.of(image1, image2))) + .willReturn( + List.of(ImageData.curation1.response.get(), ImageData.curation2.response.get())); + + // when + mockMvc.perform(multipart("/api/images") + .file(image1) + .file(image2) + .contentType(MediaType.MULTIPART_FORM_DATA) + .param("type", ImageDomain.CURATION.name()) + .with(csrf())) + .andDo(print()) + .andExpect(status().isOk()); + + // then + then(imageServiceFactory).should(times(1)).getImageService(eq(ImageDomain.CURATION)); + then(curationImageService).should(times(1)).saveImages(List.of(image1, image2)); + } + + @Test + @DisplayName("RequestParam의 이미지 도메인의 값으로는 MEMBER, CURATION, ADVERTISEMENT, EVENT만 사용할 수 있다.") + @WithMockCurator1 + void uploadImages2() throws Exception { + // given + MockMultipartFile image1 = mockMultipartFile1.get(); + MockMultipartFile image2 = mockMultipartFile2.get(); + + // when + mockMvc.perform(multipart("/api/images") + .file(image1) + .file(image2) + .contentType(MediaType.MULTIPART_FORM_DATA) + .param("type", "INVALID") + .with(csrf())) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("해당 이미지 도메인에 대한 서비스가 존재하지 않습니다.")); + + // then + then(imageServiceFactory).shouldHaveNoInteractions(); + then(curationImageService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("PathVariable로 이미지의 ID를 받아 이미지를 삭제한다.") + @WithMockCurator1 + void deleteImage1() throws Exception { + // given + given((CurationImageService) imageServiceFactory.getImageService( + ImageDomain.CURATION)).willReturn(curationImageService); + + // when + mockMvc.perform(delete("/api/images/" + randomId) + .param("type", ImageDomain.CURATION.name()) + .with(csrf())) + .andDo(print()) + .andExpect(status().isNoContent()); + + // then + then(imageServiceFactory).should(times(1)).getImageService(eq(ImageDomain.CURATION)); + then(curationImageService).should(times(1)) + .deleteImage(curator1.memberIdentifier, randomId); + } + + @Test + @DisplayName("RequestParam의 이미지 도메인의 값으로는 MEMBER, CURATION, ADVERTISEMENT, EVENT만 사용할 수 있다.") + @WithMockCurator1 + void deleteImage2() throws Exception { + // given + given((CurationImageService) imageServiceFactory.getImageService( + ImageDomain.CURATION)).willReturn(curationImageService); + + // when + mockMvc.perform(delete("/api/images/" + randomId) + .param("type", "INVALID") + .with(csrf())) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("해당 이미지 도메인에 대한 서비스가 존재하지 않습니다.")); + + // then + then(imageServiceFactory).shouldHaveNoInteractions(); + then(curationImageService).shouldHaveNoInteractions(); + } +} \ No newline at end of file diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/like/LikeControllerUnitTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/like/LikeControllerUnitTest.java new file mode 100644 index 00000000..ad2a69d1 --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/like/LikeControllerUnitTest.java @@ -0,0 +1,287 @@ +package org.ktc2.cokaen.wouldyouin.like; + +import static java.lang.Math.abs; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.times; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import java.util.Random; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockCurator1; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockHost1; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockMember1; +import org.ktc2.cokaen.wouldyouin._global.testdata.LikeData; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.curator1; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.normal1; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.auth.application.JwtAuthFilter; +import org.ktc2.cokaen.wouldyouin.like.api.LikeController; +import org.ktc2.cokaen.wouldyouin.like.api.dto.LikeSliceResponse; +import org.ktc2.cokaen.wouldyouin.like.application.CuratorLikeService; +import org.ktc2.cokaen.wouldyouin.like.application.HostLikeService; +import org.ktc2.cokaen.wouldyouin.like.application.LikeServiceFactory; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@WebMvcTest(LikeController.class) +public class LikeControllerUnitTest { + + private final long randomId = abs(new Random().nextLong()); + @Autowired + private MockMvc mockMvc; + @Autowired + private WebApplicationContext context; + @MockBean + private LikeServiceFactory likeServiceFactory; + @MockBean + private CuratorLikeService curatorLikeService; + @MockBean + private HostLikeService hostLikeService; + @MockBean + private JwtAuthFilter jwtAuthFilter; + + + @BeforeEach + public void setup() throws Exception { + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @Test + @DisplayName("jwt 토큰 정보에 해당하는 member의 Curator 좋아요 목록을 조회한다.") + @WithMockMember1 + void getLikes1() throws Exception { + // given + MemberIdentifier identifier = normal1.memberIdentifier; + MemberType targetMemberType = MemberType.curator; + LikeSliceResponse sliceResponse = LikeData.sliceResponse.normal1.curatorLikes.get(); + int pageNumber = 0; + int pageSize = 10; + Pageable pageable = PageRequest.of(pageNumber, pageSize); + Long beforeLastId = sliceResponse.getLikes().getLast().getMemberId(); + + given((CuratorLikeService) likeServiceFactory.getLikeServiceFrom(targetMemberType)) + .willReturn(curatorLikeService); + given(curatorLikeService.getLikes(eq(identifier), eq(pageable), eq(beforeLastId))) + .willReturn(sliceResponse); + + // when + mockMvc.perform(get("/api/likes") + .param("type", targetMemberType.name()) + .param("page", Integer.toString(pageNumber)) + .param("size", Integer.toString(pageSize)) + .param("lastId", beforeLastId.toString())) + .andDo(print()) + .andExpect(status().isOk()); + + // then + then(likeServiceFactory).should(times(1)).getLikeServiceFrom(eq(targetMemberType)); + then(curatorLikeService).should(times(1)) + .getLikes(eq(identifier), eq(pageable), eq(beforeLastId)); + } + + @Test + @DisplayName("jwt 토큰 정보에 해당하는 member의 Host 좋아요 목록을 조회한다.") + @WithMockMember1 + void getLikes2() throws Exception { + // given + MemberIdentifier identifier = normal1.memberIdentifier; + MemberType targetMemberType = MemberType.host; + LikeSliceResponse sliceResponse = LikeData.sliceResponse.normal1.hostLikes.get(); + int pageNumber = 0; + int pageSize = 10; + Pageable pageable = PageRequest.of(pageNumber, pageSize); + Long beforeLastId = sliceResponse.getLikes().getLast().getMemberId(); + + given((HostLikeService) likeServiceFactory.getLikeServiceFrom(targetMemberType)) + .willReturn(hostLikeService); + given(hostLikeService.getLikes(eq(identifier), eq(pageable), eq(beforeLastId))) + .willReturn(sliceResponse); + + // when + mockMvc.perform(get("/api/likes") + .param("type", targetMemberType.name()) + .param("page", Integer.toString(pageNumber)) + .param("size", Integer.toString(pageSize)) + .param("lastId", beforeLastId.toString())) + .andDo(print()) + .andExpect(status().isOk()); + + // then + then(likeServiceFactory).should(times(1)).getLikeServiceFrom(eq(targetMemberType)); + then(hostLikeService).should(times(1)) + .getLikes(eq(identifier), eq(pageable), eq(beforeLastId)); + } + + @Test + @DisplayName("jwt 토큰 정보에 해당하는 curator의 Host 좋아요 목록을 조회한다.") + @WithMockCurator1 + void getLikes3() throws Exception { + // given + MemberIdentifier identifier = curator1.memberIdentifier; + MemberType targetMemberType = MemberType.host; + LikeSliceResponse sliceResponse = LikeData.sliceResponse.curator1.hostLikes.get(); + int pageNumber = 0; + int pageSize = 10; + Pageable pageable = PageRequest.of(pageNumber, pageSize); + Long beforeLastId = sliceResponse.getLikes().getLast().getMemberId(); + + given((HostLikeService) likeServiceFactory.getLikeServiceFrom(targetMemberType)) + .willReturn(hostLikeService); + given(hostLikeService.getLikes(eq(identifier), eq(pageable), eq(beforeLastId))) + .willReturn(sliceResponse); + + // when + mockMvc.perform(get("/api/likes") + .param("type", targetMemberType.name()) + .param("page", Integer.toString(pageNumber)) + .param("size", Integer.toString(pageSize)) + .param("lastId", beforeLastId.toString())) + .andDo(print()) + .andExpect(status().isOk()); + + // then + then(likeServiceFactory).should(times(1)).getLikeServiceFrom(eq(targetMemberType)); + then(hostLikeService).should(times(1)) + .getLikes(eq(identifier), eq(pageable), eq(beforeLastId)); + } + + @Test + @DisplayName("Member 권한으로 Host ID를 통해 Host 좋아요를 생성한다.") + @WithMockMember1 + void createOrDeleteLike1() throws Exception { + // given + Long beforeLastId = LikeData.sliceResponse.normal1.hostLikes.get().getSliceInfo() + .getLastId(); + given((HostLikeService) likeServiceFactory.getLikeServiceFrom(MemberType.host)) + .willReturn(hostLikeService); + given(hostLikeService.toggleLike(eq(normal1.memberIdentifier), eq(beforeLastId))) + .willReturn(LikeData.toggleResponse.hostLikes.create.get()); + + // when + mockMvc.perform(post("/api/likes/" + randomId) + .param("type", MemberType.host.name()).with(csrf())) + .andDo(print()) + .andExpect(status().isCreated()); + + // then + then(likeServiceFactory).should(times(1)).getLikeServiceFrom(eq(MemberType.host)); + then(hostLikeService).should(times(1)) + .toggleLike(eq(normal1.memberIdentifier), eq(randomId)); + } + + @Test + @DisplayName("Member 권한으로 Host ID를 통해 Host 좋아요를 삭제한다.") + @WithMockMember1 + void createOrDeleteLike2() throws Exception { + // given + Long beforeLastId = LikeData.sliceResponse.normal1.hostLikes.get().getSliceInfo() + .getLastId(); + given((HostLikeService) likeServiceFactory.getLikeServiceFrom(MemberType.host)) + .willReturn(hostLikeService); + given(hostLikeService.toggleLike(eq(normal1.memberIdentifier), eq(beforeLastId))) + .willReturn(LikeData.toggleResponse.hostLikes.delete.get()); + + // when + mockMvc.perform(post("/api/likes/" + randomId) + .param("type", MemberType.host.name()).with(csrf())) + .andDo(print()) + .andExpect(status().isCreated()); + + // then + then(likeServiceFactory).should(times(1)).getLikeServiceFrom(eq(MemberType.host)); + then(hostLikeService).should(times(1)) + .toggleLike(eq(normal1.memberIdentifier), eq(randomId)); + } + + @Test + @DisplayName("Curator 권한으로 Host ID를 통해 Host 좋아요를 생성한다.") + @WithMockCurator1 + void createOrDeleteLike3() throws Exception { + // given + Long beforeLastId = LikeData.sliceResponse.normal1.hostLikes.get().getSliceInfo() + .getLastId(); + given((HostLikeService) likeServiceFactory.getLikeServiceFrom(MemberType.host)) + .willReturn(hostLikeService); + given(hostLikeService.toggleLike(eq(normal1.memberIdentifier), eq(beforeLastId))) + .willReturn(LikeData.toggleResponse.hostLikes.create.get()); + + // when + mockMvc.perform(post("/api/likes/" + randomId) + .param("type", MemberType.host.name()).with(csrf())) + .andDo(print()) + .andExpect(status().isCreated()); + + // then + then(likeServiceFactory).should(times(1)).getLikeServiceFrom(eq(MemberType.host)); + then(hostLikeService).should(times(1)) + .toggleLike(eq(curator1.memberIdentifier), eq(randomId)); + } + + @Test + @DisplayName("Curator 권한으로 Host ID를 통해 Host 좋아요를 삭제한다.") + @WithMockCurator1 + void createOrDeleteLike4() throws Exception { + // given + Long beforeLastId = LikeData.sliceResponse.normal1.hostLikes.get().getSliceInfo() + .getLastId(); + given((HostLikeService) likeServiceFactory.getLikeServiceFrom(MemberType.host)) + .willReturn(hostLikeService); + given(hostLikeService.toggleLike(eq(normal1.memberIdentifier), eq(beforeLastId))) + .willReturn(LikeData.toggleResponse.hostLikes.delete.get()); + + // when + mockMvc.perform(post("/api/likes/" + randomId) + .param("type", MemberType.host.name()).with(csrf())) + .andDo(print()) + .andExpect(status().isCreated()); + + // then + then(likeServiceFactory).should(times(1)).getLikeServiceFrom(eq(MemberType.host)); + then(hostLikeService).should(times(1)) + .toggleLike(eq(curator1.memberIdentifier), eq(randomId)); + } + + @Test + @DisplayName("Host 권한으로 좋아요할 수 없다.") + @WithMockHost1 + void createOrDeleteLike5() throws Exception { + // given + Long beforeLastId = LikeData.sliceResponse.normal1.hostLikes.get().getSliceInfo() + .getLastId(); + given((HostLikeService) likeServiceFactory.getLikeServiceFrom(MemberType.host)) + .willReturn(hostLikeService); + given(hostLikeService.toggleLike(eq(normal1.memberIdentifier), eq(beforeLastId))) + .willReturn(LikeData.toggleResponse.hostLikes.delete.get()); + + // when + mockMvc.perform(post("/api/likes/" + randomId) + .param("type", MemberType.host.name()).with(csrf())) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(likeServiceFactory).shouldHaveNoInteractions(); + then(hostLikeService).shouldHaveNoInteractions(); + } +} diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/BaseMemberServiceUnitTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/BaseMemberServiceUnitTest.java index 8a3d0abc..395c428e 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/BaseMemberServiceUnitTest.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/BaseMemberServiceUnitTest.java @@ -2,14 +2,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain.createValidCurator; -import static org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain.createValidHost; -import static org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain.createValidMember; -import static org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain.createValidWelcomeMember; -import static org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain.validCuratorId; -import static org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain.validHostId; -import static org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain.validMemberId; -import static org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain.validWelcomeMemberId; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; @@ -25,7 +17,13 @@ import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.ValueSource; -import org.ktc2.cokaen.wouldyouin.member.application.dto.MemberResponse; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.curator1; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.host1; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.normal1; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.welcome1; +import org.ktc2.cokaen.wouldyouin.member.api.dto.MemberResponse; +import org.ktc2.cokaen.wouldyouin.member.exception.EmailAlreadyExistsException; import org.ktc2.cokaen.wouldyouin.member.persist.BaseMember; import org.ktc2.cokaen.wouldyouin.member.persist.BaseMemberRepository; import org.ktc2.cokaen.wouldyouin.member.persist.Curator; @@ -61,10 +59,10 @@ class BaseMemberServiceUnitTest { private BaseMemberService baseMemberService; private static Map members; - private static final Member validMember = createValidMember(); - private static final Member validWelcomeMember = createValidWelcomeMember(); - private static final Host validHost = createValidHost(); - private static final Curator validCurator = createValidCurator(); + private static final Member validMember = MemberData.normal1.entity.get(); + private static final Member validWelcomeMember = MemberData.welcome1.entity.get(); + private static final Host validHost = MemberData.host1.entity.get(); + private static final Curator validCurator = MemberData.curator1.entity.get(); @BeforeAll static void beforeAll() { @@ -92,7 +90,7 @@ MemberType welcomeTypeMapping(MemberType memberType) { } @ParameterizedTest - @ValueSource(longs = {validMemberId, validCuratorId, validHostId, validWelcomeMemberId}) + @ValueSource(longs = {normal1.id, curator1.id, host1.id, welcome1.id}) @DisplayName("임의의 사용자를 찾는 메서드 테스트") void getByIdOrThrow(long id) { // when @@ -105,7 +103,7 @@ void getByIdOrThrow(long id) { } @ParameterizedTest - @ValueSource(longs = {validMemberId, validCuratorId, validHostId, validWelcomeMemberId}) + @ValueSource(longs = {normal1.id, curator1.id, host1.id, welcome1.id}) @DisplayName("임의의 사용자의 타입을 반환하는 메서드 테스트") void getMemberType(long id) { // when @@ -118,7 +116,7 @@ void getMemberType(long id) { } @ParameterizedTest - @ValueSource(longs = {validMemberId, validCuratorId, validHostId, validWelcomeMemberId}) + @ValueSource(longs = {normal1.id, curator1.id, host1.id, welcome1.id}) @DisplayName("각 유형의 사용자를 찾고 응답 반환하는 메서드 테스트") void findById(long id) { // given @@ -140,7 +138,7 @@ void findById(long id) { } @ParameterizedTest - @ValueSource(longs = {validMemberId, validCuratorId, validHostId, validWelcomeMemberId}) + @ValueSource(longs = {normal1.id, curator1.id, host1.id, welcome1.id}) @DisplayName("중복 있는 이메일 있는 경우 테스트") void checkUniqueEmailOrThrow(long id) { // given @@ -149,8 +147,7 @@ void checkUniqueEmailOrThrow(long id) { given(baseMemberRepository.findByEmail(email)).willReturn(optionalMember); // when & then - // TODO: 커스텀 예외 처리 필요 - assertThrows(RuntimeException.class, () -> baseMemberService.checkUniqueEmailOrThrow(email)); + assertThrows(EmailAlreadyExistsException.class, () -> baseMemberService.checkUniqueEmailOrThrow(email)); then(baseMemberRepository).should(times(1)).findByEmail(email); } @@ -169,7 +166,7 @@ void checkUniqueEmailOrThrow_Unique() { } @ParameterizedTest - @ValueSource(longs = {validMemberId, validCuratorId, validHostId, validWelcomeMemberId}) + @ValueSource(longs = {normal1.id, curator1.id, host1.id, welcome1.id}) @DisplayName("각 유형의 사용자를 제거하는 메서드 테스트") void deleteById(long id) { // given diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/CuratorServiceUnitTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/CuratorServiceUnitTest.java index 735d8c4a..4ebc6be3 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/CuratorServiceUnitTest.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/CuratorServiceUnitTest.java @@ -1,9 +1,6 @@ package org.ktc2.cokaen.wouldyouin.member.application; import static org.assertj.core.api.Assertions.assertThat; -import static org.ktc2.cokaen.wouldyouin._global.TestData.ImageDomain.createValidMemberImage; -import static org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain.createValidCurator; -import static org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain.createValidMember; import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; @@ -14,11 +11,8 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.ktc2.cokaen.wouldyouin.Image.application.MemberImageService; -import org.ktc2.cokaen.wouldyouin.Image.persist.MemberImage; -import org.ktc2.cokaen.wouldyouin._common.vo.Area; -import org.ktc2.cokaen.wouldyouin._global.TestUtil; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.edit.CuratorEditRequest; +import org.ktc2.cokaen.wouldyouin.image.application.MemberImageService; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData; import org.ktc2.cokaen.wouldyouin.member.persist.BaseMemberRepository; import org.ktc2.cokaen.wouldyouin.member.persist.Curator; import org.ktc2.cokaen.wouldyouin.member.persist.CuratorRepository; @@ -51,14 +45,14 @@ class CuratorServiceUnitTest { @BeforeEach void setUp() { - validCurator = createValidCurator(); + validCurator = MemberData.curator1.entity.get(); } @Test @DisplayName("큐레이터 사용자 생성 테스트") void createCurator() { // given - Member validMember = createValidMember(); + Member validMember = MemberData.normal1.entity.get(); given(memberRepository.findById(validMember.getId())).willReturn(Optional.of(validMember)); // when @@ -72,34 +66,34 @@ void createCurator() { then(curatorRepository).should(times(1)).save(any(Curator.class)); } - @Test - @DisplayName("") - void updateCurator() { - // given - Long newProfileImageId = 5L; - MemberImage newProfileImage = createValidMemberImage(newProfileImageId); - CuratorEditRequest editRequest = CuratorEditRequest.curatorEditRequestBuilder() - .nickname(TestUtil.getOrNull("newNickname")) - .phoneNumber(TestUtil.getOrNull("010-1010-8888")) - .profileImageId(TestUtil.getOrNull(newProfileImageId)) - .area(Area.광주) - .intro(TestUtil.getOrNull("new intro")) - .build(); - - given(curatorRepository.findById(validCurator.getId())).willReturn(Optional.of(validCurator)); - given(memberImageService.getById(newProfileImageId)).willReturn(newProfileImage); - - // when - curatorService.updateCurator(validCurator.getId(), editRequest); - - // then - then(curatorRepository).should(times(1)).findById(validCurator.getId()); - int times = 1; - if (editRequest.getProfileImageId() == null) { - times = 0; - } - then(memberImageService).should(times(times)).getById(newProfileImageId); - } +// @Test +// @DisplayName("") +// void updateCurator() { +// // given +// Long newProfileImageId = 5L; +// MemberImage newProfileImage = createValidMemberImage(newProfileImageId); +// CuratorEditRequest editRequest = CuratorEditRequest.curatorEditRequestBuilder() +// .nickname(TestUtil.getOrNull("newNickname")) +// .phoneNumber(TestUtil.getOrNull("010-1010-8888")) +// .profileImageId(TestUtil.getOrNull(newProfileImageId)) +// .area(Area.광주) +// .intro(TestUtil.getOrNull("new intro")) +// .build(); +// +// given(curatorRepository.findById(validCurator.getId())).willReturn(Optional.of(validCurator)); +// given(memberImageService.getById(newProfileImageId)).willReturn(newProfileImage); +// +// // when +// curatorService.updateCurator(validCurator.getId(), editRequest); +// +// // then +// then(curatorRepository).should(times(1)).findById(validCurator.getId()); +// int times = 1; +// if (editRequest.getProfileImageId() == null) { +// times = 0; +// } +// then(memberImageService).should(times(times)).getById(newProfileImageId); +// } @Test @DisplayName("큐레이터 삭제 테스트") diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/HostServiceUnitTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/HostServiceUnitTest.java index a4052dd2..03d184dc 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/HostServiceUnitTest.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/HostServiceUnitTest.java @@ -1,23 +1,24 @@ package org.ktc2.cokaen.wouldyouin.member.application; import static org.assertj.core.api.Assertions.assertThat; -import static org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain.createValidHost; -import static org.ktc2.cokaen.wouldyouin._global.TestData.ImageDomain.createValidMemberImage; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.times; +import java.util.List; import java.util.Optional; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.ktc2.cokaen.wouldyouin.Image.application.MemberImageService; -import org.ktc2.cokaen.wouldyouin.Image.persist.MemberImage; -import org.ktc2.cokaen.wouldyouin.auth.application.dto.LocalLoginRequest; +import org.ktc2.cokaen.wouldyouin.image.application.MemberImageService; +import org.ktc2.cokaen.wouldyouin.image.persist.MemberImage; +import org.ktc2.cokaen.wouldyouin._global.testdata.ImageData; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData; +import org.ktc2.cokaen.wouldyouin.auth.api.dto.LocalLoginRequest; import org.ktc2.cokaen.wouldyouin._global.TestUtil; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.create.HostCreateRequest; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.edit.HostEditRequest; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.create.HostCreateRequest; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.edit.HostEditRequest; import org.ktc2.cokaen.wouldyouin.member.persist.Host; import org.ktc2.cokaen.wouldyouin.member.persist.HostRepository; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; @@ -49,7 +50,7 @@ class HostServiceUnitTest { @BeforeEach void setUp() { - validHost = createValidHost(); + validHost = MemberData.host1.entity.get(); } @Test @@ -59,13 +60,15 @@ void createHost() { String password = "host0password"; String hashedPassword = validHost.getHashedPassword(); MemberImage profileImage = validHost.getProfileImage(); + String thumbnailImageUrl = validHost.getProfileImageThumbnailUrl(); Long profileImageId = profileImage.getId(); given(hostCreateRequest.getPassword()).willReturn(password); given(hostCreateRequest.getProfileImageId()).willReturn(profileImageId); given(passwordEncoder.encode(password)).willReturn(hashedPassword); given(memberImageService.getById(profileImageId)).willReturn(profileImage); - given(hostCreateRequest.toEntity(hashedPassword, profileImage)).willReturn(validHost); + given(memberImageService.createThumbnail(profileImage.getName())).willReturn(thumbnailImageUrl); + given(hostCreateRequest.toEntity(hashedPassword, profileImage, thumbnailImageUrl)).willReturn(validHost); given(hostRepository.save(validHost)).willReturn(validHost); // when @@ -77,7 +80,7 @@ void createHost() { then(passwordEncoder).should(times(1)).encode(password); then(memberImageService).should(times(1)).getById(profileImageId); then(hostCreateRequest).should(times(1)) - .toEntity(hashedPassword, profileImage); + .toEntity(hashedPassword, profileImage, thumbnailImageUrl); then(hostRepository).should(times(1)).save(validHost); then(memberImageService).should(times(1)).setBaseMember(profileImage, validHost); } @@ -87,14 +90,14 @@ void createHost() { void updateHost() { // given Long newProfileImageId = 5L; - MemberImage newProfileImage = createValidMemberImage(newProfileImageId); + MemberImage newProfileImage = ImageData.member.host.entity.get(); newProfileImage.setBaseMember(validHost); HostEditRequest editRequest = HostEditRequest.builder() .nickname(TestUtil.getOrNull("newNickname")) .phoneNumber(TestUtil.getOrNull("010-1010-8888")) .profileImageId(TestUtil.getOrNull(newProfileImageId)) .intro(TestUtil.getOrNull("new intro")) - .hashtag(TestUtil.getOrNull("#new#hashtag")) + .hashtags(TestUtil.getOrNull(List.of("#new", "#hashtag"))) .build(); given(hostRepository.findById(validHost.getId())).willReturn(Optional.of(validHost)); diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/MemberServiceUnitTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/MemberServiceUnitTest.java index 038ec16b..de970f6f 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/MemberServiceUnitTest.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/member/application/MemberServiceUnitTest.java @@ -2,9 +2,7 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.ktc2.cokaen.wouldyouin._global.TestData.ImageDomain.createValidMemberImage; -import static org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain.createValidMember; -import static org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain.createValidWelcomeMember; +import static org.ktc2.cokaen.wouldyouin._global.testdata.ImageData.member.normal.entity.get; import static org.mockito.BDDMockito.given; import static org.mockito.BDDMockito.then; import static org.mockito.Mockito.never; @@ -15,12 +13,14 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; -import org.ktc2.cokaen.wouldyouin.Image.application.MemberImageService; -import org.ktc2.cokaen.wouldyouin.Image.persist.MemberImage; +import org.ktc2.cokaen.wouldyouin.image.application.MemberImageService; +import org.ktc2.cokaen.wouldyouin.image.persist.MemberImage; import org.ktc2.cokaen.wouldyouin._global.TestUtil; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.MemberAdditionalInfoRequest; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.create.MemberCreateRequest; -import org.ktc2.cokaen.wouldyouin.member.application.dto.request.edit.MemberEditRequest; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.MemberAdditionalInfoRequest; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.create.MemberCreateRequest; +import org.ktc2.cokaen.wouldyouin.member.api.dto.request.edit.MemberEditRequest; +import org.ktc2.cokaen.wouldyouin.member.exception.AdditionalInfoIllegalAccessException; import org.ktc2.cokaen.wouldyouin.member.persist.Member; import org.ktc2.cokaen.wouldyouin.member.persist.MemberRepository; import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; @@ -53,7 +53,7 @@ class MemberServiceUnitTest { @BeforeEach void setUp() { - validMember = createValidMember(); + validMember = MemberData.normal1.entity.get(); } @Test @@ -61,9 +61,10 @@ void setUp() { void createMember() { // given given(memberCreateRequest.getProfileImageUrl()).willReturn(validMember.getProfileImageUrl()); - given(memberCreateRequest.toEntity(validMember.getProfileImage())).willReturn(validMember); + given(memberCreateRequest.toEntity(validMember.getProfileImage(), validMember.getProfileImageThumbnailUrl())).willReturn(validMember); given(memberRepository.save(validMember)).willReturn(validMember); given(memberImageService.convert(memberCreateRequest.getProfileImageUrl())).willReturn(validMember.getProfileImage()); + given(memberImageService.createThumbnail(validMember.getProfileImage().getName())).willReturn(validMember.getProfileImageThumbnailUrl()); // when memberService.createMember(memberCreateRequest); @@ -76,7 +77,7 @@ void createMember() { @DisplayName("사용자 업데이트 테스트") void updateMember() { // given - MemberImage validMemberImage = createValidMemberImage(5L); + MemberImage validMemberImage = get(); Long givenMemberId = validMember.getId(); given(memberRepository.findById(givenMemberId)).willReturn(Optional.of(validMember)); given(memberImageService.getById(validMember.getProfileImage().getId())).willReturn(validMemberImage); @@ -101,7 +102,7 @@ void updateMember() { @Test @DisplayName("소셜 신규 사용자 추가정보 기입 테스트") void updateWelcomeMember() { - Member validWelcomeMember = createValidWelcomeMember(); + Member validWelcomeMember = MemberData.welcome1.entity.get(); given(memberRepository.findById(validWelcomeMember.getId())).willReturn(Optional.of(validWelcomeMember)); given(memberAdditionalInfoRequest.getPhone()).willReturn(validWelcomeMember.getPhone()); given(memberAdditionalInfoRequest.getArea()).willReturn(validWelcomeMember.getArea()); @@ -123,8 +124,7 @@ void updateWelcomeMember_NormalMemberUsage() { given(memberRepository.findById(validMember.getId())).willReturn(Optional.of(validMember)); // when & then - // TODO: 커스텀 예외 필요 - assertThrows(RuntimeException.class, () -> + assertThrows(AdditionalInfoIllegalAccessException.class, () -> memberService.updateWelcomeMember(validMember.getId(), memberAdditionalInfoRequest)); // then diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/reservation/ReservationControllerUnitTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/reservation/ReservationControllerUnitTest.java index 7b9124ff..3fd6996f 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/reservation/ReservationControllerUnitTest.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/reservation/ReservationControllerUnitTest.java @@ -1,140 +1,364 @@ -//package org.ktc2.cokaen.wouldyouin.reservation; -// -//import static java.lang.Math.abs; -//import static org.mockito.ArgumentMatchers.eq; -//import static org.mockito.BDDMockito.then; -//import static org.mockito.Mockito.times; -//import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; -//import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -//import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -//import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; -// -//import com.fasterxml.jackson.databind.ObjectMapper; -//import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule; -//import java.util.Random; -//import org.junit.jupiter.api.BeforeAll; -//import org.junit.jupiter.api.BeforeEach; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Test; -//import org.ktc2.cokaen.wouldyouin._global.TestData.MemberDomain; -//import org.ktc2.cokaen.wouldyouin._global.mockMember.WithMockCurator; -//import org.ktc2.cokaen.wouldyouin._global.mockMember.WithMockHost; -//import org.ktc2.cokaen.wouldyouin._global.mockMember.WithMockMember; -//import org.ktc2.cokaen.wouldyouin.auth.application.JwtAuthFilter; -//import org.ktc2.cokaen.wouldyouin.reservation.api.ReservationController; -//import org.ktc2.cokaen.wouldyouin.reservation.application.ReservationService; -//import org.springframework.beans.factory.annotation.Autowired; -//import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; -//import org.springframework.boot.test.mock.mockito.MockBean; -//import org.springframework.data.domain.PageRequest; -//import org.springframework.test.web.servlet.MockMvc; -//import org.springframework.test.web.servlet.setup.MockMvcBuilders; -//import org.springframework.web.context.WebApplicationContext; -// -//@WebMvcTest(ReservationController.class) -//class ReservationControllerUnitTest { -// -// private static ObjectMapper objectMapper; -// -// @MockBean -// private ReservationService reservationService; -// -// @MockBean -// private JwtAuthFilter jwtAuthFilter; -// -// @Autowired -// private MockMvc mockMvc; -// -// @Autowired -// private WebApplicationContext context; -// private static final long randomId = abs(new Random().nextLong()); -// -// @BeforeAll -// public static void init() { -// objectMapper = new ObjectMapper(); -// objectMapper.registerModule(new JavaTimeModule()); -// } -// -// @BeforeEach -// public void setup() throws Exception { -// mockMvc = MockMvcBuilders -// .webAppContextSetup(context) -// .apply(springSecurity()) -// .build(); -// } -// -// @Test -// @DisplayName("jwt 토큰 정보에 해당하는 member의 예약 목록을 조회한다.") -// @WithMockMember -// void getReservationsByMemberId1() throws Exception { -// // given, when -// mockMvc.perform(get("/api/reservations") -// .param("page", "5") -// .param("size", "20") -// .param("lastId", "100")).andDo(print()) -// .andExpect(status().isOk()); -// -// // then -// then(reservationService).should(times(1)).getAllByMemberId( -// eq(MemberDomain.validMemberId), eq(PageRequest.of(5, 20)), eq(100L)); -// } -// -// @Test -// @DisplayName("RequestParam으로 페이지 정보가 주어지지 않으면 기본값으로 페이지를 생성한다") -// @WithMockMember -// void getReservationsByMemberId2() throws Exception { -// // given, when -// mockMvc.perform(get("/api/reservations")).andDo(print()) -// .andExpect(status().isOk()); -// -// // then -// then(reservationService).should(times(1)).getAllByMemberId( -// eq(MemberDomain.validMemberId), eq(PageRequest.of(0, 10)), eq(Long.MAX_VALUE)); -// } -// -// @Test -// @DisplayName("jwt 토큰 정보에 해당하는 curator의 예약 목록을 조회한다.") -// @WithMockCurator -// void getReservationsByMemberId3() throws Exception { -// // given, when -// mockMvc.perform(get("/api/reservations") -// .param("page", "5") -// .param("size", "20") -// .param("lastId", "100")).andDo(print()) -// .andExpect(status().isOk()); -// -// // then -// then(reservationService).should(times(1)).getAllByMemberId( -// eq(MemberDomain.validMemberId), eq(PageRequest.of(5, 20)), eq(100L)); -// } -// -// @Test -// @DisplayName("Host의 권한으로 자신의 예약을 조회할 수 없다.") -// @WithMockHost -// void getReservationsByMemberId4() throws Exception { -// // given, when -// mockMvc.perform(get("/api/reservations") -// .param("page", "5") -// .param("size", "20") -// .param("lastId", "100")).andDo(print()) -// .andExpect(status().isUnauthorized()); -// -// // then -// then(reservationService).shouldHaveNoInteractions(); -// } -// -// @Test -// @DisplayName("PathVariable로 이벤트 ID를 받아 해당하는 이벤트를 조회한다.") -// @WithMockHost -// void getReservationsByMemberId4() throws Exception { -// // given, when -// mockMvc.perform(get("/api/reservations") -// .param("page", "5") -// .param("size", "20") -// .param("lastId", "100")).andDo(print()) -// .andExpect(status().isUnauthorized()); -// -// // then -// then(reservationService).shouldHaveNoInteractions(); -// } -//} \ No newline at end of file +package org.ktc2.cokaen.wouldyouin.reservation; + +import static java.lang.Math.abs; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.times; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Random; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockCurator1; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockHost1; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockMember1; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.curator1; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.host1; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.normal1; +import org.ktc2.cokaen.wouldyouin._global.testdata.ReservationData; +import org.ktc2.cokaen.wouldyouin.auth.application.JwtAuthFilter; +import org.ktc2.cokaen.wouldyouin.reservation.api.ReservationController; +import org.ktc2.cokaen.wouldyouin.reservation.api.dto.ReservationRequest; +import org.ktc2.cokaen.wouldyouin.reservation.application.ReservationService; +import org.mockito.ArgumentCaptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@WebMvcTest(ReservationController.class) +class ReservationControllerUnitTest { + + private static final long randomId = abs(new Random().nextLong()); + @Autowired + private ObjectMapper objectMapper; + @MockBean + private ReservationService reservationService; + @MockBean + private JwtAuthFilter jwtAuthFilter; + @Autowired + private MockMvc mockMvc; + @Autowired + private WebApplicationContext context; + + @BeforeEach + public void setup() throws Exception { + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @Test + @DisplayName("jwt 토큰 정보에 해당하는 member의 예약 목록을 조회한다.") + @WithMockMember1 + void getReservationsByMemberId1() throws Exception { + // given, when + mockMvc.perform(get("/api/reservations") + .param("page", "5") + .param("size", "20") + .param("lastId", "100")).andDo(print()) + .andExpect(status().isOk()); + + // then + then(reservationService).should(times(1)).getAllByMemberId( + eq(normal1.memberIdentifier), eq(PageRequest.of(5, 20)), eq(100L)); + } + + @Test + @DisplayName("RequestParam으로 페이지 정보가 주어지지 않으면 기본값으로 예약을 조회한다.") + @WithMockMember1 + void getReservationsByMemberId2() throws Exception { + // given, when + mockMvc.perform(get("/api/reservations")).andDo(print()) + .andExpect(status().isOk()); + + // then + then(reservationService).should(times(1)).getAllByMemberId( + eq(normal1.memberIdentifier), eq(PageRequest.of(0, 10)), eq(Long.MAX_VALUE)); + } + + @Test + @DisplayName("jwt 토큰 정보에 해당하는 curator의 예약 목록을 조회한다.") + @WithMockCurator1 + void getReservationsByMemberId3() throws Exception { + // given, when + mockMvc.perform(get("/api/reservations") + .param("page", "5") + .param("size", "20") + .param("lastId", "100")).andDo(print()) + .andExpect(status().isOk()); + + // then + then(reservationService).should(times(1)).getAllByMemberId( + eq(curator1.memberIdentifier), eq(PageRequest.of(5, 20)), eq(100L)); + } + + @Test + @DisplayName("Host의 권한으로 자신의 예약을 조회할 수 없다.") + @WithMockHost1 + void getReservationsByMemberId4() throws Exception { + // given, when + mockMvc.perform(get("/api/reservations") + .param("page", "5") + .param("size", "20") + .param("lastId", "100")).andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(reservationService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("PathVariable로 이벤트 ID를 받아 해당하는 이벤트를 조회한다.") + @WithMockHost1 + void getReservationByEventId1() throws Exception { + // given, when + mockMvc.perform(get("/api/reservations/events/" + randomId) + .param("page", "5") + .param("size", "20") + .param("lastId", "100")).andDo(print()) + .andExpect(status().isOk()); + + // then + then(reservationService).should(times(1)).getAllByEventId( + eq(host1.memberIdentifier), eq(randomId), eq(PageRequest.of(5, 20)), eq(100L)); + } + + @Test + @DisplayName("RequestParam으로 페이지 정보가 주어지지 않으면 기본값으로 예약을 조회한다.") + @WithMockHost1 + void getReservationByEventId2() throws Exception { + // given, when + mockMvc.perform(get("/api/reservations/events/" + randomId)) + .andDo(print()) + .andExpect(status().isOk()); + + // then + then(reservationService).should(times(1)).getAllByEventId( + eq(host1.memberIdentifier), eq(randomId), eq(PageRequest.of(0, 10)), + eq(Long.MAX_VALUE)); + } + + @Test + @DisplayName("Member의 권한으로 이벤트 ID를 통해 이벤트의 예약을 조회할 수 없다.") + @WithMockMember1 + void getReservationByEventId3() throws Exception { + // given, when + mockMvc.perform(get("/api/reservations/events/" + randomId)) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(reservationService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("Curator의 권한으로 이벤트 ID를 통해 이벤트의 예약을 조회할 수 없다.") + @WithMockCurator1 + void getReservationByEventId4() throws Exception { + // given, when + mockMvc.perform(get("/api/reservations/events/" + randomId)) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(reservationService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("예약 ID를 통해 예약을 조회한다.") + @WithMockCurator1 + void getReservationById1() throws Exception { + // given, when + mockMvc.perform(get("/api/reservations/" + randomId)).andDo(print()) + .andExpect(status().isOk()); + + // then + then(reservationService).should(times(1)).getById(randomId); + } + + @Test + @DisplayName("Member 권한으로 RequestBody를 통해 예약을 생성한다.") + @WithMockMember1 + void createReservation1() throws Exception { + // given + ArgumentCaptor captor = ArgumentCaptor.forClass( + ReservationRequest.class); + ReservationRequest request = ReservationData.reservation1.request.get(); + + // when + mockMvc.perform(post("/api/reservations") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isCreated()); + + // then + then(reservationService).should(times(1)).create(eq(normal1.memberIdentifier), captor.capture()); + assertThat(captor.getValue()).isEqualTo(request); + } + + @Test + @DisplayName("Curator 권한으로 RequestBody를 통해 예약을 생성한다.") + @WithMockCurator1 + void createReservation2() throws Exception { + // given + ArgumentCaptor captor = ArgumentCaptor.forClass( + ReservationRequest.class); + ReservationRequest request = ReservationData.reservation1.request.get(); + + // when + mockMvc.perform(post("/api/reservations") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isCreated()); + + // then + then(reservationService).should(times(1)).create(eq(curator1.memberIdentifier), captor.capture()); + assertThat(captor.getValue()).isEqualTo(request); + } + + @Test + @DisplayName("Host 권한으로 예약을 생성할 수 없다.") + @WithMockHost1 + void createReservation3() throws Exception { + // given, when + mockMvc.perform(post("/api/reservations") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(ReservationData.reservation1.request.get()))) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(reservationService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("예약 생성 시 이벤트 ID는 빈 값이 될 수 없다.") + @WithMockMember1 + void createReservation4() throws Exception { + // given + ReservationRequest request = ReservationData.reservation1.request.get().toBuilder() + .eventId(null).build(); + + // when + mockMvc.perform(post("/api/reservations") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("이벤트 ID는 필수입니다.")); + + // then + then(reservationService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("예약 생성 시 수량은 빈 값이 될 수 없다.") + @WithMockMember1 + void createReservation5() throws Exception { + // given + ReservationRequest request = ReservationData.reservation1.request.get().toBuilder() + .quantity(null).build(); + + // when + mockMvc.perform(post("/api/reservations") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("수량은 필수입니다.")); + + // then + then(reservationService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("예약 생성 시 수량은 1개 이상이어야 한다.") + @WithMockMember1 + void createReservation6() throws Exception { + // given + ReservationRequest request = ReservationData.reservation1.request.get().toBuilder() + .quantity(0).build(); + + // when + mockMvc.perform(post("/api/reservations") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("수량은 1개 이상이어야 합니다.")); + + // then + then(reservationService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("Member 권한으로 예약 ID를 통해 예약을 삭제한다.") + @WithMockMember1 + void deleteReservation1() throws Exception { + // given, when + mockMvc.perform(delete("/api/reservations/" + randomId) + .with(csrf())) + .andDo(print()) + .andExpect(status().isNoContent()); + + // then + then(reservationService).should(times(1)).delete(normal1.memberIdentifier, randomId); + } + + @Test + @DisplayName("Curator 권한으로 예약 ID를 통해 예약을 삭제한다.") + @WithMockCurator1 + void deleteReservation2() throws Exception { + // given, when + mockMvc.perform(delete("/api/reservations/" + randomId) + .with(csrf())) + .andDo(print()) + .andExpect(status().isNoContent()); + + // then + then(reservationService).should(times(1)).delete(curator1.memberIdentifier, randomId); + } + + @Test + @DisplayName("Host 권한으로 예약 ID를 통해 예약을 삭제할 수 없다.") + @WithMockHost1 + void deleteReservation3() throws Exception { + // given, when + mockMvc.perform(delete("/api/reservations/" + randomId) + .with(csrf())) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(reservationService).shouldHaveNoInteractions(); + } +} \ No newline at end of file diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/reservation/ReservationServiceUnitTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/reservation/ReservationServiceUnitTest.java index 86fa090a..d58454e1 100644 --- a/src/test/java/org/ktc2/cokaen/wouldyouin/reservation/ReservationServiceUnitTest.java +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/reservation/ReservationServiceUnitTest.java @@ -1,101 +1,174 @@ -//package org.ktc2.cokaen.wouldyouin.reservation; -// -//import static java.lang.Math.abs; -//import static org.junit.jupiter.api.Assertions.assertThrows; -//import static org.mockito.ArgumentMatchers.any; -//import static org.mockito.Mockito.times; -//import static org.mockito.Mockito.verify; -//import static org.mockito.Mockito.when; -// -//import java.util.List; -//import java.util.Optional; -//import java.util.Random; -//import org.junit.jupiter.api.BeforeEach; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Test; -//import org.junit.jupiter.api.extension.ExtendWith; -//import org.ktc2.cokaen.wouldyouin.event.application.EventService; -//import org.ktc2.cokaen.wouldyouin.global.TestData; -//import org.ktc2.cokaen.wouldyouin.member.application.MemberService; -//import org.ktc2.cokaen.wouldyouin.payment.application.PaymentService; -//import org.ktc2.cokaen.wouldyouin.reservation.application.ReservationService; -//import org.ktc2.cokaen.wouldyouin.reservation.persist.ReservationRepository; -//import org.mockito.Mock; -//import org.mockito.junit.jupiter.MockitoExtension; -// -//@ExtendWith({MockitoExtension.class}) -//class ReservationServiceUnitTest { -// -// private ReservationService reservationService; -// -// @Mock -// private ReservationRepository reservationRepository; -// @Mock -// private MemberService memberService; -// @Mock -// private EventService eventService; -// @Mock -// private PaymentService paymentService; -// -// private Long id; -// -// @BeforeEach -// void setUp() { -// reservationService = new ReservationService(reservationRepository, paymentService, memberService, eventService); -// id = abs(new Random().nextLong()); -// } -// -// @Test -// @DisplayName("사용자 id를 통한 모든 예약 조회 - 성공") -// void getAllByMemberId() { -// when(reservationRepository.findByMemberIdOrderByReservationIdDesc(id)).thenReturn(List.of()); -// reservationService.getAllByMemberId(id); -// verify(reservationRepository, times(1)).findByMemberIdOrderByReservationIdDesc(id); -// } -// -// @Test -// @DisplayName("행사 id를 통한 모든 예약 조회 - 성공") -// void getAllByEventId() { -// when(reservationRepository.findByEventIdOrderByReservationIdDesc(id)).thenReturn(List.of()); -// reservationService.getAllByEventId(id); -// verify(reservationRepository, times(1)).findByEventIdOrderByReservationIdDesc(id); -// } -// -// @Test -// @DisplayName("예약 id를 통한 예약 조회 - 성공") -// void getById() { -// when(reservationRepository.findById(id)).thenReturn(Optional.of(TestData.validReservation)); -// reservationService.getById(id); -// verify(reservationRepository, times(1)).findById(id); -// } -// -// @Test -// @DisplayName("유효하지 않은 예약 id를 통한 모든 예약 조회 - 실패") -// void getByInvalidId() { -// when(reservationRepository.findById(id)).thenThrow(RuntimeException.class); -// assertThrows(RuntimeException.class, () -> reservationService.getById(id)); -// } -// -// @Test -// @DisplayName("예약 생성 - 성공") -// void create() { -// when(reservationRepository.save(any())).thenReturn(TestData.validReservation); -// reservationService.create(1L, TestData.validReservationRequest); -// verify(reservationRepository, times(1)).save(any()); -// } -// -// @Test -// @DisplayName("예약 삭제 - 성공") -// void delete() { -// when(reservationRepository.findById(id)).thenReturn(Optional.of(TestData.validReservation)); -// reservationService.delete(id); -// verify(reservationRepository, times(1)).findById(id); -// } -// -// @Test -// @DisplayName("예약 삭제 - 실패") -// void deleteByInvalidId() { -// when(reservationRepository.findById(id)).thenThrow(RuntimeException.class); -// assertThrows(RuntimeException.class, () -> reservationService.delete(id)); -// } -//} \ No newline at end of file +package org.ktc2.cokaen.wouldyouin.reservation; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.times; + +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.ktc2.cokaen.wouldyouin.reservation.exception.ReservationNotFoundForReviewException; +import org.ktc2.cokaen.wouldyouin._common.exception.UnauthorizedException; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.normal1; +import org.ktc2.cokaen.wouldyouin._global.testdata.ReservationData; +import org.ktc2.cokaen.wouldyouin._global.testdata.ReservationData.R; +import org.ktc2.cokaen.wouldyouin._global.testdata.ReservationData.R.reservation1; +import org.ktc2.cokaen.wouldyouin.auth.MemberIdentifier; +import org.ktc2.cokaen.wouldyouin.event.application.EventService; +import org.ktc2.cokaen.wouldyouin.member.application.MemberService; +import org.ktc2.cokaen.wouldyouin.member.persist.MemberType; +import org.ktc2.cokaen.wouldyouin.payment.application.PaymentService; +import org.ktc2.cokaen.wouldyouin.reservation.api.dto.KakaoPayReservationResponse; +import org.ktc2.cokaen.wouldyouin.reservation.api.dto.ReservationResponse; +import org.ktc2.cokaen.wouldyouin.reservation.api.dto.ReservationSliceResponse; +import org.ktc2.cokaen.wouldyouin.reservation.application.ReservationService; +import org.ktc2.cokaen.wouldyouin.reservation.persist.ReservationRepository; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +@ExtendWith({MockitoExtension.class}) +class ReservationServiceUnitTest { + + private ReservationService reservationService; + + @Mock + private ReservationRepository reservationRepository; + @Mock + private MemberService memberService; + @Mock + private EventService eventService; + @Mock + private PaymentService paymentService; + + @BeforeEach + void setUp() { + reservationService = new ReservationService(reservationRepository, paymentService, memberService, eventService); + } + + @Test + @DisplayName("예약 ID를 통한 해당 예약을 반환한다.") + void getById() { + // given + given(reservationRepository.findById(reservation1.id)).willReturn(Optional.of(ReservationData.reservation1.entity.get())); + + // when + ReservationResponse response = reservationService.getById(reservation1.id); + + // then + assertThat(response).isEqualTo(ReservationResponse.from(ReservationData.reservation1.entity.get())); + } + + @Test + @DisplayName("멤버 ID를 통해 자신의 모든 예약을 반환한다.") + void getAllByMemberId() { + // given + given(reservationRepository.findByMemberIdOrderByReservationIdDesc(normal1.id, R.lastId, ReservationData.R.pageable)) + .willReturn(ReservationData.ReservationSlice.get()); + + // when + ReservationSliceResponse response = + reservationService.getAllByMemberId(normal1.memberIdentifier, R.pageable, R.lastId); + + // then + assertThat(response).isEqualTo(ReservationData.sliceResponse.get()); + } + + @Test + @DisplayName("이벤트 ID를 통해 해당 행사의 모든 예약을 반환한다.") + void getAllByEventId() { + // given + given(eventService.getByIdOrThrow(R.reservation1._Relation.event().getId())).willReturn(R.reservation1._Relation.event()); + given(reservationRepository.findByEventIdOrderByReservationIdDesc(R.reservation1._Relation.event().getId(), R.lastId, R.pageable)) + .willReturn(ReservationData.ReservationSlice.get()); + + // when + ReservationSliceResponse response = + reservationService.getAllByEventId(normal1.memberIdentifier, R.reservation1._Relation.event().getId(), R.pageable, R.lastId); + + // then + assertThat(response).isEqualTo(ReservationData.sliceResponse.get()); + } + + @Test + @DisplayName("예약 생성 DTO를 통해 결제를 진행하고 예약을 생성한다.") + void create() { + // given + given(reservationRepository.save(any())).willReturn(ReservationData.reservation1.entity.get()); + given(eventService.getByIdOrThrow(R.reservation1._Relation.event().getId())).willReturn(R.reservation1._Relation.event()); + given(reservationRepository.save(any())).willReturn(ReservationData.reservation1.entity.get()); + given(paymentService.createPayment(any())).willReturn(ReservationData.reservation1.kakaoPayResponse.get()); + + // when + KakaoPayReservationResponse response = + reservationService.create(normal1.memberIdentifier, ReservationData.reservation1.request.get()); + + // then + assertThat(response).isEqualTo(ReservationData.reservation1.kakaoPayReservationResponse.get()); + } + + @Test + @DisplayName("예약 생성 DTO를 통해 예약을 생성한다.") + void createTest() { + // given + given(reservationRepository.save(any())).willReturn(ReservationData.reservation1.entity.get()); + given(eventService.getByIdOrThrow(R.reservation1._Relation.event().getId())).willReturn(R.reservation1._Relation.event()); + given(reservationRepository.save(any())).willReturn(ReservationData.reservation1.entity.get()); + + // when + ReservationResponse response = + reservationService.createTest(normal1.memberIdentifier, ReservationData.reservation1.request.get()); + + // then + assertThat(response).isEqualTo(ReservationData.reservation1.response.get()); + } + + @Test + @DisplayName("예약 ID를 통해 해당 예약을 삭제한다.") + void delete1() { + // given + given(reservationRepository.findById(reservation1.id)).willReturn(Optional.of(ReservationData.reservation1.entity.get())); + + // when + reservationService.delete(normal1.memberIdentifier, reservation1.id); + + // then + then(reservationRepository).should(times(1)).deleteById(reservation1.id); + } + + @Test + @DisplayName("멤버 ID와 예약의 멤버 ID가 일치하지 않으면 예약을 삭제할 수 없다.") + void delete2() { + // given + MemberIdentifier anotherMember = new MemberIdentifier(100L, MemberType.normal); + given(reservationRepository.findById(reservation1.id)).willReturn(Optional.of(ReservationData.reservation1.entity.get())); + + // when + UnauthorizedException exception = + assertThrows(UnauthorizedException.class, () -> reservationService.delete(anotherMember, reservation1.id)); + + // then + then(reservationRepository).should(times(0)).deleteById(reservation1.id); + assertThat(exception.getMessage()).isEqualTo("사용자 ID가 예약한 사용자 ID와 일치하지 않습니다."); + } + + @Test + @DisplayName("사용자는 해당 이벤트에 대한 예약이 없으면 리뷰를 작성할 수 없다.") + void validateByMemberIdAndEventId() { + // given + given(reservationRepository.findByMemberIdAndEventId(normal1.id, R.reservation1._Relation.event().getId())).willReturn(List.of()); + + // when + ReservationNotFoundForReviewException exception = + assertThrows(ReservationNotFoundForReviewException.class, + () -> reservationService.validateByMemberIdAndEventId(normal1.id, R.reservation1._Relation.event().getId())); + + // then + assertThat(exception.getMessage()).isEqualTo("해당 이벤트에 대한 리뷰를 작성할 수 없습니다."); + } +} \ No newline at end of file diff --git a/src/test/java/org/ktc2/cokaen/wouldyouin/review/ReviewControllerUnitTest.java b/src/test/java/org/ktc2/cokaen/wouldyouin/review/ReviewControllerUnitTest.java new file mode 100644 index 00000000..d34f7790 --- /dev/null +++ b/src/test/java/org/ktc2/cokaen/wouldyouin/review/ReviewControllerUnitTest.java @@ -0,0 +1,571 @@ +package org.ktc2.cokaen.wouldyouin.review; + +import static java.lang.Math.abs; +import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.BDDMockito.then; +import static org.mockito.Mockito.times; +import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf; +import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.put; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +import com.fasterxml.jackson.databind.ObjectMapper; +import java.util.Random; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockCurator1; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockHost1; +import org.ktc2.cokaen.wouldyouin._global.mockmember.WithMockMember1; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.curator1; +import org.ktc2.cokaen.wouldyouin._global.testdata.MemberData.R.normal1; +import org.ktc2.cokaen.wouldyouin._global.testdata.ReviewData; +import org.ktc2.cokaen.wouldyouin.auth.application.JwtAuthFilter; +import org.ktc2.cokaen.wouldyouin.review.api.ReviewController; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewCreateRequest; +import org.ktc2.cokaen.wouldyouin.review.api.dto.ReviewEditRequest; +import org.ktc2.cokaen.wouldyouin.review.application.ReviewService; +import org.mockito.ArgumentCaptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; +import org.springframework.boot.test.mock.mockito.MockBean; +import org.springframework.data.domain.PageRequest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +@WebMvcTest(ReviewController.class) +public class ReviewControllerUnitTest { + + private static final long randomId = abs(new Random().nextLong()); + @Autowired + private ObjectMapper objectMapper; + @MockBean + private ReviewService reviewService; + @MockBean + private JwtAuthFilter jwtAuthFilter; + @Autowired + private MockMvc mockMvc; + @Autowired + private WebApplicationContext context; + + @BeforeEach + public void setup() throws Exception { + mockMvc = MockMvcBuilders + .webAppContextSetup(context) + .apply(springSecurity()) + .build(); + } + + @Test + @DisplayName("jwt 토큰 정보에 해당하는 member의 리뷰 목록을 조회한다.") + @WithMockMember1 + void getReviewsByMemberId1() throws Exception { + // given, when + mockMvc.perform(get("/api/reviews") + .param("page", "5") + .param("size", "20") + .param("lastId", "100")).andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)).getAllByMemberId( + eq(normal1.id), eq(PageRequest.of(5, 20)), eq(100L)); + } + + @Test + @DisplayName("RequestParam으로 페이지 정보가 주어지지 않으면 기본값으로 리뷰 목록을 조회한다.") + @WithMockMember1 + void getReviewsByMemberId2() throws Exception { + // given, when + mockMvc.perform(get("/api/reviews")).andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)).getAllByMemberId( + eq(normal1.id), eq(PageRequest.of(0, 10)), eq(Long.MAX_VALUE)); + } + + @Test + @DisplayName("PathVariable로 이벤트 ID를 받아 해당하는 리뷰 목록을 조회한다.") + @WithMockMember1 + void getReviewsByEventId() throws Exception { + // given, when + mockMvc.perform(get("/api/reviews/events/" + randomId) + .param("page", "5") + .param("size", "20") + .param("lastId", "100")).andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)).getAllByEventId( + eq(randomId), eq(PageRequest.of(5, 20)), eq(100L)); + } + + @Test + @DisplayName("RequestParam으로 페이지 정보가 주어지지 않으면 기본값으로 리뷰 목록을 조회한다.") + @WithMockMember1 + void getReviewsByEventId2() throws Exception { + // given, when + mockMvc.perform(get("/api/reviews/events/" + randomId)) + .andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)).getAllByEventId( + eq(randomId), eq(PageRequest.of(0, 10)), eq(Long.MAX_VALUE)); + } + + @Test + @DisplayName("리뷰 ID를 통해 리뷰를 조회한다.") + @WithMockMember1 + void getReviewByReviewId() throws Exception { + // given, when + mockMvc.perform(get("/api/reviews/" + randomId)).andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)).getById(randomId); + } + + @Test + @DisplayName("jwt 토큰 정보에 해당하는 member가 리뷰를 작성하지 않은 이벤트 목록을 조회한다.") + @WithMockMember1 + void getUnreviewedEventsByMemberId1() throws Exception { + // given, when + mockMvc.perform(get("/api/reviews/events") + .param("page", "5") + .param("size", "20") + .param("lastId", "100")).andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)).getUnreviewedEventsByMemberId( + eq(normal1.id), eq(PageRequest.of(5, 20)), eq(100L)); + } + + @Test + @DisplayName("Host의 권한으로 자신이 리뷰를 작성하지 않은 이벤트 목록을 조회할 수 없다.") + @WithMockHost1 + void getUnreviewedEventsByMemberId2() throws Exception { + // given, when + mockMvc.perform(get("/api/reviews/events") + .param("page", "5") + .param("size", "20") + .param("lastId", "100")).andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("Member 권한으로 RequestBody를 통해 리뷰를 생성한다.") + @WithMockMember1 + void createReview1() throws Exception { + // given + ArgumentCaptor captor = ArgumentCaptor.forClass( + ReviewCreateRequest.class); + ReviewCreateRequest request = ReviewData.review1.request.create.get(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isCreated()); + + // then + then(reviewService).should(times(1)).create(eq(normal1.id), captor.capture()); + assertThat(captor.getValue()).isEqualTo(request); + } + + @Test + @DisplayName("Curator 권한으로 RequestBody를 통해 리뷰를 생성한다.") + @WithMockCurator1 + void createReview2() throws Exception { + // given + ArgumentCaptor captor = ArgumentCaptor.forClass( + ReviewCreateRequest.class); + ReviewCreateRequest request = ReviewData.review1.request.create.get(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isCreated()); + + // then + then(reviewService).should(times(1)).create(eq(curator1.id), captor.capture()); + assertThat(captor.getValue()).isEqualTo(request); + } + + @Test + @DisplayName("Host 권한으로 RequestBody를 통해 리뷰를 생성할 수 없다.") + @WithMockHost1 + void createReview3() throws Exception { + // given + ArgumentCaptor captor = ArgumentCaptor.forClass( + ReviewCreateRequest.class); + ReviewCreateRequest request = ReviewData.review1.request.create.get(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 생성 시 이벤트 ID는 빈 값이 될 수 없다.") + @WithMockMember1 + void createReview4() throws Exception { + // given + ReviewCreateRequest request = ReviewData.review1.request.create.get().toBuilder() + .eventId(null).build(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("이벤트 ID는 필수입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 생성 시 별점은 빈 값이 될 수 없다.") + @WithMockMember1 + void createReview5() throws Exception { + // given + ReviewCreateRequest request = ReviewData.review1.request.create.get().toBuilder() + .score(null).build(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("별점은 필수입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 생성 시 별점은 0점 이상이어야 한다.") + @WithMockMember1 + void createReview6() throws Exception { + // given + ReviewCreateRequest request = ReviewData.review1.request.create.get().toBuilder() + .score(-1).build(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("별점은 0점 이상입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 생성 시 별점은 5점 이하이어야 한다.") + @WithMockMember1 + void createReview7() throws Exception { + // given + ReviewCreateRequest request = ReviewData.review1.request.create.get().toBuilder() + .score(8).build(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("별점은 5점 이하입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 생성 시 내용은 빈 값이 될 수 없다.") + @WithMockMember1 + void createReview8() throws Exception { + // given + ReviewCreateRequest request = ReviewData.review1.request.create.get().toBuilder() + .content(null).build(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("내용은 필수입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 생성 시 내용은 5자 이상 50자 이하입니다.") + @WithMockMember1 + void createReview9() throws Exception { + // given + ReviewCreateRequest request = ReviewData.review1.request.create.get().toBuilder() + .content("짧은내용").build(); + + // when + mockMvc.perform(post("/api/reviews") + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("내용은 5자 이상 50자 이하입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("Member 권한으로 RequestBody를 통해 리뷰를 수정한다.") + @WithMockMember1 + void updateReview1() throws Exception { + // given + ArgumentCaptor captor = ArgumentCaptor.forClass( + ReviewEditRequest.class); + ReviewEditRequest request = ReviewData.review1.request.edit1.get(); + + // when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)).update(eq(normal1.id), eq(randomId), captor.capture()); + assertThat(captor.getValue()).isEqualTo(request); + } + + @Test + @DisplayName("Curator 권한으로 RequestBody를 통해 리뷰를 생성한다.") + @WithMockCurator1 + void updateReview2() throws Exception { + // given + ArgumentCaptor captor = ArgumentCaptor.forClass( + ReviewEditRequest.class); + ReviewEditRequest request = ReviewData.review1.request.edit1.get(); + + // when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isOk()); + + // then + then(reviewService).should(times(1)) + .update(eq(curator1.id), eq(randomId), captor.capture()); + assertThat(captor.getValue()).isEqualTo(request); + } + + @Test + @DisplayName("Host 권한으로 RequestBody를 통해 리뷰를 생성할 수 없다.") + @WithMockHost1 + void updateReview3() throws Exception { + // given, when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(ReviewData.review1.request.edit1.get()))) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 수정 시, 별점은 빈 값이 될 수 없다. ") + @WithMockCurator1 + void updateReview4() throws Exception { + // given + ReviewEditRequest request = ReviewData.review1.request.edit1.get().toBuilder() + .score(null).build(); + + // when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("별점은 필수입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 수정 시, 별점은 0점 이상이어야 한다.") + @WithMockCurator1 + void updateReview5() throws Exception { + // given + ReviewEditRequest request = ReviewData.review1.request.edit1.get().toBuilder() + .score(-1).build(); + + // when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("별점은 0점 이상입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 수정 시, 별점은 5점 이하여야 한다.") + @WithMockCurator1 + void updateReview6() throws Exception { + // given + ReviewEditRequest request = ReviewData.review1.request.edit1.get().toBuilder() + .score(8).build(); + + // when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("별점은 5점 이하입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 수정 시, 내용은 필수입니다.") + @WithMockCurator1 + void updateReview7() throws Exception { + // given + ReviewEditRequest request = ReviewData.review1.request.edit1.get().toBuilder() + .content(null).build(); + + // when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("내용은 필수입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("리뷰 수정 시, 내용은 5자 이상 50자 이하입니다.") + @WithMockCurator1 + void updateReview8() throws Exception { + // given + ReviewEditRequest request = ReviewData.review1.request.edit1.get().toBuilder() + .content("짧은내용").build(); + + // when + mockMvc.perform(put("/api/reviews/" + randomId) + .with(csrf()) + .contentType(MediaType.APPLICATION_JSON) + .content(objectMapper.writeValueAsString(request))) + .andDo(print()) + .andExpect(status().isBadRequest()) + .andExpect(jsonPath("$.message").value("내용은 5자 이상 50자 이하입니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } + + @Test + @DisplayName("Member 권한으로 리뷰 ID를 통해 리뷰를 삭제한다.") + @WithMockMember1 + void deleteReview1() throws Exception { + // given, when + mockMvc.perform(delete("/api/reviews/" + randomId) + .with(csrf())) + .andDo(print()) + .andExpect(status().isNoContent()); + + // then + then(reviewService).should(times(1)).delete(normal1.id, randomId); + } + + @Test + @DisplayName("Curator 권한으로 리뷰 ID를 통해 리뷰를 삭제한다.") + @WithMockCurator1 + void deleteReview2() throws Exception { + // given, when + mockMvc.perform(delete("/api/reviews/" + randomId) + .with(csrf())) + .andDo(print()) + .andExpect(status().isNoContent()); + + // then + then(reviewService).should(times(1)).delete(curator1.id, randomId); + } + + @Test + @DisplayName("Host 권한으로 리뷰 ID를 통해 리뷰를 삭제할 수 없다.") + @WithMockHost1 + void deleteReview3() throws Exception { + // given, when + mockMvc.perform(delete("/api/reviews/" + randomId) + .with(csrf())) + .andDo(print()) + .andExpect(status().isUnauthorized()) + .andExpect(jsonPath("$.message").value("요구된 멤버 형식과 실제 형식이 다릅니다.")); + + // then + then(reviewService).shouldHaveNoInteractions(); + } +}