Full CRUD for vets, sponsors, mentors, volunteer applications, and users. Includes approve/deny flows for pending users, role management, and password reset. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
77 lines
3.3 KiB
TypeScript
77 lines
3.3 KiB
TypeScript
import type { PageServerLoad, Actions } from './$types';
|
|
import { db } from '$lib/server/db';
|
|
import { users } from '$lib/server/schema';
|
|
import { error, fail } from '@sveltejs/kit';
|
|
import { hasPermission } from '$lib/roles';
|
|
import type { Role } from '$lib/roles';
|
|
import { eq, desc, count } from 'drizzle-orm';
|
|
import { logAudit } from '$lib/server/audit';
|
|
import { sendRegistrationApproved, sendRegistrationDenied } from '$lib/server/email';
|
|
|
|
export const load: PageServerLoad = async ({ locals }) => {
|
|
if (!hasPermission(locals.user!.role as Role, 'users')) error(403, 'You do not have permission to manage users');
|
|
|
|
const userList = await db
|
|
.select({
|
|
id: users.id,
|
|
name: users.name,
|
|
email: users.email,
|
|
role: users.role,
|
|
active: users.active,
|
|
createdAt: users.createdAt
|
|
})
|
|
.from(users)
|
|
.orderBy(desc(users.createdAt));
|
|
|
|
const [pendingRow] = await db.select({ total: count() }).from(users).where(eq(users.active, false));
|
|
const pendingCount = pendingRow?.total ?? 0;
|
|
|
|
return { users: userList, pendingCount };
|
|
};
|
|
|
|
export const actions: Actions = {
|
|
approve: async ({ request, locals }) => {
|
|
if (!hasPermission(locals.user!.role as Role, 'users')) error(403, 'Access denied');
|
|
const fd = await request.formData();
|
|
const userId = parseInt(fd.get('userId')?.toString() ?? '', 10);
|
|
const role = fd.get('role')?.toString() ?? 'viewer';
|
|
|
|
if (isNaN(userId)) return fail(400, { error: 'Invalid user ID' });
|
|
|
|
const validRoles = ['sysadmin', 'director', 'foster_coordinator', 'volunteer_manager', 'vet_liaison', 'content_editor', 'applications_manager', 'viewer', 'foster'];
|
|
if (!validRoles.includes(role)) return fail(400, { error: 'Invalid role' });
|
|
|
|
const [user] = await db.select({ id: users.id, name: users.name, email: users.email }).from(users).where(eq(users.id, userId)).limit(1);
|
|
if (!user) return fail(404, { error: 'User not found' });
|
|
|
|
await db.update(users).set({ active: true, role: role as typeof users.role.enumValues[number] }).where(eq(users.id, userId));
|
|
await logAudit({ userId: locals.user!.id, action: 'approve_user', entity: 'user', entityId: userId, diff: { role } });
|
|
|
|
try {
|
|
await sendRegistrationApproved(process.env.ORG_NAME ?? 'Rescue', user.email, user.name, `${process.env.SITE_URL ?? ''}/login`);
|
|
} catch { /* noop */ }
|
|
|
|
return { success: true, action: 'approve' };
|
|
},
|
|
|
|
deny: async ({ request, locals }) => {
|
|
if (!hasPermission(locals.user!.role as Role, 'users')) error(403, 'Access denied');
|
|
const fd = await request.formData();
|
|
const userId = parseInt(fd.get('userId')?.toString() ?? '', 10);
|
|
if (isNaN(userId)) return fail(400, { error: 'Invalid user ID' });
|
|
|
|
const [user] = await db.select({ id: users.id, name: users.name, email: users.email, active: users.active }).from(users).where(eq(users.id, userId)).limit(1);
|
|
if (!user) return fail(404, { error: 'User not found' });
|
|
if (user.active) return fail(400, { error: 'Cannot deny an active user. Use the edit page to deactivate.' });
|
|
|
|
await db.delete(users).where(eq(users.id, userId));
|
|
await logAudit({ userId: locals.user!.id, action: 'deny_user', entity: 'user', entityId: userId });
|
|
|
|
try {
|
|
await sendRegistrationDenied(process.env.ORG_NAME ?? 'Rescue', user.email, user.name);
|
|
} catch { /* noop */ }
|
|
|
|
return { success: true, action: 'deny' };
|
|
}
|
|
};
|