permission.ts 4.99 KB
export const LOGIN_PATH = '/login'
export const FORBIDDEN_PATH = '/forbidden'

export const APP_PAGE_PATHS = [
  '/',
  '/work-orders',
  '/sn-management',
  '/operations',
  '/inbox',
  '/customers',
  '/base-info/device-types',
  '/settings',
  '/settings/members',
  '/settings/notifications',
  '/settings/security'
] as const

export const BUTTON_PERMISSION_KEYS = [
  'work-orders.create',
  'work-orders.edit',
  'work-orders.dispatch',
  'work-orders.pause',
  'work-orders.resume',
  'work-orders.close',
  'sn.import',
  'sn.freeze',
  'sn.unfreeze',
  'sn.scrap',
  'operations.skip',
  'members.invite',
  'members.edit',
  'members.role.update',
  'members.remove',
  'customers.create',
  'customers.delete',
  'device-types.create',
  'device-types.update',
  'device-types.delete'
] as const

export type AppPagePath = (typeof APP_PAGE_PATHS)[number]
export type ButtonPermissionKey = (typeof BUTTON_PERMISSION_KEYS)[number]

export type RolePermissionMatrix = Record<string, Partial<Record<AppPagePath, ButtonPermissionKey[]>>>

const APP_PAGE_PATH_SET = new Set<string>(APP_PAGE_PATHS)
const PUBLIC_PATH_SET = new Set<string>([LOGIN_PATH, FORBIDDEN_PATH])

const ROLE_PERMISSION_MATRIX: RolePermissionMatrix = {
  admin: {
    '/': [],
    '/work-orders': ['work-orders.create', 'work-orders.edit', 'work-orders.dispatch', 'work-orders.pause', 'work-orders.resume', 'work-orders.close'],
    '/sn-management': ['sn.import', 'sn.freeze', 'sn.unfreeze', 'sn.scrap'],
    '/operations': ['operations.skip'],
    '/inbox': [],
    '/customers': ['customers.create', 'customers.delete'],
    '/base-info/device-types': ['device-types.create', 'device-types.update', 'device-types.delete'],
    '/settings': [],
    '/settings/members': ['members.invite', 'members.edit', 'members.role.update', 'members.remove'],
    '/settings/notifications': [],
    '/settings/security': []
  },
  qa_manager: {
    '/': [],
    '/work-orders': ['work-orders.create', 'work-orders.dispatch', 'work-orders.pause', 'work-orders.resume', 'work-orders.close'],
    '/sn-management': ['sn.import', 'sn.freeze', 'sn.unfreeze', 'sn.scrap'],
    '/operations': [],
    '/inbox': [],
    '/customers': [],
    '/base-info/device-types': ['device-types.create', 'device-types.update'],
    '/settings': [],
    '/settings/members': ['members.edit', 'members.role.update'],
    '/settings/notifications': [],
    '/settings/security': []
  },
  operator: {
    '/': [],
    '/work-orders': ['work-orders.dispatch', 'work-orders.pause', 'work-orders.resume'],
    '/sn-management': [],
    '/operations': [],
    '/inbox': [],
    '/customers': [],
    '/base-info/device-types': []
  }
}

function normalizePath(path: string) {
  const [pathname] = path.split('?')
  const purePath = pathname ? pathname.split('#')[0] : '/'
  const trimmed = (purePath ?? '/').trim() || '/'

  if (trimmed.length > 1 && trimmed.endsWith('/')) {
    return trimmed.slice(0, -1)
  }

  return trimmed
}

export function normalizePagePath(path: string) {
  return normalizePath(path)
}

export function toPagePath(path: string): AppPagePath | null {
  const normalized = normalizePath(path)

  if (!APP_PAGE_PATH_SET.has(normalized)) {
    return null
  }

  return normalized as AppPagePath
}

function getRolePagePermissions(role: string) {
  return ROLE_PERMISSION_MATRIX[role] ?? null
}

export function getAccessiblePagesForRoles(roles: string[]): Set<AppPagePath> {
  const pages = new Set<AppPagePath>()

  for (const role of roles) {
    const rolePermissions = getRolePagePermissions(role)
    if (!rolePermissions) {
      continue
    }

    for (const rawPagePath of Object.keys(rolePermissions)) {
      const pagePath = toPagePath(rawPagePath)
      if (pagePath) {
        pages.add(pagePath)
      }
    }
  }

  return pages
}

export function getAllowedButtonsForPage(roles: string[], pagePath: AppPagePath): Set<ButtonPermissionKey> {
  const buttons = new Set<ButtonPermissionKey>()

  for (const role of roles) {
    const rolePermissions = getRolePagePermissions(role)
    if (!rolePermissions || !rolePermissions[pagePath]) {
      continue
    }

    for (const button of rolePermissions[pagePath] ?? []) {
      buttons.add(button)
    }
  }

  return buttons
}

export function isPublicPath(path: string) {
  return PUBLIC_PATH_SET.has(normalizePath(path))
}

export function isProtectedPage(path: string) {
  return toPagePath(path) !== null
}

export function canAccessPageByRoles(roles: string[], pagePath: string) {
  const pageKey = toPagePath(pagePath)
  if (!pageKey) {
    return true
  }

  const accessiblePages = getAccessiblePagesForRoles(roles)
  return accessiblePages.has(pageKey)
}

export function canUseButtonByRoles(roles: string[], pagePath: string, buttonKey: ButtonPermissionKey) {
  const pageKey = toPagePath(pagePath)
  if (!pageKey) {
    return false
  }

  const accessiblePages = getAccessiblePagesForRoles(roles)
  if (!accessiblePages.has(pageKey)) {
    return false
  }

  const allowedButtons = getAllowedButtonsForPage(roles, pageKey)

  return allowedButtons.has(buttonKey)
}