mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-24 16:03:02 +00:00
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.
This commit is contained in:
@@ -5,7 +5,7 @@
|
||||
* host, and trip broadcasts are force-namespaced to plugin:{id}:{event}.
|
||||
* DI-native domains (budget/exchange-rates/reservations/tags/categories/todo/
|
||||
* packing/day-notes/days/assignments/oauth/llm-config/files/collab/vacay/
|
||||
* permissions/trips/places/collections)
|
||||
* permissions/trips/places/collections/atlas)
|
||||
* are constructor-injected stubs; legacy services/* domains stay path-mocked
|
||||
* until their own DI migration lands.
|
||||
*/
|
||||
@@ -256,17 +256,19 @@ vi.mock('../../../src/services/journeyService', () => ({
|
||||
updateEntry: vi.fn((entryId: number, _uid: number, data: unknown) => (entryId === 99 ? null : { id: entryId, ...(data as object) })),
|
||||
deleteEntry: vi.fn((entryId: number) => entryId !== 99),
|
||||
}));
|
||||
vi.mock('../../../src/services/atlasService', () => ({
|
||||
// Atlas is a constructor-injected stub since the atlas fold (same behaviors as
|
||||
// the old services/atlasService path mock, keyed by the service method names).
|
||||
const atlasStub = {
|
||||
listVisitedCountries: vi.fn(() => [{ country_code: 'JP' }]),
|
||||
listManuallyVisitedRegions: vi.fn(() => [{ region_code: 'JP-13' }]),
|
||||
listBucketList: vi.fn((uid: number) => [{ id: 5, user_id: uid, name: 'Kyoto' }]),
|
||||
markCountryVisited: vi.fn(),
|
||||
unmarkCountryVisited: vi.fn(),
|
||||
markRegionVisited: vi.fn(),
|
||||
unmarkRegionVisited: vi.fn(),
|
||||
bucketList: vi.fn((uid: number) => [{ id: 5, user_id: uid, name: 'Kyoto' }]),
|
||||
markCountry: vi.fn(),
|
||||
unmarkCountry: vi.fn(),
|
||||
markRegion: vi.fn(),
|
||||
unmarkRegion: vi.fn(),
|
||||
createBucketItem: vi.fn((uid: number, data: { name: string }) => ({ id: 110, user_id: uid, name: data.name })),
|
||||
deleteBucketItem: vi.fn((_uid: number, itemId: number) => Number(itemId) !== 404),
|
||||
}));
|
||||
} as unknown as AtlasService;
|
||||
// Collections is a constructor-injected stub since the collections fold (same
|
||||
// behaviors as the old services/collectionsService path mock — the service
|
||||
// throws status-tagged errors the factory maps onto the RPC error classes).
|
||||
@@ -321,6 +323,7 @@ import type { VacayService } from '../../../src/nest/vacay/vacay.service';
|
||||
import type { PermissionsService } from '../../../src/nest/permissions/permissions.service';
|
||||
import type { PlacesService } from '../../../src/nest/places/places.service';
|
||||
import type { CollectionsService } from '../../../src/nest/collections/collections.service';
|
||||
import type { AtlasService } from '../../../src/nest/atlas/atlas.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';
|
||||
@@ -343,7 +346,7 @@ const tripsStub = {
|
||||
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, placesStub, collectionsStub);
|
||||
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, placesStub, collectionsStub, atlasStub);
|
||||
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