/** * Roles & permissions admin (legacy CI rolepermission/*). * Live Laravel routes are exposed under `/api/v1/role-permissions`. */ import { ApiHttpError, apiFetch } from './http' const BASE = '/api/v1/role-permissions' type DataEnvelope = { data?: T } function unwrapData(body: T | DataEnvelope): T { if (body && typeof body === 'object' && 'data' in body) { const data = (body as DataEnvelope).data if (data !== undefined) return data } return body as T } export type RoleRow = { id?: number | string name?: string slug?: string description?: string dashboard_route?: string priority?: number is_active?: number | string | boolean user_count?: number } export type PermissionRow = { id?: number | string name?: string description?: string created_at?: string updated_at?: string } export async function fetchRolesList(params?: { page?: number per_page?: number sort_by?: string sort_dir?: 'asc' | 'desc' }): Promise<{ roles?: RoleRow[] meta?: { current_page?: number; last_page?: number; total?: number; per_page?: number } }> { const qs = new URLSearchParams() qs.set('per_page', String(params?.per_page ?? 200)) if (params?.page) qs.set('page', String(params.page)) if (params?.sort_by) qs.set('sort_by', params.sort_by) if (params?.sort_dir) qs.set('sort_dir', params.sort_dir) const suffix = qs.toString() ? `?${qs}` : '' const body = await apiFetch< | { roles?: RoleRow[] meta?: { current_page?: number; last_page?: number; total?: number; per_page?: number } } | { data?: { roles?: RoleRow[] meta?: { current_page?: number; last_page?: number; total?: number; per_page?: number } } } >(`${BASE}/roles${suffix}`) return unwrapData(body) } export async function fetchAllRoles(): Promise<{ roles: RoleRow[] }> { const rolesById = new Map() let page = 1 let lastPage = 1 do { const response = await fetchRolesList({ page, per_page: 200, sort_by: 'name', sort_dir: 'asc', }) for (const role of response.roles ?? []) { const key = String(role.id ?? '').trim() if (key === '') { continue } if (!rolesById.has(key)) { rolesById.set(key, role) } } lastPage = Number(response.meta?.last_page ?? page) page += 1 } while (page <= lastPage) return { roles: [...rolesById.values()] } } export async function fetchPermissionsList(): Promise<{ permissions?: PermissionRow[] }> { const body = await apiFetch< { permissions?: PermissionRow[] } | { data?: { permissions?: PermissionRow[] } } >(`${BASE}/permissions`) return unwrapData(body) } export type AssignUserRow = { id?: number | string account_id?: string firstname?: string lastname?: string email?: string roles?: string[] } export async function fetchUsersForAssign(params?: { school_year?: string semester?: string page?: number per_page?: number }): Promise<{ users?: AssignUserRow[]; meta?: { current_page?: number; last_page?: number; total?: number; per_page?: number } }> { const qs = new URLSearchParams() if (params?.school_year) qs.set('school_year', params.school_year) if (params?.semester) qs.set('semester', params.semester) qs.set('per_page', String(params?.per_page ?? 200)) if (params?.page) qs.set('page', String(params.page)) qs.set('sort_by', 'lastname') qs.set('sort_dir', 'asc') const suffix = qs.toString() ? `?${qs}` : '' const body = await apiFetch< | { users?: AssignUserRow[] meta?: { current_page?: number; last_page?: number; total?: number; per_page?: number } } | { data?: { users?: AssignUserRow[] meta?: { current_page?: number; last_page?: number; total?: number; per_page?: number } } } >( `${BASE}/users${suffix}`, ) return unwrapData(body) } export async function fetchAllUsersForAssign(params?: { school_year?: string semester?: string }): Promise<{ users: AssignUserRow[] }> { const usersById = new Map() let page = 1 let lastPage = 1 do { const response = await fetchUsersForAssign({ school_year: params?.school_year, semester: params?.semester, page, per_page: 200, }) for (const user of response.users ?? []) { const key = String(user.id ?? '').trim() if (key === '') { continue } if (!usersById.has(key)) { usersById.set(key, user) } } lastPage = Number(response.meta?.last_page ?? page) page += 1 } while (page <= lastPage) return { users: [...usersById.values()] } } export async function saveUserRoles(payload: { user_id: number | string role_ids: (number | string)[] }): Promise<{ ok?: boolean; message?: string }> { const userId = encodeURIComponent(String(payload.user_id)) const body = await apiFetch<{ ok?: boolean; message?: string } | { data?: { ok?: boolean; message?: string } }>( `${BASE}/users/${userId}/roles`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ role_ids: payload.role_ids }), }, ) return unwrapData(body) } export async function createRole(payload: Record): Promise<{ ok?: boolean; message?: string }> { const body = await apiFetch<{ ok?: boolean; message?: string } | { data?: { ok?: boolean; message?: string } }>( `${BASE}/roles`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }, ) return unwrapData(body) } export async function updateRole( roleId: number | string, payload: Record, ): Promise<{ ok?: boolean; message?: string }> { try { const body = await apiFetch< { ok?: boolean; message?: string } | { data?: { ok?: boolean; message?: string } } >(`${BASE}/roles/${encodeURIComponent(String(roleId))}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }) return unwrapData(body) } catch (error) { if (error instanceof ApiHttpError && error.status === 422) { const body = error.body if (body && typeof body === 'object') { const errors = (body as { errors?: unknown }).errors if (errors && typeof errors === 'object') { for (const value of Object.values(errors as Record)) { if (Array.isArray(value) && value.length > 0) { throw new Error(String(value[0])) } if (typeof value === 'string' && value.trim() !== '') { throw new Error(value) } } } } } throw error } } export async function deleteRole(roleId: number | string): Promise<{ ok?: boolean }> { const body = await apiFetch<{ ok?: boolean } | { data?: { ok?: boolean } }>( `${BASE}/roles/${encodeURIComponent(String(roleId))}`, { method: 'DELETE' }, ) return unwrapData(body) } export async function fetchRole(roleId: number | string): Promise<{ role?: RoleRow }> { const body = await apiFetch<{ role?: RoleRow } | { data?: { role?: RoleRow } }>( `${BASE}/roles/${encodeURIComponent(String(roleId))}`, ) return unwrapData(body) } export async function createPermission(payload: { name: string description?: string }): Promise<{ ok?: boolean; message?: string }> { const body = await apiFetch<{ ok?: boolean; message?: string } | { data?: { ok?: boolean; message?: string } }>( `${BASE}/permissions`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }, ) return unwrapData(body) } export async function fetchPermission( permissionId: number | string, ): Promise<{ permission?: PermissionRow }> { const body = await apiFetch<{ permission?: PermissionRow } | { data?: { permission?: PermissionRow } }>( `${BASE}/permissions/${encodeURIComponent(String(permissionId))}`, ) return unwrapData(body) } export async function updatePermission( permissionId: number | string, payload: { name: string; description?: string }, ): Promise<{ ok?: boolean; message?: string }> { const body = await apiFetch<{ ok?: boolean; message?: string } | { data?: { ok?: boolean; message?: string } }>( `${BASE}/permissions/${encodeURIComponent(String(permissionId))}`, { method: 'PATCH', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload), }, ) return unwrapData(body) } export type PermissionMatrixRow = { id?: number | string permission_id?: number | string name?: string description?: string can_create?: boolean | number can_read?: boolean | number can_update?: boolean | number can_delete?: boolean | number } export type ExistingPermissionFlags = { can_create?: boolean can_read?: boolean can_update?: boolean can_delete?: boolean } export async function fetchRolePermissionMatrix(roleId: number | string): Promise<{ roleId?: number | string permissions?: PermissionMatrixRow[] existing?: Record }> { const body = await apiFetch< | { roleId?: number | string permissions?: PermissionMatrixRow[] existing?: Record } | { data?: { roleId?: number | string permissions?: PermissionMatrixRow[] existing?: Record } } >(`${BASE}/roles/${encodeURIComponent(String(roleId))}/permissions`) return unwrapData(body) } export async function saveRolePermissionMatrix( roleId: number | string, payload: Record, ): Promise<{ ok?: boolean; message?: string }> { const body = await apiFetch<{ ok?: boolean; message?: string } | { data?: { ok?: boolean; message?: string } }>( `${BASE}/roles/${encodeURIComponent(String(roleId))}/permissions`, { method: 'PUT', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ permissions: payload }), }, ) return unwrapData(body) }