mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-20 14:06:27 +00:00
86e2a96eb6bc89ba7fae2f62fc171a165e82ecea
53 Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
3efedd5e48 |
refactor(server): decorate the last three MCP prompts
The prompts registrar is gone. Each prompt moves to the domain that owns its data instead of a shared file that had to import three bridges to serve them. packing-list goes to PackingMcp and budget-overview to BudgetMcp, both reusing the `when` addon gate their tools already carry — so a disabled addon keeps the prompt out of the listing, exactly as the `if (isAddonEnabled(...))` around the registration call did. Both keep taking the trip title through trips.bridge: TripsModule imports PackingModule and BudgetModule, so injecting TripsService would close a cycle. Same documented in-container exception as atlas.mcp.ts, and the registrar reached for it the same way. token_auth_notice goes to a new AuthMcp — it is a statement about how the session authenticated, not about trip data, and it injects nothing because the message is static. Its `if (isStaticToken)` guard becomes `when: (ctx) => ctx.isStaticToken`; the registry hands `when` the session context, so an OAuth 2.1 session never sees the prompt listed at all, which is what the registrar achieved by skipping registration. src/mcp/tools/ is down to _shared.ts and journey.ts. journey stays until services/journeyService folds, which waits on memories. |
||
|
|
cacab0795f |
refactor(server): decorate the weather, airport and transport MCP tools
Three of the four remaining hand-written registrars are gone. The tools keep their names, descriptions, input schemas, annotations and result shapes; the registration-time scope gates (`if (canWeather)`, `if (canGeo)`, `if (!canWrite(scopes, 'reservations')) return`) become declarative access markers, resolved by trekMcpAccessPolicy like every other decorated domain. get_weather / get_detailed_weather move into a new WeatherMcp over the injected WeatherService, including the catch that answers with the upstream message and isError rather than throwing - a client sees "Weather service not available." instead of a transport failure when the provider is down. search_airports / get_airport move into a new AirportsMcp with no injected service at all: the airport table is a static dataset. AirportsService owns the flight-endpoint backfill against the database, which neither tool touches. create/update/delete_transport move into ReservationsMcp rather than a domain of their own, because a transport IS a reservation - same table, same service, same reservation_edit permission - and that class already injects everything they needed from the four bridges the registrar was importing. That was budget.bridge's last outside-container consumer, so linkBudgetItemToReservation is pruned with it; the file is down to one export and dies as soon as BudgetMcp stops needing AuthService. journey.ts stays: it still reaches into services/journeyService, which cannot fold until memories does. |
||
|
|
ac96644508 |
refactor(server): fold the notifications domain out of services/
Four files taken apart rather than moved, because they only worked as a knot. The mailer becomes its own leaf module. AuthService sends the password-reset mail while NotificationsModule imports AuthModule for the demo gate in NotificationsMcp - a mailer inside the notifications domain would have closed a hard cycle that only a forwardRef could open again. Webhook and ntfy stay with notifications; auth has no business importing them. The channel registry stays a module singleton and does NOT become a provider. PluginRuntimeService pushes its channel getter in at onModuleInit, and notifications.bridge.ts builds its own NotificationsService for the scheduler crons and the memories senders. A provider would hand the bridge a second, empty registry - every plugin channel would then go quiet with no error anywhere. CHOVR-015 drives that seam end to end: a channel published through the runtime, delivered through the bridge. Nothing pinned it before; the existing cases all set the source themselves. The built-ins no longer self-register by side-effect import. They used to exist only because notificationPreferencesService happened to `import './notifications/builtins'`; removing that line would have silenced email, webhook and ntfy without a failure. They are built from the injected transports and registered from NotificationsService's constructor, so both instances have them. No cached nodemailer transport: MailerService.createTransport re-reads the SMTP config and skipTlsVerify per send, exactly as the three inline call sites did, so an admin changing them does not have to restart. Preferences become a provider over DatabaseService. Eight places built NotificationsService by hand, so a tests/helpers/notifications.ts factory keeps the next constructor parameter a one-file diff instead of an eight-file one. |
||
|
|
939452f9d8 |
refactor(server): consolidate trip access and fold the query helpers into the container (#1824)
* refactor(server): route every trip-access check through DatabaseService
Twelve Nest services carry the same verifyTripAccess/canEdit pair. Six already
asked DatabaseService.canAccessTrip; the other six went through
services/tripAccess, a nine-line re-export of the identical function. Same
query, same result, two ways to reach it.
All twelve now take the injected route, so services/tripAccess is gone.
The bigger duplication was in the types. Thirteen files declared their own
`Trip` for the same return value, in three different shapes:
NonNullable<ReturnType<typeof verifyTripAccess>> 6x
{ user_id: number } 4x
TripAccess 3x
The narrow one is why assignments, days, places and accommodations all wrote
`as Trip | null | undefined` on the call — the cast existed to shrink
TripAccess down to a local type that was never meant to differ from it. All
thirteen now say TripAccess, and the four casts are gone with no other change.
middleware/tripAccess.ts goes too: it was already dead in production — nothing
under src/ imported requireTripAccess or requireTripOwner, only comments in the
services that had absorbed them. Its test went with it.
* refactor(server): fold the query helpers and trip joins into the container
Two more legacy files whose only reason for staying outside the container was
that they predated it.
services/queryHelpers.ts held five exports doing two different jobs. Three are
the batch loaders that keep the list endpoints off N+1 queries — they hit the
database and are now QueryHelpersService over the injected connection, imported
by assignments, days, places and share. The other two, formatAssignmentWithPlace
and ratingAggregate, touch no database at all; they sat next to the loaders only
because the loaders were free functions too. Those stay free functions, in
common/rowShape.ts.
The README note on the assignments service claimed the loaders had to stay in
services/ because the day, share and place services were unmigrated. All three
have since moved, so the reason is gone.
services/tripMembership.ts becomes TripMembershipService in its own module. It
could have folded into auth or trip-invite, but both call it — as do oidc and
the plugin RPC host — and putting it in either would have made AuthModule and
TripInviteModule import each other.
Neither module is @Global, matching PermissionsModule: e2e TestingModules
resolve them transitively through each consumer's explicit import.
Test seams changed shape accordingly. Two path mocks of services/tripMembership
became injected stubs; the trip-invite e2e overrides the provider in the
container instead. The oidc test keeps the real implementation, which is what
OIDC-SVC-045 was always asserting.
ratingAggregate moved out of the ungated services/ tree into the coverage-gated
one, so it gets the direct tests it never had — as does the tags/participants
pass-through, which two long-dead fixture variables in the moved test file had
been waiting for.
Also drops four stale comments that outlived their subject: the requireTripAccess
middleware they pointed at was deleted in the previous commit.
|
||
|
|
6dda05f672 |
refactor(server): move the stateless helpers into nest/common (#1823)
Eight files in services/ hold no state and touch no database: avatarUrl, conflictResult, demo, passwordPolicy, timezoneService, cookie, and the two crypto helpers. They are imported from 65 places, 62 of them already inside src/nest, so having them live in the legacy layer was pure inertia. They stay plain functions rather than becoming providers. Three importers sit outside the container and would have no way to reach an injectable one: db/migrations.ts runs before the app is built, and middleware/auth.ts and middleware/mfaPolicy.ts run beside it. Wrapping these in DI would buy ceremony and nothing else. mfaCrypto and apiKeyCrypto move together into common/crypto — they read the same ENCRYPTION_KEY, and the directory says so. services/ drops from 44 files to 36, 9,870 lines to 8,493. One trap worth recording: src/app-config and src/nest/app-config both exist. cookie.ts imported '../app-config' from services/, which resolved to the former; after the move the same string silently pointed at the latter, which has no readEnv. It is now explicit about which one it wants. Nothing else changes — no signatures, no behaviour, no tests rewritten beyond their import paths. |
||
|
|
e606ebd9b9 |
chore(server): pin the WebSocket envelope, dedupe the rate limiter (#1803)
* chore(nest): register LlmParseModule in AppModule
The module was only in the container because BookingImportModule and
PluginsModule happen to import it, so /api/admin/llm/local depended on those two
keeping that import. Register it where every other module is registered.
* refactor(nest): give the rate limiter one home and one instance
RateLimitService keeps its counters in a Map on the instance, but it was listed
in the providers of auth, oauth, transit and trip-invite, so each of them got its
own. Nothing is broken today — the bucket names in use (login/mfa/forgot/reset,
oauth_*, transit_*, trip_invite) don't overlap — but two modules picking the same
name would have quietly allowed twice the configured attempts.
Move it to nest/common, where it belongs now that four domains use it, and hand
it out through a small module they import. Not @Global: the e2e suites build a
container around one domain module, so a global AppModule never pulled in isn't
there at all.
transit.mcp.ts keeps its own instance on purpose — it keys by userId under
mcp_transit_* while the REST path keys by IP.
* test(ws): pin the wire envelope before touching the transport
Every frame on the socket is flat: {type, tripId, ...payload} out, {type, tripId}
in. Nest's WsAdapter dispatches on message.event and answers {event, data}, so
moving this onto a gateway without a custom serialiser would leave join silently
unhandled — no welcome.socketId to send back as X-Socket-Id, and every client
would then receive its own writes.
Nothing catches that today: the 100+ tests that touch realtime mock src/websocket
and never look at the bytes. WS-032 sends the exact shape the stock adapter would
and asserts the server ignores it.
* docs(nest): drop the parts of the README that no longer exist
strangler.ts, DEFAULT_NEST_PREFIXES/NEST_PREFIXES and tests/parity/ are all gone —
every request goes through Nest now — but the README still walks a new contributor
through adding a prefix and writing a parity test against the Express route.
Also spell out that the 80% gate is an average over src/nest/**, not a floor per
file, so a large untested module can sit behind the well-covered small ones.
|
||
|
|
d203d8cccd |
chore: move notifications backend to proper DI
The send() dispatcher (services/notificationService.ts) and the in-app store (services/inAppNotifications.ts) fold together into the DI-native NotificationsService over DatabaseService + RealtimeService; the prefs matrix, transports, channel registry and inAppNotificationActions stay plain infra modules. A 1-export notifications.bridge covers scheduler, legacy adminService/memories and the six lazy fire-and-forget sends; AdminController and the plugin RPC host (24th factory dep) inject. The 5-tool registrar + notifications-in-app resource move to notifications.mcp.ts. Tests move with case IDs preserved (NSVC-*, INOTIF-*, + NSVC-020 bridge pin); ~18 suites repoint path mocks/warm-ups; the module e2e goes DI-native. Also carries the verified-quirk fixes that would normally trail as a separate fix(server) commit (file-overlap with the fold): toUtcIso on all three created_at paths (INOTIF-013/014), respond claims the response CAS before running the action handler with release-on-failure (INOTIF-011/012), synology's fire-and-forget send gains .catch, the dispatch-failure log unwraps Error messages (NSVC-021), and the MCP resource description drops its false 'unread first' claim. |
||
|
|
e65f851446 | chore: move passkey backend to proper DI | ||
|
|
45564b0a8a |
chore: move auth backend to proper DI
Fold the 1497-line services/authService.ts into the DI-native nest/auth
domain (Wave-5 chain opener, unblocked by the atlas fold):
- Pure crypto half (backup-code hash/match/generate, stripUserForClient,
key masking, the import-time DUMMY_PASSWORD_HASH timing equaliser and
avatarDir mkdir — documented parity exceptions) moves verbatim to the
plain module nest/auth/auth.helpers.ts.
- DB half folds into AuthService over DatabaseService + injected
PermissionsService (ex permissions.bridge) + AtlasService (ex
atlas.bridge, deleted on schedule). mfaSetupPending and the per-email
reset throttle stay module-scoped so the bridge and container instances
share them; avatarDir and require('../../../package.json') re-anchor one
directory deeper — with the two injection swaps, the only non-verbatim
lines. Controller-facing method surface unchanged.
- New 8-export auth.bridge.ts for out-of-container consumers: mcp/index.ts
token verification, the journey/notifications/transports registrars'
isDemoUser, and the still-legacy adminService/oidcService/passkeyService.
- 15 domain *.mcp.ts demo guards now inject AuthService (their modules
import AuthModule); atlas.mcp.ts alone stays on the bridge because
AuthService injects AtlasService and the reverse module edge would close
an AuthModule<->AtlasModule cycle (places.mcp precedent). OidcService and
PasskeyEnabledGuard inject AuthService too.
- Tests move with case IDs preserved: authService.test.ts ->
auth.helpers.test.ts, authServiceDb.test.ts -> auth.service.test.ts
(+ AUTH-BR-001..007 bridge delegation, + AUTH-DB-050..088 pinning the
previously untested getAppConfig/login/MFA/reset/admin-settings branches
now under the src/nest coverage gate). auth.e2e converts DI-native (real
bcrypt, real audit rows); oidc.e2e swaps its dead path mock for an
instance spy; websocket/integration suites repoint to bridge/helpers.
Quirks preserved byte-for-byte (0-coord drop in getTravelStats,
un-transactioned registerUser/verifyMfaLogin multi-writes, unguarded
revokeUserSessions in deleteMcpToken, current! assertions in
updateApiKeys) — fixes land separately.
|
||
|
|
ae3ba18b9e |
chore: move atlas backend to proper DI
Fold the 1612-line services/atlasService.ts into the DI-native AtlasService: the stats aggregation, visited countries/regions with the #1490 tombstone and cascade logic, and the bucket-list CRUD become methods over the injected DatabaseService. The ~750-line pure-geo half — the bundled admin0/admin1 boundary stores with their #1576 streaming builders, the point-in-polygon indexes, Nominatim geocoding with its shared throttle, and every module-scoped cache including the import-time unref'd cleanup interval — moves verbatim to the plain module atlas-geo.ts so the caches stay process-global across the container instance, the bridge instance and test helpers (assetPath re-anchored one directory deeper, the only non-verbatim line). The 10-tool mcp/tools/atlas.ts registrar plus all four atlas resources in mcp/resources.ts move onto the decorator registry as atlas.mcp.ts — the when: atlas-addon gate is parity here (legacy tools and resources both gated; the REST controller deliberately does not). resources.test.ts retires with its last two cases, which move to tools-atlas-expanded.test.ts. The plugin RPC host swaps its 9 atlas imports for the injected AtlasService (23rd constructor dep); a minimal 2-export atlas.bridge.ts serves the one legacy consumer, authService.getTravelStats, and dies when authService migrates. Adopts atlas.dto.ts over the existing shared schemas and clears all three AtlasController entries from body-contract-allow-list.ts: the hand-rolled 'name and country_code are required' 400 becomes the ZodValidationPipe envelope (todo/places trade) while the whitespace-only bucket name keeps its legacy 'Name is required' trim guard. Fixes the verified defects the relocation had faithfully carried, each with a regression test (ATLAS-SVC-031..036 plus two MCP casing cases): the four multi-statement mark/unmark writes now run in db.transaction() (the region cascade nests as a savepoint), the trip-less countryPlaces early return honours manually_marked, the '|| null' bindings that dropped lat/lng 0 and empty-string notes on bucket update become '?? null', the mutating bucket SQL is user-scoped, and the MCP region/country-places tools uppercase their codes to match REST. Quirks deliberately preserved: the detached GET-path place_regions backfill writes, getStats' two divergent return shapes, the import-time cache-cleanup interval, getCountryFromAddress's unvalidated bare 2-letter codes, and the whitespace-only-name silent no-op on bucket update. Tests move with their case IDs: atlasService.test.ts becomes unit/nest/atlas.service.test.ts (pure-geo imports from atlas-geo, DB calls through the constructed service, plus bridge-delegation cases), the atlas e2e converts to the DI-native pattern (real SQL against the full temp-db schema), and the plugin-host path mock becomes a constructor stub. |
||
|
|
cab49ea511 | chore: move collections backend to proper DI | ||
|
|
4f0f80ad69 |
chore: move place backend to proper DI
Fold the 1029-line services/placeService.ts into the DI-native PlacesService: the CRUD + ratings SQL, the GPX/KML/KMZ importers and the Google/Naver list importers become methods over the injected DatabaseService, byte-identical statements, COALESCE semantics and the If-Match conflict protocol (#1135). The pure half — frozen XML parsers, the KMZ unpacker, the dedup predicates, the Google hex-id parsers, reclaimPhotoCache — moves to places.helpers.ts (maps.helpers precedent). The 10-tool mcp/tools/places.ts registrar plus the trek://trips/{tripId}/places resource move onto the decorator registry as places.mcp.ts. search_place travels with them because its gate is places:read, not the read-only geo group, and now injects MapsService. TripsService, DaysMcp, BookingImportService and the plugin RPC host (PluginHostDepsFactory's 21st constructor dep) all inject PlacesService, so the domain needs no bridge at all. The two assignments.bridge imports that places.mcp.ts keeps are a deliberate cycle break: AssignmentsModule imports DaysModule and DaysModule now imports PlacesModule for days.mcp.ts's place creation, so injecting AssignmentsService there would close DaysModule -> PlacesModule -> AssignmentsModule -> DaysModule. Same seam reservations.mcp.ts uses, same trade trips.bridge.ts documents. Also folds services/placeEnrichment.ts in — its DB/websocket/Maps half becomes PlacesService methods over the injected DatabaseService/RealtimeService/ MapsService, its pure match selector joins places.helpers.ts — which retires nest/maps/maps.bridge.ts with its last consumer. Adopts places.dto.ts for the seven grandfathered body contracts and clears every PlacesController entry from body-contract-allow-list.ts. Two consequences worth knowing: the bespoke 400s 'Place name is required', 'ids must be an array of numbers' and 'URL is required' are now the ZodValidationPipe envelope, and because a pipe runs before the handler a malformed body 400s ahead of the trip-access 404 it used to follow — the same trade the todo and trips migrations took. placeBulkUpdateRequestSchema.ids drops .min(1) so the endpoint's empty-list short-circuit stays reachable. Fixes four defects the relocation had faithfully carried, each with a failing-first regression test: - create dropped lat/lng of exactly 0, so a place on the equator or the prime meridian lost its coordinates (`x || null` cannot tell absent from zero). - duration_minutes: 0 was unsettable — replaced by the 60-minute default on create, and read as "absent" by COALESCE on update. - the journey delete hook fired on unscoped ids: DELETE /:id, POST /bulk-delete and the delete_place MCP tool could detach another trip's journey entries for an id they then refused. All three scope first via the new PlacesService.scopedIds, mirroring the guard PluginHostDepsFactory already had. bulk_delete_places also moves its hook ahead of the DELETE — journey_entries.source_place_id is ON DELETE SET NULL, so running it afterwards left the entries as orphans. - the place search treated % and _ as LIKE wildcards; a bare % returned the whole trip. The three clauses now carry ESCAPE with the term escaped. Plus hardening on the Google list fetch: the response is capped at 8 MB (declared content-length and post-read length, the transit.service precedent) and its JSON.parse is guarded, so a malformed provider payload produces the existing 'Invalid list data received from Google Maps' 400 instead of throwing. Quirks deliberately preserved: the non-COALESCE route_color (#776), currency/transport_mode staying unclearable, importGpx returning null for a file that parses but yields no usable geometry, update skipping the If-Match check when the stored updated_at is null, and every string-valued `x || null`. Tests move with their case IDs: placeService.test.ts and the wrapper suite merge into unit/nest/places.service.test.ts, kmzUnpack into places.helpers.test.ts, placeEnrichment into places.enrichment.test.ts. The places e2e converts to the DI-native pattern (real SQL against temp-db DDL), tools-places.test.ts is kept and extended so it exercises the decorator path through the test registry, and the plugin-host and booking-import path mocks become constructor stubs. |
||
|
|
f4bf7cf3dc |
chore: move transit backend to proper DI
Includes the quirk-repair pass (upstream timeout + size cap, LRU cache, NaN-duration guard) that maps landed as a sibling fix commit. |
||
|
|
2e40c19249 | chore: move maps backend to proper DI | ||
|
|
287dceb532 |
chore: wire TripsService into the plugin RPC host and retire the legacy tripService
Swap the factory's six tripService imports for the injected TripsService; delete services/tripService.ts and the collab/vacay bridges with it; prune the days/budget bridges to their surviving exports; update the migration docs and regenerate migration-graph.md. |
||
|
|
530c7cb005 |
chore: move trip backend to proper DI
Fold the legacy tripService SQL into TripsService; port the trips MCP registrar/resources/trip-summary prompt to trips.mcp.ts and the share-link tools to share.mcp.ts (predicate access for the trips:delete/trips:share/canReadTrips gates, deprecation notice via the attach ctx); adopt trips.dto.ts and clear the seven allow-list entries; delete todo/share bridges with their last consumers. Includes the trailing quirk fixes: transactional deleteTrip/deleteGuest, owner display_name in listMembers. |
||
|
|
a2861d40f3 |
feat(server): injectable RealtimeService over the ws singleton
Nest services inject RealtimeService (call-time delegation to the websocket module, DatabaseService pattern) instead of importing broadcast/broadcastToUser globals. Zero behavior change; test edits are constructor-arg additions plus RealtimeModule in e2e harness imports only. |
||
|
|
65fb00d63b | feat(server): type MCP access groups against SCOPES + boot gate | ||
|
|
ee2ea1d065 | refactor(server): extract addon enablement into the addons domain | ||
|
|
7f34dadf70 | chore: move budget backend to proper DI | ||
|
|
a3e6eb52a4 | chore: fold exchangeRateService into the nest budget domain | ||
|
|
6ff936c613 | chore: move auditLog backend to proper DI | ||
|
|
1d56de3897 | chore: move permissions backend to proper DI | ||
|
|
2d1eec76c3 | chore: move day backend to proper DI | ||
|
|
203d67c45f |
chore: move reservations backend to proper DI
Folds the 626-line services/reservationService.ts into the DI-native ReservationsService — byte-identical SQL, statuses, bodies, error strings and broadcasts; the legacy file is deleted. - reservations.mcp.ts: the 5 legacy tools (create/update/delete_reservation, reorder_reservations, link_hotel_accommodation) + the trip-reservations resource move to the decorator registry (identical names, schemas, annotations, error strings, broadcasts); the canWrite/canRead registration gates become declarative access markers. Legacy registrar and resource entries removed. - reservations.bridge.ts: 9 legacy-named exports + 3 type re-exports for the outside-container consumers (tripService, airtrail import/sync, the still-legacy transit + transports registrars, transitItineraryService's type-only import). - notifyBookingChange folds to the legacy (tripId, actorId) signature; the controller passes user.id and the plugin-host factory drops its last plain-function reservation import for a private method over the injected service (constructor unchanged). - TripsService and BookingImportService inject ReservationsService instead of importing legacy functions. - Tests: reservations.service.test.ts rewritten as a real-SQL suite (RESV-SVC-001..030, RES-TRAV-001..005 moved verbatim, RESV-BRIDGE-001..009 pinning the bridge); both e2e suites converted to the DI-native pattern; path mocks repointed to the bridge or constructor stubs; tools-reservations kept and extended with the moved resource cases. Quirks preserved on purpose (parity first — see the follow-up fix commit): the raw-accommodation_id metadata-sync gate, the un-transactioned multi-statement writes, the truthy updatePositions dayId check, the empty-string COALESCE keeps, and the TEXT accommodation_id normalization. |
||
|
|
588457710c | chore: move vacay backend to proper DI | ||
|
|
c08c595b7e | chore: move collab backend to proper DI | ||
|
|
f3e5be78c9 |
chore: move files backend to proper DI
Folds the legacy services/fileService.ts into the DI-native FilesService (injected DatabaseService, byte-identical SQL). Load-time constants move to files.constants.ts; files.bridge.ts survives with a single export for the request-time getAllowedExtensions read the module-scope multer configs need outside DI. TripsService and PluginHostDepsFactory inject FilesService. Also rides the nestjs-zod ratchet (FileUploadDto/FileUpdateDto/FileLinkDto over the @trek/shared contracts, FilesController entries dropped from the body-contract allow-list) and fixes the verified legacy quirks: file-link insert failures now surface instead of being swallowed, updateFile coerces an empty-string description to NULL like createFile, and formatFile's misleading optional trip_id annotation is gone. |
||
|
|
cece6509b9 |
chore: move settings backend to proper DI
Folds the legacy services/settingsService SQL into the Nest SettingsService (byte-identical statements; BEGIN/COMMIT blocks become db.transaction()). AdminService and ShareService inject it; llm-config.resolver becomes the injectable LlmConfigResolver (SettingsService + DatabaseService), injected by LlmParseService and PluginHostDepsFactory. No MCP surface, no bridge. Whole-file commit, so this also carries the post-migration quirk fixes that live in the same files: null serializes as '' (a stored "null" leaked out of getDecryptedUserSetting as the literal string), bulk upserts skip the masked sentinel (mirroring the single-upsert no-op) and return the written count. Unit suite moved to tests/unit/nest with SET-SVC-001..026 preserved, 027..030 pin the fixes; settings/admin e2e converted to DI-native. |
||
|
|
164f5152be | chore: move assignments backend to proper DI and harden its writes | ||
|
|
4e49e9c21e | chore: move day-notes backend to proper DI | ||
|
|
f1960dc364 | chore: move packing backend to proper DI | ||
|
|
811230fa5d | refactor(plugins): wire PluginHostDepsFactory through DI, drop tags/categories bridges | ||
|
|
79946cc711 | chore: move todo backend to proper DI | ||
|
|
2ce40724d7 | chore: move category backend to proper DI | ||
|
|
8f3ee53841 | feat(mcp): Create nest wrapper for MCP for proper DI (#1708) | ||
|
|
9d93ed95bf |
feat(vacay): read and write the leave year over its own window (#737)
Adds GET/PUT /addons/vacay/year-settings and makes the reads window-aware, so the backend from the previous commit is actually reachable. Entries and shared calendars now load over the caller's window instead of a date prefix — a shifted year spans two calendar years and the prefix silently dropped the second half. Reads are month-aligned (the grid draws month cards), while entitlement stays day-exact; the two coincide for every window starting on the 1st, which is all of calendar. deleteYear clears entries per author over that author's period, and company holidays only over the range every member shares, so one member deleting a period cannot reach into another's live one. getStats reports the window it counted, and getOwnPlan seeds the period today falls into rather than the calendar year. A start day the month cannot have (Feb 29 from a hire date, a stored Feb 31) is clamped to one every year has — otherwise the boundary string matches no date and the whole window slides. Anniversary before a hire date is entered resolves to the calendar year instead of borrowing a month a previous fiscal setting left behind. MCP reads pass their user through; without one the range stays the plain calendar year, so plan-scoped callers are unchanged. |
||
|
|
31f6ec1f47 |
feat(vacay): read-only calendar sharing (#444, #667) (#1637)
* feat(vacay): read-only calendar shares on the server (#444, #667) * feat(vacay): shared calendars card and ring overlay on desktop * feat(vacay): share sheet and calendar rings on mobile * test(vacay): cover the calendar share flows * docs(vacay): document read-only calendar sharing |
||
|
|
ad893eb1cc |
Release 3.1.0 (#1185)
* Phase 0 — NestJS + Zod foundation harness (F1–F8) (#1050) Co-hosted NestJS app behind the existing Express server via a strangler-fig dispatcher, sharing the same better-sqlite3 connection and JWT httpOnly cookie. Additive and dormant: default routing stays on Express, Nest only serves its own /api/_nest diagnostics until a module opts in. F1 @trek/shared Zod contract package; F2 Nest bootstrap co-hosted (fall-through, single Dockerfile/port); F3 shared better-sqlite3 provider; F4 JWT cookie auth guard (+ @CurrentUser, admin guard); F5 Zod validation pipe + error-envelope parity; F6 Nest test + coverage gates; F7 per-prefix strangler toggle (env, default Express); F8 CI build/typecheck/test/coverage. Remaining F4/F6/F8 checklist items (trip-access + permission levels + MFA policy, e2e harness/seed + 80% gate, Nest↔Express parity test, Playwright PR-comment workflow) are tracked on the first consuming module cards (L1/A1/C1). * feat(weather): migrate /api/weather to the NestJS pilot module (L1) (#1053) First strangler migration (L1): /api/weather is served by a NestJS module. - @trek/shared/weather Zod contract; Nest controller byte-identical to the legacy Express route (paths, query params, status codes, { error } bodies, lang default, ApiError/500 passthrough). Service reuses getWeather/getDetailedWeather (+ shared cache; MCP tools unchanged). - Strangler routes /api/weather to Nest by default; the legacy Express route + its migration-time parity test were decommissioned in this PR. - Frontend (FE2): weatherApi typed against the @trek/shared WeatherResult contract. - Harness: reusable Nest-vs-Express parity harness, e2e harness (temp SQLite + seed/cookie helpers, real JwtAuthGuard), src/nest coverage gate raised to >=80%, src/nest test guide. - Verified end-to-end on a prod mirror (dev1): 401/400/200 via Nest with real Open-Meteo data, Express route gone. * fix(packing): multiply item weight by quantity in bag/total weight calcs (#898) Quantity now counts toward bag and total weights. Generalised to an itemWeight() helper used by every weight sum (bag totals + max, unassigned, grand total; sidebar + bag modal) with unit tests. * feat(i18n): add Korean (ko) translation (#977) Korean translation by @ppuassi, topped up to full en.ts key parity. Language registration follows separately. * feat(i18n): add Japanese (ja) translation (#829) Japanese translation by @soma3978, at full en.ts key parity, registered in supportedLanguages + TranslationContext. * Add Turkish (tr) translation + language registry (#1029) Turkish translation by @SkyLostTR, at full en.ts key parity, registered in supportedLanguages + TranslationContext. * i18n: register Korean + add Ukrainian translation (#1055) Korean translation by @ppuassi (#977) — now registered. Ukrainian by @JeffyOLOLO (#902) — lifted onto a clean branch. Both at full en.ts key parity (2258 keys). * chore: fix monorepo build pipeline and migrate shared to built package (#1056) * chore: fix monorepo build pipeline and migrate shared to built package - Root package.json: add workspace scripts (dev, build, test, test:cov, test:e2e) that delegate to actual scripts in shared/server/client workspaces - shared: add tsup build step (CJS + ESM dual output, .d.ts); consumers now import from the built dist instead of raw TS source via path aliases - server: replace tsc-alias with tsconfig-paths (tsc-alias mangled node_modules paths); fix MCP SDK path aliases to point to root node_modules (../node_modules) - server/scripts/dev.mjs: delay node --watch until tsc -w signals first-pass done, eliminating the spurious restart on every dev startup - client/vite.config.js + vitest.config.ts: remove @trek/shared path alias (no longer needed now that shared is a proper package) - Consolidate package-lock.json at the workspace root; drop per-workspace lock files * chore: fix test script to reflect root package.json * chore: add missing lint and prettier script in root package.json * fix(ci): build shared before tests; fix vitest MCP SDK alias paths vitest.config.ts aliases pointed at ./node_modules/ (server-local) but packages are hoisted to the root node_modules/ in the npm workspace — changed to ../node_modules/. CI jobs now install and build shared before running server/client tests so that @trek/shared's dist/ exists when vitest resolves the package. * fix(docker): update Dockerfile and CI for monorepo workspace structure Dockerfile: - Add shared-builder stage that produces @trek/shared dist before client and server stages need it - Each build stage carries root package.json + package-lock.json so npm can resolve @trek/shared as a workspace dependency - Production stage installs via workspace context (npm ci --workspace=server --omit=dev) so node_modules/@trek/shared symlinks to shared/dist correctly - Copy server/tsconfig.json into the image so tsconfig-paths/register can find the MCP SDK path aliases at runtime - CMD cds into /app/server before starting node so tsconfig-paths baseUrl resolves and ../node_modules points to /app/node_modules - Remove mkdir for /app/server (now a real dir); keep symlinks for uploads/data docker.yml version-bump: - Replace manual per-workspace cd+npm-version calls with single: npm version --workspaces --include-workspace-root --no-git-tag-version (mirrors the version:* scripts in root package.json) - git add now references root package-lock.json; adds shared/package.json .dockerignore: add shared/dist package.json: fix version:prerelease preid (alpha → pre) * fix(tests): use in-memory SQLite per worker in test mode vitest pool:forks spawns parallel worker processes that all called initDb() on the same data/travel.db, causing SQLite "database is locked" and "duplicate column name" races. When NODE_ENV=test each fork now gets an isolated :memory: DB so migrations run independently with no file contention. * chore(ci): add ACT guards to skip DockerHub steps in local act runs act sets ACT=true automatically. Guards added: - docker login: if: ${{ !env.ACT }} - build outputs: type=docker (local load) when ACT, push-by-digest when CI - digest export/upload: if: ${{ !env.ACT }} - merge job: if: ${{ !env.ACT }} - release-helm job (docker.yml): if: ${{ !env.ACT }} - version-bump git push (docker.yml): wrapped in [ -z "$ACT" ] shell guard Run locally with: ./bin/act -j build -W .github/workflows/docker.yml \ -P ubuntu-latest=catthehacker/ubuntu:act-latest * fix(ci): move ACT guards to step level; add guards to security.yml env context is invalid in job-level if conditions — moved all ACT guards down to individual steps. Also guards docker login + scout in security.yml so act can run the build-only part of that workflow. * fix(ci): skip git fetch and tag logic in act (no remote access in local containers) * Revert "fix(ci): skip git fetch and tag logic in act (no remote access in local containers)" This reverts commit |
||
|
|
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. |
||
|
|
4362406e74 | Merge remote-tracking branch 'refs/remotes/pull/495' into feat/naver-support | ||
|
|
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. |
||
|
|
18da5aed39 | Merge branch 'dev' into feature/naver-support | ||
|
|
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) |
||
|
|
6a632137ed | refactor(trip): Naver List Import as Addon | ||
|
|
f2908fdd65 |
test(mcp): add tests for OAuth 2.1, addon gating, and budget reorder
Covers OAuth integration flow, scope enforcement, addon-gated tool access, oauthService unit tests, and budget reorder/permission/reservation-sync scenarios. |
||
|
|
63784d86a3 | refactor(mcp): extract all MCP tools into dedicated modules and add shared helpers | ||
|
|
b4922322ae |
test: expand test suite to 87.3% backend coverage
Add new integration test files covering previously untested routes: - categories.test.ts — GET /api/categories - oidc.test.ts — full OIDC login flow (callback, state, errors) - settings.test.ts — GET/PUT /api/settings, bulk save - tags.test.ts — CRUD for trip tags - todo.test.ts — todo items CRUD and reorder Add new unit test files covering service-layer logic: - adminService.test.ts — user/invite management, packing templates, OIDC settings - atlasService.test.ts — atlas search and place enrichment - authServiceDb.test.ts — DB-backed auth helpers (login, register, MFA) - backupService.test.ts — export/import/restore logic - categoryService.test.ts — category CRUD - dayService.test.ts — day management and accommodation helpers - mapsService.test.ts — route/directions helpers - oidcService.test.ts — OIDC state, auth code, role resolution, user upsert - packingService.test.ts — packing item/bag/template operations - placeService.test.ts — place CRUD and tag attachment - settingsService.test.ts — settings get/set/bulk - tagService.test.ts — tag CRUD - todoService.test.ts — todo CRUD and reorder - tripService.test.ts — trip CRUD, member management, archiving - vacayService.test.ts — vacay integration helpers - tripAccess.test.ts (middleware) — requireTripAccess middleware Expand existing integration and unit test files with additional cases across admin, atlas, auth, backup, collab, days, files, maps, memories (Immich/Synology), notifications, places, reservations, share, vacay, weather, auth middleware, ephemeral tokens, notification preferences, permissions, SSRF guard, and WebSocket connection tests. Update test helpers (factories.ts, test-db.ts) with new factory functions and seed data required by the expanded suite. Fix minor issues in server/src/routes/reservations.ts and server/src/services/atlasService.ts surfaced by new test coverage. Update sonar-project.properties to reflect new coverage thresholds. |
||
|
|
5cc81ae4b0 |
refactor(server): replace node-fetch with native fetch + undici, fix photo integrations
Replace node-fetch v2 with Node 22's built-in fetch API across the entire server.
Add undici as an explicit dependency to provide the dispatcher API needed for
DNS pinning (SSRF rebinding prevention) in ssrfGuard.ts. All seven service files
that used a plain `import fetch from 'node-fetch'` are updated to use the global.
The ssrfGuard safeFetch/createPinnedAgent is rewritten as createPinnedDispatcher
using an undici Agent, with correct handling of the `all: true` lookup callback
required by Node 18+. The collabService dynamic require() and notifications agent
option are updated to use the dispatcher pattern. Test mocks are migrated from
vi.mock('node-fetch') to vi.stubGlobal('fetch'), and streaming test fixtures are
updated to use Web ReadableStream instead of Node Readable.
Fix several bugs in the Synology and Immich photo integrations:
- pipeAsset: guard against setting headers after stream has already started
- _getSynologySession: clear stale SID and re-login when decrypt_api_key returns null
instead of propagating success(null) downstream
- _requestSynologyApi: return retrySession error (not stale session) on retry failure;
also retry on error codes 106 (timeout) and 107 (duplicate login), not only 119
- searchSynologyPhotos: fix incorrect total field type (Synology list_item returns no
total); hasMore correctly uses allItems.length === limit
- _splitPackedSynologyId: validate cache_key format before use; callers return 400
- getImmichCredentials / _getSynologyCredentials: treat null from decrypt_api_key as
a missing-credentials condition rather than casting null to string
- Synology size param: enforce allowlist ['sm', 'm', 'xl'] per API documentation
|
||
|
|
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 |