From 732675eea17a78d02490a6abf44e91e0f5442e19 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Tue, 12 Dec 2023 17:37:22 +0900 Subject: [PATCH 1/4] =?UTF-8?q?[FEAT]=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/.gitignore | 2 + backend/build.gradle | 2 + .../config/properties/PropertiesConfig.java | 3 +- .../backend/config/storage/StorageConfig.java | 26 +++++++++ .../advice/FriendControllerAdvice.java | 4 +- .../advice/GroupControllerAdvice.java | 4 +- .../image/controller/ImageController.java | 24 ++++++++ .../advice/ImageControllerAdvice.java | 22 ++++++++ .../domain/image/dto/ImageResponse.java | 10 ++++ .../exception/InvalidFileTypeException.java | 10 ++++ .../domain/image/service/ImageService.java | 56 +++++++++++++++++++ .../advice/AuthControllerAdvice.java | 8 +-- .../backend/domain/member/entity/Member.java | 4 ++ .../advice/PlanControllerAdvice.java | 6 +- .../global/advice/GlobalErrorAdvice.java | 8 +-- .../global/properties/StorageProperties.java | 14 +++++ 16 files changed, 182 insertions(+), 21 deletions(-) create mode 100644 backend/src/main/java/com/twtw/backend/config/storage/StorageConfig.java create mode 100644 backend/src/main/java/com/twtw/backend/domain/image/controller/ImageController.java create mode 100644 backend/src/main/java/com/twtw/backend/domain/image/controller/advice/ImageControllerAdvice.java create mode 100644 backend/src/main/java/com/twtw/backend/domain/image/dto/ImageResponse.java create mode 100644 backend/src/main/java/com/twtw/backend/domain/image/exception/InvalidFileTypeException.java create mode 100644 backend/src/main/java/com/twtw/backend/domain/image/service/ImageService.java create mode 100644 backend/src/main/java/com/twtw/backend/global/properties/StorageProperties.java diff --git a/backend/.gitignore b/backend/.gitignore index 5de39c9e..cb978bdf 100644 --- a/backend/.gitignore +++ b/backend/.gitignore @@ -39,3 +39,5 @@ out/ .vscode/ /src/main/generated/ + +/src/main/resources/*.json diff --git a/backend/build.gradle b/backend/build.gradle index 3bc15416..44feb95f 100644 --- a/backend/build.gradle +++ b/backend/build.gradle @@ -56,6 +56,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-actuator' implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + implementation 'org.springframework.cloud:spring-cloud-gcp-starter:1.2.5.RELEASE' + implementation 'org.springframework.cloud:spring-cloud-gcp-storage:1.2.5.RELEASE' annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" compileOnly 'org.projectlombok:lombok' runtimeOnly 'org.mariadb.jdbc:mariadb-java-client' diff --git a/backend/src/main/java/com/twtw/backend/config/properties/PropertiesConfig.java b/backend/src/main/java/com/twtw/backend/config/properties/PropertiesConfig.java index 6d86810b..c3b25f46 100644 --- a/backend/src/main/java/com/twtw/backend/config/properties/PropertiesConfig.java +++ b/backend/src/main/java/com/twtw/backend/config/properties/PropertiesConfig.java @@ -11,6 +11,7 @@ KakaoProperties.class, TmapProperties.class, RabbitMQProperties.class, - RedisProperties.class + RedisProperties.class, + StorageProperties.class }) public class PropertiesConfig {} diff --git a/backend/src/main/java/com/twtw/backend/config/storage/StorageConfig.java b/backend/src/main/java/com/twtw/backend/config/storage/StorageConfig.java new file mode 100644 index 00000000..0e7895f4 --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/config/storage/StorageConfig.java @@ -0,0 +1,26 @@ +package com.twtw.backend.config.storage; + +import com.google.auth.oauth2.GoogleCredentials; +import com.google.cloud.storage.Storage; +import com.google.cloud.storage.StorageOptions; +import com.twtw.backend.global.properties.StorageProperties; +import java.io.IOException; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.core.io.ClassPathResource; + +@Configuration +@RequiredArgsConstructor +public class StorageConfig { + + private final StorageProperties storageProperties; + + @Bean + public Storage storage() throws IOException { + final GoogleCredentials googleCredentials = GoogleCredentials.fromStream( + new ClassPathResource(storageProperties.getCredentialsFile()).getInputStream()); + + return StorageOptions.newBuilder().setCredentials(googleCredentials).build().getService(); + } +} diff --git a/backend/src/main/java/com/twtw/backend/domain/friend/controller/advice/FriendControllerAdvice.java b/backend/src/main/java/com/twtw/backend/domain/friend/controller/advice/FriendControllerAdvice.java index ec9df366..16ed489e 100644 --- a/backend/src/main/java/com/twtw/backend/domain/friend/controller/advice/FriendControllerAdvice.java +++ b/backend/src/main/java/com/twtw/backend/domain/friend/controller/advice/FriendControllerAdvice.java @@ -2,8 +2,6 @@ import com.twtw.backend.domain.friend.exception.InvalidFriendMemberException; import com.twtw.backend.global.advice.ErrorResponse; - -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -13,7 +11,7 @@ public class FriendControllerAdvice { @ExceptionHandler(InvalidFriendMemberException.class) public ResponseEntity invalidFriendMember(final InvalidFriendMemberException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.badRequest() .body(new ErrorResponse(e.getMessage())); } } diff --git a/backend/src/main/java/com/twtw/backend/domain/group/controller/advice/GroupControllerAdvice.java b/backend/src/main/java/com/twtw/backend/domain/group/controller/advice/GroupControllerAdvice.java index ba6bb154..86dcea4a 100644 --- a/backend/src/main/java/com/twtw/backend/domain/group/controller/advice/GroupControllerAdvice.java +++ b/backend/src/main/java/com/twtw/backend/domain/group/controller/advice/GroupControllerAdvice.java @@ -2,8 +2,6 @@ import com.twtw.backend.domain.group.exception.IllegalGroupMemberException; import com.twtw.backend.global.advice.ErrorResponse; - -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -13,7 +11,7 @@ public class GroupControllerAdvice { @ExceptionHandler(IllegalGroupMemberException.class) public ResponseEntity invalidFriendMember(final IllegalGroupMemberException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.badRequest() .body(new ErrorResponse(e.getMessage())); } } diff --git a/backend/src/main/java/com/twtw/backend/domain/image/controller/ImageController.java b/backend/src/main/java/com/twtw/backend/domain/image/controller/ImageController.java new file mode 100644 index 00000000..40ad9d33 --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/domain/image/controller/ImageController.java @@ -0,0 +1,24 @@ +package com.twtw.backend.domain.image.controller; + +import com.twtw.backend.domain.image.dto.ImageResponse; +import com.twtw.backend.domain.image.service.ImageService; +import java.io.IOException; +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.RestController; +import org.springframework.web.multipart.MultipartFile; + +@RestController +@RequiredArgsConstructor +@RequestMapping("images") +public class ImageController { + + private final ImageService imageService; + + @PostMapping + public ResponseEntity uploadImage(final MultipartFile image) throws IOException { + return ResponseEntity.ok(imageService.uploadImage(image)); + } +} diff --git a/backend/src/main/java/com/twtw/backend/domain/image/controller/advice/ImageControllerAdvice.java b/backend/src/main/java/com/twtw/backend/domain/image/controller/advice/ImageControllerAdvice.java new file mode 100644 index 00000000..1307c160 --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/domain/image/controller/advice/ImageControllerAdvice.java @@ -0,0 +1,22 @@ +package com.twtw.backend.domain.image.controller.advice; + +import com.twtw.backend.domain.image.exception.InvalidFileTypeException; +import com.twtw.backend.global.advice.ErrorResponse; +import java.io.IOException; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +@RestControllerAdvice +public class ImageControllerAdvice { + + @ExceptionHandler(InvalidFileTypeException.class) + public ResponseEntity handleInvalidFileType(final InvalidFileTypeException e) { + return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); + } + + @ExceptionHandler(IOException.class) + public ResponseEntity io(final IOException e) { + return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); + } +} diff --git a/backend/src/main/java/com/twtw/backend/domain/image/dto/ImageResponse.java b/backend/src/main/java/com/twtw/backend/domain/image/dto/ImageResponse.java new file mode 100644 index 00000000..78d7adfe --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/domain/image/dto/ImageResponse.java @@ -0,0 +1,10 @@ +package com.twtw.backend.domain.image.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ImageResponse { + private String imageUrl; +} diff --git a/backend/src/main/java/com/twtw/backend/domain/image/exception/InvalidFileTypeException.java b/backend/src/main/java/com/twtw/backend/domain/image/exception/InvalidFileTypeException.java new file mode 100644 index 00000000..4cdcf242 --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/domain/image/exception/InvalidFileTypeException.java @@ -0,0 +1,10 @@ +package com.twtw.backend.domain.image.exception; + +public class InvalidFileTypeException extends IllegalArgumentException { + + private static final String MESSAGE = "파일 형식이 잘못되었습니다."; + + public InvalidFileTypeException() { + super(MESSAGE); + } +} diff --git a/backend/src/main/java/com/twtw/backend/domain/image/service/ImageService.java b/backend/src/main/java/com/twtw/backend/domain/image/service/ImageService.java new file mode 100644 index 00000000..b28fa454 --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/domain/image/service/ImageService.java @@ -0,0 +1,56 @@ +package com.twtw.backend.domain.image.service; + +import com.google.cloud.storage.BlobInfo; +import com.google.cloud.storage.Storage; +import com.twtw.backend.domain.image.dto.ImageResponse; +import com.twtw.backend.domain.image.exception.InvalidFileTypeException; +import com.twtw.backend.domain.member.entity.Member; +import com.twtw.backend.domain.member.service.AuthService; +import com.twtw.backend.global.properties.StorageProperties; +import java.io.IOException; +import java.util.UUID; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.web.multipart.MultipartFile; + +@Service +@RequiredArgsConstructor +public class ImageService { + + private static final String IMAGE_FILE_TYPE = "image"; + private final AuthService authService; + private final Storage storage; + private final StorageProperties storageProperties; + + public ImageResponse uploadImage(final MultipartFile image) throws IOException { + final Member member = authService.getMemberByJwt(); + final String contentType = image.getContentType(); + validate(contentType); + + final String uuid = UUID.randomUUID().toString(); + upload(image, contentType, uuid); + + final String imagePath = getImageUrl(uuid); + member.updateProfileImage(imagePath); + + return new ImageResponse(imagePath); + } + + private String getImageUrl(final String uuid) { + return storageProperties.getStorageUrl() + uuid; + } + + private void upload(final MultipartFile image, final String contentType, final String uuid) throws IOException { + storage.create( + BlobInfo.newBuilder(storageProperties.getBucket(), uuid) + .setContentType(contentType) + .build(), + image.getInputStream()); + } + + private void validate(final String contentType) { + if (contentType == null || !contentType.startsWith(IMAGE_FILE_TYPE)) { + throw new InvalidFileTypeException(); + } + } +} diff --git a/backend/src/main/java/com/twtw/backend/domain/member/controller/advice/AuthControllerAdvice.java b/backend/src/main/java/com/twtw/backend/domain/member/controller/advice/AuthControllerAdvice.java index cb70fed8..a86e6417 100644 --- a/backend/src/main/java/com/twtw/backend/domain/member/controller/advice/AuthControllerAdvice.java +++ b/backend/src/main/java/com/twtw/backend/domain/member/controller/advice/AuthControllerAdvice.java @@ -4,8 +4,6 @@ import com.twtw.backend.domain.member.exception.RefreshTokenInfoMismatchException; import com.twtw.backend.domain.member.exception.RefreshTokenValidationException; import com.twtw.backend.global.advice.ErrorResponse; - -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -16,20 +14,20 @@ public class AuthControllerAdvice { @ExceptionHandler(RefreshTokenInfoMismatchException.class) public ResponseEntity refreshTokenInfoMismatch( final RefreshTokenInfoMismatchException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.badRequest() .body(new ErrorResponse(e.getMessage())); } @ExceptionHandler(RefreshTokenValidationException.class) public ResponseEntity refreshTokenValidation( final RefreshTokenValidationException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.badRequest() .body(new ErrorResponse(e.getMessage())); } @ExceptionHandler(NicknameExistsException.class) public ResponseEntity nicknameExists(final NicknameExistsException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.badRequest() .body(new ErrorResponse(e.getMessage())); } } diff --git a/backend/src/main/java/com/twtw/backend/domain/member/entity/Member.java b/backend/src/main/java/com/twtw/backend/domain/member/entity/Member.java index 3696c930..99b76e6b 100644 --- a/backend/src/main/java/com/twtw/backend/domain/member/entity/Member.java +++ b/backend/src/main/java/com/twtw/backend/domain/member/entity/Member.java @@ -71,4 +71,8 @@ public void addGroupMember(final GroupMember groupMember) { public boolean hasNoGroupMember() { return this.groupMembers.isEmpty(); } + + public void updateProfileImage(final String profileImage) { + this.profileImage = profileImage; + } } diff --git a/backend/src/main/java/com/twtw/backend/domain/plan/controller/advice/PlanControllerAdvice.java b/backend/src/main/java/com/twtw/backend/domain/plan/controller/advice/PlanControllerAdvice.java index 96b6ad98..aeb75fc3 100644 --- a/backend/src/main/java/com/twtw/backend/domain/plan/controller/advice/PlanControllerAdvice.java +++ b/backend/src/main/java/com/twtw/backend/domain/plan/controller/advice/PlanControllerAdvice.java @@ -3,8 +3,6 @@ import com.twtw.backend.domain.plan.exception.InvalidPlanMemberException; import com.twtw.backend.domain.plan.exception.PlanMakerNotExistsException; import com.twtw.backend.global.advice.ErrorResponse; - -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -15,14 +13,14 @@ public class PlanControllerAdvice { @ExceptionHandler(InvalidPlanMemberException.class) public ResponseEntity refreshTokenInfoMismatch( final InvalidPlanMemberException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.badRequest() .body(new ErrorResponse(e.getMessage())); } @ExceptionHandler(PlanMakerNotExistsException.class) public ResponseEntity refreshTokenInfoMismatch( final PlanMakerNotExistsException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.badRequest() .body(new ErrorResponse(e.getMessage())); } } diff --git a/backend/src/main/java/com/twtw/backend/global/advice/GlobalErrorAdvice.java b/backend/src/main/java/com/twtw/backend/global/advice/GlobalErrorAdvice.java index 92da3486..f1ae27b5 100644 --- a/backend/src/main/java/com/twtw/backend/global/advice/GlobalErrorAdvice.java +++ b/backend/src/main/java/com/twtw/backend/global/advice/GlobalErrorAdvice.java @@ -3,8 +3,6 @@ import com.twtw.backend.global.exception.AuthorityException; import com.twtw.backend.global.exception.EntityNotFoundException; import com.twtw.backend.global.exception.WebClientResponseException; - -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -14,19 +12,19 @@ public class GlobalErrorAdvice { @ExceptionHandler(WebClientResponseException.class) public ResponseEntity webClientResponse(final WebClientResponseException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.badRequest() .body(new ErrorResponse(e.getMessage())); } @ExceptionHandler(EntityNotFoundException.class) public ResponseEntity entityNotFound(final EntityNotFoundException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.badRequest() .body(new ErrorResponse(e.getMessage())); } @ExceptionHandler(AuthorityException.class) public ResponseEntity authority(final AuthorityException e) { - return ResponseEntity.status(HttpStatus.BAD_REQUEST) + return ResponseEntity.badRequest() .body(new ErrorResponse(e.getMessage())); } } diff --git a/backend/src/main/java/com/twtw/backend/global/properties/StorageProperties.java b/backend/src/main/java/com/twtw/backend/global/properties/StorageProperties.java new file mode 100644 index 00000000..7b4d50da --- /dev/null +++ b/backend/src/main/java/com/twtw/backend/global/properties/StorageProperties.java @@ -0,0 +1,14 @@ +package com.twtw.backend.global.properties; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.springframework.boot.context.properties.ConfigurationProperties; + +@Getter +@RequiredArgsConstructor +@ConfigurationProperties(prefix = "spring.cloud.gcp.storage") +public class StorageProperties { + private final String bucket; + private final String credentialsFile; + private final String storageUrl; +} From 38dc7314f27cde21c977a909c9425b67ed75aa6e Mon Sep 17 00:00:00 2001 From: github-actions <> Date: Tue, 12 Dec 2023 08:37:49 +0000 Subject: [PATCH 2/4] Google Java Format --- .../twtw/backend/config/storage/StorageConfig.java | 11 ++++++++--- .../controller/advice/FriendControllerAdvice.java | 4 ++-- .../controller/advice/GroupControllerAdvice.java | 4 ++-- .../domain/image/controller/ImageController.java | 5 ++++- .../controller/advice/ImageControllerAdvice.java | 4 +++- .../backend/domain/image/service/ImageService.java | 10 +++++++--- .../controller/advice/AuthControllerAdvice.java | 10 ++++------ .../plan/controller/advice/PlanControllerAdvice.java | 7 +++---- .../twtw/backend/global/advice/GlobalErrorAdvice.java | 10 ++++------ .../backend/global/properties/StorageProperties.java | 1 + 10 files changed, 38 insertions(+), 28 deletions(-) diff --git a/backend/src/main/java/com/twtw/backend/config/storage/StorageConfig.java b/backend/src/main/java/com/twtw/backend/config/storage/StorageConfig.java index 0e7895f4..aed2922a 100644 --- a/backend/src/main/java/com/twtw/backend/config/storage/StorageConfig.java +++ b/backend/src/main/java/com/twtw/backend/config/storage/StorageConfig.java @@ -4,12 +4,15 @@ import com.google.cloud.storage.Storage; import com.google.cloud.storage.StorageOptions; import com.twtw.backend.global.properties.StorageProperties; -import java.io.IOException; + import lombok.RequiredArgsConstructor; + import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.core.io.ClassPathResource; +import java.io.IOException; + @Configuration @RequiredArgsConstructor public class StorageConfig { @@ -18,8 +21,10 @@ public class StorageConfig { @Bean public Storage storage() throws IOException { - final GoogleCredentials googleCredentials = GoogleCredentials.fromStream( - new ClassPathResource(storageProperties.getCredentialsFile()).getInputStream()); + final GoogleCredentials googleCredentials = + GoogleCredentials.fromStream( + new ClassPathResource(storageProperties.getCredentialsFile()) + .getInputStream()); return StorageOptions.newBuilder().setCredentials(googleCredentials).build().getService(); } diff --git a/backend/src/main/java/com/twtw/backend/domain/friend/controller/advice/FriendControllerAdvice.java b/backend/src/main/java/com/twtw/backend/domain/friend/controller/advice/FriendControllerAdvice.java index 16ed489e..c22d9a5b 100644 --- a/backend/src/main/java/com/twtw/backend/domain/friend/controller/advice/FriendControllerAdvice.java +++ b/backend/src/main/java/com/twtw/backend/domain/friend/controller/advice/FriendControllerAdvice.java @@ -2,6 +2,7 @@ import com.twtw.backend.domain.friend.exception.InvalidFriendMemberException; import com.twtw.backend.global.advice.ErrorResponse; + import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -11,7 +12,6 @@ public class FriendControllerAdvice { @ExceptionHandler(InvalidFriendMemberException.class) public ResponseEntity invalidFriendMember(final InvalidFriendMemberException e) { - return ResponseEntity.badRequest() - .body(new ErrorResponse(e.getMessage())); + return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); } } diff --git a/backend/src/main/java/com/twtw/backend/domain/group/controller/advice/GroupControllerAdvice.java b/backend/src/main/java/com/twtw/backend/domain/group/controller/advice/GroupControllerAdvice.java index 86dcea4a..bfea72f0 100644 --- a/backend/src/main/java/com/twtw/backend/domain/group/controller/advice/GroupControllerAdvice.java +++ b/backend/src/main/java/com/twtw/backend/domain/group/controller/advice/GroupControllerAdvice.java @@ -2,6 +2,7 @@ import com.twtw.backend.domain.group.exception.IllegalGroupMemberException; import com.twtw.backend.global.advice.ErrorResponse; + import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -11,7 +12,6 @@ public class GroupControllerAdvice { @ExceptionHandler(IllegalGroupMemberException.class) public ResponseEntity invalidFriendMember(final IllegalGroupMemberException e) { - return ResponseEntity.badRequest() - .body(new ErrorResponse(e.getMessage())); + return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); } } diff --git a/backend/src/main/java/com/twtw/backend/domain/image/controller/ImageController.java b/backend/src/main/java/com/twtw/backend/domain/image/controller/ImageController.java index 40ad9d33..91cf5ee5 100644 --- a/backend/src/main/java/com/twtw/backend/domain/image/controller/ImageController.java +++ b/backend/src/main/java/com/twtw/backend/domain/image/controller/ImageController.java @@ -2,14 +2,17 @@ import com.twtw.backend.domain.image.dto.ImageResponse; import com.twtw.backend.domain.image.service.ImageService; -import java.io.IOException; + 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.RestController; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; + @RestController @RequiredArgsConstructor @RequestMapping("images") diff --git a/backend/src/main/java/com/twtw/backend/domain/image/controller/advice/ImageControllerAdvice.java b/backend/src/main/java/com/twtw/backend/domain/image/controller/advice/ImageControllerAdvice.java index 1307c160..311fd9fd 100644 --- a/backend/src/main/java/com/twtw/backend/domain/image/controller/advice/ImageControllerAdvice.java +++ b/backend/src/main/java/com/twtw/backend/domain/image/controller/advice/ImageControllerAdvice.java @@ -2,11 +2,13 @@ import com.twtw.backend.domain.image.exception.InvalidFileTypeException; import com.twtw.backend.global.advice.ErrorResponse; -import java.io.IOException; + import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; +import java.io.IOException; + @RestControllerAdvice public class ImageControllerAdvice { diff --git a/backend/src/main/java/com/twtw/backend/domain/image/service/ImageService.java b/backend/src/main/java/com/twtw/backend/domain/image/service/ImageService.java index b28fa454..1988f68d 100644 --- a/backend/src/main/java/com/twtw/backend/domain/image/service/ImageService.java +++ b/backend/src/main/java/com/twtw/backend/domain/image/service/ImageService.java @@ -7,12 +7,15 @@ import com.twtw.backend.domain.member.entity.Member; import com.twtw.backend.domain.member.service.AuthService; import com.twtw.backend.global.properties.StorageProperties; -import java.io.IOException; -import java.util.UUID; + import lombok.RequiredArgsConstructor; + import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; +import java.util.UUID; + @Service @RequiredArgsConstructor public class ImageService { @@ -40,7 +43,8 @@ private String getImageUrl(final String uuid) { return storageProperties.getStorageUrl() + uuid; } - private void upload(final MultipartFile image, final String contentType, final String uuid) throws IOException { + private void upload(final MultipartFile image, final String contentType, final String uuid) + throws IOException { storage.create( BlobInfo.newBuilder(storageProperties.getBucket(), uuid) .setContentType(contentType) diff --git a/backend/src/main/java/com/twtw/backend/domain/member/controller/advice/AuthControllerAdvice.java b/backend/src/main/java/com/twtw/backend/domain/member/controller/advice/AuthControllerAdvice.java index a86e6417..76bcfc44 100644 --- a/backend/src/main/java/com/twtw/backend/domain/member/controller/advice/AuthControllerAdvice.java +++ b/backend/src/main/java/com/twtw/backend/domain/member/controller/advice/AuthControllerAdvice.java @@ -4,6 +4,7 @@ import com.twtw.backend.domain.member.exception.RefreshTokenInfoMismatchException; import com.twtw.backend.domain.member.exception.RefreshTokenValidationException; import com.twtw.backend.global.advice.ErrorResponse; + import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -14,20 +15,17 @@ public class AuthControllerAdvice { @ExceptionHandler(RefreshTokenInfoMismatchException.class) public ResponseEntity refreshTokenInfoMismatch( final RefreshTokenInfoMismatchException e) { - return ResponseEntity.badRequest() - .body(new ErrorResponse(e.getMessage())); + return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); } @ExceptionHandler(RefreshTokenValidationException.class) public ResponseEntity refreshTokenValidation( final RefreshTokenValidationException e) { - return ResponseEntity.badRequest() - .body(new ErrorResponse(e.getMessage())); + return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); } @ExceptionHandler(NicknameExistsException.class) public ResponseEntity nicknameExists(final NicknameExistsException e) { - return ResponseEntity.badRequest() - .body(new ErrorResponse(e.getMessage())); + return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); } } diff --git a/backend/src/main/java/com/twtw/backend/domain/plan/controller/advice/PlanControllerAdvice.java b/backend/src/main/java/com/twtw/backend/domain/plan/controller/advice/PlanControllerAdvice.java index aeb75fc3..67f86032 100644 --- a/backend/src/main/java/com/twtw/backend/domain/plan/controller/advice/PlanControllerAdvice.java +++ b/backend/src/main/java/com/twtw/backend/domain/plan/controller/advice/PlanControllerAdvice.java @@ -3,6 +3,7 @@ import com.twtw.backend.domain.plan.exception.InvalidPlanMemberException; import com.twtw.backend.domain.plan.exception.PlanMakerNotExistsException; import com.twtw.backend.global.advice.ErrorResponse; + import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -13,14 +14,12 @@ public class PlanControllerAdvice { @ExceptionHandler(InvalidPlanMemberException.class) public ResponseEntity refreshTokenInfoMismatch( final InvalidPlanMemberException e) { - return ResponseEntity.badRequest() - .body(new ErrorResponse(e.getMessage())); + return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); } @ExceptionHandler(PlanMakerNotExistsException.class) public ResponseEntity refreshTokenInfoMismatch( final PlanMakerNotExistsException e) { - return ResponseEntity.badRequest() - .body(new ErrorResponse(e.getMessage())); + return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); } } diff --git a/backend/src/main/java/com/twtw/backend/global/advice/GlobalErrorAdvice.java b/backend/src/main/java/com/twtw/backend/global/advice/GlobalErrorAdvice.java index f1ae27b5..50631494 100644 --- a/backend/src/main/java/com/twtw/backend/global/advice/GlobalErrorAdvice.java +++ b/backend/src/main/java/com/twtw/backend/global/advice/GlobalErrorAdvice.java @@ -3,6 +3,7 @@ import com.twtw.backend.global.exception.AuthorityException; import com.twtw.backend.global.exception.EntityNotFoundException; import com.twtw.backend.global.exception.WebClientResponseException; + import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -12,19 +13,16 @@ public class GlobalErrorAdvice { @ExceptionHandler(WebClientResponseException.class) public ResponseEntity webClientResponse(final WebClientResponseException e) { - return ResponseEntity.badRequest() - .body(new ErrorResponse(e.getMessage())); + return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); } @ExceptionHandler(EntityNotFoundException.class) public ResponseEntity entityNotFound(final EntityNotFoundException e) { - return ResponseEntity.badRequest() - .body(new ErrorResponse(e.getMessage())); + return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); } @ExceptionHandler(AuthorityException.class) public ResponseEntity authority(final AuthorityException e) { - return ResponseEntity.badRequest() - .body(new ErrorResponse(e.getMessage())); + return ResponseEntity.badRequest().body(new ErrorResponse(e.getMessage())); } } diff --git a/backend/src/main/java/com/twtw/backend/global/properties/StorageProperties.java b/backend/src/main/java/com/twtw/backend/global/properties/StorageProperties.java index 7b4d50da..10e4b4ef 100644 --- a/backend/src/main/java/com/twtw/backend/global/properties/StorageProperties.java +++ b/backend/src/main/java/com/twtw/backend/global/properties/StorageProperties.java @@ -2,6 +2,7 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; + import org.springframework.boot.context.properties.ConfigurationProperties; @Getter From 5d49934902705d862f2109b904951e9f061923f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=8A=B9=EC=A7=84?= Date: Tue, 12 Dec 2023 17:52:54 +0900 Subject: [PATCH 3/4] =?UTF-8?q?[TEST]=20=EC=9D=B4=EB=AF=B8=EC=A7=80=20?= =?UTF-8?q?=EC=97=85=EB=A1=9C=EB=93=9C=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EB=B0=8F=20rest=20do?= =?UTF-8?q?cs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/src/docs/image.adoc | 9 ++++ backend/src/docs/index.adoc | 1 + .../image/controller/ImageControllerTest.java | 54 +++++++++++++++++++ 3 files changed, 64 insertions(+) create mode 100644 backend/src/docs/image.adoc create mode 100644 backend/src/test/java/com/twtw/backend/domain/image/controller/ImageControllerTest.java diff --git a/backend/src/docs/image.adoc b/backend/src/docs/image.adoc new file mode 100644 index 00000000..3e26f3be --- /dev/null +++ b/backend/src/docs/image.adoc @@ -0,0 +1,9 @@ +:doctype: book +:icons: font +:source-highlighter: highlightjs +:toc: left +:toclevels: 4 + +== Image +=== 프로필 이미지 업로드 +operation::post upload image[snippets='http-request,http-response'] \ No newline at end of file diff --git a/backend/src/docs/index.adoc b/backend/src/docs/index.adoc index 57b5b5fb..adfcbf50 100644 --- a/backend/src/docs/index.adoc +++ b/backend/src/docs/index.adoc @@ -15,3 +15,4 @@ include::place.adoc[] include::member.adoc[] include::friend.adoc[] include::group.adoc[] +include::image.adoc[] diff --git a/backend/src/test/java/com/twtw/backend/domain/image/controller/ImageControllerTest.java b/backend/src/test/java/com/twtw/backend/domain/image/controller/ImageControllerTest.java new file mode 100644 index 00000000..d78084d7 --- /dev/null +++ b/backend/src/test/java/com/twtw/backend/domain/image/controller/ImageControllerTest.java @@ -0,0 +1,54 @@ +package com.twtw.backend.domain.image.controller; + +import static com.twtw.backend.support.docs.ApiDocsUtils.getDocumentRequest; +import static com.twtw.backend.support.docs.ApiDocsUtils.getDocumentResponse; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.BDDMockito.given; +import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; +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.twtw.backend.domain.image.dto.ImageResponse; +import com.twtw.backend.domain.image.service.ImageService; +import com.twtw.backend.support.docs.RestDocsTest; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +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.test.web.servlet.ResultActions; + +@DisplayName("ImageController의") +@WebMvcTest(ImageController.class) +class ImageControllerTest extends RestDocsTest { + + @MockBean private ImageService imageService; + + @Test + @DisplayName("이미지 업로드 API가 수행되는가") + void uploadImage() throws Exception { + // given + final ImageResponse expected = new ImageResponse("https://storage.googleapis.com/bucket-name/some-file-id"); + given(imageService.uploadImage(any())).willReturn(expected); + + // when + final ResultActions perform = + mockMvc.perform( + post("/images") + .contentType(MediaType.MULTIPART_FORM_DATA) + .content(toRequestBody("image를 request시 넣어주세요")) + .header( + "Authorization", + "Bearer wefa3fsdczf32.gaoiuergf92.gb5hsa2jgh")); + + // then + perform.andExpect(status().isOk()) + .andExpect(jsonPath("$.imageUrl").isString()); + + // docs + perform.andDo(print()) + .andDo(document("post upload image", getDocumentRequest(), getDocumentResponse())); + } +} From 37be8ac44dc938c02ca9daf532d0670bd13eca1c Mon Sep 17 00:00:00 2001 From: github-actions <> Date: Tue, 12 Dec 2023 08:53:14 +0000 Subject: [PATCH 4/4] Google Java Format --- .../domain/image/controller/ImageControllerTest.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/backend/src/test/java/com/twtw/backend/domain/image/controller/ImageControllerTest.java b/backend/src/test/java/com/twtw/backend/domain/image/controller/ImageControllerTest.java index d78084d7..ea8e0266 100644 --- a/backend/src/test/java/com/twtw/backend/domain/image/controller/ImageControllerTest.java +++ b/backend/src/test/java/com/twtw/backend/domain/image/controller/ImageControllerTest.java @@ -2,6 +2,7 @@ import static com.twtw.backend.support.docs.ApiDocsUtils.getDocumentRequest; import static com.twtw.backend.support.docs.ApiDocsUtils.getDocumentResponse; + import static org.mockito.ArgumentMatchers.any; import static org.mockito.BDDMockito.given; import static org.springframework.restdocs.mockmvc.MockMvcRestDocumentation.document; @@ -13,6 +14,7 @@ import com.twtw.backend.domain.image.dto.ImageResponse; import com.twtw.backend.domain.image.service.ImageService; import com.twtw.backend.support.docs.RestDocsTest; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest; @@ -30,7 +32,8 @@ class ImageControllerTest extends RestDocsTest { @DisplayName("이미지 업로드 API가 수행되는가") void uploadImage() throws Exception { // given - final ImageResponse expected = new ImageResponse("https://storage.googleapis.com/bucket-name/some-file-id"); + final ImageResponse expected = + new ImageResponse("https://storage.googleapis.com/bucket-name/some-file-id"); given(imageService.uploadImage(any())).willReturn(expected); // when @@ -44,8 +47,7 @@ void uploadImage() throws Exception { "Bearer wefa3fsdczf32.gaoiuergf92.gb5hsa2jgh")); // then - perform.andExpect(status().isOk()) - .andExpect(jsonPath("$.imageUrl").isString()); + perform.andExpect(status().isOk()).andExpect(jsonPath("$.imageUrl").isString()); // docs perform.andDo(print())