import { Body, Controller, Get, Patch, Req, UseGuards } from '@nestjs/common'; import { z } from 'zod'; import type { AuthedRequest } from '../auth/auth.guard'; import { ZodValidationPipe } from '../common/zod-validation.pipe'; import { INSTANCE_SETTINGS, InstanceSettingKey, InstanceSettings, InstanceSettingsService, } from '../settings/instance-settings.service'; import { SiteAdminGuard } from './site-admin.guard'; // Lifecycle markers, not configuration: never editable through this // endpoint (the setup lock must be irreversible, issue #80). const INTERNAL_KEYS: ReadonlySet = new Set(['setup.completedAt']); // Partial update: any subset of the known settings, each validated by // its own schema inside the service (double validation is fine — this // outer schema only gates unknown and internal keys). const patchSchema = z .object( Object.fromEntries( Object.keys(INSTANCE_SETTINGS) .filter((key) => !INTERNAL_KEYS.has(key as InstanceSettingKey)) .map((key) => [key, z.unknown().optional()]), ) as Record>, ) .strict(); @Controller('admin/settings') @UseGuards(SiteAdminGuard) export class AdminSettingsController { constructor(private readonly settings: InstanceSettingsService) {} @Get() getSettings(): Promise { return this.settings.getAll(); } @Patch() async patchSettings( @Body(new ZodValidationPipe(patchSchema)) input: Partial>, @Req() request: AuthedRequest, ): Promise { for (const [key, value] of Object.entries(input)) { if (value === undefined) continue; await this.settings.set(key as InstanceSettingKey, value, request.user!.id); } return this.settings.getAll(); } }