Skip to content

Commit c6a85c3

Browse files
authored
feat: 정책, 클라이언트 settings 정보 조회 api 구축
# 변경점 👍 - GET /api/users/init-settings : 클라이언트(Android, IOS)에 맞는 버전 충족 여부, 필수 약관 동의 여부 및 초기 태그 설정 여부 조회 - POST /api/users/policy-agreement : 약관 동의 api - 약관 종류는 enum인 PolicyType으로 관리하고 revision으로 약관 버전을 관리해서 필요 시 재동의 유도하도록 구성했습니다. - 기존 유저는 tag값을 변경할 필요가 없기에 앞으로 신규 유저들만 UserInitSettingEntity를 만들어서 tag 설정 여부를 확인합니다. # 비고 ✏ - 현재는 figma 상 보이는 2개의 약관만 정의해두었고, 필요 시 enum 값 추가 가능합니다. - 현재는 클라이언트 최소요구버전, 최신버전 모두 1.0.0으로 둬서 needsOptionalUpdate, needsForseUpdate 모두 항상 false 입니다.
1 parent c7b4f3b commit c6a85c3

25 files changed

Lines changed: 818 additions & 2 deletions

src/main/java/apptive/team5/global/exception/ExceptionCode.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,8 @@ public enum ExceptionCode {
2121
BAD_SUBSCRIBE_REQUEST("자기 자신은 구독할 수 없습니다."),
2222
DUPLICATE_SUBSCRIBE_REQUEST("이미 구독한 회원입니다."),
2323
INVALID_DIARY_LIST("유효하지 않은 다이어리 ID가 포함되어 있습니다."),
24-
DUPLICATE_DIARY_REPORT("이미 신고한 게시글입니다.");
24+
DUPLICATE_DIARY_REPORT("이미 신고한 게시글입니다."),
25+
REQUIRED_POLICY_NOT_AGREED("필수 약관에 동의해야 합니다.");
2526

2627
private final String description;
2728

src/main/java/apptive/team5/user/controller/UserController.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,20 @@
11
package apptive.team5.user.controller;
22

33
import apptive.team5.file.dto.FileUploadRequest;
4+
import apptive.team5.user.dto.InitSettingsRequest;
5+
import apptive.team5.user.dto.InitSettingsResponse;
6+
import apptive.team5.user.dto.InitSettingsResponse.AppUpdateStatus;
7+
import apptive.team5.user.dto.PolicyAgreementRequest;
8+
import apptive.team5.user.dto.PolicyStatusResponse;
49
import apptive.team5.user.dto.UserResponse;
510
import apptive.team5.user.dto.UserSearchResponse;
611
import apptive.team5.user.dto.UserStaticsResponse;
712
import apptive.team5.user.dto.UserTagUpdateRequest;
13+
import apptive.team5.user.service.UserInitSettingService;
14+
import apptive.team5.user.service.UserPolicyService;
815
import apptive.team5.user.service.UserService;
916
import jakarta.validation.Valid;
17+
import java.util.List;
1018
import lombok.RequiredArgsConstructor;
1119
import org.springframework.data.domain.Page;
1220
import org.springframework.data.domain.PageRequest;
@@ -21,6 +29,8 @@
2129
public class UserController {
2230

2331
private final UserService userService;
32+
private final UserPolicyService userPolicyService;
33+
private final UserInitSettingService userInitSettingService;
2434

2535
@GetMapping("/my")
2636
public ResponseEntity<UserResponse> getMyInfo(@AuthenticationPrincipal Long userId) {
@@ -82,4 +92,25 @@ public ResponseEntity<UserStaticsResponse> getUserStatics(@PathVariable Long use
8292

8393
return ResponseEntity.status(HttpStatus.OK).body(response);
8494
}
95+
96+
@GetMapping("/init-settings")
97+
public ResponseEntity<InitSettingsResponse> getInitSettings(@AuthenticationPrincipal Long userId,
98+
@Valid @ModelAttribute InitSettingsRequest request) {
99+
100+
AppUpdateStatus appUpdateStatus = userInitSettingService.checkAppUpdate(request.clientType(), request.clientVersion());
101+
List<PolicyStatusResponse> policies = userPolicyService.getPolicyStatuses(userId);
102+
boolean needsPolicyAgreement = policies.stream().anyMatch(PolicyStatusResponse::needsUpdate);
103+
boolean needsTagSetup = userInitSettingService.checkNeedsTagSetup(userId);
104+
105+
return ResponseEntity.ok(new InitSettingsResponse(appUpdateStatus, needsPolicyAgreement, needsTagSetup, policies));
106+
}
107+
108+
@PostMapping("/policy-agreement")
109+
public ResponseEntity<Void> agreePolicy(@Valid @RequestBody PolicyAgreementRequest request,
110+
@AuthenticationPrincipal Long userId) {
111+
112+
userPolicyService.agreePolicy(userId, request);
113+
114+
return ResponseEntity.status(HttpStatus.OK).build();
115+
}
85116
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package apptive.team5.user.domain;
2+
3+
public enum ClientType {
4+
ANDROID, IOS
5+
}
Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package apptive.team5.user.domain;
2+
3+
import java.util.Map;
4+
5+
public class ClientVersion {
6+
7+
private static final Map<ClientType, String> MIN_VERSION = Map.of(
8+
ClientType.ANDROID, "1.0.0",
9+
ClientType.IOS, "1.0.0"
10+
);
11+
12+
private static final Map<ClientType, String> LATEST_VERSION = Map.of(
13+
ClientType.ANDROID, "1.0.0",
14+
ClientType.IOS, "1.0.0"
15+
);
16+
17+
public static String getMinVersion(ClientType type) {
18+
return MIN_VERSION.get(type);
19+
}
20+
21+
public static String getLatestVersion(ClientType type) {
22+
return LATEST_VERSION.get(type);
23+
}
24+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package apptive.team5.user.domain;
2+
3+
import java.util.Map;
4+
5+
public class PolicyRevision {
6+
7+
private static final Map<PolicyType, Long> LATEST = Map.of(
8+
PolicyType.SERVICE_TERMS, 1L,
9+
PolicyType.PRIVACY, 1L
10+
);
11+
12+
public static Long getLatest(PolicyType type) {
13+
return LATEST.get(type);
14+
}
15+
}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package apptive.team5.user.domain;
2+
3+
public enum PolicyType {
4+
5+
SERVICE_TERMS(true),
6+
PRIVACY(true);
7+
8+
private final boolean required;
9+
10+
PolicyType(boolean required) {
11+
this.required = required;
12+
}
13+
14+
public boolean isRequired() {
15+
return required;
16+
}
17+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package apptive.team5.user.domain;
2+
3+
import apptive.team5.global.entity.BaseTimeEntity;
4+
import jakarta.persistence.*;
5+
import lombok.AccessLevel;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
@Getter
10+
@Entity
11+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
12+
public class UserInitSettingEntity extends BaseTimeEntity {
13+
14+
@Id
15+
@GeneratedValue(strategy = GenerationType.IDENTITY)
16+
private Long id;
17+
18+
@OneToOne(fetch = FetchType.LAZY)
19+
@JoinColumn(name = "user_id", nullable = false, unique = true)
20+
private UserEntity userEntity;
21+
22+
@Column(nullable = false)
23+
private Boolean isTagSet;
24+
25+
public UserInitSettingEntity(UserEntity userEntity, boolean isTagSet) {
26+
this.userEntity = userEntity;
27+
this.isTagSet = isTagSet;
28+
}
29+
30+
public void markTagSet() {
31+
this.isTagSet = true;
32+
}
33+
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package apptive.team5.user.domain;
2+
3+
import apptive.team5.global.entity.BaseTimeEntity;
4+
import jakarta.persistence.*;
5+
import lombok.AccessLevel;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
import java.time.LocalDateTime;
10+
11+
@Getter
12+
@Entity
13+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
14+
@Table(uniqueConstraints = @UniqueConstraint(columnNames = {"user_id", "policy_type"}))
15+
public class UserPolicyAgreementEntity extends BaseTimeEntity {
16+
17+
@Id
18+
@GeneratedValue(strategy = GenerationType.IDENTITY)
19+
private Long id;
20+
21+
@ManyToOne(fetch = FetchType.LAZY)
22+
@JoinColumn(name = "user_id", nullable = false)
23+
private UserEntity userEntity;
24+
25+
@Enumerated(EnumType.STRING)
26+
@Column(name = "policy_type", nullable = false)
27+
private PolicyType policyType;
28+
29+
@Column(nullable = false)
30+
private Boolean agreed;
31+
32+
@Column(nullable = false)
33+
private Long revision;
34+
35+
private LocalDateTime agreedAt;
36+
37+
public UserPolicyAgreementEntity(UserEntity userEntity, PolicyType policyType, boolean agreed, Long revision) {
38+
this.userEntity = userEntity;
39+
this.policyType = policyType;
40+
this.agreed = agreed;
41+
this.revision = revision;
42+
this.agreedAt = agreed ? LocalDateTime.now() : null;
43+
}
44+
45+
public void updateAgreement(boolean agreed, Long revision) {
46+
this.agreed = agreed;
47+
this.revision = revision;
48+
this.agreedAt = agreed ? LocalDateTime.now() : null;
49+
}
50+
51+
public boolean needsUpdate(Long latestRevision) {
52+
return !agreed || revision < latestRevision;
53+
}
54+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package apptive.team5.user.dto;
2+
3+
import apptive.team5.user.domain.PolicyType;
4+
import jakarta.validation.constraints.NotNull;
5+
6+
public record AgreementItem(
7+
8+
@NotNull(message = "약관 종류는 필수입니다.")
9+
PolicyType policyType,
10+
11+
@NotNull(message = "동의 여부는 필수입니다.")
12+
Boolean agreed
13+
) {
14+
}
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
package apptive.team5.user.dto;
2+
3+
import apptive.team5.user.domain.ClientType;
4+
import jakarta.validation.constraints.NotNull;
5+
6+
public record InitSettingsRequest(
7+
8+
@NotNull(message = "클라이언트 타입은 필수입니다.")
9+
ClientType clientType,
10+
11+
@NotNull(message = "클라이언트 버전은 필수입니다.")
12+
String clientVersion
13+
) {
14+
}

0 commit comments

Comments
 (0)