mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-20 13:51:45 +00:00
fb36ae5678
The i18n and schema changes added code that wasn't prettier-formatted, and place.schema.ts imported categorySchema without using it. Run prettier over shared and remove the import so 'npm run lint' + 'format:check' pass.
132 lines
5.0 KiB
TypeScript
132 lines
5.0 KiB
TypeScript
import { tagSchema } from '../tag/tag.schema';
|
|
|
|
import { z } from 'zod';
|
|
|
|
/**
|
|
* Place API contract — single source of truth for the /api/trips/:tripId/places
|
|
* endpoints (place pool CRUD, GPX/map/list imports, image search, bulk delete).
|
|
*
|
|
* Trip-scoped; mutations use the 'place_edit' permission. The legacy route
|
|
* (server/src/routes/places.ts) wraps placeService and fires the journey
|
|
* place-created/updated/deleted hooks. Place rows are wide and provider-derived,
|
|
* so create/update payloads stay mostly open with `name` pinned; string fields
|
|
* are capped (name 200, description 2000, address 500, notes 2000) by the legacy
|
|
* validateStringLengths, reproduced in the controller.
|
|
*/
|
|
|
|
const open = z.record(z.string(), z.unknown());
|
|
|
|
/**
|
|
* Embedded category as returned on a place — a trimmed projection of the
|
|
* categories row (id/name/color/icon), built inline by placeService and
|
|
* getPlaceWithTags. `null` when the place has no category_id.
|
|
*/
|
|
export const placeCategorySchema = z
|
|
.object({
|
|
id: z.number(),
|
|
name: z.string().nullable(),
|
|
color: z.string().nullable(),
|
|
icon: z.string().nullable(),
|
|
})
|
|
.nullable();
|
|
export type PlaceCategory = z.infer<typeof placeCategorySchema>;
|
|
|
|
/**
|
|
* Full place entity as returned by the place list / get / create / update
|
|
* endpoints (server/src/services/placeService.ts -> getPlaceWithTags). All
|
|
* columns of the `places` table (see server/data DB) plus the joined `category`
|
|
* projection and `tags` array. Numbers (lat/lng/price) are SQLite REAL, ids are
|
|
* INTEGER; provider-derived columns are nullable.
|
|
*/
|
|
export const placeSchema = z.object({
|
|
id: z.number(),
|
|
trip_id: z.number(),
|
|
name: z.string(),
|
|
description: z.string().nullable().optional(),
|
|
lat: z.number().nullable().optional(),
|
|
lng: z.number().nullable().optional(),
|
|
address: z.string().nullable().optional(),
|
|
category_id: z.number().nullable().optional(),
|
|
price: z.number().nullable().optional(),
|
|
currency: z.string().nullable().optional(),
|
|
reservation_status: z.string().nullable().optional(),
|
|
reservation_notes: z.string().nullable().optional(),
|
|
reservation_datetime: z.string().nullable().optional(),
|
|
place_time: z.string().nullable().optional(),
|
|
end_time: z.string().nullable().optional(),
|
|
duration_minutes: z.number().nullable().optional(),
|
|
notes: z.string().nullable().optional(),
|
|
image_url: z.string().nullable().optional(),
|
|
google_place_id: z.string().nullable().optional(),
|
|
osm_id: z.string().nullable().optional(),
|
|
route_geometry: z.string().nullable().optional(),
|
|
website: z.string().nullable().optional(),
|
|
phone: z.string().nullable().optional(),
|
|
transport_mode: z.string().nullable().optional(),
|
|
created_at: z.string().optional(),
|
|
updated_at: z.string().optional(),
|
|
category: placeCategorySchema.optional(),
|
|
tags: z.array(tagSchema.partial()).optional(),
|
|
});
|
|
export type Place = z.infer<typeof placeSchema>;
|
|
|
|
/**
|
|
* Trimmed place projection embedded inside a day-assignment response
|
|
* (server/src/services/queryHelpers.ts -> formatAssignmentWithPlace). This is a
|
|
* SUBSET of the full place: no trip_id / osm_id / route_geometry / created_at /
|
|
* reservation_* — only the fields the planner needs to render the itinerary card.
|
|
*/
|
|
export const assignmentPlaceSchema = z.object({
|
|
id: z.number(),
|
|
name: z.string(),
|
|
description: z.string().nullable().optional(),
|
|
lat: z.number().nullable().optional(),
|
|
lng: z.number().nullable().optional(),
|
|
address: z.string().nullable().optional(),
|
|
category_id: z.number().nullable().optional(),
|
|
price: z.number().nullable().optional(),
|
|
currency: z.string().nullable().optional(),
|
|
place_time: z.string().nullable().optional(),
|
|
end_time: z.string().nullable().optional(),
|
|
duration_minutes: z.number().nullable().optional(),
|
|
notes: z.string().nullable().optional(),
|
|
image_url: z.string().nullable().optional(),
|
|
transport_mode: z.string().nullable().optional(),
|
|
google_place_id: z.string().nullable().optional(),
|
|
website: z.string().nullable().optional(),
|
|
phone: z.string().nullable().optional(),
|
|
category: placeCategorySchema.optional(),
|
|
tags: z.array(tagSchema.partial()).optional(),
|
|
});
|
|
export type AssignmentPlace = z.infer<typeof assignmentPlaceSchema>;
|
|
|
|
export const placeCreateRequestSchema = open.and(
|
|
z.object({ name: z.string().min(1) }),
|
|
);
|
|
export type PlaceCreateRequest = z.infer<typeof placeCreateRequestSchema>;
|
|
|
|
export const placeUpdateRequestSchema = open;
|
|
export type PlaceUpdateRequest = z.infer<typeof placeUpdateRequestSchema>;
|
|
|
|
export const placeBulkDeleteRequestSchema = z.object({
|
|
ids: z.array(z.number()),
|
|
});
|
|
export type PlaceBulkDeleteRequest = z.infer<
|
|
typeof placeBulkDeleteRequestSchema
|
|
>;
|
|
|
|
export const placeImportListRequestSchema = z.object({
|
|
url: z.string().min(1),
|
|
});
|
|
export type PlaceImportListRequest = z.infer<
|
|
typeof placeImportListRequestSchema
|
|
>;
|
|
|
|
/** Query filters for the place list. */
|
|
export const placeListQuerySchema = z.object({
|
|
search: z.string().optional(),
|
|
category: z.string().optional(),
|
|
tag: z.string().optional(),
|
|
});
|
|
export type PlaceListQuery = z.infer<typeof placeListQuerySchema>;
|