Commit Graph

559 Commits

Author SHA1 Message Date
Maurice
21511c2f68
Merge pull request #700 from mauriceboe/feat/v3-thankyou-notice
feat: v3 thank-you notice, mobile map+timeline, modal UX improvements
2026-04-16 23:51:13 +02:00
jubnl
e078a9d9e1
fix: getAppVersion now getting 1st from environment, fallback to package.json, fallback to 0.0.0 if all failed 2026-04-16 23:36:33 +02:00
Maurice
df075630fb feat(system-notices): add personal thank-you notice for v3.0.0
Personal note from the creator shown as the first page in the 3.0
upgrade modal. Includes community links (Discord, Ko-fi) and a
special shout-out to jubnl. Modal UX improved: users must click
through all pages before dismissing, wider layout, enhanced
markdown rendering with styled links, signature, and HR separator.
i18n coverage across all 15 languages.
2026-04-16 22:25:03 +02:00
jubnl
ae4d317dc3
fix(journey): serve local file when uploading photos with Immich sync enabled
After upload, trek_photos.provider is immediately flipped to 'immich' even
though Immich's thumbnail generation is async. streamPhoto then routed to
Immich, which returned an error for the not-yet-processed asset. Because
Cache-Control was set before the proxy attempt, the error response was cached
by the browser for 24h — breaking thumbnails until a hard refresh bypassed
the cache and Immich had finished processing.

- streamPhoto now prefers the local file_path when it exists on disk,
  regardless of provider; Immich/Synology are only used when no local
  file is available (fixes the immediate broken-thumbnail symptom)
- pipeAsset sets Cache-Control: no-store on upstream errors and uses the
  caller-supplied default only on success (prevents cache poisoning)
- streamImmichAsset no longer pre-sets Cache-Control before the proxy
- streamSynologyAsset passes the same defaultCacheControl through pipeAsset

Closes #691
2026-04-16 21:20:38 +02:00
jubnl
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
2026-04-16 20:54:35 +02:00
jubnl
129dfabaa3
feat(synology): persist and use passphrase for shared album photo streaming (#689-4)
- syncSynologyAlbumLink now uses getAlbumLinkForSync to read the stored
  passphrase and passes it in the SYNO.Foto.Browse.Item call when present,
  falling back to album_id for links without a passphrase.
- Selection type gains optional passphrase field; addTripPhotos and
  _addTripPhoto thread it through to getOrCreateTrekPhoto.
- getOrCreateTrekPhoto accepts an optional passphrase (4th param) and
  encrypts it when inserting a new trek_photos row; backfills existing
  rows that lack a passphrase.
- streamPhoto and getPhotoInfo decrypt the stored passphrase from
  trek_photos and forward it to streamSynologyAsset / getSynologyAssetInfo
  so shared-album photos resolve correctly at access time.
- Add SYNO-054 integration test covering the passphrase sync-and-persist
  path end-to-end.
2026-04-16 20:05:18 +02:00
jubnl
8a6d1b2aaf
feat(synology): merge personal, shared-out, and shared-with-me albums in listSynologyAlbums
Fire all three Synology album sources in parallel via Promise.allSettled so a
permissions failure on one source (e.g. SYNO.Foto.Sharing.Misc) never blocks
personal album display. Deduplicate by album id (last-write-wins), propagate
passphrase from shared/shared-with-me entries, and return the merged list sorted
by albumName. Extends AlbumsList type to carry optional passphrase.

Adds SYNO-027/028/029 integration tests; updates SYNO-060/061/081 to match
the new multi-source call pattern.
2026-04-16 19:56:10 +02:00
jubnl
465b78411a
fix(synology): resolve pagination offset using correct size before computing page offset
The `size` → `limit` assignment was evaluated after `page * limit`, causing
the offset to be computed using the hardcoded default (100) instead of the
caller-supplied page size. Swapping the two `if` blocks ensures `limit` is
resolved from `size` first so the offset is always `(page-1) * size`.

Adds SYNO-025 and SYNO-026 integration tests that capture the raw Synology
API body and assert `offset` and `limit` are forwarded correctly.
2026-04-16 19:49:08 +02:00
jubnl
da70388f4b
fix(journey): resolve Immich photos on public share by matching trek_photos.id
validateShareTokenForPhoto was querying journey_photos by jp.id but the
public page sends p.photo_id (trek_photos.id) in the URL. In a fresh
database the IDs coincidentally match, masking the bug. In production
instances with many Immich-synced photos the trek_photos autoincrement
is far ahead of journey_photos, causing a 404 for every Immich photo
on the public share page.

Fix: change the lookup to jp.photo_id = ? so validation is keyed on
trek_photos.id, which is what the client sends and what streamPhoto
needs. Updated the test helper to return trekId and added a regression
test that pre-populates trek_photos to produce diverging IDs. Closes #675.
2026-04-16 15:37:24 +02:00
jubnl
6c1a795460
fix(journey): paginate Immich picker and group photos by date
The /search route was looping up to 20 pages server-side, returning a
blob of up to 1000 photos with no hasMore flag, which prevented the
client's existing ScrollTrigger infinite scroll from ever firing.

Now the route proxies the client's page param directly to Immich and
returns a single page plus hasMore, enabling full library browsing.

The photo picker grid now groups photos by takenAt date (already
present in every asset response) with a date label above each group,
restoring the date-oriented browsing from V2. Closes #674.
2026-04-16 15:32:56 +02:00
jubnl
61b8070626
fix(system-notices): coerce prerelease app version before semver comparison 2026-04-16 14:58:38 +02:00
jubnl
5caaeff67c
fix: syntax 2026-04-16 14:55:35 +02:00
jubnl
58a8e97f94
feat(system-notices): add v3-mcp notice for OAuth 2.1 upgrade
Adds a warn-severity modal notice targeting existing users who have the
MCP addon enabled. Communicates that OAuth 2.1 is now the recommended
auth method, static trek_ tokens are deprecated, and the toolset has
been significantly expanded. Priority 75 — slots between v3-journey and
v3-features in the upgrade modal sequence. Translations for all 15 languages.
2026-04-16 14:48:13 +02:00
Julien G.
d80bbd5bed
Merge branch 'feat/system-notices' into dev 2026-04-16 14:38:14 +02:00
jubnl
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.
2026-04-16 14:36:33 +02:00
Maurice
9739542a3a
Merge pull request #672 from mauriceboe/feature/uncategorized-filter
feat: add uncategorized filter to category dropdown and more
2026-04-16 00:34:28 +02:00
Maurice
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
2026-04-16 00:23:00 +02:00
Maurice
975846c236 fix: update tests for naver always-on and reservations redesign
- Remove server test for naver addon disabled (addon check removed)
- Update PlacesSidebar tests: "Google List" → "Import List" (both
  providers always shown)
- Update ReservationsPanel tests: status is always a span (no toggle),
  remove click-to-toggle test, update summary test
2026-04-16 00:04:14 +02:00
Maurice
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
2026-04-15 23:57:09 +02:00
Maurice
099255761c feat: collab sub-feature toggles and provider icons
- Add admin toggles for individual collab sections (Chat, Notes,
  Polls, What's Next) stored in app_settings
- CollabPanel adapts layout dynamically: chat always fixed 380px,
  remaining panels share space equally
- Mobile: disabled tabs are hidden
- Add Immich and Synology Photos SVG icons to photo provider toggles
- Add Luggage icon to bag tracking sub-toggle
- API: GET/PUT /admin/collab-features endpoints
- i18n: all 15 languages updated

Closes #604
2026-04-15 23:53:16 +02:00
jubnl
e45a0efce3
feat(admin): add admin-configurable default user settings
Allow admins to set instance-wide defaults for temperature unit, color
mode, time format, route calculation, blur booking codes, and map tile
URL via a new Admin > User Defaults tab. Defaults are stored in
app_settings (prefixed default_user_setting_*) and applied at read time
as a fallback — user's own explicit values always take priority.
Translations added for all 16 supported languages.
2026-04-15 22:31:41 +02:00
jubnl
42c216b00b
fix(immich): serve fullsize thumbnail for original to fix HEIC rendering
Raw /assets/{id}/original returns HEIC bytes which only Safari can
render natively. Switch to /assets/{id}/thumbnail?size=fullsize which
Immich transcodes to a browser-compatible format.

Closes #668
2026-04-15 22:02:48 +02:00
jubnl
9e8d101d63
fix(ntfy): improve admin ntfy UX and add clear token button
- Add missing admin.ntfy.hint translation key in all 15 languages
- Add admin ntfy server hint clarifying it is the default for users
- Expose admin_ntfy_server via PreferencesMatrix so user settings
  placeholder reflects the admin-configured default
- Add clear token button to admin ntfy panel (same pattern as user settings)
- Extract common.clear from settings.ntfyUrl.clearToken across all 15 languages
2026-04-15 20:23:31 +02:00
jubnl
bfe84b3016
feat(notifications): add ntfy as a first-class notification channel
Adds ntfy.sh (and self-hosted instances) as a new push notification
channel with full parity to the existing webhook channel.

- Backend: NtfyConfig type, getUserNtfyConfig, getAdminNtfyConfig,
  resolveNtfyUrl, sendNtfy (header-based API with Title/Priority/Tags/
  Click headers), testNtfy, NTFY_EVENT_META (priority + emoji tags per
  event), SSRF guard via existing checkSsrf + createPinnedDispatcher
- notificationPreferencesService: ntfy added to NotifChannel union,
  IMPLEMENTED_COMBOS, getActiveChannels parser, getAvailableChannels,
  ADMIN_GLOBAL_CHANNELS, and AvailableChannels interface
- notificationService: per-user ntfy dispatch after webhook block;
  admin-scoped ntfy via getAdminGlobalPref for version_available events
- Routes: POST /api/notifications/test-ntfy with saved-token fallback
- authService: admin_ntfy_server/topic/token in ADMIN_SETTINGS_KEYS,
  masked + encrypted on read/write
- settingsService: ntfy_token added to ENCRYPTED_SETTING_KEYS
- Frontend: ntfy topic/server/token inputs + Save/Test/Clear buttons in
  NotificationsTab; admin Ntfy panel in AdminPage; testNtfy API method
- i18n: full English strings; English placeholders in 14 other locales
- Tests: resolveNtfyUrl, sendNtfy, dispatch integration, UI tests,
  MSW handler for test-ntfy endpoint
2026-04-15 13:59:25 +02:00
Julien G.
f349e567f8
Merge pull request #665 from mauriceboe/feat/indonesian-translation
Feat/indonesian translation
2026-04-15 08:17:55 +02:00
Julien G.
326f9c0823
Merge pull request #664 from mauriceboe/main
Align dev
2026-04-15 07:38:11 +02:00
github-actions[bot]
6df5edfbdb chore: bump version to 2.9.14 [skip ci] 2026-04-15 05:33:46 +00:00
jubnl
191d59166c
Merge remote-tracking branch 'origin/dev' into feat/indonesian-translation 2026-04-15 06:28:35 +02:00
jubnl
875c91e5ff
feat(places): unified file import modal with drag-and-drop and deduplication
- Replace separate GPX and KML/KMZ import buttons with a single "Import
  file" modal accepting all three formats, with a drag-and-drop drop zone
- Support dragging files directly onto the Places sidebar panel; overlay
  appears on hover and pre-loads the file into the modal on drop
- Fix [object Object] description bug in KML imports caused by
  fast-xml-parser returning mixed-content nodes as objects; add stopNodes
  config and object guard in asTrimmedString
- Fix CDATA sections leaking into descriptions (e.g. "text.]]>") by
  unwrapping CDATA markers before tag stripping
- Add import deduplication across all import paths (GPX, KML/KMZ, Google
  list, Naver list): reimporting skips places already in the trip by name
  (case-insensitive) or by coordinates (within ~11 m tolerance), with
  intra-batch dedup so duplicate placemarks within the same file are
  also collapsed
- Fix KML route returning 400 "No valid Placemarks found" when all
  placemarks were valid but deduplicated; 400 now only fires when the
  file contains zero placemarks
- Show a warning toast "All places were already in the trip" instead of
  a misleading success toast when a reimport produces zero new places
  (GPX, KML/KMZ, Google list, Naver list)
- Add 8 new i18n keys across all 14 locales; remove 11 keys made unused
  by the modal consolidation
2026-04-15 06:07:26 +02:00
jubnl
801ffbfb7b
fix(kml-import): address PR #488 review issues
- Strip BOM (U+FEFF) from 14 translation files injected by editor
- Guard KMZ unpack against zip-bomb: check entry.uncompressedSize against
  50 MB cap (KMZ_DECOMPRESSED_SIZE_LIMIT) before calling .buffer();
  limit is an exported constant so tests can override it
- Fix non-BMP HTML entity decoding: replace String.fromCharCode with
  String.fromCodePoint + 0x10FFFF bounds check so emoji like 😀
  round-trip correctly
- Switch KML namespace stripping from regex to fast-xml-parser's
  removeNSPrefix option; XMLValidator accepts namespaced XML natively,
  making the pre-strip step unnecessary
- Remove dead skippedCount overwrite after transaction; per-loop
  increment already tracks it alongside per-item error messages
- Type multer req.file as Express.Multer.File on both /import/gpx
  and /import/map routes instead of (req as any).file
- Add unit tests: emoji entity decoding (decimal + hex), KMZ zip-bomb
  rejection, KMZ-with-no-KML rejection
2026-04-15 05:16:47 +02:00
jubnl
a1a7795945
Merge PR #488: KMZ/KML place import
Resolves conflicts with Naver list import (PR #662) — kept both unified
list-import dialog and new KMZ/KML dialog. Dropped duplicate react-dom
import and unused CustomSelect import from PlacesSidebar.
2026-04-15 05:09:45 +02:00
jubnl
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)
2026-04-15 04:48:39 +02:00
jubnl
4362406e74
Merge remote-tracking branch 'refs/remotes/pull/495' into feat/naver-support 2026-04-15 04:38:50 +02:00
jubnl
607498cabe
fix(search-autocomplete): address PR #542 review issues
- Fix race condition: AbortController cancels in-flight autocomplete
  requests on each keystroke; stale responses no longer overwrite fresh ones
- Remove acTrigger state hack; onFocus calls fetchSuggestions directly
- Cap autocomplete input at 200 chars server-side (400 on violation)
- Filter Nominatim suggestions with empty osm_id segments
- Revert getPlaceDetails OSM branch from unconditional parallel fetch to
  conditional serial: Nominatim called only when Overpass lacks coords/address
- Wire places.loadingDetails i18n key to Loader2 spinner via aria-label/role
- Add tests: MAPS-017, MAPS-040c, MAPS-093, FE-MAPS-004
2026-04-15 04:16:56 +02:00
jubnl
35321076cf
Merge branch 'review/pr-542' into feat/search-autocomplete 2026-04-15 04:02:08 +02:00
jubnl
a07e76c740
fix(login): address review feedback on language dropdown PR
- Fix import path: use i18n barrel instead of TranslationContext directly
- Encapsulate localStorage key behind hasStoredLanguage() helper in settingsStore
- Fix pt-BR detection: only map pt-BR to br, pt-PT now returns null correctly
- Add comment linking server SUPPORTED_LANG_CODES to canonical client source
- Extract /api/config inline handler to routes/publicConfig.ts
- Add aria-haspopup, aria-expanded, role=listbox/option, aria-selected to dropdown
- Add 8 tests for detectBrowserLanguage (FE-COMP-I18N-016–023)
- Add 3 tests for setLanguageTransient (FE-STORE-SETTINGS-015–017)
2026-04-15 03:04:25 +02:00
jubnl
f35c503658
chore: merge PR 592 changes into branch 2026-04-15 02:50:49 +02:00
jubnl
a438652a50
fix(trips): preserve day content when trip date range changes
Rewrites generateDays to remap days positionally by day_number instead
of matching by date identity. Previously any date range shift with no
overlap would cascade-delete all day_assignments, day_notes, and
day_accommodations.

New behaviour:
- Shift/partial overlap: existing days remapped to new dates in order
- Shrink: overflow days become dateless (date=NULL) instead of deleted,
  preserving all child data for manual reassignment
- Grow: existing days kept, new empty days appended
- Clear dates: all days nullified, content intact

Also fixes a UNIQUE(trip_id, day_number) collision that would occur when
spare dateless days remained after growing into a partially-dateless trip
(maxAssigned base was wrong).

Closes #646
2026-04-15 01:28:53 +02:00
jubnl
4e3b27c712
fix(offline): cache accommodations, trip members, tags, and categories for full offline support 2026-04-14 23:50:52 +02:00
jubnl
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
2026-04-14 23:04:25 +02:00
Isaias Tavares
bb8783d217
Merge branch 'dev' into feat/login-language-detection-dropdown 2026-04-14 17:07:18 -03:00
Julien G.
1268d3e7b1
Merge pull request #632 from tiquis0290/bug/synology-thumbnail
fix: currently synology thumbnails resolve to error
2026-04-14 20:57:50 +02:00
Julien G.
80e1574c26
Merge pull request #643 from tiquis0290/fix/synology-adding-photos
fix: pagination in synology
2026-04-14 20:57:27 +02:00
Maurice
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
2026-04-14 19:58:13 +02:00
Marek Maslowski
65931a1777 fix pagination in synology 2026-04-14 19:03:31 +02:00
Marek Maslowski
d04a4bcbf8 fix for test suit 2026-04-14 17:45:51 +02:00
Marek Maslowski
1d4f18bdf9 adding test 2026-04-14 17:40:40 +02:00
jubnl
6a23118342
fix(notifications): fix SMTP error surfacing, webhook button label, backup timestamp
- testSmtp now surfaces real nodemailer error instead of generic 'SMTP not configured' on send failure
- admin webhook test button uses correct i18n key (was showing 'Test-E-Mail senden' in all languages)
- backup created_at uses stat.mtime instead of unreliable stat.birthtime on Linux
2026-04-14 16:20:52 +02:00
jubnl
98340aa855
fix(tests): fix remaining 3 immich test failures
IMMICH-057: use two-step trek_photos/trip_photos insert (same fix
as SYNO-035) to avoid missing asset_id column error.

IMMICH-061: mock regex /\/api\/albums$/ did not match the ?shared=true
variant; updated to /\/api\/albums(\?.*)?$/ so both owned and shared
album requests resolve correctly.

IMMICH-090: /search route only fetched a single page; implement
internal pagination loop (max 20 pages) accumulating all assets
before responding, which is what the test and the feature require.
2026-04-14 13:57:38 +02:00
jubnl
714e2ad703
fix(tests): update test helpers and assertions for migration-98 photo schema
trek_photos is now the central registry; trip_photos and journey_photos
reference it via photo_id FK. Updated all affected test helpers and
direct-SQL assertions to join trek_photos instead of querying stale
columns (asset_id, provider, owner_id) on the leaf tables.

Also fix ATLAS-UNIT-019: getVisitedRegions now fires background geocoding
and returns immediately, so the test must call it twice — once to trigger
the fill, once after advancing fake timers to read cached results.
2026-04-14 13:54:48 +02:00
jubnl
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.
2026-04-14 13:39:28 +02:00
jubnl
375ae53566
fix(atlas): shared Nominatim throttle, background region fill, fetch timeout
- Extract throttleNominatim() so reverseGeocodeCountry and
  reverseGeocodeRegion share the same lastNominatimCall state.
  Concurrent /stats + /regions no longer interleave requests
  faster than 1 req/s, closing the remaining 429 path from #576.
- getVisitedRegions now returns cached data immediately and fills
  uncached places in a fire-and-forget background loop. Eliminates
  the N×1.1s response time that caused 504s behind reverse proxies
  (likely root cause of #493). geocodingInFlight set prevents
  double-enqueuing on concurrent page loads.
- Add AbortSignal.timeout(10_000) to both Nominatim fetch calls so
  a hung upstream no longer stalls the endpoint indefinitely.
- Unify User-Agent header in reverseGeocodeRegion to match policy.
2026-04-14 13:29:14 +02:00
Marek Maslowski
f686902cd3 adding default value of small when getting thumbnail 2026-04-14 11:22:20 +02:00
Maurice
24bcf6ded8 fix(journey): websocket sync across devices + 404 redirect
- broadcastJourneyEvent now excludes by socket ID instead of user ID,
  so other devices of the same user receive real-time updates (#615)
- Routes pass x-socket-id header through to broadcast functions
- loadJourney handles 404 gracefully — redirects to /journey with
  toast instead of infinite spinner (#616)
2026-04-13 23:03:58 +02:00
Maurice
240b10a192 fix(journey): thumbnails, batch add, optimistic delete, shared albums
- Gallery/timeline load thumbnails instead of originals (50-100KB vs 2-5MB)
- Batch endpoint for adding multiple provider photos in one request
- Optimistic photo deletion — no full page reload on delete
- Immich albums include shared albums
- Select-all button moved outside scroll container (always visible)
- Album tab loads actual album contents via /albums/:id/photos
2026-04-13 22:48:40 +02:00
Maurice
87de60d8de fix(photos): paginated search with infinite scroll (#613)
Replace bulk-loading all Immich photos (up to 20k) with paginated
search: 50 photos per page, automatic infinite scroll via
IntersectionObserver. Prevents server blocking on large libraries.

- Backend: searchPhotos accepts page/size params, returns hasMore
- Frontend: loads 50 at a time, appends on scroll
- AbortController cancels in-flight requests on tab switch
2026-04-13 21:46:48 +02:00
Maurice
e395935f6a fix(photos): cap search to 5000 photos + abort pending requests
Large Immich libraries (7k+ photos) caused timeouts and pending
requests when using "All Photos". Cap pagination at 5 pages (5000
photos) and abort in-flight requests when switching tabs.
2026-04-13 21:31:03 +02:00
Maurice
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.
2026-04-13 21:16:16 +02:00
Maurice
7e3cb29c57 fix(journey): album photos, select-all, heading/hr fixes, dark mode
- Load actual album photos instead of date-range search fallback
  (new GET /albums/:id/photos for Immich + Synology)
- Add select all / deselect all toggle in photo picker
- Normalize Markdown headings to plain text in journal stories
- Fix setext headings (---) rendering as hr instead of h2
- Add remark-breaks for proper line break rendering
- Fix pros/cons dark mode gradient backgrounds
- i18n: selectAll/deselectAll in 14 languages
2026-04-13 21:06:15 +02:00
Maurice
6c253c71c3 fix(weather): handle archive date out of range for future trips (#599)
When a trip is far in the future (e.g. May 2027), the climate fallback
looked up last year's data (May 2026). But if that date hasn't passed
yet, the Open-Meteo archive API returns 400. Now checks if the
reference date is still in the future and goes back one more year.

Fixes the flood of 400 errors that could trigger CrowdSec bans.
2026-04-13 20:33:30 +02:00
Maurice
33c63d34e7 fix(journey): prevent duplicate skeleton entries for multi-day places (#606)
When syncing trip places to journal, places assigned to multiple days
(e.g. multi-night hotels) produced one skeleton entry per day_assignment
row. The existing dedup check only looked at DB state, not at entries
added within the same sync loop. Add the place ID to the tracking set
after insertion so the same place is never inserted twice.
2026-04-13 20:27:48 +02:00
Maurice
1f68ba1ea1 fix(atlas): prevent Nominatim 429 rate limiting (#576)
- Swap resolve order: try local bbox lookup before Nominatim reverse
  geocode — eliminates most external API calls
- Add global throttling (1.1s min between requests) to
  reverseGeocodeCountry so /stats can't flood Nominatim
- Update User-Agent header to include repo URL per Nominatim policy
2026-04-13 20:16:36 +02:00
Maurice
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
2026-04-13 20:08:31 +02:00
Ben Haas
479ab49d67
Merge branch 'dev' into search-auto-complete 2026-04-13 08:47:36 -07:00
Ben Haas
1a51f8e3e1
Add translations for "Loading place details…" and improve place search functionality
- Integrate a loading spinner for "Name" input field during place search.
- Enhance OpenStreetMap place detail retrieval with Nominatim lookup.
- Update `authStore` to track Google Maps API key presence.
2026-04-13 08:28:34 -07:00
Ben Haas
7fca16d866
Switch location bias from a point to a bounding box for improved autocomplete accuracy and validation. 2026-04-13 07:53:40 -07:00
jubnl
e629548a42
fix(tests): align tests to actual working code 2026-04-13 14:48:25 +02:00
Marco Sadowski
18da5aed39
Merge branch 'dev' into feature/naver-support 2026-04-13 10:04:28 +02:00
Isaias Tavares
abed22661a fix(login): address PR review feedback
- Use apiClient instead of raw fetch() in configApi.getPublicConfig
- Validate DEFAULT_LANGUAGE against supported codes on server startup
- Log warning instead of silently swallowing fetch errors in LoginPage
- Case-insensitive browser language matching in detectBrowserLanguage
- Guard against undefined navigator in detectBrowserLanguage
- Validate language code in setLanguageTransient before applying
- Import directly from TranslationContext instead of barrel index
2026-04-12 20:03:57 -03:00
Isaias Tavares
57503a6a10 feat(login): add language dropdown, browser auto-detection and configurable default
Replace the language cycling button on the login page with a dropdown
showing all 14 supported languages. Add automatic browser/OS language
detection via navigator.languages, falling back to a configurable
DEFAULT_LANGUAGE env var, then 'en' as last resort.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-12 20:03:57 -03:00
Maurice
e179769a8f Fix ghost Gallery entries in journal timeline and public share
- deleteEntry now deletes photos with the entry instead of moving them
  to a hidden Gallery entry that becomes an undeletable ghost
- deletePhoto cleans up empty Gallery entries after last photo removed
- getJourneyFull and getPublicJourney filter out empty Gallery entries
  so existing ghosts are hidden in both internal and shared views
2026-04-13 00:18:45 +02:00
Maurice
6a19807a72 Fix Nominatim User-Agent and improve error diagnostics
- Update User-Agent from old NOMAD URL to TREK
- Include HTTP status code and response body in Nominatim error messages
2026-04-12 23:31:22 +02:00
Maurice
4680aa254d Fix map tooltips, journey creation, and contributor avatars
- Map tooltips now respect light/dark mode via CSS variables
- Journey creation inherits cover image from first selected trip
- Only day-assigned places are synced to journey (no unplanned places)
- Place count in trip picker reflects assigned places only
- Contributor avatars shown in journey detail page
- Suggestion banner button visible in dark mode (!important override)
- Dashboard list view uses correct trips array and status label
2026-04-12 23:20:13 +02:00
jubnl
ad27c5f6be
fix: restore broken tests after prerelease workflow refactor
- Export __clearVersionCacheForTests() from adminService; call in
  versionNotification beforeEach to reset module-scoped cache between
  tests (VNOTIF-002..006 failed because VNOTIF-001 cached
  update_available:false, short-circuiting all subsequent test fetches)
- Seed appVersion:'2.9.10' in Navbar test authStore; appVersion moved
  from local useEffect state to authStore in last commit so the test
  render no longer fetches it independently (FE-COMP-NAVBAR-016)
- Add data-testid="weekend-days" to VacaySettings weekend-days
  container; use within() in tests to scope button count to that
  section, fixing false positives from the week-start buttons which
  share the same inline styles (FE-COMP-VACAYSETTINGS-003/004)
- Pass isPrerelease={true} in GitHubPanel FE-ADMIN-GH-007; component
  filters out prerelease releases when isPrerelease=false so the badge
  was never rendered (pre-existing, unrelated to last commit)
2026-04-12 17:19:24 +02:00
jubnl
86be4d7997
fix: address prerelease workflow review bugs
- Type checkVersion() with VersionInfo interface; fixes TS errors in
  checkAndNotifyVersion() where object type blocked property access
- Don't cache fallback on !resp.ok or fetch throw; prevents a transient
  GitHub outage from poisoning the 5-min version cache
- Guard parseInt result with Number.isFinite() in compareVersions;
  malformed -pre.abc tags no longer silently compare as equal via NaN
- Pre-compute stripped versions before sort in checkVersion(); avoids
  mutating input array and redundant replace() calls in comparator
- Bump GitHub releases fetch from per_page=20 to per_page=100
- Store appVersion in authStore; populate from App.tsx getAppConfig call
  and remove redundant getAppConfig fetch in Navbar useEffect
- Type GitHubPanel error/expanded state as string|null and Record<number,boolean>
2026-04-12 17:05:17 +02:00
jubnl
62453ebefa
fix: harden prerelease workflow against races, orphan tags, and edge cases
- Add concurrency groups to both workflows to prevent parallel version-bump races
- Defer git tag push to merge job so orphan tags can't exist without a live image
- Pin build/merge jobs to the SHA captured in version-bump to prevent TOCTOU
- Guard auto-finalize in docker.yml against cross-major prereleases (requires bump=major + confirm_major=MAJOR)
- Add STABLE fallback to 0.0.0 for fresh repos with no stable tag
- Fix cleanup sort to extract numeric N via awk instead of fragile sort -t. -k4 -n
- Add 5-minute in-memory cache to checkVersion to avoid GitHub API rate limits
- Type GitHubPanel releases state; remove any cast on filter
- Quote all $VERSION/$MAJOR_TAG vars in imagetools create calls
2026-04-12 16:50:54 +02:00
jubnl
e198791139
fix: address prerelease workflow review issues
- Remove stale mauriceboe/nomad tags from docker-dev.yml
- Fix APP_VERSION empty string fallback (?? -> ||)
- Fix compareVersions to handle -pre.N suffixes correctly
- Use highest existing N instead of tag count to avoid collision after cleanup
- Add cleanup step to keep only last 5 prerelease tags per base version
2026-04-12 16:39:50 +02:00
jubnl
981b667fbb
feat: prerelease workflow with major version support and version propagation
- Add docker-dev.yml: prerelease CI for dev branch with minor/major bump
  inputs; auto-continues in-flight major line via existing pre tags;
  publishes floating major-pre Docker tag (e.g. 2-pre)
- Rewrite docker.yml version-bump: tag-based versioning, manual bump
  inputs (auto/patch/minor/major), major guarded by confirm_major=MAJOR,
  auto-finalizes in-flight prereleases; publishes floating major tag (e.g. 2)
- Inject APP_VERSION build-arg through Dockerfile so the running container
  knows its real version instead of reading package.json
- Server reads APP_VERSION env in authService/adminService; exposes
  is_prerelease in app config and update-check response; prerelease builds
  compare against GitHub prerelease releases rather than latest stable
- Client stores isPrerelease from config; navbar shows amber version badge
  on prerelease builds (left of dark-mode toggle); GitHubPanel filters out
  prerelease releases unless the running build is itself a prerelease
2026-04-12 16:26:44 +02:00
Maurice
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
2026-04-12 02:18:45 +02:00
Maurice
344b769583 fix: deduplicate places on Google Maps list re-import (#543)
Skip places that already exist in the trip (same name + coordinates
within ~10m) when re-importing a Google Maps list. Only new places
are added, preventing duplicates on repeated imports.
2026-04-12 01:45:32 +02:00
Maurice
de157cb87b test: comprehensive Journey test suite — 89.5% new code coverage
Server (172 tests):
- journeyService unit tests (87 tests): CRUD, access control, sync, photos, contributors
- journeyShareService unit tests (20 tests): share links, token validation, public access
- journey integration tests (45 tests): all API routes, auth, permissions, edge cases
- Test helpers: journey factories, RESET_TABLES updated

Client (340+ tests):
- journeyStore tests (15 tests): all store actions and state management
- JourneyPage tests (20 tests): frontpage, create flow, suggestions, navigation
- JourneyDetailPage tests (94 tests): all sub-components, entry editor, settings,
  share links, contributors, gallery, map, trip linking
- JourneyPublicPage tests (18 tests): public view, tabs, restricted access
- JourneyBookPDF tests (6 tests): PDF generation
- BottomNav tests (9 tests): profile sheet, navigation
- PhotoLightbox tests (8 tests): keyboard nav, counter
- JourneyMap tests (12 tests): markers, polylines, zoom
- Component tests: moodConfig, stripMarkdown, MarkdownToolbar, JournalBody, MobileTopHeader
- DashboardPage tests (32 tests): spotlight card, quick actions, widget settings

SonarQube: exclude unused MemoriesPanel from coverage (dead code, moved to Journey)
2026-04-12 01:19:53 +02:00
jubnl
47d9cce936
fix(tests): update tests for granular auth toggles
- Add new fields to AppConfig type and buildAppConfig factory
- Update FE-PAGE-ADMIN-018: heading changed to "Authentication Methods"
- Update FE-PAGE-ADMIN-053: oidc_only toggle removed from OIDC panel
- Update FE-PAGE-LOGIN-007/017: mocks now include password_login/oidc_login
- Update ADMIN-SVC-049: updateOidcSettings no longer writes oidc_only
2026-04-11 20:33:51 +02:00
jubnl
bfd2553d1e
feat(auth): split OIDC_ONLY into granular auth toggles
Replaces the coarse oidc_only + allow_registration settings with four
independent toggles: password_login, password_registration, oidc_login,
oidc_registration. Each can be enabled/disabled individually in
Admin > Settings without affecting the others.

- Add resolveAuthToggles() in authService.ts as the central resolver;
  falls back to legacy oidc_only/allow_registration keys when new keys
  are absent (backward compat)
- OIDC_ONLY env var still works and overrides DB toggles for password_*,
  with a visual lock in the admin UI when active
- Server enforces lockout prevention: cannot disable all login methods
- oidc_login gate added to OIDC /login and /callback routes
- Remove oidc_only toggle from OIDC settings panel; replaced by the
  granular toggles in the Settings tab
- Add 6 new resolveAuthToggles() unit tests; fix AUTH-DB-033 error
  message assertion
- Update OIDC_ONLY descriptions in README, docker-compose, Helm values,
  Unraid template, and .env.example to clarify override semantics

Closes #492
2026-04-11 20:21:36 +02:00
Julien G.
2b1889b9a9
Merge pull request #567 from mauriceboe/fix/atlas-country-region-matching
fix(atlas): scope region name matching by country and expand country lookup tables
2026-04-11 19:50:30 +02:00
Maurice
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.
2026-04-11 19:48:43 +02:00
jubnl
467d35702b
fix(atlas): scope region name matching by country and expand country lookup tables
- Fix #521: `isVisitedFeature()` now scopes name-based region matching to
  the feature's parent country (via `iso_a2`), preventing same-name regions
  in different countries (e.g. Luxembourg BE vs LU) from falsely lighting up
- Fix #489: Add ~50 missing countries to COUNTRY_BOXES, NAME_TO_CODE, and
  CONTINENT_MAP so the bounding-box fallback correctly identifies Georgia
  instead of falling through to Russia/Azerbaijan's overlapping boxes
2026-04-11 19:45:26 +02:00
Maurice
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)
2026-04-11 19:11:21 +02:00
Maurice
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)
2026-04-11 19:01:34 +02:00
jubnl
eff3fcfe10
test: update expected event_types count after adding synology_session_cleared 2026-04-11 18:44:40 +02:00
jubnl
0257e0d842
feat: route Synology session-cleared notification through unified send()
Replace direct createNotification() call with notificationService.send()
so the notification respects user preferences and reaches all enabled
channels (in-app, email, webhook) instead of only WebSocket.

Registers synology_session_cleared as a proper NotifEventType (inapp-only)
and adds localized text for all 14 supported languages.
2026-04-11 18:36:50 +02:00
jubnl
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
2026-04-11 18:25:42 +02:00
Julien G.
bcc37d6b7d
Merge pull request #562 from mauriceboe/main
Align dev
2026-04-11 15:41:34 +02:00
github-actions[bot]
0f6be35870 chore: bump version to 2.9.13 [skip ci] 2026-04-11 13:26:44 +00:00
jubnl
f47852d689
docs: improve FORCE_HTTPS, COOKIE_SECURE, TRUST_PROXY documentation
FORCE_HTTPS now documents all four effects (redirect, HSTS, CSP
upgrade-insecure-requests, secure cookie flag) and is clearly marked
optional. COOKIE_SECURE default updated to "auto" with explanation of
auto-derivation logic. TRUST_PROXY clarifies it's off in dev unless
set and is required for FORCE_HTTPS. charts/README.md gains FORCE_HTTPS
and TRUST_PROXY entries. README prose expanded to explain all three
vars and their interaction.
2026-04-11 15:26:19 +02:00
Julien G.
3b080ac116
Merge pull request #544 from mauriceboe/feat/mcp-oauth2-addon-gating
Implement OAuth 2.1 authentication for MCP, enforce addon gating
2026-04-11 14:39:50 +02:00
jubnl
7a22d742ab
test: add comprehensive coverage for OAuth scopes, MCP, and core services
Adds new and expanded test suites across client and server to cover the
OAuth 2.1 scope system, MCP session manager, collab service, unified
memories helpers, OIDC service, budget slice, and OAuth authorize page.
Also extends SonarQube coverage exclusions to include bootstrapping files
(migrations, scheduler, main.tsx, types.ts) that are not meaningfully
testable.
2026-04-11 14:08:09 +02:00
xenocent
577f2b05ca feat(i18n): add Indonesian translation 2026-04-11 15:26:16 +07:00
jubnl
1585c472c2
fix(test): bumb default limit to reflect implementation 2026-04-11 02:32:17 +02:00
jubnl
dd8d2ae54a
chore(mcp): raise default session and rate-limit caps
Higher defaults reduce config friction for self-hosters while
staying within reasonable server limits.

- MCP_MAX_SESSION_PER_USER: 5 → 20
- MCP_RATE_LIMIT: 60 → 300 req/min
2026-04-11 02:29:11 +02:00
jubnl
535c06bb3f
feat(mcp): granular OAuth scopes and per-client rate limiting
- Split `media:read` into `geo:read` and `weather:read` scopes
- Add dedicated `atlas:read/write` scopes (previously under `places`)
- Add dedicated `todos:read/write` scopes (previously under `collab`)
- Rate limiting now keyed by userId+clientId instead of userId alone
- Bind MCP sessions to the OAuth client that created them
- Log MCP tool calls to audit log with clientId
- Invalidate all MCP sessions on addon state change
- Reduce session sweep interval from 10min to 1min
- Update all translations with new scope labels
2026-04-11 02:06:32 +02:00