mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-19 05:26:47 +00:00
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.
This commit is contained in:
+1
-1
@@ -28,7 +28,7 @@ Wave 4 — the coupled cluster (order matters here)
|
||||
dayService; the budget/day coupling lives in the Nest wrapper's budget-sync seam and the MCP registrars, which keep their legacy imports until those domains migrate. Reservations went first as the frontier residue fold; dayService
|
||||
(done 2026-07 — the 592-line service folded into the wrapper `DaysService`, the accommodations seam in `nest/reservations/` now injects it, and the hand-rolled reorder/insert transactions became `db.transaction()`) followed;
|
||||
budgetService followed (done 2026-07 — the 755-line money core folded into the wrapper `BudgetService` (settlement maths, FX freeze/rebase, items/members/payers CRUD; the raw settlement writes became `insertSettlement`/`applySettlementUpdate` so the MCP paths keep skipping the freeze); the 11-tool mcp registrar + 3 budget resources moved to the decorator-driven `budget.mcp.ts`; TripsService/ReservationsService/BookingImportService now inject `BudgetService`; a 4-export `budget.bridge.ts` serves tripService, userCleanupService and the legacy trips/transports registrars; `exchange-rates.bridge.ts` was deleted with its last consumers; and the controller adopted `budget.dto.ts` over the shared schemas, clearing all nine budget allow-list entries — **tripService is now unblocked**).
|
||||
9. Then placeService (7 internal deps, mostly on things migrated by now) and tripService (10 deps — the biggest hub; last in this wave, since nearly everything it needs will already be injectable).
|
||||
9. tripService (done 2026-07 — the 1121-line hub folded into the wrapper `TripsService`: generateDays, the updateTrip date-shift transaction, members/guests (#973/#1362), the ICS export, copyTripById and getTripSummary; its six bridge imports became injected services (CollabService/VacayService joined the constructor); the 10-tool trips registrar + 3 trip resources + the trip-summary prompt moved to the decorator-driven `trips.mcp.ts` (first `@Prompt`; the static-token deprecation notice rides the `registry.attach` ctx) and the 3 share-link tools to `share.mcp.ts` on the `canShareTrips` predicate; FeedsService and the plugin RPC host inject `TripsService`; a 3-export `trips.bridge.ts` serves the legacy prompts registrar and budget.mcp.ts's owner/member seam; todo/share/collab/vacay bridges deleted with their last consumers; the controller adopted `trips.dto.ts`, clearing all seven trip allow-list entries). Then placeService (7 internal deps, mostly on things migrated by now — the remaining Wave-4 pick).
|
||||
|
||||
Wave 5 — the heavyweights, last
|
||||
|
||||
|
||||
+44
-28
@@ -1,8 +1,14 @@
|
||||
# Legacy `src/services/` dependency graph
|
||||
|
||||
Generated from the actual imports in `server/src` on **2026-07-28** (after the
|
||||
budgetService migration + its quirk-fix pass — completing step 1 of the
|
||||
dependency-honest order below and unblocking the tripService hub — and the
|
||||
Generated from the actual imports in `server/src` on **2026-07-29** (after the
|
||||
tripService migration + its quirk-fix pass — completing step 2 of the
|
||||
dependency-honest order below: the 1121-line hub folded into the DI-native
|
||||
`TripsService`, its MCP surface moved to the decorator-driven
|
||||
`trips.mcp.ts`/`share.mcp.ts`, the plugin RPC host injects it, and the bridge
|
||||
cascade landed — `todo.bridge`, `share.bridge`, `collab.bridge` and
|
||||
`vacay.bridge` deleted with their last consumers, the days/budget bridges
|
||||
pruned to their surviving exports. Earlier context: the budgetService
|
||||
migration (step 1) and the
|
||||
Phase 0 quick wins: `getAppUrl`/`getMcpSafeUrl` → `src/app-config` deleted the
|
||||
fake `→ notifications` edges for the identity/MCP stack and freed mapsService;
|
||||
the addon-enablement reads moved from adminService into `nest/addons/`
|
||||
@@ -44,14 +50,20 @@ How to read it:
|
||||
`ExchangeRatesService` — module-scoped rate cache retained on purpose, permissions-style,
|
||||
so out-of-container instances and the DI singleton share one cached upstream feed;
|
||||
its `exchange-rates.bridge` was deleted with the budgetService migration),
|
||||
budget (the 755-line money core folded into `BudgetService`; `budget.bridge.ts` carries
|
||||
the four exports still consumed outside the container — tripService, userCleanupService
|
||||
and the legacy trips/transports MCP registrars).
|
||||
budget (the 755-line money core folded into `BudgetService`; `budget.bridge.ts` is down
|
||||
to the two exports still consumed outside the container — userCleanupService and the
|
||||
legacy transports registrar),
|
||||
trip (the 1121-line hub folded into `TripsService`; its six bridge imports became
|
||||
injected services, and the fold deleted `todo.bridge`, `share.bridge`, `collab.bridge`
|
||||
and `vacay.bridge` with their last consumers; a 3-export `trips.bridge.ts` serves the
|
||||
legacy prompts registrar's getTripSummary and `budget.mcp.ts`'s getTripOwner/listMembers
|
||||
seam — injecting there would need a forwardRef'd TripsModule↔BudgetModule cycle;
|
||||
`days.bridge.ts` shrank to getDay/listDays for the transit/transports registrars).
|
||||
- **Domain migration targets** (the wave material): adminService, airportService, atlasService,
|
||||
authService, backupService, collectionsService,
|
||||
journeyService, journeyShareService, mapsService, notificationService, oauthService,
|
||||
oidcService, passkeyService, placeService, transitService,
|
||||
transitItineraryService, tripService, weatherService, wikiService.
|
||||
transitItineraryService, weatherService, wikiService.
|
||||
- **Cross-cutting Wave-2 targets:** permissions and auditLog are done (2026-07) — see the
|
||||
DI-native list above; only tripAccess remains (delete, don't migrate).
|
||||
- **Helpers that stay as plain modules** (pure/infra, not wave material): avatarUrl,
|
||||
@@ -74,7 +86,6 @@ flowchart TD
|
||||
classDef infra fill:#455a64,color:#fff
|
||||
|
||||
subgraph frontier["READY FRONTIER (no unmigrated domain deps)"]
|
||||
trip["tripService (the hub)"]:::hub
|
||||
atlas[atlasService]:::ready
|
||||
weather[weatherService]:::ready
|
||||
airport["airportService (boot special case)"]:::ready
|
||||
@@ -99,7 +110,6 @@ flowchart TD
|
||||
memories["memories/ cluster"]:::infra
|
||||
cleanup[userCleanupService]:::infra
|
||||
|
||||
trip --> cleanup
|
||||
collections -.-> notifSvc
|
||||
place --> maps
|
||||
transit --> maps
|
||||
@@ -122,10 +132,11 @@ journey/memories corner tangle with the admin corner. The former
|
||||
`auth/collections/backup → permissions` and `notifSvc/oauth → auditLog` edges are gone since the
|
||||
2026-07 Wave-2 pair: the permissions consumers repointed to `nest/permissions/permissions.bridge`,
|
||||
the writeAudit consumers to `nest/audit/audit.bridge`, and the log*-only consumers to the plain
|
||||
`nest/audit/audit-log.logger` — none of them block a migration anymore. The former
|
||||
`trip → budget` and `cleanup → budget` edges are gone since the 2026-07 budgetService
|
||||
migration: both repointed to `nest/budget/budget.bridge`, leaving tripService with no
|
||||
unmigrated domain dependency — the hub is frontier-ready. The dotted
|
||||
`nest/audit/audit-log.logger` — none of them block a migration anymore. The
|
||||
tripService hub node is gone since the 2026-07 trip fold — the DI-native
|
||||
`TripsService` injects its former bridge targets and keeps the legacy
|
||||
avatarUrl/timezoneService/userCleanupService helpers as plain imports (helpers
|
||||
never block). The dotted
|
||||
`collections -.-> notifSvc` edge is a call-time `import()` for the collection-invite
|
||||
notification: it stays as-is through a migration, exactly like the identical lazy sends in the
|
||||
already-migrated collab/packing/trips/reservations/vacay services.)
|
||||
@@ -138,8 +149,8 @@ already-migrated collab/packing/trips/reservations/vacay services.)
|
||||
| `airportService` | (none) | (none) | nest/airports/airports.service.ts, nest/booking-import/kitinerary-mapper.ts | db/database.ts, mcp/tools/mapsWeather.ts, mcp/tools/transports.ts |
|
||||
| `apiKeyCrypto` | (none) | adminService, airtrail/airtrailService, authService, llmConfig, mapsService, memories/helpersService, memories/immichService, memories/photoResolverService, memories/synologyService, memories/unifiedService, notifications, oidcService, unsplashService | nest/plugins/plugin-oauth.service.ts, nest/plugins/plugin-runtime.service.ts, nest/plugins/plugins.service.ts, nest/settings/settings.service.ts | db/migrations.ts |
|
||||
| `atlasService` | (none) | authService | nest/atlas/atlas.service.ts, nest/plugins/host/plugin-host-deps.factory.ts | mcp/resources.ts, mcp/tools/atlas.ts |
|
||||
| `authService` | apiKeyCrypto, atlasService, avatarUrl, demo, distanceService, ephemeralTokens, mfaCrypto, passwordPolicy, tripMembership, userCleanupService, webauthnConfig (+ `permissions.bridge`) | adminService, oidcService, passkeyService | nest/assignments/assignments.mcp.ts, nest/auth/auth.service.ts, nest/auth/passkey-enabled.guard.ts, nest/budget/budget.mcp.ts, nest/collab/collab.mcp.ts, nest/days/day-notes.mcp.ts, nest/days/days.mcp.ts, nest/oidc/oidc.service.ts, nest/packing/packing.mcp.ts, nest/reservations/reservations.mcp.ts, nest/tags/tags.mcp.ts, nest/todo/todo.mcp.ts, nest/vacay/vacay.mcp.ts | mcp/index.ts, mcp/tools/atlas.ts, mcp/tools/collections.ts, mcp/tools/journey.ts, mcp/tools/notifications.ts, mcp/tools/places.ts, mcp/tools/transit.ts, mcp/tools/transports.ts, mcp/tools/trips.ts |
|
||||
| `avatarUrl` | (none) | adminService, authService, inAppNotifications, journeyService, tripService | nest/budget/budget.service.ts, nest/collab/collab.service.ts, nest/files/files.service.ts, nest/packing/packing.service.ts, nest/reservations/reservations.service.ts | (none) |
|
||||
| `authService` | apiKeyCrypto, atlasService, avatarUrl, demo, distanceService, ephemeralTokens, mfaCrypto, passwordPolicy, tripMembership, userCleanupService, webauthnConfig (+ `permissions.bridge`) | adminService, oidcService, passkeyService | nest/assignments/assignments.mcp.ts, nest/auth/auth.service.ts, nest/auth/passkey-enabled.guard.ts, nest/budget/budget.mcp.ts, nest/collab/collab.mcp.ts, nest/days/day-notes.mcp.ts, nest/days/days.mcp.ts, nest/oidc/oidc.service.ts, nest/packing/packing.mcp.ts, nest/reservations/reservations.mcp.ts, nest/share/share.mcp.ts, nest/tags/tags.mcp.ts, nest/todo/todo.mcp.ts, nest/trips/trips.mcp.ts, nest/vacay/vacay.mcp.ts | mcp/index.ts, mcp/tools/atlas.ts, mcp/tools/collections.ts, mcp/tools/journey.ts, mcp/tools/notifications.ts, mcp/tools/places.ts, mcp/tools/transit.ts, mcp/tools/transports.ts |
|
||||
| `avatarUrl` | (none) | adminService, authService, inAppNotifications, journeyService | nest/budget/budget.service.ts, nest/collab/collab.service.ts, nest/files/files.service.ts, nest/packing/packing.service.ts, nest/reservations/reservations.service.ts, nest/trips/trips.service.ts | (none) |
|
||||
| `backupService` | (none — `permissions.bridge`, plugin backup/paths infra only) | (none) | nest/backup/backup.controller.ts, nest/backup/backup.service.ts | (none) |
|
||||
| `collectionsService` | notificationService (lazy), placeImage (+ `permissions.bridge`) | (none) | nest/collections/collections.service.ts, nest/plugins/host/plugin-host-deps.factory.ts | mcp/tools/collections.ts |
|
||||
| `conflictResult` | (none) | placeService | nest/packing/packing.controller.ts, nest/packing/packing.service.ts, nest/places/places.controller.ts, nest/plugins/host/plugin-host-deps.factory.ts | (none) |
|
||||
@@ -167,14 +178,13 @@ already-migrated collab/packing/trips/reservations/vacay services.)
|
||||
| `placePhotoCache` | (none) | mapsService, placeService | nest/maps/maps.service.ts, nest/share/share.service.ts | scheduler.ts |
|
||||
| `placeService` | conflictResult, kmlImport, placeEnrichment, placeImage, placePhotoCache, queryHelpers, unsplashService | (none) | nest/booking-import/booking-import.service.ts, nest/days/days.mcp.ts, nest/places/places.service.ts, nest/plugins/host/plugin-host-deps.factory.ts, nest/trips/trips.service.ts | mcp/resources.ts, mcp/tools/places.ts |
|
||||
| `queryHelpers` | (none) | placeService | nest/assignments/assignments.service.ts, nest/days/days.service.ts, nest/share/share.service.ts | (none) |
|
||||
| `timezoneService` | (none) | airtrail/airtrailMapper, transitItineraryService, tripService | (none) | (none) |
|
||||
| `timezoneService` | (none) | airtrail/airtrailMapper, transitItineraryService | nest/trips/trips.service.ts | (none) |
|
||||
| `transitItineraryService` | distanceService, timezoneService, transitService (+ type-only `reservations.bridge`) | (none) | (none) | mcp/tools/transit.ts |
|
||||
| `transitService` | mapsService | transitItineraryService | nest/transit/transit.controller.ts | mcp/tools/transit.ts |
|
||||
| `tripAccess` | (none) | tripService | nest/booking-import/booking-import.service.ts, nest/budget/budget.service.ts, nest/collab/collab.service.ts, nest/integrations/airtrail-import.controller.ts, nest/packing/packing.service.ts, nest/reservations/reservations.service.ts, nest/todo/todo.service.ts | (none) |
|
||||
| `tripAccess` | (none) | (none) | nest/booking-import/booking-import.service.ts, nest/budget/budget.service.ts, nest/collab/collab.service.ts, nest/days/day-notes.service.ts, nest/integrations/airtrail-import.controller.ts, nest/packing/packing.service.ts, nest/reservations/reservations.service.ts, nest/todo/todo.service.ts | (none) |
|
||||
| `tripMembership` | (none) | authService, oidcService | nest/plugins/host/plugin-host-deps.factory.ts, nest/trip-invite/trip-invite.service.ts | (none) |
|
||||
| `tripService` | avatarUrl, timezoneService, tripAccess, userCleanupService (+ `budget.bridge`, `collab.bridge`, `days.bridge`, `packing.bridge`, `reservations.bridge`, `vacay.bridge`) | (none) | nest/budget/budget.mcp.ts, nest/feeds/feeds.service.ts, nest/plugins/host/plugin-host-deps.factory.ts, nest/trips/trips.controller.ts, nest/trips/trips.service.ts | mcp/resources.ts, mcp/tools/prompts.ts, mcp/tools/trips.ts |
|
||||
| `unsplashService` | apiKeyCrypto | placeService | nest/trips/trips.controller.ts, nest/trips/trips.service.ts | (none) |
|
||||
| `userCleanupService` | (none — `budget.bridge`, plugin paths infra only) | adminService, authService, tripService | (none) | (none) |
|
||||
| `userCleanupService` | (none — `budget.bridge`, plugin paths infra only) | adminService, authService | nest/trips/trips.service.ts | (none) |
|
||||
| `weatherService` | (none) | (none) | nest/plugins/host/plugin-host-deps.factory.ts, nest/weather/weather.controller.ts, nest/weather/weather.service.ts | mcp/tools/mapsWeather.ts |
|
||||
| `webauthnConfig` | (none) | authService, passkeyService | (none) | (none) |
|
||||
| `wikiService` | (none) | (none) | nest/help/help.controller.ts | (none) |
|
||||
@@ -205,19 +215,14 @@ already-migrated collab/packing/trips/reservations/vacay services.)
|
||||
|
||||
| Candidate | Why now / why not | Bridge tax (legacy dependents + out-of-container) |
|
||||
|---|---|---|
|
||||
| **tripService** ← pick | budgetService landed (2026-07), so the hub's last unmigrated domain edge is gone (its remaining legacy deps are helpers + the budget/collab/days/packing/reservations/vacay bridges); every other migration keeps paying it a repoint tax until it moves | no legacy dependents; mcp resources/prompts/trips registrars + `budget.mcp.ts`'s getTripOwner/listMembers seam |
|
||||
| **mapsService** ← pick | Frontier-ready since the Phase 0 `getAppUrl` → app-config move; heads the step-4 chain that unblocks the placeService Wave-4 remainder (tripService — the hub — landed 2026-07) | `placeEnrichment`, `transitService`; `mcp/tools/mapsWeather.ts`, `mcp/tools/places.ts` |
|
||||
| **collectionsService** | Frontier-ready — legacy deps are the placeImage helper + `permissions.bridge`, plus a call-time lazy `import()` of notificationService that a migrated service keeps (collab precedent) | `mcp/tools/collections.ts` |
|
||||
| **atlasService** | Zero deps, but its legacy dependent is `authService` (Wave-5) → bridge lives long | `authService`; `mcp/tools/atlas.ts`, `mcp/resources.ts` |
|
||||
| **mapsService** | Frontier-ready since the Phase 0 `getAppUrl` → app-config move (remaining legacy deps are the apiKeyCrypto/placePhotoCache helpers); unblocks transitService → placeService → transitItineraryService | `placeEnrichment`, `transitService`; `mcp/tools/mapsWeather.ts`, `mcp/tools/places.ts` |
|
||||
| **oauthService** | Dependency-free since the Phase 0 addons extraction (adminService edge was only `isAddonEnabled` → `addons.bridge`) — but the coherence order keeps it after admin so the `mcp/oauthProvider.ts` merge (`mcp-2`) lands with it | `mcp/index.ts`, `mcp/oauthProvider.ts` |
|
||||
| **weatherService / wikiService / airportService** | Independent leaves; airport has the `db/database.ts` boot lazy-require special case | little / none |
|
||||
|
||||
**Blocked, and by what (shortest unblock path):**
|
||||
|
||||
- `tripService` (the hub) — **unblocked since the 2026-07 budgetService
|
||||
migration** (its budget edge and `userCleanupService`'s are now
|
||||
`budget.bridge` repoints, joining the collab/vacay/reservations/days ones).
|
||||
See the frontier table above.
|
||||
- `placeService` ← `mapsService` (via placeEnrichment) — mapsService itself is
|
||||
frontier-ready since the Phase 0 app-url move deleted its notifications edge.
|
||||
- `notificationService` ← notifications cluster (its auditLog edge is now the plain
|
||||
@@ -246,7 +251,9 @@ Wave-2 `permissions` + `auditLog` pair were the first frontier picks — all don
|
||||
1. `exchangeRateService` fold → `budgetService` (both done 2026-07 — `ExchangeRatesService`
|
||||
then the full `BudgetService` fold in `nest/budget/`; `userCleanupService` is free,
|
||||
repointed to `budget.bridge`)
|
||||
2. `tripService` — all remaining domain edges + userCleanup now gone (frontier `← pick`)
|
||||
2. `tripService` (done 2026-07 — the hub folded into `TripsService`; the trips/share MCP
|
||||
surfaces moved to the decorator registry, the plugin host injects it, and the
|
||||
todo/share/collab/vacay bridges died with their last consumers)
|
||||
3. notifications cluster → `notificationService`
|
||||
4. `mapsService` → `transitService` → `placeService` → `transitItineraryService`
|
||||
5. `atlasService` → `authService` (+ oidc/passkey) → `adminService` → `oauthService`
|
||||
@@ -262,8 +269,10 @@ Wave-2 `permissions` + `auditLog` pair were the first frontier picks — all don
|
||||
frontier-ready. **Borne out by the 2026-07 fold**: the budget/day coupling lives in the
|
||||
Nest wrapper's budget-sync seam and the MCP surface, which keep their legacy imports until
|
||||
those domains migrate.
|
||||
- Every remaining Wave-3/4 domain has legacy `tripService` as a dependent — each migration
|
||||
before tripService pays a small bridge/repoint tax the files migration didn't have.
|
||||
- Every remaining Wave-3/4 domain had legacy `tripService` as a dependent — each migration
|
||||
before tripService paid a small bridge/repoint tax the files migration didn't have.
|
||||
**Resolved by the 2026-07 trip fold**: that recurring tax is gone, and the fold cashed in
|
||||
the accumulated bridges (todo/share/collab/vacay deleted, days/budget pruned).
|
||||
- (2026-07-28 regeneration) The earlier grep-based analysis only saw static imports, so it
|
||||
missed every lazy edge: the scheduler's `require()` jobs (admin, notificationService,
|
||||
placePhotoCache, airtrailSync, trekPhotoCache), the `db/database.ts` boot backfill's
|
||||
@@ -272,3 +281,10 @@ Wave-2 `permissions` + `auditLog` pair were the first frontier picks — all don
|
||||
also under-reported `tripService`'s bridge repoints (six, not three: budget, collab, days,
|
||||
packing, reservations, vacay). None of this changes the order — lazy edges don't block —
|
||||
but the bridge-pressure column was undercounting scheduler/system-notice consumers.
|
||||
- (2026-07-29 regeneration, post trip fold) The predicted bridge tax was right in shape but
|
||||
the fold went further than a bridge: the whole trips MCP surface (10 tools + 3 resources
|
||||
+ the first `@Prompt`) and the 3 share-link tools moved onto the decorator registry, so
|
||||
the only bridge left is the 3-export `trips.bridge.ts` for the legacy prompts registrar
|
||||
and `budget.mcp.ts`'s owner/member seam. `verifyTripAccess`'s `tripAccess` re-export died
|
||||
with the legacy file, leaving `services/tripAccess` with zero legacy importers — it is
|
||||
now purely Nest-consumed (Wave-2 "delete, don't migrate" is down to those repoints).
|
||||
|
||||
@@ -28,8 +28,8 @@ mount to Nest and leaves the sibling trip routes (days, places, ...) on Express.
|
||||
- **Phase 2 (trip sub-domains):** vacay (addon), packing, todo.
|
||||
- **DI-native services (legacy `src/services/*` deleted):** tags, categories,
|
||||
todo, packing, day-notes, trip-invite, assignments, share, settings, files,
|
||||
collab, vacay, reservations, day, permissions, audit — see the migration
|
||||
recipe below.
|
||||
collab, vacay, reservations, day, permissions, audit, budget, trip — see the
|
||||
migration recipe below.
|
||||
|
||||
## Cross-cutting Foundation pieces
|
||||
|
||||
@@ -203,9 +203,30 @@ MCP class) and BookingImportService inject `BudgetService`; a 4-export
|
||||
trips/transports registrars; `exchange-rates.bridge.ts` was deleted with its
|
||||
last consumers, and the controller adopted `budget.dto.ts` — all nine
|
||||
allow-list entries removed).
|
||||
tripService followed (the 1121-line Wave-4 hub — the biggest fold — moved into
|
||||
the wrapper `TripsService`: TRIP_SELECT + list/create/get, the
|
||||
`generateDays` two-phase renumber engine, the updateTrip date-shift
|
||||
transaction, the member/guest lifecycle (#973/#1362), the ICS export with its
|
||||
module-scoped tz-validity cache, `copyTripById` and `getTripSummary`; its six
|
||||
bridge imports became injected services (CollabService + VacayService joined
|
||||
the constructor); the 10-tool trips registrar + 3 `resources.ts` trip
|
||||
resources + the trip-summary prompt moved to `trips.mcp.ts` — the first
|
||||
`@Prompt` use, with the fire-once static-token deprecation notice now riding
|
||||
the `registry.attach` ctx — and the 3 share-link tools it carried moved to
|
||||
`share.mcp.ts` on the `canShareTrips` predicate (delete_trip and the
|
||||
canReadTrips reads are predicates too — the broadened legacy gates have no
|
||||
declarative equivalent); FeedsService and the plugin host inject
|
||||
`TripsService` (its 20th constructor dep); a 3-export `trips.bridge.ts`
|
||||
serves the legacy prompts registrar and budget.mcp.ts's owner/member seam
|
||||
(injecting there would need a forwardRef'd TripsModule↔BudgetModule cycle);
|
||||
`todo.bridge.ts`, `share.bridge.ts`, `collab.bridge.ts` and `vacay.bridge.ts`
|
||||
were deleted with their last consumers and the unused days/budget bridge
|
||||
exports pruned; the controller adopted `trips.dto.ts` — all seven allow-list
|
||||
entries removed).
|
||||
Repeat these steps per
|
||||
service (next up: **tripService** — the budget fold was its unblock, per the
|
||||
dependency-honest order in `migration-graph.md`). This is a
|
||||
service (next up: the **mapsService → transitService → placeService** chain —
|
||||
or the notifications fan-in — per the dependency-honest order in
|
||||
`migration-graph.md`). This is a
|
||||
**pure relocation** — byte-identical
|
||||
SQL, statuses, bodies, and error strings. The plugin RPC host is **no longer a
|
||||
bridge consumer**: since Option A of `src/nest/plugins/DI-MIGRATION.md` it
|
||||
|
||||
@@ -7,15 +7,16 @@ import { ExchangeRatesService } from './exchange-rates.service';
|
||||
|
||||
/**
|
||||
* Non-Nest entry point for the budget domain — for code running OUTSIDE the
|
||||
* Nest container (the legacy services/tripService.ts and
|
||||
* services/userCleanupService.ts, and the legacy update_trip / create_transport
|
||||
* registrars in src/mcp/tools/trips.ts and src/mcp/tools/transports.ts; the
|
||||
* budget MCP tools and resources moved to the DI-discovered budget.mcp.ts, and
|
||||
* the plugin RPC host injects BudgetService via PluginHostDepsFactory).
|
||||
* Exports only the legacy services/budgetService names still consumed outside
|
||||
* the container, 1:1, so repointing a consumer is an import-path-only diff.
|
||||
* Inside the container, inject BudgetService instead. Delete this file when
|
||||
* tripService/userCleanupService and the legacy MCP registrars migrate.
|
||||
* Nest container (services/userCleanupService.ts and the legacy
|
||||
* create_transport registrar in src/mcp/tools/transports.ts; the budget MCP
|
||||
* tools and resources moved to the DI-discovered budget.mcp.ts, the plugin
|
||||
* RPC host injects BudgetService via PluginHostDepsFactory, and the trip
|
||||
* domain folded into the DI-native TripsService, which injects BudgetService —
|
||||
* its bridge exports were pruned with it). Exports only the legacy
|
||||
* services/budgetService names still consumed outside the container, 1:1, so
|
||||
* repointing a consumer is an import-path-only diff. Inside the container,
|
||||
* inject BudgetService instead. Delete this file when
|
||||
* userCleanupService and the transports registrar migrate.
|
||||
*
|
||||
* Module-level construction is safe: `db` is the reinitialize-proof Proxy onto
|
||||
* the shared better-sqlite3 singleton, and ExchangeRatesService keeps its FX
|
||||
@@ -24,18 +25,10 @@ import { ExchangeRatesService } from './exchange-rates.service';
|
||||
*/
|
||||
const budget = new BudgetService(new DatabaseService(db), new PermissionsService(new DatabaseService(db)), new ExchangeRatesService(), new RealtimeService());
|
||||
|
||||
export function listBudgetItems(tripId: string | number) {
|
||||
return budget.listBudgetItems(tripId);
|
||||
}
|
||||
|
||||
export function removeUserFromBudgetItems(userId: number): void {
|
||||
budget.removeUserFromBudgetItems(userId);
|
||||
}
|
||||
|
||||
export function rebaseTripCurrency(tripId: string | number, newCurrency: string | null | undefined): Promise<void> {
|
||||
return budget.rebaseTripCurrency(tripId, newCurrency);
|
||||
}
|
||||
|
||||
export function linkBudgetItemToReservation(
|
||||
tripId: string | number,
|
||||
reservationId: number,
|
||||
|
||||
@@ -1,33 +0,0 @@
|
||||
import { db } from '../../db/database';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
import { RealtimeService } from '../realtime/realtime.service';
|
||||
import { CollabService } from './collab.service';
|
||||
import { PermissionsService } from '../permissions/permissions.service';
|
||||
|
||||
/**
|
||||
* Non-Nest entry point for the collab domain — for code running OUTSIDE the
|
||||
* Nest container (the legacy trip-summary builder in src/services/tripService.ts
|
||||
* and the legacy get_trip_summary registrar in src/mcp/tools/trips.ts; the
|
||||
* collab MCP tools and resources moved to the DI-discovered collab.mcp.ts, and
|
||||
* the plugin RPC host injects CollabService via PluginHostDepsFactory). Exports
|
||||
* only the legacy services/collabService names still consumed outside the
|
||||
* container, 1:1, so repointing a consumer is an import-path-only diff. Inside
|
||||
* the container, inject CollabService instead. Delete this file when
|
||||
* tripService and the legacy MCP trips registrar migrate.
|
||||
*
|
||||
* Module-level construction is safe: `db` is the reinitialize-proof Proxy onto
|
||||
* the shared better-sqlite3 singleton.
|
||||
*/
|
||||
const collab = new CollabService(new DatabaseService(db), new PermissionsService(new DatabaseService(db)), new RealtimeService());
|
||||
|
||||
export function listNotes(tripId: string | number) {
|
||||
return collab.listNotes(tripId);
|
||||
}
|
||||
|
||||
export function listPolls(tripId: string | number) {
|
||||
return collab.listPolls(tripId);
|
||||
}
|
||||
|
||||
export function countMessages(tripId: string | number): number {
|
||||
return collab.countMessages(tripId);
|
||||
}
|
||||
@@ -58,8 +58,9 @@ export interface LinkPreviewResult {
|
||||
* run in db.transaction(), getFormattedNoteById is trip-scoped and null-safe,
|
||||
* votePoll rejects non-integer indexes, and linkPreview absorbs malformed URLs
|
||||
* instead of throwing.
|
||||
* Non-Nest consumers (legacy tripService, the legacy MCP trips registrar) go
|
||||
* through collab.bridge.ts instead of importing this class directly.
|
||||
* All consumers are in-container since the trip fold (TripsService and
|
||||
* TripsMcp inject this class); collab.bridge.ts was deleted with its last
|
||||
* outside-container consumers.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CollabService {
|
||||
|
||||
@@ -1,27 +1,26 @@
|
||||
import { db } from '../../db/database';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
import { RealtimeService } from '../realtime/realtime.service';
|
||||
import { DaysService, addDays } from './days.service';
|
||||
import { DaysService } from './days.service';
|
||||
import { PermissionsService } from '../permissions/permissions.service';
|
||||
|
||||
/**
|
||||
* Non-Nest entry point for the day domain — for code running OUTSIDE the
|
||||
* Nest container (the legacy services/tripService.ts and the legacy
|
||||
* transit/transports MCP registrars in src/mcp/tools/; the day MCP tools and
|
||||
* resources moved to the DI-discovered days.mcp.ts, and the plugin RPC host
|
||||
* injects DaysService via PluginHostDepsFactory). Exports only the legacy
|
||||
* services/dayService names still consumed outside the container, 1:1, so
|
||||
* repointing a consumer is an import-path-only diff. Inside the container,
|
||||
* inject DaysService instead. Delete this file when tripService and the
|
||||
* transit/transports registrars migrate.
|
||||
* Nest container (the legacy transit/transports MCP registrars in
|
||||
* src/mcp/tools/; the day MCP tools and resources moved to the DI-discovered
|
||||
* days.mcp.ts, the plugin RPC host injects DaysService via
|
||||
* PluginHostDepsFactory, and the trip domain folded into the DI-native
|
||||
* TripsService, which injects DaysService — its bridge exports were pruned
|
||||
* with it). Exports only the legacy services/dayService names still consumed
|
||||
* outside the container, 1:1, so repointing a consumer is an import-path-only
|
||||
* diff. Inside the container, inject DaysService instead. Delete this file
|
||||
* when the transit/transports registrars migrate.
|
||||
*
|
||||
* Module-level construction is safe: `db` is the reinitialize-proof Proxy onto
|
||||
* the shared better-sqlite3 singleton.
|
||||
*/
|
||||
const days = new DaysService(new DatabaseService(db), new PermissionsService(new DatabaseService(db)), new RealtimeService());
|
||||
|
||||
export { addDays };
|
||||
|
||||
export function getDay(id: string | number, tripId: string | number) {
|
||||
return days.getDay(id, tripId);
|
||||
}
|
||||
@@ -29,22 +28,3 @@ export function getDay(id: string | number, tripId: string | number) {
|
||||
export function listDays(tripId: string | number) {
|
||||
return days.list(tripId);
|
||||
}
|
||||
|
||||
export function listAccommodations(tripId: string | number) {
|
||||
return days.listAccommodations(tripId);
|
||||
}
|
||||
|
||||
export function restampReservationDates(
|
||||
tripId: string | number,
|
||||
oldDateById: Map<number, string | null>,
|
||||
newDateById: Map<number, string | null>,
|
||||
): void {
|
||||
days.restampReservationDates(tripId, oldDateById, newDateById);
|
||||
}
|
||||
|
||||
export function resyncAccommodationDays(
|
||||
tripId: string | number,
|
||||
prevDateByDayId: Map<number, string | null>,
|
||||
): void {
|
||||
days.resyncAccommodationDays(tripId, prevDateByDayId);
|
||||
}
|
||||
|
||||
@@ -36,10 +36,11 @@ DI-only service and the legacy function module was deleted, but the wiring
|
||||
layer didn't inject — so a module-level shim re-exported the legacy names over
|
||||
a hand-built instance. Option A deleted `tags.bridge.ts` and
|
||||
`categories.bridge.ts` (the plugin host was their only consumer).
|
||||
**`todo.bridge.ts` survives** for exactly one remaining consumer that genuinely
|
||||
runs outside the container: the legacy `get_trip_summary` registrar in
|
||||
`src/mcp/tools/trips.ts`. Delete it when that registrar migrates to the
|
||||
DI-discovered MCP registry. New bridges should only be added for genuinely
|
||||
`todo.bridge.ts`, `share.bridge.ts`, `collab.bridge.ts` and `vacay.bridge.ts`
|
||||
followed with the 2026-07 trip fold — their last consumers (the legacy
|
||||
`src/mcp/tools/trips.ts` registrar and the legacy tripService itself) migrated
|
||||
into the DI-discovered `trips.mcp.ts`/`share.mcp.ts` and the DI-native
|
||||
`TripsService`. New bridges should only be added for genuinely
|
||||
non-Nest consumers (legacy MCP registrars, scheduler, websocket) — the plugin
|
||||
host no longer needs them, ever.
|
||||
|
||||
@@ -118,8 +119,12 @@ host's audit trail is the separate `plugin-audit.ts`); exchangeRateService
|
||||
swapped in 2026-07 with the budget-domain fold — its `getRates` import became
|
||||
the injected `ExchangeRatesService`; budgetService swapped in 2026-07 with the
|
||||
budget migration — its `listBudgetItems` import became the already-injected
|
||||
`BudgetService`, so no constructor change at all; the remaining factory
|
||||
imports are all Wave-4+ domains: trips, places, journeys, atlas, collections).
|
||||
`BudgetService`, so no constructor change at all; tripService swapped in
|
||||
2026-07 with the trip fold — its six imported symbols (`listTrips`,
|
||||
`updateTrip`, `createTrip`, `removeMember`, `NotFoundError`, `ValidationError`)
|
||||
became the injected `TripsService`, with the error classes now imported from
|
||||
`nest/trips/trips.service`; the remaining factory
|
||||
imports are all Wave-4+/5 domains: places, journeys, atlas, collections).
|
||||
|
||||
### Test impact (as landed)
|
||||
|
||||
@@ -127,7 +132,10 @@ imports are all Wave-4+ domains: trips, places, journeys, atlas, collections).
|
||||
`PluginRpcHost` directly with hand-built `HostDeps`).
|
||||
- `tests/unit/plugins/plugin-host-deps.factory.test.ts` (was
|
||||
`create-rpc-host.test.ts`) — the six DI-domain path mocks became constructor
|
||||
stubs (sixteen stubs as of the 2026-07 exchange-rates fold); the ~22
|
||||
stubs (sixteen stubs as of the 2026-07 exchange-rates fold, plus `tripsStub`
|
||||
with the 2026-07 trip fold — the tripService path mock's sentinel behaviors
|
||||
moved onto the stub, its throws now built from the real
|
||||
`nest/trips/trips.service` error classes); the ~22
|
||||
legacy-service path mocks shrank by one with the 2026-07 budget migration
|
||||
(the budgetService satisfy-the-import mock died with the legacy file — the
|
||||
`budgetStub` gained `listBudgetItems` for the two swapped closures); a
|
||||
|
||||
@@ -14,7 +14,7 @@ import fsMod from 'node:fs';
|
||||
import pathMod from 'node:path';
|
||||
import { randomUUID } from 'node:crypto';
|
||||
import { PermissionsService } from '../../permissions/permissions.service';
|
||||
import { listTrips, updateTrip, createTrip, removeMember as removeTripMemberSvc, NotFoundError, ValidationError } from '../../../services/tripService';
|
||||
import { TripsService, NotFoundError, ValidationError } from '../../trips/trips.service';
|
||||
import { createPlace, updatePlace, deletePlace } from '../../../services/placeService';
|
||||
import { AddonsService } from '../../addons/addons.service';
|
||||
import { isDemoEmail } from '../../../services/demo';
|
||||
@@ -125,7 +125,7 @@ export interface PluginCallRouter {
|
||||
*
|
||||
* DI-native domain services (budget, exchange-rates, reservations, tags,
|
||||
* categories, todo, packing, day-notes, assignments, oauth, files, collab,
|
||||
* vacay, the LLM config resolver) and
|
||||
* vacay, trips, the LLM config resolver) and
|
||||
* the DatabaseService (all inline SQL + the trip-access helper) are
|
||||
* constructor-injected; legacy `services/*` domains stay plain function
|
||||
* imports until their own migration lands (DI-MIGRATION.md), at which point
|
||||
@@ -153,6 +153,7 @@ export class PluginHostDepsFactory {
|
||||
private readonly exchangeRates: ExchangeRatesService,
|
||||
private readonly addons: AddonsService,
|
||||
private readonly realtime: RealtimeService,
|
||||
private readonly trips: TripsService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -347,7 +348,7 @@ export class PluginHostDepsFactory {
|
||||
// transfer is a separate, deliberate action, not a member-management side effect.
|
||||
const trip = this.db.prepare('SELECT user_id FROM trips WHERE id = ?').get(tripId) as { user_id: number } | undefined;
|
||||
if (trip && trip.user_id === targetUserId) throw new ForbiddenResource('cannot remove the trip owner');
|
||||
removeTripMemberSvc(tripId, targetUserId);
|
||||
this.trips.removeMember(tripId, targetUserId);
|
||||
return { removed: true };
|
||||
},
|
||||
// --- Host-mediated notifications. Recipient resolution + channel fan-out +
|
||||
@@ -431,7 +432,7 @@ export class PluginHostDepsFactory {
|
||||
// Cross-trip: every accessible trip's budget items (membership predicate is
|
||||
// baked into listTrips). Reuses the hydrated list so members/payers come too.
|
||||
listCostsForUser: (userId) => {
|
||||
const trips = listTrips(userId, null) as Array<{ id: number }>;
|
||||
const trips = this.trips.list(userId, null) as Array<{ id: number }>;
|
||||
return trips.flatMap((t) => this.budget.listBudgetItems(t.id));
|
||||
},
|
||||
// Reuses BudgetService.create (frozen FX + members/payers), then broadcasts
|
||||
@@ -528,7 +529,7 @@ export class PluginHostDepsFactory {
|
||||
},
|
||||
createTripForUser: (userId, input) => {
|
||||
try {
|
||||
const result = createTrip(userId, input as unknown as Parameters<typeof createTrip>[1]);
|
||||
const result = this.trips.create(userId, input as unknown as Parameters<TripsService['create']>[1]);
|
||||
return result.trip;
|
||||
} catch (e) {
|
||||
if (e instanceof ValidationError) throw new BadParams(e.message);
|
||||
@@ -552,7 +553,9 @@ export class PluginHostDepsFactory {
|
||||
}
|
||||
const u = this.db.prepare('SELECT role FROM users WHERE id = ?').get(userId) as { role?: string } | undefined;
|
||||
try {
|
||||
const result = updateTrip(tripId, userId, input as Parameters<typeof updateTrip>[2], u?.role ?? 'user');
|
||||
// The no-rebase updateTrip core — parity with the legacy host path,
|
||||
// which never re-anchored the budget currency.
|
||||
const result = this.trips.updateTrip(tripId, userId, input as Parameters<TripsService['updateTrip']>[2], u?.role ?? 'user');
|
||||
this.realtime.broadcast(tripId, 'trip:updated', { trip: result.updatedTrip });
|
||||
return result.updatedTrip;
|
||||
} catch (e) {
|
||||
@@ -564,9 +567,9 @@ export class PluginHostDepsFactory {
|
||||
// --- Cross-trip reads. The membership predicate is baked into listTrips, so a
|
||||
// plugin only ever sees the acting user's own trips/reservations (no raw
|
||||
// cross-tenant SELECT). Reuses the same hydrated services as the REST paths. ---
|
||||
listTripsForUser: (userId) => listTrips(userId, null),
|
||||
listTripsForUser: (userId) => this.trips.list(userId, null),
|
||||
listReservationsForUser: (userId) => {
|
||||
const trips = listTrips(userId, null) as Array<{ id: number }>;
|
||||
const trips = this.trips.list(userId, null) as Array<{ id: number }>;
|
||||
return trips.flatMap((t) => this.reservations.list(String(t.id)));
|
||||
},
|
||||
// --- Trip-scoped hydrated reads (membership already checked by tripRead). Same
|
||||
|
||||
@@ -36,6 +36,7 @@ import { LlmParseModule } from '../llm-parse/llm-parse.module';
|
||||
import { FilesModule } from '../files/files.module';
|
||||
import { CollabModule } from '../collab/collab.module';
|
||||
import { VacayModule } from '../vacay/vacay.module';
|
||||
import { TripsModule } from '../trips/trips.module';
|
||||
import { PermissionsModule } from '../permissions/permissions.module';
|
||||
import { AuditModule } from '../audit/audit.module';
|
||||
import { AddonsModule } from '../addons/addons.module';
|
||||
@@ -50,7 +51,7 @@ import { AddonsModule } from '../addons/addons.module';
|
||||
@Module({
|
||||
// The DI-native domain services the plugin host wiring injects
|
||||
// (PluginHostDepsFactory); DatabaseModule is @Global, so not listed.
|
||||
imports: [TagsModule, CategoriesModule, BudgetModule, ReservationsModule, TodoModule, PackingModule, DaysModule, AssignmentsModule, LlmParseModule, FilesModule, CollabModule, VacayModule, PermissionsModule, AuditModule, AddonsModule],
|
||||
imports: [TagsModule, CategoriesModule, BudgetModule, ReservationsModule, TodoModule, PackingModule, DaysModule, AssignmentsModule, LlmParseModule, FilesModule, CollabModule, VacayModule, TripsModule, PermissionsModule, AuditModule, AddonsModule],
|
||||
controllers: [PluginsController, PluginsFeedController, PluginsProxyController, PluginFrameController, PlaceDetailsController, TripWarningsController, ViewContributionsController, TripCardContributionsController, PluginPhotosController, PluginCalendarController, MapMarkersController, MapLayersController, PluginRoutesController, DayScheduleController, PdfSectionsController, AtlasLayersController, JournalEntryRowsController, PluginUserSettingsController, PluginOAuthController, PluginActivityController],
|
||||
providers: [PluginsService, PluginRuntimeService, PluginRegistryService, PluginOAuthService, PluginHostDepsFactory],
|
||||
// Exported so the admin addon-toggle handler can cascade-disable plugins whose
|
||||
|
||||
@@ -1,28 +0,0 @@
|
||||
import { db } from '../../db/database';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
import { RealtimeService } from '../realtime/realtime.service';
|
||||
import { VacayService } from './vacay.service';
|
||||
|
||||
/**
|
||||
* Non-Nest entry point for the vacay domain — for code running OUTSIDE the
|
||||
* Nest container (the legacy services/tripService.ts, which shifts a trip
|
||||
* owner's vacation entries when the trip window moves; the vacay MCP tools and
|
||||
* resources moved to the DI-discovered vacay.mcp.ts, and the plugin RPC host
|
||||
* injects VacayService via PluginHostDepsFactory). Exports only the legacy
|
||||
* services/vacayService names still consumed outside the container, 1:1, so
|
||||
* repointing a consumer is an import-path-only diff. Inside the container,
|
||||
* inject VacayService instead. Delete this file when tripService migrates.
|
||||
*
|
||||
* Module-level construction is safe: `db` is the reinitialize-proof Proxy onto
|
||||
* the shared better-sqlite3 singleton.
|
||||
*/
|
||||
const vacay = new VacayService(new DatabaseService(db), new RealtimeService());
|
||||
|
||||
export function shiftOwnerEntriesForTripWindow(
|
||||
ownerId: number,
|
||||
oldStart: string,
|
||||
oldEnd: string,
|
||||
newStart: string
|
||||
): void {
|
||||
return vacay.shiftOwnerEntriesForTripWindow(ownerId, oldStart, oldEnd, newStart);
|
||||
}
|
||||
@@ -155,8 +155,9 @@ function windowEndYear(end: string): number {
|
||||
* an AbortSignal timeout and the nager.at responses are ok-checked;
|
||||
* applyHolidayCalendars honors the cache TTL; addYear no longer swallows real
|
||||
* errors; the holiday cache is instance state instead of a module-level map.
|
||||
* The only consumer outside the Nest container is the legacy tripService,
|
||||
* which goes through vacay.bridge.ts instead of importing this class.
|
||||
* All consumers are in-container since the trip fold (TripsService injects
|
||||
* this class); vacay.bridge.ts was deleted with its last outside-container
|
||||
* consumer.
|
||||
*/
|
||||
@Injectable()
|
||||
export class VacayService {
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -18,6 +18,7 @@ import { SettingsService } from '../../src/nest/settings/settings.service';
|
||||
import { FilesService } from '../../src/nest/files/files.service';
|
||||
import { CollabService } from '../../src/nest/collab/collab.service';
|
||||
import { VacayService } from '../../src/nest/vacay/vacay.service';
|
||||
import { TripsService } from '../../src/nest/trips/trips.service';
|
||||
import { PermissionsService } from '../../src/nest/permissions/permissions.service';
|
||||
import { AuditService } from '../../src/nest/audit/audit.service';
|
||||
import { AddonsService } from '../../src/nest/addons/addons.service';
|
||||
@@ -35,26 +36,34 @@ export function createHostDepsFactory(dbs: DatabaseService): PluginHostDepsFacto
|
||||
const realtime = new RealtimeService();
|
||||
const budget = new BudgetService(dbs, permissions, exchangeRates, realtime);
|
||||
const addons = new AddonsService(dbs);
|
||||
const todos = new TodoService(dbs, permissions, realtime);
|
||||
const packing = new PackingService(dbs, permissions, realtime);
|
||||
const files = new FilesService(dbs, permissions, realtime);
|
||||
const reservations = new ReservationsService(dbs, permissions, budget, realtime);
|
||||
const collab = new CollabService(dbs, permissions, realtime);
|
||||
const vacay = new VacayService(dbs, realtime);
|
||||
const days = new DaysService(dbs, permissions, realtime);
|
||||
return new PluginHostDepsFactory(
|
||||
budget,
|
||||
new ReservationsService(dbs, permissions, budget, realtime),
|
||||
reservations,
|
||||
new TagsService(dbs),
|
||||
new CategoriesService(dbs),
|
||||
new TodoService(dbs, permissions, realtime),
|
||||
new PackingService(dbs, permissions, realtime),
|
||||
todos,
|
||||
packing,
|
||||
new PluginOAuthService(dbs),
|
||||
new DayNotesService(dbs, permissions, realtime),
|
||||
new AssignmentsService(dbs, permissions, realtime),
|
||||
new LlmConfigResolver(new SettingsService(dbs), dbs, addons),
|
||||
dbs,
|
||||
new FilesService(dbs, permissions, realtime),
|
||||
new CollabService(dbs, permissions, realtime),
|
||||
new VacayService(dbs, realtime),
|
||||
new DaysService(dbs, permissions, realtime),
|
||||
files,
|
||||
collab,
|
||||
vacay,
|
||||
days,
|
||||
permissions,
|
||||
exchangeRates,
|
||||
addons,
|
||||
realtime,
|
||||
new TripsService(dbs, todos, packing, files, reservations, days, permissions, budget, collab, vacay, realtime),
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* Unit tests for the DI-native CollabService — COLLAB-SVC-001 to COLLAB-SVC-038
|
||||
* (001–030 moved 1:1 from the legacy tests/unit/services/collabService.test.ts;
|
||||
* 031–033 pin the collab.bridge delegation; 034–038 pin the post-migration
|
||||
* 031–033 (collab.bridge delegation) died with the bridge; 034–038 pin the post-migration
|
||||
* hardening: transactional writes, trip-scoped getFormattedNoteById, the
|
||||
* integer vote guard and malformed-URL absorption). Covers votePoll edge
|
||||
* cases, listMessages pagination, deleteMessage ownership, updateNote partial
|
||||
@@ -61,7 +61,6 @@ import { avatarUrl } from '../../../src/services/avatarUrl';
|
||||
import { DatabaseService } from '../../../src/nest/database/database.service';
|
||||
import { PermissionsService } from '../../../src/nest/permissions/permissions.service';
|
||||
import { CollabService } from '../../../src/nest/collab/collab.service';
|
||||
import { listNotes as bridgeListNotes, listPolls as bridgeListPolls, countMessages as bridgeCountMessages } from '../../../src/nest/collab/collab.bridge';
|
||||
import { RealtimeService } from '../../../src/nest/realtime/realtime.service';
|
||||
|
||||
const svc = new CollabService(new DatabaseService(testDb), new PermissionsService(new DatabaseService(testDb)), new RealtimeService());
|
||||
@@ -406,35 +405,9 @@ describe('linkPreview', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ── collab.bridge delegation (out-of-container consumers) ─────────────────────
|
||||
|
||||
describe('collab.bridge', () => {
|
||||
it('COLLAB-SVC-031: listNotes delegates to CollabService over the shared db', () => {
|
||||
const { user1, trip } = setup();
|
||||
svc.createNote(trip.id, user1.id, { title: 'Bridged note' });
|
||||
|
||||
const notes = bridgeListNotes(trip.id);
|
||||
expect(notes).toHaveLength(1);
|
||||
expect(notes[0].title).toBe('Bridged note');
|
||||
});
|
||||
|
||||
it('COLLAB-SVC-032: listPolls delegates to CollabService over the shared db', () => {
|
||||
const { user1, trip } = setup();
|
||||
svc.createPoll(trip.id, user1.id, { question: 'Bridged?', options: ['A', 'B'] });
|
||||
|
||||
const polls = bridgeListPolls(trip.id);
|
||||
expect(polls).toHaveLength(1);
|
||||
expect(polls[0]!.question).toBe('Bridged?');
|
||||
});
|
||||
|
||||
it('COLLAB-SVC-033: countMessages delegates to CollabService over the shared db', () => {
|
||||
const { user1, trip } = setup();
|
||||
svc.createMessage(trip.id, user1.id, 'one');
|
||||
svc.createMessage(trip.id, user1.id, 'two');
|
||||
|
||||
expect(bridgeCountMessages(trip.id)).toBe(2);
|
||||
});
|
||||
});
|
||||
// COLLAB-SVC-031..033 (collab.bridge delegation) were deleted with the bridge —
|
||||
// its last consumers (the legacy tripService and the legacy get_trip_summary
|
||||
// registrar) migrated into the DI-native TripsService/TripsMcp.
|
||||
|
||||
// ── Post-migration hardening (transactions, scoping, guards) ──────────────────
|
||||
|
||||
|
||||
@@ -52,13 +52,7 @@ import { createUser, createTrip, createDay, createPlace, createDayAssignment, cr
|
||||
import { DatabaseService } from '../../../src/nest/database/database.service';
|
||||
import { PermissionsService } from '../../../src/nest/permissions/permissions.service';
|
||||
import { DaysService, addDays } from '../../../src/nest/days/days.service';
|
||||
import {
|
||||
getDay as bridgeGetDay,
|
||||
listDays as bridgeListDays,
|
||||
listAccommodations as bridgeListAccommodations,
|
||||
restampReservationDates as bridgeRestampReservationDates,
|
||||
resyncAccommodationDays as bridgeResyncAccommodationDays,
|
||||
} from '../../../src/nest/days/days.bridge';
|
||||
import { getDay as bridgeGetDay, listDays as bridgeListDays } from '../../../src/nest/days/days.bridge';
|
||||
import { RealtimeService } from '../../../src/nest/realtime/realtime.service';
|
||||
|
||||
const svc = new DaysService(new DatabaseService(testDb), new PermissionsService(new DatabaseService(testDb)), new RealtimeService());
|
||||
@@ -406,6 +400,10 @@ describe('deleteAccommodation', () => {
|
||||
});
|
||||
|
||||
// ── days.bridge delegation (out-of-container consumers) ───────────────────────
|
||||
// The listAccommodations / restampReservationDates / resyncAccommodationDays /
|
||||
// addDays bridge exports were pruned when their last outside-container
|
||||
// consumer (legacy tripService) folded into the DI-native TripsService —
|
||||
// 029/031/032 pin the same behavior on the service.
|
||||
|
||||
describe('days.bridge', () => {
|
||||
it('DAY-SVC-027 — getDay delegates to DaysService.getDay', () => {
|
||||
@@ -425,13 +423,13 @@ describe('days.bridge', () => {
|
||||
expect(Array.isArray(result.days[0].assignments)).toBe(true);
|
||||
});
|
||||
|
||||
it('DAY-SVC-029 — listAccommodations delegates to DaysService.listAccommodations', () => {
|
||||
it('DAY-SVC-029 — listAccommodations returns the hydrated stays', () => {
|
||||
const { user } = createUser(testDb);
|
||||
const trip = createTrip(testDb, user.id);
|
||||
const day = createDay(testDb, trip.id);
|
||||
const place = createPlace(testDb, trip.id, { name: 'Ryokan' });
|
||||
createDayAccommodation(testDb, trip.id, place.id, day.id, day.id);
|
||||
const rows = bridgeListAccommodations(trip.id) as { place_name: string }[];
|
||||
const rows = svc.listAccommodations(trip.id) as { place_name: string }[];
|
||||
expect(rows).toHaveLength(1);
|
||||
expect(rows[0].place_name).toBe('Ryokan');
|
||||
});
|
||||
@@ -450,7 +448,7 @@ describe('days.bridge', () => {
|
||||
'INSERT INTO reservations (trip_id, day_id, title, reservation_time) VALUES (?, ?, ?, ?)'
|
||||
).run(trip.id, day.id, 'Dinner', '2026-01-01T19:00');
|
||||
|
||||
bridgeRestampReservationDates(
|
||||
svc.restampReservationDates(
|
||||
trip.id,
|
||||
new Map([[day.id, '2026-01-01']]),
|
||||
new Map([[day.id, '2026-01-05']]),
|
||||
@@ -475,7 +473,7 @@ describe('days.bridge', () => {
|
||||
testDb.prepare('UPDATE days SET date = ? WHERE id = ?').run('2026-01-01', d2.id);
|
||||
testDb.prepare('UPDATE days SET day_number = 0 WHERE id = ?').run(d2.id);
|
||||
|
||||
bridgeResyncAccommodationDays(trip.id, new Map([[d1.id, '2026-01-01'], [d2.id, '2026-01-02']]));
|
||||
svc.resyncAccommodationDays(trip.id, new Map([[d1.id, '2026-01-01'], [d2.id, '2026-01-02']]));
|
||||
|
||||
const row = testDb.prepare('SELECT start_day_id, end_day_id FROM day_accommodations WHERE id = ?').get(accom.id) as { start_day_id: number; end_day_id: number };
|
||||
expect(row.start_day_id).toBe(d2.id);
|
||||
|
||||
@@ -35,14 +35,14 @@ import { createUser } from '../../helpers/factories';
|
||||
|
||||
import { DatabaseService } from '../../../src/nest/database/database.service';
|
||||
import { VacayService } from '../../../src/nest/vacay/vacay.service';
|
||||
import { shiftOwnerEntriesForTripWindow } from '../../../src/nest/vacay/vacay.bridge';
|
||||
import { RealtimeService } from '../../../src/nest/realtime/realtime.service';
|
||||
|
||||
// VACAY-SVC-001 through VACAY-SVC-066 moved 1:1 from the legacy
|
||||
// tests/unit/services/vacayService.test.ts (the named-function imports became
|
||||
// method calls on a directly constructed VacayService; the legacy
|
||||
// updateUserYearSettings is the class's updateYearSettings). VACAY-SVC-067
|
||||
// pins the vacay.bridge delegation for the one outside-container consumer.
|
||||
// updateUserYearSettings is the class's updateYearSettings).
|
||||
// VACAY-SVC-067 (vacay.bridge delegation) died with the bridge — its only
|
||||
// consumer, the legacy tripService, folded into the DI-native TripsService.
|
||||
const svc = new VacayService(new DatabaseService(testDb), new RealtimeService());
|
||||
|
||||
// ── Lifecycle ─────────────────────────────────────────────────────────────────
|
||||
@@ -1367,20 +1367,6 @@ describe('getSharedCalendars', () => {
|
||||
});
|
||||
});
|
||||
|
||||
// ── vacay.bridge delegation ───────────────────────────────────────────────────
|
||||
|
||||
describe('vacay.bridge', () => {
|
||||
it('VACAY-SVC-067: shiftOwnerEntriesForTripWindow shifts the owner entries through the bridge', () => {
|
||||
const { user, plan } = setupUserWithPlan();
|
||||
svc.toggleEntry(user.id, plan.id, '2025-05-10', 1);
|
||||
|
||||
shiftOwnerEntriesForTripWindow(user.id, '2025-05-08', '2025-05-12', '2025-05-15');
|
||||
|
||||
const dates = testDb.prepare('SELECT date FROM vacay_entries WHERE plan_id = ? ORDER BY date').all(plan.id);
|
||||
expect(dates).toEqual([{ date: '2025-05-17' }]);
|
||||
});
|
||||
});
|
||||
|
||||
// ── Quirk fixes (transactions, fetch hygiene, cache TTL, addYear errors) ──────
|
||||
|
||||
describe('quirk fixes', () => {
|
||||
|
||||
@@ -66,27 +66,10 @@ const budgetStub = {
|
||||
const checkPermission = vi.fn((..._a: any[]) => true as boolean);
|
||||
const permissionsStub = { checkPermission } as unknown as PermissionsService;
|
||||
|
||||
// The core write services are delegated to; mock them so the host deps'
|
||||
// wiring + error branches run without the full core schema. The error classes must
|
||||
// be defined INSIDE the factory (vi.mock is hoisted above module-scope code).
|
||||
vi.mock('../../../src/services/tripService', () => {
|
||||
class NotFoundError extends Error {}
|
||||
class ValidationError extends Error {}
|
||||
return {
|
||||
updateTrip: (tripId: number, _u: number, input: Record<string, unknown>) => {
|
||||
if (input.title === 'boom') throw new ValidationError('bad dates');
|
||||
if (input.title === 'gone') throw new NotFoundError('no trip');
|
||||
if (input.title === 'crash') throw new Error('unexpected');
|
||||
return { updatedTrip: { id: tripId, ...input } };
|
||||
},
|
||||
createTrip: (userId: number, input: Record<string, unknown>) => {
|
||||
if (input.title === 'boom') throw new ValidationError('bad dates');
|
||||
return { trip: { id: 99, user_id: userId, ...input }, tripId: 99, reminderDays: 3 };
|
||||
},
|
||||
listTrips: () => [{ id: 1 }],
|
||||
NotFoundError, ValidationError,
|
||||
};
|
||||
});
|
||||
// Trips are a constructor-injected stub since the trip fold (same behaviors as
|
||||
// the old services/tripService path mock). The throws use the REAL error
|
||||
// classes from trips.service so the factory's instanceof mapping keeps
|
||||
// matching — see the imports below the mocks.
|
||||
// Exchange rates are a constructor-injected stub since the budget-domain fold
|
||||
// (was a path mock of the deleted services/exchangeRateService — same behavior).
|
||||
const exchangeRatesStub = {
|
||||
@@ -96,6 +79,9 @@ vi.mock('../../../src/services/placeService', () => ({
|
||||
createPlace: vi.fn((tid: string, body: Record<string, unknown>) => ({ id: 10, trip_id: Number(tid), ...body })),
|
||||
updatePlace: vi.fn((_tid: string, pid: string) => (pid === '99' ? null : { id: Number(pid) })),
|
||||
deletePlace: vi.fn((_tid: string, pid: string) => pid !== '99'),
|
||||
// trips.service (loaded for real since the trip fold) imports listPlaces for
|
||||
// its offline bundle — the mock must carry every named export it pulls.
|
||||
listPlaces: vi.fn(() => []),
|
||||
}));
|
||||
// Days are a constructor-injected stub (same behaviors as the old path mock,
|
||||
// mapped onto the DaysService method names).
|
||||
@@ -320,12 +306,27 @@ import type { VacayService } from '../../../src/nest/vacay/vacay.service';
|
||||
import type { PermissionsService } from '../../../src/nest/permissions/permissions.service';
|
||||
import { DatabaseService } from '../../../src/nest/database/database.service';
|
||||
import { RealtimeService } from '../../../src/nest/realtime/realtime.service';
|
||||
import { NotFoundError, ValidationError } from '../../../src/nest/trips/trips.service';
|
||||
|
||||
// The factory under test, wired exactly like PluginsModule does — but with the
|
||||
// DI-native domain services replaced by the stubs above. The shim keeps the
|
||||
// ~45 historical call sites unchanged and supplies a default no-op router.
|
||||
const addonsStub = { isAddonEnabled } as unknown as import('../../../src/nest/addons/addons.service').AddonsService;
|
||||
const factory = new PluginHostDepsFactory(budgetStub, reservationsStub, tagsStub, categoriesStub, todoStub, packingStub, oauthStub, dayNotesStub, assignmentsStub, llmConfigStub, new DatabaseService(mockDb), filesStub, collabStub, vacayStub, daysStub, permissionsStub, exchangeRatesStub, addonsStub, new RealtimeService());
|
||||
const tripsStub = {
|
||||
updateTrip: (tripId: number, _u: number, input: Record<string, unknown>) => {
|
||||
if (input.title === 'boom') throw new ValidationError('bad dates');
|
||||
if (input.title === 'gone') throw new NotFoundError('no trip');
|
||||
if (input.title === 'crash') throw new Error('unexpected');
|
||||
return { updatedTrip: { id: tripId, ...input } };
|
||||
},
|
||||
create: (userId: number, input: Record<string, unknown>) => {
|
||||
if (input.title === 'boom') throw new ValidationError('bad dates');
|
||||
return { trip: { id: 99, user_id: userId, ...input }, tripId: 99, reminderDays: 3 };
|
||||
},
|
||||
list: () => [{ id: 1 }],
|
||||
removeMember: vi.fn(),
|
||||
} as unknown as import('../../../src/nest/trips/trips.service').TripsService;
|
||||
const factory = new PluginHostDepsFactory(budgetStub, reservationsStub, tagsStub, categoriesStub, todoStub, packingStub, oauthStub, dayNotesStub, assignmentsStub, llmConfigStub, new DatabaseService(mockDb), filesStub, collabStub, vacayStub, daysStub, permissionsStub, exchangeRatesStub, addonsStub, new RealtimeService(), tripsStub);
|
||||
const stubRouter: PluginCallRouter = { callPlugin: async () => undefined, emitPluginEvent: () => {} };
|
||||
const createRealRpcHost = (id: string, granted: ReadonlySet<string>, router: PluginCallRouter = stubRouter) => factory.create(id, granted, router);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user