feat: basic admin interface
This commit is contained in:
@@ -0,0 +1,39 @@
|
||||
import { eq } from 'drizzle-orm';
|
||||
import {
|
||||
agents,
|
||||
files,
|
||||
models,
|
||||
providers,
|
||||
settings,
|
||||
toolCalls,
|
||||
topics,
|
||||
users,
|
||||
} from '~~/drizzle/schema';
|
||||
import { db } from '~~/server/lib/db';
|
||||
|
||||
/**
|
||||
* Deletes an account and everything that belongs to it.
|
||||
*
|
||||
* The app tables only store a plain user id, nothing cascades from the users
|
||||
* table, so every one of them is cleared here. It all happens in one
|
||||
* transaction: either the whole account is gone or nothing changed.
|
||||
*
|
||||
* Objects in file storage are left alone on purpose.
|
||||
*/
|
||||
export const deleteAccount = async (userId: string) => {
|
||||
await db.transaction(async (tx) => {
|
||||
// messages, their parts, generations and attachments cascade from topics
|
||||
await tx.delete(topics).where(eq(topics.userId, userId));
|
||||
await tx.delete(agents).where(eq(agents.userId, userId));
|
||||
await tx.delete(models).where(eq(models.userId, userId));
|
||||
await tx.delete(providers).where(eq(providers.userId, userId));
|
||||
// nothing points a tool call back at its message, so these never cascade
|
||||
await tx.delete(toolCalls).where(eq(toolCalls.userId, userId));
|
||||
// embeddings go with their file
|
||||
await tx.delete(files).where(eq(files.userId, userId));
|
||||
await tx.delete(settings).where(eq(settings.userId, userId));
|
||||
|
||||
// sessions, accounts and two-factor rows cascade from the user
|
||||
await tx.delete(users).where(eq(users.id, userId));
|
||||
});
|
||||
};
|
||||
@@ -0,0 +1,26 @@
|
||||
import type { H3Event } from 'h3';
|
||||
import { db } from '~~/server/lib/db';
|
||||
|
||||
/**
|
||||
* Like protectRoute(), but only lets admins through.
|
||||
*
|
||||
* The role is read from the database: the session cookie keeps a copy of the
|
||||
* user for a few minutes, and someone who just stopped being an admin should
|
||||
* not keep the access for that long.
|
||||
*/
|
||||
export const protectAdminRoute = async (event: H3Event) => {
|
||||
await protectRoute(event);
|
||||
|
||||
const account = await db.query.users.findFirst({
|
||||
where: {
|
||||
id: event.context.user!.id as string,
|
||||
},
|
||||
});
|
||||
|
||||
if (account?.role !== 'admin' || account.banned) {
|
||||
throw createError({
|
||||
statusCode: 403,
|
||||
statusMessage: 'Forbidden',
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,27 @@
|
||||
import { INSTANCE_SETTINGS_ID, REGISTRATION_MODES, type RegistrationMode } from '../../drizzle/schema';
|
||||
import { db } from '../lib/db';
|
||||
|
||||
/**
|
||||
* DISABLE_SIGNUP in the environment wins over whatever admins pick in the UI,
|
||||
* so a deployment that was locked down this way stays locked down.
|
||||
*/
|
||||
export const isRegistrationLocked = (): boolean => {
|
||||
const value = process.env.DISABLE_SIGNUP?.toLowerCase();
|
||||
|
||||
return value === 'true' || value === '1';
|
||||
};
|
||||
|
||||
export const getRegistrationMode = async (): Promise<RegistrationMode> => {
|
||||
if (isRegistrationLocked()) return 'closed';
|
||||
|
||||
const row = await db.query.instanceSettings.findFirst({
|
||||
where: {
|
||||
id: INSTANCE_SETTINGS_ID,
|
||||
},
|
||||
});
|
||||
|
||||
const mode = row?.registration;
|
||||
|
||||
// instances that never touched the setting keep taking sign ups like before
|
||||
return mode !== undefined && REGISTRATION_MODES.includes(mode) ? mode : 'open';
|
||||
};
|
||||
Reference in New Issue
Block a user