feat(api): set user role with self-demote + last-admin guards
This commit is contained in:
@@ -1,10 +1,10 @@
|
||||
import { Hono } from 'hono';
|
||||
import { asc, eq } from 'drizzle-orm';
|
||||
import { CreateUserInput, type AdminUser, type Role } from '@solelog/shared';
|
||||
import { CreateUserInput, SetRoleInput, type AdminUser, type Role } from '@solelog/shared';
|
||||
import { db } from '../db/client';
|
||||
import { session, user } from '../db/schema';
|
||||
import { auth } from '../auth';
|
||||
import { adminGuard } from '../lib/require-user';
|
||||
import { adminGuard, getSessionUser } from '../lib/require-user';
|
||||
|
||||
export const adminUsersRoutes = new Hono();
|
||||
|
||||
@@ -54,3 +54,36 @@ adminUsersRoutes.post('/api/admin/users', async (c) => {
|
||||
const [row] = await db.select().from(user).where(eq(user.email, d.email));
|
||||
return c.json(toListItem(row));
|
||||
});
|
||||
|
||||
// Count admins that are active (not banned), excluding one user id.
|
||||
export async function activeAdminsExcluding(excludeId: string): Promise<number> {
|
||||
const admins = await db
|
||||
.select({ id: user.id, banned: user.banned })
|
||||
.from(user)
|
||||
.where(eq(user.role, 'admin'));
|
||||
return admins.filter((a) => !a.banned && a.id !== excludeId).length;
|
||||
}
|
||||
|
||||
adminUsersRoutes.post('/api/admin/users/:id/role', async (c) => {
|
||||
const id = c.req.param('id');
|
||||
const parsed = SetRoleInput.safeParse(await c.req.json().catch(() => null));
|
||||
if (!parsed.success) return c.json({ error: 'Invalid input' }, 400);
|
||||
const newRole = parsed.data.role;
|
||||
|
||||
const caller = await getSessionUser(c);
|
||||
const [target] = await db.select().from(user).where(eq(user.id, id));
|
||||
if (!target) return c.json({ error: 'Gebruiker niet gevonden' }, 404);
|
||||
|
||||
if (id === caller?.id && newRole !== 'admin') {
|
||||
return c.json({ error: 'Je kunt jezelf niet degraderen.' }, 400);
|
||||
}
|
||||
if (target.role === 'admin' && newRole !== 'admin') {
|
||||
if ((await activeAdminsExcluding(id)) === 0) {
|
||||
return c.json({ error: 'Er moet minstens één actieve beheerder blijven.' }, 400);
|
||||
}
|
||||
}
|
||||
|
||||
await db.update(user).set({ role: newRole }).where(eq(user.id, id));
|
||||
const [updated] = await db.select().from(user).where(eq(user.id, id));
|
||||
return c.json(toListItem(updated));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user