Skip to content

Commit

Permalink
Merge pull request #36 from BokDoong/member_feature
Browse files Browse the repository at this point in the history
이미지 수정 및 삭제 API 설계
  • Loading branch information
BokDoong authored Oct 5, 2023
2 parents 7a428bf + 88c3c97 commit 9da728d
Show file tree
Hide file tree
Showing 32 changed files with 274 additions and 144 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package hanium.englishfairytale.tale.application;
package hanium.englishfairytale.common.files;

import hanium.englishfairytale.common.util.ImageFileUtility;
import hanium.englishfairytale.exception.RuntimeIOException;
import hanium.englishfairytale.exception.code.ErrorCode;
import hanium.englishfairytale.tale.domain.*;
import hanium.englishfairytale.tale.application.dto.TaleImageInfo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand All @@ -15,23 +16,17 @@
public class FileManageService {

private final FileStore fileStore;
private final ImageRepository imageRepository;

@Transactional
public String uploadImage(Tale tale, MultipartFile image) {
public TaleImageInfo uploadTaleImage(MultipartFile image) {
try {
String storedName = ImageFileUtility.createObjectNameByUUID(image.getOriginalFilename());
String originalName = image.getOriginalFilename();
String storedName = ImageFileUtility.createObjectNameByUUID(originalName);
String imageUrl = fileStore.upload(storedName, image.getInputStream(), ImageFileUtility.createObjectMetadata(image));

return saveImage(tale, image.getOriginalFilename(), storedName, imageUrl);
return new TaleImageInfo(originalName, storedName, imageUrl);
} catch (IOException e) {
throw new RuntimeIOException(e, ErrorCode.IMAGE_PROCESSING_IO);
}
}

@Transactional
public String saveImage(Tale tale, String originalName, String storedName, String imageUrl) {
TaleImage taleImage = TaleImage.createTaleImage(tale, originalName, storedName, imageUrl);
return imageRepository.save(taleImage);
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
package hanium.englishfairytale.tale.infra;
package hanium.englishfairytale.common.files;

import com.amazonaws.services.s3.AmazonS3Client;
import com.amazonaws.services.s3.model.ObjectMetadata;
import hanium.englishfairytale.tale.domain.FileStore;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
Expand All @@ -11,20 +10,18 @@

@RequiredArgsConstructor
@Component
public class S3ImageStore implements FileStore {
public class FileStore {

private final AmazonS3Client amazonS3Client;

@Value("${cloud.aws.s3.bucket}")
private String bucket;

@Override
public String upload(String storedName, InputStream inputStream, ObjectMetadata objectMetadata) {
amazonS3Client.putObject(bucket, storedName, inputStream, objectMetadata);
return amazonS3Client.getUrl(bucket, storedName).toString();
}

@Override
public void delete(String storedName) {
amazonS3Client.deleteObject(bucket, storedName);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package hanium.englishfairytale.tale.domain;
package hanium.englishfairytale.common.util;

import com.amazonaws.services.s3.model.ObjectMetadata;
import lombok.experimental.UtilityClass;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ public enum ErrorCode {
EXCEED_KEYWORD_LIMIT(TaleCode.KEYWORD_COUNT_LIMIT.getCode(), BAD_REQUEST, "입력 키워드 개수 초과"),
DUPLICATED_KEYWORD(TaleCode.KEYWORD_DUPLICATED.getCode(), BAD_REQUEST, "중복된 키워드가 있는 경우"),
IMAGE_PROCESSING_IO(TaleCode.IMAGE_IO.getCode(), BAD_REQUEST, "이미지 처리 중 문제가 발생한 경우"),
IMAGE_NON_EXITED(TaleCode.NON_EXISTED_IMAGE.getCode(), NOT_ACCEPTABLE, "삭제할 이미지가 존재하지 않는 경우"),

// Member
MEMBER_NOT_FOUND(MemberCode.NOT_FOUND.getCode(), NOT_FOUND, "존재하지 않는 회원"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public enum TaleCode {
KEYWORD_DUPLICATED("T-003"),
IMAGE_IO("T-004"),
NON_CREATED("T-005"),
NON_EXISTED_IMAGE("T-006"),
;

private final String code;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@

import hanium.englishfairytale.exception.BusinessException;
import hanium.englishfairytale.exception.code.ErrorCode;
import hanium.englishfairytale.member.application.dto.request.MemberCreateCommand;
import hanium.englishfairytale.member.application.dto.response.CreateMemberResponse;
import hanium.englishfairytale.member.application.dto.MemberCreateCommand;
import hanium.englishfairytale.member.application.dto.CreateMemberResponse;
import hanium.englishfairytale.member.domain.Member;
import hanium.englishfairytale.member.domain.MemberRepository;
import lombok.RequiredArgsConstructor;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package hanium.englishfairytale.member.application;

import hanium.englishfairytale.tale.application.dto.response.TalesInfo;
import hanium.englishfairytale.tale.application.dto.TalesInfo;
import hanium.englishfairytale.tale.domain.Keyword;
import hanium.englishfairytale.tale.domain.TaleKeyword;
import hanium.englishfairytale.tale.domain.TaleRepository;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package hanium.englishfairytale.member.application.dto.response;
package hanium.englishfairytale.member.application.dto;

import lombok.AllArgsConstructor;
import lombok.Getter;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package hanium.englishfairytale.member.application.dto.request;
package hanium.englishfairytale.member.application.dto;

import lombok.Builder;
import lombok.Getter;
Expand Down
20 changes: 20 additions & 0 deletions src/main/java/hanium/englishfairytale/member/domain/Image.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package hanium.englishfairytale.member.domain;

import lombok.Getter;

import javax.persistence.*;

@Embeddable
@Getter
public class Image {
@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name="memberImage_id")
private MemberImage memberImage;

void putMemberImage(MemberImage newMemberImage) {
memberImage = newMemberImage;
}
public String getUrl() {
return memberImage.getImageUrl();
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package hanium.englishfairytale.member.domain;

import hanium.englishfairytale.tale.domain.Tale;
import hanium.englishfairytale.tale.domain.TaleKeyword;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
Expand Down Expand Up @@ -30,6 +29,8 @@ public class Member {
private String email;
@Column(name = "password")
private String password;
@Embedded
private Image image;
@Column(name = "created_date")
private LocalDateTime createdTime;

Expand All @@ -50,4 +51,7 @@ public Member(String name, String phoneNumber, String nickname, String email, St
public void addTale(Tale newTale) {
this.tales.add(newTale);
}
public void putImage(MemberImage memberImage) {
image.putMemberImage(memberImage);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package hanium.englishfairytale.member.domain;

import lombok.*;

import javax.persistence.*;
import javax.validation.constraints.NotNull;

@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
public class MemberImage {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private long id;
@NotNull
@Column(name = "original_name")
private String originalName;
@NotNull
@Column(name = "stored_name")
private String storedName;
@NotNull
@Column(name = "image_url")
private String imageUrl;

public static MemberImage createMemberImage(Member member, String originalName, String storedName, String imageUrl) {
MemberImage memberImage = setMemberImage(originalName, storedName, imageUrl);
member.putImage(memberImage);
return memberImage;
}

private static MemberImage setMemberImage(String originalName, String storedName, String imageUrl) {
return MemberImage.builder()
.originalName(originalName)
.storedName(storedName)
.imageUrl(imageUrl)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
package hanium.englishfairytale.member.infra.http;

import hanium.englishfairytale.member.application.MemberCommandService;
import hanium.englishfairytale.member.application.dto.request.MemberCreateCommand;
import hanium.englishfairytale.member.application.dto.response.CreateMemberResponse;
import hanium.englishfairytale.member.application.dto.MemberCreateCommand;
import hanium.englishfairytale.member.application.dto.CreateMemberResponse;
import hanium.englishfairytale.member.infra.http.dto.MemberCreateDto;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package hanium.englishfairytale.member.infra.http;

import hanium.englishfairytale.member.application.dto.request.MemberCreateCommand;
import hanium.englishfairytale.member.application.dto.MemberCreateCommand;
import hanium.englishfairytale.member.infra.http.dto.MemberCreateDto;
import org.springframework.stereotype.Component;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,6 @@ public class MemberCreateDto {
@Pattern(regexp = "^[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+.[A-Za-z]{2,6}$", message = "이메일 형식에 맞지 않습니다.")
private String email;
@NotNull
@Size(min = 2, max = 10)
@Pattern(regexp = "^(?i)(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]{2,10}$")
private String password;
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package hanium.englishfairytale.tale.application;

import hanium.englishfairytale.common.files.FileManageService;
import hanium.englishfairytale.exception.BusinessException;
import hanium.englishfairytale.exception.NotFoundException;
import hanium.englishfairytale.exception.code.ErrorCode;
import hanium.englishfairytale.member.domain.Member;
import hanium.englishfairytale.member.domain.MemberRepository;
import hanium.englishfairytale.tale.application.dto.request.TaleCreateCommand;
import hanium.englishfairytale.tale.domain.Keyword;
import hanium.englishfairytale.tale.domain.TaleKeyword;
import hanium.englishfairytale.tale.domain.TaleRepository;
import hanium.englishfairytale.tale.application.dto.response.TaleCreateResponse;
import hanium.englishfairytale.tale.domain.Tale;
import hanium.englishfairytale.tale.application.dto.TaleCreateCommand;
import hanium.englishfairytale.tale.application.dto.TaleUpdateCommand;
import hanium.englishfairytale.tale.domain.*;
import hanium.englishfairytale.tale.application.dto.TaleCreateResponse;
import hanium.englishfairytale.tale.domain.factory.CreatedTale;
import hanium.englishfairytale.tale.infra.TaleQueryDao;
import lombok.RequiredArgsConstructor;
Expand All @@ -27,33 +27,54 @@ public class TaleCommandService {

private final MemberRepository memberRepository;
private final TaleRepository taleRepository;
private final ImageRepository imageRepository;
private final TaleQueryDao taleQueryDao;
private final TaleManageService taleManageService;
private final FileManageService fileManageService;

@Transactional
public TaleCreateResponse create(TaleCreateCommand taleCreateCommand) {
Tale tale = createTale(taleCreateCommand);
List<Keyword> keywords = createKeywords(taleCreateCommand);
String imageUrl = saveTaleKeywordAndGetImageUrl(tale, keywords, taleCreateCommand.getImage());
List<Keyword> keywords = findAndCreateKeywords(taleCreateCommand);
return saveTaleAndKeywords(tale, keywords, taleCreateCommand.getImage());
}

return new TaleCreateResponse(tale, keywords, imageUrl);
@Transactional
public void deleteTale(Long taleId) {
findExistedTale(taleId);
taleRepository.deleteByTaleId(taleId);
}

@Transactional
public void updateTaleImage(TaleUpdateCommand taleUpdateCommand) {
Tale tale = findExistedTale(taleUpdateCommand.getTaleId());
tale.updateTaleImage(saveTaleImage(taleUpdateCommand.getImage()));
}

// TODO: 2023.09.30 동화삭제 API 버그
@Transactional
public void delete(Long taleId) {
verifyExistedTale(taleId);
deleteTales(taleId);
public void deleteTaleImage(Long taleId) {
Tale tale = findExistedTale(taleId);
Long imageId = findImageId(tale);
tale.makeImageNull();
deleteImage(imageId);
}

private void deleteTales(Long taleId) {
taleRepository.deleteByTaleId(taleId);
private void deleteImage(Long imageUrl) {
imageRepository.delete(imageUrl);
}

private Long findImageId(Tale tale) {
verifyImageIsEmpty(tale);
return tale.getImageId();
}

private void verifyExistedTale(Long taleId) {
taleQueryDao.findTaleByTaleId(taleId)
private void verifyImageIsEmpty(Tale tale) {
if (tale.checkImageEmpty())
throw new BusinessException(ErrorCode.IMAGE_NON_EXITED);
}

private Tale findExistedTale(Long taleId) {
return taleQueryDao.findTaleByTaleId(taleId)
.orElseThrow(() -> new NotFoundException(ErrorCode.TALE_NOT_FOUND));
}

Expand All @@ -74,38 +95,37 @@ private Member findMember(Long memberId) {
}

private CreatedTale createEnglishTale(TaleCreateCommand taleCreateCommand) {
verifyKeywords(taleCreateCommand);
verifyInputKeywords(taleCreateCommand);
return taleManageService.post(taleCreateCommand.getModel(), taleCreateCommand.getKeywords());
}

private void verifyKeywords(TaleCreateCommand taleCreateCommand) {
private void verifyInputKeywords(TaleCreateCommand taleCreateCommand) {
Keyword.verifyNumberOfKeywords(taleCreateCommand.getKeywords());
Keyword.verifyDuplicatedKeywords(taleCreateCommand.getKeywords());
}

private String saveTaleKeywordAndGetImageUrl(Tale tale, List<Keyword> keywords, MultipartFile image) {
for(Keyword keyword: keywords) {
taleRepository.save(TaleKeyword.createTaleKeyword(tale, keyword));
private TaleCreateResponse saveTaleAndKeywords(Tale tale, List<Keyword> keywords, MultipartFile image) {
if (!image.isEmpty()) {
tale.putImage(saveTaleImage(image));
}
return saveAndGetImageUrl(tale, image);
saveTaleKeywords(tale, keywords);
return new TaleCreateResponse(tale,keywords);
}

private String saveAndGetImageUrl(Tale tale, MultipartFile image) {
if (image == null) {
return null;
private void saveTaleKeywords(Tale tale, List<Keyword> keywords) {
for(Keyword keyword: keywords) {
taleRepository.save(TaleKeyword.createTaleKeyword(tale, keyword));
}

return fileManageService.uploadImage(tale, image);
}

private List<Keyword> createKeywords(TaleCreateCommand taleCreateCommand) {
return findAndCreateKeywords(taleCreateCommand.getKeywords());
private TaleImage saveTaleImage(MultipartFile image) {
return new TaleImage(fileManageService.uploadTaleImage(image));
}

private List<Keyword> findAndCreateKeywords(List<String> words) {
private List<Keyword> findAndCreateKeywords(TaleCreateCommand taleCreateCommand) {
List<Keyword> keywords = new ArrayList<>();

words.forEach(word -> {
taleCreateCommand.getKeywords().forEach(word -> {
Optional<Keyword> optionalKeyword = taleRepository.findByWord(word);
if (optionalKeyword.isEmpty()) {
keywords.add(Keyword.builder().word(word).build());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import hanium.englishfairytale.exception.NotFoundException;
import hanium.englishfairytale.exception.code.ErrorCode;
import hanium.englishfairytale.member.domain.MemberRepository;
import hanium.englishfairytale.tale.application.dto.response.TaleDetailInfo;
import hanium.englishfairytale.tale.application.dto.response.TalesInfo;
import hanium.englishfairytale.tale.application.dto.TaleDetailInfo;
import hanium.englishfairytale.tale.application.dto.TalesInfo;
import hanium.englishfairytale.tale.domain.Keyword;
import hanium.englishfairytale.tale.domain.Tale;
import hanium.englishfairytale.tale.infra.TaleQueryDao;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package hanium.englishfairytale.tale.application.dto.request;
package hanium.englishfairytale.tale.application.dto;

import lombok.Builder;
import lombok.Getter;
Expand Down
Loading

0 comments on commit 9da728d

Please sign in to comment.