Feat: add role priority management
- Added `priority` column to `roles` table with migration and model updates. - Introduced priority management methods in `RoleService` and validation logic for updates. - Enhanced access-manager UI to display and edit role priorities via a modal. - Updated related data classes and views to support and handle the new priority field.
This commit is contained in:
parent
d7c2491fed
commit
cd29191c7b
@ -10,5 +10,6 @@ final class CreateRoleData extends Data
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public string $name,
|
public string $name,
|
||||||
|
public int $priority,
|
||||||
) {}
|
) {}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -13,6 +13,7 @@ final class RoleData extends Data
|
|||||||
public function __construct(
|
public function __construct(
|
||||||
public int $id,
|
public int $id,
|
||||||
public string $name,
|
public string $name,
|
||||||
|
public int $priority = 0,
|
||||||
#[DataCollectionOf(class: RoleAppData::class)]
|
#[DataCollectionOf(class: RoleAppData::class)]
|
||||||
public ?DataCollection $apps = null,
|
public ?DataCollection $apps = null,
|
||||||
#[DataCollectionOf(class: RoleUserData::class)]
|
#[DataCollectionOf(class: RoleUserData::class)]
|
||||||
|
|||||||
15
app/Data/Role/UpdateRolePriorityData.php
Normal file
15
app/Data/Role/UpdateRolePriorityData.php
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace App\Data\Role;
|
||||||
|
|
||||||
|
use Spatie\LaravelData\Data;
|
||||||
|
|
||||||
|
final class UpdateRolePriorityData extends Data
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
public int $roleId,
|
||||||
|
public int $priority,
|
||||||
|
) {}
|
||||||
|
}
|
||||||
@ -10,4 +10,7 @@ class CreateRoleForm extends AssignAppToRoleForm
|
|||||||
{
|
{
|
||||||
#[Validate('required|string|max:255|min:3')]
|
#[Validate('required|string|max:255|min:3')]
|
||||||
public string $name = '';
|
public string $name = '';
|
||||||
|
|
||||||
|
#[Validate('required|integer|min:0')]
|
||||||
|
public int $priority = 0;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,11 +4,18 @@
|
|||||||
|
|
||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||||
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
|
||||||
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
||||||
use Spatie\Activitylog\Support\LogOptions;
|
use Spatie\Activitylog\Support\LogOptions;
|
||||||
use Spatie\Permission\Models\Role as SpatieRole;
|
use Spatie\Permission\Models\Role as SpatieRole;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @property int $priority
|
||||||
|
* @property AppRole $pivot
|
||||||
|
* @property ConnectedApp $apps
|
||||||
|
*/
|
||||||
|
#[Fillable(['priority'])]
|
||||||
class Role extends SpatieRole
|
class Role extends SpatieRole
|
||||||
{
|
{
|
||||||
use LogsActivity;
|
use LogsActivity;
|
||||||
|
|||||||
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
namespace App\Services;
|
namespace App\Services;
|
||||||
|
|
||||||
use App\Data\Role\{CreateRoleData, RoleData};
|
use App\Data\Role\{CreateRoleData, RoleData, UpdateRolePriorityData};
|
||||||
use App\Models\Role;
|
use App\Models\Role;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Illuminate\Support\Facades\DB;
|
use Illuminate\Support\Facades\DB;
|
||||||
@ -25,6 +25,7 @@ public function create(CreateRoleData $data): RoleData
|
|||||||
{
|
{
|
||||||
$role = DB::transaction(fn () => Role::query()->create([
|
$role = DB::transaction(fn () => Role::query()->create([
|
||||||
'name' => $data->name,
|
'name' => $data->name,
|
||||||
|
'priority' => $data->priority,
|
||||||
'guard_name' => 'web',
|
'guard_name' => 'web',
|
||||||
]));
|
]));
|
||||||
|
|
||||||
@ -93,8 +94,32 @@ public function getPermissionsGroupedByRole(array $roleIds): ?DataCollection
|
|||||||
$roles = Role::query()
|
$roles = Role::query()
|
||||||
->with('permissions:id,name')
|
->with('permissions:id,name')
|
||||||
->whereIn('id', $roleIds)
|
->whereIn('id', $roleIds)
|
||||||
->get(['id', 'name']);
|
->get(['id', 'name', 'priority']);
|
||||||
|
|
||||||
return RoleData::collect($roles, DataCollection::class);
|
return RoleData::collect($roles, DataCollection::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update the priority of a role.
|
||||||
|
*
|
||||||
|
* @throws InvalidArgumentException when roleId is invalid.
|
||||||
|
* @throws Throwable when an error occurs during the update.
|
||||||
|
*/
|
||||||
|
public function updatePriority(UpdateRolePriorityData $data): RoleData
|
||||||
|
{
|
||||||
|
if ($data->roleId <= 0) {
|
||||||
|
throw new InvalidArgumentException('Invalid role id');
|
||||||
|
}
|
||||||
|
|
||||||
|
$role = DB::transaction(function () use ($data) {
|
||||||
|
$role = Role::query()->findOrFail($data->roleId);
|
||||||
|
$role->update([
|
||||||
|
'priority' => $data->priority,
|
||||||
|
]);
|
||||||
|
|
||||||
|
return $role;
|
||||||
|
});
|
||||||
|
|
||||||
|
return RoleData::from($role);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,24 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class() extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('roles', function (Blueprint $table): void {
|
||||||
|
$table->unsignedInteger('priority')->default(100)->after('guard_name');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('roles', function (Blueprint $table): void {
|
||||||
|
$table->dropColumn('priority');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@ -37,6 +37,7 @@ public function mount(): void
|
|||||||
{
|
{
|
||||||
$this->headers = TableHeader::collect([
|
$this->headers = TableHeader::collect([
|
||||||
new TableHeader(key: 'name', label: 'Name'),
|
new TableHeader(key: 'name', label: 'Name'),
|
||||||
|
new TableHeader(key: 'priority', label: 'Priority'),
|
||||||
new TableHeader(key: 'apps', label: 'Apps'),
|
new TableHeader(key: 'apps', label: 'Apps'),
|
||||||
new TableHeader(key: 'users', label: 'Users'),
|
new TableHeader(key: 'users', label: 'Users'),
|
||||||
], DataCollection::class);
|
], DataCollection::class);
|
||||||
@ -85,7 +86,10 @@ public function save(): void
|
|||||||
$this->form->validate();
|
$this->form->validate();
|
||||||
$this->attempt(
|
$this->attempt(
|
||||||
action: function (): void {
|
action: function (): void {
|
||||||
$role = $this->roleService->create(new CreateRoleData(name: $this->form->name));
|
$role = $this->roleService->create(new CreateRoleData(
|
||||||
|
name: $this->form->name,
|
||||||
|
priority: (int) $this->form->priority,
|
||||||
|
));
|
||||||
$this->appRoleService->assign(new AssignAppsToRoleData(
|
$this->appRoleService->assign(new AssignAppsToRoleData(
|
||||||
roleId: $role->id,
|
roleId: $role->id,
|
||||||
validUpto: $this->form->isUnlimited ? now()->addYears(15) : $this->form->validUpto,
|
validUpto: $this->form->isUnlimited ? now()->addYears(15) : $this->form->validUpto,
|
||||||
@ -128,6 +132,10 @@ public function delete(int $roleId): void
|
|||||||
<span class="font-medium text-gray-900">{{ $row->name }}</span>
|
<span class="font-medium text-gray-900">{{ $row->name }}</span>
|
||||||
@endscope
|
@endscope
|
||||||
|
|
||||||
|
@scope('cell_priority', $row)
|
||||||
|
<span class="text-gray-500 font-medium">{{ $row->priority }}</span>
|
||||||
|
@endscope
|
||||||
|
|
||||||
@scope('cell_apps', $row)
|
@scope('cell_apps', $row)
|
||||||
@foreach($row->apps as $app)
|
@foreach($row->apps as $app)
|
||||||
@php /** @var RoleAppData $app */ @endphp
|
@php /** @var RoleAppData $app */ @endphp
|
||||||
@ -178,6 +186,14 @@ class="btn-sm btn-circle btn-error btn-soft"
|
|||||||
autofocus
|
autofocus
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<x-mary-input
|
||||||
|
label="Priority"
|
||||||
|
wire:model="form.priority"
|
||||||
|
id="role-priority-input"
|
||||||
|
type="number"
|
||||||
|
placeholder="e.g. 1, 2, 3"
|
||||||
|
/>
|
||||||
|
|
||||||
<x-shared.choices
|
<x-shared.choices
|
||||||
wire:model="form.appIds"
|
wire:model="form.appIds"
|
||||||
:options="$appsSearchable"
|
:options="$appsSearchable"
|
||||||
|
|||||||
@ -1,11 +1,11 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Concerns\{Confirmation, HandlesOperations};
|
use App\Concerns\{Confirmation, HandlesOperations};
|
||||||
use App\Data\Role\{AssignAppsToRoleData, AssignUsersToRoleData, RoleAppData};
|
use App\Data\Role\{AssignAppsToRoleData, AssignUsersToRoleData, RoleAppData, UpdateRolePriorityData};
|
||||||
use App\Data\Ui\TableHeader;
|
use App\Data\Ui\TableHeader;
|
||||||
use App\Livewire\Forms\AssignAppToRoleForm;
|
use App\Livewire\Forms\AssignAppToRoleForm;
|
||||||
use App\Models\Role;
|
use App\Models\Role;
|
||||||
use App\Services\AppRoleService;
|
use App\Services\{AppRoleService, RoleService};
|
||||||
use Livewire\Attributes\{Computed, Layout, Title};
|
use Livewire\Attributes\{Computed, Layout, Title};
|
||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
@ -24,6 +24,9 @@ class extends Component {
|
|||||||
public int $roleId = 0;
|
public int $roleId = 0;
|
||||||
|
|
||||||
public string $roleName = '';
|
public string $roleName = '';
|
||||||
|
public int $rolePriority = 0;
|
||||||
|
public bool $showEditPriorityModal = false;
|
||||||
|
public ?int $newPriority = null;
|
||||||
public bool $showAddAppModal = false;
|
public bool $showAddAppModal = false;
|
||||||
|
|
||||||
// UI State for Editing
|
// UI State for Editing
|
||||||
@ -50,9 +53,12 @@ class extends Component {
|
|||||||
#[DataCollectionOf(TableHeader::class)]
|
#[DataCollectionOf(TableHeader::class)]
|
||||||
public DataCollection $userHeaders;
|
public DataCollection $userHeaders;
|
||||||
|
|
||||||
public function boot(AppRoleService $appRoleService): void
|
protected RoleService $roleService;
|
||||||
|
|
||||||
|
public function boot(AppRoleService $appRoleService, RoleService $roleService): void
|
||||||
{
|
{
|
||||||
$this->appRoleService = $appRoleService;
|
$this->appRoleService = $appRoleService;
|
||||||
|
$this->roleService = $roleService;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function mount(int $id): void
|
public function mount(int $id): void
|
||||||
@ -62,6 +68,7 @@ public function mount(int $id): void
|
|||||||
$role = Role::query()->findOrFail($id);
|
$role = Role::query()->findOrFail($id);
|
||||||
$this->roleId = $role->id;
|
$this->roleId = $role->id;
|
||||||
$this->roleName = $role->name;
|
$this->roleName = $role->name;
|
||||||
|
$this->rolePriority = $role->priority;
|
||||||
},
|
},
|
||||||
onError: function (): void {
|
onError: function (): void {
|
||||||
$this->redirectRoute('access-manager.roles-and-apps.index', navigate: true);
|
$this->redirectRoute('access-manager.roles-and-apps.index', navigate: true);
|
||||||
@ -253,6 +260,32 @@ public function detachUser(int $userId): void
|
|||||||
successMessage: 'User removed from role.',
|
successMessage: 'User removed from role.',
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function openEditPriorityModal(): void
|
||||||
|
{
|
||||||
|
$this->newPriority = $this->rolePriority;
|
||||||
|
$this->resetErrorBag();
|
||||||
|
$this->showEditPriorityModal = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function savePriority(): void
|
||||||
|
{
|
||||||
|
$this->validate([
|
||||||
|
'newPriority' => 'required|integer|min:0',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$this->attempt(
|
||||||
|
action: function (): void {
|
||||||
|
$this->roleService->updatePriority(new UpdateRolePriorityData(
|
||||||
|
roleId: $this->roleId,
|
||||||
|
priority: (int) $this->newPriority,
|
||||||
|
));
|
||||||
|
$this->rolePriority = (int) $this->newPriority;
|
||||||
|
$this->showEditPriorityModal = false;
|
||||||
|
},
|
||||||
|
successMessage: 'Role priority updated successfully!',
|
||||||
|
);
|
||||||
|
}
|
||||||
};
|
};
|
||||||
?>
|
?>
|
||||||
|
|
||||||
@ -268,6 +301,16 @@ class="btn-sm btn-ghost"
|
|||||||
tooltip-right="Back To Roles & Apps"
|
tooltip-right="Back To Roles & Apps"
|
||||||
/>
|
/>
|
||||||
<h1 class="text-2xl font-semibold text-gray-900">{{ $roleName }}</h1>
|
<h1 class="text-2xl font-semibold text-gray-900">{{ $roleName }}</h1>
|
||||||
|
<div class="flex items-center gap-1.5">
|
||||||
|
<x-mary-badge class="badge-soft badge-secondary" value="Priority: {{ $rolePriority }}"/>
|
||||||
|
<x-mary-button
|
||||||
|
icon="lucide.pencil"
|
||||||
|
wire:click="openEditPriorityModal"
|
||||||
|
spinner="openEditPriorityModal"
|
||||||
|
class="btn-sm btn-ghost btn-circle"
|
||||||
|
tooltip="Edit priority"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -440,4 +483,26 @@ class="toggle-secondary"
|
|||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
</x-mary-form>
|
</x-mary-form>
|
||||||
</x-mary-modal>
|
</x-mary-modal>
|
||||||
|
|
||||||
|
<x-mary-modal wire:model="showEditPriorityModal" title="Edit Role Priority">
|
||||||
|
<x-mary-form wire:submit="savePriority">
|
||||||
|
<x-mary-input
|
||||||
|
label="Priority"
|
||||||
|
wire:model="newPriority"
|
||||||
|
id="role-new-priority-input"
|
||||||
|
type="number"
|
||||||
|
placeholder="e.g. 1, 2, 3"
|
||||||
|
autofocus
|
||||||
|
/>
|
||||||
|
|
||||||
|
<x-slot:actions>
|
||||||
|
<x-mary-button @click="$wire.showEditPriorityModal = false" class="btn-ghost">
|
||||||
|
Cancel
|
||||||
|
</x-mary-button>
|
||||||
|
<x-mary-button type="submit" class="btn-primary" icon="lucide.save" spinner="savePriority">
|
||||||
|
Save
|
||||||
|
</x-mary-button>
|
||||||
|
</x-slot:actions>
|
||||||
|
</x-mary-form>
|
||||||
|
</x-mary-modal>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user