mirror of
https://github.com/mauriceboe/TREK.git
synced 2026-10-22 06:56:35 +00:00
feat(admin): photo providers require the journey addon
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
import type { Addon } from '../../types';
|
||||
import { getPhotoProviderConfig } from '../memories/memories.helpers';
|
||||
@@ -85,14 +86,21 @@ export class AddonsService {
|
||||
const addons = this.db
|
||||
.prepare('SELECT id, name, type, icon, enabled FROM addons WHERE enabled = 1 ORDER BY sort_order')
|
||||
.all() as Pick<Addon, 'id' | 'name' | 'type' | 'icon' | 'enabled'>[];
|
||||
const providers = this.db
|
||||
.prepare(
|
||||
`SELECT id, name, icon, enabled, sort_order
|
||||
FROM photo_providers
|
||||
WHERE enabled = 1
|
||||
ORDER BY sort_order, id`,
|
||||
)
|
||||
.all() as Array<{ id: string; name: string; icon: string; enabled: number; sort_order: number }>;
|
||||
// Photo providers surface only inside journeys, so with the journey addon
|
||||
// off they are unavailable no matter what their own rows say. Deriving that
|
||||
// here (instead of a migration) also covers installs that still hold an
|
||||
// enabled provider under a disabled journey from before updateAddon
|
||||
// cascaded the disable.
|
||||
const providers = !this.isAddonEnabled(ADDON_IDS.JOURNEY)
|
||||
? []
|
||||
: (this.db
|
||||
.prepare(
|
||||
`SELECT id, name, icon, enabled, sort_order
|
||||
FROM photo_providers
|
||||
WHERE enabled = 1
|
||||
ORDER BY sort_order, id`,
|
||||
)
|
||||
.all() as Array<{ id: string; name: string; icon: string; enabled: number; sort_order: number }>);
|
||||
const fields = this.db
|
||||
.prepare(
|
||||
`SELECT provider_id, field_key, label, input_type, placeholder, hint, required, secret, settings_key, payload_key, sort_order
|
||||
|
||||
@@ -643,10 +643,22 @@ export class AdminService {
|
||||
return { error: MANAGED_FORBIDDEN_ERROR.error, status: 403 };
|
||||
}
|
||||
|
||||
// Photo providers are Journey's shelf rows — their whole UI lives inside
|
||||
// journeys, so enabling one under a disabled journey addon would only
|
||||
// advertise an integration nothing can reach.
|
||||
if (provider && data.enabled === true && !this.addons.isAddonEnabled(ADDON_IDS.JOURNEY)) {
|
||||
return { error: 'Enable the Journey addon first', status: 409 };
|
||||
}
|
||||
|
||||
this.db.transaction(() => {
|
||||
if (addon) {
|
||||
if (data.enabled !== undefined)
|
||||
if (data.enabled !== undefined) {
|
||||
this.db.run('UPDATE addons SET enabled = ? WHERE id = ?', data.enabled ? 1 : 0, id);
|
||||
// Journey off takes its providers with it: a row left enabled would
|
||||
// resurface the moment journey returns, which nobody switched on.
|
||||
if (id === ADDON_IDS.JOURNEY && !data.enabled)
|
||||
this.db.run('UPDATE photo_providers SET enabled = 0');
|
||||
}
|
||||
if (data.config !== undefined) {
|
||||
// The AI-parsing addon holds an API key — encrypt it at rest and preserve
|
||||
// the stored key when the client echoes the mask sentinel (see llmConfig.ts).
|
||||
|
||||
@@ -3,6 +3,8 @@ import {
|
||||
errorResult, ok, type McpContext,
|
||||
} from '../../nest-mcp';
|
||||
import { z } from 'zod';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
import { AddonsService } from '../addons/addons.service';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
import { ImmichService } from './immich.service';
|
||||
import { SynologyService } from './synology.service';
|
||||
@@ -31,7 +33,9 @@ const SYNOLOGY_DEFAULT_LIMIT = 100;
|
||||
/**
|
||||
* Availability gate. With every `photo_providers` row switched off the admin has
|
||||
* turned the whole integration off, and the settings page stops offering it at
|
||||
* all: nothing here has anything left to browse. Which of the two providers is
|
||||
* all: nothing here has anything left to browse. The journey addon rides along
|
||||
* because the providers exist to feed journey entries — journey off means no
|
||||
* provider counts as on, whatever its row says. Which of the two providers is
|
||||
* on is a per-call question, answered by providerRefusal().
|
||||
*/
|
||||
const anyPhotoProviderEnabled = (_ctx: McpContext, self: MemoriesMcp): boolean =>
|
||||
@@ -62,6 +66,7 @@ export class MemoriesMcp {
|
||||
private readonly immich: ImmichService,
|
||||
private readonly synology: SynologyService,
|
||||
private readonly db: DatabaseService,
|
||||
private readonly addons: AddonsService,
|
||||
) {}
|
||||
|
||||
/**
|
||||
@@ -69,6 +74,7 @@ export class MemoriesMcp {
|
||||
* a method, the same reason the addon gates need a public `addons`.
|
||||
*/
|
||||
enabledProviderIds(): string[] {
|
||||
if (!this.addons.isAddonEnabled(ADDON_IDS.JOURNEY)) return [];
|
||||
return this.db.all<{ id: string }>('SELECT id FROM photo_providers WHERE enabled = 1').map((row) => row.id);
|
||||
}
|
||||
|
||||
@@ -81,7 +87,8 @@ export class MemoriesMcp {
|
||||
private providerRefusal(provider: ProviderId) {
|
||||
const row = this.db.get<{ enabled: number }>('SELECT enabled FROM photo_providers WHERE id = ?', provider);
|
||||
if (!row) return errorResult(`Provider: "${provider}" is not supported`);
|
||||
if (row.enabled !== 1) return errorResult(`Provider: "${provider}" is not enabled, contact server administrator`);
|
||||
if (row.enabled !== 1 || !this.addons.isAddonEnabled(ADDON_IDS.JOURNEY))
|
||||
return errorResult(`Provider: "${provider}" is not enabled, contact server administrator`);
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
import { ADDON_IDS } from '../../addons';
|
||||
import { broadcast } from '../../websocket';
|
||||
import { encrypt_api_key } from '../common/crypto/apiKeyCrypto';
|
||||
import { AddonsService } from '../addons/addons.service';
|
||||
import { DatabaseService } from '../database/database.service';
|
||||
import { TrekPhotosRepository } from '../photos/trek-photos.repository';
|
||||
import { ImmichService } from './immich.service';
|
||||
@@ -31,12 +33,17 @@ export class UnifiedMemoriesService {
|
||||
private readonly synology: SynologyService,
|
||||
private readonly access: MemoriesAccessService,
|
||||
private readonly notifications: NotificationsService,
|
||||
private readonly addons: AddonsService,
|
||||
) {}
|
||||
|
||||
private _providers(): Array<{id: string; enabled: boolean}> {
|
||||
// A provider only counts as enabled while the journey addon is — its whole
|
||||
// surface lives inside journeys. Covers rows left enabled from before
|
||||
// updateAddon cascaded the journey disable.
|
||||
const journeyOn = this.addons.isAddonEnabled(ADDON_IDS.JOURNEY);
|
||||
const rows = this.db.prepare('SELECT id, enabled FROM photo_providers').all() as Array<{id: string; enabled: number}>;
|
||||
return rows.map(r => ({ id: r.id, enabled: r.enabled === 1 }));
|
||||
}
|
||||
return rows.map(r => ({ id: r.id, enabled: journeyOn && r.enabled === 1 }));
|
||||
}
|
||||
|
||||
private _validProvider(provider: string): ServiceResult<string> {
|
||||
const providers = this._providers();
|
||||
|
||||
Reference in New Issue
Block a user