mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-20 13:51:45 +00:00
247433fb2a
* fix(journey): authorize reads of the journey share link GET /api/journeys/:id/share-link now requires journey access (canAccessJourney), matching the create/delete share-link routes and the get_journey_share_link MCP tool. Returns no link when the caller lacks access to the journey. * feat(costs): rework Budget into Costs — Splitwise-style, multi-currency, mobile Renames the Budget addon to "Costs" (UI only) and reworks it into a Tricount/ Splitwise-style cost tracker: multiple payers per expense, equal split across chosen members, settle-up with persisted history + undo, 12 fixed categories, per-expense currency with live FX conversion to a user-set display currency (Settings -> Display), and locale-correct money formatting. Adds a desktop and a dedicated mobile layout. A migration backfills existing budget items (single payer, split members, currency). Closes #551 (per-expense currency). Also switches the app font to self-hosted Poppins (Geist for secondary subtext), replacing the Google Fonts CDN dependency. * fix(costs): neutral dashboard dark palette + liquid glass, full page width, entry-count badge - Dark mode used a warm oklch palette that read brownish; switch to the neutral zinc tokens used by the dashboard (#121215 bg, #f4f4f5 ink) and add a subtle backdrop-blur glass on cards. - Costs now uses the full available page width on desktop instead of a 1280px cap. - Render the expense count next to the Expenses title as a badge. - Adapt budget/journey unit tests to the new payer-based settlement model and the Costs rename (category default 'other', Costs tab/CostsPanel). * fix(costs): drop the entry-count badge, always show row edit/delete actions Removes the count badge next to the Expenses title and makes the per-row edit/delete actions permanently visible (no longer hover-only) on desktop too. * feat(costs): currency-native money formatting, custom select/date, rename addon to Costs - Format every amount in its own currency convention (symbol position, grouping and decimal separators) regardless of app language, via a currency->locale map (EUR -> '12,00 €', USD -> '$12.00', JPY -> '¥12', ...). Previously Intl used the app locale, so EUR showed the symbol in front under an English UI. - Use TREK's CustomSelect (searchable, with symbols) and CustomDatePicker in the add/edit expense modal instead of the native <select>/<input type=date>. - Rename the 'Budget Planner' add-on to 'Costs' in the admin list (display only; id/tables/permissions/MCP stay 'budget') via seed + a migration for existing DBs. * feat(auth): configurable session duration via SESSION_DURATION Adds a SESSION_DURATION env var (ms-style strings: 1h, 7d, 30d, ...) controlling how long a session stays valid before re-login. It drives both the trek_session JWT exp claim and the cookie maxAge from one source, so they never drift. Invalid values warn at startup and fall back to the default (24h — unchanged). The MFA challenge token and MCP OAuth tokens keep their own TTL. Implements the request from discussion #946. Documented in the env-var wiki page, .env.example and docker-compose.yml.
438 lines
9.4 KiB
TypeScript
438 lines
9.4 KiB
TypeScript
import { Request } from 'express';
|
|
|
|
export interface User {
|
|
id: number;
|
|
username: string;
|
|
email: string;
|
|
role: 'admin' | 'user';
|
|
password_hash?: string;
|
|
maps_api_key?: string | null;
|
|
unsplash_api_key?: string | null;
|
|
openweather_api_key?: string | null;
|
|
avatar?: string | null;
|
|
oidc_sub?: string | null;
|
|
oidc_issuer?: string | null;
|
|
last_login?: string | null;
|
|
mfa_enabled?: number | boolean;
|
|
mfa_secret?: string | null;
|
|
mfa_backup_codes?: string | null;
|
|
must_change_password?: number | boolean;
|
|
first_seen_version?: string;
|
|
login_count?: number;
|
|
created_at?: string;
|
|
updated_at?: string;
|
|
}
|
|
|
|
export interface Trip {
|
|
id: number;
|
|
user_id: number;
|
|
title: string;
|
|
description?: string | null;
|
|
start_date?: string | null;
|
|
end_date?: string | null;
|
|
currency: string;
|
|
cover_image?: string | null;
|
|
is_archived: number;
|
|
reminder_days: number;
|
|
created_at?: string;
|
|
updated_at?: string;
|
|
}
|
|
|
|
export interface Day {
|
|
id: number;
|
|
trip_id: number;
|
|
day_number: number;
|
|
date?: string | null;
|
|
notes?: string | null;
|
|
title?: string | null;
|
|
}
|
|
|
|
export interface Place {
|
|
id: number;
|
|
trip_id: number;
|
|
name: string;
|
|
description?: string | null;
|
|
lat?: number | null;
|
|
lng?: number | null;
|
|
address?: string | null;
|
|
category_id?: number | null;
|
|
price?: number | null;
|
|
currency?: string | null;
|
|
reservation_status?: string;
|
|
reservation_notes?: string | null;
|
|
reservation_datetime?: string | null;
|
|
place_time?: string | null;
|
|
end_time?: string | null;
|
|
duration_minutes?: number;
|
|
notes?: string | null;
|
|
image_url?: string | null;
|
|
google_place_id?: string | null;
|
|
osm_id?: string | null;
|
|
website?: string | null;
|
|
phone?: string | null;
|
|
transport_mode?: string;
|
|
created_at?: string;
|
|
updated_at?: string;
|
|
}
|
|
|
|
export interface Category {
|
|
id: number;
|
|
name: string;
|
|
color: string;
|
|
icon: string;
|
|
user_id?: number | null;
|
|
created_at?: string;
|
|
}
|
|
|
|
export interface Tag {
|
|
id: number;
|
|
user_id: number;
|
|
name: string;
|
|
color: string;
|
|
created_at?: string;
|
|
}
|
|
|
|
export interface DayAssignment {
|
|
id: number;
|
|
day_id: number;
|
|
place_id: number;
|
|
order_index: number;
|
|
notes?: string | null;
|
|
reservation_status?: string;
|
|
reservation_notes?: string | null;
|
|
reservation_datetime?: string | null;
|
|
assignment_time?: string | null;
|
|
assignment_end_time?: string | null;
|
|
created_at?: string;
|
|
}
|
|
|
|
export interface PackingItem {
|
|
id: number;
|
|
trip_id: number;
|
|
name: string;
|
|
checked: number;
|
|
category?: string | null;
|
|
sort_order: number;
|
|
created_at?: string;
|
|
}
|
|
|
|
export interface BudgetItem {
|
|
id: number;
|
|
trip_id: number;
|
|
category: string;
|
|
name: string;
|
|
total_price: number;
|
|
currency?: string | null;
|
|
exchange_rate?: number;
|
|
persons?: number | null;
|
|
days?: number | null;
|
|
note?: string | null;
|
|
reservation_id?: number | null;
|
|
paid_by_user_id?: number | null;
|
|
expense_date?: string | null;
|
|
sort_order: number;
|
|
created_at?: string;
|
|
members?: BudgetItemMember[];
|
|
payers?: BudgetItemPayer[];
|
|
}
|
|
|
|
export interface BudgetItemMember {
|
|
user_id: number;
|
|
paid: number;
|
|
username: string;
|
|
avatar_url?: string | null;
|
|
avatar?: string | null;
|
|
budget_item_id?: number;
|
|
}
|
|
|
|
export interface BudgetItemPayer {
|
|
user_id: number;
|
|
amount: number;
|
|
username?: string;
|
|
avatar_url?: string | null;
|
|
avatar?: string | null;
|
|
budget_item_id?: number;
|
|
}
|
|
|
|
export interface ReservationEndpoint {
|
|
id: number;
|
|
reservation_id: number;
|
|
role: 'from' | 'to' | 'stop';
|
|
sequence: number;
|
|
name: string;
|
|
code: string | null;
|
|
lat: number;
|
|
lng: number;
|
|
timezone: string | null;
|
|
local_time: string | null;
|
|
local_date: string | null;
|
|
}
|
|
|
|
export interface Reservation {
|
|
id: number;
|
|
trip_id: number;
|
|
day_id?: number | null;
|
|
end_day_id?: number | null;
|
|
place_id?: number | null;
|
|
assignment_id?: number | null;
|
|
title: string;
|
|
reservation_time?: string | null;
|
|
reservation_end_time?: string | null;
|
|
location?: string | null;
|
|
confirmation_number?: string | null;
|
|
notes?: string | null;
|
|
status: string;
|
|
type: string;
|
|
accommodation_id?: number | null;
|
|
metadata?: string | null;
|
|
needs_review?: number;
|
|
endpoints?: ReservationEndpoint[];
|
|
created_at?: string;
|
|
day_number?: number;
|
|
place_name?: string;
|
|
}
|
|
|
|
export interface TripFile {
|
|
id: number;
|
|
trip_id: number;
|
|
place_id?: number | null;
|
|
reservation_id?: number | null;
|
|
note_id?: number | null;
|
|
uploaded_by?: number | null;
|
|
uploaded_by_name?: string | null;
|
|
filename: string;
|
|
original_name: string;
|
|
file_size?: number | null;
|
|
mime_type?: string | null;
|
|
description?: string | null;
|
|
starred?: number;
|
|
deleted_at?: string | null;
|
|
created_at?: string;
|
|
reservation_title?: string;
|
|
url?: string;
|
|
}
|
|
|
|
export interface TripMember {
|
|
id: number;
|
|
trip_id: number;
|
|
user_id: number;
|
|
invited_by?: number | null;
|
|
added_at?: string;
|
|
}
|
|
|
|
export interface DayNote {
|
|
id: number;
|
|
day_id: number;
|
|
trip_id: number;
|
|
text: string;
|
|
time?: string | null;
|
|
icon: string;
|
|
sort_order: number;
|
|
created_at?: string;
|
|
}
|
|
|
|
export interface CollabNote {
|
|
id: number;
|
|
trip_id: number;
|
|
user_id: number;
|
|
category: string;
|
|
title: string;
|
|
content?: string | null;
|
|
color: string;
|
|
pinned: number;
|
|
website?: string | null;
|
|
username?: string;
|
|
avatar?: string | null;
|
|
created_at?: string;
|
|
updated_at?: string;
|
|
}
|
|
|
|
export interface CollabPoll {
|
|
id: number;
|
|
trip_id: number;
|
|
user_id: number;
|
|
question: string;
|
|
options: string;
|
|
multiple: number;
|
|
closed: number;
|
|
deadline?: string | null;
|
|
username?: string;
|
|
avatar?: string | null;
|
|
created_at?: string;
|
|
}
|
|
|
|
export interface CollabMessage {
|
|
id: number;
|
|
trip_id: number;
|
|
user_id: number;
|
|
text: string;
|
|
reply_to?: number | null;
|
|
deleted?: number;
|
|
username?: string;
|
|
avatar?: string | null;
|
|
reply_text?: string | null;
|
|
reply_username?: string | null;
|
|
created_at?: string;
|
|
}
|
|
|
|
export interface Addon {
|
|
id: string;
|
|
name: string;
|
|
description?: string | null;
|
|
type: string;
|
|
icon: string;
|
|
enabled: number;
|
|
config: string;
|
|
sort_order: number;
|
|
}
|
|
|
|
export interface AppSetting {
|
|
key: string;
|
|
value?: string | null;
|
|
}
|
|
|
|
export interface Setting {
|
|
id: number;
|
|
user_id: number;
|
|
key: string;
|
|
value?: string | null;
|
|
}
|
|
|
|
export interface AuthRequest extends Request {
|
|
user: User;
|
|
trip?: { id: number; user_id: number };
|
|
}
|
|
|
|
export interface OptionalAuthRequest extends Request {
|
|
user: User | null;
|
|
}
|
|
|
|
export interface AssignmentRow extends DayAssignment {
|
|
place_name: string;
|
|
place_description: string | null;
|
|
lat: number | null;
|
|
lng: number | null;
|
|
address: string | null;
|
|
category_id: number | null;
|
|
price: number | null;
|
|
place_currency: string | null;
|
|
place_time: string | null;
|
|
end_time: string | null;
|
|
duration_minutes: number;
|
|
place_notes: string | null;
|
|
image_url: string | null;
|
|
transport_mode: string;
|
|
google_place_id: string | null;
|
|
website: string | null;
|
|
phone: string | null;
|
|
category_name: string | null;
|
|
category_color: string | null;
|
|
category_icon: string | null;
|
|
}
|
|
|
|
export interface Participant {
|
|
user_id: number;
|
|
username: string;
|
|
avatar?: string | null;
|
|
}
|
|
|
|
// ── Journey addon ─────────────────────────────────────────────────────────
|
|
|
|
export interface Journey {
|
|
id: number;
|
|
user_id: number;
|
|
title: string;
|
|
subtitle?: string | null;
|
|
cover_gradient?: string | null;
|
|
cover_image?: string | null;
|
|
status: 'draft' | 'active' | 'completed' | 'archived';
|
|
created_at: number;
|
|
updated_at: number;
|
|
}
|
|
|
|
export interface JourneyEntry {
|
|
id: number;
|
|
journey_id: number;
|
|
source_trip_id?: number | null;
|
|
source_place_id?: number | null;
|
|
author_id: number;
|
|
type: 'entry' | 'checkin' | 'skeleton';
|
|
title?: string | null;
|
|
story?: string | null;
|
|
entry_date: string;
|
|
entry_time?: string | null;
|
|
location_name?: string | null;
|
|
location_lat?: number | null;
|
|
location_lng?: number | null;
|
|
mood?: string | null;
|
|
weather?: string | null;
|
|
tags?: string | null;
|
|
pros_cons?: string | null;
|
|
visibility: 'private' | 'shared' | 'public';
|
|
sort_order: number;
|
|
created_at: number;
|
|
updated_at: number;
|
|
}
|
|
|
|
export interface TrekPhoto {
|
|
id: number;
|
|
provider: string;
|
|
asset_id?: string | null;
|
|
owner_id?: number | null;
|
|
file_path?: string | null;
|
|
thumbnail_path?: string | null;
|
|
width?: number | null;
|
|
height?: number | null;
|
|
passphrase?: string | null;
|
|
created_at: string;
|
|
}
|
|
|
|
export interface JourneyPhoto {
|
|
id: number;
|
|
entry_id: number;
|
|
photo_id: number;
|
|
caption?: string | null;
|
|
sort_order: number;
|
|
shared: number;
|
|
created_at: number;
|
|
// Joined from trek_photos for API responses
|
|
provider?: string;
|
|
asset_id?: string | null;
|
|
owner_id?: number | null;
|
|
file_path?: string | null;
|
|
thumbnail_path?: string | null;
|
|
width?: number | null;
|
|
height?: number | null;
|
|
}
|
|
|
|
export interface GalleryPhoto {
|
|
id: number;
|
|
journey_id: number;
|
|
photo_id: number;
|
|
caption?: string | null;
|
|
shared: number;
|
|
sort_order: number;
|
|
created_at: number;
|
|
// Joined from trek_photos for API responses
|
|
provider?: string;
|
|
asset_id?: string | null;
|
|
owner_id?: number | null;
|
|
file_path?: string | null;
|
|
thumbnail_path?: string | null;
|
|
width?: number | null;
|
|
height?: number | null;
|
|
}
|
|
|
|
export interface JourneyTrip {
|
|
journey_id: number;
|
|
trip_id: number;
|
|
added_at: number;
|
|
}
|
|
|
|
export interface JourneyContributor {
|
|
journey_id: number;
|
|
user_id: number;
|
|
role: 'owner' | 'editor' | 'viewer';
|
|
added_at: number;
|
|
}
|