Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,8 +1,13 @@
package org.sopt.app.application.platform;

import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.sopt.app.application.platform.dto.PlatformUserIdsRequest;
import org.sopt.app.application.platform.dto.PlatformUserInfoResponse;
import org.sopt.app.application.platform.dto.PlatformUserInfoWrapper;
Expand All @@ -13,9 +18,6 @@
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

@Slf4j
@Service
@RequiredArgsConstructor
Expand All @@ -39,7 +41,7 @@ public PlatformUserInfoResponse getPlatformUserInfoResponse(Long userId) {
final Map<String, String> headers = createAuthorizationHeader();
final Map<String, String> params = createQueryParams(Collections.singletonList(userId));
PlatformUserInfoWrapper platformUserInfoWrapper = platformClient.getPlatformUserInfo(headers, params);
List<PlatformUserInfoResponse> data= platformUserInfoWrapper.data();
List<PlatformUserInfoResponse> data = platformUserInfoWrapper.data();
if (data == null || data.isEmpty()) {
throw new BadRequestException(ErrorCode.PLATFORM_USER_NOT_EXISTS);
}
Expand All @@ -49,7 +51,7 @@ public PlatformUserInfoResponse getPlatformUserInfoResponse(Long userId) {
public List<PlatformUserInfoResponse> getPlatformUserInfosResponse(List<Long> userIds) {
final Map<String, String> headers = createAuthorizationHeader();

if(userIds == null || userIds.isEmpty()){
if (userIds == null || userIds.isEmpty()) {
return Collections.emptyList();
}

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

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

List<PlatformUserInfoResponse> data= platformUserInfoWrapper.data();
List<PlatformUserInfoResponse> data = platformUserInfoWrapper.data();
if (data == null || data.isEmpty()) {
throw new BadRequestException(ErrorCode.PLATFORM_USER_NOT_EXISTS);
}
Expand Down Expand Up @@ -98,11 +100,13 @@ public List<PlatformUserInfoResponse> getPlatformUserInfosResponseSmart(List<Lon
}

public UserStatus getStatus(Long userId) {
return Long.valueOf(getPlatformUserInfoResponse(userId).lastGeneration()).equals(currentGeneration) ? UserStatus.ACTIVE : UserStatus.INACTIVE;
return getStatus(getPlatformUserInfoResponse(userId));
}

private UserStatus getStatus(List<Long> generationList) {
return generationList.contains(currentGeneration) ? UserStatus.ACTIVE : UserStatus.INACTIVE;
public UserStatus getStatus(PlatformUserInfoResponse profile) {
return Long.valueOf(profile.getLastSoptGeneration()).equals(currentGeneration)
? UserStatus.ACTIVE
: UserStatus.INACTIVE;
}

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

public List<Long> getMemberGenerationList(Long userId) {
return getPlatformUserInfoResponse(userId)
.soptActivities().stream()
.map(PlatformUserInfoResponse.SoptActivities::generation)
.map(Integer::longValue)
.distinct()
.sorted(Comparator.reverseOrder())
.toList();
.soptActivities().stream()
.map(PlatformUserInfoResponse.SoptActivities::generation)
.map(Integer::longValue)
.distinct()
.sorted(Comparator.reverseOrder())
.toList();
}

public boolean isCurrentGeneration(Long generation) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ public record PlatformUserInfoResponse(
String birthday,
String phone,
String email,
int lastGeneration,
int lastGeneration, // 솝트 기수 기준으로 내려주긴 함.
List<SoptActivities> soptActivities
) {
public record SoptActivities(
int activityId,
int generation,
String part,
String team
String team,
Boolean isSopt
){
}

Expand All @@ -31,4 +32,26 @@ public SoptActivities getLatestActivity() {
.max(java.util.Comparator.comparingInt(SoptActivities::generation))
.orElse(null);
}

/**
* isSopt=true인 활동 중 가장 최신 기수의 SOPT 정규 활동을 반환.
* 반환값이 null이면 현재 기수에 솝트 활동이 없음.
*/
public SoptActivities getLatestSoptActivity() {
if (soptActivities == null) return null;
return soptActivities.stream()
.filter(a -> Boolean.TRUE.equals(a.isSopt()))
.max(java.util.Comparator.comparingInt(SoptActivities::generation))
.orElse(null);
}

public int getLastSoptGeneration(){
if (soptActivities == null || soptActivities.isEmpty()) return 0;

return soptActivities.stream()
.filter(SoptActivities::isSopt)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요 부분 위에선 '.filter(a -> Boolean.TRUE.equals(a.isSopt()))' 으로 처리되어있는데 이부분에선 안되어있어서 NPE가 날 수 있을 것같아 혹시 통일하는 방향은 어떠실까요..?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

좋은데요?? 바로 반영하겠습니다~~ 💯

.map(SoptActivities::generation)
.max(Integer::compareTo)
.orElse(0);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,9 @@
import static org.sopt.app.domain.entity.soptamp.SoptampUser.createNewSoptampUser;
import static org.sopt.app.domain.enums.SoptPart.findSoptPartByPartName;

import java.util.*;

import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
import org.sopt.app.application.platform.dto.PlatformUserInfoResponse;
import org.sopt.app.application.rank.CachedUserInfo;
import org.sopt.app.application.rank.RankCacheService;
Expand All @@ -20,8 +21,6 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import lombok.RequiredArgsConstructor;

@Service
@RequiredArgsConstructor
public class SoptampUserService {
Expand All @@ -47,7 +46,8 @@ public SoptampUserInfo editProfileMessage(Long userId, String profileMessage) {
SoptampUser soptampUser = soptampUserRepository.findByUserId(userId)
.orElseThrow(() -> new BadRequestException(ErrorCode.USER_NOT_FOUND));
soptampUser.updateProfileMessage(profileMessage);
rankCacheService.updateCachedUserInfo(soptampUser.getUserId(), CachedUserInfo.of(SoptampUserInfo.of(soptampUser)));
rankCacheService.updateCachedUserInfo(soptampUser.getUserId(),
CachedUserInfo.of(SoptampUserInfo.of(soptampUser)));
return SoptampUserInfo.of(soptampUser);
}

Expand All @@ -58,52 +58,61 @@ public SoptampUserInfo editProfileMessage(Long userId, String profileMessage) {
public void upsertSoptampUser(PlatformUserInfoResponse profile, Long userId) {
if (profile == null)
return;
var latest = profile.getLatestActivity();
if (latest == null)
return;

if (appjamMode) {
var latest = profile.getLatestActivity();
if (latest == null) {
return;
}
upsertSoptampUserForAppjam(profile, userId, latest);
} else {
upsertSoptampUserNormal(profile, userId, latest);
var latestSopt = profile.getLatestSoptActivity();
if (latestSopt == null) {
return;
}
upsertSoptampUserNormal(profile, userId, latestSopt);
}
}

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

// 기본 시즌용 upsert (파트 + 이름 기반 닉네임)
private void upsertSoptampUserNormal(PlatformUserInfoResponse profile, Long userId,
PlatformUserInfoResponse.SoptActivities latest) {
PlatformUserInfoResponse.SoptActivities latest) {
Optional<SoptampUser> user = soptampUserRepository.findByUserId(userId);
if (user.isEmpty()) {
this.createSoptampUserNormal(profile, userId, latest);
return;
}
SoptampUser registeredUser = user.get();
if(this.isGenerationChanged(registeredUser, (long)profile.lastGeneration())) {
if (this.isGenerationChanged(registeredUser, (long) profile.lastGeneration())) {
updateSoptampUserNormal(registeredUser, profile, latest);
}
}

private void updateSoptampUserNormal(SoptampUser registeredUser, PlatformUserInfoResponse profile, PlatformUserInfoResponse.SoptActivities latest){
private void updateSoptampUserNormal(SoptampUser registeredUser, PlatformUserInfoResponse profile,
PlatformUserInfoResponse.SoptActivities latest) {
Long userId = registeredUser.getUserId();
String part = latest.part() == null ? "미상" : latest.part();
String newNickname = generatePartBasedUniqueNickname(profile.name(), part, userId);

registeredUser.initTotalPoints();
registeredUser.updateChangedGenerationInfo(
(long)profile.lastGeneration(),
findSoptPartByPartName(part),
(long) profile.lastGeneration(),
findSoptPartByPartName(part),
newNickname
);
rankCacheService.removeRank(userId);
rankCacheService.createNewRank(userId);
}

private void createSoptampUserNormal(PlatformUserInfoResponse profile, Long userId, PlatformUserInfoResponse.SoptActivities latest) {
String part = latest.part() == null ? "미상" : latest.part();
private void createSoptampUserNormal(PlatformUserInfoResponse profile, Long userId,
PlatformUserInfoResponse.SoptActivities latestSopt
) {
String part = latestSopt.part() == null ? "미상" : latestSopt.part();
String uniqueNickname = generatePartBasedUniqueNickname(profile.name(), part, null);
SoptampUser newSoptampUser = createNewSoptampUser(userId, uniqueNickname, (long)profile.lastGeneration(), findSoptPartByPartName(part));
SoptampUser newSoptampUser = createNewSoptampUser(userId, uniqueNickname, (long) profile.lastGeneration(),
findSoptPartByPartName(part));
soptampUserRepository.save(newSoptampUser);
rankCacheService.createNewRank(userId);
}
Expand All @@ -115,8 +124,8 @@ private boolean isGenerationChanged(SoptampUser registeredUser, Long profileGene
// ==================== 앱잼 시즌용 upsert ====================

private void upsertSoptampUserForAppjam(PlatformUserInfoResponse profile,
Long userId,
PlatformUserInfoResponse.SoptActivities latest) {
Long userId,
PlatformUserInfoResponse.SoptActivities latest) {
Optional<SoptampUser> userOpt = soptampUserRepository.findByUserId(userId);

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

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

// 앱잼 시즌: 파트, Makers 무관 buildAppjamBaseNickname이 자연스럽게 처리
registeredUser.updateChangedGenerationInfo(
(long) profile.lastGeneration(),
findSoptPartByPartName(part),
uniqueNickname
);
(long) profile.lastGeneration(),
findSoptPartByPartName(part),
uniqueNickname
);

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

private void createSoptampUserAppjam(PlatformUserInfoResponse profile,
Long userId,
PlatformUserInfoResponse.SoptActivities latest) {
Long userId,
PlatformUserInfoResponse.SoptActivities latest) {

String baseNickname = buildAppjamBaseNickname(profile, userId);

// 새 유저: 전체에서 중복 검사
String uniqueNickname = generateUniqueNicknameInternal(
baseNickname,
null
);
baseNickname,
null
);

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

SoptampUser newSoptampUser = createNewSoptampUser(
userId,
uniqueNickname,
(long) profile.lastGeneration(),
findSoptPartByPartName(part)
);
userId,
uniqueNickname,
(long) profile.lastGeneration(),
findSoptPartByPartName(part));
newSoptampUser.initTotalPoints(); // 새 시즌이니 0점부터

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

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

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

/**
* baseNickname을 기준으로, 전역 유니크 닉네임 생성
* - currentUserIdOrNull == null : 새 유저 생성 (그냥 existsByNickname)
* - currentUserIdOrNull == null : 새 유저 생성 (그냥 existsByNickname)
* - currentUserIdOrNull != null : 내 row는 제외하고 중복 체크
*/
private String generateUniqueNicknameInternal(String baseNickname, Long currentUserIdOrNull) {
Expand Down
26 changes: 26 additions & 0 deletions src/main/java/org/sopt/app/domain/enums/SoptPart.java
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package org.sopt.app.domain.enums;

import java.util.Arrays;
import java.util.EnumSet;
import java.util.Set;
import lombok.AllArgsConstructor;
import lombok.Getter;

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

NONE("미상", "선배"),

/**
* Sopt Makers Chapter
*/
PM("PM", "PM"),
FRONTEND("프론트엔드", "FE"),
BACKEND("백엔드", "BE"),
MARKETER("마케터", "마케터"),
RESEARCHER("리서처", "리서처"),
ORGANIZER("오거나이저", "오거나이저"),
CX("CX", "CX"),
;

final String partName;
final String shortedPartName;

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

private static final Set<SoptPart> SOPT_PARTS = EnumSet.of(
PLAN, PLAN_PART_LEADER,
DESIGN, DESIGN_PART_LEADER,
ANDROID, ANDROID_PART_LEADER,
IOS, IOS_PART_LEADER,
WEB, WEB_PART_LEADER,
SERVER, SERVER_PART_LEADER);

public boolean isSoptPart() {
return SOPT_PARTS.contains(this);
}

public static Part toPart(SoptPart soptPart) {
return switch (soptPart) {
case PLAN, PLAN_PART_LEADER -> Part.PLAN;
Expand Down
3 changes: 1 addition & 2 deletions src/main/java/org/sopt/app/facade/HomeFacade.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,10 +68,9 @@ public List<AppServiceEntryStatusResponse> checkAppServiceEntryStatus(Long userI
if(userId == null){
return this.getOnlyAppServiceInfo();
}
UserStatus status = platformService.getStatus(userId);

// TODO : 추후 유저 생성 api response 변경해 생성 api 쪽에서 soptamp user upsert 하도록 변경
PlatformUserInfoResponse platformUserInfo = platformService.getPlatformUserInfoResponse(userId);
UserStatus status = platformService.getStatus(platformUserInfo);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

오호 platformUserInfo를 한 번 조회한 뒤 status 계산에 재사용하도록 바뀐 걸까요? 어떤 변화였을지 궁금합니다! 우선 좋아보여요,,,, 😍

Copy link
Member Author

@jher235 jher235 Mar 11, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

맞아요! 기존에

  1. getStatus 메서드가 userId를 받아서 유저를 조회한 뒤 UserStatus를 반환함.
  2. 이후 getPlatformUserInfoResponse(userId) 로 유저 정보를 다시 가져와서 사용함

이런 흐름이어서

  1. getStatus 메서드를 PlatformUserInfoResponse 를 파라미터로 받는 형태와
  2. userId로 받았을 경우 PlatformUserInfoResponse 를 조회해서 내부적으로 getStatus를 호출하는 경우

로 분리했어요. 미세한 오버헤드가 있을 수 있지만, 기존 동작을 잘 유지하고 현재 상황에서 효율적이라고 생각했습니다~


List<AppServiceEntryStatusResponse> appServiceEntryStatusResponses = appServiceService.getAllAppService().stream()
.filter(appServiceInfo -> isServiceVisibleToUser(appServiceInfo, status))
Expand Down
Loading
Loading