Skip to content

Commit af1571d

Browse files
committed
Add docs
1 parent 63e6ba2 commit af1571d

1 file changed

Lines changed: 82 additions & 23 deletions

File tree

server/src/router/ride.ts

Lines changed: 82 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,21 @@
1+
/**
2+
* ride.ts — Express router for ride CRUD operations.
3+
*
4+
* Handles creating, reading, updating, and deleting rides, including support
5+
* for recurring ride series. Recurring rides share a recurrenceId and are
6+
* generated up to 4 months out from the series start.
7+
*
8+
* Routes:
9+
* GET / — query all rides (filterable)
10+
* GET /repeating — get all active recurring rides
11+
* GET /download — export rides for a given date as CSV
12+
* GET /:id — get a single ride by ID
13+
* GET /rider/:id — get all rides for a specific rider
14+
* POST / — create a ride (single or recurring)
15+
* PUT /:id — update a ride (?scope=single|future)
16+
* DELETE /:id — delete a ride (?scope=single|future)
17+
*/
18+
119
import express from 'express';
220
import { v4 as uuid } from 'uuid';
321
import * as csv from '@fast-csv/format';
@@ -72,17 +90,21 @@ function generateRecurringRides(
7290
return rides;
7391
}
7492

75-
// Transform Prisma ride (uppercase enums) to frontend-expected format (lowercase enums)
93+
/**
94+
* Convert prisma ride object to the format the frontend expects
95+
* Prisma stores enums in uppercase (e.g. "UPCOMING", "NOT_STARTED")
96+
* but frontend uses lowercase strings throughout.
97+
*/
7698
const formatRide = (ride: any) => ({
7799
...ride,
78100
type: ride.type?.toLowerCase(),
79101
status: ride.status?.toLowerCase(),
80102
schedulingState: ride.schedulingState?.toLowerCase(),
81103
});
82104

83-
// Build a Prisma update payload from request body fields.
105+
// Build prisma update payload from request body fields
84106
// Used for single-ride edits — includes schedulingState since admins manage that per-ride.
85-
// Do NOT use this for bulk future-ride regeneration.
107+
// not to be used for bulk future-ride regeneration.
86108
function buildUpdateData(body: any): any {
87109
const updateData: any = {};
88110

@@ -156,6 +178,9 @@ router.get('/diagnose', async (_req, res) => {
156178
}
157179
});
158180

181+
// Export all non-cancelled rides for a given date as a csv
182+
// Expects a `date` query param
183+
// Each row is one rider ... rides with multiple riders expand into multiple rows
159184
router.get('/download', async (req, res) => {
160185
try {
161186
const dateStart = moment(req.query.date as string).toDate();
@@ -367,16 +392,24 @@ router.post('/', validateUser('User'), async (req, res) => {
367392
const { recurrenceDays, recurrenceEndDate } = body;
368393
const timezone = body.timezone || 'America/New_York';
369394

370-
if (!recurrenceDays || !Array.isArray(recurrenceDays) || recurrenceDays.length === 0) {
395+
if (
396+
!recurrenceDays ||
397+
!Array.isArray(recurrenceDays) ||
398+
recurrenceDays.length === 0
399+
) {
371400
return res.status(400).send({
372401
err: 'recurrenceDays is required for recurring rides (array of 0–6, where 0=Sun).',
373402
});
374403
}
375404
if (!recurrenceEndDate) {
376-
return res.status(400).send({ err: 'recurrenceEndDate is required for recurring rides.' });
405+
return res
406+
.status(400)
407+
.send({ err: 'recurrenceEndDate is required for recurring rides.' });
377408
}
378409
if (!body.startTime || !body.endTime) {
379-
return res.status(400).send({ err: 'startTime and endTime are required.' });
410+
return res
411+
.status(400)
412+
.send({ err: 'startTime and endTime are required.' });
380413
}
381414
const maxEnd = moment().tz(timezone).add(4, 'months').endOf('day');
382415
if (moment.tz(recurrenceEndDate, timezone).isAfter(maxEnd)) {
@@ -391,25 +424,29 @@ router.post('/', validateUser('User'), async (req, res) => {
391424
return res.status(400).send({ err: 'At least one rider is required.' });
392425
}
393426

394-
const riderIds: string[] =
395-
hasRiders
396-
? body.riders.map((r: any) => (typeof r === 'string' ? r : r.id))
397-
: [typeof body.rider === 'string' ? body.rider : body.rider.id];
427+
const riderIds: string[] = hasRiders
428+
? body.riders.map((r: any) => (typeof r === 'string' ? r : r.id))
429+
: [typeof body.rider === 'string' ? body.rider : body.rider.id];
398430

399431
const startLocationId =
400432
typeof startLocation === 'string' ? startLocation : startLocation.id;
401433
const endLocationId =
402434
typeof endLocation === 'string' ? endLocation : endLocation.id;
403435
const driverId = body.driver
404-
? typeof body.driver === 'string' ? body.driver : body.driver.id
436+
? typeof body.driver === 'string'
437+
? body.driver
438+
: body.driver.id
405439
: null;
406440
const schedulingState: SchedulingState = driverId
407441
? SchedulingState.SCHEDULED
408442
: SchedulingState.UNSCHEDULED;
409443

410444
const recurrenceId = uuid();
411445
const ridesData = generateRecurringRides(
412-
{ startTime: new Date(body.startTime), endTime: new Date(body.endTime) },
446+
{
447+
startTime: new Date(body.startTime),
448+
endTime: new Date(body.endTime),
449+
},
413450
recurrenceDays,
414451
new Date(recurrenceEndDate),
415452
timezone,
@@ -446,7 +483,9 @@ router.post('/', validateUser('User'), async (req, res) => {
446483
)
447484
);
448485

449-
return res.status(200).send({ data: { recurrenceId, count: ridesData.length } });
486+
return res
487+
.status(200)
488+
.send({ data: { recurrenceId, count: ridesData.length } });
450489
}
451490

452491
const hasRiders = body.riders && body.riders.length > 0;
@@ -600,33 +639,49 @@ router.put('/:id', validateUser('User'), async (req, res) => {
600639
// --- edit all future rides in the series ---
601640
if (scope === 'future' && ride.recurrenceId) {
602641
const timezone = body.timezone ?? ride.timezone ?? 'America/New_York';
603-
const recurrenceDays: number[] = body.recurrenceDays ?? ride.recurrenceDays ?? [];
642+
const recurrenceDays: number[] =
643+
body.recurrenceDays ?? ride.recurrenceDays ?? [];
604644
const recurrenceEndDate = body.recurrenceEndDate
605645
? new Date(body.recurrenceEndDate)
606646
: ride.recurrenceEndDate;
607647

608648
if (!recurrenceEndDate) {
609-
return res.status(400).send({ err: 'recurrenceEndDate is missing from the series.' });
649+
return res
650+
.status(400)
651+
.send({ err: 'recurrenceEndDate is missing from the series.' });
610652
}
611653

612654
const startLocationId = body.startLocation
613-
? typeof body.startLocation === 'string' ? body.startLocation : body.startLocation.id
655+
? typeof body.startLocation === 'string'
656+
? body.startLocation
657+
: body.startLocation.id
614658
: ride.startLocationId;
615659
const endLocationId = body.endLocation
616-
? typeof body.endLocation === 'string' ? body.endLocation : body.endLocation.id
660+
? typeof body.endLocation === 'string'
661+
? body.endLocation
662+
: body.endLocation.id
617663
: ride.endLocationId;
618664
const driverId = Object.prototype.hasOwnProperty.call(body, 'driver')
619-
? body.driver ? (typeof body.driver === 'string' ? body.driver : body.driver.id) : null
665+
? body.driver
666+
? typeof body.driver === 'string'
667+
? body.driver
668+
: body.driver.id
669+
: null
620670
: ride.driverId;
621671
const riderIds: string[] = body.riders
622672
? body.riders.map((r: any) => (typeof r === 'string' ? r : r.id))
623673
: ride.riders.map((r) => r.id);
624-
const startTime = body.startTime ? new Date(body.startTime) : ride.startTime;
674+
const startTime = body.startTime
675+
? new Date(body.startTime)
676+
: ride.startTime;
625677
const endTime = body.endTime ? new Date(body.endTime) : ride.endTime;
626678

627679
// delete this ride and all future rides in the series
628680
await prisma.ride.deleteMany({
629-
where: { recurrenceId: ride.recurrenceId, startTime: { gte: ride.startTime } },
681+
where: {
682+
recurrenceId: ride.recurrenceId,
683+
startTime: { gte: ride.startTime },
684+
},
630685
});
631686

632687
// regenerate from this point forward with the new parameters
@@ -643,7 +698,9 @@ router.put('/:id', validateUser('User'), async (req, res) => {
643698
}
644699

645700
// schedulingState is intentionally NOT carried over — admins manage that per-ride
646-
const schedulingState = driverId ? SchedulingState.SCHEDULED : SchedulingState.UNSCHEDULED;
701+
const schedulingState = driverId
702+
? SchedulingState.SCHEDULED
703+
: SchedulingState.UNSCHEDULED;
647704

648705
await prisma.$transaction(
649706
ridesData.map((r) =>
@@ -707,7 +764,6 @@ router.put('/:id', validateUser('User'), async (req, res) => {
707764
}
708765
});
709766

710-
711767
// Delete an existing ride
712768
// ?scope=single (default) — delete only this ride
713769
// ?scope=future — delete this ride and all future rides in the series
@@ -760,7 +816,10 @@ router.delete('/:id', validateUser('User'), async (req, res) => {
760816
// delete this ride and all future rides in the series
761817
if (scope === 'future' && ride.recurrenceId) {
762818
const { count } = await prisma.ride.deleteMany({
763-
where: { recurrenceId: ride.recurrenceId, startTime: { gte: ride.startTime } },
819+
where: {
820+
recurrenceId: ride.recurrenceId,
821+
startTime: { gte: ride.startTime },
822+
},
764823
});
765824
return res.status(200).send({ deleted: count });
766825
}

0 commit comments

Comments
 (0)