fix(search): compute next-day date bound with real arithmetic, not string padding

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 <noreply@anthropic.com>
This commit is contained in:
Muluhabt
2026-07-28 09:28:34 +03:00
parent 11e6d226df
commit 1d6f89df29

View File

@@ -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,
);