From 1d6f89df29b943f7be4531dac90f7b30dda9b7b7 Mon Sep 17 00:00:00 2001 From: Muluhabt Date: Tue, 28 Jul 2026 09:28:34 +0300 Subject: [PATCH] fix(search): compute next-day date bound with real arithmetic, not string padding MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Searching on the last day of a month (e.g. 2026-07-31) built an invalid "next day" string like "2026-07-32" for the Prisma date-range filter, crashing POST /search with a PrismaClientValidationError in production. Affected searchSchedules, searchAlternatives, classifyEmptySearch, and searchTransitOptions — all four built the bound the same way. Co-Authored-By: Claude Sonnet 5 --- .../src/modules/search/search.service.ts | 44 +++++++------------ 1 file changed, 16 insertions(+), 28 deletions(-) diff --git a/apps/edr-passenger-api/src/modules/search/search.service.ts b/apps/edr-passenger-api/src/modules/search/search.service.ts index de07f5d6a..8820619d8 100644 --- a/apps/edr-passenger-api/src/modules/search/search.service.ts +++ b/apps/edr-passenger-api/src/modules/search/search.service.ts @@ -224,13 +224,10 @@ export class SearchService { childCount?: number, nationality?: string, ) { - const [y, m, d] = dateStr.split("-").map(Number); - const requestedDate = new Date( - `${String(y)}-${String(m).padStart(2, "0")}-${String(d).padStart(2, "0")}T00:00:00+03:00`, - ); - const requestedNextDay = new Date( - `${String(y)}-${String(m).padStart(2, "0")}-${String(d + 1).padStart(2, "0")}T00:00:00+03:00`, - ); + // Real millisecond arithmetic, not string-padded day-of-month increment — the latter + // produces an invalid date (e.g. "2026-07-32") for any search on the last day of a month. + const requestedDate = new Date(`${dateStr}T00:00:00+03:00`); + const requestedNextDay = new Date(requestedDate.getTime() + 24 * 60 * 60 * 1000); const now = new Date(); const totalPassengers = adultCount + (childCount ?? 0); const NEEDED = 3; @@ -314,13 +311,10 @@ export class SearchService { childCount?: number, nationality?: string, ) { - const [y, m, d] = dateStr.split("-").map(Number); - const date = new Date( - `${String(y)}-${String(m).padStart(2, "0")}-${String(d).padStart(2, "0")}T00:00:00+03:00`, - ); - const nextDay = new Date( - `${String(y)}-${String(m).padStart(2, "0")}-${String(d + 1).padStart(2, "0")}T00:00:00+03:00`, - ); + // Real millisecond arithmetic, not string-padded day-of-month increment — the latter + // produces an invalid date (e.g. "2026-07-32") for any search on the last day of a month. + const date = new Date(`${dateStr}T00:00:00+03:00`); + const nextDay = new Date(date.getTime() + 24 * 60 * 60 * 1000); const totalPassengers = adultCount + (childCount ?? 0); // Match on the schedule's own departure DATE only — do NOT use `now` as a lower bound here. @@ -390,13 +384,10 @@ export class SearchService { // 2. A route exists — is there any schedule at all on the requested date for this pair // (regardless of status/package/coach/cutoff — those are checked next)? - const [y, m, d] = dateStr.split("-").map(Number); - const date = new Date( - `${String(y)}-${String(m).padStart(2, "0")}-${String(d).padStart(2, "0")}T00:00:00+03:00`, - ); - const nextDay = new Date( - `${String(y)}-${String(m).padStart(2, "0")}-${String(d + 1).padStart(2, "0")}T00:00:00+03:00`, - ); + // Real millisecond arithmetic, not string-padded day-of-month increment — the latter + // produces an invalid date (e.g. "2026-07-32") for any search on the last day of a month. + const date = new Date(`${dateStr}T00:00:00+03:00`); + const nextDay = new Date(date.getTime() + 24 * 60 * 60 * 1000); const dayCandidates = await this.prisma.trainSchedule.findMany({ where: { departureAt: { gte: date, lt: nextDay }, stopTimes: { some: { stationId: originStationId } } }, select: { @@ -557,13 +548,10 @@ export class SearchService { childCount?: number, nationality?: string, ) { - const [y, m, d] = dateStr.split("-").map(Number); - const dayStart = new Date( - `${String(y)}-${String(m).padStart(2, "0")}-${String(d).padStart(2, "0")}T00:00:00+03:00`, - ); - const dayEnd = new Date( - `${String(y)}-${String(m).padStart(2, "0")}-${String(d + 1).padStart(2, "0")}T00:00:00+03:00`, - ); + // Real millisecond arithmetic, not string-padded day-of-month increment — the latter + // produces an invalid date (e.g. "2026-07-32") for any search on the last day of a month. + const dayStart = new Date(`${dateStr}T00:00:00+03:00`); + const dayEnd = new Date(dayStart.getTime() + 24 * 60 * 60 * 1000); const leg2WindowEnd = new Date( dayEnd.getTime() + this.MAX_CONNECTION_MINUTES * 60_000, );