refactor(api): extract settings response support

This commit is contained in:
2026-03-31 14:30:57 +02:00
parent 85b4121253
commit 5be1ef15dd
3 changed files with 206 additions and 45 deletions
@@ -0,0 +1,122 @@
import { describe, expect, it } from "vitest";
import {
buildSettingsUpdatePayload,
buildSystemSettingsViewModel,
getDefaultScoreWeights,
sanitizeSettingsAuditSnapshot,
} from "../router/settings-support.js";
describe("settings support", () => {
it("builds the admin-facing system settings view model with defaults", () => {
const runtimeSecrets = {
azureOpenAiApiKey: {
configured: true,
activeSource: "environment" as const,
hasStoredValue: true,
envVarNames: ["OPENAI_API_KEY"],
},
azureDalleApiKey: {
configured: false,
activeSource: "none" as const,
hasStoredValue: false,
envVarNames: ["AZURE_DALLE_API_KEY"],
},
geminiApiKey: {
configured: true,
activeSource: "database" as const,
hasStoredValue: true,
envVarNames: ["GEMINI_API_KEY"],
},
smtpPassword: {
configured: false,
activeSource: "none" as const,
hasStoredValue: false,
envVarNames: ["SMTP_PASSWORD"],
},
anonymizationSeed: {
configured: false,
activeSource: "none" as const,
hasStoredValue: false,
envVarNames: ["ANONYMIZATION_SEED"],
},
};
expect(buildSystemSettingsViewModel({
settings: {
aiProvider: "azure",
azureOpenAiEndpoint: "https://example.openai.azure.com",
scoreVisibleRoles: ["ADMIN"],
smtpTls: false,
vacationDefaultDays: 30,
},
runtimeSettings: {
azureOpenAiApiKey: "secret",
smtpPassword: null,
azureDalleApiKey: null,
geminiApiKey: "gemini-secret",
},
runtimeSecrets,
defaultSummaryPrompt: "Summarize this resource.",
})).toEqual({
aiProvider: "azure",
azureOpenAiEndpoint: "https://example.openai.azure.com",
azureOpenAiDeployment: null,
azureApiVersion: "2025-01-01-preview",
aiMaxCompletionTokens: 300,
aiTemperature: 1,
aiSummaryPrompt: null,
defaultSummaryPrompt: "Summarize this resource.",
hasApiKey: true,
runtimeSecrets,
legacyStoredSecretFields: ["azureOpenAiApiKey", "geminiApiKey"],
scoreWeights: getDefaultScoreWeights(),
scoreVisibleRoles: ["ADMIN"],
smtpHost: null,
smtpPort: 587,
smtpUser: null,
smtpFrom: null,
smtpTls: false,
hasSmtpPassword: false,
anonymizationEnabled: false,
anonymizationDomain: "superhartmut.de",
anonymizationMode: "global",
azureDalleDeployment: null,
azureDalleEndpoint: null,
hasDalleApiKey: false,
geminiModel: "gemini-2.5-flash-image",
hasGeminiApiKey: true,
imageProvider: "dalle",
vacationDefaultDays: 30,
timelineUndoMaxSteps: 50,
});
});
it("builds sparse updates and strips secret persistence", () => {
expect(buildSettingsUpdatePayload({
aiProvider: "openai",
azureOpenAiApiKey: "secret",
smtpPassword: "smtp-secret",
aiSummaryPrompt: "",
imageProvider: "gemini",
})).toEqual({
data: {
aiProvider: "openai",
aiSummaryPrompt: null,
imageProvider: "gemini",
},
ignoredSecretFields: ["azureOpenAiApiKey", "smtpPassword"],
});
});
it("sanitizes sensitive fields in audit snapshots", () => {
expect(sanitizeSettingsAuditSnapshot({
azureOpenAiApiKey: "secret",
smtpPassword: null,
aiProvider: "azure",
})).toEqual({
azureOpenAiApiKey: "***",
smtpPassword: null,
aiProvider: "azure",
});
});
});
@@ -5,6 +5,10 @@ import {
parseAiError,
sanitizeDiagnosticError,
} from "../ai-client.js";
import {
RUNTIME_SECRET_FIELDS,
} from "../lib/system-settings-runtime.js";
import type { RuntimeSecretField, RuntimeSecretStatus } from "../lib/system-settings-runtime.js";
import { logger } from "../lib/logger.js";
/** Fields that must never appear in audit log values */
@@ -75,6 +79,80 @@ export function getDefaultScoreWeights() {
};
}
export function buildSystemSettingsViewModel(input: {
settings: {
aiProvider?: string | null;
azureOpenAiEndpoint?: string | null;
azureOpenAiDeployment?: string | null;
azureApiVersion?: string | null;
aiMaxCompletionTokens?: number | null;
aiTemperature?: number | null;
aiSummaryPrompt?: string | null;
scoreWeights?: unknown;
scoreVisibleRoles?: unknown;
smtpHost?: string | null;
smtpPort?: number | null;
smtpUser?: string | null;
smtpFrom?: string | null;
smtpTls?: boolean | null;
anonymizationEnabled?: boolean | null;
anonymizationDomain?: string | null;
anonymizationMode?: string | null;
azureDalleDeployment?: string | null;
azureDalleEndpoint?: string | null;
geminiModel?: string | null;
imageProvider?: string | null;
vacationDefaultDays?: number | null;
timelineUndoMaxSteps?: number | null;
} | null | undefined;
runtimeSettings: {
azureOpenAiApiKey?: string | null;
smtpPassword?: string | null;
azureDalleApiKey?: string | null;
geminiApiKey?: string | null;
} | null | undefined;
runtimeSecrets: Record<RuntimeSecretField, RuntimeSecretStatus>;
defaultSummaryPrompt: string;
}) {
const defaultWeights = getDefaultScoreWeights();
const settings = input.settings;
return {
aiProvider: settings?.aiProvider ?? "openai",
azureOpenAiEndpoint: settings?.azureOpenAiEndpoint ?? null,
azureOpenAiDeployment: settings?.azureOpenAiDeployment ?? null,
azureApiVersion: settings?.azureApiVersion ?? "2025-01-01-preview",
aiMaxCompletionTokens: settings?.aiMaxCompletionTokens ?? 300,
aiTemperature: settings?.aiTemperature ?? 1,
aiSummaryPrompt: settings?.aiSummaryPrompt ?? null,
defaultSummaryPrompt: input.defaultSummaryPrompt,
hasApiKey: !!input.runtimeSettings?.azureOpenAiApiKey,
runtimeSecrets: input.runtimeSecrets,
legacyStoredSecretFields: RUNTIME_SECRET_FIELDS.filter(
(field) => input.runtimeSecrets[field].hasStoredValue,
),
scoreWeights: (settings?.scoreWeights as typeof defaultWeights | null | undefined) ?? defaultWeights,
scoreVisibleRoles: (settings?.scoreVisibleRoles as string[] | null | undefined) ?? ["ADMIN", "MANAGER"],
smtpHost: settings?.smtpHost ?? null,
smtpPort: settings?.smtpPort ?? 587,
smtpUser: settings?.smtpUser ?? null,
smtpFrom: settings?.smtpFrom ?? null,
smtpTls: settings?.smtpTls ?? true,
hasSmtpPassword: !!input.runtimeSettings?.smtpPassword,
anonymizationEnabled: settings?.anonymizationEnabled ?? false,
anonymizationDomain: settings?.anonymizationDomain ?? "superhartmut.de",
anonymizationMode: settings?.anonymizationMode ?? "global",
azureDalleDeployment: settings?.azureDalleDeployment ?? null,
azureDalleEndpoint: settings?.azureDalleEndpoint ?? null,
hasDalleApiKey: !!input.runtimeSettings?.azureDalleApiKey,
geminiModel: settings?.geminiModel ?? "gemini-2.5-flash-image",
hasGeminiApiKey: !!input.runtimeSettings?.geminiApiKey,
imageProvider: settings?.imageProvider ?? "dalle",
vacationDefaultDays: settings?.vacationDefaultDays ?? 28,
timelineUndoMaxSteps: settings?.timelineUndoMaxSteps ?? 50,
};
}
export function buildSettingsUpdatePayload(input: SettingsUpdateInput): {
data: Record<string, unknown>;
ignoredSecretFields: string[];
+6 -45
View File
@@ -5,8 +5,8 @@ import { testSmtpConnection } from "../lib/email.js";
import { createAuditEntry } from "../lib/audit.js";
import { getRuntimeSecretStatuses, RUNTIME_SECRET_FIELDS, resolveSystemSettingsRuntime } from "../lib/system-settings-runtime.js";
import {
buildSystemSettingsViewModel,
buildSettingsUpdatePayload,
getDefaultScoreWeights,
sanitizeSettingsAuditSnapshot,
settingsUpdateInputSchema,
testRuntimeAiConnection,
@@ -19,51 +19,12 @@ export const settingsRouter = createTRPCRouter({
});
const runtimeSettings = resolveSystemSettingsRuntime(settings);
const runtimeSecrets = getRuntimeSecretStatuses(settings);
const legacyStoredSecretFields = RUNTIME_SECRET_FIELDS.filter(
(field) => runtimeSecrets[field].hasStoredValue,
);
const defaultWeights = getDefaultScoreWeights();
return {
aiProvider: settings?.aiProvider ?? "openai",
azureOpenAiEndpoint: settings?.azureOpenAiEndpoint ?? null,
azureOpenAiDeployment: settings?.azureOpenAiDeployment ?? null,
azureApiVersion: settings?.azureApiVersion ?? "2025-01-01-preview",
aiMaxCompletionTokens: settings?.aiMaxCompletionTokens ?? 300,
aiTemperature: settings?.aiTemperature ?? 1,
aiSummaryPrompt: settings?.aiSummaryPrompt ?? null,
defaultSummaryPrompt: DEFAULT_SUMMARY_PROMPT,
hasApiKey: !!runtimeSettings.azureOpenAiApiKey,
return buildSystemSettingsViewModel({
settings,
runtimeSettings,
runtimeSecrets,
legacyStoredSecretFields,
scoreWeights: (settings?.scoreWeights as unknown as typeof defaultWeights) ?? defaultWeights,
scoreVisibleRoles: (settings?.scoreVisibleRoles as unknown as string[]) ?? ["ADMIN", "MANAGER"],
// SMTP
smtpHost: settings?.smtpHost ?? null,
smtpPort: settings?.smtpPort ?? 587,
smtpUser: settings?.smtpUser ?? null,
smtpFrom: settings?.smtpFrom ?? null,
smtpTls: settings?.smtpTls ?? true,
hasSmtpPassword: !!runtimeSettings.smtpPassword,
// Global anonymization
anonymizationEnabled: settings?.anonymizationEnabled ?? false,
anonymizationDomain: settings?.anonymizationDomain ?? "superhartmut.de",
anonymizationMode: settings?.anonymizationMode ?? "global",
// DALL-E
azureDalleDeployment: settings?.azureDalleDeployment ?? null,
azureDalleEndpoint: settings?.azureDalleEndpoint ?? null,
hasDalleApiKey: !!runtimeSettings.azureDalleApiKey,
// Gemini
geminiModel: settings?.geminiModel ?? "gemini-2.5-flash-image",
hasGeminiApiKey: !!runtimeSettings.geminiApiKey,
// Image provider
imageProvider: settings?.imageProvider ?? "dalle",
// Vacation defaults
vacationDefaultDays: settings?.vacationDefaultDays ?? 28,
// Timeline
timelineUndoMaxSteps: settings?.timelineUndoMaxSteps ?? 50,
};
defaultSummaryPrompt: DEFAULT_SUMMARY_PROMPT,
});
}),
updateSystemSettings: adminProcedure