import { requireDriverProfile } from "@/lib/driver"; import { transaction } from "@/lib/db"; import { matchNextDriver } from "@/lib/dispatch"; // POST — a driver responds to a ride offer. // { action: 'accept' } — claim the ride: offer -> accepted, ride -> accepted, // ride.driver_id set to this driver. Guarded so only // the offered driver can accept, and only while the // offer is still 'offered' (not expired/timed out). // { action: 'decline' } — release the ride: offer -> declined, then offer // it to the next-nearest driver via matchNextDriver. export async function POST(req: Request, { id }: { id: string }) { const rideId = Number(id); if (!Number.isInteger(rideId)) { return Response.json({ error: "Invalid ride id." }, { status: 400 }); } const result = await requireDriverProfile(req); if ("error" in result) return result.error; const { driverId } = result; let body: { action?: string }; try { body = await req.json(); } catch { return Response.json({ error: "Invalid JSON body." }, { status: 400 }); } const action = body.action; if (action !== "accept" && action !== "decline") { return Response.json( { error: "action must be 'accept' or 'decline'." }, { status: 400 }, ); } try { if (action === "accept") { const claimed = await transaction(async (tx) => { // Atomically flip the offer to accepted only if it's still offered to // this driver. This is the race guard: two drivers can't both accept, // and an expired offer can't be revived. const offer = await tx<{ id: number }>` UPDATE ride_offers SET status = 'accepted', responded_at = CURRENT_TIMESTAMP WHERE ride_id = ${rideId} AND driver_id = ${driverId} AND status = 'offered' RETURNING id `; if (!offer[0]) return null; // Assign the ride to this driver. The status='requested' guard means // we never overwrite a ride another driver already accepted. const ride = await tx` UPDATE rides SET status = 'accepted', driver_id = ${driverId} WHERE ride_id = ${rideId} AND status = 'requested' RETURNING ride_id `; if (!ride[0]) return null; return offer[0].id; }); if (claimed === null) { return Response.json( { error: "This offer is no longer available." }, { status: 409 }, ); } return Response.json({ data: { action: "accepted" } }); } // Decline: mark the offer declined and offer the ride to the next driver. const declined = await transaction(async (tx) => { const offer = await tx` UPDATE ride_offers SET status = 'declined', responded_at = CURRENT_TIMESTAMP WHERE ride_id = ${rideId} AND driver_id = ${driverId} AND status = 'offered' RETURNING id `; return offer[0]?.id ?? null; }); if (declined === null) { return Response.json( { error: "This offer is no longer available." }, { status: 409 }, ); } void matchNextDriver(rideId); return Response.json({ data: { action: "declined" } }); } catch (error) { console.error("[RIDE_RESPOND]: ", error); return Response.json({ error: "Internal Server Error" }, { status: 500 }); } }