From 806f3c8e62bb9af68908229e21e613fe7ba05815 Mon Sep 17 00:00:00 2001 From: Maurice Date: Sat, 10 Oct 2026 12:36:06 +0200 Subject: [PATCH] refactor(addons): the transit-provider setting and photo-provider routes move into the shared kernel AddonsService read the transit backend through transit/transit-provider.ts and built photo-provider endpoint paths with memories.helpers, so the addons domain, which nearly every domain imports for its gates, depended on transit and memories. Both are free functions over a repository or plain strings; they move unchanged to common/, where the transit and admin sides read them too. --- .../scripts/import-boundaries-baseline.json | 26 ----------- server/src/nest/addons/addons.service.ts | 6 +-- server/src/nest/admin/admin.service.ts | 2 +- .../src/nest/common/photo-provider-config.ts | 21 +++++++++ .../{transit => common}/transit-provider.ts | 0 server/src/nest/memories/memories.helpers.ts | 25 ----------- .../nest/transit/google-transit.provider.ts | 2 +- server/tests/e2e/addons.e2e.test.ts | 2 +- server/tests/unit/nest/addons.service.test.ts | 2 +- .../common/kernel-settings-helpers.test.ts | 43 +++++++++++++++++++ 10 files changed, 71 insertions(+), 58 deletions(-) create mode 100644 server/src/nest/common/photo-provider-config.ts rename server/src/nest/{transit => common}/transit-provider.ts (100%) create mode 100644 server/tests/unit/nest/common/kernel-settings-helpers.test.ts diff --git a/server/scripts/import-boundaries-baseline.json b/server/scripts/import-boundaries-baseline.json index a2d580d42..94d14b60c 100644 --- a/server/scripts/import-boundaries-baseline.json +++ b/server/scripts/import-boundaries-baseline.json @@ -3,44 +3,21 @@ "domainCycles": [ "accommodations -> assignments", "accommodations -> places", - "addons -> memories", - "addons -> transit", "assignments -> days", - "assignments -> journey", - "atlas -> addons", "auth -> plugins", - "budget -> addons", "days -> accommodations", "days -> assignments", "days -> places", "geo -> maps", - "integrations -> addons", - "integrations -> assignments", - "integrations -> atlas", - "integrations -> journey", - "integrations -> places", "integrations -> reservations", - "journey -> addons", - "journey -> atlas", - "journey -> memories", - "journey -> photos", "maps -> geo", - "memories -> addons", "memories -> photos", "photos -> memories", "places -> accommodations", "places -> assignments", - "places -> atlas", - "places -> journey", "plugins -> trips", "plugins -> vacay", - "reservations -> accommodations", - "reservations -> assignments", - "reservations -> budget", - "reservations -> days", "reservations -> integrations", - "transit -> days", - "transit -> reservations", "trip-members -> auth", "trip-members -> trips", "trip-read-model -> trip-members", @@ -57,11 +34,8 @@ "nest/common/validate-route-guards.ts -> nest/public-api/api-token.guard.ts" ], "domainInternals": [ - "addons -> memories/memories.helpers.ts", "addons -> settings/instance-api-keys.ts", - "addons -> transit/transit-provider.ts", "admin -> llm-parse/llm-config.ts", - "admin -> memories/memories.helpers.ts", "atlas -> geo/nominatim.client.ts", "atlas -> public-api/public-api-request.ts", "auth -> maps/maps.helpers.ts", diff --git a/server/src/nest/addons/addons.service.ts b/server/src/nest/addons/addons.service.ts index 6f708ff46..896a13b60 100644 --- a/server/src/nest/addons/addons.service.ts +++ b/server/src/nest/addons/addons.service.ts @@ -1,8 +1,8 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@mikro-orm/nestjs'; import { ADDON_IDS } from '../../addons'; -import { getPhotoProviderConfig } from '../memories/memories.helpers'; -import { readTransitProvider, writeTransitProvider } from '../transit/transit-provider'; +import { getPhotoProviderConfig } from '../common/photo-provider-config'; +import { readTransitProvider, writeTransitProvider } from '../common/transit-provider'; import { resolveApiKey, type ApiKeySource } from '../settings/instance-api-keys'; import { readEnv } from '../../app-config'; import type { TransitProvider } from '@trek/shared'; @@ -233,7 +233,7 @@ export class AddonsService { // ── Transit backend (#1699) ──────────────────────────────────────────────── // Not a flag: two named backends, so it stores the name rather than a - // boolean. The read/write pair lives in transit/transit-provider.ts because + // boolean. The read/write pair lives in common/transit-provider.ts because // TransitService reads the same row on every request — one key, one reader, // one writer. Neither that module nor instance-api-keys.ts (below) is one of // this plan's six domains, so they stay on DatabaseService until their own diff --git a/server/src/nest/admin/admin.service.ts b/server/src/nest/admin/admin.service.ts index 8e2a3b304..47400793d 100644 --- a/server/src/nest/admin/admin.service.ts +++ b/server/src/nest/admin/admin.service.ts @@ -18,7 +18,7 @@ import { maybe_encrypt_api_key, decrypt_api_key } from '../common/crypto/apiKeyC import { avatarUrl } from '../common/avatarUrl'; import { DATABASE_BACKUP, type DatabaseBackupStrategy } from '../database/database-backup.interface'; import { prepareLlmAddonConfigForWrite, maskLlmAddonConfig } from '../llm-parse/llm-config'; -import { getPhotoProviderConfig } from '../memories/memories.helpers'; +import { getPhotoProviderConfig } from '../common/photo-provider-config'; import { validatePassword } from '../common/passwordPolicy'; import { UserCleanupService } from '../auth/user-cleanup.service'; import { UnitOfWork } from '../database/unit-of-work'; diff --git a/server/src/nest/common/photo-provider-config.ts b/server/src/nest/common/photo-provider-config.ts new file mode 100644 index 000000000..5a0e2162b --- /dev/null +++ b/server/src/nest/common/photo-provider-config.ts @@ -0,0 +1,21 @@ +/** + * Where the client finds a provider's settings/status/test endpoints. Pure + * string building in the shared kernel: the admin and addons surfaces both + * read it, and neither should have to import the memories domain to do so. + */ +export interface PhotoProviderConfig { + settings_get: string; + settings_put: string; + status_get: string; + test_post: string; +} + +export function getPhotoProviderConfig(providerId: string): PhotoProviderConfig { + const prefix = `/integrations/memories/${providerId}`; + return { + settings_get: `${prefix}/settings`, + settings_put: `${prefix}/settings`, + status_get: `${prefix}/status`, + test_post: `${prefix}/test`, + }; +} diff --git a/server/src/nest/transit/transit-provider.ts b/server/src/nest/common/transit-provider.ts similarity index 100% rename from server/src/nest/transit/transit-provider.ts rename to server/src/nest/common/transit-provider.ts diff --git a/server/src/nest/memories/memories.helpers.ts b/server/src/nest/memories/memories.helpers.ts index 7da518d51..d930663a9 100644 --- a/server/src/nest/memories/memories.helpers.ts +++ b/server/src/nest/memories/memories.helpers.ts @@ -360,28 +360,3 @@ export async function pipeAsset(url: string, response: Response, headers?: Recor } } } - -// ── Route shape for the settings page ───────────────────────────────────── - -/** - * Where the client finds a provider's settings/status/test endpoints. Pure - * string building, so it stays here rather than on a service — the admin and - * addons surfaces both read it and neither should have to import the memories - * domain to do so. - */ -export interface PhotoProviderConfig { - settings_get: string; - settings_put: string; - status_get: string; - test_post: string; -} - -export function getPhotoProviderConfig(providerId: string): PhotoProviderConfig { - const prefix = `/integrations/memories/${providerId}`; - return { - settings_get: `${prefix}/settings`, - settings_put: `${prefix}/settings`, - status_get: `${prefix}/status`, - test_post: `${prefix}/test`, - }; -} diff --git a/server/src/nest/transit/google-transit.provider.ts b/server/src/nest/transit/google-transit.provider.ts index 47ef3d0fc..52757f316 100644 --- a/server/src/nest/transit/google-transit.provider.ts +++ b/server/src/nest/transit/google-transit.provider.ts @@ -8,7 +8,7 @@ import type { AppSettingsRepository } from '../../db/repositories/AppSettings.re import { Users } from '../../db/entities/Users.entity'; import type { UsersRepository } from '../../db/repositories/Users.repository'; import { GoogleQuotaService } from '../google-quota/google-quota.service'; -import { readTransitProvider } from './transit-provider'; +import { readTransitProvider } from '../common/transit-provider'; import { decodePolyline, deriveTransitStats, diff --git a/server/tests/e2e/addons.e2e.test.ts b/server/tests/e2e/addons.e2e.test.ts index 0823f6691..f1136561a 100644 --- a/server/tests/e2e/addons.e2e.test.ts +++ b/server/tests/e2e/addons.e2e.test.ts @@ -35,7 +35,7 @@ db.exec('DELETE FROM app_settings'); const { getPhotoProviderConfig } = vi.hoisted(() => ({ getPhotoProviderConfig: vi.fn(() => ({ url: 'https://immich.example' })), })); -vi.mock('../../src/nest/memories/memories.helpers', () => ({ getPhotoProviderConfig })); +vi.mock('../../src/nest/common/photo-provider-config', () => ({ getPhotoProviderConfig })); import { AddonsModule } from '../../src/nest/addons/addons.module'; import { TrekExceptionFilter } from '../../src/nest/common/trek-exception.filter'; diff --git a/server/tests/unit/nest/addons.service.test.ts b/server/tests/unit/nest/addons.service.test.ts index c8a950a6e..1edfbac5a 100644 --- a/server/tests/unit/nest/addons.service.test.ts +++ b/server/tests/unit/nest/addons.service.test.ts @@ -43,7 +43,7 @@ import { PlaceShadowPicks } from '../../../src/db/entities/PlaceShadowPicks.enti import type { UsersRepository } from '../../../src/db/repositories/Users.repository'; const { getPhotoProviderConfig } = vi.hoisted(() => ({ getPhotoProviderConfig: vi.fn(() => ({})) })); -vi.mock('../../../src/nest/memories/memories.helpers', () => ({ getPhotoProviderConfig })); +vi.mock('../../../src/nest/common/photo-provider-config', () => ({ getPhotoProviderConfig })); const testDb = createSnapshotTestDb(); let t: TestOrm; diff --git a/server/tests/unit/nest/common/kernel-settings-helpers.test.ts b/server/tests/unit/nest/common/kernel-settings-helpers.test.ts new file mode 100644 index 000000000..f0a841bc3 --- /dev/null +++ b/server/tests/unit/nest/common/kernel-settings-helpers.test.ts @@ -0,0 +1,43 @@ +import { describe, expect, it, vi } from 'vitest'; +import type { AppSettingsRepository } from '../../../../src/db/repositories/AppSettings.repository'; +import { + DEFAULT_TRANSIT_PROVIDER, + readTransitProvider, + TRANSIT_PROVIDER_SETTING, + writeTransitProvider, +} from '../../../../src/nest/common/transit-provider'; +import { getPhotoProviderConfig } from '../../../../src/nest/common/photo-provider-config'; + +function settings(value: string | null) { + return { getValue: vi.fn(async () => value), setValue: vi.fn(async () => undefined) }; +} + +describe('transit provider setting', () => { + it('reads a known backend as stored', async () => { + const repo = settings('google'); + expect(await readTransitProvider(repo as unknown as AppSettingsRepository)).toBe('google'); + expect(repo.getValue).toHaveBeenCalledWith(TRANSIT_PROVIDER_SETTING); + }); + + it('falls back to Transitous for a missing row or a name this version does not know', async () => { + expect(await readTransitProvider(settings(null) as unknown as AppSettingsRepository)).toBe(DEFAULT_TRANSIT_PROVIDER); + expect(await readTransitProvider(settings('rail-x') as unknown as AppSettingsRepository)).toBe(DEFAULT_TRANSIT_PROVIDER); + }); + + it('writes the name under its one key and hands it back', async () => { + const repo = settings(null); + expect(await writeTransitProvider(repo as unknown as AppSettingsRepository, 'google')).toBe('google'); + expect(repo.setValue).toHaveBeenCalledWith(TRANSIT_PROVIDER_SETTING, 'google'); + }); +}); + +describe('photo provider config', () => { + it('points every endpoint under the provider prefix', () => { + expect(getPhotoProviderConfig('immich')).toEqual({ + settings_get: '/integrations/memories/immich/settings', + settings_put: '/integrations/memories/immich/settings', + status_get: '/integrations/memories/immich/status', + test_post: '/integrations/memories/immich/test', + }); + }); +});