Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
@@ -0,0 +1,28 @@
package org.sopt.makers.internal.member.constants;

import java.util.List;
import java.util.Map;
import lombok.Getter;
import org.sopt.makers.internal.member.domain.enums.Part;

public class AskMemberId {
@Getter
private static final Map<Part, List<Long>> askMembersByPart = Map.of(
Part.SERVER, List.of(929L,209L),
Part.IOS, List.of(192L),
Part.ANDROID, List.of(223L),
Part.WEB, List.of(945L),
Part.DESIGN, List.of(930L),
Part.PLAN, List.of(229L)
);
Comment on lines +9 to +17
Copy link
Contributor

Choose a reason for hiding this comment

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

DEV기준 유저 아이디로 되어 있는 것 같은데 혹시 PROD환경에는 코드가 어떻게 올라가나요?
PROD환경의 유저 아이디가 데브에 없는 경우가 많아서 데브 서버에 추후 에러가 날 수도 있을 것 같습니다

Copy link
Member Author

Choose a reason for hiding this comment

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

Dev 에서 테스트 후, 명단 확정되면 프로덕션 배포할 때 반영할 예정입니다!


public static List<Long> getAskMembersByPart(Part part) {
return askMembersByPart.getOrDefault(part, List.of());
}

public static List<Long> getAllAskMembers() {
return askMembersByPart.values().stream()
.flatMap(List::stream)
.toList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@
import org.sopt.makers.internal.member.dto.response.MemberProfileSpecificResponse;
import org.sopt.makers.internal.member.dto.response.MemberPropertiesResponse;
import org.sopt.makers.internal.member.dto.response.MemberResponse;
import org.sopt.makers.internal.member.dto.response.AskMemberResponse;
import org.sopt.makers.internal.member.dto.response.WorkPreferenceRecommendationResponse;
import org.sopt.makers.internal.member.dto.response.WorkPreferenceResponse;
import org.sopt.makers.internal.member.dto.response.TlMemberResponse;
Expand Down Expand Up @@ -102,6 +103,20 @@ public ResponseEntity<List<TlMemberResponse>> getTlMembers(
return ResponseEntity.status(HttpStatus.OK).body(responses);
}

@Operation(summary = "질문 대상 멤버 조회 API", description = """
질문을 받을 수 있는 대상 멤버들을 파트별로 조회합니다.
part 파라미터가 없으면 모든 파트의 멤버를 반환합니다.
part 파라미터 옵션: 서버, SERVER, iOS, 안드로이드, ANDROID, 웹, WEB, 디자인, DESIGN, 기획, PLAN
각 파트별로 하드코딩된 멤버를 반환합니다.
""")
@GetMapping("/ask/list")
public ResponseEntity<AskMemberResponse> getAskMembers(
@RequestParam(required = false, name = "part") String part
) {
AskMemberResponse response = memberService.getAskMembers(part);
return ResponseEntity.status(HttpStatus.OK).body(response);
}


// 프론트 연결 되면 삭제 예정
@Deprecated
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
import java.io.Serializable;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
Copy link
Contributor

Choose a reason for hiding this comment

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

해당 코드 변경의 이유가 있을까요?

Copy link
Member Author

Choose a reason for hiding this comment

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

이 부분 관련해서, 조회 시에 API 오류가 나서 수정하였습니다!

@NoArgsConstructor
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Builder
public class WorkPreference implements Serializable {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package org.sopt.makers.internal.member.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import java.util.List;

@Schema(description = "질문 대상 멤버 응답 DTO")
public record
AskMemberResponse(
@Schema(description = "질문 대상 멤버 목록")
List<QuestionTargetMember> members
) {
@Schema(description = "질문 대상 멤버 정보")
public record QuestionTargetMember(
@Schema(description = "멤버 ID", required = true)
Long id,

@Schema(description = "멤버 이름", required = true)
String name,

@Schema(description = "프로필 이미지 URL")
String profileImage,

@Schema(description = "소개")
String introduction,

@Schema(description = "최근 활동 정보", required = true)
MemberSoptActivityResponse latestActivity,

@Schema(description = "현재 커리어")
MemberCareerResponse currentCareer,

@Schema(description = "직전 커리어")
MemberCareerResponse previousCareer,

@Schema(description = "답변 보장 여부", example = "true", required = true)
Boolean isAnswerGuaranteed
) {}

public record MemberSoptActivityResponse(
Integer generation,
String part,
String team
) {}

public record MemberCareerResponse(
String companyName,
String title
) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import org.sopt.makers.internal.external.slack.SlackClient;
import org.sopt.makers.internal.external.slack.SlackMessageUtil;
import org.sopt.makers.internal.member.constants.AppJamObMemberId;
import org.sopt.makers.internal.member.constants.AskMemberId;
import org.sopt.makers.internal.member.constants.MakersMemberId;
import org.sopt.makers.internal.member.domain.Member;
import org.sopt.makers.internal.member.domain.MemberBlock;
Expand All @@ -40,6 +41,7 @@
import org.sopt.makers.internal.member.domain.WorkPreference;
import org.sopt.makers.internal.member.domain.enums.ActivityTeam;
import org.sopt.makers.internal.member.domain.enums.OrderByCondition;
import org.sopt.makers.internal.member.domain.enums.Part;
import org.sopt.makers.internal.member.domain.enums.ServiceType;
import org.sopt.makers.internal.member.dto.ActivityVo;
import org.sopt.makers.internal.member.dto.MemberProfileProjectDao;
Expand All @@ -58,6 +60,7 @@
import org.sopt.makers.internal.member.dto.response.MemberPropertiesResponse;
import org.sopt.makers.internal.member.dto.response.MemberResponse;
import org.sopt.makers.internal.member.dto.response.MemberSoptActivityResponse;
import org.sopt.makers.internal.member.dto.response.AskMemberResponse;
import org.sopt.makers.internal.member.dto.response.WorkPreferenceRecommendationResponse;
import org.sopt.makers.internal.member.dto.response.WorkPreferenceResponse;
import org.sopt.makers.internal.member.dto.response.TlMemberResponse;
Expand Down Expand Up @@ -1048,4 +1051,128 @@ private TlMemberResponse buildTlMemberResponse(
);
}

@Transactional(readOnly = true)
public AskMemberResponse getAskMembers(String partName) {
List<AskMemberResponse.QuestionTargetMember> targetMembers = new ArrayList<>();

Part part = convertToPart(partName);

// 특정 파트가 지정된 경우 해당 파트만, 없으면 모든 파트
List<Long> memberIds;
if (part != null) {
memberIds = AskMemberId.getAskMembersByPart(part);
} else {
memberIds = AskMemberId.getAllAskMembers();
}

for (Long memberId : memberIds) {
try {
Member member = memberRepository.findById(memberId).orElse(null);
if (member == null || !member.getHasProfile()) {
continue;
}

InternalUserDetails userDetails = platformService.getInternalUser(memberId);

// 최근 활동 정보 가져오기
SoptActivity latestActivity = userDetails.soptActivities().stream()
.max((a1, a2) -> Integer.compare(a1.generation(), a2.generation()))
.orElse(null);

if (latestActivity == null) {
continue;
}

// 커리어 정보 처리
AskMemberResponse.MemberCareerResponse currentCareer = null;
AskMemberResponse.MemberCareerResponse previousCareer = null;

List<MemberCareer> careers = member.getCareers();
if (careers != null && !careers.isEmpty()) {
// 현재 재직중인 커리어 찾기
Optional<MemberCareer> currentCareerOpt = careers.stream()
.filter(career -> career.getIsCurrent() != null && career.getIsCurrent())
.findFirst();

if (currentCareerOpt.isPresent()) {
MemberCareer current = currentCareerOpt.get();
currentCareer = new AskMemberResponse.MemberCareerResponse(
current.getCompanyName(),
current.getTitle()
);
}

// 직전 커리어 찾기 (isCurrent가 false인 것 중 가장 최근)
List<MemberCareer> pastCareers = careers.stream()
.filter(career -> career.getIsCurrent() == null || !career.getIsCurrent())
.filter(career -> career.getEndDate() != null)
.toList();

if (!pastCareers.isEmpty()) {
// endDate로 정렬하여 가장 최근 것 선택
MemberCareer mostRecentPast = pastCareers.stream()
.max((c1, c2) -> {
try {
val formatter = DateTimeFormatter.ofPattern("yyyy-MM");
val end1 = YearMonth.parse(c1.getEndDate(), formatter);
val end2 = YearMonth.parse(c2.getEndDate(), formatter);
return end1.compareTo(end2);
} catch (Exception e) {
return 0;
}
})
.orElse(null);

previousCareer = new AskMemberResponse.MemberCareerResponse(
mostRecentPast.getCompanyName(),
mostRecentPast.getTitle()
);
}
}

AskMemberResponse.MemberSoptActivityResponse activityResponse =
new AskMemberResponse.MemberSoptActivityResponse(
latestActivity.generation(),
latestActivity.part(),
latestActivity.team()
);

AskMemberResponse.QuestionTargetMember targetMember =
new AskMemberResponse.QuestionTargetMember(
memberId,
userDetails.name(),
userDetails.profileImage(),
member.getIntroduction(),
activityResponse,
currentCareer,
previousCareer,
true // 답변보장 항상 true
);

targetMembers.add(targetMember);
} catch (Exception e) {
log.error("Failed to process member ID: " + memberId, e);
continue;
}
}

return new AskMemberResponse(targetMembers);
}

private Part convertToPart(String partName) {
if (partName == null || partName.isBlank()) {
return null;
}

return switch (partName.toUpperCase()) {
case "서버", "SERVER" -> Part.SERVER;
case "IOS" -> Part.IOS;
case "안드로이드", "ANDROID" -> Part.ANDROID;
case "웹", "WEB" -> Part.WEB;
case "디자인", "DESIGN" -> Part.DESIGN;
case "기획", "PLAN" -> Part.PLAN;
default -> null;
};
}

}