diff --git a/assets/js/App.vue b/assets/js/App.vue
index 38ed836..5e71181 100644
--- a/assets/js/App.vue
+++ b/assets/js/App.vue
@@ -30,6 +30,9 @@
Benutzerverwaltung
+
+ Rollenverwaltung
+
diff --git a/assets/js/router.js b/assets/js/router.js
index 8311c35..1f2fd03 100644
--- a/assets/js/router.js
+++ b/assets/js/router.js
@@ -4,6 +4,7 @@ import ContactList from './views/ContactList.vue';
import CompanyList from './views/CompanyList.vue';
import DealList from './views/DealList.vue';
import UserManagement from './views/UserManagement.vue';
+import RoleManagement from './views/RoleManagement.vue';
const routes = [
{ path: '/', name: 'dashboard', component: Dashboard },
@@ -11,6 +12,7 @@ const routes = [
{ path: '/companies', name: 'companies', component: CompanyList },
{ path: '/deals', name: 'deals', component: DealList },
{ path: '/users', name: 'users', component: UserManagement, meta: { requiresAdmin: true } },
+ { path: '/roles', name: 'roles', component: RoleManagement, meta: { requiresAdmin: true } },
];
const router = createRouter({
diff --git a/assets/js/views/RoleManagement.vue b/assets/js/views/RoleManagement.vue
new file mode 100644
index 0000000..36f81be
--- /dev/null
+++ b/assets/js/views/RoleManagement.vue
@@ -0,0 +1,688 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/assets/js/views/UserManagement.vue b/assets/js/views/UserManagement.vue
index 70dd971..78697c6 100644
--- a/assets/js/views/UserManagement.vue
+++ b/assets/js/views/UserManagement.vue
@@ -30,9 +30,18 @@
-
+
-
+
+
+ Keine Rollen
+
@@ -130,7 +139,30 @@
+
+
@@ -199,6 +232,7 @@ import Dialog from 'primevue/dialog';
import InputText from 'primevue/inputtext';
import Password from 'primevue/password';
import Checkbox from 'primevue/checkbox';
+import MultiSelect from 'primevue/multiselect';
import Tag from 'primevue/tag';
import { useToast } from 'primevue/usetoast';
@@ -206,6 +240,8 @@ const authStore = useAuthStore();
const toast = useToast();
const users = ref([]);
+const roles = ref([]);
+const modules = ref([]);
const loading = ref(false);
const dialogVisible = ref(false);
const deleteDialogVisible = ref(false);
@@ -221,6 +257,7 @@ const formData = ref({
email: '',
plainPassword: '',
roles: ['ROLE_USER'],
+ userRoles: [],
isActive: true
});
@@ -246,6 +283,8 @@ const fetchUsers = async () => {
// API Platform JSON-LD uses 'member' (hydra:member becomes 'member' in JS)
users.value = data.member || data['hydra:member'] || [];
+ console.log('Loaded users:', users.value);
+ console.log('First user userRoles:', users.value[0]?.userRoles);
} catch (error) {
console.error('Error fetching users:', error);
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 = () => {
isEditMode.value = false;
showPasswordField.value = false;
@@ -268,6 +335,7 @@ const openCreateDialog = () => {
email: '',
plainPassword: '',
roles: ['ROLE_USER'],
+ userRoles: [],
isActive: true
};
errors.value = {};
@@ -280,7 +348,8 @@ const openEditDialog = (user) => {
formData.value = {
...user,
plainPassword: '',
- roles: user.roles || ['ROLE_USER']
+ roles: user.roles || ['ROLE_USER'],
+ userRoles: (user.userRoles || []).map(role => role['@id'] || role)
};
errors.value = {};
dialogVisible.value = true;
@@ -381,6 +450,8 @@ const formatDate = (dateString) => {
onMounted(() => {
fetchUsers();
+ fetchRoles();
+ fetchModules();
});
@@ -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);
+ }
}
diff --git a/assets/styles/app.scss b/assets/styles/app.scss
index 9ca8ff1..7c5f505 100644
--- a/assets/styles/app.scss
+++ b/assets/styles/app.scss
@@ -139,6 +139,7 @@ body {
}
&.p-tag-info {
- background: #3b82f6;
+ background: #93c5fd;
+ color: #1e3a8a;
}
}
diff --git a/cookies.txt b/cookies.txt
new file mode 100644
index 0000000..eac71f7
--- /dev/null
+++ b/cookies.txt
@@ -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
diff --git a/src/Entity/Module.php b/src/Entity/Module.php
index 54319f7..32e68bb 100644
--- a/src/Entity/Module.php
+++ b/src/Entity/Module.php
@@ -2,36 +2,55 @@
namespace App\Entity;
+use ApiPlatform\Metadata\ApiResource;
+use ApiPlatform\Metadata\GetCollection;
+use ApiPlatform\Metadata\Get;
use App\Repository\ModuleRepository;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: ModuleRepository::class)]
#[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
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
+ #[Groups(['module:read', 'role_permission:read', 'role:read'])]
private ?int $id = null;
#[ORM\Column(length: 100, unique: true)]
+ #[Groups(['module:read', 'role_permission:read', 'role:read'])]
private ?string $name = null;
#[ORM\Column(length: 100, unique: true)]
+ #[Groups(['module:read', 'role_permission:read', 'role:read'])]
private ?string $code = null;
#[ORM\Column(length: 255, nullable: true)]
+ #[Groups(['module:read', 'role_permission:read', 'role:read'])]
private ?string $description = null;
#[ORM\Column]
+ #[Groups(['module:read', 'role_permission:read', 'role:read'])]
private bool $isActive = true;
#[ORM\Column]
+ #[Groups(['module:read', 'role_permission:read', 'role:read'])]
private int $sortOrder = 0;
#[ORM\Column(length: 50, nullable: true)]
+ #[Groups(['module:read', 'role_permission:read', 'role:read'])]
private ?string $icon = null;
/**
@@ -88,6 +107,12 @@ class Module
return $this->isActive;
}
+ #[Groups(['module:read', 'role_permission:read', 'role:read'])]
+ public function getIsActive(): bool
+ {
+ return $this->isActive;
+ }
+
public function setIsActive(bool $isActive): static
{
$this->isActive = $isActive;
diff --git a/src/Entity/Role.php b/src/Entity/Role.php
index 0eb00d2..e1594ce 100644
--- a/src/Entity/Role.php
+++ b/src/Entity/Role.php
@@ -2,27 +2,50 @@
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 Doctrine\Common\Collections\ArrayCollection;
use Doctrine\Common\Collections\Collection;
use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: RoleRepository::class)]
#[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
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
+ #[Groups(['role:read', 'user:read'])]
private ?int $id = null;
#[ORM\Column(length: 100, unique: true)]
+ #[Groups(['role:read', 'role:write', 'user:read'])]
private ?string $name = null;
#[ORM\Column(length: 255, nullable: true)]
+ #[Groups(['role:read', 'role:write', 'user:read'])]
private ?string $description = null;
#[ORM\Column]
+ #[Groups(['role:read', 'role:write', 'user:read'])]
private bool $isSystem = false;
/**
@@ -35,6 +58,7 @@ class Role
* @var Collection
*/
#[ORM\OneToMany(targetEntity: RolePermission::class, mappedBy: 'role', cascade: ['persist', 'remove'], orphanRemoval: true)]
+ #[Groups(['role:read', 'role:write'])]
private Collection $permissions;
#[ORM\Column]
diff --git a/src/Entity/RolePermission.php b/src/Entity/RolePermission.php
index 5cbbd15..b29c5cb 100644
--- a/src/Entity/RolePermission.php
+++ b/src/Entity/RolePermission.php
@@ -2,43 +2,70 @@
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 Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Serializer\Annotation\Groups;
#[ORM\Entity(repositoryClass: RolePermissionRepository::class)]
#[ORM\Table(name: 'role_permissions')]
#[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
{
#[ORM\Id]
#[ORM\GeneratedValue]
#[ORM\Column]
+ #[Groups(['role_permission:read', 'role:read'])]
private ?int $id = null;
#[ORM\ManyToOne(targetEntity: Role::class, inversedBy: 'permissions')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
+ #[Groups(['role_permission:read', 'role_permission:write'])]
private ?Role $role = null;
#[ORM\ManyToOne(targetEntity: Module::class, inversedBy: 'permissions')]
#[ORM\JoinColumn(nullable: false, onDelete: 'CASCADE')]
+ #[Groups(['role_permission:read', 'role_permission:write', 'role:read'])]
private ?Module $module = null;
#[ORM\Column]
+ #[Groups(['role_permission:read', 'role_permission:write', 'role:read'])]
private bool $canView = false;
#[ORM\Column]
+ #[Groups(['role_permission:read', 'role_permission:write', 'role:read'])]
private bool $canCreate = false;
#[ORM\Column]
+ #[Groups(['role_permission:read', 'role_permission:write', 'role:read'])]
private bool $canEdit = false;
#[ORM\Column]
+ #[Groups(['role_permission:read', 'role_permission:write', 'role:read'])]
private bool $canDelete = false;
#[ORM\Column]
+ #[Groups(['role_permission:read', 'role_permission:write', 'role:read'])]
private bool $canExport = false;
#[ORM\Column]
+ #[Groups(['role_permission:read', 'role_permission:write', 'role:read'])]
private bool $canManage = false;
public function getId(): ?int
@@ -73,6 +100,12 @@ class RolePermission
return $this->canView;
}
+ #[Groups(['role_permission:read', 'role:read'])]
+ public function getCanView(): bool
+ {
+ return $this->canView;
+ }
+
public function setCanView(bool $canView): static
{
$this->canView = $canView;
@@ -84,6 +117,12 @@ class RolePermission
return $this->canCreate;
}
+ #[Groups(['role_permission:read', 'role:read'])]
+ public function getCanCreate(): bool
+ {
+ return $this->canCreate;
+ }
+
public function setCanCreate(bool $canCreate): static
{
$this->canCreate = $canCreate;
@@ -95,6 +134,12 @@ class RolePermission
return $this->canEdit;
}
+ #[Groups(['role_permission:read', 'role:read'])]
+ public function getCanEdit(): bool
+ {
+ return $this->canEdit;
+ }
+
public function setCanEdit(bool $canEdit): static
{
$this->canEdit = $canEdit;
@@ -106,6 +151,12 @@ class RolePermission
return $this->canDelete;
}
+ #[Groups(['role_permission:read', 'role:read'])]
+ public function getCanDelete(): bool
+ {
+ return $this->canDelete;
+ }
+
public function setCanDelete(bool $canDelete): static
{
$this->canDelete = $canDelete;
@@ -117,6 +168,12 @@ class RolePermission
return $this->canExport;
}
+ #[Groups(['role_permission:read', 'role:read'])]
+ public function getCanExport(): bool
+ {
+ return $this->canExport;
+ }
+
public function setCanExport(bool $canExport): static
{
$this->canExport = $canExport;
@@ -128,6 +185,12 @@ class RolePermission
return $this->canManage;
}
+ #[Groups(['role_permission:read', 'role:read'])]
+ public function getCanManage(): bool
+ {
+ return $this->canManage;
+ }
+
public function setCanManage(bool $canManage): static
{
$this->canManage = $canManage;