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
@@ -1,37 +1,29 @@
package org.sopt.kareer.domain.member.controller;


import static org.sopt.kareer.global.config.swagger.SwaggerResponseDescription.*;

import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.*;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.sopt.kareer.domain.member.dto.request.MemberOnboardRequest;
import org.sopt.kareer.domain.member.dto.request.MemberTermsRequest;
import org.sopt.kareer.domain.member.dto.request.MypageRequest;
import org.sopt.kareer.domain.member.dto.response.*;
import org.sopt.kareer.domain.member.entity.constants.Field;
import org.sopt.kareer.domain.member.entity.constants.Major;
import org.sopt.kareer.domain.member.entity.constants.University;
import org.sopt.kareer.domain.member.entity.enums.Country;
import org.sopt.kareer.domain.member.service.MemberService;
import org.sopt.kareer.domain.member.service.*;
import org.sopt.kareer.domain.roadmap.dto.response.RoadmapTestResponse;
import org.sopt.kareer.domain.roadmap.service.RoadMapService;
import org.sopt.kareer.domain.roadmap.service.RoadmapAsyncService;
import org.sopt.kareer.domain.roadmap.service.*;
import org.sopt.kareer.global.annotation.CustomExceptionDescription;
import org.sopt.kareer.global.auth.service.AuthService;
import org.sopt.kareer.global.config.swagger.SwaggerResponseDescription;
import org.sopt.kareer.global.response.BaseResponse;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.http.*;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import static org.sopt.kareer.global.config.swagger.SwaggerResponseDescription.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/members")
Expand All @@ -42,6 +34,7 @@ public class MemberController {
private final RoadMapService roadMapService;
private final RoadmapAsyncService roadmapAsyncService;
private final AuthService authService;
private final LocalizedOnboardQueryService localizedOnboardQueryService;

@GetMapping("/me")
@Operation(summary = "회원 정보 조회", description = "로그인한 회원의 정보를 조회합니다.")
Expand All @@ -68,7 +61,7 @@ public ResponseEntity<BaseResponse<Void>> onboardMember(@AuthenticationPrincipal
public ResponseEntity<BaseResponse<OnboardUniversitiesResponse>> getOnboardUniversities() {
return ResponseEntity
.status(HttpStatus.OK)
.body(BaseResponse.ok(OnboardUniversitiesResponse.from(University.UNIVERSITY_LIST),
.body(BaseResponse.ok(localizedOnboardQueryService.getUniversities(),
"온보딩 대학교 목록 조회에 성공하였습니다."));
}

Expand All @@ -78,7 +71,7 @@ public ResponseEntity<BaseResponse<OnboardCountriesResponse>> getOnboardCountrie
return ResponseEntity
.status(HttpStatus.OK)
.body(BaseResponse.ok(
OnboardCountriesResponse.from(Country.getCountries()),
localizedOnboardQueryService.getCountries(),
"온보딩 국가 목록 조회에 성공하였습니다."));
}

Expand All @@ -87,17 +80,15 @@ public ResponseEntity<BaseResponse<OnboardCountriesResponse>> getOnboardCountrie
public ResponseEntity<BaseResponse<OnboardMajorsResponse>> getOnboardMajors() {
return ResponseEntity
.status(HttpStatus.OK)
.body(BaseResponse.ok(OnboardMajorsResponse.from(Major.MAJOR_LIST), "온보딩 전공 목록 조회에 성공하였습니다.")
);
.body(BaseResponse.ok(localizedOnboardQueryService.getMajors(), "온보딩 전공 목록 조회에 성공하였습니다."));
}

@GetMapping("/onboard/fields")
@Operation(summary = "온보딩 관심 분야 목록 조회", description = "회원 온보딩 시 선택할 수 있는 관심 분야 목록을 조회합니다.")
public ResponseEntity<BaseResponse<OnboardFieldsResponse>> getOnboardFields() {
return ResponseEntity
.status(HttpStatus.OK)
.body(BaseResponse.ok(OnboardFieldsResponse.from(Field.FIELD_LIST), "온보딩 관심 분야 목록 조회에 성공하였습니다.")
);
.body(BaseResponse.ok(localizedOnboardQueryService.getFields(), "온보딩 관심 분야 목록 조회에 성공하였습니다."));
}

@Operation(summary = "AI 로드맵 생성 API", description = "사용자가 온보딩에 입력한 정보를 통해 로드맵을 생성합니다.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,11 @@ public record MemberOnboardV2Request(
@NotNull(message = "생년월일은 필수 입력값입니다.")
LocalDate birthDate,

@NotNull(message = "대학교는 필수 입력값입니다.")
String university,
@NotNull(message = "대학교 코드는 필수 입력값입니다.")
String universityCode,

@NotNull(message = "국가는 필수 입력값입니다.")
Country country,
@NotNull(message = "국가 코드는 필수 입력값입니다.")
String countryCode,
Comment on lines +16 to +20
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n src/main/java/org/sopt/kareer/domain/member/dto/request/MemberOnboardV2Request.java

Repository: team-kareer/kareer-server

Length of output: 2327


🏁 Script executed:

rg -A 3 "universityCode|countryCode" src/main/java/org/sopt/kareer --type java | head -50

Repository: team-kareer/kareer-server

Length of output: 4773


🏁 Script executed:

rg "@NotBlank|@NotNull" src/main/java/org/sopt/kareer/domain/member --type java -B 1 | head -40

Repository: team-kareer/kareer-server

Length of output: 3237


코드 문자열은 @NotBlank로 검증해야 합니다.

universityCodecountryCodeString인데 @NotNull만 사용하고 있어서 "" 또는 공백 문자열이 통과합니다. 같은 레코드의 다른 코드 필드들(primaryMajorCode, name, targetJob)은 모두 @NotBlank를 사용하고 있습니다. 이 두 필드는 이후 라벨 조회 키로 사용되므로 빈 값이 저장되면 매핑이 깨집니다.

수정 예시
-        `@NotNull`(message = "대학교 코드는 필수 입력값입니다.")
+        `@NotBlank`(message = "대학교 코드는 필수 입력값입니다.")
         String universityCode,

-        `@NotNull`(message = "국가 코드는 필수 입력값입니다.")
+        `@NotBlank`(message = "국가 코드는 필수 입력값입니다.")
         String countryCode,
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
@NotNull(message = "대학교 코드는 필수 입력값입니다.")
String universityCode,
@NotNull(message = "국가는 필수 입력값입니다.")
Country country,
@NotNull(message = "국가 코드는 필수 입력값입니다.")
String countryCode,
`@NotBlank`(message = "대학교 코드는 필수 입력값입니다.")
String universityCode,
`@NotBlank`(message = "국가 코드는 필수 입력값입니다.")
String countryCode,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In
`@src/main/java/org/sopt/kareer/domain/member/dto/request/MemberOnboardV2Request.java`
around lines 16 - 20, The fields universityCode and countryCode in
MemberOnboardV2Request are annotated with `@NotNull` which allows empty or
whitespace strings; change their validation annotation to `@NotBlank` so
empty/blank values are rejected and label lookup keys remain valid. Update the
annotations on the universityCode and countryCode fields in the
MemberOnboardV2Request class (same style as primaryMajorCode, name, targetJob)
to use `@NotBlank` with an appropriate message.


@NotNull(message = "언어 능력은 필수 입력값입니다.")
LanguageLevel languageLevel,
Expand All @@ -40,11 +40,8 @@ public record MemberOnboardV2Request(
@NotNull(message = "비자 만료일은 필수 입력값입니다.")
LocalDate visaExpiredAt,

@Schema(description = "비자 점수, D10 비자인 경우만", example = "50")
Integer visaPoint,

@NotBlank(message = "제1전공은 필수 입력값입니다.")
String primaryMajor,
@NotBlank(message = "제1전공 코드는 필수 입력값입니다.")
String primaryMajorCode,

String secondaryMajor,

Expand All @@ -63,15 +60,4 @@ public record MemberOnboardV2Request(
@Size(max = 1000, message = "개인 배경은 최대 1000자까지 입력할 수 있습니다.")
String personalBackground
) {
@AssertTrue(message = "visaPoint는 D10 비자인 경우에만 입력할 수 있습니다.")
@Schema(hidden = true)
public boolean isVisaPointValid() {
if (visaType == null) {
return visaPoint == null;
}
if (visaType == VisaType.D10) {
return visaPoint != null;
}
return visaPoint == null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,20 @@ public record MypageRequest(
LocalDate birthDate,

@Schema(description = "국가", example = "Afghanistan")
@NotNull(message = "국가는 필수 입력값입니다.")
Country country,
@NotNull(message = "국가코드는 필수 입력값입니다.")
String countryCode,

@Schema(description = "학위", example = "DOMESTIC_ASSOCIATE")
@NotNull(message = "학위는 필수 입력값입니다.")
Degree degree,

@Schema(description = "대학", example = "Konkuk University")
@NotBlank(message = "대학은 필수 입력값입니다.")
String university,
@NotBlank(message = "대학코드는 필수 입력값입니다.")
String universityCode,

@Schema(description = "전공", example = "Computer Science")
@NotBlank(message = "전공은 필수 입력값입니다.")
String primaryMajor,
@NotBlank(message = "전공코드는 필수 입력값입니다.")
String primaryMajorCode,
Comment on lines +22 to +35
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

find . -type f -name "MypageRequest.java" | head -5

Repository: team-kareer/kareer-server

Length of output: 145


🏁 Script executed:

cat -n src/main/java/org/sopt/kareer/domain/member/dto/request/MypageRequest.java | head -50

Repository: team-kareer/kareer-server

Length of output: 2152


코드형 요청 계약을 일관되게 맞춰 주세요.

countryCode@NotNull이라 ""/공백 문자열이 통과하는 반면, universityCodeprimaryMajorCode@NotBlank로 보호됩니다. 또한 세 필드의 @Schema example이 모두 표시명("Afghanistan", "Konkuk University", "Computer Science")이라 실제 코드 형식을 명시하지 않아 API 소비자 혼동을 야기합니다.

🧩 수정 예시
-        `@Schema`(description = "국가", example = "Afghanistan")
-        `@NotNull`(message = "국가코드는 필수 입력값입니다.")
+        `@Schema`(description = "국가 코드", example = "afghanistan")
+        `@NotBlank`(message = "국가코드는 필수 입력값입니다.")
         String countryCode,
@@
-        `@Schema`(description = "대학", example = "Konkuk University")
+        `@Schema`(description = "대학 코드", example = "konkuk-university")
         `@NotBlank`(message = "대학코드는 필수 입력값입니다.")
         String universityCode,
@@
-        `@Schema`(description = "전공", example = "Computer Science")
+        `@Schema`(description = "전공 코드", example = "computer-science")
         `@NotBlank`(message = "전공코드는 필수 입력값입니다.")
         String primaryMajorCode,
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/main/java/org/sopt/kareer/domain/member/dto/request/MypageRequest.java`
around lines 22 - 35, MypageRequest has inconsistent validation and misleading
schema examples: change countryCode's annotation from `@NotNull` to `@NotBlank` (and
keep or harmonize validation messages) so empty/blank strings are rejected like
universityCode and primaryMajorCode, and update all `@Schema` example values for
countryCode, universityCode, and primaryMajorCode to show actual code formats
(e.g., "KR" or "AF" for countryCode, a university code token for universityCode,
and a major code token for primaryMajorCode) so API consumers see the expected
code strings; locate and modify the fields countryCode, universityCode, and
primaryMajorCode in class MypageRequest to apply these changes.


@Schema(description = "부전공", example = "Statistic")
@NotBlank(message = "부전공은 필수 입력값입니다.")
Expand All @@ -55,8 +55,8 @@ public record MypageRequest(
EnglishLevel englishLevel
) {
public MypageCommand toCommand(){
return new MypageCommand(this.targetJob, this.birthDate, this.country,
this.degree, this.university, this.primaryMajor, this.secondaryMajor, this.visaType,
return new MypageCommand(this.targetJob, this.birthDate, this.countryCode,
this.degree, this.universityCode, this.primaryMajorCode, this.secondaryMajor, this.visaType,
this.visaExpiredAt, this.languageLevel, this.englishLevel);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package org.sopt.kareer.domain.member.dto.response;

public record LocalizedItemResponse(String code, String label) {}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@

import io.swagger.v3.oas.annotations.media.Schema;
import java.time.LocalDate;
import org.sopt.kareer.domain.member.entity.Member;
import org.sopt.kareer.domain.member.entity.MemberVisa;
import org.sopt.kareer.domain.member.entity.enums.Country;
import org.sopt.kareer.domain.member.entity.enums.LanguageLevel;
import org.sopt.kareer.domain.member.entity.enums.VisaType;
import org.sopt.kareer.domain.member.entity.*;
import org.sopt.kareer.domain.member.entity.enums.*;

@Schema(description = "회원 정보 응답")
public record MemberInfoResponse(
Expand All @@ -26,7 +23,7 @@ public record MemberInfoResponse(
LocalDate birthDate,

@Schema(description = "거주 국가")
Country country,
String country,

@Schema(description = "주 전공", example = "Computer Science")
String primaryMajor,
Expand All @@ -37,6 +34,9 @@ public record MemberInfoResponse(
@Schema(description = "타겟 직무", example = "Backend Engineer")
String targetJob,

@Schema(description = "대학교", example = "Seoul National University")
String university,

@Schema(description = "졸업일", example = "2026-02-11")
LocalDate graduationDate,

Expand All @@ -49,27 +49,37 @@ public record MemberInfoResponse(
@Schema(description = "학위", example = "Bachelor")
String degree,

@Schema(description = "영어 수준", example = "Upper Intermediate")
String englishLevel,

@Schema(description = "타겟 직무 스킬", example = "Java, Spring")
String targetJobSkill,

@Schema(description = "비자 유형")
VisaType visaType
) {
public static MemberInfoResponse from(Member member, MemberVisa memberVisa) {
public static MemberInfoResponse of(Member member, MemberVisa memberVisa,
String countryLabel,
String primaryMajorLabel,
String universityLabel,
String degreeLabel,
String englishLevelLabel) {
return new MemberInfoResponse(
member.getId(),
member.getName(),
member.getEmail(),
member.getProfileImageUrl(),
member.getBirthDate(),
member.getCountry(),
member.getPrimaryMajor(),
countryLabel,
primaryMajorLabel,
member.getSecondaryMajor(),
member.getTargetJob(),
universityLabel,
member.getGraduationDate(),
member.getExpectedGraduationDate(),
member.getLanguageLevel(),
member.getDegree().getDescription(),
degreeLabel,
englishLevelLabel,
member.getTargetJobSkill(),
memberVisa.getVisaType()
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -51,21 +51,26 @@ public record MypageResponse(
String englishLevel

) {
public static MypageResponse from(Member member, MemberVisa memberVisa) {
public static MypageResponse of(Member member, MemberVisa memberVisa,
String countryLabel,
String primaryMajorLabel,
String universityLabel,
String degreeLabel,
String englishLevelLabel) {
return MypageResponse.builder()
.name(member.getName())
.profileImageUrl(member.getProfileImageUrl())
.targetJob(member.getTargetJob())
.birthDate(member.getBirthDate())
.country(member.getCountry().getCountryName())
.degree(member.getDegree().getDescription())
.university(member.getUniversity())
.primaryMajor(member.getPrimaryMajor())
.country(countryLabel)
.degree(degreeLabel)
.university(universityLabel)
.primaryMajor(primaryMajorLabel)
.secondaryMajor(member.getSecondaryMajor())
.visaType(memberVisa.getVisaType().getDescription())
.visaExpiredAt(memberVisa.getVisaExpiredAt())
.languageLevel(member.getLanguageLevel())
.englishLevel(member.getEnglishLevel().getDescription())
.englishLevel(englishLevelLabel)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import java.util.List;

public record OnboardCountriesResponse(
List<String> countries
List<LocalizedItemResponse> countries
) {
public static OnboardCountriesResponse from(List<String> countries) {
public static OnboardCountriesResponse of(List<LocalizedItemResponse> countries) {
return new OnboardCountriesResponse(countries);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import java.util.List;

public record OnboardFieldsResponse(
List<String> fields
List<LocalizedItemResponse> fields
) {
public static OnboardFieldsResponse from(List<String> fields) {
public static OnboardFieldsResponse of(List<LocalizedItemResponse> fields) {
return new OnboardFieldsResponse(fields);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import java.util.List;

public record OnboardMajorsResponse(
List<String> majors
List<LocalizedItemResponse> majors
) {
public static OnboardMajorsResponse from(List<String> majorList) {
public static OnboardMajorsResponse of(List<LocalizedItemResponse> majorList) {
return new OnboardMajorsResponse(majorList);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
import java.util.List;

public record OnboardUniversitiesResponse(
List<String> universities
List<LocalizedItemResponse> universities
) {
public static OnboardUniversitiesResponse from(List<String> universities) {
public static OnboardUniversitiesResponse of(List<LocalizedItemResponse> universities) {
return new OnboardUniversitiesResponse(universities);
}
}
Loading
Loading