Align commerce module with the monorepo module structure by updating all namespaces to use the Core\Mod\Commerce convention. This change supports the recent monorepo separation and ensures consistency with other modules. Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
270 lines
8.1 KiB
PHP
270 lines
8.1 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace Core\Mod\Commerce\View\Modal\Admin;
|
|
|
|
use Core\Mod\Commerce\Models\Entity;
|
|
use Core\Mod\Commerce\Models\PermissionMatrix;
|
|
use Core\Mod\Commerce\Models\PermissionRequest;
|
|
use Core\Mod\Commerce\Services\PermissionLockedException;
|
|
use Core\Mod\Commerce\Services\PermissionMatrixService;
|
|
use Livewire\Attributes\Layout;
|
|
use Livewire\Component;
|
|
use Livewire\WithPagination;
|
|
|
|
/**
|
|
* Manage Permission Matrix training and permissions.
|
|
*/
|
|
#[Layout('hub::admin.layouts.app')]
|
|
class PermissionMatrixManager extends Component
|
|
{
|
|
use WithPagination;
|
|
|
|
// Filters
|
|
public ?int $entityFilter = null;
|
|
|
|
public string $statusFilter = '';
|
|
|
|
public string $search = '';
|
|
|
|
// Training modal
|
|
public bool $showTrainModal = false;
|
|
|
|
public ?int $trainingEntityId = null;
|
|
|
|
public string $trainingKey = '';
|
|
|
|
public string $trainingScope = '';
|
|
|
|
public bool $trainingAllow = true;
|
|
|
|
public bool $trainingLock = false;
|
|
|
|
// Bulk training
|
|
public array $selectedRequests = [];
|
|
|
|
protected PermissionMatrixService $matrix;
|
|
|
|
public function boot(PermissionMatrixService $matrix): void
|
|
{
|
|
$this->matrix = $matrix;
|
|
}
|
|
|
|
/**
|
|
* Authorize access - Hades tier only.
|
|
*/
|
|
public function mount(): void
|
|
{
|
|
if (! auth()->user()?->isHades()) {
|
|
abort(403, 'Hades tier required for permission matrix management.');
|
|
}
|
|
}
|
|
|
|
public function updatingSearch(): void
|
|
{
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function updatingEntityFilter(): void
|
|
{
|
|
$this->resetPage();
|
|
}
|
|
|
|
public function openTrain(?int $requestId = null): void
|
|
{
|
|
if ($requestId) {
|
|
$request = PermissionRequest::find($requestId);
|
|
if ($request) {
|
|
$this->trainingEntityId = $request->entity_id;
|
|
$this->trainingKey = $request->action;
|
|
$this->trainingScope = $request->scope ?? '';
|
|
}
|
|
}
|
|
|
|
$this->trainingAllow = true;
|
|
$this->trainingLock = false;
|
|
$this->showTrainModal = true;
|
|
}
|
|
|
|
public function openTrainNew(): void
|
|
{
|
|
$this->trainingEntityId = null;
|
|
$this->trainingKey = '';
|
|
$this->trainingScope = '';
|
|
$this->trainingAllow = true;
|
|
$this->trainingLock = false;
|
|
$this->showTrainModal = true;
|
|
}
|
|
|
|
public function train(): void
|
|
{
|
|
$this->validate([
|
|
'trainingEntityId' => 'required|exists:commerce_entities,id',
|
|
'trainingKey' => 'required|string|max:255',
|
|
'trainingScope' => 'nullable|string|max:255',
|
|
]);
|
|
|
|
$entity = Entity::findOrFail($this->trainingEntityId);
|
|
|
|
try {
|
|
if ($this->trainingLock) {
|
|
$this->matrix->lock(
|
|
entity: $entity,
|
|
key: $this->trainingKey,
|
|
allowed: $this->trainingAllow,
|
|
scope: $this->trainingScope ?: null
|
|
);
|
|
} else {
|
|
$this->matrix->train(
|
|
entity: $entity,
|
|
key: $this->trainingKey,
|
|
scope: $this->trainingScope ?: null,
|
|
allow: $this->trainingAllow
|
|
);
|
|
}
|
|
|
|
// Mark related requests as trained
|
|
$this->matrix->markRequestsTrained(
|
|
$entity,
|
|
$this->trainingKey,
|
|
$this->trainingScope ?: null
|
|
);
|
|
|
|
$action = $this->trainingAllow ? 'allowed' : 'denied';
|
|
$lock = $this->trainingLock ? ' (locked)' : '';
|
|
session()->flash('message', "Permission '{$this->trainingKey}' {$action} for {$entity->name}{$lock}.");
|
|
|
|
$this->closeTrainModal();
|
|
|
|
} catch (PermissionLockedException $e) {
|
|
session()->flash('error', $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function bulkTrain(bool $allow): void
|
|
{
|
|
if (empty($this->selectedRequests)) {
|
|
session()->flash('error', 'No requests selected.');
|
|
|
|
return;
|
|
}
|
|
|
|
$trained = 0;
|
|
$errors = [];
|
|
|
|
foreach ($this->selectedRequests as $requestId) {
|
|
$request = PermissionRequest::find($requestId);
|
|
if (! $request) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
$entity = $request->entity;
|
|
if (! $entity) {
|
|
continue;
|
|
}
|
|
|
|
$this->matrix->train(
|
|
entity: $entity,
|
|
key: $request->action,
|
|
scope: $request->scope,
|
|
allow: $allow
|
|
);
|
|
|
|
$this->matrix->markRequestsTrained(
|
|
$entity,
|
|
$request->action,
|
|
$request->scope
|
|
);
|
|
|
|
$trained++;
|
|
|
|
} catch (PermissionLockedException $e) {
|
|
$errors[] = $e->getMessage();
|
|
}
|
|
}
|
|
|
|
$this->selectedRequests = [];
|
|
|
|
if ($errors) {
|
|
session()->flash('error', implode(', ', $errors));
|
|
}
|
|
|
|
$action = $allow ? 'allowed' : 'denied';
|
|
session()->flash('message', "{$trained} permissions {$action}.");
|
|
}
|
|
|
|
public function deletePermission(int $id): void
|
|
{
|
|
$permission = PermissionMatrix::findOrFail($id);
|
|
|
|
if ($permission->locked) {
|
|
session()->flash('error', 'Cannot delete a locked permission.');
|
|
|
|
return;
|
|
}
|
|
|
|
$permission->delete();
|
|
session()->flash('message', 'Permission deleted.');
|
|
}
|
|
|
|
public function unlockPermission(int $id): void
|
|
{
|
|
$permission = PermissionMatrix::findOrFail($id);
|
|
|
|
try {
|
|
$this->matrix->unlock($permission->entity, $permission->key, $permission->scope);
|
|
session()->flash('message', 'Permission unlocked.');
|
|
} catch (\Exception $e) {
|
|
session()->flash('error', $e->getMessage());
|
|
}
|
|
}
|
|
|
|
public function closeTrainModal(): void
|
|
{
|
|
$this->showTrainModal = false;
|
|
$this->trainingEntityId = null;
|
|
$this->trainingKey = '';
|
|
$this->trainingScope = '';
|
|
$this->trainingAllow = true;
|
|
$this->trainingLock = false;
|
|
}
|
|
|
|
public function render()
|
|
{
|
|
// Get pending requests
|
|
$pendingRequests = PermissionRequest::query()
|
|
->with('entity')
|
|
->where('status', PermissionRequest::STATUS_PENDING)
|
|
->when($this->entityFilter, fn ($q) => $q->where('entity_id', $this->entityFilter))
|
|
->when($this->search, fn ($q) => $q->where('action', 'like', "%{$this->search}%"))
|
|
->latest()
|
|
->paginate(20, ['*'], 'pending_page');
|
|
|
|
// Get trained permissions
|
|
$permissions = PermissionMatrix::query()
|
|
->with('entity', 'setByEntity')
|
|
->when($this->entityFilter, fn ($q) => $q->where('entity_id', $this->entityFilter))
|
|
->when($this->search, fn ($q) => $q->where('key', 'like', "%{$this->search}%"))
|
|
->when($this->statusFilter === 'allowed', fn ($q) => $q->where('allowed', true))
|
|
->when($this->statusFilter === 'denied', fn ($q) => $q->where('allowed', false))
|
|
->when($this->statusFilter === 'locked', fn ($q) => $q->where('locked', true))
|
|
->orderBy('entity_id')
|
|
->orderBy('key')
|
|
->paginate(30, ['*'], 'permissions_page');
|
|
|
|
return view('commerce::admin.permission-matrix-manager', [
|
|
'pendingRequests' => $pendingRequests,
|
|
'permissions' => $permissions,
|
|
'entities' => Entity::active()->orderBy('path')->get(),
|
|
'stats' => [
|
|
'total_permissions' => PermissionMatrix::count(),
|
|
'allowed' => PermissionMatrix::where('allowed', true)->count(),
|
|
'denied' => PermissionMatrix::where('allowed', false)->count(),
|
|
'locked' => PermissionMatrix::where('locked', true)->count(),
|
|
'pending_requests' => PermissionRequest::where('status', PermissionRequest::STATUS_PENDING)->count(),
|
|
],
|
|
])->layout('hub::admin.layouts.app', ['title' => 'Permission Matrix']);
|
|
}
|
|
}
|