Skip to content

Commit 57f2553

Browse files
authored
Merge pull request #73 from IT-Cotato/feature/69
Feat: 회원 체형 정보 조회/저장 API 구현
2 parents e875497 + b63cac5 commit 57f2553

File tree

13 files changed

+416
-2
lines changed

13 files changed

+416
-2
lines changed

src/main/java/com/ongil/backend/domain/user/controller/UserController.java

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,19 +6,26 @@
66
import org.springframework.web.bind.annotation.GetMapping;
77
import org.springframework.web.bind.annotation.PatchMapping;
88
import org.springframework.web.bind.annotation.PathVariable;
9+
import org.springframework.web.bind.annotation.PutMapping;
910
import org.springframework.web.bind.annotation.RequestBody;
1011
import org.springframework.web.bind.annotation.RequestMapping;
1112
import org.springframework.web.bind.annotation.RestController;
1213

14+
import com.ongil.backend.domain.user.dto.request.BodyInfoRequest;
1315
import com.ongil.backend.domain.user.dto.request.UserUpdateProfileRequest;
14-
16+
import com.ongil.backend.domain.user.dto.response.BodyInfoResponse;
17+
import com.ongil.backend.domain.user.dto.response.SizeOptionsResponse;
18+
import com.ongil.backend.domain.user.dto.response.TermsResponse;
1519
import com.ongil.backend.domain.user.dto.response.UserInfoResDto;
1620
import com.ongil.backend.domain.user.service.UserService;
1721
import com.ongil.backend.global.common.dto.DataResponse;
1822

1923
import io.swagger.v3.oas.annotations.Operation;
24+
import io.swagger.v3.oas.annotations.tags.Tag;
25+
import jakarta.validation.Valid;
2026
import lombok.RequiredArgsConstructor;
2127

28+
@Tag(name = "User", description = "회원 API")
2229
@Validated
2330
@RestController
2431
@RequiredArgsConstructor
@@ -55,4 +62,37 @@ public ResponseEntity<DataResponse<UserInfoResDto>> updateProfileImage(
5562
return ResponseEntity.ok(DataResponse.from(res));
5663
}
5764

65+
@GetMapping("/me/body-info")
66+
@Operation(summary = "체형 정보 조회 API", description = "로그인한 회원의 체형 정보를 조회합니다. 5개 항목 중 하나라도 없으면 hasBodyInfo=false (토큰 필요)")
67+
public ResponseEntity<DataResponse<BodyInfoResponse>> getBodyInfo(
68+
@AuthenticationPrincipal Long userId
69+
) {
70+
BodyInfoResponse res = userService.getBodyInfo(userId);
71+
return ResponseEntity.ok(DataResponse.from(res));
72+
}
73+
74+
@PutMapping("/me/body-info")
75+
@Operation(summary = "체형 정보 저장/수정 API", description = "로그인한 회원의 체형 정보를 저장하거나 수정합니다. 모든 항목 필수입니다. (토큰 필요)")
76+
public ResponseEntity<DataResponse<BodyInfoResponse>> updateBodyInfo(
77+
@AuthenticationPrincipal Long userId,
78+
@Valid @RequestBody BodyInfoRequest request
79+
) {
80+
BodyInfoResponse res = userService.updateBodyInfo(userId, request);
81+
return ResponseEntity.ok(DataResponse.from(res));
82+
}
83+
84+
@GetMapping("/body-info/size-options")
85+
@Operation(summary = "사이즈 옵션 목록 조회 API", description = "드롭다운에 표시할 사이즈 옵션 목록을 조회합니다.")
86+
public ResponseEntity<DataResponse<SizeOptionsResponse>> getSizeOptions() {
87+
SizeOptionsResponse res = userService.getSizeOptions();
88+
return ResponseEntity.ok(DataResponse.from(res));
89+
}
90+
91+
@GetMapping("/body-info/terms")
92+
@Operation(summary = "수집·이용 동의 약관 조회 API", description = "사이즈 정보 수집 동의 약관을 조회합니다.")
93+
public ResponseEntity<DataResponse<TermsResponse>> getBodyInfoTerms() {
94+
TermsResponse res = userService.getBodyInfoTerms();
95+
return ResponseEntity.ok(DataResponse.from(res));
96+
}
97+
5898
}
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
package com.ongil.backend.domain.user.converter;
2+
3+
import com.ongil.backend.domain.user.dto.response.BodyInfoResponse;
4+
import com.ongil.backend.domain.user.dto.response.SizeOptionsResponse;
5+
import com.ongil.backend.domain.user.entity.User;
6+
import com.ongil.backend.domain.user.enums.BottomSize;
7+
import com.ongil.backend.domain.user.enums.ShoeSize;
8+
import com.ongil.backend.domain.user.enums.TopSize;
9+
10+
import lombok.experimental.UtilityClass;
11+
12+
@UtilityClass
13+
public class BodyInfoConverter {
14+
15+
/**
16+
* User 엔티티를 BodyInfoResponse로 변환
17+
* 5개 필드 중 하나라도 null이거나 동의하지 않은 경우 hasBodyInfo = false
18+
*/
19+
public static BodyInfoResponse toResponse(User user) {
20+
boolean hasBodyInfo = user.getHeight() != null
21+
&& user.getWeight() != null
22+
&& user.getUsualTopSize() != null
23+
&& user.getUsualBottomSize() != null
24+
&& user.getUsualShoeSize() != null
25+
&& Boolean.TRUE.equals(user.getBodyInfoAgreed());
26+
27+
return BodyInfoResponse.builder()
28+
.hasBodyInfo(hasBodyInfo)
29+
.height(user.getHeight())
30+
.weight(user.getWeight())
31+
.topSize(user.getUsualTopSize())
32+
.bottomSize(user.getUsualBottomSize())
33+
.shoeSize(user.getUsualShoeSize())
34+
.build();
35+
}
36+
37+
/**
38+
* Enum에서 사이즈 옵션 목록 생성
39+
*/
40+
public static SizeOptionsResponse toSizeOptionsResponse() {
41+
return SizeOptionsResponse.builder()
42+
.topSizes(TopSize.getAllDisplayNames())
43+
.bottomSizes(BottomSize.getAllDisplayNames())
44+
.shoeSizes(ShoeSize.getAllDisplayNames())
45+
.build();
46+
}
47+
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package com.ongil.backend.domain.user.dto.request;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import jakarta.validation.constraints.AssertTrue;
5+
import jakarta.validation.constraints.Max;
6+
import jakarta.validation.constraints.Min;
7+
import jakarta.validation.constraints.NotBlank;
8+
import jakarta.validation.constraints.NotNull;
9+
10+
@Schema(description = "체형 정보 저장 요청")
11+
public record BodyInfoRequest(
12+
13+
@NotNull(message = "키를 입력해주세요.")
14+
@Min(value = 100, message = "키는 100cm 이상이어야 합니다.")
15+
@Max(value = 250, message = "키는 250cm 이하여야 합니다.")
16+
@Schema(description = "키 (cm)", example = "175")
17+
Integer height,
18+
19+
@NotNull(message = "몸무게를 입력해주세요.")
20+
@Min(value = 20, message = "몸무게는 20kg 이상이어야 합니다.")
21+
@Max(value = 300, message = "몸무게는 300kg 이하여야 합니다.")
22+
@Schema(description = "몸무게 (kg)", example = "70")
23+
Integer weight,
24+
25+
@NotBlank(message = "상의 사이즈를 선택해주세요.")
26+
@Schema(description = "상의 사이즈", example = "66")
27+
String topSize,
28+
29+
@NotBlank(message = "하의 사이즈를 선택해주세요.")
30+
@Schema(description = "하의 사이즈", example = "30")
31+
String bottomSize,
32+
33+
@NotBlank(message = "신발 사이즈를 선택해주세요.")
34+
@Schema(description = "신발 사이즈", example = "270")
35+
String shoeSize,
36+
37+
@NotNull(message = "수집 동의 여부를 선택해주세요.")
38+
@AssertTrue(message = "사이즈 정보 수집에 동의해야 합니다.")
39+
@Schema(description = "수집 동의 여부", example = "true")
40+
Boolean agreedToCollection
41+
) {
42+
}
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
package com.ongil.backend.domain.user.dto.response;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
@Getter
8+
@Builder
9+
@Schema(description = "체형 정보 응답")
10+
public class BodyInfoResponse {
11+
12+
@Schema(description = "체형 정보 존재 여부", example = "true")
13+
private boolean hasBodyInfo;
14+
15+
@Schema(description = "키 (cm)", example = "175")
16+
private Integer height;
17+
18+
@Schema(description = "몸무게 (kg)", example = "70")
19+
private Integer weight;
20+
21+
@Schema(description = "상의 사이즈", example = "66")
22+
private String topSize;
23+
24+
@Schema(description = "하의 사이즈", example = "30")
25+
private String bottomSize;
26+
27+
@Schema(description = "신발 사이즈", example = "270")
28+
private String shoeSize;
29+
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.ongil.backend.domain.user.dto.response;
2+
3+
import java.util.List;
4+
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
import lombok.Builder;
7+
import lombok.Getter;
8+
9+
@Getter
10+
@Builder
11+
@Schema(description = "사이즈 옵션 목록 응답")
12+
public class SizeOptionsResponse {
13+
14+
@Schema(description = "상의 사이즈 목록")
15+
private List<String> topSizes;
16+
17+
@Schema(description = "하의 사이즈 목록")
18+
private List<String> bottomSizes;
19+
20+
@Schema(description = "신발 사이즈 목록")
21+
private List<String> shoeSizes;
22+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package com.ongil.backend.domain.user.dto.response;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.Builder;
5+
import lombok.Getter;
6+
7+
@Getter
8+
@Builder
9+
@Schema(description = "약관 내용 응답")
10+
public class TermsResponse {
11+
12+
@Schema(description = "약관 제목")
13+
private String title;
14+
15+
@Schema(description = "약관 내용")
16+
private String content;
17+
18+
@Schema(description = "약관 버전")
19+
private String version;
20+
21+
@Schema(description = "시행일")
22+
private String effectiveDate;
23+
}

src/main/java/com/ongil/backend/domain/user/entity/User.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,10 @@ public class User extends BaseEntity {
6767
@Column(name = "usual_shoe_size", length = 10)
6868
private String usualShoeSize;
6969

70+
@Column(name = "body_info_agreed", nullable = false)
71+
@Builder.Default
72+
private Boolean bodyInfoAgreed = false;
73+
7074
@Column(nullable = false)
7175
@Builder.Default
7276
private Integer points = 0;
@@ -79,4 +83,16 @@ public class User extends BaseEntity {
7983
public void updateProfileImage(String newProfileImgUrl) {
8084
this.profileImg = newProfileImgUrl;
8185
}
86+
87+
// 체형 정보 수정 비즈니스 로직
88+
public void updateBodyInfo(Integer height, Integer weight,
89+
String topSize, String bottomSize,
90+
String shoeSize, Boolean agreed) {
91+
this.height = height;
92+
this.weight = weight;
93+
this.usualTopSize = topSize;
94+
this.usualBottomSize = bottomSize;
95+
this.usualShoeSize = shoeSize;
96+
this.bodyInfoAgreed = agreed;
97+
}
8298
}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
package com.ongil.backend.domain.user.enums;
2+
3+
import java.util.Arrays;
4+
import java.util.List;
5+
import java.util.stream.Collectors;
6+
7+
import com.ongil.backend.global.common.exception.AppException;
8+
import com.ongil.backend.global.common.exception.ErrorCode;
9+
10+
import lombok.Getter;
11+
import lombok.RequiredArgsConstructor;
12+
13+
@Getter
14+
@RequiredArgsConstructor
15+
public enum BottomSize {
16+
SIZE_22_OR_LESS("22이하"),
17+
SIZE_23("23"),
18+
SIZE_24("24"),
19+
SIZE_25("25"),
20+
SIZE_26("26"),
21+
SIZE_27("27"),
22+
SIZE_28("28"),
23+
SIZE_29("29"),
24+
SIZE_30("30"),
25+
SIZE_31("31"),
26+
SIZE_32("32"),
27+
SIZE_33("33"),
28+
SIZE_34("34"),
29+
SIZE_35("35"),
30+
SIZE_36("36"),
31+
SIZE_37("37"),
32+
SIZE_38("38"),
33+
SIZE_39("39"),
34+
SIZE_40_OR_MORE("40이상");
35+
36+
private final String displayName;
37+
38+
public static BottomSize fromDisplayName(String displayName) {
39+
return Arrays.stream(values())
40+
.filter(size -> size.displayName.equals(displayName))
41+
.findFirst()
42+
.orElseThrow(() -> new AppException(ErrorCode.INVALID_SIZE));
43+
}
44+
45+
public static List<String> getAllDisplayNames() {
46+
return Arrays.stream(values())
47+
.map(BottomSize::getDisplayName)
48+
.collect(Collectors.toList());
49+
}
50+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
package com.ongil.backend.domain.user.enums;
2+
3+
import java.util.Arrays;
4+
import java.util.List;
5+
import java.util.stream.Collectors;
6+
7+
import com.ongil.backend.global.common.exception.AppException;
8+
import com.ongil.backend.global.common.exception.ErrorCode;
9+
10+
import lombok.Getter;
11+
import lombok.RequiredArgsConstructor;
12+
13+
@Getter
14+
@RequiredArgsConstructor
15+
public enum ShoeSize {
16+
SIZE_210_OR_LESS("210이하"),
17+
SIZE_215("215"),
18+
SIZE_220("220"),
19+
SIZE_225("225"),
20+
SIZE_230("230"),
21+
SIZE_235("235"),
22+
SIZE_240("240"),
23+
SIZE_245("245"),
24+
SIZE_250("250"),
25+
SIZE_255("255"),
26+
SIZE_260("260"),
27+
SIZE_265("265"),
28+
SIZE_270("270"),
29+
SIZE_275("275"),
30+
SIZE_280_OR_MORE("280이상");
31+
32+
private final String displayName;
33+
34+
public static ShoeSize fromDisplayName(String displayName) {
35+
return Arrays.stream(values())
36+
.filter(size -> size.displayName.equals(displayName))
37+
.findFirst()
38+
.orElseThrow(() -> new AppException(ErrorCode.INVALID_SIZE));
39+
}
40+
41+
public static List<String> getAllDisplayNames() {
42+
return Arrays.stream(values())
43+
.map(ShoeSize::getDisplayName)
44+
.collect(Collectors.toList());
45+
}
46+
}

0 commit comments

Comments
 (0)