// Shared ownership + liveness checks for anything scoped to a ride that both // the rider and the assigned driver can touch (chat messages, calls). A // rider authenticates via requireAuth (users.id UUID); a driver authenticates // via requireDriverProfile (drivers.id INT). Because a single user account can // be both a rider and a driver, we check the RIDER path first — otherwise a // user who is also a driver would be misrouted to the driver branch for their // own ride. import { requireAuth } from "@/lib/jwt"; import { requireDriverProfile } from "@/lib/driver"; import { sql } from "@/lib/db"; import { CONNECTED_STATUS_ARRAY } from "@/lib/ride-lifecycle"; export type RideParticipant = | { role: "rider"; userId: string; driverId: null } | { role: "driver"; userId: string; driverId: number }; export type ParticipantError = { error: Response }; // Proves the caller is the ride's rider or its assigned driver and returns // which one, so the caller can stamp sender_type / caller_type. Returns a // ready-to-ship 403/401 error Response otherwise. export const requireRideParticipant = async ( req: Request, rideId: number, ): Promise => { // Rider path first: a user who owns the ride. const auth = requireAuth(req); if (!("error" in auth)) { const riderRows = await sql<{ user_id: string }>` SELECT user_id FROM rides WHERE ride_id = ${rideId} AND user_id = ${auth.userId} `; if (riderRows[0]) { return { role: "rider", userId: auth.userId, driverId: null }; } } // Driver path: a user with a driver profile assigned to the ride. const driver = await requireDriverProfile(req); if ("error" in driver) { // If the request had no valid auth at all, surface that 401 rather than a // generic 403, so the client can re-authenticate. if ("error" in auth) return { error: auth.error }; return { error: Response.json( { error: "You are not part of this ride." }, { status: 403 }, ), }; } const driverRows = await sql<{ ride_id: number }>` SELECT ride_id FROM rides WHERE ride_id = ${rideId} AND driver_id = ${driver.driverId} `; if (!driverRows[0]) { return { error: Response.json( { error: "You are not part of this ride." }, { status: 403 }, ), }; } return { role: "driver", userId: driver.auth.userId, driverId: driver.driverId, }; }; // A ride is "active" (chat/call allowed) while a driver is assigned and the // ride is en route to or past acceptance but not yet terminal. export const rideIsActive = async (rideId: number): Promise => { const rows = await sql<{ status: string }>` SELECT status FROM rides WHERE ride_id = ${rideId} AND driver_id IS NOT NULL AND status = ANY(${CONNECTED_STATUS_ARRAY}::text[]) `; return Boolean(rows[0]); };