Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
fd4ac93
feat: 장소 운영시간을 콘텐츠당 한 번만 받아 둔다
sevineleven Aug 11, 2026
b0b66c8
feat: 코스 슬롯에 운영시간·휴무일을 인라인으로 낸다
sevineleven Aug 11, 2026
21bd53c
test: 운영시간 적재와 재조회 방지를 잠근다
sevineleven Aug 11, 2026
211c15f
feat: 오늘 여는지를 도메인이 판정한다
sevineleven Aug 11, 2026
cb497ee
feat: 여행일이 오늘이면 슬롯에 운영 상태를 낸다
sevineleven Aug 11, 2026
1004c92
test: 오늘 여는지 판정을 망라한다
sevineleven Aug 11, 2026
85a667a
feat: 혜택이 어느 슬롯에 붙는지를 enum 이 안다
sevineleven Aug 11, 2026
02ca1f7
feat: 슬롯에 혜택 뱃지를 낸다
sevineleven Aug 11, 2026
90e9c31
feat: 지역 한 줄 소개를 데이터에서 도출한다
sevineleven Aug 11, 2026
c11d9b8
feat: 추천 카드에 지역 소개를 싣는다
sevineleven Aug 11, 2026
7e8286c
feat: 사진 없는 슬롯을 지도로 넘긴다
sevineleven Aug 11, 2026
3380cc7
refactor: 운영시간 저장소를 port 와 JDBC 어댑터로 나눈다
sevineleven Aug 13, 2026
8d09620
fix: 빈 운영시간 응답을 영구 캐시로 굳히지 않는다
sevineleven Aug 13, 2026
82f1a59
test: 운영시간 배치가 무엇을 남기는지 실제로 단언한다
sevineleven Aug 13, 2026
f87349f
refactor: 지역 대표 볼거리 조회를 port·adapter 로 나눈다
sevineleven Aug 13, 2026
13f063e
Merge remote-tracking branch 'origin/dev' into feat/157-slot-hours-in…
sevineleven Aug 13, 2026
02f6ebd
refactor: 혜택이 붙는 자리를 정책·코스가 나눠 안다
sevineleven Aug 13, 2026
14b25ba
docs: 지역 소개 OpenAPI 예시를 실제 출력과 맞춘다
sevineleven Aug 13, 2026
84950a3
perf: 운영시간 배치도 로컬에서는 적게 쓴다
sevineleven Aug 13, 2026
e966210
Merge branch 'feat/157-slot-hours-inline' into feat/189-today-status
sevineleven Aug 13, 2026
b8515f3
Merge branch 'feat/189-today-status' into feat/140-slot-benefit
sevineleven Aug 13, 2026
c0588f2
Merge branch 'feat/140-slot-benefit' into feat/236-stay-presentation
sevineleven Aug 13, 2026
7a5a2cd
fix: 못 읽은 조건이 남으면 운영 상태를 확정하지 않는다
sevineleven Aug 13, 2026
8e6bd92
fix: 공휴일 조회 실패로 degrade 한 것을 로그에 남긴다
sevineleven Aug 13, 2026
002aaeb
Merge remote-tracking branch 'origin/dev' into feat/189-today-status
sevineleven Aug 13, 2026
9026d10
Merge remote-tracking branch 'origin/feat/189-today-status' into feat…
sevineleven Aug 13, 2026
1957063
Merge remote-tracking branch 'origin/feat/140-slot-benefit' into feat…
sevineleven Aug 13, 2026
fa9f8cb
Merge remote-tracking branch 'origin/dev' into feat/140-slot-benefit
sevineleven Aug 13, 2026
b8702db
Merge remote-tracking branch 'origin/feat/140-slot-benefit' into feat…
sevineleven Aug 13, 2026
4404b4e
Merge remote-tracking branch 'origin/dev' into feat/236-stay-presenta…
sevineleven Aug 13, 2026
4bfa65e
Merge branch 'dev' into feat/236-stay-presentation
sevineleven Aug 13, 2026
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
Expand Up @@ -5,6 +5,7 @@
import com.offway.core.itinerary.domain.DaySchedule;
import com.offway.core.itinerary.domain.Slot;
import com.offway.core.itinerary.domain.SlotKind;
import com.offway.core.trip.domain.MapSearchLink;
import com.offway.core.itinerary.service.dto.GeneratedCourse;
import com.offway.core.itinerary.service.dto.SlotHours;
import com.offway.core.policy.domain.PolicyType;
Expand Down Expand Up @@ -266,6 +267,14 @@ public record Item(
알 수 있는데 그 데이터가 없어 붙이지 않는다 — 근거 없이 붙이면 사용자가 못 받는 할인을
기대하고 간다. 지역 단위 혜택은 코스 `benefits` 로 그대로 나간다.""",
example = "숙박 할인", nullable = true) String benefit,
@Schema(description = """
지도 검색 링크(#236). **사진이 없는 슬롯에만** 실린다.

숙소는 89곳 중 45곳에서 사진 있는 후보가 2곳도 안 된다 — 인허가 데이터에 사진이 없고,
공식 API 로 숙소 사진을 주는 곳은 유료뿐이다. 사진 없는 카드를 그대로 두는 대신
지도로 넘겨 위치·사진·리뷰를 거기서 보게 한다.""",
example = "https://map.naver.com/p/search/%EC%9D%98%EC%84%B1%EA%B5%B0+%EC%98%AC%EC%9D%B8%EB%AA%A8%ED%85%94",
nullable = true) String mapSearchUrl,
double lat,
double lng,
int travelMinutes,
Expand All @@ -290,6 +299,7 @@ static Item from(Slot slot, Integer distanceFromPrevMeters, String regionName,
hours == null ? null : hours.restDate(),
hours == null ? null : hours.displayStatus(),
benefit,
mapSearchUrlFor(slot),
slot.getLat(),
slot.getLng(),
slot.getTravelMinutesFromPrev(),
Expand Down Expand Up @@ -388,4 +398,18 @@ private static Map<SlotKind, String> slotBenefits(GeneratedCourse generated) {
private static String benefitFor(Slot slot, Map<SlotKind, String> slotBenefits) {
return slotBenefits.get(slot.getKind());
}

/**
* 사진이 없는 슬롯에만 지도 링크를 준다(#236).
*
* <p>사진이 있으면 카드가 이미 설 수 있어 링크가 군더더기다. 없을 때만 "여기서 보세요" 가 값어치를 갖는다.
*
* <p>숙소가 이 경우의 대부분이다 — 89곳 중 45곳에서 사진 있는 숙소가 2곳도 안 된다.
*/
private static String mapSearchUrlFor(Slot slot) {
if (slot.getImageUrl() != null && !slot.getImageUrl().isBlank()) {
return null;
}
return MapSearchLink.of(slot.getTitle(), slot.getAddress()).orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class ResponseLogSummaryTest {
@Test
void 코스는_지역명과_규모를_낸다() {
CourseResponse.Item item = new CourseResponse.Item(
1, "MORNING", "SIGHT", "관광", "c1", "장소1", null, null, null, null, null, null, null, null, 37.5, 128.6, 0, null, "정선군");
1, "MORNING", "SIGHT", "관광", "c1", "장소1", null, null, null, null, null, null, null, null, null, 37.5, 128.6, 0, null, "정선군");
CourseResponse.Day day = new CourseResponse.Day(1, null, null, null, null, null, List.of(item));
CourseResponse response = new CourseResponse(
1L, 16, 3, null, "PACKED", "CAR", List.of(day), List.of(), null, null, null);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.offway.core.itinerary.controller.dto;

import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.offway.core.itinerary.domain.Slot;
import com.offway.core.itinerary.domain.SlotDisplay;
import com.offway.core.itinerary.domain.SlotKind;
import com.offway.core.itinerary.domain.TimeOfDay;
import org.junit.jupiter.api.Test;

/**
* 사진 없는 슬롯에 지도 링크(#236).
*
* <p>숙소는 89곳 중 45곳에서 사진 있는 후보가 2곳도 안 된다. 인허가 데이터에 사진이 없고, 공식 API 로
* 숙소 사진을 주는 곳은 유료뿐이다. 사진 없는 카드를 그대로 두는 대신 지도로 넘긴다.
*/
class SlotMapLinkTest {

private static Slot slot(String imageUrl) {
return Slot.of(1, TimeOfDay.DINNER, SlotKind.STAY, "LIC-1", "올인모텔", 36.35, 128.69, 0,
new SlotDisplay(imageUrl, "경상북도 의성군 의성읍 후죽리 1", null, "054-1"));
}

@Test
void 사진이_없으면_지도로_넘긴다() {
CourseResponse.Item item = CourseResponse.Item.from(slot(null), null, "의성군", null, null);

assertNotNull(item.mapSearchUrl());
assertTrue(item.mapSearchUrl().startsWith("https://map.naver.com/p/search/"));
}

@Test
void 사진이_있으면_링크를_안_붙인다() {
// 카드가 이미 설 수 있어 링크가 군더더기다.
CourseResponse.Item item =
CourseResponse.Item.from(slot("http://img/1.jpg"), null, "의성군", null, null);

assertNull(item.mapSearchUrl());
}
}