Skip to content

Commit

Permalink
[Weekly/11/Reservation/test] Reservation 유닛테스트 (#140)
Browse files Browse the repository at this point in the history
  • Loading branch information
Daolove0323 authored Nov 14, 2024
1 parent 268537b commit e27b377
Show file tree
Hide file tree
Showing 28 changed files with 577 additions and 277 deletions.
Original file line number Diff line number Diff line change
@@ -1,9 +1,12 @@
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;
Expand Down Expand Up @@ -37,6 +40,17 @@ public static void saveFile(byte[] file, Path path) {
}
}

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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ public enum Category {
전체,
밴드,
연극,
뮤지컬,
원데이클래스,
뮤지컬,
전시회,
공예,
축제
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,16 +49,6 @@ public ResponseEntity<ApiResponseBody<AdvertisementResponse>> createAdvertisemen
return ApiResponse.created(advertisementService.create(advertisementRequest, image));
}

@PutMapping(path = "/{adId}", consumes = {MediaType.APPLICATION_JSON_VALUE,
MediaType.MULTIPART_FORM_DATA_VALUE})
public ResponseEntity<ApiResponseBody<AdvertisementResponse>> updateAdvertisement(
@PathVariable Long adId,
@Valid @RequestPart AdvertisementRequest advertisementRequest,
@RequestPart(required = false) MultipartFile image,
@Authorize(MemberType.admin) MemberIdentifier identifier) {
return ApiResponse.ok(advertisementService.update(identifier, adId, advertisementRequest, image));
}

@DeleteMapping("/{adId}")
public ResponseEntity<ApiResponseBody<Void>> deleteAdvertisement(
@PathVariable Long adId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,12 +23,6 @@ public class AdvertisementService {
private final AdvertisementRepository adRepository;
private final AdvertisementImageService adImageService;

@Transactional
public Advertisement getByIdOrThrow(Long adId) {
return adRepository.findById(adId)
.orElseThrow(() -> new EntityNotFoundException("해당하는 광고를 찾을 수 없습니다."));
}

@Transactional(readOnly = true)
public AdvertisementResponse getById(Long adId) {
return AdvertisementResponse.from(getByIdOrThrow(adId));
Expand All @@ -40,7 +34,6 @@ public List<AdvertisementResponse> getAllActiveAdvertisements() {
.map(AdvertisementResponse::from).toList();
}

// Todo: 롤백될 경우, 저장한 이미지 삭제
@Transactional
public AdvertisementResponse create(AdvertisementRequest adRequest, MultipartFile image) {
AdvertisementImage adImage = adImageService.saveImage(image);
Expand All @@ -49,30 +42,15 @@ public AdvertisementResponse create(AdvertisementRequest adRequest, MultipartFil
return AdvertisementResponse.from(ad);
}

// Todo: 수정될 때 이미지가 null인 경우 기존 이미지로 대체하는 로직 프론트와 협의
// Todo: 롤백될 경우, 저장한 이미지 삭제
@Transactional
public AdvertisementResponse update(MemberIdentifier identifier, Long adId, AdvertisementRequest adRequest, MultipartFile multipartFile) {
Advertisement ad = getByIdOrThrow(adId);
Optional.ofNullable(multipartFile).ifPresentOrElse(
image -> {
adImageService.deleteImage(identifier, ad.getAdvertisementImage().getId());
AdvertisementImage adImage = adImageService.saveImage(image);
ad.updateFrom(adRequest, adImage);
adImage.setAdvertisement(ad);
},
() -> {
AdvertisementImage adImage = ad.getAdvertisementImage();
ad.updateFrom(adRequest, adImage);
}
);
return AdvertisementResponse.from(ad);
}

@Transactional
public void delete(MemberIdentifier identifier, Long adId) {
Advertisement ad = getByIdOrThrow(adId);
adImageService.deleteImage(identifier, ad.getAdvertisementImage().getId());
adRepository.deleteById(adId);
}

private Advertisement getByIdOrThrow(Long adId) {
return adRepository.findById(adId)
.orElseThrow(() -> new EntityNotFoundException("해당하는 광고를 찾을 수 없습니다."));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ private CurationCard getByIdOrThrow(Long id) {
return curationCardRepository.findById(id).orElseThrow(() -> new EntityNotFoundException("해당하는 큐레이션 카드를 찾을 수 없습니다."));
}

public List<String> getImageUrls(CurationCard curationCard) {
private List<String> getImageUrls(CurationCard curationCard) {
return curationCard.getCurationImages().stream()
.map(curationImageService::getImageUrl)
.toList();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
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.ktc2.cokaen.wouldyouin.payment.application.PaymentService;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
Expand All @@ -31,15 +30,15 @@ public class ImageController {

private final ImageServiceFactory imageServiceFactory;
private final ImageStorageService imageStorageService;
private final PaymentService paymentService;

@GetMapping(value = "/{directory}/{file}", produces = {MediaType.IMAGE_PNG_VALUE, MediaType.IMAGE_JPEG_VALUE})
public ResponseEntity<byte[]> 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<byte[]> getThumnailImage(@PathVariable String directory, @PathVariable String thumbnail, @PathVariable String file) {
public ResponseEntity<byte[]> getThumnailImage(@PathVariable String directory, @PathVariable String thumbnail,
@PathVariable String file) {
return ResponseEntity.status(HttpStatus.OK).body(imageStorageService.readFromDirectory(Paths.get(directory, thumbnail, file)));
}

Expand All @@ -51,7 +50,6 @@ public ResponseEntity<ApiResponseBody<List<ImageResponse>>> uploadImages(
return ApiResponse.ok(imageServiceFactory.getImageService(imageDomain).saveImages(images));
}

// Todo: 삭제로직 수정필요
@DeleteMapping("/{imageId}")
public ResponseEntity<ApiResponseBody<Void>> deleteImage(
@PathVariable Long imageId,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,17 @@
import org.springframework.web.multipart.MultipartFile;

@Service
@RequiredArgsConstructor
public class AdvertisementImageService extends ImageService<AdvertisementImage> {

@Value("${image.upload.ad.child-path}")
private String childPath;
private final ImageStorageService imageStorageService;
private final AdvertisementImageRepository adImageRepository;

public AdvertisementImageService(ImageStorageService imageStorageService, AdvertisementImageRepository adImageRepository) {
this.imageStorageService = imageStorageService;
this.adImageRepository = adImageRepository;
}

@Override
protected ImageRepository<AdvertisementImage> getImageRepository() {
return adImageRepository;
Expand All @@ -48,7 +51,6 @@ protected AdvertisementImage mapToEntityFrom(ImageRequest imageRequest) {
.build();
}

// TODO : ad image 삭제 인가
@Override
protected void validateMemberId(MemberIdentifier identifier, AdvertisementImage image) {
if (!identifier.type().equals(MemberType.admin)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class CurationImageService extends ImageService<CurationImage> {

@Value("${image.upload.curation.child-path}")
private String childPath;
private final CurationImageRepository curationImageRepository;

public CurationImageService(ImageStorageService imageStorageService, CurationImageRepository curationImageRepository) {
this.imageStorageService = imageStorageService;
this.curationImageRepository = curationImageRepository;
}

@Override
public ImageRepository<CurationImage> getImageRepository() {
return curationImageRepository;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,14 +14,18 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@RequiredArgsConstructor
@Service
public class EventImageService extends ImageService<EventImage> {

@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<EventImage> getImageRepository() {
return eventImageRepository;
Expand All @@ -48,7 +52,8 @@ protected EventImage mapToEntityFrom(ImageRequest imageRequest) {

@Override
protected void validateMemberId(MemberIdentifier identifier, EventImage image) {
if (!identifier.type().equals(MemberType.admin) && !identifier.id().equals(image.getEvent().getHost().getId())) {
if (!identifier.type().equals(MemberType.admin) &&
!identifier.id().equals(image.getEvent().getHost().getId())) {
throw new UnauthorizedException("해당 이벤트 이미지에 접근할 권한이 없습니다.");
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,6 @@
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.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -20,11 +19,10 @@
@RequiredArgsConstructor
public abstract class ImageService<T extends Image> {

@Autowired
protected ImageStorageService imageStorageService;

@Value("${image.api-url}")
private String parentPath;
private String imageApiHeader;

protected abstract ImageRepository<T> getImageRepository();

Expand All @@ -36,43 +34,42 @@ public abstract class ImageService<T extends Image> {

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<ImageResponse> saveImages(List<MultipartFile> images) {
return images.stream()
.map(image -> create(imageStorageService.saveToDirectory(image, getChildPath())))
.toList();
}

// TODO: delete 이미지 바꿔야함
@Transactional
public void deleteImage(MemberIdentifier identifier, Long imageId) {
T image = getById(imageId);
validateMemberId(identifier, image);
delete(imageId);
imageStorageService.delete(getChildPath(), image.getName());
// ToDO : 썸네일도 지워라
}

public String createThumbnail(String fileName) {
return imageStorageService.createThumbnailImage(parentPath, getChildPath(), fileName);
}

public T getById(Long id) {
return getImageRepository().findById(id)
.orElseThrow(() -> new EntityNotFoundException(getImageDomain().name() + " 이미지를 찾을 수 없습니다."));
}

protected ImageResponse create(ImageRequest imageRequest) {
T image = mapToEntityFrom(imageRequest);
return ImageResponse.from(getImageRepository().save(image), getImageUrl(image));
}

public String getImageUrl(T image) {
return UriUtil.assembleFullUrl(parentPath, getChildPath(), image.getName());
}

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());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -34,9 +34,6 @@ public class ImageStorageService {
@Value("${image.upload.thumbnail.width}")
private Integer thumbnailWidth;

@Value("${image.upload.thumbnail.extension}")
private String thumbnailExtension;

private final RestClientUtil client;

public byte[] readFromDirectory(Path childPath) {
Expand All @@ -56,7 +53,6 @@ public ImageRequest saveToDirectory(String imageUrl, String childPath) {
throw new FailedToUploadImageException("이미지 URL에 대한 요청을 실패하였습니다.");
}
);

String contentType = Optional.ofNullable(response.getHeaders().getContentType()).orElseThrow(
() -> new FailedToUploadImageException("응답 헤더에 콘텐츠 타입이 없어 이미지를 가져올 수 없습니다.")
).toString();
Expand All @@ -71,23 +67,15 @@ public ImageRequest saveToDirectory(String imageUrl, String childPath) {
return ImageRequest.of(fileName, (long)body.length, extension);
}

// TODO : 썸네일 생성 코드 리팩토링, 파일 유틸로 이동
public String createThumbnailImage(String apiHeader, String childPath, String originFileName) {
String fileName = originFileName;
String originImagePath = Path.of(parentPath, childPath, originFileName).toString();
String thumbnailImagePath = Path.of(parentPath, childPath, thumbnailChildPath).toString();
try {
Files.createDirectories(Paths.get(thumbnailImagePath));
Thumbnails.of(new File(originImagePath))
.size(thumbnailHeight, thumbnailWidth)
.toFile(new File(thumbnailImagePath, fileName));
} catch (Exception e) {
throw new RuntimeException(e);
}
return UriUtil.assembleFullUrl(apiHeader,childPath, thumbnailChildPath, fileName);
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));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,17 @@
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class MemberImageService extends ImageService<MemberImage> {

@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<MemberImage> getImageRepository() {
return memberImageRepository;
Expand All @@ -48,7 +52,8 @@ protected MemberImage mapToEntityFrom(ImageRequest imageRequest) {

@Override
protected void validateMemberId(MemberIdentifier identifier, MemberImage image) {
if (!identifier.type().equals(MemberType.admin) && !identifier.id().equals(image.getBaseMember().getId())) {
if (!identifier.type().equals(MemberType.admin) &&
!identifier.id().equals(image.getBaseMember().getId())) {
throw new UnauthorizedException("해당 프로필 이미지에 접근할 권한이 없습니다.");
}
}
Expand Down
Loading

0 comments on commit e27b377

Please sign in to comment.