Files
TREK/server/src/db/entities/Places.entity.ts
T
Maurice c956fe92ba Merge dev into feat/tours and port Tours onto MikroORM
dev now runs on MikroORM, so the positional migrations, seeds.ts and
DatabaseService the Tours server side was built on are gone. Behaviour
is unchanged; persistence moves onto dev's architecture:

- one MikroORM migration for tour_types, tours and tour_waypoints,
  idempotent so instances that ran the old positional steps keep
  their data; the hike tour type is seeded inside it, the addon row
  in the AddonSeeder
- entities and repositories for the three tables, tours domain on
  repositories and UnitOfWork
- tour facet joins in places, assignments and days re-applied on the
  repository code

Fixes found while porting:

- GPX import checks addon, trip access and permission in the handler
  instead of guards, so a refused upload gets a 403/404 instead of a
  connection reset
- copying a trip copies its tours and waypoints
- a tour cannot be moved onto a day that already has it
- updateTour checks existence inside its transaction

Adds tours e2e tests and rewrites the tours unit tests onto the
migrated test database.
2026-10-06 22:43:09 +02:00

131 lines
6.0 KiB
TypeScript

import { Collection, EntityRepositoryType, type Opt, type Ref, defineEntity, p } from '@mikro-orm/core';
import { PlacesRepository } from '../repositories/Places.repository';
import { DbTimestampType } from '../types';
import { BudgetItems } from './BudgetItems.entity';
import { Categories } from './Categories.entity';
import { DawarichVisitSuggestions } from './DawarichVisitSuggestions.entity';
import { DayAccommodations } from './DayAccommodations.entity';
import { DayAssignments } from './DayAssignments.entity';
import { FileLinks } from './FileLinks.entity';
import { JourneyEntries } from './JourneyEntries.entity';
import { Photos } from './Photos.entity';
import { PlaceRatings } from './PlaceRatings.entity';
import { PlaceRegions } from './PlaceRegions.entity';
import { Reservations } from './Reservations.entity';
import { RoadtripDayTracks } from './RoadtripDayTracks.entity';
import { Tags } from './Tags.entity';
import { Tours } from './Tours.entity';
import { TripFiles } from './TripFiles.entity';
import { Trips } from './Trips.entity';
export class Places {
[EntityRepositoryType]?: PlacesRepository;
id!: number & Opt;
trip!: Ref<Trips>;
trip_id!: number;
name!: string;
description?: string | null;
lat?: number | null;
lng?: number | null;
address?: string | null;
category?: Ref<Categories> | null;
category_id?: number | null;
price?: number | null;
currency?: string | null;
reservation_status?: string | null = 'none';
reservation_notes?: string | null;
reservation_datetime?: string | null;
place_time?: string | null;
end_time?: string | null;
duration_minutes?: number | null = 60;
notes?: string | null;
image_url?: string | null;
google_place_id?: string | null;
google_ftid?: string | null;
website?: string | null;
phone?: string | null;
transport_mode?: string | null = 'walking';
created_at?: string | null;
updated_at?: string | null;
osm_id?: string | null;
route_geometry?: string | null;
route_color?: string | null;
stop_type?: string | null;
fill_percent?: number | null;
amap_poi_id?: string | null;
source?: string | null;
email?: string | null;
opening_hours?: string | null;
place_tags = new Collection<Tags>(this);
budget_items_collection = new Collection<BudgetItems>(this);
dawarich_visit_suggestions_collection = new Collection<DawarichVisitSuggestions>(this);
day_accommodations_collection = new Collection<DayAccommodations>(this);
day_assignments_collection = new Collection<DayAssignments>(this);
file_links_collection = new Collection<FileLinks>(this);
journey_entries_collection = new Collection<JourneyEntries>(this);
photos_collection = new Collection<Photos>(this);
place_ratings_collection = new Collection<PlaceRatings>(this);
place_regions: Ref<PlaceRegions> | null = null;
reservations_collection = new Collection<Reservations>(this);
roadtrip_day_tracks_collection = new Collection<RoadtripDayTracks>(this);
tours: Ref<Tours> | null = null;
trip_files_collection = new Collection<TripFiles>(this);
}
export const PlacesSchema = defineEntity({
class: Places,
repository: () => PlacesRepository,
properties: {
id: p.integer().primary(),
trip: () => p.manyToOne(Trips).ref().deleteRule('cascade').hidden().index('idx_places_trip_id'),
trip_id: p.integer().persist(false).index('idx_places_trip_id'),
name: p.text(),
description: p.text().nullable(),
lat: p.double().nullable(),
lng: p.double().nullable(),
address: p.text().nullable(),
category: () => p.manyToOne(Categories).ref().nullable().hidden().index('idx_places_category_id'),
category_id: p.integer().nullable().persist(false).index('idx_places_category_id'),
price: p.double().nullable(),
currency: p.text().nullable(),
reservation_status: p.text().nullable(),
reservation_notes: p.text().nullable(),
reservation_datetime: p.text().nullable(),
place_time: p.text().nullable(),
end_time: p.text().nullable(),
duration_minutes: p.integer().nullable(),
notes: p.text().nullable(),
image_url: p.text().nullable(),
google_place_id: p.text().nullable(),
google_ftid: p.text().nullable(),
website: p.text().nullable(),
phone: p.text().nullable(),
transport_mode: p.text().nullable(),
created_at: p.type(DbTimestampType).nullable().defaultRaw(`CURRENT_TIMESTAMP`),
updated_at: p.type(DbTimestampType).nullable().defaultRaw(`CURRENT_TIMESTAMP`),
osm_id: p.text().nullable(),
route_geometry: p.text().nullable(),
route_color: p.text().nullable(),
stop_type: p.text().nullable(),
fill_percent: p.integer().nullable(),
amap_poi_id: p.text().nullable(),
source: p.text().nullable(),
email: p.text().nullable(),
opening_hours: p.text().nullable(),
place_tags: () => p.manyToMany(Tags).pivotTable('place_tags').joinColumn('place_id').inverseJoinColumn('tag_id').hidden(),
budget_items_collection: () => p.oneToMany(BudgetItems).mappedBy('place').hidden(),
dawarich_visit_suggestions_collection: () => p.oneToMany(DawarichVisitSuggestions).mappedBy('acceptedPlace').hidden(),
day_accommodations_collection: () => p.oneToMany(DayAccommodations).mappedBy('place').hidden(),
day_assignments_collection: () => p.oneToMany(DayAssignments).mappedBy('place').hidden(),
file_links_collection: () => p.oneToMany(FileLinks).mappedBy('place').hidden(),
journey_entries_collection: () => p.oneToMany(JourneyEntries).mappedBy('sourcePlace').hidden(),
photos_collection: () => p.oneToMany(Photos).mappedBy('place').hidden(),
place_ratings_collection: () => p.oneToMany(PlaceRatings).mappedBy('place').hidden(),
place_regions: () => p.oneToOne(PlaceRegions).ref().mappedBy('place').hidden(),
reservations_collection: () => p.oneToMany(Reservations).mappedBy('place').hidden(),
roadtrip_day_tracks_collection: () => p.oneToMany(RoadtripDayTracks).mappedBy('place').hidden(),
tours: () => p.oneToOne(Tours).ref().mappedBy('place').hidden(),
trip_files_collection: () => p.oneToMany(TripFiles).mappedBy('place').hidden(),
},
});