Commit Graph

195 Commits

Author SHA1 Message Date
Maurice 42c12ea26d fix: update Dashboard tests for dual mobile+desktop rendering in jsdom
- Use getAllBy* instead of getBy* where mobile + desktop render same content
- Settings button finder uses .lucide-settings selector
2026-04-11 19:25:30 +02:00
Maurice a6a12acad7 fix: add title attrs to icon-only buttons, remove obsolete Memories tab test
- Add title attributes to action buttons in SpotlightCard, MobileTripCard, TripCard
  so tests can find them by accessible name (edit, delete, archive, copy)
- Remove FE-PAGE-PLANNER-018 test — MemoriesPanel moved to Journey addon
2026-04-11 19:18:17 +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 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
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
jubnl cc2a2ddca3 remove(oauth): drop browser-initiated DCR registration flow
OAuthRegisterPage and its server routes (GET /api/oauth/register/validate,
POST /api/oauth/register) are superseded by the RFC 7591 machine-to-machine
DCR endpoint (POST /oauth/register). Claude.ai and compliant MCP clients
register via RFC 7591, then go through the standard /oauth/authorize consent
screen for scope selection.
2026-04-10 06:23:07 +02:00
jubnl 4ad1ccf5dd fix(oauth): gate scope selection UI to DCR clients only
Settings-created clients have fixed scopes chosen at creation time and
should show a read-only scope list on the consent screen. Only DCR-registered
clients expose the interactive checkbox UI for user-controlled scope selection.
2026-04-10 06:03:52 +02:00
jubnl ac9c5784ee feat(oauth): user scope selection on authorization consent screen
When an MCP client registers via DCR and redirects the user to authorize,
the consent screen now shows checkboxes instead of a read-only scope list.
The user can grant any subset of the scopes the client requested — the same
level of control as when creating a client manually from user settings.

- selectedScopes state initialized from validation.scopes (all pre-checked)
- Group-level indeterminate checkbox to select/deselect an entire category
- Approve button reflects selection count and is disabled when nothing selected
- Auto-approve path (consent already on record) bypasses selection and passes
  the existing granted scopes directly
2026-04-10 06:03:44 +02:00
jubnl 9b1baaf7b8 feat(oauth): browser-initiated dynamic client registration (DCR)
Adds an OAuth 2.1 public client registration flow so MCP clients can
self-register via a user-facing consent page instead of requiring manual
setup in Settings.

Server:
- DB migration adds `is_public` and `created_via` columns to oauth_clients
- New GET /api/oauth/register/validate — validates DCR params, returns
  requested scopes; unauthenticated callers get loginRequired flag
- New POST /api/oauth/register — creates a public client, saves consent,
  and redirects with client_id (cookie auth required)
- `authenticateClient` / `refreshTokens` skip secret check for public
  clients (PKCE provides the security guarantee)
- `createOAuthClient` accepts options for isPublic/createdVia; public
  clients store an opaque secret hash instead of a usable secret
- `rotateOAuthClientSecret` blocked on public clients
- `isValidRedirectUri` extracted as a shared helper
- Discovery metadata now advertises registration_endpoint and auth method
  `none`; token/revoke endpoints no longer require client_secret for
  public clients

Client:
- New OAuthRegisterPage (/oauth/register) — loading → optional
  login-required gate → scope selection → done states
- New ScopeGroupPicker component — collapsible groups, indeterminate
  checkboxes, select-all per group or globally
- oauthApi.register.{validate,submit} added to api/client.ts
- apiClient exported so it can be reused outside api/client.ts
- IntegrationsTab tests fixed for new collapsible section structure
- collab_notes fallback changed from undefined to [] in MCP trip tools
2026-04-10 05:20:54 +02:00
jubnl 1187883c6b feat(mcp): always register list_trips & get_trip_summary; inject deprecation notice into tool results
Navigation tools:
- list_trips and get_trip_summary are now always registered for any
  OAuth session regardless of granted scopes — they are required for
  trip ID discovery before any scoped tool can be used
- get_trip_summary filters optional sections (budget, packing, collab,
  reservations) by the client's OAuth scopes when called without trips:read

Deprecation notice:
- Inject static token deprecation warning into the first tool result
  (list_trips or get_trip_summary) via a per-session closure so Claude
  is forced to surface it — the instructions field alone is only
  background context and is not proactively shown to the user

UI:
- OAuth client creation modal: add hint explaining the always-available
  tools, remove the "must select at least one scope" submit guard
- OAuth consent screen: add "Always included" section showing list_trips
  and get_trip_summary; handles zero-scope clients gracefully (empty
  permissions section is hidden)
2026-04-10 02:45:16 +02:00
jubnl 8212f3c023 feat(oauth): add trips:share scope and redesign consent screen
Introduce trips:share as a dedicated OAuth scope for managing public
share links, decoupled from trips:read and trips:write. Share link
tools (get/create/delete_share_link) now gate on canShareTrips()
instead of the generic read/write scopes. Scope added to both client
and server definitions with full test coverage.

Redesign the consent screen from a narrow single-column card
(max-w-sm) to a two-panel layout (max-w-2xl): app identity and
action buttons on the left, scrollable scope list on the right.
Responsive — stacks vertically on mobile.
2026-04-10 00:55:12 +02:00
jubnl 830f6c0706 feat(mcp): introduce OAuth 2.1 auth and enforce addon gating
OAuth 2.1 authentication for MCP:
- Add OAuth 2.1 authorization server with PKCE support (routes/oauth.ts)
- Add OAuth service for client CRUD, auth-code flow, and token management (services/oauthService.ts)
- Add typed scope definitions and enforcement helpers (mcp/scopes.ts)
- Add OAuth consent UI page (OAuthAuthorizePage.tsx)
- Add client-side scope labels and descriptions (api/oauthScopes.ts)
- Integrate OAuth token auth into MCP handler alongside existing static tokens
- All OAuth endpoints gated on `mcp` addon

Addon gating across MCP tools, resources, and prompts:
- Add typed ADDON_IDS constant (server/src/addons.ts) replacing all string literals
- Gate budget tools and resources (trip-budget, per-person, settlement) on `budget` addon
- Gate packing tools and resources (trip-packing, trip-packing-bags, trip-todos) on `packing` addon
- Gate todos tools on `packing` addon (mirrors web UI Lists tab behavior)
- Expand atlas gate to cover full tool body (bucket-list + country tools no longer leak)
- Expand collab gate to cover full tool body (collab notes no longer leak)
- Gate packing-list and budget-overview MCP prompts on their respective addons
- Gate get_trip_summary sections per addon; blank packing/budget/collab_notes/todos when disabled
- Remove trip-files resource and files field from get_trip_summary
- Replace all isAddonEnabled('literal') calls with ADDON_IDS constants

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-09 22:25:58 +02:00
Maurice add979a9f5 fix: sync unplanned filter with map markers (#385)
The "Unplanned" filter button in PlacesSidebar only filtered the place
list but not the map. Propagate the filter state to TripPlannerPage so
mapPlaces excludes planned places when the filter is active.
2026-04-09 18:00:58 +02:00
jubnl d4bb8be86b test: expand frontend test suite to 82% coverage
Adds ~45 new and updated test files covering Admin, Collab, Dashboard, Map, Memories, PDF, Photos, Planner, Settings, Vacay, Weather components, pages, stores, and a WebSocket integration test.
2026-04-08 21:14:49 +02:00
Julien G. 2b7057b922 Merge pull request #520 from mauriceboe/dev
Dev
2026-04-08 18:51:05 +02:00
Maurice 2d17ec60db fix: missing avatar URLs in notifications, admin panel, and budget
- Notifications: map raw avatar filename to /uploads/avatars/ URL in
  getNotifications, createNotification broadcasts, and respond handler
- Admin listUsers: include avatar field in SELECT and map to avatar_url
- Admin page: render actual avatar image instead of initial letter only
- Budget loadItemMembers: map avatar to avatar_url (fixed in prior commit)

Fixes #507
2026-04-08 18:17:08 +02:00
Julien G. 955a3cff78 Merge pull request #517 from mauriceboe/dev
Dev
2026-04-08 17:53:06 +02:00
Maurice 741a8d3f09 feat: collapsible day detail panel in planner
Adds a collapse/expand toggle to the day detail panel header.
Collapsed state persists across day switches. Clicking the header
or the chevron button toggles between compact header-only view
and the full detail panel.

Closes #457
2026-04-08 17:48:29 +02:00
jubnl fd48169219 test(client): expand frontend test suite to 69.1% coverage
Add and extend tests across 32 files (+10 595 lines) covering Admin
panels (AuditLog, Backup, DevNotifications, GitHub), Collab (Chat,
Notes, Panel, Polls), Planner (DayDetailPanel, DayPlanSidebar),
Settings (DisplaySettings, Integrations, MapSettings), Files
(FileManager, FilesPage), Map, Layout (DemoBanner,
InAppNotificationBell), shared pickers (CustomDateTimePicker,
CustomTimePicker), Vacay holidays, pages (Dashboard, Login), unit
stores (authStore, inAppNotificationStore), API (authUrl, client
integration), and i18n. Also updates sonar-project.properties and
MSW trip handlers to support the new cases.
2026-04-07 21:56:08 +02:00
Julien G. 9390a2e9c6 Merge pull request #501 from mauriceboe/dev
get backend tests
2026-04-07 18:57:16 +02:00
jubnl 47b880221d fix(oidc): resolve login/logout loop in OIDC-only mode
Three distinct bugs caused infinite OIDC redirect loops:

1. After logout, navigating to /login with no signal to suppress the
   auto-redirect caused the login page to immediately re-trigger the
   OIDC flow. Fixed by passing `{ state: { noRedirect: true } }` via
   React Router's navigation state (not URL params, which were fragile
   due to async cleanup timing) from all logout call sites.

2. On the OIDC callback page (/login?oidc_code=...), App.tsx's
   mount-level loadUser() fired concurrently with the LoginPage's
   exchange fetch. The App-level call had no cookie yet and got a 401,
   which (if it resolved after the successful exchange loadUser()) would
   overwrite isAuthenticated back to false. Fixed by skipping loadUser()
   in App.tsx when the initial path is /login.

3. React 18 StrictMode double-invokes useEffect. The first run called
   window.history.replaceState to clean the oidc_code from the URL
   before starting the async exchange, so the second run saw no
   oidc_code and fell through to the getAppConfig auto-redirect, firing
   window.location.href = '/api/auth/oidc/login' before the exchange
   could complete. Fixed by adding a useRef guard to prevent
   double-execution and moving replaceState into the fetch callbacks so
   the URL is only cleaned after the exchange resolves.

Also adds login.oidcLoggedOut translation key in all 14 languages to
show "You have been logged out" instead of the generic OIDC-only
message when landing on /login after an intentional logout.

Closes #491
2026-04-07 13:18:24 +02:00
jubnl 3c31902885 test(front): add test suite frontend (WIP) 2026-04-07 12:31:09 +02:00
Maurice 4ba6005ca3 fix(dayplan): resolve duplicate reservation display, date off-by-one, and missing day_id on edit
- Exclude place-assigned reservations from timeline to prevent duplicate display
- Use selected day's date instead of today when entering time without date
- Pass day_id when updating reservations, not only when creating
2026-04-06 12:56:54 +02:00
jubnl e2be3ec191 fix(atlas): replace fuzzy region matching with exact name_en check
Bidirectional substring matching in isVisitedFeature caused unrelated
regions to be highlighted as visited (e.g. selecting Nordrhein-Westfalen
also marked Nord France due to "nord" being a substring match).

Replace the fuzzy loop with an additional exact check against the Natural
Earth name_en property to cover English-vs-native name mismatches.
Also fix Nominatim field priority to prefer state over county so
reverse-geocoded places resolve to the correct admin-1 level.

Adds integration tests ATLAS-009 through ATLAS-011 covering mark/unmark
region endpoints and user isolation.

Fixes #446
2026-04-05 23:38:34 +02:00
jubnl 306626ee1c fix(trip): redirect to plan tab when active tab's addon is disabled
If a user's last visited tab belongs to an addon that gets disabled while
they are away, re-opening the trip now resets the active tab to 'plan'
instead of rendering the inaccessible addon page.

Closes #441
2026-04-05 22:30:22 +02:00
Maurice 83bac11173 ui(trip): replace plane loading animation with TREK logo GIF
- Use animated TREK logo instead of plane SVG on trip loading screen
- Dark/light mode aware (switches GIF based on theme)
2026-04-05 17:28:04 +02:00
Maurice a9d6ce87c1 Merge pull request #336 from tiquis0290/test
Adding support for SynologyPhoto (immich like) and adding support to use more photo proiders not just immich
2026-04-05 15:08:50 +02:00
Maurice 67b21d5fe3 i18n(admin): rename tabs and merge notification panels
- Configuration → Personalization (all 13 languages)
- Merge Notification Channels + Admin Notifications into single Notifications tab
- Audit Log → Audit (all 13 languages)
2026-04-05 14:46:36 +02:00
Marek Maslowski b0b85fff3a fix for settings page 2026-04-05 11:08:58 +02:00
Marek Maslowski b8c3d5b3d1 Merge branch 'dev' into test 2026-04-05 10:26:09 +02:00
jubnl 959015928f feat(security): mask saved webhook URLs instead of returning encrypted values
Encrypted webhook URLs are no longer returned to the frontend. Both user
and admin webhook fields now show '••••••••' as a placeholder when a URL
is already saved, and the sentinel value is skipped on save/test so the
stored secret is never exposed or accidentally overwritten.
2026-04-05 06:08:44 +02:00
jubnl 4e4afe2545 feat(settings): remake settings page with admin-style tabbed layout
Replaces the 2-column masonry layout with a horizontal pill tab bar
matching the admin page pattern. Extracts all sections into self-contained
components under components/Settings/ and reduces SettingsPage.tsx from
1554 lines to 93. Adds i18n tab label keys across all 13 language files.
2026-04-05 05:32:21 +02:00
jubnl 0c99eb1d07 chore: merge dev branch, resolve conflicts for migrations and translations
- migrations.ts: keep dev's migrations 69 (place_regions) + 70 (visited_regions), renumber our notification_channel_preferences migration to 71 and drop-old-table to 72
- translations: use dev values for existing keys, add notification system keys unique to this branch

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-05 03:46:53 +02:00
jubnl 71c1683bb3 feat(atlas): mark sub-national regions as visited with cascade behavior
- Add visited_regions table migration
- Mark/unmark region endpoints with auto-mark parent country
- Unmark country cascades to its regions; unmark last region cascades to country
- Region modal with mark/unmark flow and bucket list shortcut
- Viewport-based lazy loading of region GeoJSON at zoom >= 6
- i18n: add atlas.markRegionVisitedHint and atlas.confirmUnmarkRegion across all 13 locales
2026-04-05 03:17:59 +02:00
mauriceboe 6df8b2555d chore: resolve merge conflicts with dev branch
Merge dev into feat/notification-system, keeping all i18n keys from both
branches (notification system keys + reservation price/budget keys).
2026-04-05 01:43:43 +02:00
mauriceboe 16cadeb09e feat(atlas): sub-national region view when zooming in
- Zoom >= 5 shows visited regions (states/provinces/departments) colored on the map
- Server resolves places to regions via Nominatim reverse geocoding (zoom=8)
- Supports all ISO levels: lvl4 (states), lvl5 (provinces), lvl6 (departments)
- Handles city-states (Berlin, Vienna, Hamburg) via city/county fallback
- Fuzzy name matching between Nominatim and GeoJSON for cross-format compatibility
- 10m admin_1 GeoJSON loaded server-side (cached), filtered per country
- Region colors match their parent country color
- Custom DOM tooltip (ref-based, no re-renders on hover)
- Country layer dims to 35% opacity when regions visible
- place_regions DB table caches resolved regions permanently
- Rate-limited Nominatim calls (1 req/sec) with progressive resolution
2026-04-05 01:31:19 +02:00
jubnl fc29c5f7d0 feat(notifications): add unified multi-channel notification system
Introduces a fully featured notification system with three delivery
channels (in-app, email, webhook), normalized per-user/per-event/
per-channel preferences, admin-scoped notifications, scheduled trip
reminders and version update alerts.

- New notificationService.send() as the single orchestration entry point
- In-app notifications with simple/boolean/navigate types and WebSocket push
- Per-user preference matrix with normalized notification_channel_preferences table
- Admin notification preferences stored globally in app_settings
- Migration 69 normalizes legacy notification_preferences table
- Scheduler hooks for daily trip reminders and version checks
- DevNotificationsPanel for testing in dev mode
- All new tests passing, covering dispatch, preferences, migration, boolean
  responses, resilience, and full API integration (NSVC, NPREF, INOTIF,
  MIGR, VNOTIF, NROUTE series)
 - Previous tests passing
2026-04-05 01:22:18 +02:00
Marek Maslowski 399684cc19 Merge branch 'dev' into test 2026-04-05 00:36:40 +02:00
Marek Maslowski 58b7c2e7ac some fixes when to display photo tab 2026-04-05 00:16:43 +02:00
Marek Maslowski 3413d3f77d fixing labels in english 2026-04-04 22:00:35 +02:00
mauriceboe e4065c276b fix(map,lightbox): center map above day detail panel and fix lightbox close
- Map pans up when DayDetailPanel is open so route markers aren't hidden
- Files lightbox: clicking dark background closes lightbox again
- Memories lightbox: clicking dark background closes lightbox again
2026-04-04 20:26:24 +02:00
Marek Maslowski 1285da063e Merge branch 'test' into dev 2026-04-04 19:27:16 +02:00
mauriceboe 0b36427c09 feat(todo): add To-Do list feature with 3-column layout
- New todo_items DB table with priority, due date, description, user assignment
- Full CRUD API with WebSocket real-time sync
- 3-column UI: sidebar filters (All, My Tasks, Overdue, Done, by Priority),
  task list with inline badges, and detail/create pane
- Apple-inspired design with custom dropdowns, date picker, priority system (P1-P3)
- Mobile responsive: icon-only sidebar, bottom-sheet modals for detail/create
- Lists tab with sub-tabs (Packing List + To-Do), persisted selection
- Addon renamed from "Packing List" to "Lists"
- i18n keys for all 13 languages
- UI polish: notification colors use system theme, mobile navbar cleanup,
  settings page responsive buttons
2026-04-04 16:58:24 +02:00
Marek Maslowski c9dd8e1192 Merge branch 'dev' into test 2026-04-04 00:53:24 +02:00
jubnl 2469739bca fix(admin): update stale NOMAD references to TREK
- GitHubPanel: point release fetcher to mauriceboe/TREK
- AdminPage: fix Docker update instructions (image, container name, volume paths)
- es.ts: replace all remaining NOMAD occurrences with TREK
2026-04-04 00:21:40 +02:00
Marek Maslowski 9ddb101135 Merge branch 'dev' into test 2026-04-03 22:28:29 +02:00
Maurice 897e1bff26 fix(dates): use UTC parsing and display for date-only strings (#351)
Date-only strings parsed with new Date(dateStr + 'T00:00:00') were
interpreted relative to the local timezone, causing off-by-one day
display for users west of UTC. Fixed across 16 files by parsing as
UTC ('T00:00:00Z') and displaying with timeZone: 'UTC'.
2026-04-03 21:18:56 +02:00
jubnl 6c72295424 fix(vacay): fix entitlement counter, year deletion, and year creation bugs
- toggleCompanyHoliday now calls loadStats() so the entitlement sidebar
  updates immediately when a vacation day is converted to a company holiday
- deleteYear now deletes vacay_user_years rows for the removed year,
  preventing stale entitlement data from persisting and re-appearing
  when the year is re-created
- deleteYear recalculates carry-over for year+1 when year N is deleted,
  using the new actual previous year as the source
- removeYear store action now calls loadStats() so the sidebar reflects
  the recalculated carry-over without requiring a page refresh
- Add prev-year button (+[<] 2026 [>]+) so users can add years going
  backwards after deleting a past year; add vacay.addPrevYear i18n key
  to all 13 supported languages

Closes #371
2026-04-03 19:51:22 +02:00
Marek Maslowski ba737a9920 Merge branch 'dev' into test 2026-04-03 19:18:28 +02:00