import { requireAuth } from "@/lib/jwt"; import { sql, query } from "@/lib/db"; import { matchNextDriver } from "@/lib/dispatch"; import { requireDriverProfile } from "@/lib/driver"; // GET — single ride by id, the rider's status-poll endpoint. If the ride is // still 'requested' with no offer in flight, kick auto-match before reading // so the rider's poll itself drives matching forward (no background worker). export async function GET(request: Request, { id }: { id: string }) { const auth = requireAuth(request); if ("error" in auth) return auth.error; const rideId = Number(id); if (!Number.isInteger(rideId)) { return Response.json({ error: "Invalid ride id." }, { status: 400 }); } try { const ride = await sql` SELECT status FROM rides WHERE ride_id = ${rideId} AND user_id = ${auth.userId} `; if (!ride[0]) { return Response.json({ error: "Ride not found." }, { status: 404 }); } // Lazy match: try to offer the ride to a driver if it's still requested. if (ride[0].status === "requested") { void matchNextDriver(rideId); } const rows = await sql` SELECT r.ride_id, r.origin_address, r.destination_address, r.origin_latitude, r.origin_longitude, r.destination_latitude, r.destination_longitude, r.ride_time, r.fare_price, r.payment_status, r.status, r.service, r.created_at, r.completed_at, r.cancelled_at, json_build_object( 'id', d.id, 'first_name', d.first_name, 'last_name', d.last_name, 'car_seats', d.car_seats, 'profile_image_url', d.profile_image_url, 'car_image_url', d.car_image_url, 'rating', d.rating, 'service', d.service, 'car_model', d.car_model, 'latitude', d.latitude, 'longitude', d.longitude ) AS driver FROM rides r LEFT JOIN drivers d ON d.id = r.driver_id WHERE r.ride_id = ${rideId} `; return Response.json({ data: rows[0] }); } catch (error) { console.error("[GET_RIDE]: ", error); return Response.json({ error: "Internal Server Error" }, { status: 500 }); } } // PATCH — ride lifecycle transitions. // Rider: { status: 'cancelled' } — only from 'requested' or 'accepted', and // only on their own ride. // Driver: { status: 'en_route' | 'completed' } — only on the ride they own // (driver_id = their profile), from the right prior state. export async function PATCH(request: Request, { id }: { id: string }) { const rideId = Number(id); if (!Number.isInteger(rideId)) { return Response.json({ error: "Invalid ride id." }, { status: 400 }); } let body: { status?: string }; try { body = await request.json(); } catch { return Response.json({ error: "Invalid JSON body." }, { status: 400 }); } const next = body.status; try { // Rider cancel — authenticate by ownership of the ride. if (next === "cancelled") { const auth = requireAuth(request); if ("error" in auth) return auth.error; const rows = await sql<{ status: string }>` UPDATE rides SET status = 'cancelled', cancelled_at = CURRENT_TIMESTAMP WHERE ride_id = ${rideId} AND user_id = ${auth.userId} AND status IN ('requested', 'accepted') RETURNING status `; if (!rows[0]) { return Response.json( { error: "Ride cannot be cancelled." }, { status: 409 }, ); } return Response.json({ data: { status: rows[0].status } }); } // Driver transitions — must be the driver assigned to the ride. if (next === "en_route" || next === "completed") { const result = await requireDriverProfile(request); if ("error" in result) return result.error; const { driverId } = result; const priorStatus = next === "en_route" ? "accepted" : "en_route"; const setClause = next === "completed" ? "status = $1, completed_at = CURRENT_TIMESTAMP, driver_id = $2" : "status = $1, driver_id = $2"; const rows = await query<{ status: string }>( `UPDATE rides SET ${setClause} WHERE ride_id = $3 AND driver_id = $2 AND status = $4 RETURNING status`, [next, driverId, rideId, priorStatus], ); if (!rows[0]) { return Response.json( { error: "Ride cannot transition to that state." }, { status: 409 }, ); } return Response.json({ data: { status: rows[0].status } }); } return Response.json({ error: "Unknown status transition." }, { status: 400 }); } catch (error) { console.error("[PATCH_RIDE]: ", error); return Response.json({ error: "Internal Server Error" }, { status: 500 }); } }