344 lines
10 KiB
TypeScript
344 lines
10 KiB
TypeScript
/**
|
|
* 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<T> = { data?: T }
|
|
|
|
function unwrapData<T>(body: T | DataEnvelope<T>): T {
|
|
if (body && typeof body === 'object' && 'data' in body) {
|
|
const data = (body as DataEnvelope<T>).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<string, RoleRow>()
|
|
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<string, AssignUserRow>()
|
|
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<string, unknown>): 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<string, unknown>,
|
|
): 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<string, unknown>)) {
|
|
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<string, ExistingPermissionFlags>
|
|
}> {
|
|
const body = await apiFetch<
|
|
| {
|
|
roleId?: number | string
|
|
permissions?: PermissionMatrixRow[]
|
|
existing?: Record<string, ExistingPermissionFlags>
|
|
}
|
|
| {
|
|
data?: {
|
|
roleId?: number | string
|
|
permissions?: PermissionMatrixRow[]
|
|
existing?: Record<string, ExistingPermissionFlags>
|
|
}
|
|
}
|
|
>(`${BASE}/roles/${encodeURIComponent(String(roleId))}/permissions`)
|
|
return unwrapData(body)
|
|
}
|
|
|
|
export async function saveRolePermissionMatrix(
|
|
roleId: number | string,
|
|
payload: Record<string, { create?: boolean; read?: boolean; update?: boolean; delete?: boolean }>,
|
|
): 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)
|
|
}
|