feat(mvp): phase 6 - admin panel

Add admin layout with auth guard, user management (CRUD + group membership),
group management, system settings (auth mode, registration, theme, healthcheck),
permission editor component, and global search API endpoint.
This commit is contained in:
2026-03-24 21:18:06 +03:00
parent b0d77d3c29
commit c5166ba3a9
21 changed files with 1709 additions and 25 deletions
+78
View File
@@ -0,0 +1,78 @@
import type { Actions, PageServerLoad } from './$types.js';
import { superValidate, setError } from 'sveltekit-superforms';
import { zod } from 'sveltekit-superforms/adapters';
import { fail } from '@sveltejs/kit';
import { requireAdmin } from '$lib/server/middleware/authorize.js';
import { prisma } from '$lib/server/prisma.js';
import { updateSystemSettingsSchema } from '$lib/utils/validators.js';
import { DEFAULTS } from '$lib/utils/constants.js';
async function getOrCreateSettings() {
return prisma.systemSettings.upsert({
where: { id: DEFAULTS.SYSTEM_SETTINGS_ID },
update: {},
create: { id: DEFAULTS.SYSTEM_SETTINGS_ID }
});
}
export const load: PageServerLoad = async (event) => {
requireAdmin(event);
const settings = await getOrCreateSettings();
const form = await superValidate(
{
authMode: settings.authMode as 'local' | 'oauth' | 'both',
registrationEnabled: settings.registrationEnabled,
oauthClientId: settings.oauthClientId,
oauthClientSecret: settings.oauthClientSecret,
oauthDiscoveryUrl: settings.oauthDiscoveryUrl,
defaultTheme: settings.defaultTheme as 'dark' | 'light',
defaultPrimaryColor: settings.defaultPrimaryColor,
healthcheckDefaults: settings.healthcheckDefaults
},
zod(updateSystemSettingsSchema)
);
return { settings, form };
};
export const actions: Actions = {
update: async (event) => {
requireAdmin(event);
const form = await superValidate(event.request, zod(updateSystemSettingsSchema));
if (!form.valid) {
return fail(400, { form });
}
try {
const data: Record<string, unknown> = {};
const input = form.data;
if (input.authMode !== undefined) data.authMode = input.authMode;
if (input.registrationEnabled !== undefined) data.registrationEnabled = input.registrationEnabled;
if (input.oauthClientId !== undefined) data.oauthClientId = input.oauthClientId;
if (input.oauthClientSecret !== undefined) data.oauthClientSecret = input.oauthClientSecret;
if (input.oauthDiscoveryUrl !== undefined) data.oauthDiscoveryUrl = input.oauthDiscoveryUrl;
if (input.defaultTheme !== undefined) data.defaultTheme = input.defaultTheme;
if (input.defaultPrimaryColor !== undefined) data.defaultPrimaryColor = input.defaultPrimaryColor;
if (input.healthcheckDefaults !== undefined) data.healthcheckDefaults = input.healthcheckDefaults;
await prisma.systemSettings.upsert({
where: { id: DEFAULTS.SYSTEM_SETTINGS_ID },
update: data,
create: {
id: DEFAULTS.SYSTEM_SETTINGS_ID,
...data
}
});
} catch (err) {
const message = err instanceof Error ? err.message : 'Failed to update settings';
return setError(form, '', message);
}
return { form };
}
};