mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-21 06:26:26 +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:
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user