Skip to content

Commit

Permalink
merge: pull request #84 from TeamACON/feat/#77
Browse files Browse the repository at this point in the history
[FEAT/#77] 닉네임 유효성 검증 API 구현
  • Loading branch information
ckkim817 authored Feb 18, 2025
2 parents 7d02a60 + 65b4349 commit 874a579
Show file tree
Hide file tree
Showing 8 changed files with 247 additions and 24 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public enum ErrorType {
DATA_INTEGRITY_VIOLATION_ERROR(HttpStatus.BAD_REQUEST, 40007, "데이터 무결성 제약 조건을 위반했습니다."),
INVALID_ACCESS_TOKEN_ERROR(HttpStatus.BAD_REQUEST, 40008, "유효하지 않은 accessToken입니다."),
INVALID_REFRESH_TOKEN_ERROR(HttpStatus.BAD_REQUEST, 40088, "유효하지 않은 refreshToken입니다."),
// TODO: NonNull 필드에 null 값이 입력되었을 때 발생하는 예외 처리 추가
INVALID_IMAGE_PATH_ERROR(HttpStatus.NOT_FOUND, 40052, "유효하지 않은 이미지 경로입니다."),

/* 401 Unauthorized */
EXPIRED_ACCESS_TOKEN_ERROR(HttpStatus.UNAUTHORIZED, 40101, "만료된 accessToken입니다."),
Expand Down Expand Up @@ -50,6 +50,11 @@ public enum ErrorType {
INVALID_FAVORITE_CUISINE_RANK_SIZE_ERROR(HttpStatus.BAD_REQUEST, 40031, "favoriteCuisineRank의 사이즈가 잘못되었습니다."),
ALREADY_VERIFIED_AREA_ERROR(HttpStatus.BAD_REQUEST, 40032, "이미 인증된 동네가 존재합니다."),
INVALID_IMAGE_TYPE_ERROR(HttpStatus.BAD_REQUEST, 40045, "유효하지 않은 imageType입니다."),
INVALID_NICKNAME_ERROR(HttpStatus.BAD_REQUEST, 40051, "닉네임이 조건을 만족하지 않습니다."),
INVALID_BIRTH_DATE_ERROR(HttpStatus.BAD_REQUEST, 40053, "유효하지 않은 생년월일입니다."),

/* 409 Conflict */
DUPLICATED_NICKNAME_ERROR(HttpStatus.CONFLICT, 40901, "이미 사용 중인 닉네임입니다."),

/* 500 Internal Server Error */
FAILED_DOWNLOAD_GOOGLE_PUBLIC_KEY_ERROR(HttpStatus.BAD_REQUEST, 50002, "구글 공개키 다운로드에 실패하였습니다."),
Expand Down
48 changes: 38 additions & 10 deletions src/main/java/com/acon/server/global/external/s3/S3Adapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import java.time.Duration;
import java.time.Instant;
import java.util.Date;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
Expand All @@ -20,16 +21,23 @@
// TODO: 추후 AWS SDK v2를 사용하는 방식으로 변경
public class S3Adapter {

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

@Value("${cloud.aws.s3.path.profileImage}")
@Value("${cloud.aws.s3.bucket.url-prefix}")
private String bucketUrlPrefix;

@Value("${cloud.aws.s3.path.profile-image}")
private String profileImagePath;

@Value("${cloud.aws.s3.path.reviewImage}")
@Getter
@Value("${cloud.aws.s3.path.basic-profile-image-url}")
private String basicProfileImageUrl;

@Value("${cloud.aws.s3.path.review-image}")
private String reviewImagePath;

@Value("${cloud.aws.s3.path.spotImage}")
@Value("${cloud.aws.s3.path.spot-image}")
private String spotImagePath;

private final AmazonS3 amazonS3;
Expand All @@ -49,7 +57,7 @@ public String getPreSignedUrlForSpotImage(String fileName) {

private String getPreSignedUrl(String path, String fileName) {
if (!path.isEmpty()) {
fileName = path + "/" + fileName;
fileName = path + fileName;
}

try {
Expand All @@ -63,7 +71,7 @@ private String getPreSignedUrl(String path, String fileName) {
}

private GeneratePresignedUrlRequest getGeneratePresignedUrlRequest(String fileName) {
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucket, fileName)
GeneratePresignedUrlRequest generatePresignedUrlRequest = new GeneratePresignedUrlRequest(bucketName, fileName)
.withMethod(HttpMethod.PUT)
.withExpiration(getPreSignedUrlExpTime());

Expand All @@ -82,9 +90,29 @@ private Date getPreSignedUrlExpTime() {
return Date.from(expirationTime);
}

public String getImageUrl(String fileName) {
return amazonS3.getUrl(bucket, profileImagePath + "/" + fileName).toString();
public String getProfileImageUrl(String fileName) {
return getImageUrl(profileImagePath, fileName);
}

private String getImageUrl(String path, String fileName) {
return amazonS3.getUrl(bucketName, path + fileName).toString();
}

public void validateProfileImageExists(String fileName) {
validateImageExists(profileImagePath, fileName);
}

// TODO: S3에서 파일 삭제하는 로직 추가
private void validateImageExists(String path, String fileName) {
if (!amazonS3.doesObjectExist(bucketName, path + fileName)) {
throw new BusinessException(ErrorType.INVALID_IMAGE_PATH_ERROR);
}
}

public void deleteFile(String fileUrl) {
amazonS3.deleteObject(bucketName, getFileKey(fileUrl));
}

private String getFileKey(String fileUrl) {
return fileUrl.substring(bucketUrlPrefix.length());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,10 @@ public class S3Config {
@Value("${cloud.aws.region.static}")
private String region;

@Value("${cloud.aws.credentials.accessKey}")
@Value("${cloud.aws.credentials.access-key}")
private String accessKey;

@Value("${cloud.aws.credentials.secretKey}")
@Value("${cloud.aws.credentials.secret-key}")
private String secretKey;

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import com.acon.server.member.api.request.LogoutRequest;
import com.acon.server.member.api.request.MemberAreaRequest;
import com.acon.server.member.api.request.PreferenceRequest;
import com.acon.server.member.api.request.ProfileRequest;
import com.acon.server.member.api.request.ReissueTokenRequest;
import com.acon.server.member.api.request.WithdrawalReasonRequest;
import com.acon.server.member.api.response.AcornCountResponse;
Expand All @@ -31,6 +32,7 @@
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PatchMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
Expand Down Expand Up @@ -138,6 +140,24 @@ public ResponseEntity<PreSignedUrlResponse> getPreSignedUrl(
);
}

@GetMapping(path = "/nickname/validate")
public ResponseEntity<Void> getNicknameValidate(
@RequestParam(name = "nickname") final String nickname
) {
memberService.validateNickname(nickname);

return ResponseEntity.ok().build();
}

@PatchMapping(path = "/members/me", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> patchProfile(
@Valid @RequestBody ProfileRequest request
) {
memberService.updateProfile(request.profileImage(), request.nickname(), request.birthDate());

return ResponseEntity.ok().build();
}

@PostMapping(path = "/auth/logout", consumes = MediaType.APPLICATION_JSON_VALUE)
public ResponseEntity<Void> logout(
@Valid @RequestBody LogoutRequest request
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.acon.server.member.api.request;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;

@JsonInclude(Include.NON_NULL)
public record ProfileRequest(
@NotNull(message = "profileImage는 필수입니다.")
String profileImage,
@NotBlank(message = "nickname은 공백일 수 없습니다.")
String nickname,
String birthDate
) {

}
Loading

0 comments on commit 874a579

Please sign in to comment.