Skip to content

Commit 54cf6bb

Browse files
authored
Merge pull request #18 from Block-Guard/feat/#16/user-api
[Feat] User api
2 parents 03499dd + 16ce651 commit 54cf6bb

File tree

16 files changed

+403
-2
lines changed

16 files changed

+403
-2
lines changed

build.gradle

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ dependencies {
4242
implementation 'io.jsonwebtoken:jjwt-api:0.11.5'
4343
implementation 'io.jsonwebtoken:jjwt-impl:0.11.5'
4444
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
45+
46+
implementation(platform("software.amazon.awssdk:bom:2.31.58"))
47+
implementation 'software.amazon.awssdk:s3'
48+
implementation 'software.amazon.awssdk:auth'
4549
}
4650

4751
tasks.named('test') {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package com.blockguard.server.domain.user.api;
2+
3+
import com.blockguard.server.domain.user.application.UserService;
4+
import com.blockguard.server.domain.user.domain.User;
5+
import com.blockguard.server.domain.user.dto.request.UpdatePasswordRequest;
6+
import com.blockguard.server.domain.user.dto.request.UpdateUserInfo;
7+
import com.blockguard.server.domain.user.dto.response.MyPageResponse;
8+
import com.blockguard.server.global.common.codes.SuccessCode;
9+
import com.blockguard.server.global.common.response.BaseResponse;
10+
import com.blockguard.server.global.config.resolver.CurrentUser;
11+
import com.blockguard.server.global.config.swagger.CustomExceptionDescription;
12+
import com.blockguard.server.global.config.swagger.SwaggerResponseDescription;
13+
import io.swagger.v3.oas.annotations.Operation;
14+
import io.swagger.v3.oas.annotations.Parameter;
15+
import lombok.AllArgsConstructor;
16+
import org.springframework.http.MediaType;
17+
import org.springframework.web.bind.annotation.*;
18+
19+
@RestController
20+
@AllArgsConstructor
21+
@RequestMapping("/api/users")
22+
public class UserApi {
23+
private final UserService userService;
24+
25+
@GetMapping("/me")
26+
@Operation(summary = "마이페이지 조회")
27+
public BaseResponse<MyPageResponse> getMyPageInfo(@Parameter(hidden = true) @CurrentUser User user){
28+
MyPageResponse myPageResponse = userService.getMyPageInfo(user);
29+
return BaseResponse.of(SuccessCode.GET_MY_PAGE_SUCCESS, myPageResponse);
30+
}
31+
32+
@PutMapping(value = "/me", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
33+
@CustomExceptionDescription(SwaggerResponseDescription.UPDATE_MY_PAGE_INFO_FAIL)
34+
@Operation(summary = "회원 정보 수정", description = "아이디를 제외하고 수정가능합니다. 생년월일 형식은 'yyyyMMDD'로 입력해야합니다.")
35+
public BaseResponse<Void> updateUserInfo(@Parameter(hidden = true) @CurrentUser User user,
36+
@ModelAttribute UpdateUserInfo updateUserInfo){
37+
userService.updateUserInfo(user.getId(), updateUserInfo);
38+
return BaseResponse.of(SuccessCode.UPDATE_USER_INFO_SUCCESS);
39+
40+
}
41+
42+
@PutMapping(value = "/me/password")
43+
@CustomExceptionDescription(SwaggerResponseDescription.UPDATE_MY_PAGE_INFO_FAIL)
44+
@Operation(summary = "비밀번호 변경", description = "현재 비밀번호를 확인하고, 새 비밀번호로 변경합니다.")
45+
public BaseResponse<Void> updatePassword(@Parameter(hidden = true) @CurrentUser User user,
46+
@RequestBody UpdatePasswordRequest updatePasswordRequest){
47+
userService.updatePassword(user, updatePasswordRequest);
48+
return BaseResponse.of(SuccessCode.UPDATE_PASSWORD_SUCCESS);
49+
50+
}
51+
52+
@DeleteMapping("/withdraw")
53+
@Operation(summary = "회원 탈퇴")
54+
public BaseResponse<Void> withdraw(@Parameter(hidden = true) @CurrentUser User user){
55+
userService.withdraw(user.getId());
56+
return BaseResponse.of(SuccessCode.WITHDRAW_SUCCESS);
57+
}
58+
59+
60+
61+
}
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
package com.blockguard.server.domain.user.application;
2+
3+
import com.blockguard.server.domain.user.dao.UserRepository;
4+
import com.blockguard.server.domain.user.domain.User;
5+
import com.blockguard.server.domain.user.dto.request.UpdatePasswordRequest;
6+
import com.blockguard.server.domain.user.dto.request.UpdateUserInfo;
7+
import com.blockguard.server.domain.user.dto.response.MyPageResponse;
8+
import com.blockguard.server.global.common.codes.ErrorCode;
9+
import com.blockguard.server.global.config.S3.S3Service;
10+
import com.blockguard.server.global.exception.BusinessExceptionHandler;
11+
import lombok.AllArgsConstructor;
12+
import org.springframework.security.crypto.password.PasswordEncoder;
13+
import org.springframework.stereotype.Service;
14+
import org.springframework.transaction.annotation.Transactional;
15+
16+
import java.time.LocalDate;
17+
import java.time.format.DateTimeFormatter;
18+
import java.time.format.DateTimeParseException;
19+
20+
@Service
21+
@AllArgsConstructor
22+
public class UserService {
23+
private final UserRepository userRepository;
24+
private final S3Service s3Service;
25+
private final PasswordEncoder passwordEncoder;
26+
27+
@Transactional
28+
public void withdraw(Long id) {
29+
User user = userRepository.findById(id)
30+
.orElseThrow(() -> new BusinessExceptionHandler(ErrorCode.INVALID_USER));
31+
32+
user.markDeleted();
33+
}
34+
35+
@Transactional
36+
public void updateUserInfo(Long id, UpdateUserInfo updateUserInfo) {
37+
User user = userRepository.findById(id)
38+
.orElseThrow(() -> new BusinessExceptionHandler(ErrorCode.INVALID_USER));
39+
40+
if (updateUserInfo.getName() != null) user.updateName(updateUserInfo.getName());
41+
42+
if (updateUserInfo.getPhoneNumber() != null) user.updatePhoneNumber(updateUserInfo.getPhoneNumber());
43+
44+
if (updateUserInfo.getBirthDate() != null) {
45+
try {
46+
LocalDate parsedBirthDate = LocalDate.parse(updateUserInfo.getBirthDate(), DateTimeFormatter.BASIC_ISO_DATE);
47+
user.updateBirthDate(parsedBirthDate);
48+
} catch (DateTimeParseException e){
49+
throw new BusinessExceptionHandler(ErrorCode.INVALID_DATE_FORMAT);
50+
}
51+
}
52+
if (updateUserInfo.getProfileImage() != null && !updateUserInfo.getProfileImage().isEmpty()) {
53+
String contentType = updateUserInfo.getProfileImage().getContentType();
54+
if (contentType == null || !contentType.startsWith("image/")) {
55+
throw new BusinessExceptionHandler(ErrorCode.INVALID_PROFILE_IMAGE);
56+
}
57+
String imageKey = s3Service.upload(updateUserInfo.getProfileImage(), "profiles");
58+
user.updateProfileImageKey(imageKey);
59+
}
60+
}
61+
62+
public MyPageResponse getMyPageInfo(User user) {
63+
String profileImageUrl = (user.getProfileImageKey() != null) ?
64+
s3Service.getPublicUrl(user.getProfileImageKey()) : null;
65+
return MyPageResponse.from(user, profileImageUrl);
66+
}
67+
68+
@Transactional
69+
public void updatePassword(User user, UpdatePasswordRequest updatePasswordRequest) {
70+
if(!passwordEncoder.matches(updatePasswordRequest.getCurrentPwd(), user.getPassword())){
71+
throw new BusinessExceptionHandler(ErrorCode.PASSWORD_MISMATCH);
72+
}
73+
74+
String encodedNewPwd = passwordEncoder.encode(updatePasswordRequest.getNewPwd());
75+
user.changePassword(encodedNewPwd);
76+
userRepository.save(user);
77+
}
78+
}

src/main/java/com/blockguard/server/domain/user/domain/User.java

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import jakarta.persistence.*;
99
import lombok.*;
1010
import org.hibernate.annotations.SQLRestriction;
11+
import org.springframework.web.multipart.MultipartFile;
1112

1213
import java.time.LocalDate;
1314
import java.time.LocalDateTime;
@@ -71,4 +72,20 @@ public void markDeleted() {
7172
public void changePassword(String tempPassword) {
7273
this.password = tempPassword;
7374
}
75+
76+
public void updateName(String updateName) {
77+
this.name = updateName;
78+
}
79+
80+
public void updatePhoneNumber(String updatePhoneNumber){
81+
this.phoneNumber = updatePhoneNumber;
82+
}
83+
84+
public void updateProfileImageKey(String updateProfileImageKey) {
85+
this.profileImageKey = updateProfileImageKey;
86+
}
87+
88+
public void updateBirthDate(LocalDate updateBirthDate) {
89+
this.birthDate = updateBirthDate;
90+
}
7491
}

src/main/java/com/blockguard/server/domain/user/dto/request/LoginRequest.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.blockguard.server.domain.user.dto.request;
22

3+
import jakarta.validation.constraints.NotBlank;
34
import lombok.AllArgsConstructor;
45
import lombok.Builder;
56
import lombok.Getter;
@@ -8,6 +9,8 @@
89
@Builder
910
@AllArgsConstructor
1011
public class LoginRequest {
12+
@NotBlank
1113
private String email;
14+
@NotBlank
1215
private String password;
1316
}
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
package com.blockguard.server.domain.user.dto.request;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
@Getter
8+
@AllArgsConstructor
9+
@Builder
10+
public class UpdatePasswordRequest {
11+
private String currentPwd;
12+
private String newPwd;
13+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package com.blockguard.server.domain.user.dto.request;
2+
3+
import lombok.AllArgsConstructor;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
import org.springframework.web.multipart.MultipartFile;
7+
8+
@Getter
9+
@AllArgsConstructor
10+
@Builder
11+
public class UpdateUserInfo {
12+
private String name;
13+
private String birthDate; //yyyyMMDD
14+
private String phoneNumber;
15+
private MultipartFile profileImage;
16+
17+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.blockguard.server.domain.user.dto.response;
2+
3+
import com.blockguard.server.domain.user.domain.User;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
8+
import java.time.format.DateTimeFormatter;
9+
10+
@Getter
11+
@Builder
12+
@AllArgsConstructor
13+
public class MyPageResponse {
14+
private String email;
15+
private String name;
16+
private String birthDate;
17+
private String phoneNumber;
18+
private String profileImageUrl;
19+
20+
public static MyPageResponse from(User user, String profileImageUrl) {
21+
return MyPageResponse.builder()
22+
.email(user.getEmail())
23+
.name(user.getName())
24+
.birthDate(user.getBirthDate().format(DateTimeFormatter.ofPattern("yyyyMMdd")))
25+
.phoneNumber(user.getPhoneNumber())
26+
.profileImageUrl(user.getProfileImageKey())
27+
.build();
28+
}
29+
}

src/main/java/com/blockguard/server/global/common/codes/ErrorCode.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@ public enum ErrorCode {
1313
INVALID_PASSWORD(HttpStatus.BAD_REQUEST, 4003, "비밀번호가 일치하지 않습니다."),
1414
USER_INFO_NOT_FOUND(HttpStatus.NOT_FOUND, 4004, "일치하는 회원 정보가 없습니다."),
1515
EMAIL_NOT_FOUND(HttpStatus.NOT_FOUND, 4005, "일치하는 이메일 정보가 없습니다."),
16-
INVALID_TOKEN(HttpStatus.UNAUTHORIZED, 4010, "유효하지 않은 토큰입니다."),
16+
INVALID_USER(HttpStatus.NOT_FOUND, 4006, "존재하지 않는 회원입니다."),
17+
INVALID_DATE_FORMAT(HttpStatus.BAD_REQUEST, 4007, "생년월일 형식이 올바르지 않습니다. yyyyMMDD 형식으로 입력해주십시오."),
18+
INVALID_PHONE_NUMBER_FORMAT(HttpStatus.BAD_REQUEST, 4008, "전화번호 형식이 올바르지 않습니다. 010-1234-5678 형식으로 입력해주십시오."),
19+
PASSWORD_MISMATCH(HttpStatus.BAD_REQUEST, 4009, "현재 비밀번호 값이 일치하지 않습니다."),
20+
INVALID_PROFILE_IMAGE(HttpStatus.BAD_REQUEST, 4010,"이미지 파일만 업로드 가능합니다."),
21+
INVALID_TOKEN(HttpStatus.UNAUTHORIZED, 4020, "유효하지 않은 토큰입니다."),
22+
FILE_NAME_NOT_FOUND(HttpStatus.NOT_FOUND, 4021, "파일명이 없습니다."),
23+
INVALID_DIRECTORY_ROUTE(HttpStatus.NOT_FOUND, 4022, "잘못된 디렉토리 경로입니다."),
1724

1825
// 5000~ : server error
1926
INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 5000, "서버 오류가 발생했습니다.");

src/main/java/com/blockguard/server/global/common/codes/SuccessCode.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,11 @@ public enum SuccessCode {
1212
USER_REGISTERED(HttpStatus.CREATED, 2001, "회원가입이 완료되었습니다."),
1313
LOGIN_SUCCESS(HttpStatus.OK, 2002, "로그인에 성공하였습니다."),
1414
USER_EMAIL_FOUND(HttpStatus.OK, 2003, "아이디 조회에 성공하였습니다."),
15-
SEND_TEMP_PASSWORD_BY_EMAIL(HttpStatus.OK, 2005, "임시 비밀번호를 메일로 전송하였습니다.");
15+
SEND_TEMP_PASSWORD_BY_EMAIL(HttpStatus.OK, 2004, "임시 비밀번호를 메일로 전송하였습니다."),
16+
GET_MY_PAGE_SUCCESS(HttpStatus.OK, 2005, "마이페이지 조회에 성공하였습니다."),
17+
UPDATE_USER_INFO_SUCCESS(HttpStatus.OK, 2006, "회원 정보를 수정하였습니다."),
18+
WITHDRAW_SUCCESS(HttpStatus.OK, 2007, "회원 탈퇴가 성공하였습니다."),
19+
UPDATE_PASSWORD_SUCCESS(HttpStatus.OK,2008,"비밀번호 변경에 성공하였습니다.");
1620

1721
private final HttpStatus status;
1822
private final int code;

0 commit comments

Comments
 (0)