dorfteich/apps/api/src/admin/admin.controller.ts
Claude Fable 5 1314096c94 Add account/session endpoints and typed instance settings with admin API
Server halves of #17/#18/#19: PATCH /users/me and change-password
(verifies the current password, logs out every other session),
GET/DELETE /users/me/sessions with current-session flag and protection
against revoking oneself; InstanceSettingsService as a typed, cached,
Zod-validated registry over instance_settings (schema-default fallback
for invalid stored values, audit-logged writes) consumed by the signup
flow; /admin/settings behind the new SiteAdminGuard with strict
unknown-key rejection. SessionsService moves to its own module to keep
Auth/Users acyclic. Three new e2e suites bring the api to 42 tests.

Part of #17, #18, #19

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-05 05:26:45 +02:00

47 lines
1.5 KiB
TypeScript

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';
// 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 keys).
const patchSchema = z
.object(
Object.fromEntries(
Object.keys(INSTANCE_SETTINGS).map((key) => [key, z.unknown().optional()]),
) as Record<InstanceSettingKey, z.ZodOptional<z.ZodUnknown>>,
)
.strict();
@Controller('admin/settings')
@UseGuards(SiteAdminGuard)
export class AdminSettingsController {
constructor(private readonly settings: InstanceSettingsService) {}
@Get()
getSettings(): Promise<InstanceSettings> {
return this.settings.getAll();
}
@Patch()
async patchSettings(
@Body(new ZodValidationPipe(patchSchema)) input: Partial<Record<InstanceSettingKey, unknown>>,
@Req() request: AuthedRequest,
): Promise<InstanceSettings> {
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();
}
}