Feat: refactor app role duration to validity date
- Replaced `duration` (integer, days) with `validUpto` (datetime) in role-app assignments. - Updated `AssignAppsToRoleData`, `AppRole` model, and Livewire forms to adopt the new `validUpto` field. - Adjusted database migrations and UI components to reflect the change in validity representation. - Enhanced validity date inputs with support for "Unlimited" roles using toggles.
This commit is contained in:
parent
1e5c084855
commit
fd57ac57e1
@ -10,7 +10,7 @@ final class AssignAppsToRoleData extends Data
|
|||||||
{
|
{
|
||||||
public function __construct(
|
public function __construct(
|
||||||
public int $roleId,
|
public int $roleId,
|
||||||
public int $duration,
|
public string $validUpto,
|
||||||
/** @var int[] */
|
/** @var int[] */
|
||||||
public array $appIds
|
public array $appIds
|
||||||
) {}
|
) {}
|
||||||
|
|||||||
@ -13,7 +13,7 @@ public function __construct(
|
|||||||
public int $id,
|
public int $id,
|
||||||
public int $appId,
|
public int $appId,
|
||||||
public string $appName,
|
public string $appName,
|
||||||
public int $duration,
|
public string $duration,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -15,13 +15,15 @@ class AssignAppToRoleForm extends Form
|
|||||||
])]
|
])]
|
||||||
public array $appIds = [];
|
public array $appIds = [];
|
||||||
|
|
||||||
#[Validate('required|integer|min:1|max:3650')]
|
#[Validate('exclude_if:isUnlimited,true|required|date|after_or_equal:today')]
|
||||||
public int $duration = 30;
|
public ?string $validUpto = null;
|
||||||
|
|
||||||
|
public bool $isUnlimited = false;
|
||||||
|
|
||||||
public function reset(mixed ...$properties): void
|
public function reset(mixed ...$properties): void
|
||||||
{
|
{
|
||||||
$this->resetErrorBag();
|
$this->resetErrorBag();
|
||||||
parent::reset(...$properties);
|
parent::reset(...$properties);
|
||||||
$this->duration = 30;
|
$this->validUpto = null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -12,7 +12,7 @@
|
|||||||
* @property int $id
|
* @property int $id
|
||||||
* @property int $app_id
|
* @property int $app_id
|
||||||
* @property int $role_id
|
* @property int $role_id
|
||||||
* @property int $duration Duration in days.
|
* @property string $duration Validity of the role
|
||||||
*/
|
*/
|
||||||
#[Fillable(['app_id', 'role_id', 'duration'])]
|
#[Fillable(['app_id', 'role_id', 'duration'])]
|
||||||
class AppRole extends Pivot
|
class AppRole extends Pivot
|
||||||
|
|||||||
@ -30,22 +30,23 @@ public function assign(AssignAppsToRoleData $data): void
|
|||||||
if (empty($data->appIds)) {
|
if (empty($data->appIds)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
$role = Role::query()->findOrFail($data->roleId);
|
DB::transaction(function () use ($data): void {
|
||||||
|
$role = Role::query()->findOrFail($data->roleId);
|
||||||
|
|
||||||
$this->assignAppPermissionsRole($role, $data->appIds);
|
$this->assignAppPermissionsRole($role, $data->appIds);
|
||||||
|
|
||||||
$records = array_map(fn (int $appId) => [
|
$records = array_map(fn (int $appId) => [
|
||||||
'role_id' => $data->roleId,
|
'role_id' => $data->roleId,
|
||||||
'app_id' => $appId,
|
'app_id' => $appId,
|
||||||
'duration' => $data->duration,
|
'duration' => $data->validUpto,
|
||||||
], $data->appIds);
|
], $data->appIds);
|
||||||
|
|
||||||
AppRole::query()->upsert(
|
|
||||||
$records,
|
|
||||||
['role_id', 'app_id'],
|
|
||||||
['duration']
|
|
||||||
);
|
|
||||||
|
|
||||||
|
AppRole::query()->upsert(
|
||||||
|
$records,
|
||||||
|
['role_id', 'app_id'],
|
||||||
|
['duration']
|
||||||
|
);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@ -18,7 +18,7 @@ public function up(): void
|
|||||||
$table->id();
|
$table->id();
|
||||||
$table->foreignIdFor(ConnectedApp::class, 'app_id');
|
$table->foreignIdFor(ConnectedApp::class, 'app_id');
|
||||||
$table->foreignId('role_id')->comment('role_id from spatie roles table');
|
$table->foreignId('role_id')->comment('role_id from spatie roles table');
|
||||||
$table->integer('duration')->comment('in days');
|
$table->dateTime('duration');
|
||||||
$table->unique(['app_id', 'role_id']);
|
$table->unique(['app_id', 'role_id']);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@ -81,13 +81,11 @@ public function save(): void
|
|||||||
$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));
|
||||||
$this->appRoleService->assign(
|
$this->appRoleService->assign(new AssignAppsToRoleData(
|
||||||
new AssignAppsToRoleData(
|
roleId: $role->id,
|
||||||
roleId: $role->id,
|
validUpto: $this->form->isUnlimited ? now()->addYears(15) : $this->form->validUpto,
|
||||||
duration: $this->form->duration,
|
appIds: $this->form->appIds,
|
||||||
appIds: $this->form->appIds
|
));
|
||||||
)
|
|
||||||
);
|
|
||||||
$this->showCreateModal = false;
|
$this->showCreateModal = false;
|
||||||
$this->form->reset();
|
$this->form->reset();
|
||||||
unset($this->getRoles);
|
unset($this->getRoles);
|
||||||
|
|||||||
@ -7,6 +7,7 @@
|
|||||||
use App\Models\Role;
|
use App\Models\Role;
|
||||||
use App\Services\AppRoleService;
|
use App\Services\AppRoleService;
|
||||||
use Livewire\Attributes\{Computed, Layout, Title};
|
use Livewire\Attributes\{Computed, Layout, Title};
|
||||||
|
use Illuminate\Support\Carbon;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||||
use Spatie\LaravelData\DataCollection;
|
use Spatie\LaravelData\DataCollection;
|
||||||
@ -71,7 +72,7 @@ public function mount(int $id): void
|
|||||||
|
|
||||||
$this->appHeaders = TableHeader::collect([
|
$this->appHeaders = TableHeader::collect([
|
||||||
new TableHeader(key: 'app_name', label: 'App'),
|
new TableHeader(key: 'app_name', label: 'App'),
|
||||||
new TableHeader(key: 'duration', label: 'Duration (days)'),
|
new TableHeader(key: 'duration', label: 'Validity'),
|
||||||
], DataCollection::class);
|
], DataCollection::class);
|
||||||
|
|
||||||
$this->userHeaders = TableHeader::collect([
|
$this->userHeaders = TableHeader::collect([
|
||||||
@ -118,7 +119,7 @@ public function openEditAppModal(int $assignmentId): void
|
|||||||
|
|
||||||
if ($assignment) {
|
if ($assignment) {
|
||||||
$this->assignForm->appIds = [$assignment['appId'] ?? $assignment['id']];
|
$this->assignForm->appIds = [$assignment['appId'] ?? $assignment['id']];
|
||||||
$this->assignForm->duration = $assignment['duration'];
|
$this->assignForm->validUpto = $assignment['duration'];
|
||||||
|
|
||||||
$this->editingAppName = $assignment['appName'];
|
$this->editingAppName = $assignment['appName'];
|
||||||
}
|
}
|
||||||
@ -160,12 +161,12 @@ public function assignApp(): void
|
|||||||
{
|
{
|
||||||
$this->attempt(
|
$this->attempt(
|
||||||
action: function (): void {
|
action: function (): void {
|
||||||
|
$this->assignForm->validate();
|
||||||
$this->appRoleService->assign(new AssignAppsToRoleData(
|
$this->appRoleService->assign(new AssignAppsToRoleData(
|
||||||
roleId: $this->roleId,
|
roleId: $this->roleId,
|
||||||
duration: $this->assignForm->duration,
|
validUpto: $this->assignForm->isUnlimited ? now()->addYears(15) : $this->assignForm->validUpto,
|
||||||
appIds: $this->assignForm->appIds,
|
appIds: $this->assignForm->appIds,
|
||||||
));
|
));
|
||||||
|
|
||||||
$this->assignForm->reset();
|
$this->assignForm->reset();
|
||||||
$this->showAddAppModal = false;
|
$this->showAddAppModal = false;
|
||||||
unset($this->apps);
|
unset($this->apps);
|
||||||
@ -178,9 +179,10 @@ public function updateApp(): void
|
|||||||
{
|
{
|
||||||
$this->attempt(
|
$this->attempt(
|
||||||
action: function (): void {
|
action: function (): void {
|
||||||
|
$this->assignForm->validate();
|
||||||
$this->appRoleService->assign(new AssignAppsToRoleData(
|
$this->appRoleService->assign(new AssignAppsToRoleData(
|
||||||
roleId: $this->roleId,
|
roleId: $this->roleId,
|
||||||
duration: $this->assignForm->duration,
|
validUpto: $this->assignForm->isUnlimited ? now()->addYears(15) : $this->assignForm->validUpto,
|
||||||
appIds: $this->assignForm->appIds,
|
appIds: $this->assignForm->appIds,
|
||||||
));
|
));
|
||||||
|
|
||||||
@ -275,7 +277,7 @@ class="btn-sm btn-ghost"
|
|||||||
|
|
||||||
<x-shared.card title="Connected Apps" icon="lucide-blocks" class="pb-2 w-full h-min">
|
<x-shared.card title="Connected Apps" icon="lucide-blocks" class="pb-2 w-full h-min">
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-mary-button wire:click="openAddAppModal" icon="lucide.plus" class="btn-sm">
|
<x-mary-button wire:click="openAddAppModal" spinner="openAddAppModal" icon="lucide.plus" class="btn-sm">
|
||||||
Add App
|
Add App
|
||||||
</x-mary-button>
|
</x-mary-button>
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
@ -290,7 +292,7 @@ class="btn-sm btn-ghost"
|
|||||||
@endscope
|
@endscope
|
||||||
|
|
||||||
@scope('cell_duration', $row)
|
@scope('cell_duration', $row)
|
||||||
{{ $row['duration'] }} days
|
{{ Carbon::parse($row['duration'])->diffForHumans() }}
|
||||||
@endscope
|
@endscope
|
||||||
|
|
||||||
@scope('actions', $row)
|
@scope('actions', $row)
|
||||||
@ -358,27 +360,16 @@ class="btn-sm btn-error btn-soft btn-circle"
|
|||||||
|
|
||||||
@if(!$isEditingApp)
|
@if(!$isEditingApp)
|
||||||
<div>
|
<div>
|
||||||
<div class="mb-1 flex items-end justify-between">
|
<x-shared.choices
|
||||||
<label class="text-sm font-semibold text-gray-700">Connected Apps</label>
|
|
||||||
<div class="flex gap-3 text-xs">
|
|
||||||
<button type="button" wire:click="selectAllApps" class="text-primary hover:underline">
|
|
||||||
Select All
|
|
||||||
</button>
|
|
||||||
<button type="button" wire:click="clearApps" class="text-error hover:underline">
|
|
||||||
Clear
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<x-mary-choices
|
|
||||||
wire:model="assignForm.appIds"
|
wire:model="assignForm.appIds"
|
||||||
|
:label="__('Connected Apps')"
|
||||||
:options="$appsSearchable"
|
:options="$appsSearchable"
|
||||||
|
searchable
|
||||||
search-function="searchApps"
|
search-function="searchApps"
|
||||||
option-label="name"
|
select-all-action="selectAllApps"
|
||||||
option-value="id"
|
clear-action="clearApps"
|
||||||
no-result-text="No apps found."
|
no-result-text="No apps found."
|
||||||
placeholder="Search apps..."
|
placeholder="Search apps..."
|
||||||
searchable
|
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
@ -390,23 +381,34 @@ class="btn-sm btn-error btn-soft btn-circle"
|
|||||||
class="bg-gray-50 text-gray-500 cursor-not-allowed"
|
class="bg-gray-50 text-gray-500 cursor-not-allowed"
|
||||||
/>
|
/>
|
||||||
@endif
|
@endif
|
||||||
|
<div x-data="{ isUnlimited: @entangle('assignForm.isUnlimited') }"
|
||||||
|
class="flex gap-2 items-baseline-last">
|
||||||
|
|
||||||
<x-mary-input
|
<div class="flex-1 transition-opacity duration-200"
|
||||||
label="Duration (days)"
|
x-bind:class="isUnlimited ? 'opacity-50 pointer-events-none' : ''">
|
||||||
wire:model="assignForm.duration"
|
<x-mary-datepicker
|
||||||
id="assign-duration-input"
|
label="Date"
|
||||||
type="number"
|
wire:model="assignForm.validUpto"
|
||||||
min="1"
|
icon="lucide.calendar"
|
||||||
max="3650"
|
x-bind:disabled="isUnlimited"
|
||||||
placeholder="30"
|
x-bind:readonly="isUnlimited"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<x-mary-toggle
|
||||||
|
class="toggle-secondary"
|
||||||
|
label="Unlimited"
|
||||||
|
x-model="isUnlimited"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<x-slot:actions>
|
<x-slot:actions>
|
||||||
<x-mary-button @click="$wire.showAddAppModal = false" class="btn-ghost">
|
<x-mary-button @click="$wire.showAddAppModal = false" class="btn-ghost">
|
||||||
Cancel
|
Cancel
|
||||||
</x-mary-button>
|
</x-mary-button>
|
||||||
<x-mary-button type="submit" icon="{{ $isEditingApp ? 'lucide.save' : 'lucide.link' }}"
|
<x-mary-button type="submit" class="btn-primary"
|
||||||
|
icon="{{ $isEditingApp ? 'lucide.save' : 'lucide.link' }}"
|
||||||
spinner="saveApp">
|
spinner="saveApp">
|
||||||
{{ $isEditingApp ? 'Update Duration' : 'Assign Apps' }}
|
{{ $isEditingApp ? 'Update Duration' : 'Assign Apps' }}
|
||||||
</x-mary-button>
|
</x-mary-button>
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
<meta charset="utf-8" />
|
<meta charset="utf-8"/>
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||||
|
|
||||||
<title>
|
<title>
|
||||||
{{ filled($title ?? null) ? $title.' - '.config('app.name', 'Laravel') : config('app.name', 'Laravel') }}
|
{{ filled($title ?? null) ? $title.' - '.config('app.name', 'Laravel') : config('app.name', 'Laravel') }}
|
||||||
@ -13,3 +13,6 @@
|
|||||||
|
|
||||||
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
@vite(['resources/css/app.css', 'resources/js/app.js'])
|
||||||
@livewireStyles
|
@livewireStyles
|
||||||
|
{{--For date picker in maryui. Remove this if opt for native --}}
|
||||||
|
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/flatpickr/dist/flatpickr.min.css">
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/flatpickr"></script>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user