mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-06-30 18:46:00 +00:00
993d9bf713e197332e4cdd0943b2e5fd5fa1f090
115 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
993d9bf713 |
feat(video): media_type discriminator + local gallery video upload (server)
trek_photos gains a media_type column (migration) so the registry can hold video as well as images. A new POST :id/gallery/video endpoint accepts a video plus a client-captured poster (500 MB cap, video MIME/extension allowlist), stores the poster as the thumbnail, and the photo stream serves the poster for the thumbnail kind and the raw file (HTTP Range) for the original — without running the image thumbnailer on video bytes. |
||
|
|
c552472b63 |
feat(offline): detect update conflicts on the server for places and packing
Update handlers accept an optional X-Base-Updated-At token and reject a stale overwrite with 409, returning the current server row. An absent token keeps the existing last-write-wins behaviour, so older clients are unaffected. packing_items gains an updated_at column (migration + stamped on every insert) so it can take part in conflict detection too. |
||
|
|
7173e82fe8 |
feat(feeds): subscribable ICS calendar feeds for trips
Adds TripIt-style live calendar subscriptions alongside the existing one-time .ics download. A trip (or all of a user's trips) exposes a secret, revocable feed URL that Google/Apple/Outlook poll to stay in sync. - Public read endpoints GET /api/feed/trip/:token.ics and /api/feed/user/:token.ics (no auth — the secret token is the credential), reusing the existing exportICS() generator and adding REFRESH-INTERVAL / X-PUBLISHED-TTL hints. - JWT-guarded token endpoints to generate (lazy, idempotent) and regenerate/revoke per-trip and per-user feed tokens; tokens stored in nullable feed_token columns. - All-trips feed excludes archived trips and trips ended >90 days ago. - UI: ICS toolbar button becomes a Download/Subscribe menu; modal offers one-click "Add to Google Calendar" (render?cid=webcal://) and a webcal:// link for Apple/Outlook, plus copy-link fallbacks. All-trips feed reachable from dashboard. - Feed base URL read from the existing APP_URL env var. Purely additive: new endpoints + two nullable columns, no breaking changes. Tests: server/tests/e2e/feeds.e2e.test.ts covers lazy token generate + idempotency, regenerate-invalidates-old, 401/404 auth+access, public feed content-type + hint injection, unknown-token 404, and the archived/>90-day all-trips exclusion. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> |
||
|
|
e34f40b686 |
feat(costs): Splitwise-like cost splitting
Add per-payer and per-member custom split amounts with Equally, Custom and Ticket split modes on top of the existing equal split, keep legacy "paid by" expenses working, and document the modes in the Budget Tracking wiki page. |
||
|
|
f0877a2e7d |
Replace the 3.0 upgrade notices with a thank-you / support modal
The 3.0 "what's new" notices have served their purpose, so swap them for a single thank-you notice that comes back once on every fresh install and version bump. It carries Buy Me a Coffee and Ko-fi buttons and only shows on desktop. Adds a per-version recurring mode (new dismissed_app_version column) plus external-link CTAs to support it; the 3.0.14 whitespace-collision admin notice stays active. |
||
|
|
91fcaa50f6 | Use Google Maps feature IDs for place map links | ||
|
|
d152f9d02b |
v3.1.1 bug fixes (#1228)
* fix(shared-view): render each leg of multi-leg flights correctly The read-only shared view showed the overall trip start/end airports and the first leg's flight number on every leg of a multi-leg flight. The Day Plan already expands legs (each carries __leg), but the renderer ignored it and read flat top-level metadata; the Bookings tab had the same bug. - Day Plan: use __leg for per-leg airline/flight number/route, plus dep-arr time - Bookings tab: list each leg via getFlightLegs() - unique React keys for multi-leg rows Closes #1219 * feat(pdf): add legs to pdf export * fix(demo): skip first-run admin seed in demo mode When DEMO_MODE is on, the demo seeder creates its own admin (admin@trek.app, username "admin") right after the generic seeds run. The first-run admin bootstrap was grabbing username "admin" first, so the demo seeder hit the UNIQUE(username) constraint and aborted before the demo user was ever created - which surfaced as a 500 "Demo user not found" on demo-login. Skip the generic admin bootstrap when demo mode owns the admin account. * fix(docker): ship the encryption-key migration script in the image The production image only copied server/dist, so the documented rotation command `node --import tsx scripts/migrate-encryption.ts` failed inside the container with a module-not-found error - the raw .ts was never present. The script runs via tsx straight from source and only pulls node builtins plus better-sqlite3 (both prod deps), so copying the single file into /app/server/scripts is enough to make the rotation work again. * fix(vacay): keep the mode toolbar above the mobile bottom nav The floating Vacation/Company toolbar was pinned at bottom-3 with z-30, so on mobile it landed in the same band as the fixed bottom nav (z-60) and got hidden behind it - and could scroll out of reach entirely. Pin it above the nav with the shared --bottom-nav-h variable (0px on desktop, so nothing changes there) and reserve matching space below the calendar grid so it never gets swallowed. * fix(dashboard): show the correct reservation date regardless of timezone The upcoming-reservations widget built the date with new Date(reservation_time) .toISOString(), which reinterprets the stored naive local time as UTC and can roll the displayed day forward in non-UTC timezones (e.g. a 23:30 reservation showing the next day). Read the date and time straight from the stored string parts via splitReservationDateTime, and format the time with the shared formatTime helper so it also honours the user's 12h/24h preference. * fix(atlas): cursor-following tooltips and removing countries from search Two related Atlas fixes: - Country tooltips were bound with sticky:false, which anchors them at the feature's bounds centre. For countries with overseas territories (e.g. France) that centre sits far out in the ocean, so the tooltip popped up nowhere near the area being hovered. Make them sticky so they track the cursor. - Selecting an already-visited country from the search bar always opened the "Mark / Bucket" dialog, with no way to remove it. Tiny countries like Vatican City or Singapore are hard to hit on the map, so search was the only way in. Mirror the map-click behaviour: a manually-marked country opens the Remove confirmation, a trip/place-backed one opens its detail. * fix(oidc): keep dots in generated usernames The OIDC username sanitizer stripped dots because they were missing from the allowed character class, so a name claim like "first.last" became "firstlast". Dots are valid usernames (the profile validator already allows ^[a-zA-Z0-9_.-]+$), so add the dot to the sanitizer. * fix(collab): show poll option labels in the UI The poll API formatted each option as { label, voters }, but the React poll component renders opt.text - so every option button came out blank. Emit text alongside label (kept for any other consumer) so options render again. * feat(backup): make the upload size limit configurable The restore upload was capped at a hard-coded 500 MB, so instances whose backup archive (uploads/ included) grew past that got a 413 "File too large" with no way to raise it. Add a BACKUP_UPLOAD_LIMIT_MB env var (default 500, invalid values warn and fall back), documented in .env.example. * feat(costs): create an expense from a booking, fix editing total-only items Replace the inline price + budget-category fields in the Transport and Reservation booking modals with a "Create expense" flow: the modal saves the booking, then opens the full Costs editor prefilled (name + category mapped from the booking type) and linked to the reservation. A booking with a linked expense shows it inline with edit / remove. Also fix the Costs editor so an expense with a recorded total but no payers (transport-derived or pre-rework items) opens with its amount, lets you set the currency, and saves - it previously showed 0 everywhere and could not be saved. Legacy / localized categories now map to the fixed keys, and changing a booking's type keeps its linked expense category in sync (unless it was manually set). - shared: reservation_id on budget create, typeToCostCategory helper, i18n keys - server: createBudgetItem stores reservation_id; keep total_price for payerless items; a booking update no longer wipes its linked expense and syncs the category on type change - client: shared BookingCostsSection, exported ExpenseModal with prefill and an editable total, page-level save-then-open wiring * test(reservations): align syncBudgetOnUpdate unit tests with no-wipe + type-sync The service now leaves a linked expense alone when no budget entry is on the payload (only an explicit total_price 0 deletes it) and syncs the category on a booking type change. Update the unit tests accordingly - the old "price cleared" case passed entry: undefined, which is now a no-op and left a mocked return queued that leaked into the next test. * fix(planner): keep a reservation on its day when edited (#1237) Editing a booking forced its day_id to the globally selected day, which is null when editing from the Book tab - so the booking lost its day and vanished from the Plan. Preserve the reservation own day_id on edit instead. * fix(planner): derive a booking day from its date when none is set (#1237) The client always sends day_id on a reservation update, so the server only derived it from reservation_time when the field was absent. A non-transport booking saved without a selected day (Book tab) therefore got day_id null and vanished from the Plan, even though its date matched a day. Derive the day from reservation_time whenever day_id is null, mirroring create. * fix(planner): let a booking's day follow its date when edited (#1237) Preserving the old day_id on edit left a re-dated booking on its previous start day while end_day_id followed the new date, so it spanned both. Stop sending day_id from the edit modal entirely - the server derives both ends from the booking's date (and keeps the current day when there is no date), so a re-dated booking moves cleanly to the matching day. * fix(atlas): keep the continent breakdown in sync on mark/unmark (#1225) The optimistic mark/unmark updates bumped the country total but never the per-continent counts, so the continent column froze until a full reload. Move the country to continent map into @trek/shared (single source for server and client) and adjust the matching continent count at every optimistic site: the country confirm flow plus the choose / region mark and region unmark handlers. * feat(admin): let admins set a default currency for new users Adds a currency picker to Admin > User Defaults. Stored as the default_currency user-default, so users who have not picked their own currency inherit it in Costs. * fix(atlas): give every sub-national region a distinct code (#1217) geoBoundaries fills shapeISO with the bare country code for some countries (every Spanish region got "ESP", every Chinese "CHN", also Chile/Oman), so marking one region lit up the whole country. build-atlas-geo.mjs now keeps shapeISO only when it is a real "XX-..." subdivision code and otherwise synthesizes a unique per-country id from the region name. Regenerated admin1.geojson.gz: Spain/China/ Chile/Oman now carry distinct region codes (countries with real codes, e.g. Germany, are unchanged). * fix(dashboard): never crash on a malformed reservation date A reservation with an invalid date blanked the whole My Trips page: the old Upcoming widget did new Date(value).toISOString(), which throws "Invalid time value" (fixed in #1222 by reading the string parts). Also guard splitDate so a bad date renders a dash instead of "Invalid Date" or throwing. * fix(airtrail): gate airtrail update behind a user setting, on airtrail update: rebuild payload from fresh data to prevent any data loss * fix(airtrail): add back missing tests * fix(costs): rework the cost panel UX wise and apply prettier on the shared package * chore(prettier) prettier this file * fix(airtrail): don't use cabin class as seat on import When an AirTrail flight has a cabin class but no seat number, the mapper fell back to the class for metadata.seat, so reservations showed e.g. "economy" as the seat. Use only the seat number; leave the seat blank otherwise. The class is still surfaced separately in the import picker. Closes #1246 * fix(airtrail): import scheduled flight times instead of actual AirTrail exposes both scheduled (departureScheduled/arrivalScheduled) and actual (departure/arrival) times. TREK read the actual times, so a delayed or early flight imported the wrong time for planning. Read the scheduled times on import and on poll-sync (both go through mapFlightToReservation); when a flight has no scheduled time, leave the clock blank (date preserved) rather than fabricating 00:00 or falling back to actual. The change-detection hash now tracks the scheduled values, so existing linked reservations re-sync once on the next poll. The opt-in writeback mirrors the read, pushing TREK edits to the scheduled fields so they round-trip. * fix(planner): hydrate per-assignment times when editing a place from the pool Times live per day-assignment, not on the pool place, so reopening a place from the Places panel / inspector showed empty Start/End fields (#1247). The editor now resolves a place's lone assignment when no day is in context and hydrates the fields from it; ambiguous (0 or 2+ days) edits hide the fields instead of showing non-persisting inputs. * fix(mcp): make write tools return client-valid, hydrated entities Audit of all write tools under server/src/mcp/tools (issue #1244 anchor). S1 (broken): - create_budget_item / create_budget_item_with_members now default the split to all trip members when member_ids omitted, so the entry passes the client save-gate instead of being member-less (#1244). - create_transport / update_transport backfill lat/lng/timezone for code-only flight endpoints (NOT NULL columns) and return a clean error for unresolvable endpoints instead of crashing. S2 (under-hydration): set_budget_item_members, create_journey, create_journey_entry, create_packing_bag, bulk_import_packing and update_vacay_plan now return the hydrated shape the matching read/REST route returns; bulk_import widened to accept bag/weight_grams/checked. S3 (parity): check_in_end added to accommodation tools; atlas mark_region_visited echoes the client shape; update_journey_entry/ update_journey_preferences, set_bag_members, set_packing_category_assignees, apply_packing_template return hydrated payloads; set_vacay_color echoes the color. Auth: save_packing_template now requires admin, matching the REST gate. Also refactors server/src/config.ts (JWT-secret handling). Adds getBudgetItem hydrated getter, exports EndpointInput, and MCP regression tests (incl. new tools-transports and tools-journey suites). * fix(mcp): fix ICS/maps/accommodation bugs, add settlement & template tools Bugs: - export_trip_ics: include flights that store times per-endpoint (local_date/local_time) instead of a top-level reservation_time - resolve_maps_url: follow redirects for cid=/share links and fall back to parsing the page body, all SSRF-guarded - link_hotel_accommodation: normalize accommodation_id (TEXT column) to an integer in the reservation read paths so it no longer returns "14.0" Gaps: - packing: save_packing_template returns the new template id; add list_packing_templates (read) and delete_packing_template (admin) - budget: update_budget_item accepts payers/member_ids; clarify create/ update/members descriptions to ask which members share the expense and who paid - budget: add settlement tools — get_settlement_summary, list_settlements, create/update/delete_settlement (budget_edit, mirrors REST + WS events) * chore: bump nodemailer * chore: bump multer --------- Co-authored-by: Maurice <mauriceboe@icloud.com> |
||
|
|
ad893eb1cc |
Release 3.1.0 (#1185)
* Phase 0 — NestJS + Zod foundation harness (F1–F8) (#1050) Co-hosted NestJS app behind the existing Express server via a strangler-fig dispatcher, sharing the same better-sqlite3 connection and JWT httpOnly cookie. Additive and dormant: default routing stays on Express, Nest only serves its own /api/_nest diagnostics until a module opts in. F1 @trek/shared Zod contract package; F2 Nest bootstrap co-hosted (fall-through, single Dockerfile/port); F3 shared better-sqlite3 provider; F4 JWT cookie auth guard (+ @CurrentUser, admin guard); F5 Zod validation pipe + error-envelope parity; F6 Nest test + coverage gates; F7 per-prefix strangler toggle (env, default Express); F8 CI build/typecheck/test/coverage. Remaining F4/F6/F8 checklist items (trip-access + permission levels + MFA policy, e2e harness/seed + 80% gate, Nest↔Express parity test, Playwright PR-comment workflow) are tracked on the first consuming module cards (L1/A1/C1). * feat(weather): migrate /api/weather to the NestJS pilot module (L1) (#1053) First strangler migration (L1): /api/weather is served by a NestJS module. - @trek/shared/weather Zod contract; Nest controller byte-identical to the legacy Express route (paths, query params, status codes, { error } bodies, lang default, ApiError/500 passthrough). Service reuses getWeather/getDetailedWeather (+ shared cache; MCP tools unchanged). - Strangler routes /api/weather to Nest by default; the legacy Express route + its migration-time parity test were decommissioned in this PR. - Frontend (FE2): weatherApi typed against the @trek/shared WeatherResult contract. - Harness: reusable Nest-vs-Express parity harness, e2e harness (temp SQLite + seed/cookie helpers, real JwtAuthGuard), src/nest coverage gate raised to >=80%, src/nest test guide. - Verified end-to-end on a prod mirror (dev1): 401/400/200 via Nest with real Open-Meteo data, Express route gone. * fix(packing): multiply item weight by quantity in bag/total weight calcs (#898) Quantity now counts toward bag and total weights. Generalised to an itemWeight() helper used by every weight sum (bag totals + max, unassigned, grand total; sidebar + bag modal) with unit tests. * feat(i18n): add Korean (ko) translation (#977) Korean translation by @ppuassi, topped up to full en.ts key parity. Language registration follows separately. * feat(i18n): add Japanese (ja) translation (#829) Japanese translation by @soma3978, at full en.ts key parity, registered in supportedLanguages + TranslationContext. * Add Turkish (tr) translation + language registry (#1029) Turkish translation by @SkyLostTR, at full en.ts key parity, registered in supportedLanguages + TranslationContext. * i18n: register Korean + add Ukrainian translation (#1055) Korean translation by @ppuassi (#977) — now registered. Ukrainian by @JeffyOLOLO (#902) — lifted onto a clean branch. Both at full en.ts key parity (2258 keys). * chore: fix monorepo build pipeline and migrate shared to built package (#1056) * chore: fix monorepo build pipeline and migrate shared to built package - Root package.json: add workspace scripts (dev, build, test, test:cov, test:e2e) that delegate to actual scripts in shared/server/client workspaces - shared: add tsup build step (CJS + ESM dual output, .d.ts); consumers now import from the built dist instead of raw TS source via path aliases - server: replace tsc-alias with tsconfig-paths (tsc-alias mangled node_modules paths); fix MCP SDK path aliases to point to root node_modules (../node_modules) - server/scripts/dev.mjs: delay node --watch until tsc -w signals first-pass done, eliminating the spurious restart on every dev startup - client/vite.config.js + vitest.config.ts: remove @trek/shared path alias (no longer needed now that shared is a proper package) - Consolidate package-lock.json at the workspace root; drop per-workspace lock files * chore: fix test script to reflect root package.json * chore: add missing lint and prettier script in root package.json * fix(ci): build shared before tests; fix vitest MCP SDK alias paths vitest.config.ts aliases pointed at ./node_modules/ (server-local) but packages are hoisted to the root node_modules/ in the npm workspace — changed to ../node_modules/. CI jobs now install and build shared before running server/client tests so that @trek/shared's dist/ exists when vitest resolves the package. * fix(docker): update Dockerfile and CI for monorepo workspace structure Dockerfile: - Add shared-builder stage that produces @trek/shared dist before client and server stages need it - Each build stage carries root package.json + package-lock.json so npm can resolve @trek/shared as a workspace dependency - Production stage installs via workspace context (npm ci --workspace=server --omit=dev) so node_modules/@trek/shared symlinks to shared/dist correctly - Copy server/tsconfig.json into the image so tsconfig-paths/register can find the MCP SDK path aliases at runtime - CMD cds into /app/server before starting node so tsconfig-paths baseUrl resolves and ../node_modules points to /app/node_modules - Remove mkdir for /app/server (now a real dir); keep symlinks for uploads/data docker.yml version-bump: - Replace manual per-workspace cd+npm-version calls with single: npm version --workspaces --include-workspace-root --no-git-tag-version (mirrors the version:* scripts in root package.json) - git add now references root package-lock.json; adds shared/package.json .dockerignore: add shared/dist package.json: fix version:prerelease preid (alpha → pre) * fix(tests): use in-memory SQLite per worker in test mode vitest pool:forks spawns parallel worker processes that all called initDb() on the same data/travel.db, causing SQLite "database is locked" and "duplicate column name" races. When NODE_ENV=test each fork now gets an isolated :memory: DB so migrations run independently with no file contention. * chore(ci): add ACT guards to skip DockerHub steps in local act runs act sets ACT=true automatically. Guards added: - docker login: if: ${{ !env.ACT }} - build outputs: type=docker (local load) when ACT, push-by-digest when CI - digest export/upload: if: ${{ !env.ACT }} - merge job: if: ${{ !env.ACT }} - release-helm job (docker.yml): if: ${{ !env.ACT }} - version-bump git push (docker.yml): wrapped in [ -z "$ACT" ] shell guard Run locally with: ./bin/act -j build -W .github/workflows/docker.yml \ -P ubuntu-latest=catthehacker/ubuntu:act-latest * fix(ci): move ACT guards to step level; add guards to security.yml env context is invalid in job-level if conditions — moved all ACT guards down to individual steps. Also guards docker login + scout in security.yml so act can run the build-only part of that workflow. * fix(ci): skip git fetch and tag logic in act (no remote access in local containers) * Revert "fix(ci): skip git fetch and tag logic in act (no remote access in local containers)" This reverts commit |
||
|
|
86ee8044da |
v3.0.22 Bug Fixes & Improvements (#1041)
Bundles the v3.0.22 bug fixes and improvements. See the release notes for the full list. |
||
|
|
22f3bf4bfc |
fix: add APP_VERSION fallback and HOST bind address env var (#952 #953) (#955)
* fix: add APP_VERSION fallback and HOST bind env var (#952 #953) - Read package.json version when APP_VERSION env var is absent so the startup banner shows the correct version for source/Proxmox installs - Add HOST env var to control the HTTP bind address; only applied when set so Docker deployments are unaffected (bind-all-interfaces default) - Parse PORT as Number() so malformed values like '10.0.0.72:3001' fall back to 3001 instead of silently misbehaving - Document HOST in .env.example, Environment-Variables wiki, and Install-Proxmox wiki with explicit warnings against using it in Docker * fix: correct package.json path in APP_VERSION fallback index.ts sits at server/src/ — one level up reaches server/package.json, not two (../../ overshot to the repo root where no package.json exists). |
||
|
|
6072b969d6 |
Bug fixes - May 2nd 2026 (#941)
* fix: collab chat input hidden by mobile bottom nav bar Closes #939 * chore: prepare database for nest + typeorm * fix(ssrf): relax internal network resolution (#947) * docs(ssrf): update Internal-Network-Access wiki to reflect relaxed guard Loopback, link-local, and .local/.internal hostnames are now all overridable with ALLOW_INTERNAL_NETWORK=true (commit |
||
|
|
51ab30f436 |
Bug fixes - April 30th 2026 (#936)
* fix: hotel day-range clamping in ReservationModal + stale assignment_id on accommodation clear (issues #929, #934)
* ReservationModal hotel start/end pickers now use findIndex-based
positional clamping instead of raw ID arithmetic, matching the fix
applied to DayDetailPanel in
|
||
|
|
4436b6f673 |
fix(journey,pdf): journey reorder sort_order + PDF multi-day transport (#848)
* fix(journey): make sort_order authoritative for within-day entry ordering Reorder buttons appeared broken because the server ORDER BY put entry_time before sort_order, so entries synced from trip places with differing times would always sort by time regardless of sort_order writes. The client store mirrored the same comparator, making even the optimistic update invisible. - Change ORDER BY to (entry_date, sort_order, id) in getJourneyFull and listEntries - Fix syncTripPlaces and onPlaceCreated to assign MAX+1 sort_order per day instead of day_number/0 - Update client store comparator to match - Add DB migration to backfill sort_order using old effective key (entry_time, id) so existing journeys retain their visual order - Add tests: JOURNEY-SVC-089–093, FE-STORE-JOURNEY-018–019 Closes #846 * fix(pdf): include multi-day transport return/arrival in PDF itinerary (#847) Reservations were matched to days by pickup date only, so the end-day card (e.g. car Return, flight Arrival) was silently dropped from the PDF. Add span-aware helpers mirroring DayPlanSidebar logic: match by day_id/end_day_id span, show reservation_end_time on end days, prefix title with phase label (Return/Arrival/etc.), and use per-day position for sort order. * test(pdf): add missing day_id to transport reservation fixture |
||
|
|
6155b6dc86 |
fix(reservations): restore correct day assignment for non-transport bookings
v3.0.0 switched the planner from rendering reservations by
reservation_time to rendering them by day_id (commit
|
||
|
|
71aa8f8051 |
feat: journey gallery 1-to-N model with M:N entry-photo junction table
Replaces the old model where journey_photos was keyed per-entry with a per-journey gallery table (one row per unique photo per journey) and a new junction table journey_entry_photos that links gallery photos to entries. Key changes: - Migration 121: renames old journey_photos to journey_photos_old, creates the new gallery table + junction table, backfills both from existing data, drops the backup, removes synthetic 'Gallery' / '[Trip Photos]' wrapper entries - journeyService: rewrites photo helpers (JP_SELECT/JOIN now joins via journey_entry_photos → journey_photos → trek_photos); adds uploadGalleryPhotos, addProviderPhotoToGallery, unlinkPhotoFromEntry, deleteGalleryPhoto; simplifies deletePhoto and linkPhotoToEntry against the new schema; syncTripPhotos inserts directly into the gallery instead of a wrapper entry - journeyShareService: updates public photo and asset validation queries to join through the gallery table instead of entry_id; getPublicJourney now returns a dedicated gallery array alongside per-entry photos - journey routes: adds gallery upload, provider-photo, and delete endpoints (POST/DELETE /:id/gallery/*); adds unlink-from-entry route (DELETE /entries/:entryId/photos/:journeyPhotoId); updates link-photo to accept journey_photo_id with a backwards-compat photo_id alias - types: adds GalleryPhoto interface - client api: adds uploadGalleryPhotos, addProviderPhotosToGallery, unlinkPhoto, deleteGalleryPhoto; updates linkPhoto param name to journeyPhotoId - journeyStore: adds GalleryPhoto type, gallery field on JourneyDetail, uploadGalleryPhotos / unlinkPhoto / deleteGalleryPhoto store actions - JourneyDetailPage + tests: updated to work with the new gallery model |
||
|
|
ee31c78db8 |
fix(maps): null stale proxy image_url entries instead of writing unbacked proxy URLs
Migration 107 and the previous fix both wrote /api/maps/place-photo/<id>/bytes into places.image_url without ever fetching the photo bytes. photoService short-circuits on that URL prefix and hits /bytes directly, which 404s because nothing is on disk. - Add migration to null proxy image_url rows with no backing google_place_photo_meta entry — restores the normal fetch-and-cache flow for affected rows - Fix the previous legacy-URL migration to null instead of rewrite, so fresh installs don't hit the same 404 path Fixes #770 (follow-up) |
||
|
|
2aad8f465c |
fix(maps): prevent server crash when legacy Google photo URLs are stored as placeIds
Migration 107 only rewrote image_url rows matching /places/%/photos/%; URLs using the /place-photos/ or /places/<opaque> paths survived the upgrade and were passed verbatim to the Places API, producing a malformed request whose empty/HTML response body threw SyntaxError before detailsRes.ok was checked. The resulting rejection was leaked by placePhotoCache.setInFlight via an unhandled .finally() chain, triggering Node 22's default unhandledRejection=throw and terminating the process. - placePhotoCache: add .catch() after .finally() to prevent unhandled rejection crash - mapsService: reject URL-shaped placeIds early; read response as text before JSON.parse - migrations: add migration to rewrite remaining googleusercontent/places.googleapis URLs - MapView/MapViewGL: prefer stable proxy URL form of image_url before google_place_id Fixes #770 |
||
|
|
16b81a8356 |
fix(bookings): preserve accommodation dates when place is unlinked or missing
- Remove NOT NULL constraint on day_accommodations.place_id (migration) and change ON DELETE CASCADE → SET NULL so deleting a place no longer cascades to the accommodation row - Switch listAccommodations / getAccommodationWithPlace to LEFT JOIN so accommodations without a linked place are visible to the modal - Relax create/update guards in reservationService to only require start_day_id + end_day_id, not place_id; place_id remains optional - Client save guard now sends create_accommodation whenever FROM/TO days are set, regardless of whether a hotel place was selected - Add re-hydration useEffect in ReservationModal to back-fill hotel fields from the accommodations prop when it arrives after modal opens (race between isOpen and the tripAccommodations fetch) - Fix demo-seed TDZ crash: move db Proxy declaration before DEMO_MODE block so circular require in demo-reset resolves correctly - Sidebar accommodation badge falls back to reservation title when place_name is null; click/cursor disabled for placeless accommodations - listAccommodations now joins reservations to expose reservation_title |
||
|
|
20bf9c2312 |
security: close SEC-H4/H6 gaps from second-pass review
- SEC-H6: remove conditional audience check in mcp/index.ts — audience is now always enforced against the mcpResource URL. Add migration to revoke pre-existing oauth_tokens with audience=NULL so dead rows don't linger. - SEC-H4: validate doc.issuer against config.issuer inside discover() to prevent a MITM'd discovery doc from supplying a crafted expected issuer. verifyIdToken caller now passes config.issuer as ground truth, not doc.issuer. - tests: cover three new OIDC callback failure paths (no_id_token, id_token_invalid, subject_mismatch) and two idempotency caps (key length >128 chars returns 400, body >256 KiB skips caching). |
||
|
|
9f57ab4517 |
security: address second-pass audit findings
- CI-C1 false positive: actions/{checkout,setup-node,upload-artifact}
@v6 do exist (v6.0.0 releases published Oct-Dec 2025). Restore the
@v6 refs — the earlier batch-1 commit downgraded them unnecessarily.
- Widen idempotency_keys primary key to (key, user_id, method, path)
via new migration. Batch 1 widened the middleware lookup but left
the table PK at (key, user_id), so `INSERT OR IGNORE` silently
skipped the second endpoint that reused a key — the cache was
never populated for it and a replay re-ran the handler. The
migration rebuilds the table preserving existing rows (the old
narrower PK guarantees no conflicts against the new looser key).
- HSTS: keep `includeSubDomains` OFF by default. Enabling it for
every NODE_ENV=production install would break apex-domain setups
where siblings still serve HTTP. Operators who want the stricter
policy opt in with HSTS_INCLUDE_SUBDOMAINS=true.
- Extend the idempotency unit tests to cover the (method, path)
dimension — same user+key on different path no longer replays.
|
||
|
|
2d0414b4a3 |
security: internal audit — batch 1
Fixes the critical + high + medium findings from our internal security
review. Bundled into one PR because the changes overlap heavily (JWT
verification unifies across three call sites; backup-code hashing and
demo-email handling cross-cut several services); splitting them out
would mean redundant reviews of the same files.
Critical
- CI-C1 — .github/workflows/test.yml: restore actions/{checkout,setup-
node,upload-artifact} to @v4. The @v6 refs don't exist, so the test
workflow was errorring before a single test ran.
- SEC-C1 — mfaPolicy now extracts the token via extractToken() (cookie-
first, Bearer fallback). Previously it only read Authorization, so
every cookie-authenticated SPA session bypassed require_mfa entirely.
- SEC-C2/C4/C6 — all JWT verification paths (MCP bearer, file download,
photo route) now go through the shared verifyJwtAndLoadUser that
checks password_version. resetPassword additionally deletes every
mcp_tokens row and marks outstanding oauth_tokens revoked, so a
password reset invalidates ALL credential classes — not just the
cookie JWT.
High
- SEC-H2 — reset email URL is built from server-side APP_URL /
ALLOWED_ORIGINS (via existing getAppUrl()), not request headers.
Closes the host-header-injection vector into reset links.
- SEC-H3 — OIDC findOrCreateUser wraps the invite-redemption UPDATE +
user INSERT in a transaction. The UPDATE is the capacity check; if
a concurrent callback takes the last slot, the whole transaction
aborts with registration_disabled instead of double-creating users.
- SEC-H4 — new verifyIdToken() performs full JWT signature
verification via the provider's JWKS (Node's crypto.createPublicKey
accepts JWK directly — no extra dependency), plus iss/aud/exp
checks. The callback also rejects the login when userinfo.sub does
not match id_token.sub.
- SEC-H5 — OAuth DCR now validates redirect_uris against an allowlist
of schemes: https, http-loopback, or a private custom scheme. Plain
http://non-loopback is rejected.
- SEC-H6 — oauthService audience defaults to mcpResource when the
`resource` parameter is missing, so tokens are always audience-bound
to /mcp instead of being issued with audience=null.
- SEC-H7 — HSTS is enabled any time NODE_ENV=production (previously
required FORCE_HTTPS=true), includeSubDomains defaults on and can
be disabled with HSTS_INCLUDE_SUBDOMAINS=false.
- SEC-H8 — trek_session cookie Secure flag is also driven by
req.secure (which Express resolves from X-Forwarded-Proto once
trust proxy is set), so instances behind a TLS-terminating proxy
get Secure cookies without needing FORCE_HTTPS.
Medium
- SEC-M1 — permanentDeleteFile / emptyTrash / avatar unlink now use
fs.promises.rm with { force: true } (one async op vs the previous
existsSync + unlinkSync pair per file).
- SEC-M2 — invalidatePermissionsCache() is called inside restoreFromZip
so a restored DB with different permission rows is honoured
immediately.
- SEC-M3 + C1 — idempotency store bounds the key at 128 chars, caches
only responses ≤ 256 KiB, and scopes the lookup by (key, user_id,
method, path) rather than (key, user_id). Same key replayed against
a different endpoint no longer returns a stale unrelated body.
- SEC-M4 — share_tokens gets an expires_at column; new tokens default
to 90-day TTL, expired tokens are denied at lookup. Existing tokens
stay NULL = no expiry so already-published links don't break.
- SEC-M5 — /uploads/photos/:filename now resolves the photo to its
trip_id and requires the share token to cover THAT trip. Previously
any share token for any trip would unlock any photo filename.
- SEC-M6 — BLOCKED_EXTENSIONS is the single source of truth shared
between fileService and collab uploads. The '*' allowed_file_types
wildcard now still rejects executables/scripts.
- SEC-M7 — single DEMO_EMAILS constant (services/demo.ts) used by
demoUploadBlock, mfaPolicy, and every demo-mode guard in
authService. The old demoUploadBlock only matched 'demo@nomad.app'
so the seed 'demo@trek.app' could in fact upload in demo mode.
- SEC-M8 — MFA backup codes are now bcrypt-hashed at rest
(hashBackupCodeBcrypt). matchBackupCode accepts both bcrypt and
legacy SHA-256 hex hashes, so existing installs keep working until
the user regenerates codes via enableMfa.
- SEC-M9 — document the "security via UUID v4 filename" model for
/uploads/avatars|covers|journey. Requires no code change but
captures the decision so future reviewers don't re-flag it.
- SEC-M10 — already covered by the resetPassword revocation logic
above: mcp_tokens DELETE + oauth_tokens UPDATE … SET revoked_at.
Performance
- PERF-H1 — new migration adds the indexes flagged in the audit:
trips(user_id), trips(created_at DESC), photos(day_id),
photos(place_id), reservations(day_id), share_tokens(token), plus
conditional day_accommodations and notifications indexes depending
on which columns are present.
Tests
- tests/integration/oidc.test.ts now mocks verifyIdToken and passes
an id_token in the exchangeCodeForToken stub for the three flows
that exercise a successful callback. The three remaining failures
tests pointed out were all pre-existing (file-upload flakes +
notificationPreferences event_types count drift), none introduced
by this PR.
|
||
|
|
d7a71c0572 |
feat(notifications): reminders for todos with upcoming due dates
Todos already support a due_date field but nothing notifies the user when a deadline is approaching — you'd only remember if you happened to look at the Lists tab. This wires a reminder into the existing notification pipeline so due-date todos behave like trip-start reminders. Details: - New `todo_due` event type alongside trip_reminder; all four channels (in-app, email, webhook, ntfy) supported and toggleable per user in Settings > Notifications. - New daily scheduler task (9 AM local TZ) queries unchecked todos whose due_date is within the next 3 days. Each todo gets at most one reminder per 24 hours, tracked via a new todo_items.reminded_at column (migration 116). - If the todo has an assigned user, only that user is reminded; if not, every member of the trip gets the notification. - Strings added in all 15 UI languages and for all notification carriers. - Gated by app_settings.notify_todo_due (default on) so admins can disable it globally. |
||
|
|
51387b0af1 |
feat(auth): add email-based password reset with MFA + session invalidation
Adds /auth/forgot-password and /auth/reset-password endpoints plus two new
client pages. When SMTP is configured the user receives a branded, i18n-aware
reset email; when it isn't the reset link is logged to the server console in
a clearly-fenced block so self-hosters can relay it manually.
Security properties:
- 256-bit cryptographically-random tokens, only SHA-256 hashes stored in DB
- 60 min expiry, single-use, prior unconsumed tokens auto-invalidated
- Enumeration-safe: /forgot-password always responds {ok:true} with a minimum
latency pad so timing doesn't leak account existence
- Per-IP rate limit (3/15min on forgot, 5/15min on reset) + per-email throttle
- If the user has MFA enabled, a valid TOTP or backup code is required at
reset-complete time — a compromised mailbox alone cannot take over a
2FA-protected account
- New users.password_version column + JWT "pv" claim: bumping it on reset
invalidates every live session immediately
- Full audit-log coverage (user.password_reset_request/_success/_fail)
- Forgot-page shows a visible hint when SMTP is unconfigured
Migration 115 adds users.password_version and password_reset_tokens
(user_id, token_hash UNIQUE, expires_at, consumed_at, created_ip).
|
||
|
|
dd90c6d424 |
fix(mcp): add RFC 9728 PRM, RFC 8707 audience binding, and collab sub-feature gating
Root cause: claude.ai's MCP connector (spec 2025-06-18) requires the resource server to publish Protected Resource Metadata and return WWW-Authenticate on 401s to bind the /mcp endpoint to its AS. Without these, it silently shows no tools after OAuth. - Add /.well-known/oauth-protected-resource (RFC 9728) with addon gating - Emit WWW-Authenticate: Bearer resource_metadata=... on 401/auth-failure 403s - Open CORS (origin: *) on both .well-known/* endpoints per RFC 8414/9728 - Accept resource parameter at authorize + token endpoints (RFC 8707) - Store audience on oauth_tokens; validate on every MCP request - Refresh tokens inherit audience; add resource_parameter_supported to AS metadata - DB migration: ADD COLUMN audience TEXT to oauth_tokens - Gate collab MCP tools/resources by chat/notes/polls sub-features individually - Invalidate MCP sessions when collab sub-features are toggled in admin - Update test mocks and MCP.md |
||
|
|
4974013995 |
fix journey bugs reported by roel-de-vries (#722-#736)
Mobile UI: - #722 timeline carousel no longer cut off by BottomNav (uses --bottom-nav-h var) - #723 scroll-snap-type relaxed to proximity so small swipes no longer skip entries - #724 defensive padding-bottom fix in JourneySettingsDialog for iOS PWA - #725 add back/settings buttons + journey title subtitle to mobile activity view - #726 active entry re-centers after scroll settle; tap inactive card activates it (does not jump straight into editor) Entry editor flow: - #727 photo uploads queue locally until Save for existing entries too (previously fired upload immediately; Cancel silently kept the new photo) - #728 Cancel/Close with unsaved changes now requires confirm (window.confirm) - #729 linking a Gallery photo into an entry now copies the row (old MOVE behavior meant Remove-from-Entry also nuked the Gallery original) - #731 addPhoto / addProviderPhoto / linkPhotoToEntry promote skeleton entries to concrete 'entry' type when content is added Permissions: - #732 updateJourney switched from canEdit to isOwner — editors can still edit entries and photos, just not the journey shell (title, cover, status) - #733 Contributors list gains a per-row remove (X) control with confirm - #734 my_role is computed server-side and returned with the journey; UI gates Settings/Add/Edit/Delete controls based on role - #736 createOrUpdateJourneyShareLink + deleteJourneyShareLink now require isOwner (previously NO permission check at all — anyone authenticated could publish or unpublish a journey) Immich upload (#730): - migration 111: add users.immich_auto_upload (default 0) - migration 112: seed provider_field for the toggle (idempotent, FK-safe) - journey photo upload only mirrors to Immich when the user has opted in - Settings UI gets a "Mirror journey photos to Immich on upload" checkbox Test updates: - JOURNEY-SVC-019 inverted to assert editor cannot update journey settings - JOURNEY-SHARE-007 now passes userId (owner) to deleteJourneyShareLink - FE-PAGE-JOURNEYDETAIL-148 inverted to assert photos stay pending until Save - client/tests still green (2676/2676) Also fixed en route: gallery entry title is now the literal 'Gallery' on the wire (used to send the translated label, which broke server-side title === 'Gallery' checks in non-English locales); confirm interpolation uses {username} single braces matching the existing i18n runtime; Settings footer uses icon-only delete/archive buttons on mobile so the row doesn't wrap. |
||
|
|
3f61e1ca38 |
feat: add multi-day transport reservations with dedicated modal and route segmentation
Introduces a TransportModal for creating/editing flight, train, car, and cruise reservations that span multiple days. Transport entries now break the map route into disconnected segments so the polyline reflects actual travel legs. - Add TransportModal with airport/location pickers, multi-day date range, and all transport types - Extend DB schema with end_day_id on reservations (migration 110) and backfill from existing dates - Refactor useRouteCalculation to emit [][][number,number] segments split at transport boundaries - Update MapView, DayPlanSidebar, ReservationsPanel, TripPlannerPage to wire up transport flow - Add transport i18n keys across all 15 languages |
||
|
|
189b257254 |
Merge remote-tracking branch 'origin/dev' into dev-maurice
# Conflicts: # client/src/components/Todo/TodoListPanel.tsx # server/src/db/migrations.ts |
||
|
|
b5b1d32b31 |
feat(photos): add 1h disk cache for remote thumbnails and keep tabs mounted
Closes #686 - Add trekPhotoCache service: SHA1-keyed disk cache under uploads/photos/trek/, 1h TTL, in-flight dedup map to prevent stampedes on concurrent requests - Add migration 108: trek_photo_cache_meta table - Hook cache into streamPhoto for Immich/Synology thumbnail path; originals bypass cache - Add fetchImmichThumbnailBytes / fetchSynologyThumbnailBytes returning Buffer instead of piping, used by the cache layer - Add scheduler entry (every 2h + startup sweep) to evict expired disk files and DB rows via sweepExpired() - Client: convert journey tab conditional-mount to hidden-toggle so img elements stay in DOM across tab switches, preventing redundant thumbnail requests on rapid tab changes - Expose invalidateSize() on JourneyMapHandle; call it on map tab activation to fix Leaflet rendering in previously-hidden container |
||
|
|
9c2decb095 |
fix(maps): reduce Google Places API quota usage with persistent caching
P0 — stop the bleeding:
- Honor place.image_url in MapView and TripPlannerPage to skip redundant fetchPhoto calls
- Trim Place Details field mask (drop reviews/editorialSummary from default; new getPlaceDetailsExpanded for inspector)
- Admin toggle places_photos_enabled (default ON) to kill Google photo fetches under quota pressure; Wikimedia unaffected
- Return { photoUrl: null } instead of 204 so client handles disabled state cleanly
P1 — structural fix:
- New placePhotoCache service: persistent disk cache at uploads/photos/google/<sha1>.jpg, atomic writes, stampede dedup via in-flight Map
- Migrations 105-107: google_place_photo_meta table, place_details_cache table, backfill signed Google URLs to stable proxy URLs
- getPlacePhoto rewrites to fetch image bytes directly, store on disk, return /api/maps/place-photo/:id/bytes proxy URL
- Stable proxy URLs written to places.image_url — survive container restarts, no expiry
- New GET /api/maps/place-photo/:placeId/bytes route serving cached files with long-lived Cache-Control
- Place Details DB row cache with 7-day TTL; ?refresh=1 escape hatch
- photoService fast-path: proxy URLs bypass the mapsApi round-trip and go straight to urlToBase64
Bug fixes:
- MapView now requests base64 thumbs for places with proxy image_url (markers were showing color fallback)
- createPlaceIcon accepts /api/maps/place-photo/ URLs as interim fallback while thumb generates
- setSelectedAssignmentId ReferenceError in mobile day-detail handler (use selectAssignment)
- Remove redundant decodeURIComponent on already-decoded Express route param
- Use SHA1 hash for disk filenames to prevent coords:lat:lng pseudo-ID collisions
- Add checkSsrf guard to Wikimedia byte fetch
- Tighten migration 107 LIKE filter to avoid rewriting manually-pasted Google image URLs
- Validate enabled is boolean on PUT /admin/places-photos
- Drop aggressive iconCache.clear() on every thumb arrival
Observability:
- googleFetch() wrapper counts and debug-logs every outbound Google API call with running total
|
||
|
|
8defc90e95 |
feat(bookings): show transport routes on map (#384, #587)
Adds from/to endpoints to flight/train/cruise/car reservations with live map rendering. Flights use geodesic arcs and a curved duration + distance badge; train/car/cruise render as straight or geodesic lines with endpoint markers. Airports come from an embedded OurAirports database (~3200 airports, offline-capable); train/cruise/car locations via Nominatim. Per-trip connection toggle sits in the day plan sidebar, persisted in localStorage. Clicking a map endpoint opens the existing transport detail popup. New display setting toggles endpoint labels on the map. Migration 105 adds the reservation_endpoints table plus needs_review flag; existing flights are backfilled from their IATA metadata on server startup. |
||
|
|
bdb6b01765 |
fix(synology): paginate all three album sources past 100 albums and tighten targetUserId type
- Extract _fetchAllSynologyAlbums helper that loops until the source is exhausted; listSynologyAlbums now uses it for personal, shared-out, and shared-with-me instead of a hard-capped single request of 100 - Make getSynologyAssetInfo targetUserId required (number, not number|undefined) to match every call site and eliminate an implicit any at the _requestSynologyApi boundary |
||
|
|
5caaeff67c | fix: syntax | ||
|
|
d80bbd5bed | Merge branch 'feat/system-notices' into dev | ||
|
|
293506217e |
feat(notices): add system notice infrastructure
Server-side notice registry with per-user condition evaluation (firstLogin, existingUserBeforeVersion, addonEnabled, dateWindow, role, custom). Notices are sorted by priority then severity, filtered against dismissals stored in a new user_notice_dismissals table, and served via GET /api/system-notices/active + POST /api/system-notices/:id/dismiss. Client renders notices through a host component that partitions by display type (modal / banner / toast). The modal renderer supports multi-page pagination with directional slide transitions, keyboard navigation, and correct dismiss-all semantics on CTA / X / ESC. Dismissals are optimistic with a single background retry. Includes 3.0.0 upgrade notices (v3-photos, v3-journey, v3-features), onboarding welcome modal, and full i18n coverage across 15 languages. The /journey route is addon-gated on both client and server. Also includes: unit + integration test suites, registry integrity test that validates action CTA IDs against client source, and technical documentation in docs/system-notices.md. |
||
|
|
409a63633c |
feat: support check-in time ranges for hotel accommodations
- Add check_in_end column to day_accommodations (Migration 102) - Server: create/update accommodation accepts check_in_end - Bidirectional sync: check_in_end synced between accommodation and linked reservation metadata (check_in_end_time) - DayDetailPanel: shows check-in range (e.g. "14:00 – 22:00"), new "Until" time picker in hotel form - ReservationModal: new check-in-until field for hotel bookings - ReservationsPanel: displays check-in range in metadata cells - i18n: checkInUntil keys in all 15 languages Closes #366 |
||
|
|
7befb7d555 |
feat: enable naver list import by default, remove addon toggle
- Remove addon check from naver import endpoint - Naver import always available alongside Google list import - Migration 101: auto-enable naver_list_import for existing installs - Remove unused isAddonEnabled import from places route - Remove unused useAddonStore import from PlacesSidebar |
||
|
|
9789c51d4f |
fix(naver-import): address PR #495 review issues
- SSRF: validate user-supplied URLs with checkSsrf() before fetch in both importNaverList and importGoogleList; upgrade naver.me substring check to exact hostname comparison to prevent bypass - i18n: add missing places.importNaverList key to de.ts and es.ts - migration: switch Naver addon seed to INSERT OR IGNORE to preserve admin customizations on re-runs; restore budget_category_order CREATE TABLE to its original formatting - route: remove redundant cast after type-narrowing guard in naver-list handler - component: hoist provider ternary above try/catch in handleListImport - tests: add four new Naver import cases (502, empty list, no-coords, canonical URL skipping redirect fetch) |
||
|
|
4362406e74 | Merge remote-tracking branch 'refs/remotes/pull/495' into feat/naver-support | ||
|
|
b194e8317d |
feat(pwa): implement real offline mode with IndexedDB sync
Add genuine offline read/write capability for trips: - Dexie IndexedDB schema (trips, places, packing, todo, budget, reservations, files, mutationQueue, syncMeta, blobCache) - Repo layer for all domains: offline reads from Dexie, writes optimistically to Dexie and enqueue mutations for later replay - Mutation queue with UUID idempotency keys (X-Idempotency-Key), FIFO flush, temp-ID reconciliation on 2xx, fail-and-continue on 4xx - Trip sync manager: caches all trips with end_date >= today or null, auto-evicts 7d after end_date, fetches bundle endpoint in one request - Map tile prefetcher: bbox from place coords, zooms 10-16, 50MB cap, warms SW cache via fetch - Sync triggers: network online → flush + syncAll; WS reconnect → flush only (rate-limiter safe); visibilitychange/30s → flush only - WS remoteEventHandler writes through to Dexie on every event - Server idempotency middleware + idempotency_keys table (migration 100, 24h TTL nightly cleanup) - GET /api/trips/:id/bundle endpoint for efficient single-request sync - OfflineBanner component: amber (offline) / blue (syncing) / hidden - OfflineTab in Settings: cached trip list, re-sync and clear actions - usePendingMutations hook for per-item pending indicators Closes #505 #541 |
||
|
|
b3571f391a |
Fix skeleton entry deletion and add hide suggestions toggle (#619)
- Revert filled skeleton entries back to skeleton on delete instead of permanently removing them - Add per-user hide_skeletons preference on journey_contributors (migration 99) - Add PATCH /journeys/:id/preferences endpoint for toggling skeleton visibility - Add Eye/EyeOff toggle button with custom tooltip in journey detail header - Filter skeleton entries from timeline when hidden - Add i18n keys for all 14 languages |
||
|
|
aa32b1f372 |
fix(migrations): qualify provider column in trip_photos JOIN (migration 98)
Both trip_photos (alias tp) and trek_photos (alias tkp) have a provider column. Using the bare identifier 'provider' in the JOIN condition was ambiguous and caused SQLite to throw SQLITE_ERROR, failing migration 98 and taking down the entire test suite setup. Fix: introduce providerJoinExpr = 'tp.provider' when the legacy trip_photos table already carries a provider column, used only in the two-table JOIN. The single-table INSERT keeps the unqualified form. |
||
|
|
3a52b80e3a |
fix(migration): handle old trip_photos schema (immich_asset_id)
Migration 98 assumed trip_photos already had asset_id + provider columns, but older DBs still have the original immich_asset_id column. Now detects schema variant and adapts accordingly. |
||
|
|
c0c59b6d80 |
feat: unified photo provider abstraction layer (#584)
Introduce trek_photos as central photo registry. Frontend uses /api/photos/:id/:kind instead of provider-specific URLs. Adding a new photo provider is now backend-only work. - New trek_photos table (migration 98) with photo_id FK in trip_photos and journey_photos - Unified /api/photos/:id/thumbnail|original|info endpoint - photoResolverService for central resolution and streaming - ProviderPicker: add "All Photos" tab, rename tabs, fix i18n - Localize all hardcoded strings in JourneyDetailPage (14 langs) - Fix date formatting to use browser locale instead of hardcoded 'en' - Journey stats as styled tile cards |
||
|
|
18da5aed39 | Merge branch 'dev' into feature/naver-support | ||
|
|
f323952012 |
feat: configurable week start day in Vacay (Monday or Sunday)
- New setting in Vacay Settings to choose Mon or Sun as week start - DB migration adds week_start column to vacay_plans (default: Monday) - Calendar grid and weekday headers adapt to the selected start day - Weekend column highlighting works correctly for both modes - Translations added for all 14 languages |
||
|
|
468035fc3c |
fix: reorder migrations — OAuth (84-88) before Journey (89-96)
Dev DB already ran OAuth migrations at indices 84-88. The merge incorrectly placed Journey migrations before OAuth, causing 'duplicate column: parent_token_id' crash on the dev server. |
||
|
|
956c4270df |
merge: resolve conflicts with dev, fix 7 Snyk security issues
- Resolve translation conflicts (keep both journey + OAuth scope keys) - Resolve migrations.ts (dev OAuth migrations + journey migrations) - Fix hono directory traversal, response splitting, input validation (CVE-2026-39407/08/09/10) - Fix @hono/node-server directory traversal (CVE-2026-39406) - Fix nodemailer CRLF injection (upgrade to 8.0.5) |
||
|
|
13956804c2 |
feat: Journey addon — travel journal with entries, photos, public sharing & PDF export
- 5-table schema (journeys, entries, photos, trips, contributors) with migrations 87-91 - Trip-to-Journey sync engine with skeleton entries and photo sync - Full CRUD API for journeys, entries, photos with Immich/Synology integration - Timeline, Gallery and Map views with entry editor (markdown, mood, weather, pros/cons) - Journey frontpage with hero card, stats and trip suggestions - Public share links with token-based access and photo proxy - PDF photo book export (Polarsteps-inspired) - Dashboard redesign: mobile greeting, live trip hero, quick actions, unified card design - BottomNav profile sheet with settings/admin/logout - DayPlan mobile inline place picker - TripFormModal members management - Vacay calendar trip date indicator dots - Fix contributor photo access (403) for journey Immich/Synology photos - Trip deletion cleanup for journey skeleton entries - i18n: 231 new keys across all 14 languages (native translations, no fallbacks) |
||
|
|
7871c06059 |
feat: enhance Synology Photos integration with OTP, SSL skip, and better UX
- Fix endpoint path: users now provide full base URL (e.g. https://nas:5001/photo) - Add OTP/2FA field for Synology login - Add skip SSL verification option (DB column + checkbox UI) - Add device ID (synology_did) column for session tracking - Trigger in-app notification when Synology session is cleared - Show disconnection banner in MemoriesPanel - Add URL hint in provider settings - Map Synology API error codes to human-readable messages - Update i18n for all locales |
||
|
|
6a632137ed | refactor(trip): Naver List Import as Addon |