Commit Graph

1247 Commits

Author SHA1 Message Date
jubnl 37d9a321ab feat: add sync progress and result feedback to offline settings tab 2026-05-05 21:02:25 +02:00
jubnl 544d5641d0 fix: resolve splash hang, dashboard skeleton, and sync-stuck regressions
- TripPlannerPage: change splash effect dep from `trip` (object ref) to
  `trip?.id` (primitive) — background refreshes no longer reset the 1500 ms
  timer on every new object reference, fixing the forever-splash on SPA nav
- tripRepo.list: await upserts on the cold-IDB path so the next mount reads
  from Dexie instead of hitting the network again, fixing the remount skeleton
- tripSyncManager: add stale-flag detection (>2 min resets _syncing), 90 s
  hard timeout via Promise.race, parallel post-sync prefetch via
  Promise.allSettled, and updated header comment to reflect manual-only policy
- OfflineTab: guard handleResync with a 120 s client-side timeout that
  interrupts and clears the spinner if syncAll stalls
2026-05-05 20:52:00 +02:00
jubnl 6b90c7b255 refactor: make syncAll manual-only via offline settings tab
Remove automatic syncAll() calls on login, MFA login, register, and the
window 'online' event. Background bundle sync was the primary cause of
request storms that slowed down initial page loads. Mutation flushing on
reconnect is preserved — only the expensive trip-bundle sync is removed
from auto-triggers.
2026-05-05 20:08:02 +02:00
jubnl 443ae7cb19 fix: prevent splash-forever on slow first-load after clearing storage
Three changes:
- tripSyncManager: add interrupt() so trip page load can stop competing
  background bundle sync requests; also try clearing blobCache before
  falling back to full clearAll() on QuotaExceededError
- TripPlannerPage: call tripSyncManager.interrupt() when mounting so
  loadTrip gets network priority over background syncAll
- TripPlannerPage: show a 'go back to dashboard' link after 12 seconds
  on the splash screen so users are never stuck with no escape
2026-05-05 20:05:10 +02:00
jubnl 83cba5a9ef fix: handle QuotaExceededError in tripSyncManager with progressive eviction
When IDB storage is full, syncTrip() throws an AbortError wrapping
QuotaExceededError. Now: clear that trip's existing data and retry once;
if quota is still exceeded, wipe all IDB data so the next sync starts
fresh rather than silently failing on every subsequent call.
2026-05-05 19:21:52 +02:00
jubnl 2ae4a18466 fix: decouple IDB writes from network-data return path
QuotaExceededError from a full IndexedDB was being caught by the IIFE's
try/catch (after the earlier await-upsert change), causing repos to return
null/empty even when the network fetch succeeded. Fire-and-forget upserts
with .catch(()=>{}) ensure write failures never suppress fetched data.
2026-05-05 19:19:48 +02:00
jubnl f8fdb14627 fix: remove navigator.onLine guards and fix upsert races in all repos
navigator.onLine returns false transiently during service worker activation
(skipWaiting + clientsClaim), causing all repo refresh IIFEs to return null
immediately on first page load — leaving the UI with empty data until F5.

Fixes applied across all list repos (trip, day, place, packing, todo, budget,
reservation, accommodation, file):
- Drop navigator.onLine guard; let fetch fail naturally when truly offline
- Await all upsert calls (some were fire-and-forget, risking race conditions
  against subsequent reads and silent swallowed failures)
- Return Promise.resolve(null) instead of Promise.resolve(fresh) in the
  IDB-empty network path, so loadTrip's background refresh Promise.all
  resolves null and skips set({trip}), preventing a spurious reference change
  that was resetting the 1500ms splash timer

Tests updated: placeRepo and packingRepo "empty cache" tests now simulate
genuine network failure (HttpResponse.error) instead of relying on the
navigator.onLine guard that no longer exists; DashboardPage tests clear IDB
before each test and use a query-safe assertion after background refresh.
2026-05-05 18:04:15 +02:00
jubnl 81a59edf03 fix: surface app-config load failure so SSO button is never silently hidden
When /api/auth/app-config fails (ZT redirect, network blip) the login page
now shows a warning banner with a Refresh button instead of silently omitting
the SSO sign-in button. The SW also now applies authRedirectPlugin to this
endpoint so ZT opaque redirects are converted to 401 AUTH_REQUIRED rather
than causing a JSON parse failure that went undetected.

Translations added for all 15 supported languages.
2026-05-05 16:27:53 +02:00
jubnl a1f4643b90 docs: document ChatGPT MCP + Cloudflare Bot Fight Mode issue 2026-05-05 15:37:59 +02:00
jubnl 55ef0f3ca9 feat: add OIDC userinfo endpoint for ChatGPT domain claiming
ChatGPT enables OIDC when it finds /.well-known/openid-configuration
and uses the userinfo endpoint to fetch the authenticated user's email
for authorization domain claiming.

- Add GET /oauth/userinfo: validates Bearer token, returns sub/email/
  email_verified/preferred_username from the OAuth access token
- Add userinfo_endpoint to /.well-known/openid-configuration response
- Add /oauth/userinfo to open-CORS pre-middleware
2026-05-05 14:41:15 +02:00
jubnl 895f34deba refactor: extract getOAuthMetadata() shared by both discovery endpoints
Both /.well-known/oauth-authorization-server (via SDK router) and
/.well-known/openid-configuration now serve the same OAuthMetadata
object built once from a shared lazy getter.

The MCP spec explicitly states clients try OIDC Discovery or RFC 8414
depending on server support — ChatGPT uses OIDC Discovery first.
Serving the OAuth AS metadata at the OIDC URL is the correct approach;
clients only read the OAuth fields (authorization_endpoint,
token_endpoint, registration_endpoint) from it.
2026-05-05 14:32:59 +02:00
jubnl f9db7e1104 fix: serve OAuth AS metadata at /.well-known/openid-configuration
ChatGPT uses OIDC discovery to bootstrap the OAuth flow: it fetches
/.well-known/openid-configuration to find the registration_endpoint,
authorization_endpoint, and token_endpoint before attempting DCR.
Without this endpoint responding, it cannot proceed and reports
'does not implement OAuth'.

Serve the same AS metadata at the OIDC discovery URL so OIDC-first
clients can bootstrap the full OAuth 2.1 + DCR flow.
2026-05-05 14:25:05 +02:00
jubnl 001cc6431b fix: 404 JSON + open CORS for all /.well-known/* paths
ChatGPT probes /.well-known/openid-configuration and the RFC 8414
path-suffixed form /.well-known/oauth-authorization-server/mcp before
(or instead of) following the RFC 9728 WWW-Authenticate chain.
Both returned 200 HTML from the SPA catch-all, which clients can't
parse as JSON — ChatGPT reported 'does not implement OAuth'.

Two fixes:
- Extend open-CORS pre-middleware from /.well-known/oauth-* to all
  /.well-known/* so browser-based probes aren't CORS-blocked
- Add a 404 JSON catch-all for /.well-known/* paths the SDK metadata
  router doesn't handle, placed before the SPA catch-all
2026-05-05 14:16:28 +02:00
jubnl af10ab1c93 fix: add /mcp to open-CORS pre-middleware
External MCP clients (ChatGPT, Claude.ai, MCP Inspector) call /mcp
cross-origin with Bearer tokens. The OPTIONS preflight was hitting the
SPA catch-all because the global cors({ origin: false }) didn't add
Access-Control-Allow-Origin. Without a valid CORS response the browser
blocked the subsequent POST, preventing the 401 WWW-Authenticate header
from being read — ChatGPT reported 'does not implement OAuth'.
2026-05-05 13:57:52 +02:00
jubnl 8e14434a1b fix: thread resource indicator through OAuth consent flow
The consent page extracted client_id, redirect_uri, scope, state,
code_challenge from URL params but silently dropped `resource`. Without
it the auth code had no resource binding, tokens were issued with
audience=null, and the MCP handler's RFC 8707 audience check rejected
every token — "There was a problem connecting TREK."

Fix: extract `resource` from URLSearchParams and forward it through
oauthApi.validate() and oauthApi.authorize(). Add the field to both
API type signatures.
2026-05-05 13:48:43 +02:00
jubnl fb6eaaf06d fix: open CORS for OAuth register/authorize + correct WWW-Authenticate PRM path
Two follow-up fixes after the SDK auth migration:

1. CORS for browser-based OAuth clients (ChatGPT DCR 403)
   The global cors({ origin: false }) intercepts OPTIONS preflight for
   /oauth/register and /oauth/authorize before the SDK's own cors()
   middleware inside clientRegistrationHandler/authorizationHandler
   runs, causing the browser to reject the response with no
   Access-Control-Allow-Origin header. ChatGPT's connector makes DCR
   from the browser, so this manifested as a 403.
   Fix: extend the open-CORS pre-middleware to also cover
   /oauth/register and /oauth/authorize (same pattern as /.well-known).

2. WWW-Authenticate resource_metadata URL (RFC 9728 §5)
   The MCP handler was advertising the base PRM path
   (/.well-known/oauth-protected-resource) instead of the path-aware
   variant (/.well-known/oauth-protected-resource/mcp). RFC 9728
   requires the resource path to be appended when the resource URI has
   a path component. The SDK registers the path-aware URL; the
   WWW-Authenticate header now points to the same location.
2026-05-05 13:29:40 +02:00
jubnl 86129bbfbc feat: migrate OAuth public endpoints to MCP SDK auth handlers
Fixes issue #959 — two bugs causing ChatGPT's custom MCP connector to fail:

1. RFC 9728 path-based PRM: ChatGPT requests
   /.well-known/oauth-protected-resource/mcp (path-aware URL per RFC 9728
   §5). The old TREK handler only registered the base path; requests for
   the path variant fell through to the SPA catch-all and returned HTML.
   mcpAuthMetadataRouter registers the path-aware URL automatically.

2. DCR without scope: ChatGPT never sends scope during Dynamic Client
   Registration (RFC 7591 makes it optional). The old handler returned
   400 for missing scope. clientRegistrationHandler accepts it;
   trekClientsStore.registerClient defaults to ALL_SCOPES when absent,
   and the user still grants only what they approve at the consent UI
   (scopeSelectable=true for DCR clients is unchanged).

Hybrid approach: SDK handles /.well-known, /oauth/authorize (redirect to
consent SPA), and /oauth/register. TREK keeps its own /oauth/token and
/oauth/revoke because SDK clientAuth does plain-text secret comparison
while TREK uses SHA-256 hashing — incompatible without a full clientAuth
rewrite.

SPA consent page renamed /oauth/authorize → /oauth/consent to avoid
routing conflict with the SDK's backend authorize handler now mounted at
that path. Existing URL paths (/oauth/token etc.) are unchanged so
active Claude.ai connections are unaffected.

Other: lazy-init SDK metadata router so getAppUrl() (DB query) is not
called at createApp() time; path-aware mcpAddonGate so only /.well-known
returns 404 when MCP is disabled (previously a blanket middleware blocked
all routes including static files); /api/oauth mounted before the SDK
middleware chain so SPA-facing routes with their own 403 gates are
reached correctly.
2026-05-05 13:01:32 +02:00
jubnl 69620e7276 feat: always-optimistic write pattern across all repos
All create/update/delete repo methods now write to IndexedDB optimistically
and fire mutationQueue.flush() as fire-and-forget, returning immediately
without waiting for the network. This eliminates the 8-second UX freeze
previously seen when the API was unreachable but navigator.onLine was true.

- Repos rewritten: trip, day, place, packing, todo, budget, accommodation,
  reservation, file — write methods never throw, always return optimistic data
- mutationQueue.flush() changed to iterative (one item per loop iteration)
  so mutations enqueued mid-flush (e.g. bulk check-all) are picked up
- fileRepo.toggleStar skips the IDB put when the file is not cached locally
- DayDetailPanel passes place_name into accommodationRepo.create so the
  optimistic accommodation renders the correct hotel label immediately
- Test suite updated throughout to reflect optimistic-first semantics:
  no more rollback assertions, IDB cleared in component test beforeEach hooks,
  FileManager tests switched from filesApi spy to MSW endpoint assertions
2026-05-05 02:14:39 +02:00
jubnl 3aa6b0952a fix: repair test suite after SWR offline-read changes
Add navigator.onLine guard to SWR refresh IIFEs so background
network calls don't fire in offline mode (prevents fake-IDB leakage
in tests via MSW default handlers).

Fix IDB isolation in affected test files by flushing pending macro
tasks then clearing IDB tables in beforeEach, so stale IDB writes
from previous tests' background IIFEs don't bleed into the next test.

Restore loadBudgetItems and refreshPlaces to apply background refresh
results to store state.

Move tags/categories API calls before the main Promise.all in
loadTrip so MSW handlers resolve during the await window.
2026-05-05 01:01:34 +02:00
jubnl a6a0521261 docs: update Offline-Mode-and-PWA wiki for SWR changes 2026-05-04 22:45:42 +02:00
jubnl a83b369cb7 fix: stale-while-revalidate for offline reads + axios timeout
navigator.onLine is unreliable on Android — returns true whenever any
network interface is up, regardless of actual reachability. This caused
all repo reads to take the API branch and either wait 5 s for the SW
NetworkFirst timeout (cache hit) or hang indefinitely (cache miss).

- All read repos (list/get) now return cached IndexedDB data instantly
  and carry a background refresh promise that resolves to fresh data or
  null on failure. Callers that opted in (loadTrip, loadTrips) apply
  fresh data silently when it arrives.
- tripStore.loadTrip: Promise.all now reads all 7 resources from
  IndexedDB (instant), fires network refreshes in background, sets
  isLoading: false immediately, then applies fresh data via a second
  Promise.all when ready. Tags/categories use upsertTags/upsertCategories.
- DashboardPage.loadTrips: same pattern — renders from cache instantly,
  silently updates trip list on refresh.
- axios timeout set to 8 s so requests can never hang indefinitely.
- SW networkTimeoutSeconds lowered from 5 to 2 as defence in depth.
2026-05-04 22:43:10 +02:00
jubnl 79d5fa7670 chore: update lock file 2026-05-04 22:01:01 +02:00
jubnl b94060aec1 fix: pass through reverse-proxy auth redirects in service worker
Navigation requests now use redirect:'manual' + network-first so upstream
auth gates (Cloudflare Zero Trust, Pangolin) can redirect the browser to
their SSO login page instead of being swallowed by the precached app shell.

An authRedirectPlugin on the API NetworkFirst strategy handles mid-session
expiry: detects opaqueredirect responses and converts them to a synthetic
401 { code: 'AUTH_REQUIRED' } that the existing Axios interceptor picks up,
triggering a full re-auth flow.

Offline fallback to the precached app shell is preserved.

Closes #836
2026-05-04 21:54:54 +02:00
jubnl 852f0085d1 feat: complete offline write support with mutation queue + runtime SW cache config
- Add offline CRUD to todoRepo, budgetRepo, reservationRepo, accommodationRepo,
  dayRepo, tripRepo, fileRepo with optimistic Dexie writes and mutation queue
- Wire all store slices (todo, budget, reservations, files, dayNotes, assignments,
  tripStore) through repos for offline-aware writes
- Cover archive/unarchive, file toggleStar/update/delete, assignment create/delete,
  day title/notes update offline paths
- Migrate service worker from generateSW to injectManifest (custom sw.ts) with
  runtime-configurable api-data (7d/500) and map-tiles (30d/1000) cache policies
- Add Settings → Offline cache configuration UI with save/reset and live SW postMessage
- Extend mutationQueue flush to cover all writable Dexie tables
2026-05-04 21:36:44 +02:00
github-actions[bot] 640e5616e9 chore: bump version to 3.0.15 [skip ci] v3.0.15 2026-05-04 12:22:15 +00:00
Julien G. 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).
2026-05-04 14:21:55 +02:00
Tranko 256f38d8fa docs: add budget documentation GIFs (create, add expense, final settlement) (#948) 2026-05-03 18:56:56 +02:00
jubnl 9592cc663f docs: document wiki-only PR exemption from branch enforcement 2026-05-03 18:48:39 +02:00
jubnl dba4b28380 ci: exempt wiki-only PRs from branch target enforcement 2026-05-03 18:43:21 +02:00
github-actions[bot] 51b5bd6966 chore: bump version to 3.0.14 [skip ci] v3.0.14 2026-05-03 15:40:00 +00:00
Julien G. 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 9a08368). Merge
the two-tier "always blocked / conditionally blocked" structure into a
single table, add a warning about cloud metadata exposure.

* fix(ssrf): let .local/.internal hostnames pass to IP-level checks

The pre-DNS hostname block was redundant: any .local/.internal host
that resolves to a private IP is already gated by isPrivateNetwork +
ALLOW_INTERNAL_NETWORK, and any that resolves to loopback/link-local
is caught by isAlwaysBlocked unconditionally.

Dropping the hostname pre-check means Docker/LAN deployments can reach
services on .local hostnames (e.g. immich.local) with
ALLOW_INTERNAL_NETWORK=true, while loopback and link-local IPs
(including 169.254.169.254) remain hard-blocked with no override.

Reverts the isAlwaysBlocked guard loosening from 9a08368.

* fix(auth): trim username and email on all write paths

Self-registration stored values verbatim, so trailing whitespace could
produce rows that lookup code (which trims input) silently misses.
Trim username and email before validation and INSERT in registerUser,
adminService.updateUser, and oidcService.findOrCreateUser. updateSettings
and adminService.createUser already trimmed correctly.

Adds a one-shot backfill migration (trimUserWhitespace) that trims
existing dirty rows; collisions are resolved by appending __migrated_<id>
to the value with a loud console.warn so operators can review affected
accounts.

18 new tests covering registration trim, duplicate detection, admin
update trim, trip-member lookup regression, and all migration branches.

* feat(notices): add v3014-whitespace-collision admin notice

Adds a dismissible banner for admins on v3.0.14+ that fires only when
the whitespace-trimming migration detected a username/email collision
(stored in app_settings as whitespace_migration_collision=true).

Notice conditions: existingUserBeforeVersion(3.0.14) + role=admin +
custom predicate reading the app_settings flag. Predicate registered in
registry.ts; migration step writes the flag when hadCollision=true.

All 15 translation files updated with title/body keys.
7 integration tests added (SN-COLLISION-1 through -7) covering all
condition branches: shown when all conditions met, hidden when flag
absent/false, hidden for non-admin, hidden for new user, hidden below
min app version, hidden after dismissal.
2026-05-03 17:39:45 +02:00
github-actions[bot] 4ae4e0c676 chore: bump version to 3.0.13 [skip ci] v3.0.13 2026-04-30 23:43:49 +00:00
Julien G. 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 8e05ba7. Prevents inverted
  start_day_id/end_day_id on trips with non-monotonic day IDs.

* Clearing accommodation_id on a hotel reservation now forces
  assignment_id to null in the save payload, removing the stale
  day-assignment link that had no UI path to clear.

* Migration: swaps inverted start_day_id/end_day_id pairs in
  day_accommodations where start.day_number > end.day_number,
  recovering existing corrupt rows from the pre-fix picker bug.

* Tests FE-PLANNER-RESMODAL-050/051/052 cover both fixes.

* fix: preserve line breaks and wrap long URLs in notes fields (#930)

Add remark-breaks to all reservation/place notes markdown renderers so
single newlines render as <br>, and add wordBreak/overflowWrap styles
so long unbroken URLs (e.g. booking.com tracking links) wrap correctly.

* fix: delete linked budget item when accommodation or reservation is deleted (#933)

Deleting an accommodation or reservation now removes any budget item
linked via reservation_id, preventing orphan entries in the Budget page.
Also fixes a pre-existing payload-shape bug where budget:deleted was
broadcast with {id} instead of {itemId}, breaking live updates for
collaborators when a reservation price was cleared.

Tests added: ACCOM-006, RESV-009b, BUDGET-004b.

* fix: restore scroll position in mobile Plan and Places sidebars on reopen (issue #932)

Both DayPlanSidebar and PlacesSidebar have their own internal scroll
containers (overflowY: auto). Scroll events don't bubble, so previous
attempts that tracked scrollTop on the outer portal div never fired.

Each sidebar now accepts initialScrollTop and onScrollTopChange props.
The internal scroll container saves its scrollTop via onScrollTopChange
on every scroll event, and restores it via useLayoutEffect on mount
(before the browser paints, so no visible flash).

TripPlannerPage holds the saved values in refs (mobilePlanScrollTopRef,
mobilePlacesScrollTopRef) and passes them through on each portal mount.

* fix(map): prevent auto zoom-out when opening/closing place inspector (issue #921)

Both Leaflet and Mapbox GL renderers now gate fitBounds strictly on fitKey
increments from the parent. Selecting or dismissing a place inspector changes
paddingOpts (via hasInspector) but no longer triggers a re-fit that zoomed
the map out to the full trip extent when no day was selected.

Also removes the zoom-12 visibility gate on Leaflet route info pills so they
render at all zoom levels when a route is active.

* fix: translate mobile bottom-nav tab labels (issue #931)

Replaced hardcoded English labels in BottomNav with t() lookups using the same translation keys as the desktop navbar (nav.myTrips, admin.addons.catalog.*.name).
2026-05-01 01:43:19 +02:00
github-actions[bot] 8b53948231 chore: bump version to 3.0.12 [skip ci] v3.0.12 2026-04-28 22:17:13 +00:00
Julien G. 78d6f2ba77 Bug fixes - April 28th 2026 (#915)
* fix: replace raw day-ID range checks with position-based helper (issue #889 follow-up)

Commit 8e05ba7 fixed the accommodation date-range pickers, but the
post-save state filters in DayDetailPanel and several other consumers
still compared `day.id >= start_day_id && day.id <= end_day_id`. With
non-monotonic ID layouts (day_number 1-9 → IDs 17-25, day_number 10-16
→ IDs 1-7) this made the just-saved accommodation immediately invisible
— matching the regression reported in the last comment of #889.

Introduces `isDayInAccommodationRange` in `client/src/utils/dayOrder.ts`
which compares positional order (`day_number` with `indexOf` fallback)
rather than raw IDs. Falls back to the old numeric comparison when
endpoint days are absent from the loaded array (sparse test data or
partial loads) so existing tests are unaffected.

Fixed call sites:
- DayDetailPanel.tsx (initial load, post-create, post-delete, post-edit-save)
- DayPlanSidebar.tsx (daily badge renderer)
- SharedTripPage.tsx (public share view)
- TripPDF.tsx (PDF export filter + sort)

Also declares `day_number?: number` on the client `Day` type (already
returned by the server but previously untyped).

Adds regression tests FE-PLANNER-DAYDETAIL-060/061/062 covering the
edit-save, create-save, and initial-load paths with the reporter's exact
non-monotonic ID layout.

* fix: non-transport reservations no longer appear as transports in day planner (issue #914)

getTransportForDay now uses TRANSPORT_TYPES allowlist instead of only excluding hotels,
and the click handler dispatches to onEditReservation for non-transport types instead of
always opening TransportModal, preventing silent type coercion to 'flight'.

* feat: add file attachment support to TransportModal (issue #918)

Transports (flight/train/car/cruise) now support file attachments identical to the reservation modal — upload on create/edit, link existing files, and unlink. The Files tab and Assign File modal now differentiate between bookings and transports with separate sections and type-specific icons. Translations added for all 15 locales.
2026-04-29 00:16:56 +02:00
jubnl bb89d70a94 docs: document required permissions for Immich and Synology photo providers
Co-authored-by: Ben Haas <ben@benhaas.io>
2026-04-28 05:32:39 +02:00
jubnl ad9f3887d8 docs: add wiki guide for adding places to day itinerary with GIFs
Co-authored-by: Tranko <tranko@gmail.com>
2026-04-28 05:32:35 +02:00
github-actions[bot] 7f1fb508db chore: bump version to 3.0.11 [skip ci] v3.0.11 2026-04-28 03:17:32 +00:00
Julien G. 1f5deeba6c Bug fixes - April 27th 2026 (#907)
* fix: clean up dangling FK references before deleting a user

Resolves FOREIGN KEY constraint failed (500) on DELETE /api/admin/users/:id
and DELETE /api/auth/me when the target user had rows in trip_members.invited_by,
share_tokens.created_by, budget_items.paid_by_user_id, journeys.user_id,
journey_entries.author_id, journey_contributors.user_id, or
journey_share_tokens.created_by — none of which had ON DELETE clauses.

Introduces deleteUserCompletely() in userCleanupService.ts which wraps all
cleanup and the final DELETE FROM users in a single transaction. Both
adminService.deleteUser and authService.deleteAccount now call it instead of
the bare DELETE. Tests ADMIN-005b and AUTH-040 cover all reference types
including notification sender/recipient and notice dismissals.

* test: extend FK deletion tests to cover journeys, files, and photos

ADMIN-005b and AUTH-040 now also seed and assert:
- owned journey with entries (cascade-deleted via journeys.user_id cleanup)
- trip_files.uploaded_by (SET NULL — file survives, attribution cleared)
- trek_photos.owner_id (SET NULL — photo record survives, owner cleared)
- trip_photos.user_id (CASCADE — photo association removed)

* test: extend user deletion tests to cover all FK relationships

ADMIN-005b and AUTH-040 now seed and assert every user FK relationship:

CASCADE (row deleted): trips, trip_members, tags, mcp_tokens, oauth_tokens,
oauth_consents, vacay_plans, vacay_plan_members, bucket_list,
visited_countries, visited_regions, packing_templates, invite_tokens,
collab_notes, settings, password_reset_tokens, notification_channel_preferences

SET NULL (row survives, column nulled): categories, todo_items.assigned_user_id,
packing_bags, audit_log

Caught and fixed: notification_preferences was dropped in migration 72;
correct table is notification_channel_preferences.

* fix: preserve URL hash and OIDC redirect target through login flow

- Include location.hash in redirect param at all three producer sites
  (ProtectedRoute, axios 401 interceptor, OAuthAuthorizePage) so
  hash fragments survive the login bounce
- Stash redirectTarget in sessionStorage before any OIDC provider
  redirect and restore it after the code exchange, since the IdP
  strips the original ?redirect= param during the roundtrip
- Clear sessionStorage on OIDC error to avoid stale state
- Add tests covering sessionStorage stash on mount, navigate to saved
  redirect after OIDC exchange, fallback to /dashboard, and cleanup
  on error

* fix: use day position instead of ID for accommodation date range clamping

Math.min/Math.max over raw day IDs breaks the start/end picker when a
trip's day IDs are non-monotonic relative to day_number (normal after
repeated generateDays extend/shrink cycles). Replaced with findIndex
lookups so clamping is always based on positional order.

Closes #889

* fix: normalize env var comparisons to be case-insensitive

All NODE_ENV, DEMO_MODE, OIDC_ONLY, FORCE_HTTPS, COOKIE_SECURE, and
ALLOW_INTERNAL_NETWORK checks now use .toLowerCase() so values like
'Production' or 'True' behave identically to their lowercase forms.
Also adds APP_VERSION to the startup banner.

* fix: delete surplus days when shortening a trip

When shrinking a trip's date range, surplus days are now deleted along
with their assignments, notes, and accommodations (cascade). Places
remain in the trip pool; reservations keep their day reference nulled
by the existing ON DELETE SET NULL constraint (issue #909).

Updates TRIP-SVC-011 to reflect the new behaviour; adds TRIP-SVC-016
as a regression test for the empty-day case.

* fix: auto-backup retention deletes itself and manual backups on Docker

Two bugs in cleanupOldBackups:
1. Filter was .endsWith('.zip') — swept manual backup-*.zip files too.
   Now restricted to auto-backup-* prefix.
2. Age was derived from stat.birthtimeMs, which is 0 on overlayfs
   (Docker default), making every backup appear epoch-old and get
   deleted immediately. Age is now parsed from the filename timestamp
   and falls back to mtimeMs (reliable on overlayfs).

Also converts inline require('./services/auditLog') calls to a static
import throughout scheduler.ts, and adds 8 unit tests covering the
fixed retention logic including the overlayfs regression case.

* test: update TRIP-024 to match delete behavior on trip shrink

* feat: add bypass-branch-check label to skip branch enforcement
2026-04-28 05:17:20 +02:00
jubnl ca832e8d88 chore: prevent new build on workflow change v3.0.10 2026-04-27 00:31:22 +02:00
jubnl 12fc7f7b68 docs: fix Proxmox update section to run inside LXC and add command 2026-04-27 00:28:48 +02:00
github-actions[bot] 2770a189df chore: bump version to 3.0.10 [skip ci] 2026-04-26 22:22:31 +00:00
jubnl 2b162a8cc7 chore: reset to 3.0.9 2026-04-27 00:22:09 +02:00
github-actions[bot] 009d89fecf chore: bump version to 3.0.10 [skip ci] 2026-04-26 22:15:15 +00:00
jubnl 5c3b89578d docs: add Proxmox VE LXC install guide and update CI ignore paths
- Add wiki/Install-Proxmox.md with full install/update/log instructions
- Add Proxmox VE section to wiki/Updating.md
- Add Install: Proxmox VE (LXC) to wiki/_Sidebar.md
- Add "Proxmox Community Script" option to bug report install dropdown
- Exclude GitHub meta files from triggering Docker CI workflow
2026-04-27 00:14:50 +02:00
github-actions[bot] 303e7de433 chore: bump version to 3.0.9 [skip ci] v3.0.9 2026-04-26 19:59:33 +00:00
Maurice 08eb7f3733 Merge pull request #892 from mauriceboe/fixes-26-04-2026
fixes-26-04-2026
2026-04-26 21:59:21 +02:00
jubnl 90d86eda61 chore: Add Trademark policy 2026-04-26 15:36:34 +02:00
jubnl 0eca6d54a1 chore: Add Trademark policy 2026-04-26 15:27:33 +02:00
Julien G. bc1fb71391 Fix exit code 132 on old CPUs by replacing sharp with jimp (issue #888) (#895)
sharp's prebuilt Linux x64 binary requires SSE4.2 (x86-64-v2), causing a
SIGILL crash on older hardware (e.g. AMD A6-3420M). Replace with jimp, a
pure-JS image library with no native binaries. Also skip thumbnail generation
entirely when the Journey addon is disabled (the default), preventing the
issue for most installs regardless of the image library used.
2026-04-26 13:26:09 +02:00