Skip to content

Commit a4c0e04

Browse files
authored
[Feat] #692 [FEAT] SOPT Makers Chapter 추가에 따른 SoptPart 수정 및 SoptampUser 생성 로직 보완 (#695)
2 parents 605d5d9 + baf672e commit a4c0e04

File tree

7 files changed

+240
-84
lines changed

7 files changed

+240
-84
lines changed

src/main/java/org/sopt/app/application/platform/PlatformService.java

Lines changed: 20 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,13 @@
11
package org.sopt.app.application.platform;
22

3+
import java.util.Collections;
4+
import java.util.Comparator;
5+
import java.util.HashMap;
6+
import java.util.List;
7+
import java.util.Map;
8+
import java.util.stream.Collectors;
39
import lombok.RequiredArgsConstructor;
410
import lombok.extern.slf4j.Slf4j;
5-
611
import org.sopt.app.application.platform.dto.PlatformUserIdsRequest;
712
import org.sopt.app.application.platform.dto.PlatformUserInfoResponse;
813
import org.sopt.app.application.platform.dto.PlatformUserInfoWrapper;
@@ -13,9 +18,6 @@
1318
import org.springframework.beans.factory.annotation.Value;
1419
import org.springframework.stereotype.Service;
1520

16-
import java.util.*;
17-
import java.util.stream.Collectors;
18-
1921
@Slf4j
2022
@Service
2123
@RequiredArgsConstructor
@@ -39,7 +41,7 @@ public PlatformUserInfoResponse getPlatformUserInfoResponse(Long userId) {
3941
final Map<String, String> headers = createAuthorizationHeader();
4042
final Map<String, String> params = createQueryParams(Collections.singletonList(userId));
4143
PlatformUserInfoWrapper platformUserInfoWrapper = platformClient.getPlatformUserInfo(headers, params);
42-
List<PlatformUserInfoResponse> data= platformUserInfoWrapper.data();
44+
List<PlatformUserInfoResponse> data = platformUserInfoWrapper.data();
4345
if (data == null || data.isEmpty()) {
4446
throw new BadRequestException(ErrorCode.PLATFORM_USER_NOT_EXISTS);
4547
}
@@ -49,7 +51,7 @@ public PlatformUserInfoResponse getPlatformUserInfoResponse(Long userId) {
4951
public List<PlatformUserInfoResponse> getPlatformUserInfosResponse(List<Long> userIds) {
5052
final Map<String, String> headers = createAuthorizationHeader();
5153

52-
if(userIds == null || userIds.isEmpty()){
54+
if (userIds == null || userIds.isEmpty()) {
5355
return Collections.emptyList();
5456
}
5557

@@ -59,7 +61,7 @@ public List<PlatformUserInfoResponse> getPlatformUserInfosResponse(List<Long> us
5961

6062
PlatformUserInfoWrapper platformUserInfoWrapper = platformClient.getPlatformUserInfo(headers, params);
6163

62-
List<PlatformUserInfoResponse> data= platformUserInfoWrapper.data();
64+
List<PlatformUserInfoResponse> data = platformUserInfoWrapper.data();
6365
if (data == null || data.isEmpty()) {
6466
throw new BadRequestException(ErrorCode.PLATFORM_USER_NOT_EXISTS);
6567
}
@@ -98,11 +100,13 @@ public List<PlatformUserInfoResponse> getPlatformUserInfosResponseSmart(List<Lon
98100
}
99101

100102
public UserStatus getStatus(Long userId) {
101-
return Long.valueOf(getPlatformUserInfoResponse(userId).lastGeneration()).equals(currentGeneration) ? UserStatus.ACTIVE : UserStatus.INACTIVE;
103+
return getStatus(getPlatformUserInfoResponse(userId));
102104
}
103105

104-
private UserStatus getStatus(List<Long> generationList) {
105-
return generationList.contains(currentGeneration) ? UserStatus.ACTIVE : UserStatus.INACTIVE;
106+
public UserStatus getStatus(PlatformUserInfoResponse profile) {
107+
return Long.valueOf(profile.getLastSoptGeneration()).equals(currentGeneration)
108+
? UserStatus.ACTIVE
109+
: UserStatus.INACTIVE;
106110
}
107111

108112
private Map<String, String> createAuthorizationHeader() {
@@ -126,12 +130,12 @@ private String toCsv(List<Long> userIds) {
126130

127131
public List<Long> getMemberGenerationList(Long userId) {
128132
return getPlatformUserInfoResponse(userId)
129-
.soptActivities().stream()
130-
.map(PlatformUserInfoResponse.SoptActivities::generation)
131-
.map(Integer::longValue)
132-
.distinct()
133-
.sorted(Comparator.reverseOrder())
134-
.toList();
133+
.soptActivities().stream()
134+
.map(PlatformUserInfoResponse.SoptActivities::generation)
135+
.map(Integer::longValue)
136+
.distinct()
137+
.sorted(Comparator.reverseOrder())
138+
.toList();
135139
}
136140

137141
public boolean isCurrentGeneration(Long generation) {

src/main/java/org/sopt/app/application/platform/dto/PlatformUserInfoResponse.java

Lines changed: 30 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package org.sopt.app.application.platform.dto;
22

33

4+
import java.util.Comparator;
45
import java.util.List;
56

67
public record PlatformUserInfoResponse(
@@ -10,15 +11,19 @@ public record PlatformUserInfoResponse(
1011
String birthday,
1112
String phone,
1213
String email,
13-
int lastGeneration,
14+
int lastGeneration, // 솝트 기수 기준으로 내려주긴 함.
1415
List<SoptActivities> soptActivities
1516
) {
1617
public record SoptActivities(
1718
int activityId,
1819
int generation,
1920
String part,
20-
String team
21+
String team,
22+
Boolean isSopt
2123
){
24+
public boolean isSoptActivity() {
25+
return Boolean.TRUE.equals(this.isSopt);
26+
}
2227
}
2328

2429
/**
@@ -28,7 +33,29 @@ public record SoptActivities(
2833
public SoptActivities getLatestActivity() {
2934
if (soptActivities == null) return null;
3035
return soptActivities.stream()
31-
.max(java.util.Comparator.comparingInt(SoptActivities::generation))
36+
.max(Comparator.comparingInt(SoptActivities::generation))
3237
.orElse(null);
3338
}
39+
40+
/**
41+
* isSopt=true인 활동 중 가장 최신 기수의 SOPT 정규 활동을 반환.
42+
* 반환값이 null이면 현재 기수에 솝트 활동이 없음.
43+
*/
44+
public SoptActivities getLatestSoptActivity() {
45+
if (soptActivities == null) return null;
46+
return soptActivities.stream()
47+
.filter(SoptActivities::isSoptActivity)
48+
.max(Comparator.comparingInt(SoptActivities::generation))
49+
.orElse(null);
50+
}
51+
52+
public int getLastSoptGeneration(){
53+
if (soptActivities == null || soptActivities.isEmpty()) return 0;
54+
55+
return soptActivities.stream()
56+
.filter(SoptActivities::isSoptActivity)
57+
.map(SoptActivities::generation)
58+
.max(Integer::compareTo)
59+
.orElse(0);
60+
}
3461
}

src/main/java/org/sopt/app/application/soptamp/SoptampUserService.java

Lines changed: 48 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@
33
import static org.sopt.app.domain.entity.soptamp.SoptampUser.createNewSoptampUser;
44
import static org.sopt.app.domain.enums.SoptPart.findSoptPartByPartName;
55

6-
import java.util.*;
7-
6+
import java.util.List;
7+
import java.util.Optional;
8+
import lombok.RequiredArgsConstructor;
89
import org.sopt.app.application.platform.dto.PlatformUserInfoResponse;
910
import org.sopt.app.application.rank.CachedUserInfo;
1011
import org.sopt.app.application.rank.RankCacheService;
@@ -20,8 +21,6 @@
2021
import org.springframework.stereotype.Service;
2122
import org.springframework.transaction.annotation.Transactional;
2223

23-
import lombok.RequiredArgsConstructor;
24-
2524
@Service
2625
@RequiredArgsConstructor
2726
public class SoptampUserService {
@@ -47,7 +46,8 @@ public SoptampUserInfo editProfileMessage(Long userId, String profileMessage) {
4746
SoptampUser soptampUser = soptampUserRepository.findByUserId(userId)
4847
.orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND));
4948
soptampUser.updateProfileMessage(profileMessage);
50-
rankCacheService.updateCachedUserInfo(soptampUser.getUserId(), CachedUserInfo.of(SoptampUserInfo.of(soptampUser)));
49+
rankCacheService.updateCachedUserInfo(soptampUser.getUserId(),
50+
CachedUserInfo.of(SoptampUserInfo.of(soptampUser)));
5151
return SoptampUserInfo.of(soptampUser);
5252
}
5353

@@ -58,52 +58,61 @@ public SoptampUserInfo editProfileMessage(Long userId, String profileMessage) {
5858
public void upsertSoptampUser(PlatformUserInfoResponse profile, Long userId) {
5959
if (profile == null)
6060
return;
61-
var latest = profile.getLatestActivity();
62-
if (latest == null)
63-
return;
6461

6562
if (appjamMode) {
63+
var latest = profile.getLatestActivity();
64+
if (latest == null) {
65+
return;
66+
}
6667
upsertSoptampUserForAppjam(profile, userId, latest);
6768
} else {
68-
upsertSoptampUserNormal(profile, userId, latest);
69+
var latestSopt = profile.getLatestSoptActivity();
70+
if (latestSopt == null) {
71+
return;
72+
}
73+
upsertSoptampUserNormal(profile, userId, latestSopt);
6974
}
7075
}
7176

7277
/* ==================== NORMAL 시즌용 upsert ==================== */
7378

7479
// 기본 시즌용 upsert (파트 + 이름 기반 닉네임)
7580
private void upsertSoptampUserNormal(PlatformUserInfoResponse profile, Long userId,
76-
PlatformUserInfoResponse.SoptActivities latest) {
81+
PlatformUserInfoResponse.SoptActivities latest) {
7782
Optional<SoptampUser> user = soptampUserRepository.findByUserId(userId);
7883
if (user.isEmpty()) {
7984
this.createSoptampUserNormal(profile, userId, latest);
8085
return;
8186
}
8287
SoptampUser registeredUser = user.get();
83-
if(this.isGenerationChanged(registeredUser, (long)profile.lastGeneration())) {
88+
if (this.isGenerationChanged(registeredUser, (long) profile.lastGeneration())) {
8489
updateSoptampUserNormal(registeredUser, profile, latest);
8590
}
8691
}
8792

88-
private void updateSoptampUserNormal(SoptampUser registeredUser, PlatformUserInfoResponse profile, PlatformUserInfoResponse.SoptActivities latest){
93+
private void updateSoptampUserNormal(SoptampUser registeredUser, PlatformUserInfoResponse profile,
94+
PlatformUserInfoResponse.SoptActivities latest) {
8995
Long userId = registeredUser.getUserId();
9096
String part = latest.part() == null ? "미상" : latest.part();
9197
String newNickname = generatePartBasedUniqueNickname(profile.name(), part, userId);
9298

9399
registeredUser.initTotalPoints();
94100
registeredUser.updateChangedGenerationInfo(
95-
(long)profile.lastGeneration(),
96-
findSoptPartByPartName(part),
101+
(long) profile.lastGeneration(),
102+
findSoptPartByPartName(part),
97103
newNickname
98104
);
99105
rankCacheService.removeRank(userId);
100106
rankCacheService.createNewRank(userId);
101107
}
102108

103-
private void createSoptampUserNormal(PlatformUserInfoResponse profile, Long userId, PlatformUserInfoResponse.SoptActivities latest) {
104-
String part = latest.part() == null ? "미상" : latest.part();
109+
private void createSoptampUserNormal(PlatformUserInfoResponse profile, Long userId,
110+
PlatformUserInfoResponse.SoptActivities latestSopt
111+
) {
112+
String part = latestSopt.part() == null ? "미상" : latestSopt.part();
105113
String uniqueNickname = generatePartBasedUniqueNickname(profile.name(), part, null);
106-
SoptampUser newSoptampUser = createNewSoptampUser(userId, uniqueNickname, (long)profile.lastGeneration(), findSoptPartByPartName(part));
114+
SoptampUser newSoptampUser = createNewSoptampUser(userId, uniqueNickname, (long) profile.lastGeneration(),
115+
findSoptPartByPartName(part));
107116
soptampUserRepository.save(newSoptampUser);
108117
rankCacheService.createNewRank(userId);
109118
}
@@ -115,8 +124,8 @@ private boolean isGenerationChanged(SoptampUser registeredUser, Long profileGene
115124
// ==================== 앱잼 시즌용 upsert ====================
116125

117126
private void upsertSoptampUserForAppjam(PlatformUserInfoResponse profile,
118-
Long userId,
119-
PlatformUserInfoResponse.SoptActivities latest) {
127+
Long userId,
128+
PlatformUserInfoResponse.SoptActivities latest) {
120129
Optional<SoptampUser> userOpt = soptampUserRepository.findByUserId(userId);
121130

122131
if (userOpt.isEmpty()) {
@@ -137,37 +146,36 @@ private void upsertSoptampUserForAppjam(PlatformUserInfoResponse profile,
137146
String uniqueNickname = generateUniqueNicknameInternal(baseNickname, userId);
138147

139148
String part = latest.part() == null ? "미상" : latest.part();
140-
149+
// 앱잼 시즌: 파트, Makers 무관 buildAppjamBaseNickname이 자연스럽게 처리
141150
registeredUser.updateChangedGenerationInfo(
142-
(long) profile.lastGeneration(),
143-
findSoptPartByPartName(part),
144-
uniqueNickname
145-
);
151+
(long) profile.lastGeneration(),
152+
findSoptPartByPartName(part),
153+
uniqueNickname
154+
);
146155

147156
// 앱잼 변환 시점에 한 번 포인트 초기화
148157
registeredUser.initTotalPoints();
149158
}
150159

151160
private void createSoptampUserAppjam(PlatformUserInfoResponse profile,
152-
Long userId,
153-
PlatformUserInfoResponse.SoptActivities latest) {
161+
Long userId,
162+
PlatformUserInfoResponse.SoptActivities latest) {
154163

155164
String baseNickname = buildAppjamBaseNickname(profile, userId);
156165

157166
// 새 유저: 전체에서 중복 검사
158167
String uniqueNickname = generateUniqueNicknameInternal(
159-
baseNickname,
160-
null
161-
);
168+
baseNickname,
169+
null
170+
);
162171

163172
String part = latest.part() == null ? "미상" : latest.part();
164173

165174
SoptampUser newSoptampUser = createNewSoptampUser(
166-
userId,
167-
uniqueNickname,
168-
(long) profile.lastGeneration(),
169-
findSoptPartByPartName(part)
170-
);
175+
userId,
176+
uniqueNickname,
177+
(long) profile.lastGeneration(),
178+
findSoptPartByPartName(part));
171179
newSoptampUser.initTotalPoints(); // 새 시즌이니 0점부터
172180

173181
soptampUserRepository.save(newSoptampUser);
@@ -180,8 +188,10 @@ private boolean needsAppjamNicknameMigration(SoptampUser user) {
180188
return true;
181189
}
182190

183-
// SoptPart 기준으로 "서버", "기획" 같은 축약/프리픽스를 모두 검사
191+
// SoptPart 기준으로 "서버", "기획" 같은 축약/프리픽스를 모두 검사 (SOPT 파트만)
184192
for (SoptPart part : SoptPart.values()) {
193+
if (!part.isSoptPart())
194+
continue;
185195
String prefix = part.getShortedPartName();
186196
if (nickname.startsWith(prefix)) {
187197
// 서버김솝트, 디자인김솝트 등 → 기존 시즌(파트 기반) 닉네임이므로 앱잼 변환 필요
@@ -200,8 +210,8 @@ private boolean needsAppjamNicknameMigration(SoptampUser user) {
200210
*/
201211
private String buildAppjamBaseNickname(PlatformUserInfoResponse profile, Long userId) {
202212
return appjamUserRepository.findByUserId(userId)
203-
.map(appjamUser -> appjamUser.getTeamName() + profile.name())
204-
.orElseGet(() -> profile.lastGeneration() + "기" + profile.name());
213+
.map(appjamUser -> appjamUser.getTeamName() + profile.name())
214+
.orElseGet(() -> profile.lastGeneration() + "기" + profile.name());
205215
}
206216

207217
// ==================== 닉네임 유니크 로직 공통부 ====================
@@ -218,7 +228,7 @@ private String generatePartBasedUniqueNickname(String name, String part, Long cu
218228

219229
/**
220230
* baseNickname을 기준으로, 전역 유니크 닉네임 생성
221-
* - currentUserIdOrNull == null : 새 유저 생성 (그냥 existsByNickname)
231+
* - currentUserIdOrNull == null : 새 유저 생성 (그냥 existsByNickname)
222232
* - currentUserIdOrNull != null : 내 row는 제외하고 중복 체크
223233
*/
224234
private String generateUniqueNicknameInternal(String baseNickname, Long currentUserIdOrNull) {

src/main/java/org/sopt/app/domain/enums/SoptPart.java

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
package org.sopt.app.domain.enums;
22

33
import java.util.Arrays;
4+
import java.util.EnumSet;
5+
import java.util.Set;
46
import lombok.AllArgsConstructor;
57
import lombok.Getter;
68

@@ -29,7 +31,19 @@ public enum SoptPart {
2931
// 파트장이 솝탬프 파트별 랭킹에 관여할 수 있으려면 각 파트의 shortedPartName이 접두사로 필요하다
3032

3133
NONE("미상", "선배"),
34+
35+
/**
36+
* Sopt Makers Chapter
37+
*/
38+
PM("PM", "PM"),
39+
FRONTEND("프론트엔드", "FE"),
40+
BACKEND("백엔드", "BE"),
41+
MARKETER("마케터", "마케터"),
42+
RESEARCHER("리서처", "리서처"),
43+
ORGANIZER("오거나이저", "오거나이저"),
44+
CX("CX", "CX"),
3245
;
46+
3347
final String partName;
3448
final String shortedPartName;
3549

@@ -40,6 +54,18 @@ public static SoptPart findSoptPartByPartName(String partName) {
4054
.orElse(SoptPart.NONE);
4155
}
4256

57+
private static final Set<SoptPart> SOPT_PARTS = EnumSet.of(
58+
PLAN, PLAN_PART_LEADER,
59+
DESIGN, DESIGN_PART_LEADER,
60+
ANDROID, ANDROID_PART_LEADER,
61+
IOS, IOS_PART_LEADER,
62+
WEB, WEB_PART_LEADER,
63+
SERVER, SERVER_PART_LEADER);
64+
65+
public boolean isSoptPart() {
66+
return SOPT_PARTS.contains(this);
67+
}
68+
4369
public static Part toPart(SoptPart soptPart) {
4470
return switch (soptPart) {
4571
case PLAN, PLAN_PART_LEADER -> Part.PLAN;

0 commit comments

Comments
 (0)