feat: Add role management functionality with CRUD operations and update user roles handling
This commit is contained in:
parent
465515191f
commit
07c675968a
@ -30,6 +30,9 @@
|
|||||||
<RouterLink to="/users" v-if="authStore.isAdmin" @click="closeMobileMenu">
|
<RouterLink to="/users" v-if="authStore.isAdmin" @click="closeMobileMenu">
|
||||||
<i class="pi pi-user-edit"></i> Benutzerverwaltung
|
<i class="pi pi-user-edit"></i> Benutzerverwaltung
|
||||||
</RouterLink>
|
</RouterLink>
|
||||||
|
<RouterLink to="/roles" v-if="authStore.isAdmin" @click="closeMobileMenu">
|
||||||
|
<i class="pi pi-shield"></i> Rollenverwaltung
|
||||||
|
</RouterLink>
|
||||||
</nav>
|
</nav>
|
||||||
|
|
||||||
<div class="user-info" v-if="authStore.isAuthenticated">
|
<div class="user-info" v-if="authStore.isAuthenticated">
|
||||||
|
|||||||
@ -4,6 +4,7 @@ import ContactList from './views/ContactList.vue';
|
|||||||
import CompanyList from './views/CompanyList.vue';
|
import CompanyList from './views/CompanyList.vue';
|
||||||
import DealList from './views/DealList.vue';
|
import DealList from './views/DealList.vue';
|
||||||
import UserManagement from './views/UserManagement.vue';
|
import UserManagement from './views/UserManagement.vue';
|
||||||
|
import RoleManagement from './views/RoleManagement.vue';
|
||||||
|
|
||||||
const routes = [
|
const routes = [
|
||||||
{ path: '/', name: 'dashboard', component: Dashboard },
|
{ path: '/', name: 'dashboard', component: Dashboard },
|
||||||
@ -11,6 +12,7 @@ const routes = [
|
|||||||
{ path: '/companies', name: 'companies', component: CompanyList },
|
{ path: '/companies', name: 'companies', component: CompanyList },
|
||||||
{ path: '/deals', name: 'deals', component: DealList },
|
{ path: '/deals', name: 'deals', component: DealList },
|
||||||
{ path: '/users', name: 'users', component: UserManagement, meta: { requiresAdmin: true } },
|
{ path: '/users', name: 'users', component: UserManagement, meta: { requiresAdmin: true } },
|
||||||
|
{ path: '/roles', name: 'roles', component: RoleManagement, meta: { requiresAdmin: true } },
|
||||||
];
|
];
|
||||||
|
|
||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
|
|||||||
688
assets/js/views/RoleManagement.vue
Normal file
688
assets/js/views/RoleManagement.vue
Normal file
@ -0,0 +1,688 @@
|
|||||||
|
<template>
|
||||||
|
<div class="role-management">
|
||||||
|
<div class="page-header">
|
||||||
|
<h2>Rollenverwaltung</h2>
|
||||||
|
<Button
|
||||||
|
label="Neue Rolle"
|
||||||
|
icon="pi pi-plus"
|
||||||
|
@click="openCreateDialog"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<DataTable
|
||||||
|
:value="roles"
|
||||||
|
:loading="loading"
|
||||||
|
stripedRows
|
||||||
|
showGridlines
|
||||||
|
>
|
||||||
|
<Column field="name" header="Name" sortable style="width: 200px" />
|
||||||
|
<Column field="description" header="Beschreibung" sortable />
|
||||||
|
<Column field="isSystem" header="System-Rolle" sortable style="width: 150px">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<Tag :value="data.isSystem ? 'Ja' : 'Nein'" :severity="data.isSystem ? 'warning' : 'secondary'" />
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
<Column header="Aktionen" style="width: 150px">
|
||||||
|
<template #body="{ data }">
|
||||||
|
<div class="action-buttons">
|
||||||
|
<Button
|
||||||
|
icon="pi pi-pencil"
|
||||||
|
severity="info"
|
||||||
|
text
|
||||||
|
rounded
|
||||||
|
@click="openEditDialog(data)"
|
||||||
|
:disabled="data.isSystem"
|
||||||
|
v-tooltip.top="data.isSystem ? 'System-Rollen können nicht bearbeitet werden' : 'Bearbeiten'"
|
||||||
|
/>
|
||||||
|
<Button
|
||||||
|
icon="pi pi-trash"
|
||||||
|
severity="danger"
|
||||||
|
text
|
||||||
|
rounded
|
||||||
|
@click="confirmDelete(data)"
|
||||||
|
:disabled="data.isSystem"
|
||||||
|
v-tooltip.top="data.isSystem ? 'System-Rollen können nicht gelöscht werden' : 'Löschen'"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</Column>
|
||||||
|
</DataTable>
|
||||||
|
|
||||||
|
<!-- Create/Edit Dialog -->
|
||||||
|
<Dialog
|
||||||
|
v-model:visible="dialogVisible"
|
||||||
|
:header="isEditMode ? 'Rolle bearbeiten' : 'Neue Rolle erstellen'"
|
||||||
|
modal
|
||||||
|
:style="{ width: '90vw', maxWidth: '900px' }"
|
||||||
|
class="role-dialog"
|
||||||
|
>
|
||||||
|
<div class="form-container">
|
||||||
|
<div class="form-field">
|
||||||
|
<label for="name">Name *</label>
|
||||||
|
<InputText
|
||||||
|
id="name"
|
||||||
|
v-model="formData.name"
|
||||||
|
:class="{ 'p-invalid': errors.name }"
|
||||||
|
placeholder="z.B. Vertriebsmitarbeiter"
|
||||||
|
/>
|
||||||
|
<small v-if="errors.name" class="p-error">{{ errors.name }}</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-field">
|
||||||
|
<label for="description">Beschreibung</label>
|
||||||
|
<Textarea
|
||||||
|
id="description"
|
||||||
|
v-model="formData.description"
|
||||||
|
rows="3"
|
||||||
|
placeholder="Beschreibung der Rolle und ihrer Aufgaben"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="permissions-section">
|
||||||
|
<h3>Modulberechtigungen</h3>
|
||||||
|
<div class="permissions-matrix">
|
||||||
|
<div class="matrix-header">
|
||||||
|
<div class="module-cell">Modul</div>
|
||||||
|
<div class="permission-cell" v-tooltip.top="'Benutzer kann Daten ansehen'">
|
||||||
|
<i class="pi pi-eye"></i>
|
||||||
|
<span>Lesen</span>
|
||||||
|
</div>
|
||||||
|
<div class="permission-cell" v-tooltip.top="'Benutzer kann neue Einträge erstellen'">
|
||||||
|
<i class="pi pi-plus"></i>
|
||||||
|
<span>Erstellen</span>
|
||||||
|
</div>
|
||||||
|
<div class="permission-cell" v-tooltip.top="'Benutzer kann bestehende Einträge bearbeiten'">
|
||||||
|
<i class="pi pi-pencil"></i>
|
||||||
|
<span>Bearbeiten</span>
|
||||||
|
</div>
|
||||||
|
<div class="permission-cell" v-tooltip.top="'Benutzer kann Einträge löschen'">
|
||||||
|
<i class="pi pi-trash"></i>
|
||||||
|
<span>Löschen</span>
|
||||||
|
</div>
|
||||||
|
<div class="permission-cell" v-tooltip.top="'Benutzer kann Daten exportieren'">
|
||||||
|
<i class="pi pi-download"></i>
|
||||||
|
<span>Export</span>
|
||||||
|
</div>
|
||||||
|
<div class="permission-cell" v-tooltip.top="'Benutzer kann Modul-Einstellungen verwalten'">
|
||||||
|
<i class="pi pi-cog"></i>
|
||||||
|
<span>Verwalten</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="module in modules"
|
||||||
|
:key="module.id"
|
||||||
|
class="matrix-row"
|
||||||
|
>
|
||||||
|
<div class="module-cell">
|
||||||
|
<i :class="module.icon"></i>
|
||||||
|
<span>{{ module.name }}</span>
|
||||||
|
</div>
|
||||||
|
<div class="permission-cell">
|
||||||
|
<Checkbox
|
||||||
|
v-model="getPermission(module.id).canView"
|
||||||
|
:binary="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="permission-cell">
|
||||||
|
<Checkbox
|
||||||
|
v-model="getPermission(module.id).canCreate"
|
||||||
|
:binary="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="permission-cell">
|
||||||
|
<Checkbox
|
||||||
|
v-model="getPermission(module.id).canEdit"
|
||||||
|
:binary="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="permission-cell">
|
||||||
|
<Checkbox
|
||||||
|
v-model="getPermission(module.id).canDelete"
|
||||||
|
:binary="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="permission-cell">
|
||||||
|
<Checkbox
|
||||||
|
v-model="getPermission(module.id).canExport"
|
||||||
|
:binary="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="permission-cell">
|
||||||
|
<Checkbox
|
||||||
|
v-model="getPermission(module.id).canManage"
|
||||||
|
:binary="true"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<Button label="Abbrechen" severity="secondary" @click="dialogVisible = false" />
|
||||||
|
<Button label="Speichern" @click="saveRole" :loading="saving" />
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
|
||||||
|
<!-- Delete Confirmation -->
|
||||||
|
<Dialog
|
||||||
|
v-model:visible="deleteDialogVisible"
|
||||||
|
header="Rolle löschen"
|
||||||
|
modal
|
||||||
|
:style="{ width: '450px' }"
|
||||||
|
>
|
||||||
|
<p>Möchten Sie die Rolle <strong>{{ roleToDelete?.name }}</strong> wirklich löschen?</p>
|
||||||
|
<p class="text-muted">Diese Aktion kann nicht rückgängig gemacht werden.</p>
|
||||||
|
|
||||||
|
<template #footer>
|
||||||
|
<Button label="Abbrechen" severity="secondary" @click="deleteDialogVisible = false" />
|
||||||
|
<Button label="Löschen" severity="danger" @click="deleteRole" :loading="deleting" />
|
||||||
|
</template>
|
||||||
|
</Dialog>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue';
|
||||||
|
import { useAuthStore } from '../stores/auth';
|
||||||
|
import DataTable from 'primevue/datatable';
|
||||||
|
import Column from 'primevue/column';
|
||||||
|
import Button from 'primevue/button';
|
||||||
|
import Dialog from 'primevue/dialog';
|
||||||
|
import InputText from 'primevue/inputtext';
|
||||||
|
import Textarea from 'primevue/textarea';
|
||||||
|
import Checkbox from 'primevue/checkbox';
|
||||||
|
import Tag from 'primevue/tag';
|
||||||
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
|
||||||
|
const authStore = useAuthStore();
|
||||||
|
const toast = useToast();
|
||||||
|
|
||||||
|
const loading = ref(false);
|
||||||
|
const saving = ref(false);
|
||||||
|
const deleting = ref(false);
|
||||||
|
const roles = ref([]);
|
||||||
|
const modules = ref([]);
|
||||||
|
const dialogVisible = ref(false);
|
||||||
|
const deleteDialogVisible = ref(false);
|
||||||
|
const isEditMode = ref(false);
|
||||||
|
const roleToDelete = ref(null);
|
||||||
|
|
||||||
|
const formData = ref({
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
isSystem: false,
|
||||||
|
permissions: []
|
||||||
|
});
|
||||||
|
|
||||||
|
const errors = ref({});
|
||||||
|
|
||||||
|
const fetchRoles = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/roles', {
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/ld+json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
roles.value = data.member || [];
|
||||||
|
console.log('Loaded roles:', roles.value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching roles:', error);
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Fehler',
|
||||||
|
detail: 'Rollen konnten nicht geladen werden',
|
||||||
|
life: 5000
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchModules = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/modules', {
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/ld+json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Modules API response status:', response.status);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
console.error('Modules API error:', response.status, errorText);
|
||||||
|
throw new Error(`HTTP error! status: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = await response.json();
|
||||||
|
console.log('Modules API raw data:', data);
|
||||||
|
console.log('First module:', data.member[0]);
|
||||||
|
console.log('First module isActive:', data.member[0]?.isActive);
|
||||||
|
|
||||||
|
const allModules = data.member || data['hydra:member'] || [];
|
||||||
|
console.log('All modules before filter:', allModules);
|
||||||
|
|
||||||
|
modules.value = allModules
|
||||||
|
.filter(m => {
|
||||||
|
console.log(`Module ${m.name}: isActive = ${m.isActive}`);
|
||||||
|
return m.isActive;
|
||||||
|
})
|
||||||
|
.sort((a, b) => a.sortOrder - b.sortOrder);
|
||||||
|
|
||||||
|
console.log('Loaded modules:', modules.value);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching modules:', error);
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Fehler',
|
||||||
|
detail: 'Module konnten nicht geladen werden: ' + error.message,
|
||||||
|
life: 5000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const getPermission = (moduleId) => {
|
||||||
|
let permission = formData.value.permissions.find(p => p.moduleId === moduleId);
|
||||||
|
if (!permission) {
|
||||||
|
permission = {
|
||||||
|
moduleId,
|
||||||
|
canView: false,
|
||||||
|
canCreate: false,
|
||||||
|
canEdit: false,
|
||||||
|
canDelete: false,
|
||||||
|
canExport: false,
|
||||||
|
canManage: false
|
||||||
|
};
|
||||||
|
formData.value.permissions.push(permission);
|
||||||
|
}
|
||||||
|
console.log(`getPermission(${moduleId}):`, permission);
|
||||||
|
return permission;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openCreateDialog = () => {
|
||||||
|
isEditMode.value = false;
|
||||||
|
formData.value = {
|
||||||
|
name: '',
|
||||||
|
description: '',
|
||||||
|
isSystem: false,
|
||||||
|
permissions: []
|
||||||
|
};
|
||||||
|
errors.value = {};
|
||||||
|
dialogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const openEditDialog = async (role) => {
|
||||||
|
isEditMode.value = true;
|
||||||
|
|
||||||
|
// Load full role details with permissions from API
|
||||||
|
try {
|
||||||
|
const response = await fetch(role['@id'] || `/api/roles/${role.id}`, {
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Accept': 'application/ld+json'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Failed to load role details');
|
||||||
|
|
||||||
|
const fullRole = await response.json();
|
||||||
|
console.log('Loaded full role for editing:', fullRole);
|
||||||
|
|
||||||
|
formData.value = {
|
||||||
|
id: fullRole.id,
|
||||||
|
'@id': fullRole['@id'],
|
||||||
|
name: fullRole.name,
|
||||||
|
description: fullRole.description || '',
|
||||||
|
isSystem: fullRole.isSystem,
|
||||||
|
permissions: []
|
||||||
|
};
|
||||||
|
|
||||||
|
// Convert role.permissions to our internal format
|
||||||
|
if (fullRole.permissions && fullRole.permissions.length > 0) {
|
||||||
|
fullRole.permissions.forEach(perm => {
|
||||||
|
console.log('Processing permission:', perm);
|
||||||
|
formData.value.permissions.push({
|
||||||
|
id: perm.id,
|
||||||
|
'@id': perm['@id'],
|
||||||
|
moduleId: perm.module?.id || perm.module,
|
||||||
|
canView: perm.canView ?? false,
|
||||||
|
canCreate: perm.canCreate ?? false,
|
||||||
|
canEdit: perm.canEdit ?? false,
|
||||||
|
canDelete: perm.canDelete ?? false,
|
||||||
|
canExport: perm.canExport ?? false,
|
||||||
|
canManage: perm.canManage ?? false
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Form data permissions:', formData.value.permissions);
|
||||||
|
|
||||||
|
errors.value = {};
|
||||||
|
dialogVisible.value = true;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error loading role:', error);
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Fehler',
|
||||||
|
detail: 'Rolle konnte nicht geladen werden',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const validateForm = () => {
|
||||||
|
errors.value = {};
|
||||||
|
if (!formData.value.name) errors.value.name = 'Name ist erforderlich';
|
||||||
|
return Object.keys(errors.value).length === 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
const saveRole = async () => {
|
||||||
|
if (!validateForm()) return;
|
||||||
|
|
||||||
|
saving.value = true;
|
||||||
|
try {
|
||||||
|
// Step 1: Save role (without permissions)
|
||||||
|
const rolePayload = {
|
||||||
|
name: formData.value.name,
|
||||||
|
description: formData.value.description,
|
||||||
|
isSystem: formData.value.isSystem
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Saving role with payload:', rolePayload);
|
||||||
|
|
||||||
|
const url = isEditMode.value ? `/api/roles/${formData.value.id}` : '/api/roles';
|
||||||
|
const method = isEditMode.value ? 'PUT' : 'POST';
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method,
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/ld+json',
|
||||||
|
'Accept': 'application/ld+json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(rolePayload)
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Save response status:', response.status);
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
console.error('API Error Response:', errorText);
|
||||||
|
throw new Error(`Fehler beim Speichern der Rolle: ${response.status}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const savedRole = await response.json();
|
||||||
|
console.log('Saved role:', savedRole);
|
||||||
|
|
||||||
|
// Step 2: Delete existing permissions for this role (if editing)
|
||||||
|
if (isEditMode.value && savedRole.permissions && savedRole.permissions.length > 0) {
|
||||||
|
for (const perm of savedRole.permissions) {
|
||||||
|
try {
|
||||||
|
await fetch(perm['@id'], {
|
||||||
|
method: 'DELETE',
|
||||||
|
credentials: 'same-origin'
|
||||||
|
});
|
||||||
|
} catch (e) {
|
||||||
|
console.warn('Could not delete permission:', e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Step 3: Create new permissions
|
||||||
|
const permissionsToCreate = formData.value.permissions
|
||||||
|
.filter(p => p.canView || p.canCreate || p.canEdit || p.canDelete || p.canExport || p.canManage);
|
||||||
|
|
||||||
|
console.log('Creating permissions:', permissionsToCreate);
|
||||||
|
|
||||||
|
for (const perm of permissionsToCreate) {
|
||||||
|
const permPayload = {
|
||||||
|
role: savedRole['@id'],
|
||||||
|
module: `/api/modules/${perm.moduleId}`,
|
||||||
|
canView: perm.canView || false,
|
||||||
|
canCreate: perm.canCreate || false,
|
||||||
|
canEdit: perm.canEdit || false,
|
||||||
|
canDelete: perm.canDelete || false,
|
||||||
|
canExport: perm.canExport || false,
|
||||||
|
canManage: perm.canManage || false
|
||||||
|
};
|
||||||
|
|
||||||
|
const permResponse = await fetch('/api/role_permissions', {
|
||||||
|
method: 'POST',
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/ld+json',
|
||||||
|
'Accept': 'application/ld+json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(permPayload)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!permResponse.ok) {
|
||||||
|
const errorText = await permResponse.text();
|
||||||
|
console.error('Permission creation error:', errorText);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Erfolg',
|
||||||
|
detail: `Rolle wurde ${isEditMode.value ? 'aktualisiert' : 'erstellt'}`,
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
|
||||||
|
dialogVisible.value = false;
|
||||||
|
await fetchRoles();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error saving role:', error);
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Fehler',
|
||||||
|
detail: 'Rolle konnte nicht gespeichert werden: ' + error.message,
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
saving.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const confirmDelete = (role) => {
|
||||||
|
roleToDelete.value = role;
|
||||||
|
deleteDialogVisible.value = true;
|
||||||
|
};
|
||||||
|
|
||||||
|
const deleteRole = async () => {
|
||||||
|
deleting.value = true;
|
||||||
|
try {
|
||||||
|
const response = await fetch(`/api/roles/${roleToDelete.value.id}`, {
|
||||||
|
method: 'DELETE',
|
||||||
|
credentials: 'same-origin'
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) throw new Error('Fehler beim Löschen');
|
||||||
|
|
||||||
|
toast.add({
|
||||||
|
severity: 'success',
|
||||||
|
summary: 'Erfolg',
|
||||||
|
detail: 'Rolle wurde gelöscht',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
|
||||||
|
deleteDialogVisible.value = false;
|
||||||
|
await fetchRoles();
|
||||||
|
} catch (error) {
|
||||||
|
toast.add({
|
||||||
|
severity: 'error',
|
||||||
|
summary: 'Fehler',
|
||||||
|
detail: 'Rolle konnte nicht gelöscht werden',
|
||||||
|
life: 3000
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
deleting.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchRoles();
|
||||||
|
fetchModules();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.role-management {
|
||||||
|
padding: 1.5rem;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
padding: 1rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: 1rem;
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.75rem;
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.action-buttons {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-dialog {
|
||||||
|
.form-container {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-field {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0.5rem;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 0.95rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.p-error {
|
||||||
|
color: var(--p-red-500);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-section {
|
||||||
|
h3 {
|
||||||
|
margin: 0 0 1rem 0;
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.permissions-matrix {
|
||||||
|
border: 1px solid var(--p-surface-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.matrix-header,
|
||||||
|
.matrix-row {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 200px repeat(6, 1fr);
|
||||||
|
|
||||||
|
@media (max-width: 1024px) {
|
||||||
|
grid-template-columns: 150px repeat(6, 1fr);
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
grid-template-columns: 120px repeat(6, 60px);
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-header {
|
||||||
|
background: var(--p-surface-100);
|
||||||
|
font-weight: 600;
|
||||||
|
border-bottom: 2px solid var(--p-surface-border);
|
||||||
|
|
||||||
|
.permission-cell {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.25rem;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
|
||||||
|
i {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 768px) {
|
||||||
|
span {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.matrix-row {
|
||||||
|
&:not(:last-child) {
|
||||||
|
border-bottom: 1px solid var(--p-surface-border);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--p-surface-50);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-cell,
|
||||||
|
.permission-cell {
|
||||||
|
padding: 0.75rem;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-cell {
|
||||||
|
justify-content: flex-start;
|
||||||
|
font-weight: 500;
|
||||||
|
border-right: 1px solid var(--p-surface-border);
|
||||||
|
|
||||||
|
i {
|
||||||
|
color: var(--p-primary-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.permission-cell {
|
||||||
|
border-right: 1px solid var(--p-surface-border);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-right: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
color: var(--p-text-muted-color);
|
||||||
|
font-size: 0.875rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -30,9 +30,18 @@
|
|||||||
<Tag :value="data.isActive ? 'Aktiv' : 'Inaktiv'" :severity="data.isActive ? 'success' : 'danger'" />
|
<Tag :value="data.isActive ? 'Aktiv' : 'Inaktiv'" :severity="data.isActive ? 'success' : 'danger'" />
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column field="roles" header="Rollen" style="width: 150px">
|
<Column field="userRoles" header="Rollen" style="width: 200px">
|
||||||
<template #body="{ data }">
|
<template #body="{ data }">
|
||||||
<Tag v-for="role in data.roles" :key="role" :value="role" severity="info" class="mr-1" />
|
<Tag
|
||||||
|
v-for="role in data.userRoles"
|
||||||
|
:key="role['@id'] || role.id"
|
||||||
|
:value="role.name"
|
||||||
|
severity="info"
|
||||||
|
class="mr-1"
|
||||||
|
/>
|
||||||
|
<span v-if="!data.userRoles || data.userRoles.length === 0" class="text-muted">
|
||||||
|
Keine Rollen
|
||||||
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</Column>
|
</Column>
|
||||||
<Column field="lastLoginAt" header="Letzter Login" sortable style="width: 180px">
|
<Column field="lastLoginAt" header="Letzter Login" sortable style="width: 180px">
|
||||||
@ -130,7 +139,30 @@
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-field">
|
<div class="form-field">
|
||||||
<label>Symfony Rollen</label>
|
<label for="userRoles">Rollen</label>
|
||||||
|
<MultiSelect
|
||||||
|
id="userRoles"
|
||||||
|
v-model="formData.userRoles"
|
||||||
|
:options="roles"
|
||||||
|
optionLabel="name"
|
||||||
|
optionValue="@id"
|
||||||
|
placeholder="Rollen auswählen"
|
||||||
|
display="chip"
|
||||||
|
filter
|
||||||
|
:loading="roles.length === 0"
|
||||||
|
class="w-full"
|
||||||
|
>
|
||||||
|
<template #option="slotProps">
|
||||||
|
<div class="role-option">
|
||||||
|
<div class="role-name">{{ slotProps.option.name }}</div>
|
||||||
|
<div class="role-description">{{ slotProps.option.description }}</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
</MultiSelect>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-field">
|
||||||
|
<label>Symfony Sicherheitsrollen</label>
|
||||||
<div class="checkbox-group">
|
<div class="checkbox-group">
|
||||||
<div class="checkbox-item">
|
<div class="checkbox-item">
|
||||||
<Checkbox
|
<Checkbox
|
||||||
@ -149,6 +181,7 @@
|
|||||||
<label for="role_admin">ROLE_ADMIN</label>
|
<label for="role_admin">ROLE_ADMIN</label>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
<small class="text-muted">Diese Rollen sind für die Symfony-Sicherheit erforderlich.</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="form-field">
|
<div class="form-field">
|
||||||
@ -199,6 +232,7 @@ import Dialog from 'primevue/dialog';
|
|||||||
import InputText from 'primevue/inputtext';
|
import InputText from 'primevue/inputtext';
|
||||||
import Password from 'primevue/password';
|
import Password from 'primevue/password';
|
||||||
import Checkbox from 'primevue/checkbox';
|
import Checkbox from 'primevue/checkbox';
|
||||||
|
import MultiSelect from 'primevue/multiselect';
|
||||||
import Tag from 'primevue/tag';
|
import Tag from 'primevue/tag';
|
||||||
import { useToast } from 'primevue/usetoast';
|
import { useToast } from 'primevue/usetoast';
|
||||||
|
|
||||||
@ -206,6 +240,8 @@ const authStore = useAuthStore();
|
|||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
|
|
||||||
const users = ref([]);
|
const users = ref([]);
|
||||||
|
const roles = ref([]);
|
||||||
|
const modules = ref([]);
|
||||||
const loading = ref(false);
|
const loading = ref(false);
|
||||||
const dialogVisible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
const deleteDialogVisible = ref(false);
|
const deleteDialogVisible = ref(false);
|
||||||
@ -221,6 +257,7 @@ const formData = ref({
|
|||||||
email: '',
|
email: '',
|
||||||
plainPassword: '',
|
plainPassword: '',
|
||||||
roles: ['ROLE_USER'],
|
roles: ['ROLE_USER'],
|
||||||
|
userRoles: [],
|
||||||
isActive: true
|
isActive: true
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -246,6 +283,8 @@ const fetchUsers = async () => {
|
|||||||
|
|
||||||
// API Platform JSON-LD uses 'member' (hydra:member becomes 'member' in JS)
|
// API Platform JSON-LD uses 'member' (hydra:member becomes 'member' in JS)
|
||||||
users.value = data.member || data['hydra:member'] || [];
|
users.value = data.member || data['hydra:member'] || [];
|
||||||
|
console.log('Loaded users:', users.value);
|
||||||
|
console.log('First user userRoles:', users.value[0]?.userRoles);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error fetching users:', error);
|
console.error('Error fetching users:', error);
|
||||||
toast.add({
|
toast.add({
|
||||||
@ -259,6 +298,34 @@ const fetchUsers = async () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const fetchRoles = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/roles', {
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: { 'Accept': 'application/ld+json' }
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error('Failed to fetch roles');
|
||||||
|
const data = await response.json();
|
||||||
|
roles.value = data.member || [];
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching roles:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const fetchModules = async () => {
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/modules', {
|
||||||
|
credentials: 'same-origin',
|
||||||
|
headers: { 'Accept': 'application/ld+json' }
|
||||||
|
});
|
||||||
|
if (!response.ok) throw new Error('Failed to fetch modules');
|
||||||
|
const data = await response.json();
|
||||||
|
modules.value = (data.member || []).filter(m => m.isActive).sort((a, b) => a.sortOrder - b.sortOrder);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error fetching modules:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
const openCreateDialog = () => {
|
const openCreateDialog = () => {
|
||||||
isEditMode.value = false;
|
isEditMode.value = false;
|
||||||
showPasswordField.value = false;
|
showPasswordField.value = false;
|
||||||
@ -268,6 +335,7 @@ const openCreateDialog = () => {
|
|||||||
email: '',
|
email: '',
|
||||||
plainPassword: '',
|
plainPassword: '',
|
||||||
roles: ['ROLE_USER'],
|
roles: ['ROLE_USER'],
|
||||||
|
userRoles: [],
|
||||||
isActive: true
|
isActive: true
|
||||||
};
|
};
|
||||||
errors.value = {};
|
errors.value = {};
|
||||||
@ -280,7 +348,8 @@ const openEditDialog = (user) => {
|
|||||||
formData.value = {
|
formData.value = {
|
||||||
...user,
|
...user,
|
||||||
plainPassword: '',
|
plainPassword: '',
|
||||||
roles: user.roles || ['ROLE_USER']
|
roles: user.roles || ['ROLE_USER'],
|
||||||
|
userRoles: (user.userRoles || []).map(role => role['@id'] || role)
|
||||||
};
|
};
|
||||||
errors.value = {};
|
errors.value = {};
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
@ -381,6 +450,8 @@ const formatDate = (dateString) => {
|
|||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
fetchUsers();
|
fetchUsers();
|
||||||
|
fetchRoles();
|
||||||
|
fetchModules();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -572,5 +643,24 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.role-option {
|
||||||
|
.role-name {
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 0.25rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.role-description {
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--p-text-muted-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.text-muted {
|
||||||
|
display: block;
|
||||||
|
margin-top: 0.5rem;
|
||||||
|
font-size: 0.875rem;
|
||||||
|
color: var(--p-text-muted-color);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -139,6 +139,7 @@ body {
|
|||||||
}
|
}
|
||||||
|
|
||||||
&.p-tag-info {
|
&.p-tag-info {
|
||||||
background: #3b82f6;
|
background: #93c5fd;
|
||||||
|
color: #1e3a8a;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
cookies.txt
Normal file
6
cookies.txt
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
# Netscape HTTP Cookie File
|
||||||
|
# https://curl.se/docs/http-cookies.html
|
||||||
|
# This file was generated by libcurl! Edit at your own risk.
|
||||||
|
|
||||||
|
#HttpOnly_localhost FALSE / FALSE 0 PHPSESSID 4c7fe7l16qpk68ne52b1ibkej5
|
||||||
|
#HttpOnly_localhost FALSE / FALSE 0 sf_redirect %7B%22token%22%3A%220c6b33%22%2C%22route%22%3A%22_api_%5C%2Fmodules%7B._format%7D_get_collection%22%2C%22method%22%3A%22GET%22%2C%22controller%22%3A%22api_platform.symfony.main_controller%22%2C%22status_code%22%3A302%2C%22status_text%22%3A%22Found%22%7D
|
||||||
@ -2,36 +2,55 @@
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
use App\Repository\ModuleRepository;
|
use App\Repository\ModuleRepository;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: ModuleRepository::class)]
|
#[ORM\Entity(repositoryClass: ModuleRepository::class)]
|
||||||
#[ORM\Table(name: 'modules')]
|
#[ORM\Table(name: 'modules')]
|
||||||
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new GetCollection(stateless: false),
|
||||||
|
new Get(stateless: false)
|
||||||
|
],
|
||||||
|
normalizationContext: ['groups' => ['module:read']],
|
||||||
|
security: "is_granted('ROLE_USER')"
|
||||||
|
)]
|
||||||
class Module
|
class Module
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
|
#[Groups(['module:read', 'role_permission:read', 'role:read'])]
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 100, unique: true)]
|
#[ORM\Column(length: 100, unique: true)]
|
||||||
|
#[Groups(['module:read', 'role_permission:read', 'role:read'])]
|
||||||
private ?string $name = null;
|
private ?string $name = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 100, unique: true)]
|
#[ORM\Column(length: 100, unique: true)]
|
||||||
|
#[Groups(['module:read', 'role_permission:read', 'role:read'])]
|
||||||
private ?string $code = null;
|
private ?string $code = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 255, nullable: true)]
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
#[Groups(['module:read', 'role_permission:read', 'role:read'])]
|
||||||
private ?string $description = null;
|
private ?string $description = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
|
#[Groups(['module:read', 'role_permission:read', 'role:read'])]
|
||||||
private bool $isActive = true;
|
private bool $isActive = true;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
|
#[Groups(['module:read', 'role_permission:read', 'role:read'])]
|
||||||
private int $sortOrder = 0;
|
private int $sortOrder = 0;
|
||||||
|
|
||||||
#[ORM\Column(length: 50, nullable: true)]
|
#[ORM\Column(length: 50, nullable: true)]
|
||||||
|
#[Groups(['module:read', 'role_permission:read', 'role:read'])]
|
||||||
private ?string $icon = null;
|
private ?string $icon = null;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -88,6 +107,12 @@ class Module
|
|||||||
return $this->isActive;
|
return $this->isActive;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Groups(['module:read', 'role_permission:read', 'role:read'])]
|
||||||
|
public function getIsActive(): bool
|
||||||
|
{
|
||||||
|
return $this->isActive;
|
||||||
|
}
|
||||||
|
|
||||||
public function setIsActive(bool $isActive): static
|
public function setIsActive(bool $isActive): static
|
||||||
{
|
{
|
||||||
$this->isActive = $isActive;
|
$this->isActive = $isActive;
|
||||||
|
|||||||
@ -2,27 +2,50 @@
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
use App\Repository\RoleRepository;
|
use App\Repository\RoleRepository;
|
||||||
use Doctrine\Common\Collections\ArrayCollection;
|
use Doctrine\Common\Collections\ArrayCollection;
|
||||||
use Doctrine\Common\Collections\Collection;
|
use Doctrine\Common\Collections\Collection;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: RoleRepository::class)]
|
#[ORM\Entity(repositoryClass: RoleRepository::class)]
|
||||||
#[ORM\Table(name: 'roles')]
|
#[ORM\Table(name: 'roles')]
|
||||||
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new GetCollection(stateless: false),
|
||||||
|
new Get(stateless: false),
|
||||||
|
new Post(stateless: false, security: "is_granted('ROLE_ADMIN')"),
|
||||||
|
new Put(stateless: false, security: "is_granted('ROLE_ADMIN')"),
|
||||||
|
new Delete(stateless: false, security: "is_granted('ROLE_ADMIN')")
|
||||||
|
],
|
||||||
|
normalizationContext: ['groups' => ['role:read']],
|
||||||
|
denormalizationContext: ['groups' => ['role:write']],
|
||||||
|
security: "is_granted('ROLE_USER')"
|
||||||
|
)]
|
||||||
class Role
|
class Role
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
|
#[Groups(['role:read', 'user:read'])]
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 100, unique: true)]
|
#[ORM\Column(length: 100, unique: true)]
|
||||||
|
#[Groups(['role:read', 'role:write', 'user:read'])]
|
||||||
private ?string $name = null;
|
private ?string $name = null;
|
||||||
|
|
||||||
#[ORM\Column(length: 255, nullable: true)]
|
#[ORM\Column(length: 255, nullable: true)]
|
||||||
|
#[Groups(['role:read', 'role:write', 'user:read'])]
|
||||||
private ?string $description = null;
|
private ?string $description = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
|
#[Groups(['role:read', 'role:write', 'user:read'])]
|
||||||
private bool $isSystem = false;
|
private bool $isSystem = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -35,6 +58,7 @@ class Role
|
|||||||
* @var Collection<int, RolePermission>
|
* @var Collection<int, RolePermission>
|
||||||
*/
|
*/
|
||||||
#[ORM\OneToMany(targetEntity: RolePermission::class, mappedBy: 'role', cascade: ['persist', 'remove'], orphanRemoval: true)]
|
#[ORM\OneToMany(targetEntity: RolePermission::class, mappedBy: 'role', cascade: ['persist', 'remove'], orphanRemoval: true)]
|
||||||
|
#[Groups(['role:read', 'role:write'])]
|
||||||
private Collection $permissions;
|
private Collection $permissions;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
|
|||||||
@ -2,43 +2,70 @@
|
|||||||
|
|
||||||
namespace App\Entity;
|
namespace App\Entity;
|
||||||
|
|
||||||
|
use ApiPlatform\Metadata\ApiResource;
|
||||||
|
use ApiPlatform\Metadata\GetCollection;
|
||||||
|
use ApiPlatform\Metadata\Get;
|
||||||
|
use ApiPlatform\Metadata\Post;
|
||||||
|
use ApiPlatform\Metadata\Put;
|
||||||
|
use ApiPlatform\Metadata\Delete;
|
||||||
use App\Repository\RolePermissionRepository;
|
use App\Repository\RolePermissionRepository;
|
||||||
use Doctrine\ORM\Mapping as ORM;
|
use Doctrine\ORM\Mapping as ORM;
|
||||||
|
use Symfony\Component\Serializer\Annotation\Groups;
|
||||||
|
|
||||||
#[ORM\Entity(repositoryClass: RolePermissionRepository::class)]
|
#[ORM\Entity(repositoryClass: RolePermissionRepository::class)]
|
||||||
#[ORM\Table(name: 'role_permissions')]
|
#[ORM\Table(name: 'role_permissions')]
|
||||||
#[ORM\UniqueConstraint(name: 'role_module_unique', columns: ['role_id', 'module_id'])]
|
#[ORM\UniqueConstraint(name: 'role_module_unique', columns: ['role_id', 'module_id'])]
|
||||||
|
#[ApiResource(
|
||||||
|
operations: [
|
||||||
|
new GetCollection(stateless: false, security: "is_granted('ROLE_ADMIN')"),
|
||||||
|
new Get(stateless: false, security: "is_granted('ROLE_ADMIN')"),
|
||||||
|
new Post(stateless: false, security: "is_granted('ROLE_ADMIN')"),
|
||||||
|
new Put(stateless: false, security: "is_granted('ROLE_ADMIN')"),
|
||||||
|
new Delete(stateless: false, security: "is_granted('ROLE_ADMIN')")
|
||||||
|
],
|
||||||
|
normalizationContext: ['groups' => ['role_permission:read']],
|
||||||
|
denormalizationContext: ['groups' => ['role_permission:write']]
|
||||||
|
)]
|
||||||
class RolePermission
|
class RolePermission
|
||||||
{
|
{
|
||||||
#[ORM\Id]
|
#[ORM\Id]
|
||||||
#[ORM\GeneratedValue]
|
#[ORM\GeneratedValue]
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
|
#[Groups(['role_permission:read', 'role:read'])]
|
||||||
private ?int $id = null;
|
private ?int $id = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: Role::class, inversedBy: 'permissions')]
|
#[ORM\ManyToOne(targetEntity: Role::class, inversedBy: 'permissions')]
|
||||||
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
|
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
|
||||||
|
#[Groups(['role_permission:read', 'role_permission:write'])]
|
||||||
private ?Role $role = null;
|
private ?Role $role = null;
|
||||||
|
|
||||||
#[ORM\ManyToOne(targetEntity: Module::class, inversedBy: 'permissions')]
|
#[ORM\ManyToOne(targetEntity: Module::class, inversedBy: 'permissions')]
|
||||||
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
|
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
|
||||||
|
#[Groups(['role_permission:read', 'role_permission:write', 'role:read'])]
|
||||||
private ?Module $module = null;
|
private ?Module $module = null;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
|
#[Groups(['role_permission:read', 'role_permission:write', 'role:read'])]
|
||||||
private bool $canView = false;
|
private bool $canView = false;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
|
#[Groups(['role_permission:read', 'role_permission:write', 'role:read'])]
|
||||||
private bool $canCreate = false;
|
private bool $canCreate = false;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
|
#[Groups(['role_permission:read', 'role_permission:write', 'role:read'])]
|
||||||
private bool $canEdit = false;
|
private bool $canEdit = false;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
|
#[Groups(['role_permission:read', 'role_permission:write', 'role:read'])]
|
||||||
private bool $canDelete = false;
|
private bool $canDelete = false;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
|
#[Groups(['role_permission:read', 'role_permission:write', 'role:read'])]
|
||||||
private bool $canExport = false;
|
private bool $canExport = false;
|
||||||
|
|
||||||
#[ORM\Column]
|
#[ORM\Column]
|
||||||
|
#[Groups(['role_permission:read', 'role_permission:write', 'role:read'])]
|
||||||
private bool $canManage = false;
|
private bool $canManage = false;
|
||||||
|
|
||||||
public function getId(): ?int
|
public function getId(): ?int
|
||||||
@ -73,6 +100,12 @@ class RolePermission
|
|||||||
return $this->canView;
|
return $this->canView;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Groups(['role_permission:read', 'role:read'])]
|
||||||
|
public function getCanView(): bool
|
||||||
|
{
|
||||||
|
return $this->canView;
|
||||||
|
}
|
||||||
|
|
||||||
public function setCanView(bool $canView): static
|
public function setCanView(bool $canView): static
|
||||||
{
|
{
|
||||||
$this->canView = $canView;
|
$this->canView = $canView;
|
||||||
@ -84,6 +117,12 @@ class RolePermission
|
|||||||
return $this->canCreate;
|
return $this->canCreate;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Groups(['role_permission:read', 'role:read'])]
|
||||||
|
public function getCanCreate(): bool
|
||||||
|
{
|
||||||
|
return $this->canCreate;
|
||||||
|
}
|
||||||
|
|
||||||
public function setCanCreate(bool $canCreate): static
|
public function setCanCreate(bool $canCreate): static
|
||||||
{
|
{
|
||||||
$this->canCreate = $canCreate;
|
$this->canCreate = $canCreate;
|
||||||
@ -95,6 +134,12 @@ class RolePermission
|
|||||||
return $this->canEdit;
|
return $this->canEdit;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Groups(['role_permission:read', 'role:read'])]
|
||||||
|
public function getCanEdit(): bool
|
||||||
|
{
|
||||||
|
return $this->canEdit;
|
||||||
|
}
|
||||||
|
|
||||||
public function setCanEdit(bool $canEdit): static
|
public function setCanEdit(bool $canEdit): static
|
||||||
{
|
{
|
||||||
$this->canEdit = $canEdit;
|
$this->canEdit = $canEdit;
|
||||||
@ -106,6 +151,12 @@ class RolePermission
|
|||||||
return $this->canDelete;
|
return $this->canDelete;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Groups(['role_permission:read', 'role:read'])]
|
||||||
|
public function getCanDelete(): bool
|
||||||
|
{
|
||||||
|
return $this->canDelete;
|
||||||
|
}
|
||||||
|
|
||||||
public function setCanDelete(bool $canDelete): static
|
public function setCanDelete(bool $canDelete): static
|
||||||
{
|
{
|
||||||
$this->canDelete = $canDelete;
|
$this->canDelete = $canDelete;
|
||||||
@ -117,6 +168,12 @@ class RolePermission
|
|||||||
return $this->canExport;
|
return $this->canExport;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Groups(['role_permission:read', 'role:read'])]
|
||||||
|
public function getCanExport(): bool
|
||||||
|
{
|
||||||
|
return $this->canExport;
|
||||||
|
}
|
||||||
|
|
||||||
public function setCanExport(bool $canExport): static
|
public function setCanExport(bool $canExport): static
|
||||||
{
|
{
|
||||||
$this->canExport = $canExport;
|
$this->canExport = $canExport;
|
||||||
@ -128,6 +185,12 @@ class RolePermission
|
|||||||
return $this->canManage;
|
return $this->canManage;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[Groups(['role_permission:read', 'role:read'])]
|
||||||
|
public function getCanManage(): bool
|
||||||
|
{
|
||||||
|
return $this->canManage;
|
||||||
|
}
|
||||||
|
|
||||||
public function setCanManage(bool $canManage): static
|
public function setCanManage(bool $canManage): static
|
||||||
{
|
{
|
||||||
$this->canManage = $canManage;
|
$this->canManage = $canManage;
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user