From af39ab03947729b3bc62ba7a7aaf22a33aa13e88 Mon Sep 17 00:00:00 2001 From: Stephanos A Date: Sat, 18 Jul 2026 11:44:11 +0300 Subject: [PATCH] Migration issue resolution --- .../migration.sql | 4 + .../migration.sql | 4 + .../migration.sql | 4 + .../migration.sql | 33 ++ booking-checker.html | 530 ++++++++++++++++++ booking-extractor.html | 256 +++++++++ booking-proxy.mjs | 53 ++ ticket-extractor.html | 239 ++++++++ 8 files changed, 1123 insertions(+) create mode 100644 booking-checker.html create mode 100644 booking-extractor.html create mode 100644 booking-proxy.mjs create mode 100644 ticket-extractor.html diff --git a/apps/edr-passenger-api/prisma/migrations/20260716202849_add_journey_segment_unique_seat_per_schedule/migration.sql b/apps/edr-passenger-api/prisma/migrations/20260716202849_add_journey_segment_unique_seat_per_schedule/migration.sql index 2608b4cbf..83a49ef67 100644 --- a/apps/edr-passenger-api/prisma/migrations/20260716202849_add_journey_segment_unique_seat_per_schedule/migration.sql +++ b/apps/edr-passenger-api/prisma/migrations/20260716202849_add_journey_segment_unique_seat_per_schedule/migration.sql @@ -1,3 +1,4 @@ +<<<<<<< Updated upstream -- Remove duplicate JourneySegment rows, keeping the one with the lowest id -- (earliest created) per (scheduleId, seatId, departureStationId) group. -- This cleans up any existing double-bookings before the unique index is applied. @@ -16,3 +17,6 @@ AND "seatId" IS NOT NULL; CREATE UNIQUE INDEX "JourneySegment_scheduleId_seatId_departureStationId_key" ON passenger."JourneySegment" ("scheduleId", "seatId", "departureStationId") WHERE "seatId" IS NOT NULL; +======= +-- Migration already applied directly to the database. +>>>>>>> Stashed changes diff --git a/apps/edr-passenger-api/prisma/migrations/20260717000001_add_route_checkin_minutes_rename_stop_status/migration.sql b/apps/edr-passenger-api/prisma/migrations/20260717000001_add_route_checkin_minutes_rename_stop_status/migration.sql index 375f40f7e..e04c02621 100644 --- a/apps/edr-passenger-api/prisma/migrations/20260717000001_add_route_checkin_minutes_rename_stop_status/migration.sql +++ b/apps/edr-passenger-api/prisma/migrations/20260717000001_add_route_checkin_minutes_rename_stop_status/migration.sql @@ -1,3 +1,4 @@ +<<<<<<< Updated upstream -- Rename StopStatus enum values to reflect segment-level booking lifecycle. -- UPCOMING → OPEN (segment is bookable) -- APPROACHING → CHECKIN_CLOSED (within check-in cutoff, no new bookings) @@ -10,3 +11,6 @@ ALTER TYPE "passenger"."StopStatus" RENAME VALUE 'CURRENT' TO 'BOARDED'; -- Add per-route check-in window. Each route can define how many minutes before -- a stop's planned departure check-in is closed. Defaults to 30 minutes. ALTER TABLE "passenger"."Route" ADD COLUMN "checkinMinutesBefore" INTEGER NOT NULL DEFAULT 30; +======= +-- Migration already applied directly to the database. +>>>>>>> Stashed changes diff --git a/apps/edr-passenger-api/prisma/migrations/20260717000002_add_route_stop_checkin_minutes/migration.sql b/apps/edr-passenger-api/prisma/migrations/20260717000002_add_route_stop_checkin_minutes/migration.sql index 0b0995292..5fd9ae0f5 100644 --- a/apps/edr-passenger-api/prisma/migrations/20260717000002_add_route_stop_checkin_minutes/migration.sql +++ b/apps/edr-passenger-api/prisma/migrations/20260717000002_add_route_stop_checkin_minutes/migration.sql @@ -1 +1,5 @@ +<<<<<<< Updated upstream ALTER TABLE "passenger"."RouteStop" ADD COLUMN "checkinMinutesBefore" INTEGER; +======= +-- Migration already applied directly to the database. +>>>>>>> Stashed changes diff --git a/apps/edr-passenger-api/prisma/migrations/20260717000003_booking_seat_schedule_unique/migration.sql b/apps/edr-passenger-api/prisma/migrations/20260717000003_booking_seat_schedule_unique/migration.sql index 2a9a1be65..8553fc81b 100644 --- a/apps/edr-passenger-api/prisma/migrations/20260717000003_booking_seat_schedule_unique/migration.sql +++ b/apps/edr-passenger-api/prisma/migrations/20260717000003_booking_seat_schedule_unique/migration.sql @@ -1,13 +1,21 @@ -- Make scheduleId non-nullable: backfill from the parent booking, then add NOT NULL. UPDATE passenger."BookingSeat" bs +<<<<<<< Updated upstream SET schedule_id = b.schedule_id FROM passenger."Booking" b WHERE bs.booking_id = b.id AND bs.schedule_id IS NULL +======= +SET "scheduleId" = b."scheduleId" +FROM passenger."Booking" b +WHERE bs."bookingId" = b.id + AND bs."scheduleId" IS NULL +>>>>>>> Stashed changes AND bs.leg = 1; -- For leg=2 rows (return/transit), pull from the booking's returnScheduleId / leg2ScheduleId. UPDATE passenger."BookingSeat" bs +<<<<<<< Updated upstream SET schedule_id = COALESCE( (b.return_schedule_id), (b.leg2_schedule_id), @@ -31,3 +39,28 @@ ALTER TABLE passenger."BookingSeat" ALTER COLUMN schedule_id SET NOT NULL; -- Add the unique constraint that is the actual double-booking guard. CREATE UNIQUE INDEX "BookingSeat_scheduleId_seatId_key" ON passenger."BookingSeat"(schedule_id, seat_id); +======= +SET "scheduleId" = COALESCE( + b."returnScheduleId", + b."leg2ScheduleId", + b."scheduleId" +) +FROM passenger."Booking" b +WHERE bs."bookingId" = b.id + AND bs."scheduleId" IS NULL + AND bs.leg = 2; + +-- Catch any remaining NULLs using the booking's schedule. +UPDATE passenger."BookingSeat" bs +SET "scheduleId" = b."scheduleId" +FROM passenger."Booking" b +WHERE bs."bookingId" = b.id + AND bs."scheduleId" IS NULL; + +-- Now enforce NOT NULL. +ALTER TABLE passenger."BookingSeat" ALTER COLUMN "scheduleId" SET NOT NULL; + +-- Add the unique constraint that is the actual double-booking guard. +CREATE UNIQUE INDEX "BookingSeat_scheduleId_seatId_key" + ON passenger."BookingSeat"("scheduleId", "seatId"); +>>>>>>> Stashed changes diff --git a/booking-checker.html b/booking-checker.html new file mode 100644 index 000000000..9d2b7ded4 --- /dev/null +++ b/booking-checker.html @@ -0,0 +1,530 @@ + + + + + + EDR Booking Checker + + + + +

EDR Booking Checker

+ +
+ + +

No trailing slash. e.g. https://api.edrsc.com

+
+ +
+ + +

Supports any format: one per line, comma-separated, or {REF1,REF2} groups.

+ +
+ + + + +
+
+
+
+
+ +
+
+ +
+ + + + + +
+
+ + + + + + + +
+ +
+ + + + + + + + +
JourneyDuplicate Bookings
+
+
+ + + + + diff --git a/booking-extractor.html b/booking-extractor.html new file mode 100644 index 000000000..844c98b2c --- /dev/null +++ b/booking-extractor.html @@ -0,0 +1,256 @@ + + + + + + EDR Booking Extractor + + + + +

EDR Booking Extractor

+ +
+ +
+ + Drop bookings.json here or click to browse +
+

Accepts a JSON array of bookings or an object with a bookings key.

+
+ + + +
+
+ +
+
+
+ + + +
+
+ + + + + + + + + + + + + + + + + + + + + +
#Booking RefStatusBooking TypePhoneEmailDepartureOriginDestinationPassenger(s)Coach - SeatPayment MethodPayment StatusTotal (DJF)Created At
+
+
+ + + + + diff --git a/booking-proxy.mjs b/booking-proxy.mjs new file mode 100644 index 000000000..27f9248ee --- /dev/null +++ b/booking-proxy.mjs @@ -0,0 +1,53 @@ +import http from 'http'; +import https from 'https'; +import fs from 'fs'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const PORT = 8080; +const __dir = path.dirname(fileURLToPath(import.meta.url)); + +const server = http.createServer((req, res) => { + res.setHeader('Access-Control-Allow-Origin', '*'); + res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization'); + + if (req.method === 'OPTIONS') { res.writeHead(204); res.end(); return; } + + // Serve any .html file in the same directory + if (req.url === '/' || req.url.endsWith('.html')) { + const filename = req.url === '/' ? 'booking-checker.html' : req.url.slice(1); + const filepath = path.join(__dir, filename); + if (fs.existsSync(filepath)) { + res.writeHead(200, { 'Content-Type': 'text/html' }); + fs.createReadStream(filepath).pipe(res); + } else { + res.writeHead(404); res.end('Not found'); + } + return; + } + + // Proxy /proxy?url= + if (req.url.startsWith('/proxy?url=')) { + const target = decodeURIComponent(req.url.slice('/proxy?url='.length)); + const parsed = new URL(target); + const mod = parsed.protocol === 'https:' ? https : http; + const options = { + hostname: parsed.hostname, + port: parsed.port || (parsed.protocol === 'https:' ? 443 : 80), + path: parsed.pathname + parsed.search, + method: req.method, + headers: { ...req.headers, host: parsed.hostname }, + }; + const proxy = mod.request(options, (apiRes) => { + res.writeHead(apiRes.statusCode, apiRes.headers); + apiRes.pipe(res); + }); + proxy.on('error', (e) => { res.writeHead(502); res.end(e.message); }); + req.pipe(proxy); + return; + } + + res.writeHead(404); res.end(); +}); + +server.listen(PORT, () => console.log(`Booking checker: http://localhost:${PORT}/booking-checker.html`)); diff --git a/ticket-extractor.html b/ticket-extractor.html new file mode 100644 index 000000000..17be60576 --- /dev/null +++ b/ticket-extractor.html @@ -0,0 +1,239 @@ + + + + + + EDR Ticket Extractor + + + + +

EDR Ticket Extractor

+ +
+ +
+ + Drop tickets.json here or click to browse +
+

Accepts a JSON array of tickets or an object with a tickets key.

+
+ + + +
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + +
#Ticket No.Booking RefPassengerPhoneEmailJourney TypeOriginDestinationSeat ClassCoachSeat
+
+
+ + + + +