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>
47 lines
1.5 KiB
TypeScript
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();
|
|
}
|
|
}
|