mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 15:06:32 +00:00
f6fd2722d0
GET /api/tours/library lists the tours of every trip the user owns or is a member of, with the trip each is in and how many tours each trip holds, to choose one to take over into the trip being planned. It is narrowed by a part of the name (matched ignoring case, a "%" or "_" just a character), a type or one trip, and the asking trip can be left out; up to 100 tours, and the answer says when there were more. GET /api/tours/library/:placeId previews one: its facts and its line thinned to 400 points, its ends kept. Neither is trip-scoped, so every answer is bounded by the user's own trips, read anew each call: a tour of a trip out of reach is "not found", nothing hints that it exists. Measures stay null where the tour has none. POST /api/trips/:tripId/tours/take-over copies one into this trip, which the guard checks with 'place_edit'; the source trip is checked against the user's trips as they are at that moment, and the source is read again inside the write. The copy is a tour of its own: its line, measures, settings and control points, without days, without its stops' links to places of the other trip and without the obstacle decisions taken there; it keeps the import record of its line. A copy that fails leaves nothing behind. Where it came from is kept as text (tours.taken_over_from_trip_title and taken_over_from_tour_name, migration 045100), not as a pointer, and read back in the provenance as takenOverFrom; a copy of such a copy names it too. The in-trip copy (P2C-07) and the take-over share one copy routine. MCP: list_tour_library and take_over_tour, over the same service and checks. The repository tests follow the hiking preferences column of MF-08.
125 lines
4.8 KiB
TypeScript
125 lines
4.8 KiB
TypeScript
import { Collection, EntityRepositoryType, type Opt, PrimaryKeyProp, type Ref, defineEntity, p } from '@mikro-orm/core';
|
|
import { ToursRepository } from '../repositories/Tours.repository';
|
|
import { Places } from './Places.entity';
|
|
import { TourObstacleReviews } from './TourObstacleReviews.entity';
|
|
import { TourSources } from './TourSources.entity';
|
|
import { TourTypes } from './TourTypes.entity';
|
|
import { TourWaypoints } from './TourWaypoints.entity';
|
|
|
|
export class Tours {
|
|
[EntityRepositoryType]?: ToursRepository;
|
|
[PrimaryKeyProp]?: 'place';
|
|
place?: Ref<Places> | null;
|
|
place_id?: number | null;
|
|
tour_type!: string;
|
|
distance?: number | null;
|
|
elevation_gain?: number | null;
|
|
elevation_loss?: number | null;
|
|
duration?: number | null;
|
|
difficulty?: string | null;
|
|
wanderer_ref?: string | null;
|
|
match_confidence?: number | null;
|
|
created_at?: string | null;
|
|
max_hiking_difficulty?: number | null = 2;
|
|
planned_duration_minutes?: number | null;
|
|
break_additional_minutes?: number | null;
|
|
derivedFromPlace?: Ref<Places> | null;
|
|
derived_from_place_id?: number | null;
|
|
recorded_route_geometry?: string | null;
|
|
origin: TToursOrigin & Opt = ToursOrigin.TREK;
|
|
origin_file_name?: string | null;
|
|
boat_type?: TToursBoatType | null;
|
|
water_context?: TToursWaterContext | null;
|
|
paddle_speed_kmh?: number | null;
|
|
portage_speed_kmh?: number | null;
|
|
track_marks?: string | null;
|
|
paddle_lake_style?: TToursPaddleLakeStyle | null;
|
|
revision: number & Opt = 1;
|
|
hiking_preferences?: string | null;
|
|
taken_over_from_trip_title?: string | null;
|
|
taken_over_from_tour_name?: string | null;
|
|
tourTypeRef!: Ref<TourTypes>;
|
|
tour_obstacle_reviews_collection = new Collection<TourObstacleReviews>(this);
|
|
tour_sources: Ref<TourSources> | null = null;
|
|
tour_waypoints_collection = new Collection<TourWaypoints>(this);
|
|
}
|
|
|
|
export const ToursOrigin = {
|
|
TREK: 'trek',
|
|
GPX: 'gpx',
|
|
WANDERER: 'wanderer',
|
|
} as const;
|
|
|
|
export type TToursOrigin = (typeof ToursOrigin)[keyof typeof ToursOrigin];
|
|
|
|
export const ToursBoatType = {
|
|
KAYAK: 'kayak',
|
|
CANOE: 'canoe',
|
|
SUP: 'sup',
|
|
OTHER: 'other',
|
|
} as const;
|
|
|
|
export type TToursBoatType = (typeof ToursBoatType)[keyof typeof ToursBoatType];
|
|
|
|
export const ToursWaterContext = {
|
|
RIVER: 'river',
|
|
LAKE: 'lake',
|
|
} as const;
|
|
|
|
export type TToursWaterContext = (typeof ToursWaterContext)[keyof typeof ToursWaterContext];
|
|
|
|
export const ToursPaddleLakeStyle = {
|
|
DIRECT: 'direct',
|
|
SHORE: 'shore',
|
|
} as const;
|
|
|
|
export type TToursPaddleLakeStyle = (typeof ToursPaddleLakeStyle)[keyof typeof ToursPaddleLakeStyle];
|
|
|
|
export const ToursSchema = defineEntity({
|
|
class: Tours,
|
|
repository: () => ToursRepository,
|
|
indexes: [
|
|
{
|
|
name: 'idx_tours_derived_from_place_id',
|
|
where: 'derived_from_place_id IS NOT NULL',
|
|
properties: ['derived_from_place_id'],
|
|
},
|
|
],
|
|
checks: [{ name: 'tours_revision_check', expression: 'revision >= 1' }],
|
|
properties: {
|
|
place: () => p.oneToOne(Places).primary().ref().deleteRule('cascade').nullable().hidden(),
|
|
place_id: p.integer().nullable().persist(false),
|
|
tour_type: p.text().persist(false).index('idx_tours_tour_type'),
|
|
distance: p.double().nullable(),
|
|
elevation_gain: p.double().nullable(),
|
|
elevation_loss: p.double().nullable(),
|
|
duration: p.double().nullable(),
|
|
difficulty: p.text().nullable(),
|
|
wanderer_ref: p.text().nullable(),
|
|
match_confidence: p.double().nullable(),
|
|
created_at: p.text().nullable().defaultRaw(`CURRENT_TIMESTAMP`),
|
|
max_hiking_difficulty: p.integer().nullable(),
|
|
planned_duration_minutes: p.integer().nullable(),
|
|
break_additional_minutes: p.integer().nullable(),
|
|
derivedFromPlace: () => p.manyToOne(Places).ref().nullable().hidden(),
|
|
derived_from_place_id: p.integer().nullable().persist(false),
|
|
recorded_route_geometry: p.text().nullable(),
|
|
origin: p.enum(() => ToursOrigin).default('trek'),
|
|
origin_file_name: p.text().nullable(),
|
|
boat_type: p.enum(() => ToursBoatType).nullable(),
|
|
water_context: p.enum(() => ToursWaterContext).nullable(),
|
|
paddle_speed_kmh: p.double().nullable(),
|
|
portage_speed_kmh: p.double().nullable(),
|
|
track_marks: p.text().nullable(),
|
|
paddle_lake_style: p.enum(() => ToursPaddleLakeStyle).nullable(),
|
|
revision: p.integer().default(1),
|
|
hiking_preferences: p.text().nullable(),
|
|
taken_over_from_trip_title: p.text().nullable(),
|
|
taken_over_from_tour_name: p.text().nullable(),
|
|
tourTypeRef: () => p.manyToOne(TourTypes).ref().joinColumn('tour_type').hidden().index('idx_tours_tour_type'),
|
|
tour_obstacle_reviews_collection: () => p.oneToMany(TourObstacleReviews).mappedBy('place').hidden(),
|
|
tour_sources: () => p.oneToOne(TourSources).ref().mappedBy('place').hidden(),
|
|
tour_waypoints_collection: () => p.oneToMany(TourWaypoints).mappedBy('place').hidden(),
|
|
},
|
|
});
|