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
Expand Up @@ -2,8 +2,6 @@ package noweekend.client.mcp.recommend

import noweekend.client.mcp.recommend.model.AiGenerateVacationRequest
import noweekend.client.mcp.recommend.model.AiGenerateVacationResponse
import noweekend.client.mcp.recommend.model.BridgeVacationPeriod
import noweekend.client.mcp.recommend.model.SandwichRequest
import noweekend.client.mcp.recommend.model.TagRequest
import noweekend.client.mcp.recommend.model.WeatherRequest
import noweekend.core.domain.tag.TagRecommendation
Expand Down Expand Up @@ -40,13 +38,6 @@ interface RecommendApi {
)
fun getTagOnlyNew(@RequestBody request: TagRequest): List<TagRecommendation>

@RequestMapping(
value = ["/getSandwich"],
consumes = [MediaType.APPLICATION_JSON_VALUE],
method = [RequestMethod.POST],
)
fun getSandwich(@RequestBody request: SandwichRequest): List<BridgeVacationPeriod>

@RequestMapping(
value = ["/generate-vacation"],
consumes = [MediaType.APPLICATION_JSON_VALUE],
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,8 @@
package noweekend.client.mcp.recommend

import feign.FeignException
import noweekend.client.mcp.McpNotRespondingException
import noweekend.client.mcp.recommend.model.AiGenerateVacationRequest
import noweekend.client.mcp.recommend.model.AiGenerateVacationResponse
import noweekend.client.mcp.recommend.model.BridgeVacationPeriod
import noweekend.client.mcp.recommend.model.SandwichRequest
import noweekend.client.mcp.recommend.model.TagRequest
import noweekend.client.mcp.recommend.model.WeatherRequest
import noweekend.client.mcp.recommend.model.toRequestType
Expand Down Expand Up @@ -82,18 +79,6 @@ class RecommendClient(
)
}

fun getSandwich(request: SandwichRequest): List<BridgeVacationPeriod> {
return try {
api.getSandwich(request)
} catch (e: FeignException) {
log.warn("[getSandwich] FeignException occurred. msg=${e.message}")
throw McpNotRespondingException()
} catch (e: Exception) {
log.error("[getSandwich] Unexpected exception occurred.", e)
throw McpNotRespondingException()
}
}

fun generateVacation(request: AiGenerateVacationRequest): AiGenerateVacationResponse? {
return try {
api.generateVacation(request)
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.tags.Tag
import noweekend.core.domain.auth.TestUserService
import noweekend.core.domain.auth.UserWithToken
import noweekend.core.domain.recommend.RecommendService
import noweekend.core.support.response.ApiResponse
import org.springframework.web.bind.annotation.GetMapping
import org.springframework.web.bind.annotation.RestController
Expand All @@ -15,6 +16,7 @@ import io.swagger.v3.oas.annotations.responses.ApiResponse as SwaggerApiResponse
@RestController
class TempController(
private val testUserService: TestUserService,
private val recommendService: RecommendService,
) {

@Operation(
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package noweekend.core.api.controller.v1

import noweekend.client.mcp.recommend.model.SandwichApiResponse
import noweekend.client.mcp.recommend.model.SandwichResponse
import noweekend.core.api.controller.v1.docs.RecommendControllerDocs
import noweekend.core.api.controller.v1.request.GenerateVacationRequest
import noweekend.core.api.controller.v1.response.AiGenerateVacationApiResponse
import noweekend.core.api.controller.v1.response.SandwichApiResponse
import noweekend.core.api.controller.v1.response.WeatherResponse
import noweekend.core.api.security.annotations.CurrentUserId
import noweekend.core.domain.IconStyle
Expand All @@ -16,7 +15,6 @@ import org.springframework.web.bind.annotation.PostMapping
import org.springframework.web.bind.annotation.RequestBody
import org.springframework.web.bind.annotation.RequestMapping
import org.springframework.web.bind.annotation.RestController
import java.time.LocalDate

@RestController
@RequestMapping("/api/v1/recommend")
Expand Down Expand Up @@ -50,27 +48,9 @@ class RecommendController(
}

@GetMapping("/sandwich")
override fun getSandwich(
@CurrentUserId userId: String,
): ApiResponse<SandwichApiResponse> {
// return ApiResponse.success(recommendService.getSandwich(userId))
val mockResponse = SandwichApiResponse(
responses = listOf(
SandwichResponse(
startDate = LocalDate.of(2025, 8, 14),
endDate = LocalDate.of(2025, 8, 16),
useAnnualLeave = 1,
totalVacationDays = 3,
),
SandwichResponse(
startDate = LocalDate.of(2025, 9, 11),
endDate = LocalDate.of(2025, 9, 15),
useAnnualLeave = 2,
totalVacationDays = 5,
),
),
)
return ApiResponse.success(mockResponse)
override fun getSandwich(): ApiResponse<SandwichApiResponse> {
val sandwichApiResponse = recommendService.getSandwich()
return ApiResponse.success(sandwichApiResponse)
}

@PostMapping("/generate-vacation")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import io.swagger.v3.oas.annotations.media.Content
import io.swagger.v3.oas.annotations.media.ExampleObject
import io.swagger.v3.oas.annotations.media.Schema
import io.swagger.v3.oas.annotations.tags.Tag
import noweekend.client.mcp.recommend.model.SandwichApiResponse
import noweekend.core.api.controller.v1.request.GenerateVacationRequest
import noweekend.core.api.controller.v1.response.AiGenerateVacationApiResponse
import noweekend.core.api.controller.v1.response.SandwichApiResponse
import noweekend.core.api.controller.v1.response.WeatherResponse
import noweekend.core.api.security.annotations.CurrentUserId
import noweekend.core.domain.tag.TagRecommendations
Expand Down Expand Up @@ -320,9 +320,7 @@ interface RecommendControllerDocs {
),
],
)
fun getSandwich(
@Parameter(hidden = true) @CurrentUserId userId: String,
): ApiResponse<SandwichApiResponse>
fun getSandwich(): ApiResponse<SandwichApiResponse>

@Operation(
summary = "AI 기반 여행 일정 생성",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package noweekend.core.api.controller.v1.response

import java.time.LocalDate

data class SandwichResponse(
val startDate: LocalDate,
val endDate: LocalDate,
val useAnnualLeave: Int,
val totalVacationDays: Int,
)

data class SandwichApiResponse(
val responses: List<SandwichResponse>,
)
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
package noweekend.core.domain.recommend

import noweekend.client.mcp.recommend.model.SandwichApiResponse
import noweekend.core.api.controller.v1.request.GenerateVacationRequest
import noweekend.core.api.controller.v1.response.AiGenerateVacationApiResponse
import noweekend.core.api.controller.v1.response.SandwichApiResponse
import noweekend.core.api.controller.v1.response.WeatherResponse
import noweekend.core.domain.tag.TagRecommendations

interface RecommendService {
fun getWeatherRecommend(userId: String): WeatherResponse
fun getTagRecommend(userId: String): TagRecommendations
fun getTagRecommendOnlyNew(userId: String): TagRecommendations
fun getSandwich(userId: String): SandwichApiResponse
fun getSandwich(): SandwichApiResponse
fun generateVacation(userId: String, request: GenerateVacationRequest): AiGenerateVacationApiResponse
}
Original file line number Diff line number Diff line change
@@ -1,22 +1,20 @@
package noweekend.core.domain.recommend

import noweekend.client.mcp.McpNotRespondingException
import noweekend.client.mcp.recommend.RecommendClient
import noweekend.client.mcp.recommend.model.AiGenerateVacationRequest
import noweekend.client.mcp.recommend.model.SandwichApiResponse
import noweekend.client.mcp.recommend.model.SandwichRequest
import noweekend.client.mcp.recommend.model.SandwichResponse
import noweekend.client.mcp.recommend.model.WeatherRequest
import noweekend.core.api.controller.v1.request.GenerateVacationRequest
import noweekend.core.api.controller.v1.response.AiGenerateVacationApiResponse
import noweekend.core.api.controller.v1.response.SandwichApiResponse
import noweekend.core.api.controller.v1.response.SandwichResponse
import noweekend.core.api.controller.v1.response.WeatherResponse
import noweekend.core.domain.ActivityType
import noweekend.core.domain.IconStyle
import noweekend.core.domain.LeisurePreference
import noweekend.core.domain.RestPreference
import noweekend.core.domain.TravelStyle
import noweekend.core.domain.holiday.Holiday
import noweekend.core.domain.holiday.HolidayReader
import noweekend.core.domain.sandwich.SandwichCalculator
import noweekend.core.domain.tag.RecommendType
import noweekend.core.domain.tag.TagReader
import noweekend.core.domain.tag.TagRecommendCache
Expand All @@ -36,6 +34,7 @@ import noweekend.core.support.error.CoreException
import noweekend.core.support.error.ErrorType
import org.springframework.stereotype.Service
import java.time.LocalDate
import java.time.Year
import java.time.temporal.ChronoUnit
import kotlin.random.Random

Expand All @@ -50,6 +49,7 @@ class RecommendServiceImpl(
private val tagRecommendCacheReader: TagRecommendCacheReader,
private val tagRecommendCacheWriter: TagRecommendCacheWriter,
private val weekendReader: WeekendReader,
private val calculator: SandwichCalculator,
) : RecommendService {

override fun getWeatherRecommend(userId: String): WeatherResponse {
Expand Down Expand Up @@ -208,42 +208,43 @@ class RecommendServiceImpl(
throw CoreException(ErrorType.MCP_SERVER_TAGS_ERROR)
}

override fun getSandwich(userId: String): SandwichApiResponse {
val findUser = userReader.findUserById(userId) ?: throw CoreException(ErrorType.USER_NOT_FOUND_INTERNAL)
val birthDate = findUser.birthDate ?: throw CoreException(ErrorType.USER_BIRTH_DAY_NOT_FOUND)
val holidays: List<LocalDate> = holidayReader.findRemainingHolidays(LocalDate.now())
.map { holiday: Holiday -> holiday.date }

val weekends: List<LocalDate> = weekendReader.getUpcomingWeekends().map { weekend -> weekend.date }
override fun getSandwich(): SandwichApiResponse {
// 1) 기준 날짜
val today = LocalDate.now()

val holidayOrWeekendSet = holidays.toSet() + weekends.toSet()
val remainingAnnualLeave = findUser.remainingAnnualLeave ?: throw CoreException(ErrorType.INVALID_LOCATION)
// 2) 남은 공휴일, 주말 조회
val holidays = holidayReader.findRemainingHolidays(today).map { it.date }.toSet()
val weekends = weekendReader.getAllThisYearWeekends()
.map { it.date }
.filter { it.isAfter(today) }
.toSet()

// 3) 연말까지 계산
val until = Year.now().atMonth(12).atEndOfMonth()
val periods = calculator.recommendSandwich(
holidays = holidays,
weekends = weekends,
maxGap = 2,
minSpan = 3,
from = today,
until = until,
)

try {
val bridgePeriods = recommendClient.getSandwich(
SandwichRequest(birthDay = birthDate, holidays = holidays, remainingAnnualLeave.toInt(), weekends),
)
return SandwichApiResponse(
bridgePeriods.map { period ->
val allDates = generateDateRange(period.startDate, period.endDate)
val useAnnualLeaveDates = allDates.filter { date ->
!holidayOrWeekendSet.contains(date) && date.dayOfWeek.value in 1..5
}
SandwichResponse(
startDate = period.startDate,
endDate = period.endDate,
useAnnualLeave = useAnnualLeaveDates.size,
totalVacationDays = allDates.size,
)
}.toList(),
// 4) VacationPeriod → SandwichResponse 매핑
val responses = periods.map { period ->
val totalDays = ChronoUnit.DAYS.between(period.startDate, period.endDate).toInt() + 1
val useAnnualLeave = generateSequence(period.startDate) { it.plusDays(1) }
.takeWhile { !it.isAfter(period.endDate) }
.count { date -> date !in holidays && date !in weekends }
SandwichResponse(
startDate = period.startDate,
endDate = period.endDate,
useAnnualLeave = useAnnualLeave,
totalVacationDays = totalDays,
)
} catch (_: McpNotRespondingException) {
throw CoreException(ErrorType.MCP_SERVER_INTERNAL_ERROR)
}
}

fun generateDateRange(start: LocalDate, end: LocalDate): List<LocalDate> {
return (0..ChronoUnit.DAYS.between(start, end)).map { start.plusDays(it) }
return SandwichApiResponse(responses = responses)
}

override fun generateVacation(userId: String, request: GenerateVacationRequest): AiGenerateVacationApiResponse {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package noweekend.core.domain.sandwich

import java.time.LocalDate

data class Sandwich(val startDate: LocalDate, val endDate: LocalDate)
Loading