import { Permissions, type Permission, type BuiltinRole } from "@/shared/types/permissions" import { isPermission } from "@/shared/lib/type-guards" import { db } from "@/shared/db" import { roles, rolePermissions } from "@/shared/db/schema" import { and, eq, inArray } from "drizzle-orm" /** * Seed role → permission mapping for the 6 builtin roles. * * Used by: * - The seed migration to populate the `role_permissions` table. * - `resolvePermissions()` as a fallback when the DB query fails (e.g. during * initial bootstrap before the migration has run). * * Runtime permission resolution reads from the DB — this constant is NOT * consulted at runtime except as a fallback. */ export const ROLE_PERMISSIONS_SEED: Record = { admin: [ Permissions.EXAM_CREATE, Permissions.EXAM_READ, Permissions.EXAM_UPDATE, Permissions.EXAM_DELETE, Permissions.EXAM_DUPLICATE, Permissions.EXAM_PUBLISH, Permissions.EXAM_AI_GENERATE, Permissions.HOMEWORK_CREATE, Permissions.HOMEWORK_GRADE, Permissions.QUESTION_CREATE, Permissions.QUESTION_READ, Permissions.QUESTION_UPDATE, Permissions.QUESTION_DELETE, Permissions.TEXTBOOK_CREATE, Permissions.TEXTBOOK_READ, Permissions.TEXTBOOK_UPDATE, Permissions.TEXTBOOK_DELETE, Permissions.CLASS_CREATE, Permissions.CLASS_READ, Permissions.CLASS_UPDATE, Permissions.CLASS_DELETE, Permissions.CLASS_ENROLL, Permissions.CLASS_SCHEDULE, Permissions.SCHOOL_MANAGE, Permissions.GRADE_MANAGE, Permissions.USER_MANAGE, Permissions.USER_PROFILE_UPDATE, Permissions.AI_CHAT, Permissions.AI_CONFIGURE, Permissions.SETTINGS_ADMIN, Permissions.AUDIT_LOG_READ, Permissions.AUDIT_LOG_PURGE, Permissions.AUDIT_RETENTION_MANAGE, Permissions.ANNOUNCEMENT_MANAGE, Permissions.ANNOUNCEMENT_READ, Permissions.GRADE_RECORD_MANAGE, Permissions.GRADE_RECORD_READ, Permissions.COURSE_PLAN_MANAGE, Permissions.COURSE_PLAN_READ, Permissions.ATTENDANCE_MANAGE, Permissions.ATTENDANCE_READ, Permissions.MESSAGE_SEND, Permissions.MESSAGE_READ, Permissions.MESSAGE_DELETE, Permissions.SCHEDULE_AUTO, Permissions.SCHEDULE_ADJUST, Permissions.ELECTIVE_MANAGE, Permissions.ELECTIVE_READ, Permissions.EXAM_PROCTOR, Permissions.EXAM_PROCTOR_READ, Permissions.DIAGNOSTIC_MANAGE, Permissions.DIAGNOSTIC_READ, Permissions.LESSON_PLAN_CREATE, Permissions.LESSON_PLAN_READ, Permissions.LESSON_PLAN_UPDATE, Permissions.LESSON_PLAN_DELETE, Permissions.LESSON_PLAN_PUBLISH, Permissions.STANDARD_READ, Permissions.STANDARD_MANAGE, Permissions.STANDARD_LINK, Permissions.FILE_UPLOAD, Permissions.FILE_READ, Permissions.FILE_DELETE, Permissions.DASHBOARD_ADMIN_READ, Permissions.ERROR_BOOK_ANALYTICS_READ, Permissions.ADAPTIVE_PRACTICE_READ, // RBAC management — admin only by default Permissions.ROLE_CREATE, Permissions.ROLE_READ, Permissions.ROLE_UPDATE, Permissions.ROLE_DELETE, Permissions.ROLE_ASSIGN, Permissions.PERMISSION_READ, ], teacher: [ Permissions.EXAM_CREATE, Permissions.EXAM_READ, Permissions.EXAM_UPDATE, Permissions.EXAM_DELETE, Permissions.EXAM_DUPLICATE, Permissions.EXAM_PUBLISH, Permissions.EXAM_AI_GENERATE, Permissions.HOMEWORK_CREATE, Permissions.HOMEWORK_GRADE, Permissions.QUESTION_CREATE, Permissions.QUESTION_READ, Permissions.QUESTION_UPDATE, Permissions.QUESTION_DELETE, Permissions.TEXTBOOK_CREATE, Permissions.TEXTBOOK_READ, Permissions.TEXTBOOK_UPDATE, Permissions.CLASS_READ, Permissions.CLASS_ENROLL, Permissions.CLASS_SCHEDULE, Permissions.USER_PROFILE_UPDATE, Permissions.AI_CHAT, Permissions.ANNOUNCEMENT_READ, Permissions.GRADE_RECORD_MANAGE, Permissions.GRADE_RECORD_READ, Permissions.COURSE_PLAN_READ, Permissions.ATTENDANCE_MANAGE, Permissions.ATTENDANCE_READ, Permissions.MESSAGE_SEND, Permissions.MESSAGE_READ, Permissions.MESSAGE_DELETE, Permissions.ELECTIVE_MANAGE, Permissions.ELECTIVE_READ, Permissions.EXAM_PROCTOR, Permissions.EXAM_PROCTOR_READ, Permissions.DIAGNOSTIC_MANAGE, Permissions.DIAGNOSTIC_READ, Permissions.LESSON_PLAN_CREATE, Permissions.LESSON_PLAN_READ, Permissions.LESSON_PLAN_UPDATE, Permissions.LESSON_PLAN_DELETE, Permissions.LESSON_PLAN_PUBLISH, Permissions.STANDARD_READ, Permissions.STANDARD_LINK, Permissions.DASHBOARD_TEACHER_READ, Permissions.ERROR_BOOK_ANALYTICS_READ, Permissions.ADAPTIVE_PRACTICE_READ, ], student: [ Permissions.EXAM_READ, Permissions.EXAM_SUBMIT, Permissions.HOMEWORK_SUBMIT, Permissions.QUESTION_READ, Permissions.TEXTBOOK_READ, Permissions.CLASS_READ, Permissions.USER_PROFILE_UPDATE, Permissions.AI_CHAT, Permissions.ANNOUNCEMENT_READ, Permissions.GRADE_RECORD_READ, Permissions.COURSE_PLAN_READ, Permissions.ATTENDANCE_READ, Permissions.MESSAGE_SEND, Permissions.MESSAGE_READ, Permissions.MESSAGE_DELETE, Permissions.ELECTIVE_SELECT, Permissions.ELECTIVE_READ, Permissions.DIAGNOSTIC_READ, Permissions.LESSON_PLAN_READ, Permissions.DASHBOARD_STUDENT_READ, Permissions.ERROR_BOOK_READ, Permissions.ERROR_BOOK_MANAGE, Permissions.ADAPTIVE_PRACTICE_READ, Permissions.ADAPTIVE_PRACTICE_MANAGE, ], parent: [ Permissions.EXAM_READ, Permissions.TEXTBOOK_READ, Permissions.CLASS_READ, Permissions.USER_PROFILE_UPDATE, Permissions.AI_CHAT, Permissions.ANNOUNCEMENT_READ, Permissions.GRADE_RECORD_READ, Permissions.ATTENDANCE_READ, Permissions.MESSAGE_SEND, Permissions.MESSAGE_READ, Permissions.MESSAGE_DELETE, Permissions.LESSON_PLAN_READ, Permissions.DASHBOARD_PARENT_READ, Permissions.ERROR_BOOK_READ, Permissions.ADAPTIVE_PRACTICE_READ, ], grade_head: [ Permissions.EXAM_CREATE, Permissions.EXAM_READ, Permissions.EXAM_UPDATE, Permissions.EXAM_DELETE, Permissions.EXAM_DUPLICATE, Permissions.EXAM_PUBLISH, Permissions.EXAM_AI_GENERATE, Permissions.HOMEWORK_CREATE, Permissions.HOMEWORK_GRADE, Permissions.QUESTION_CREATE, Permissions.QUESTION_READ, Permissions.QUESTION_UPDATE, Permissions.QUESTION_DELETE, Permissions.TEXTBOOK_CREATE, Permissions.TEXTBOOK_READ, Permissions.TEXTBOOK_UPDATE, Permissions.CLASS_CREATE, Permissions.CLASS_READ, Permissions.CLASS_UPDATE, Permissions.CLASS_ENROLL, Permissions.CLASS_SCHEDULE, Permissions.GRADE_MANAGE, Permissions.USER_PROFILE_UPDATE, Permissions.AI_CHAT, Permissions.ANNOUNCEMENT_READ, Permissions.GRADE_RECORD_READ, Permissions.COURSE_PLAN_READ, Permissions.ATTENDANCE_READ, Permissions.MESSAGE_SEND, Permissions.MESSAGE_READ, Permissions.MESSAGE_DELETE, Permissions.ELECTIVE_READ, Permissions.EXAM_PROCTOR_READ, Permissions.DIAGNOSTIC_MANAGE, Permissions.DIAGNOSTIC_READ, Permissions.LESSON_PLAN_READ, Permissions.ERROR_BOOK_ANALYTICS_READ, Permissions.ADAPTIVE_PRACTICE_READ, ], teaching_head: [ Permissions.EXAM_CREATE, Permissions.EXAM_READ, Permissions.EXAM_UPDATE, Permissions.EXAM_DELETE, Permissions.EXAM_DUPLICATE, Permissions.EXAM_PUBLISH, Permissions.EXAM_AI_GENERATE, Permissions.HOMEWORK_CREATE, Permissions.HOMEWORK_GRADE, Permissions.QUESTION_CREATE, Permissions.QUESTION_READ, Permissions.QUESTION_UPDATE, Permissions.QUESTION_DELETE, Permissions.TEXTBOOK_CREATE, Permissions.TEXTBOOK_READ, Permissions.TEXTBOOK_UPDATE, Permissions.CLASS_READ, Permissions.GRADE_MANAGE, Permissions.USER_PROFILE_UPDATE, Permissions.AI_CHAT, Permissions.ANNOUNCEMENT_READ, Permissions.GRADE_RECORD_READ, Permissions.COURSE_PLAN_READ, Permissions.ATTENDANCE_READ, Permissions.MESSAGE_SEND, Permissions.MESSAGE_READ, Permissions.MESSAGE_DELETE, Permissions.ELECTIVE_READ, Permissions.EXAM_PROCTOR_READ, Permissions.DIAGNOSTIC_READ, Permissions.LESSON_PLAN_READ, Permissions.ERROR_BOOK_ANALYTICS_READ, Permissions.ADAPTIVE_PRACTICE_READ, ], } /** * @deprecated Use `ROLE_PERMISSIONS_SEED` instead. Kept as a re-export for * backward compatibility with any code that still imports `ROLE_PERMISSIONS`. */ export const ROLE_PERMISSIONS = ROLE_PERMISSIONS_SEED /** * Merge permissions from all roles by querying the `role_permissions` table. * * - Only enabled roles contribute permissions (`roles.is_enabled = true`). * - Falls back to `ROLE_PERMISSIONS_SEED` for builtin roles if the DB query * fails (e.g. during initial bootstrap before the migration has run). * - Deduplicates the resulting permission list. */ export async function resolvePermissions(roleNames: string[]): Promise { if (roleNames.length === 0) return [] try { const rows = await db .select({ permission: rolePermissions.permission }) .from(rolePermissions) .innerJoin(roles, eq(rolePermissions.roleId, roles.id)) .where(and(inArray(roles.name, roleNames), eq(roles.isEnabled, true))) const set = new Set() for (const row of rows) { if (isPermission(row.permission)) { set.add(row.permission) } } // Fallback: if the DB returned nothing for builtin roles (e.g. migration // not yet applied), use the seed constant so login still works. if (set.size === 0) { for (const name of roleNames) { const seed = ROLE_PERMISSIONS_SEED[name as BuiltinRole] if (seed) for (const p of seed) set.add(p) } } return Array.from(set) } catch { // DB unavailable (e.g. during build) — fall back to seed for builtin roles const set = new Set() for (const name of roleNames) { const seed = ROLE_PERMISSIONS_SEED[name as BuiltinRole] if (seed) for (const p of seed) set.add(p) } return Array.from(set) } }