mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
refactor(trips): the trip span check and the feed token strip move into trips helpers
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
import { ValidationError } from '../common/domain-errors';
|
||||
import { MAX_TRIP_DAYS, tripSpanDays } from '@trek/shared';
|
||||
|
||||
/**
|
||||
* Pure helpers of the trips domain, moved out of trips.service.ts. Nothing
|
||||
* here touches the DB, so they stay plain exports rather than methods on
|
||||
* TripsService.
|
||||
*/
|
||||
|
||||
/**
|
||||
* The date range is refused, not cut short: generateDays used to clip the day
|
||||
* rows at the limit while the trip kept its full end date, so everything past
|
||||
* the cut-off had dates but no day to go on (#2403). An inverted range is
|
||||
* refused here as well, because a start date moved past the stored end date
|
||||
* arrives on its own and would otherwise empty the trip.
|
||||
*/
|
||||
export function assertTripSpan(startDate: string, endDate: string) {
|
||||
const span = tripSpanDays(startDate, endDate);
|
||||
if (span < 1) throw new ValidationError('End date must be after start date');
|
||||
if (span > MAX_TRIP_DAYS) throw new ValidationError(`A trip can span at most ${MAX_TRIP_DAYS} days`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips `feed_token` from a trip row on its way out.
|
||||
*
|
||||
* The column is the sole credential for the anonymous /api/feed/trip/:token.ics
|
||||
* route, and `SELECT t.*` hands it to every reader of the trip. Gating the
|
||||
* token endpoint on `share_manage` means nothing while any member can read the
|
||||
* same value out of the trip payload, so the two go together. No TREK client
|
||||
* reads the field (it is absent from client/ and shared/ entirely).
|
||||
*/
|
||||
export function withoutFeedToken<T>(row: T): T {
|
||||
if (row && typeof row === 'object') delete (row as { feed_token?: unknown }).feed_token;
|
||||
return row;
|
||||
}
|
||||
@@ -27,11 +27,9 @@ import { Tours } from '../../db/entities/Tours.entity';
|
||||
import { TourWaypoints } from '../../db/entities/TourWaypoints.entity';
|
||||
import type { TourWaypointRow } from '../../db/repositories/TourWaypoints.repository';
|
||||
import {
|
||||
MAX_TRIP_DAYS,
|
||||
addIsoDays,
|
||||
planDayGrid,
|
||||
resolveDayGridRange,
|
||||
tripSpanDays,
|
||||
type ActiveTrip,
|
||||
type DayGridPlan,
|
||||
type DayGridRemoval,
|
||||
@@ -53,34 +51,7 @@ import { NotFoundError, ValidationError } from '../common/domain-errors';
|
||||
import { UnitOfWork } from '../database/unit-of-work';
|
||||
import { legacyBoundIntegerText } from '../common/row-id';
|
||||
import { appClock } from '../common/timezoneService';
|
||||
|
||||
/**
|
||||
* The date range is refused, not cut short: generateDays used to clip the day
|
||||
* rows at the limit while the trip kept its full end date, so everything past
|
||||
* the cut-off had dates but no day to go on (#2403). An inverted range is
|
||||
* refused here as well, because a start date moved past the stored end date
|
||||
* arrives on its own and would otherwise empty the trip.
|
||||
*/
|
||||
function assertTripSpan(startDate: string, endDate: string) {
|
||||
const span = tripSpanDays(startDate, endDate);
|
||||
if (span < 1) throw new ValidationError('End date must be after start date');
|
||||
if (span > MAX_TRIP_DAYS) throw new ValidationError(`A trip can span at most ${MAX_TRIP_DAYS} days`);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips `feed_token` from a trip row on its way out.
|
||||
*
|
||||
* The column is the sole credential for the anonymous /api/feed/trip/:token.ics
|
||||
* route, and `SELECT t.*` hands it to every reader of the trip. Gating the
|
||||
* token endpoint on `share_manage` means nothing while any member can read the
|
||||
* same value out of the trip payload, so the two go together. No TREK client
|
||||
* reads the field (it is absent from client/ and shared/ entirely).
|
||||
*/
|
||||
export function withoutFeedToken<T>(row: T): T {
|
||||
if (row && typeof row === 'object') delete (row as Record<string, unknown>).feed_token;
|
||||
return row;
|
||||
}
|
||||
|
||||
import { assertTripSpan } from './trips.helpers';
|
||||
|
||||
interface CreateTripData {
|
||||
title: string;
|
||||
@@ -1003,3 +974,6 @@ export class TripsService {
|
||||
// trip aggregate; re-exported here because nine files already import them from
|
||||
// this module.
|
||||
export { NotFoundError, ValidationError };
|
||||
|
||||
// Lives in trips.helpers.ts; re-exported for the importers that read it from here.
|
||||
export { withoutFeedToken } from './trips.helpers';
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/**
|
||||
* The pure helpers of the trips domain: the date range a trip may span and the
|
||||
* feed token a trip row loses on its way out. TRIP-HELPERS-001 to 003.
|
||||
*/
|
||||
import { assertTripSpan, withoutFeedToken } from '../../../src/nest/trips/trips.helpers';
|
||||
import { MAX_TRIP_DAYS, addIsoDays } from '@trek/shared';
|
||||
|
||||
import { describe, expect, it } from 'vitest';
|
||||
|
||||
describe('assertTripSpan', () => {
|
||||
it('TRIP-HELPERS-001: takes a range from a single day up to the limit', () => {
|
||||
const lastAllowed = addIsoDays('2025-07-01', MAX_TRIP_DAYS - 1);
|
||||
expect(() => assertTripSpan('2025-07-01', '2025-07-01')).not.toThrow();
|
||||
expect(() => assertTripSpan('2025-07-01', lastAllowed)).not.toThrow();
|
||||
});
|
||||
|
||||
it('TRIP-HELPERS-002: refuses an inverted range and one past the limit', () => {
|
||||
const tooLate = addIsoDays('2025-07-01', MAX_TRIP_DAYS);
|
||||
expect(() => assertTripSpan('2025-07-02', '2025-07-01')).toThrow('End date must be after start date');
|
||||
expect(() => assertTripSpan('2025-07-01', tooLate)).toThrow(`A trip can span at most ${MAX_TRIP_DAYS} days`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('withoutFeedToken', () => {
|
||||
it('TRIP-HELPERS-003: drops feed_token from the row it was given and passes anything else through', () => {
|
||||
const row = { id: 1, title: 'Japan', feed_token: 'feed-1' };
|
||||
expect(withoutFeedToken(row)).toBe(row);
|
||||
expect(row).toEqual({ id: 1, title: 'Japan' });
|
||||
expect(row).not.toHaveProperty('feed_token');
|
||||
expect(withoutFeedToken(undefined)).toBeUndefined();
|
||||
expect(withoutFeedToken(null)).toBeNull();
|
||||
expect(withoutFeedToken(42)).toBe(42);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user