mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 07:53:09 +00:00
feat(server): expose the active trip as its own endpoint
GET /api/trips/active answers with the trip a user means by "my trip" right now: the one running today, else the next one starting, else the most recently started. Archived trips never qualify. That order is the dashboard hero's (sortTrips), on purpose - the startup redirect this is for has to land on the same trip the dashboard features, or the two would disagree about which trip is yours. Kept apart from list() because it runs on a first paint: four columns of one row instead of every trip with its per-trip day and place counts.
This commit is contained in:
@@ -1,4 +1,9 @@
|
||||
import { tripCreateRequestSchema, tripUpdateRequestSchema, tripAddMemberRequestSchema } from './trip.schema';
|
||||
import {
|
||||
tripCreateRequestSchema,
|
||||
tripUpdateRequestSchema,
|
||||
tripAddMemberRequestSchema,
|
||||
activeTripResponseSchema,
|
||||
} from './trip.schema';
|
||||
|
||||
import { describe, it, expect } from 'vitest';
|
||||
|
||||
@@ -29,3 +34,18 @@ describe('tripAddMemberRequestSchema', () => {
|
||||
expect(tripAddMemberRequestSchema.safeParse({}).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe('activeTripResponseSchema', () => {
|
||||
it('accepts a trip with or without dates, and an explicit null', () => {
|
||||
expect(activeTripResponseSchema.safeParse({ trip: { id: 1, title: 'Japan' } }).success).toBe(true);
|
||||
expect(
|
||||
activeTripResponseSchema.safeParse({ trip: { id: 1, title: 'Japan', start_date: null, end_date: null } }).success,
|
||||
).toBe(true);
|
||||
expect(activeTripResponseSchema.safeParse({ trip: null }).success).toBe(true);
|
||||
});
|
||||
|
||||
it('rejects a missing trip key — "no trip" is null, not absent', () => {
|
||||
expect(activeTripResponseSchema.safeParse({}).success).toBe(false);
|
||||
expect(activeTripResponseSchema.safeParse({ trip: { title: 'Japan' } }).success).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -118,3 +118,28 @@ export const tripTransferOwnershipRequestSchema = z.object({
|
||||
newOwnerId: z.number().int().positive(),
|
||||
});
|
||||
export type TripTransferOwnershipRequest = z.infer<typeof tripTransferOwnershipRequestSchema>;
|
||||
|
||||
/**
|
||||
* The one trip TREK opens on for a user who wants to land in their trip instead
|
||||
* of on the dashboard (GET /api/trips/active). Deliberately narrow: it is read
|
||||
* on the very first paint to decide where to navigate, so it carries the id plus
|
||||
* enough to label the destination — nothing that would make it as wide (or as
|
||||
* slow) as the trip list it replaces there.
|
||||
*
|
||||
* Relevance order matches the dashboard hero (client sortTrips): the trip
|
||||
* running today, else the next one starting, else the most recently started.
|
||||
*/
|
||||
export const activeTripSchema = z.object({
|
||||
id: z.number(),
|
||||
title: z.string(),
|
||||
start_date: z.string().nullable().optional(),
|
||||
end_date: z.string().nullable().optional(),
|
||||
});
|
||||
export type ActiveTrip = z.infer<typeof activeTripSchema>;
|
||||
|
||||
// null when the user has no unarchived trip at all — the caller falls back to
|
||||
// the dashboard rather than navigating into nothing.
|
||||
export const activeTripResponseSchema = z.object({
|
||||
trip: activeTripSchema.nullable(),
|
||||
});
|
||||
export type ActiveTripResponse = z.infer<typeof activeTripResponseSchema>;
|
||||
|
||||
Reference in New Issue
Block a user