// lib/account-status.ts // Whether an account may currently use the platform, checked against the // database rather than whatever was true when the user signed in. import { prisma } from './prisma'; import { normalizeEmail } from './normalize-email'; export type AccountStatus = { /** False once a student is removed from, or deactivated on, the allow-list. */ allowed: boolean; role: string; /** Comma-separated course codes from the allow-list. */ levels: string; }; // Sessions are checked on every request, so results are held briefly. A change // to a student's access therefore takes effect within this window. const TTL_MS = 60_000; const MAX_ENTRIES = 5000; const cache = new Map(); export async function getAccountStatus( userId: string, opts: { fresh?: boolean } = {} ): Promise { const hit = cache.get(userId); if (!opts.fresh && hit && hit.expires > Date.now()) return hit.status; const user = await prisma.user.findUnique({ where: { id: userId }, select: { email: true, role: true }, }); let status: AccountStatus = { allowed: false, role: 'user', levels: '' }; if (user) { const role = user.role ?? 'user'; const email = normalizeEmail(user.email); const student = email ? await prisma.allowedStudent.findFirst({ where: { email: { equals: email, mode: 'insensitive' } }, select: { levels: true, active: true }, }) : null; const isAdmin = role === 'admin' || role === 'superadmin'; status = { allowed: isAdmin || !!student?.active, role, levels: student?.levels ?? '', }; } if (cache.size >= MAX_ENTRIES) cache.clear(); cache.set(userId, { expires: Date.now() + TTL_MS, status }); return status; }