Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 반려동물 정보 등록 API 기능 #257

Merged
merged 28 commits into from
Aug 16, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
ff83c85
build: AWS 의존성 추가
iamjooon2 Aug 14, 2023
aa4072b
build: AWS 의존성 추가
iamjooon2 Aug 14, 2023
d1ccb97
feat: 사진 업로드 기능 구현
iamjooon2 Aug 14, 2023
80139e7
feat: 성별을 찾는 기능 추가
iamjooon2 Aug 14, 2023
cb86b42
build: aws 의존성 추가
iamjooon2 Aug 14, 2023
1127374
chore: application.yml 설정 추가
iamjooon2 Aug 14, 2023
6447189
feat: 반려견 등록 구현
iamjooon2 Aug 14, 2023
8559c7a
test: petServiceTest 구현
iamjooon2 Aug 14, 2023
e298d5c
refactor: 이미지 업로드 API 분리
iamjooon2 Aug 15, 2023
5ee975f
test: 테스트용 application.yml 업데이트
iamjooon2 Aug 15, 2023
924db09
test: Controller 테스트 작성
iamjooon2 Aug 15, 2023
f2df4c9
test: repository 의존하여 테스트 실행 전 reviewRepository 초기화 추가
iamjooon2 Aug 15, 2023
5d674bd
refactor: key값 이미지로 변경
iamjooon2 Aug 15, 2023
a32c98f
test: key값 이미지로 변경
iamjooon2 Aug 15, 2023
c62cc33
chore: solve conflicts
iamjooon2 Aug 15, 2023
cc3a41a
chore: 프로필 파일 업데이트
iamjooon2 Aug 15, 2023
d5a1dee
feat: 견종 크기 검증 추가
iamjooon2 Aug 15, 2023
86375ed
refactor: 견종 크기 예외 케이스 추가
iamjooon2 Aug 15, 2023
31f958d
refactor: 견종 찾지 못할시 발생하는 예외를 BreedsNotFound로 변경
iamjooon2 Aug 15, 2023
56a09f0
refactor: PetControllerTest Nested 적용
iamjooon2 Aug 15, 2023
0d1ce09
refactor: getByNameAndPetSize -> getByNameAndPetSizeId 네이밍 수정
iamjooon2 Aug 15, 2023
540b633
chore: 개행 수정
iamjooon2 Aug 15, 2023
51786f3
refactor: 불필요해진 어노테이션 제거
iamjooon2 Aug 16, 2023
d0fcc0f
refactor: 무민 리뷰 반영
iamjooon2 Aug 16, 2023
c30e3d8
fix: 불필요한 라인 제거
iamjooon2 Aug 16, 2023
52da151
chore : 개행 방식 제거
iamjooon2 Aug 16, 2023
9993ea4
refactor: 불필요한 필드 제거
iamjooon2 Aug 16, 2023
f9f790a
refactor: 베베 리뷰사항 반영
iamjooon2 Aug 16, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ dependencies {
runtimeOnly 'com.mysql:mysql-connector-j'
runtimeOnly 'com.h2database:h2'

implementation 'software.amazon.awssdk:s3:2.20.121'
compileOnly 'software.amazon.awssdk:url-connection-client:2.20.121'

testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'io.rest-assured:rest-assured:5.3.1'

Expand Down Expand Up @@ -103,6 +106,7 @@ jacocoTestReport {
'**/*Request*',
'**/BaseTimeEntity',
'**/*Dto*',
'**/S3*',
'**/*Interceptor*',
'**/*ArgumentResolver*',
'**/*ExceptionHandler*',
Expand Down Expand Up @@ -137,6 +141,7 @@ jacocoTestCoverageVerification {
'*.*Application',
'*.*Exception',
'*.*Dto',
'*.S3*',
'*.*Response',
'*.*Request',
'*.BaseTimeEntity',
Expand Down
4 changes: 0 additions & 4 deletions backend/src/main/java/zipgo/ZipgoApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import zipgo.auth.infra.kakao.config.KakaoCredentials;
import zipgo.common.config.JwtCredentials;

@SpringBootApplication
@EnableConfigurationProperties({KakaoCredentials.class, JwtCredentials.class})
public class ZipgoApplication {

public static void main(String[] args) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
import zipgo.auth.exception.AuthException;
import zipgo.common.logging.LoggingUtils;
import zipgo.member.exception.MemberException;
import zipgo.pet.exception.PetException;
import zipgo.petfood.exception.PetFoodException;
import zipgo.petfood.presentation.dto.ErrorResponse;
import zipgo.review.exception.ReviewException;
Expand All @@ -29,7 +30,11 @@ public class GlobalExceptionHandler extends ResponseEntityExceptionHandler {
PetFoodException.NotFound.class,
ReviewException.NotFound.class,
MemberException.NotFound.class,
AuthException.ResourceNotFound.class
AuthException.ResourceNotFound.class,
PetException.AgeNotFound.class,
PetException.GenderNotFound.class,
PetException.PetSizeNotFound.class,
PetException.BreedsNotFound.class
})
public ResponseEntity<ErrorResponse> handleNotFoundException(Exception exception) {
LoggingUtils.error(exception);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package zipgo.common.config;

import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import zipgo.auth.infra.kakao.config.KakaoCredentials;

@Configuration
@EnableConfigurationProperties({
KakaoCredentials.class,
JwtCredentials.class,
AwsS3Credentials.class
})
Comment on lines +7 to +12
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

키야~ 감탄하고갑니다;; 야무지게 분리해놨노

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍🏻

public class AdditionalConfiguration {

}
17 changes: 17 additions & 0 deletions backend/src/main/java/zipgo/common/config/AwsS3Credentials.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package zipgo.common.config;

import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.context.properties.ConfigurationProperties;

@Getter
@RequiredArgsConstructor
@ConfigurationProperties(prefix = "cloud.aws.s3")
public class AwsS3Credentials {

private final String bucket;
private final String zipgoDirectoryName;
private final String petImageDirectory;
private final String imageUrl;

}
21 changes: 21 additions & 0 deletions backend/src/main/java/zipgo/common/config/S3Config.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package zipgo.common.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.ProfileCredentialsProvider;
import software.amazon.awssdk.services.s3.S3Client;

import static software.amazon.awssdk.regions.Region.AP_NORTHEAST_2;

@Configuration
public class S3Config {

@Bean
public S3Client s3Client() {
return S3Client.builder()
.credentialsProvider(ProfileCredentialsProvider.create())
.region(AP_NORTHEAST_2)
.build();
}

}
20 changes: 20 additions & 0 deletions backend/src/main/java/zipgo/image/application/ImageService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package zipgo.image.application;

import java.util.UUID;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;
import zipgo.pet.application.ImageClient;

@Service
@RequiredArgsConstructor
public class ImageService {

private final ImageClient imageClient;

public String save(MultipartFile image) {
UUID uuid = UUID.randomUUID();
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

나중에는 사진 용도가 다양해질텐데, 그 사진들을 어떻게 분류하고 관리할지 고민해볼 수 있겠어요!! (함께)

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞아요!! 일단 사진이 사용되는 곳이 여기 하나라서 일단 이렇게 만들었어요 🫠

return imageClient.upload(uuid.toString() ,image);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package zipgo.image.infrastructure.aws.s3;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import zipgo.common.config.AwsS3Credentials;
import zipgo.pet.application.ImageClient;

@Component
@RequiredArgsConstructor
public class S3PetImageClient implements ImageClient {

private final AwsS3Credentials awsS3Credentials;
private final S3Uploader s3Uploader;

@Override
public String upload(String name, MultipartFile file) {
String bucket = awsS3Credentials.getBucket();
String zipgoDirectoryName = awsS3Credentials.getZipgoDirectoryName();
String petImageDirectory = awsS3Credentials.getPetImageDirectory();
String key = zipgoDirectoryName + petImageDirectory + name;
s3Uploader.upload(bucket, key, file);
return awsS3Credentials.getImageUrl() + petImageDirectory + name;
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package zipgo.image.infrastructure.aws.s3;

import java.io.IOException;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.core.sync.RequestBody;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.model.PutObjectRequest;

@Component
@RequiredArgsConstructor
public class S3Uploader {

private final S3Client s3Client;

public void upload(String bucket, String key, MultipartFile file) {
PutObjectRequest objectRequest = PutObjectRequest.builder()
.bucket(bucket)
.key(key)
.contentType("image/png")
.build();
try {
s3Client.putObject(objectRequest, RequestBody.fromBytes(getBytes(file)));
} catch (UnsupportedOperationException e) {
throw new IllegalArgumentException("엑세스 접근 중 예외가 발생했습니다.");
}
}

private byte[] getBytes(MultipartFile file) {
try {
return file.getBytes();
} catch (IOException e) {
throw new IllegalArgumentException("바이트 파싱 중 에러가 발생했습니다.");
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package zipgo.image.presentaion;

import java.net.URI;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestPart;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import zipgo.image.application.ImageService;
import zipgo.image.presentaion.dto.ImageResponse;

@RestController
@RequiredArgsConstructor
@RequestMapping("/images")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ImageController라 하니깐 공용 image 처리하는거 같지 않나용

반려동물 controller에 있는게 더 좋을거 같아요

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이거 일단 4차 데모까지 사진 업로드 사용하는 게 여기밖에 없어서 사실상 공용 이미지 처리가 가능하긴 합니다🫠
이건 추후 리팩터링 해보죠

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

근데 사실 이미지는 도메인 자체를 분리해서 이렇게 쓰는것도 괜찮은듯요

public class ImageController {

private final ImageService imageService;

@PostMapping
public ResponseEntity<ImageResponse> upload(
@RequestPart(required = false, value = "image") MultipartFile imageFile
) {
String url = imageService.save(imageFile);
return ResponseEntity.created(URI.create(url)).body(ImageResponse.from(url));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package zipgo.image.presentaion.dto;

public record ImageResponse (
String imageUrl
) {

public static ImageResponse from(String url) {
return new ImageResponse(url);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,8 @@ public interface MemberRepository extends JpaRepository<Member, Long> {

Optional<Member> findByEmail(String email);

default Member getById(Long id) {
return findById(id).orElseThrow(MemberException.NotFound::new);
default Member getById(Long memberId) {
return findById(memberId).orElseThrow(MemberException.NotFound::new);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package zipgo.pet.application;

import org.springframework.web.multipart.MultipartFile;

public interface ImageClient {

String upload(String name, MultipartFile file);

}
35 changes: 35 additions & 0 deletions backend/src/main/java/zipgo/pet/application/PetService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package zipgo.pet.application;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import zipgo.member.domain.Member;
import zipgo.member.domain.repository.MemberRepository;
import zipgo.pet.domain.Breeds;
import zipgo.pet.domain.Pet;
import zipgo.pet.domain.PetSize;
import zipgo.pet.domain.repository.BreedsRepository;
import zipgo.pet.domain.repository.PetRepository;
import zipgo.pet.domain.repository.PetSizeRepository;
import zipgo.pet.presentation.dto.request.CreatePetRequest;

@Service
@Transactional
@RequiredArgsConstructor
public class PetService {

private final PetRepository petRepository;
private final MemberRepository memberRepository;
private final BreedsRepository breedsRepository;
private final PetSizeRepository petSizeRepository;

public Long createPet(Long memberId, CreatePetRequest request) {
Member owner = memberRepository.getById(memberId);
PetSize petSize = petSizeRepository.getByName(request.petSize());
Breeds breeds = breedsRepository.getByNameAndPetSizeId(request.breed(), petSize.getId());

Pet pet = petRepository.save(request.toEntity(owner, breeds));
return pet.getId();
}

}
2 changes: 1 addition & 1 deletion backend/src/main/java/zipgo/pet/domain/AgeGroup.java
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ public static AgeGroup from(int age) {
return Arrays.stream(values())
.filter(ageGroup -> ageGroup.greaterThanOrEqual <= age && age < ageGroup.lessThan)
.findFirst()
.orElseThrow(PetException.NotFound::new);
.orElseThrow(PetException.AgeNotFound::new);
}

}
19 changes: 17 additions & 2 deletions backend/src/main/java/zipgo/pet/domain/Gender.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,23 @@
package zipgo.pet.domain;

import java.util.Arrays;
import lombok.RequiredArgsConstructor;
import zipgo.pet.exception.PetException;

@RequiredArgsConstructor
public enum Gender {

MALE,
FEMALE;
MALE("남"),
FEMALE("여"),
;

private final String value;

public static Gender from(String other) {
return Arrays.stream(values())
.filter(gender -> gender.value.equals(other))
.findFirst()
.orElseThrow(PetException.GenderNotFound::new);
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package zipgo.pet.domain.repository;

import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import zipgo.pet.domain.Breeds;
import zipgo.pet.exception.PetException;

public interface BreedsRepository extends JpaRepository<Breeds, Long> {

Optional<Breeds> findByNameAndPetSizeId(String name, Long petSizeId);

default Breeds getByNameAndPetSizeId(String name, Long petSizeId) {
return findByNameAndPetSizeId(name, petSizeId).orElseThrow(PetException.BreedsNotFound::new);
}

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,16 @@
package zipgo.pet.domain.repository;

import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
import zipgo.pet.domain.PetSize;
import zipgo.pet.exception.PetException;

public interface PetSizeRepository extends JpaRepository<PetSize, Long> {

Optional<PetSize> findByName(String name);

default PetSize getByName(String name) {
return findByName(name).orElseThrow(PetException.PetSizeNotFound::new);
}

}
Loading