import { db } from '$lib/server/db'; import type { User } from '$lib/server/db/types/user.js'; import { fail, redirect, type Actions } from '@sveltejs/kit'; import bcrypt from 'bcryptjs'; import { nanoid } from 'nanoid'; import { z } from 'zod'; export const load = async ({ locals: { guard }, params }) => { guard.requiresAdmin().orRedirects(); let user = db.data.users.find((u) => u.id === params.slug) as Partial; if (!user && params.slug !== 'new') { redirect(302, '/dashboard/users'); } if (user) { user = structuredClone(user); delete user['password']; } return { user, groups: db.data.groups, devices: db.data.devices, }; }; export const actions: Actions = { update: async ({ request, locals: { guard }, params }) => { if (guard.requiresAdmin().isFailed()) return fail(403); const form = await request.formData(); const schema = z.object({ name: z .string({ message: 'Name is required.' }) .min(3, { message: 'Name must be at least 3 characters.' }) .max(24, { message: 'Name must be at most 24 characters.' }), admin: z.boolean(), password: z .string() .optional() .refine((v) => (params.slug === 'new' ? v && v.length > 0 : true), { message: 'Password is required at user creation.', }) .refine((v) => !v || v.length >= 8, { message: 'Password must be at least 8 characters.', }), groups: z.array( z.string().refine((v) => db.data.groups.find((g) => g.id === v), { message: 'Invalid group ID.', }), ), devices: z.array( z.string().refine((v) => db.data.devices.find((d) => d.id === v), { message: 'Invalid device ID.', }), ), }); const parsed = schema.safeParse({ name: form.get('name'), admin: form.get('admin') === 'on', password: form.get('password'), groups: form.getAll('groups'), devices: form.getAll('devices'), }); if (!parsed.success) { return fail(400, { error: parsed.error.errors[0].message }); } if (params.slug === 'new') { await db.update(({ users }) => { users.push({ id: nanoid(), name: parsed.data.name, admin: parsed.data.admin, groups: parsed.data.groups, devices: parsed.data.devices, password: bcrypt.hashSync(parsed.data.password!, 10), }); }); redirect(302, '/dashboard/users'); } await db.update(({ users }) => { let user = users.find((u) => u.id === params.slug); if (!user) return; user.name = parsed.data.name; user.admin = parsed.data.admin; user.groups = parsed.data.groups; user.devices = parsed.data.devices; if (parsed.data.password && parsed.data.password.length > 0) { user.password = bcrypt.hashSync(parsed.data.password, 10); } }); redirect(302, '/dashboard/users'); }, delete: async ({ locals: { guard }, params }) => { if (guard.requiresAdmin().isFailed()) { return fail(403); } db.data.users = db.data.users.filter((u) => u.id !== params.slug); db.write(); }, };