feat(core) : add usePermissions composable and rbac roles admin front
This commit is contained in:
@@ -0,0 +1,116 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<div class="flex items-center justify-between">
|
||||||
|
<h2 class="text-lg font-bold text-neutral-900">{{ $t('admin.roles.title') }}</h2>
|
||||||
|
<MalioButton
|
||||||
|
icon-name="mdi:plus"
|
||||||
|
icon-position="left"
|
||||||
|
button-class="w-auto px-4"
|
||||||
|
:label="$t('admin.roles.addRole')"
|
||||||
|
@click="openCreate"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
:columns="columns"
|
||||||
|
:items="items"
|
||||||
|
:loading="isLoading"
|
||||||
|
:empty-message="$t('admin.roles.empty')"
|
||||||
|
@row-click="openEdit"
|
||||||
|
>
|
||||||
|
<template #cell-isSystem="{ item }">
|
||||||
|
<span
|
||||||
|
v-if="item.isSystem"
|
||||||
|
class="rounded-full bg-primary-100 px-2 py-0.5 text-xs font-semibold text-primary-600"
|
||||||
|
>
|
||||||
|
{{ $t('admin.roles.system') }}
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
<template #cell-permissions="{ item }">
|
||||||
|
<span class="text-neutral-600">{{ item.permissions.length }}</span>
|
||||||
|
</template>
|
||||||
|
<template #actions="{ item }">
|
||||||
|
<MalioButtonIcon
|
||||||
|
v-if="!item.isSystem"
|
||||||
|
icon="mdi:delete-outline"
|
||||||
|
:aria-label="$t('common.delete')"
|
||||||
|
variant="ghost"
|
||||||
|
icon-size="20"
|
||||||
|
button-class="text-neutral-400 hover:text-red-500"
|
||||||
|
@click.stop="handleDelete(item.id)"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
|
||||||
|
<RoleDrawer
|
||||||
|
v-model="drawerOpen"
|
||||||
|
:item="selectedItem"
|
||||||
|
:permissions="permissions"
|
||||||
|
@saved="onSaved"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Role } from '~/modules/core/services/roles'
|
||||||
|
import { useRoleService } from '~/modules/core/services/roles'
|
||||||
|
import type { Permission } from '~/modules/core/services/permissions'
|
||||||
|
import { usePermissionService } from '~/modules/core/services/permissions'
|
||||||
|
|
||||||
|
import type { DataTableColumn } from '~/components/ui/DataTable.vue'
|
||||||
|
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const columns = computed<DataTableColumn[]>(() => [
|
||||||
|
{ key: 'label', label: t('admin.roles.label'), primary: true },
|
||||||
|
{ key: 'code', label: t('admin.roles.code') },
|
||||||
|
{ key: 'permissions', label: t('admin.roles.permissions') },
|
||||||
|
{ key: 'isSystem', label: '' },
|
||||||
|
])
|
||||||
|
|
||||||
|
const roleService = useRoleService()
|
||||||
|
const permissionService = usePermissionService()
|
||||||
|
|
||||||
|
const items = ref<Role[]>([])
|
||||||
|
const permissions = ref<Permission[]>([])
|
||||||
|
const isLoading = ref(true)
|
||||||
|
const drawerOpen = ref(false)
|
||||||
|
const selectedItem = ref<Role | null>(null)
|
||||||
|
|
||||||
|
async function loadItems() {
|
||||||
|
isLoading.value = true
|
||||||
|
try {
|
||||||
|
items.value = await roleService.list()
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadPermissions() {
|
||||||
|
permissions.value = await permissionService.list()
|
||||||
|
}
|
||||||
|
|
||||||
|
function openCreate() {
|
||||||
|
selectedItem.value = null
|
||||||
|
drawerOpen.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
function openEdit(item: Role) {
|
||||||
|
selectedItem.value = item
|
||||||
|
drawerOpen.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
async function handleDelete(id: number) {
|
||||||
|
await roleService.remove(id)
|
||||||
|
await loadItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
async function onSaved() {
|
||||||
|
await loadItems()
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
loadItems()
|
||||||
|
loadPermissions()
|
||||||
|
})
|
||||||
|
</script>
|
||||||
@@ -0,0 +1,186 @@
|
|||||||
|
<template>
|
||||||
|
<MalioDrawer v-model="isOpen">
|
||||||
|
<template #header>
|
||||||
|
<h2 class="text-xl font-bold">
|
||||||
|
{{ isEditing ? $t('admin.roles.editRole') : $t('admin.roles.addRole') }}
|
||||||
|
</h2>
|
||||||
|
</template>
|
||||||
|
<form class="flex flex-col gap-3" @submit.prevent="handleSubmit">
|
||||||
|
<MalioInputText
|
||||||
|
v-model="form.code"
|
||||||
|
:label="$t('admin.roles.code')"
|
||||||
|
input-class="w-full"
|
||||||
|
:disabled="isEditing"
|
||||||
|
:hint="isEditing ? $t('admin.roles.codeImmutable') : $t('admin.roles.codeHint')"
|
||||||
|
:error="touched.code && !codeValid ? $t('admin.roles.codeInvalid') : ''"
|
||||||
|
@blur="touched.code = true"
|
||||||
|
/>
|
||||||
|
<MalioInputText
|
||||||
|
v-model="form.label"
|
||||||
|
:label="$t('admin.roles.label')"
|
||||||
|
input-class="w-full"
|
||||||
|
:error="touched.label && !form.label.trim() ? $t('admin.roles.labelRequired') : ''"
|
||||||
|
@blur="touched.label = true"
|
||||||
|
/>
|
||||||
|
<MalioInputTextArea
|
||||||
|
v-model="form.description"
|
||||||
|
:label="$t('admin.roles.description')"
|
||||||
|
input-class="w-full"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div class="mt-2">
|
||||||
|
<label class="text-sm font-semibold text-neutral-700">
|
||||||
|
{{ $t('admin.roles.permissions') }}
|
||||||
|
</label>
|
||||||
|
<p v-if="permissions.length === 0" class="mt-2 text-xs text-neutral-400">
|
||||||
|
{{ $t('admin.roles.noPermissions') }}
|
||||||
|
</p>
|
||||||
|
<div
|
||||||
|
v-for="group in groupedPermissions"
|
||||||
|
:key="group.module"
|
||||||
|
class="mt-3 rounded-lg border border-neutral-200 p-3"
|
||||||
|
>
|
||||||
|
<p class="mb-2 text-xs font-bold uppercase tracking-wide text-neutral-500">
|
||||||
|
{{ group.module }}
|
||||||
|
</p>
|
||||||
|
<div class="flex flex-col gap-2">
|
||||||
|
<label
|
||||||
|
v-for="perm in group.permissions"
|
||||||
|
:key="perm.id"
|
||||||
|
class="flex items-start gap-2 text-sm text-neutral-700"
|
||||||
|
>
|
||||||
|
<input
|
||||||
|
v-model="form.permissions"
|
||||||
|
type="checkbox"
|
||||||
|
:value="perm['@id']"
|
||||||
|
class="mt-0.5 rounded border-neutral-300"
|
||||||
|
/>
|
||||||
|
<span>
|
||||||
|
{{ perm.label }}
|
||||||
|
<span class="block text-xs text-neutral-400">{{ perm.code }}</span>
|
||||||
|
</span>
|
||||||
|
</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="mt-4 flex justify-end">
|
||||||
|
<MalioButton
|
||||||
|
:label="$t('common.save')"
|
||||||
|
button-class="w-auto px-6"
|
||||||
|
:disabled="isSubmitting"
|
||||||
|
@click="handleSubmit"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</MalioDrawer>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import type { Role, RoleWrite } from '~/modules/core/services/roles'
|
||||||
|
import { useRoleService } from '~/modules/core/services/roles'
|
||||||
|
import type { Permission } from '~/modules/core/services/permissions'
|
||||||
|
|
||||||
|
const props = defineProps<{
|
||||||
|
modelValue: boolean
|
||||||
|
item: Role | null
|
||||||
|
permissions: Permission[]
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'update:modelValue', value: boolean): void
|
||||||
|
(e: 'saved'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const isOpen = computed({
|
||||||
|
get: () => props.modelValue,
|
||||||
|
set: (v) => emit('update:modelValue', v),
|
||||||
|
})
|
||||||
|
|
||||||
|
const isEditing = computed(() => !!props.item)
|
||||||
|
const isSubmitting = ref(false)
|
||||||
|
|
||||||
|
const form = reactive({
|
||||||
|
code: '',
|
||||||
|
label: '',
|
||||||
|
description: '',
|
||||||
|
permissions: [] as string[],
|
||||||
|
})
|
||||||
|
|
||||||
|
const touched = reactive({
|
||||||
|
code: false,
|
||||||
|
label: false,
|
||||||
|
})
|
||||||
|
|
||||||
|
const codeValid = computed(() => /^[a-z][a-z0-9_]*$/.test(form.code))
|
||||||
|
|
||||||
|
const groupedPermissions = computed(() => {
|
||||||
|
const byModule = new Map<string, Permission[]>()
|
||||||
|
for (const perm of props.permissions) {
|
||||||
|
const list = byModule.get(perm.module) ?? []
|
||||||
|
list.push(perm)
|
||||||
|
byModule.set(perm.module, list)
|
||||||
|
}
|
||||||
|
return [...byModule.entries()]
|
||||||
|
.map(([module, permissions]) => ({ module, permissions }))
|
||||||
|
.sort((a, b) => a.module.localeCompare(b.module))
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(() => props.modelValue, (open) => {
|
||||||
|
if (open) {
|
||||||
|
if (props.item) {
|
||||||
|
form.code = props.item.code
|
||||||
|
form.label = props.item.label
|
||||||
|
form.description = props.item.description ?? ''
|
||||||
|
form.permissions = props.item.permissions
|
||||||
|
.map((p) => p['@id'])
|
||||||
|
.filter((iri): iri is string => !!iri)
|
||||||
|
} else {
|
||||||
|
form.code = ''
|
||||||
|
form.label = ''
|
||||||
|
form.description = ''
|
||||||
|
form.permissions = []
|
||||||
|
}
|
||||||
|
touched.code = false
|
||||||
|
touched.label = false
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const { create, update } = useRoleService()
|
||||||
|
|
||||||
|
async function handleSubmit() {
|
||||||
|
touched.code = true
|
||||||
|
touched.label = true
|
||||||
|
if (!form.label.trim()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if (!isEditing.value && !codeValid.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
isSubmitting.value = true
|
||||||
|
try {
|
||||||
|
if (isEditing.value && props.item) {
|
||||||
|
const payload: Partial<RoleWrite> = {
|
||||||
|
label: form.label.trim(),
|
||||||
|
description: form.description.trim() || null,
|
||||||
|
permissions: form.permissions,
|
||||||
|
}
|
||||||
|
await update(props.item.id, payload)
|
||||||
|
} else {
|
||||||
|
const payload: RoleWrite = {
|
||||||
|
code: form.code.trim(),
|
||||||
|
label: form.label.trim(),
|
||||||
|
description: form.description.trim() || null,
|
||||||
|
permissions: form.permissions,
|
||||||
|
}
|
||||||
|
await create(payload)
|
||||||
|
}
|
||||||
|
|
||||||
|
emit('saved')
|
||||||
|
isOpen.value = false
|
||||||
|
} finally {
|
||||||
|
isSubmitting.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
@@ -195,6 +195,27 @@
|
|||||||
"addUser": "Ajouter un utilisateur",
|
"addUser": "Ajouter un utilisateur",
|
||||||
"editUser": "Modifier un utilisateur"
|
"editUser": "Modifier un utilisateur"
|
||||||
},
|
},
|
||||||
|
"admin": {
|
||||||
|
"roles": {
|
||||||
|
"title": "Rôles",
|
||||||
|
"addRole": "Ajouter un rôle",
|
||||||
|
"editRole": "Modifier un rôle",
|
||||||
|
"empty": "Aucun rôle trouvé.",
|
||||||
|
"system": "Système",
|
||||||
|
"code": "Code",
|
||||||
|
"codeHint": "Identifiant technique en snake_case (immuable).",
|
||||||
|
"codeImmutable": "Le code ne peut pas être modifié après création.",
|
||||||
|
"codeInvalid": "Code invalide (attendu snake_case : minuscules, chiffres et underscores).",
|
||||||
|
"label": "Libellé",
|
||||||
|
"labelRequired": "Le libellé est requis.",
|
||||||
|
"description": "Description",
|
||||||
|
"permissions": "Permissions",
|
||||||
|
"noPermissions": "Aucune permission disponible.",
|
||||||
|
"created": "Rôle créé avec succès.",
|
||||||
|
"updated": "Rôle mis à jour avec succès.",
|
||||||
|
"deleted": "Rôle supprimé avec succès."
|
||||||
|
}
|
||||||
|
},
|
||||||
"timeEntries": {
|
"timeEntries": {
|
||||||
"created": "Temps enregistré",
|
"created": "Temps enregistré",
|
||||||
"updated": "Temps modifié",
|
"updated": "Temps modifié",
|
||||||
|
|||||||
@@ -0,0 +1,27 @@
|
|||||||
|
export function usePermissions() {
|
||||||
|
const auth = useAuthStore()
|
||||||
|
|
||||||
|
function isAdmin(): boolean {
|
||||||
|
return auth.user?.roles?.includes('ROLE_ADMIN') ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
function can(code: string): boolean {
|
||||||
|
if (!auth.user) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if (isAdmin()) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return auth.user.effectivePermissions?.includes(code) ?? false
|
||||||
|
}
|
||||||
|
|
||||||
|
function canAny(codes: string[]): boolean {
|
||||||
|
return codes.some((c) => can(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
function canAll(codes: string[]): boolean {
|
||||||
|
return codes.every((c) => can(c))
|
||||||
|
}
|
||||||
|
|
||||||
|
return { can, canAny, canAll, isAdmin }
|
||||||
|
}
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import type { HydraCollection } from '~/utils/api'
|
||||||
|
import { extractHydraMembers } from '~/utils/api'
|
||||||
|
|
||||||
|
export type Permission = {
|
||||||
|
id: number
|
||||||
|
'@id'?: string
|
||||||
|
code: string
|
||||||
|
label: string
|
||||||
|
module: string
|
||||||
|
orphan?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function usePermissionService() {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
async function list(): Promise<Permission[]> {
|
||||||
|
const data = await api.get<HydraCollection<Permission>>('/permissions')
|
||||||
|
return extractHydraMembers(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { list }
|
||||||
|
}
|
||||||
@@ -0,0 +1,50 @@
|
|||||||
|
import type { Permission } from './permissions'
|
||||||
|
import type { HydraCollection } from '~/utils/api'
|
||||||
|
import { extractHydraMembers } from '~/utils/api'
|
||||||
|
|
||||||
|
export type Role = {
|
||||||
|
id: number
|
||||||
|
'@id'?: string
|
||||||
|
code: string
|
||||||
|
label: string
|
||||||
|
description?: string | null
|
||||||
|
isSystem: boolean
|
||||||
|
permissions: Permission[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type RoleWrite = {
|
||||||
|
code?: string
|
||||||
|
label: string
|
||||||
|
description?: string | null
|
||||||
|
/** IRIs of the granted permissions (e.g. /api/permissions/3). */
|
||||||
|
permissions: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRoleService() {
|
||||||
|
const api = useApi()
|
||||||
|
|
||||||
|
async function list(): Promise<Role[]> {
|
||||||
|
const data = await api.get<HydraCollection<Role>>('/roles')
|
||||||
|
return extractHydraMembers(data)
|
||||||
|
}
|
||||||
|
|
||||||
|
async function create(payload: RoleWrite): Promise<Role> {
|
||||||
|
return api.post<Role>('/roles', payload as Record<string, unknown>, {
|
||||||
|
toastSuccessKey: 'admin.roles.created',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function update(id: number, payload: Partial<RoleWrite>): Promise<Role> {
|
||||||
|
return api.patch<Role>(`/roles/${id}`, payload as Record<string, unknown>, {
|
||||||
|
toastSuccessKey: 'admin.roles.updated',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async function remove(id: number): Promise<void> {
|
||||||
|
await api.delete(`/roles/${id}`, {}, {
|
||||||
|
toastSuccessKey: 'admin.roles.deleted',
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return { list, create, update, remove }
|
||||||
|
}
|
||||||
@@ -6,7 +6,7 @@
|
|||||||
<div class="mt-6 border-b border-neutral-200 overflow-x-auto">
|
<div class="mt-6 border-b border-neutral-200 overflow-x-auto">
|
||||||
<nav class="flex gap-4 sm:gap-6">
|
<nav class="flex gap-4 sm:gap-6">
|
||||||
<button
|
<button
|
||||||
v-for="tab in tabs"
|
v-for="tab in visibleTabs"
|
||||||
:key="tab.key"
|
:key="tab.key"
|
||||||
class="whitespace-nowrap px-1 pb-3 text-sm font-semibold transition"
|
class="whitespace-nowrap px-1 pb-3 text-sm font-semibold transition"
|
||||||
:class="activeTab === tab.key
|
:class="activeTab === tab.key
|
||||||
@@ -27,6 +27,7 @@
|
|||||||
<AdminPriorityTab v-if="activeTab === 'priorities'" />
|
<AdminPriorityTab v-if="activeTab === 'priorities'" />
|
||||||
<AdminTagTab v-if="activeTab === 'tags'" />
|
<AdminTagTab v-if="activeTab === 'tags'" />
|
||||||
<AdminUserTab v-if="activeTab === 'users'" />
|
<AdminUserTab v-if="activeTab === 'users'" />
|
||||||
|
<AdminRoleTab v-if="activeTab === 'roles' && canViewRoles" />
|
||||||
<AdminGiteaTab v-if="activeTab === 'gitea'" />
|
<AdminGiteaTab v-if="activeTab === 'gitea'" />
|
||||||
<AdminBookStackTab v-if="activeTab === 'bookstack'" />
|
<AdminBookStackTab v-if="activeTab === 'bookstack'" />
|
||||||
<AdminZimbraTab v-if="activeTab === 'zimbra'" />
|
<AdminZimbraTab v-if="activeTab === 'zimbra'" />
|
||||||
@@ -41,6 +42,11 @@
|
|||||||
definePageMeta({ middleware: ['admin'] })
|
definePageMeta({ middleware: ['admin'] })
|
||||||
useHead({ title: 'Administration' })
|
useHead({ title: 'Administration' })
|
||||||
|
|
||||||
|
const { can } = usePermissions()
|
||||||
|
const { t } = useI18n()
|
||||||
|
|
||||||
|
const canViewRoles = computed(() => can('core.roles.view'))
|
||||||
|
|
||||||
const tabs = [
|
const tabs = [
|
||||||
{ key: 'clients', label: 'Clients' },
|
{ key: 'clients', label: 'Clients' },
|
||||||
{ key: 'workflows', label: 'Workflows' },
|
{ key: 'workflows', label: 'Workflows' },
|
||||||
@@ -48,6 +54,7 @@ const tabs = [
|
|||||||
{ key: 'priorities', label: 'Priorités' },
|
{ key: 'priorities', label: 'Priorités' },
|
||||||
{ key: 'tags', label: 'Tags' },
|
{ key: 'tags', label: 'Tags' },
|
||||||
{ key: 'users', label: 'Utilisateurs' },
|
{ key: 'users', label: 'Utilisateurs' },
|
||||||
|
{ key: 'roles', label: t('admin.roles.title'), permission: 'core.roles.view' },
|
||||||
{ key: 'gitea', label: 'Gitea' },
|
{ key: 'gitea', label: 'Gitea' },
|
||||||
{ key: 'bookstack', label: 'BookStack' },
|
{ key: 'bookstack', label: 'BookStack' },
|
||||||
{ key: 'zimbra', label: 'Zimbra' },
|
{ key: 'zimbra', label: 'Zimbra' },
|
||||||
@@ -58,5 +65,9 @@ const tabs = [
|
|||||||
|
|
||||||
type TabKey = typeof tabs[number]['key']
|
type TabKey = typeof tabs[number]['key']
|
||||||
|
|
||||||
|
const visibleTabs = computed(() =>
|
||||||
|
tabs.filter((tab) => !('permission' in tab) || can(tab.permission)),
|
||||||
|
)
|
||||||
|
|
||||||
const activeTab = ref<TabKey>('clients')
|
const activeTab = ref<TabKey>('clients')
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ export type UserData = {
|
|||||||
firstName?: string | null
|
firstName?: string | null
|
||||||
lastName?: string | null
|
lastName?: string | null
|
||||||
roles: string[]
|
roles: string[]
|
||||||
|
effectivePermissions?: string[]
|
||||||
avatarUrl?: string | null
|
avatarUrl?: string | null
|
||||||
apiToken?: string | null
|
apiToken?: string | null
|
||||||
// HR / absence management
|
// HR / absence management
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ use Symfony\Component\Serializer\Attribute\Groups;
|
|||||||
#[ORM\Index(name: 'idx_permission_orphan', columns: ['orphan'])]
|
#[ORM\Index(name: 'idx_permission_orphan', columns: ['orphan'])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new GetCollection(),
|
new GetCollection(paginationEnabled: false),
|
||||||
new Get(),
|
new Get(),
|
||||||
],
|
],
|
||||||
normalizationContext: ['groups' => ['permission:read']],
|
normalizationContext: ['groups' => ['permission:read']],
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ use Symfony\Component\Serializer\Attribute\SerializedName;
|
|||||||
#[ORM\Index(name: 'idx_role_is_system', columns: ['is_system'])]
|
#[ORM\Index(name: 'idx_role_is_system', columns: ['is_system'])]
|
||||||
#[ApiResource(
|
#[ApiResource(
|
||||||
operations: [
|
operations: [
|
||||||
new GetCollection(security: "is_granted('core.roles.view')"),
|
new GetCollection(security: "is_granted('core.roles.view')", paginationEnabled: false),
|
||||||
new Get(security: "is_granted('core.roles.view')"),
|
new Get(security: "is_granted('core.roles.view')"),
|
||||||
new Post(security: "is_granted('core.roles.manage')", processor: RoleProcessor::class),
|
new Post(security: "is_granted('core.roles.manage')", processor: RoleProcessor::class),
|
||||||
new Patch(security: "is_granted('core.roles.manage')", processor: RoleProcessor::class),
|
new Patch(security: "is_granted('core.roles.manage')", processor: RoleProcessor::class),
|
||||||
|
|||||||
Reference in New Issue
Block a user