Skip to content

[feat] #9 - 온보딩 여행 정보 저장 API 구현 - #10

Merged
laura-jung merged 16 commits into
mainfrom
feat/#9
Jul 27, 2026
Merged

[feat] #9 - 온보딩 여행 정보 저장 API 구현#10
laura-jung merged 16 commits into
mainfrom
feat/#9

Conversation

@laura-jung

@laura-jung laura-jung commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Related issue 🛠

Work Description ✏️

온보딩에서 수집한 여행 기본 정보를 바탕으로 첫 번째 여행 일정(TravelPlan)을 생성하는 POST /api/onboarding API를 구현했습니다.

기존에는 온보딩 정보를 회원당 1개만 가질 수 있는 TravelProfile 형태로 저장했지만, 한 회원이 여러 여행 일정을 가질 수 있어야 하므로 구조를 변경했습니다.

Member 1 : N TravelPlan
TravelPlan 1 : N TravelPreference

성공 시 201 Created와 함께 SuccessResponse<OnboardingCompleteResponse>를 내려주며, 응답에는 생성된 여행 일정 ID만 포함됩니다.

{
  "travelPlanId": 1
}

역할 변경 및 토큰 재발급은 온보딩 API에서 처리하지 않고, 기존에 구현된 PATCH /api/auth/role API가 담당하도록 분리했습니다.

TourAPI 기준 취향 구조 정리

기존 TravelCategory / TravelSubcategory 구조는 우리 서비스 UX 기준이라 TourAPI KorService2의 contentTypeId, lclsSystm1/2/3와 맞지 않았습니다.

따라서 온보딩 취향 상위 기준을 TourApiContentType으로 변경했습니다.

  • TOURIST_ATTRACTION(12)
  • CULTURAL_FACILITY(14)
  • FESTIVAL_EVENT(15)
  • LEISURE_SPORTS(28)
  • SHOPPING(38)
  • RESTAURANT(39)

TRAVEL_COURSE(25), LODGING(32)도 enum에는 포함했지만 온보딩 취향 선택에서는 제외했습니다.

요청 예시는 다음과 같습니다.

{
  "contentType": "RESTAURANT",
  "subcategories": ["KOREAN_FOOD", "CAFE_TEAHOUSE"]
}

TravelSubcategory에는 TourAPI 검색에 사용할 수 있도록 contentTypelclsSystm1/2/3 매핑 정보를 추가했습니다.
매핑 값은 신분류체계정보 관광타입정보 연계 정의서.xlsx분류체계_관광타입맵핑 시트를 기준으로 확인한 값만 반영했습니다.

지역 구조 변경

기존 Region enum은 제거했습니다.
디자인상 기본 지역 외에도 “기타” 선택 후 지역 검색이 가능해야 하므로, 지역은 enum이 아니라 DB 테이블로 관리하도록 변경했습니다.

추가된 엔티티:

TravelRegion
- id
- displayName
- lDongRegnCd
- lDongSignguCd

기타 지역 검색 API도 추가했습니다.

GET /api/onboarding/regions?keyword=서울

keyword가 비어 있으면 빈 목록을 반환합니다.
기본 지역 목록은 프론트 화면에서 관리하고, 백엔드는 기타 지역 검색과 온보딩 저장 시 regionId 검증만 담당합니다.

온보딩 요청의 지역 필드도 enum이 아니라 regionId 기준으로 변경했습니다.

{
  "regionId": 1,
  "regionUndecided": false
}

검증 분리

단일 필드 검증은 Bean Validation(@NotNull, @NotEmpty)으로, 필드 간 교차 검증은 OnboardingRequestValidator로 분리했습니다. 컨트롤러에는 검증 로직을 두지 않았습니다.

  • 지역: regionIdregionUndecided는 정확히 하나만 성립
  • 날짜: 시작일 ≤ 종료일, 시작일은 오늘 이후 (Asia/Seoul 기준)
  • 활동 시간: 시작 < 종료
  • 취향: 선택 가능한 TourAPI 관광타입 2~4개, 관광타입 중복 불가
  • 각 관광타입마다 세부 취향 1개 이상
  • 세부 취향 중복 불가
  • 세부 취향은 요청한 contentType에 속해야 함
  • TRAVEL_COURSE, LODGING은 온보딩 취향 선택 불가
  • LIKE_ALL_FOODRESTAURANT 내에서 단독 선택만 허용

Trouble Shooting ⚽️

1. 날짜 검증의 테스트 가능성

"시작일은 오늘 이후"를 LocalDate.now()로 짜면 테스트에서 시간을 고정할 수 없습니다. ClockConfigClock 빈을 등록하고 validator가 이를 주입받게 해서, 테스트에서 Clock.fixed()로 특정 날짜를 고정할 수 있도록 했습니다. 시점 기준은 ZoneId.of("Asia/Seoul")로 고정했습니다.

2. TourAPI 취향 분류 매핑

기존 UX 기준 대분류(FOOD, NATURE, CULTURE 등)는 TourAPI의 contentTypeId와 1:1로 맞지 않았습니다.
예를 들어 기존 CULTURE 안에는 문화시설, 관광지, 축제/공연/행사 성격이 섞여 있어 TourAPI 검색 조건으로 쓰기 어렵습니다.

그래서 TravelCategory는 제거하고, TourAPI 검색 기준인 TourApiContentType을 온보딩 취향의 상위 기준으로 사용했습니다.

lclsSystm1/2/3 값은 추측하지 않고 첨부 엑셀 정의서에서 확인한 코드만 넣었습니다.
공식 코드와 1:1 매핑이 애매한 항목은 contentType만 보관하거나 TODO로 남겼습니다.

예:

  • VINTAGE
  • FIREWORKS
  • NIGHT_MARKET
  • HIKING

3. 온보딩과 토큰 재발급 책임 분리

온보딩 건너뛰기 기능이 있고, role 변경으로 인한 토큰 재발급 API가 이미 따로 구현되어 있어 온보딩 API 응답에서 토큰을 제거했습니다.

현재 책임은 다음처럼 분리했습니다.

POST /api/onboarding
→ 첫 여행 일정 생성

PATCH /api/auth/role
→ role 변경 및 토큰 재발급

4. 지역 enum 제거

기타 도시 검색이 필요한 화면 구조라서 Region enum에 도시를 계속 추가하는 방식은 확장성이 낮다고 판단했습니다.
따라서 지역은 TravelRegion 테이블에서 관리하고, 온보딩 요청은 regionId를 받도록 변경했습니다.

다만 lDongRegnCd/lDongSignguCd는 실제 TourAPI KorService2 연동에 쓰일 값이라 임의로 seed하지 않았습니다. 정확한 법정동 코드 데이터 주입 방식은 추후 정해야 합니다.

Related ScreenShot 📷

정상적으로 완료된 경우
스크린샷 2026-07-25 오전 3 37 13

현재 응답 구조는 스크린샷과 달리 토큰 없이 travelPlanId만 반환합니다.

Uncompleted Tasks 😅

  • 테스트 코드가 포함되어 있지 않습니다. 작성 후 제거한 상태입니다.
  • travel_region 초기 데이터 seed
  • TravelRegion의 정확한 lDongRegnCd/lDongSignguCd 데이터 확보
  • 기존 DB가 있다면 travel_profile → travel_plan, profile_id → travel_plan_id 마이그레이션
  • 온보딩 정보 기반 여행 일정 조회 / 수정 API
  • 기타 지역 검색 API의 검색 기준 고도화 (contains 외 초성/공백/정렬 등)

To Reviewers 📢

  • 온보딩 결과를 회원 프로필이 아니라 첫 번째 여행 일정(TravelPlan)으로 저장하도록 변경했습니다. Member 1 : N TravelPlan 구조가 이후 일정 생성/조회/수정 흐름에도 맞는지 확인 부탁드립니다.
  • 온보딩 API에서 role 변경과 토큰 재발급을 제거했습니다. 온보딩 완료 후 프론트가 별도로 PATCH /api/auth/role을 호출하는 흐름이 맞는지 확인 부탁드립니다.
  • 지역은 enum이 아니라 TravelRegion 테이블로 관리하도록 변경했습니다. 기본 지역은 프론트에 노출하고, 백엔드는 기타 지역 검색만 담당하는 방향이 적절한지 의견 부탁드립니다.
  • TourAPI lclsSystm1/2/3 매핑은 첨부 엑셀 기준으로 확인한 값만 넣었습니다. 애매한 항목을 억지 매핑하지 않고 TODO/contentType only로 남긴 판단이 괜찮은지 봐주세요.
  • 교차 검증을 OnboardingRequestValidator라는 별도 @Component로 뺐습니다. 커스텀 어노테이션(ConstraintValidator) 방식보다 명시적인 validator가 낫다고 판단했는데, 이 방향이 괜찮은지 확인 부탁드립니다.

@laura-jung
laura-jung requested a review from sae2say July 22, 2026 08:42
@laura-jung laura-jung self-assigned this Jul 22, 2026
@laura-jung laura-jung linked an issue Jul 22, 2026 that may be closed by this pull request
1 task
@laura-jung laura-jung changed the title [feat] #9 온보딩 여행 정보 저장 API 구현 [feat] #9 - 온보딩 여행 정보 저장 API 구현 Jul 22, 2026

@sae2say sae2say left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

전체적으로 짜임새가 너무 좋고 읽기가 너무 편했어요!! 역시 대윤아..
다만 카톡으로도 한 번 언급한 것과 같이, 추후 TourAPI를 호출할 때 원활할 수 있도록 초반 설정을 검토해 보는 부분이 필요할 것 같습니다. 이외에는 너무 짱짱이어서 할 말이 업슴니다..

저도 다음 이슈 코드는 윤아님처럼 작성해보려고요 ㅎㅎ 내 코드컨벤션은 대윤아

너무 수고하셨습니다~ 수정하신 후 다시 요청 주세요!

* 요청 매핑과 파라미터 바인딩 애노테이션은 구현 컨트롤러에 둔다.
*/
@Tag(name = "Onboarding", description = "온보딩 여행 기본 정보 API")
public interface OnboardingControllerDocs {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

와우 너무 꼼꼼띠 하게 잘 쓰여져 있네요 👍

Comment thread src/main/java/com/JJIN/domain/onboarding/entity/TravelPlan.java
Comment thread src/main/java/com/JJIN/domain/onboarding/entity/enums/Region.java Outdated
@laura-jung
laura-jung requested a review from sae2say July 24, 2026 18:38

@sae2say sae2say left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

너무 꼼꼼하게 잘 작업해주셨네요. 많이 배웠숩니다!! 코멘트 몇 개만 확인해주시고, 더 이상 변경사항 없으면 더 이상은 코리 요청 없이 바로 머지해주셔도 될 것 같습니다. 수고하셨습니다!

TRADITIONAL_MARKET(TourApiContentType.SHOPPING, "전통 시장", of("SH", "SH06")),
LOCAL_SHOP(TourApiContentType.SHOPPING, "로컬 상점", of("SH", "SH05")),
DUTY_FREE(TourApiContentType.SHOPPING, "면세점", of("SH", "SH04")),
// TODO: 정의서에 빈티지와 1:1로 대응되는 lclsSystm 코드가 없어 contentType만 보관한다.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

좋네요~ TourAPI에 바로 사용가능하도록 대분류는 기존의 TourAPI 분류체계 대분류를 그대로 따르는 것이 아닌, contentType을 기준으로 하고 중분류는 그대로 따름으로써 이제 검색도 원활하고, 사용자 맞춤 추천도 가능할 것 같습니다! 좋아요!

@Enumerated(EnumType.STRING)
@Column(name = "region")
private Region region;
@ManyToOne(fetch = FetchType.LAZY)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

member 필드에는 optional = false로 설정하시고 여기는 default로 두신 이유가 있나요? (궁금띠해서)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

member의 경우에는 여행일정의 소유자이기 때문에 항상 존재해야해서 optional을 false로 설정하였습니다! 반면 region의 경우에는 일정 선택시 지역 미정 플로우에서 region이 null일 수 있어 기본값으로 설정해두었습니당!

Comment on lines +60 to +71
public static TravelRegion create(
final String displayName,
final String lDongRegnCd,
final String lDongSignguCd
) {
return TravelRegion.builder()
.displayName(displayName)
.lDongRegnCd(lDongRegnCd)
.lDongSignguCd(lDongSignguCd)
.build();
}
}

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

이 정팩메는 언제 쓰일 걸 생각하고 만드신건가요?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

저희 서비스에서는 지역 정보를 db에 직접 넣어주면 될 것 같아서 정적팩토리메서드가 필요가 없겠네요....! 삭제하겠습니당

@laura-jung
laura-jung merged commit a14c4eb into main Jul 27, 2026
1 check failed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[feat] 온보딩 이후 여행 기본 정보 세팅

2 participants