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 int $roleId,
|
||||
public int $duration,
|
||||
public string $validUpto,
|
||||
/** @var int[] */
|
||||
public array $appIds
|
||||
) {}
|
||||
|
||||
@ -13,7 +13,7 @@ public function __construct(
|
||||
public int $id,
|
||||
public int $appId,
|
||||
public string $appName,
|
||||
public int $duration,
|
||||
public string $duration,
|
||||
) {}
|
||||
|
||||
/**
|
||||
|
||||
@ -15,13 +15,15 @@ class AssignAppToRoleForm extends Form
|
||||
])]
|
||||
public array $appIds = [];
|
||||
|
||||
#[Validate('required|integer|min:1|max:3650')]
|
||||
public int $duration = 30;
|
||||
#[Validate('exclude_if:isUnlimited,true|required|date|after_or_equal:today')]
|
||||
public ?string $validUpto = null;
|
||||
|
||||
public bool $isUnlimited = false;
|
||||
|
||||
public function reset(mixed ...$properties): void
|
||||
{
|
||||
$this->resetErrorBag();
|
||||
parent::reset(...$properties);
|
||||
$this->duration = 30;
|
||||
$this->validUpto = null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,7 +12,7 @@
|
||||
* @property int $id
|
||||
* @property int $app_id
|
||||
* @property int $role_id
|
||||
* @property int $duration Duration in days.
|
||||
* @property string $duration Validity of the role
|
||||
*/
|
||||
#[Fillable(['app_id', 'role_id', 'duration'])]
|
||||
class AppRole extends Pivot
|
||||
|
||||
@ -30,6 +30,7 @@ public function assign(AssignAppsToRoleData $data): void
|
||||
if (empty($data->appIds)) {
|
||||
return;
|
||||
}
|
||||
DB::transaction(function () use ($data): void {
|
||||
$role = Role::query()->findOrFail($data->roleId);
|
||||
|
||||
$this->assignAppPermissionsRole($role, $data->appIds);
|
||||
@ -37,7 +38,7 @@ public function assign(AssignAppsToRoleData $data): void
|
||||
$records = array_map(fn (int $appId) => [
|
||||
'role_id' => $data->roleId,
|
||||
'app_id' => $appId,
|
||||
'duration' => $data->duration,
|
||||
'duration' => $data->validUpto,
|
||||
], $data->appIds);
|
||||
|
||||
AppRole::query()->upsert(
|
||||
@ -45,7 +46,7 @@ public function assign(AssignAppsToRoleData $data): void
|
||||
['role_id', 'app_id'],
|
||||
['duration']
|
||||
);
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -18,7 +18,7 @@ public function up(): void
|
||||
$table->id();
|
||||
$table->foreignIdFor(ConnectedApp::class, 'app_id');
|
||||
$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']);
|
||||
});
|
||||
}
|
||||
|
||||
@ -81,13 +81,11 @@ public function save(): void
|
||||
$this->attempt(
|
||||
action: function (): void {
|
||||
$role = $this->roleService->create(new CreateRoleData(name: $this->form->name));
|
||||
$this->appRoleService->assign(
|
||||
new AssignAppsToRoleData(
|
||||
$this->appRoleService->assign(new AssignAppsToRoleData(
|
||||
roleId: $role->id,
|
||||
duration: $this->form->duration,
|
||||
appIds: $this->form->appIds
|
||||
)
|
||||
);
|
||||
validUpto: $this->form->isUnlimited ? now()->addYears(15) : $this->form->validUpto,
|
||||
appIds: $this->form->appIds,
|
||||
));
|
||||
$this->showCreateModal = false;
|
||||
$this->form->reset();
|
||||
unset($this->getRoles);
|
||||
|
||||
@ -7,6 +7,7 @@
|
||||
use App\Models\Role;
|
||||
use App\Services\AppRoleService;
|
||||
use Livewire\Attributes\{Computed, Layout, Title};
|
||||
use Illuminate\Support\Carbon;
|
||||
use Livewire\Component;
|
||||
use Spatie\LaravelData\Attributes\DataCollectionOf;
|
||||
use Spatie\LaravelData\DataCollection;
|
||||
@ -71,7 +72,7 @@ public function mount(int $id): void
|
||||
|
||||
$this->appHeaders = TableHeader::collect([
|
||||
new TableHeader(key: 'app_name', label: 'App'),
|
||||
new TableHeader(key: 'duration', label: 'Duration (days)'),
|
||||
new TableHeader(key: 'duration', label: 'Validity'),
|
||||
], DataCollection::class);
|
||||
|
||||
$this->userHeaders = TableHeader::collect([
|
||||
@ -118,7 +119,7 @@ public function openEditAppModal(int $assignmentId): void
|
||||
|
||||
if ($assignment) {
|
||||
$this->assignForm->appIds = [$assignment['appId'] ?? $assignment['id']];
|
||||
$this->assignForm->duration = $assignment['duration'];
|
||||
$this->assignForm->validUpto = $assignment['duration'];
|
||||
|
||||
$this->editingAppName = $assignment['appName'];
|
||||
}
|
||||
@ -160,12 +161,12 @@ public function assignApp(): void
|
||||
{
|
||||
$this->attempt(
|
||||
action: function (): void {
|
||||
$this->assignForm->validate();
|
||||
$this->appRoleService->assign(new AssignAppsToRoleData(
|
||||
roleId: $this->roleId,
|
||||
duration: $this->assignForm->duration,
|
||||
validUpto: $this->assignForm->isUnlimited ? now()->addYears(15) : $this->assignForm->validUpto,
|
||||
appIds: $this->assignForm->appIds,
|
||||
));
|
||||
|
||||
$this->assignForm->reset();
|
||||
$this->showAddAppModal = false;
|
||||
unset($this->apps);
|
||||
@ -178,9 +179,10 @@ public function updateApp(): void
|
||||
{
|
||||
$this->attempt(
|
||||
action: function (): void {
|
||||
$this->assignForm->validate();
|
||||
$this->appRoleService->assign(new AssignAppsToRoleData(
|
||||
roleId: $this->roleId,
|
||||
duration: $this->assignForm->duration,
|
||||
validUpto: $this->assignForm->isUnlimited ? now()->addYears(15) : $this->assignForm->validUpto,
|
||||
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-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
|
||||
</x-mary-button>
|
||||
</x-slot:actions>
|
||||
@ -290,7 +292,7 @@ class="btn-sm btn-ghost"
|
||||
@endscope
|
||||
|
||||
@scope('cell_duration', $row)
|
||||
{{ $row['duration'] }} days
|
||||
{{ Carbon::parse($row['duration'])->diffForHumans() }}
|
||||
@endscope
|
||||
|
||||
@scope('actions', $row)
|
||||
@ -358,27 +360,16 @@ class="btn-sm btn-error btn-soft btn-circle"
|
||||
|
||||
@if(!$isEditingApp)
|
||||
<div>
|
||||
<div class="mb-1 flex items-end justify-between">
|
||||
<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
|
||||
<x-shared.choices
|
||||
wire:model="assignForm.appIds"
|
||||
:label="__('Connected Apps')"
|
||||
:options="$appsSearchable"
|
||||
searchable
|
||||
search-function="searchApps"
|
||||
option-label="name"
|
||||
option-value="id"
|
||||
select-all-action="selectAllApps"
|
||||
clear-action="clearApps"
|
||||
no-result-text="No apps found."
|
||||
placeholder="Search apps..."
|
||||
searchable
|
||||
/>
|
||||
</div>
|
||||
@else
|
||||
@ -390,23 +381,34 @@ class="btn-sm btn-error btn-soft btn-circle"
|
||||
class="bg-gray-50 text-gray-500 cursor-not-allowed"
|
||||
/>
|
||||
@endif
|
||||
<div x-data="{ isUnlimited: @entangle('assignForm.isUnlimited') }"
|
||||
class="flex gap-2 items-baseline-last">
|
||||
|
||||
<x-mary-input
|
||||
label="Duration (days)"
|
||||
wire:model="assignForm.duration"
|
||||
id="assign-duration-input"
|
||||
type="number"
|
||||
min="1"
|
||||
max="3650"
|
||||
placeholder="30"
|
||||
<div class="flex-1 transition-opacity duration-200"
|
||||
x-bind:class="isUnlimited ? 'opacity-50 pointer-events-none' : ''">
|
||||
<x-mary-datepicker
|
||||
label="Date"
|
||||
wire:model="assignForm.validUpto"
|
||||
icon="lucide.calendar"
|
||||
x-bind:disabled="isUnlimited"
|
||||
x-bind:readonly="isUnlimited"
|
||||
/>
|
||||
</div>
|
||||
|
||||
<x-mary-toggle
|
||||
class="toggle-secondary"
|
||||
label="Unlimited"
|
||||
x-model="isUnlimited"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<x-slot:actions>
|
||||
<x-mary-button @click="$wire.showAddAppModal = false" class="btn-ghost">
|
||||
Cancel
|
||||
</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">
|
||||
{{ $isEditingApp ? 'Update Duration' : 'Assign Apps' }}
|
||||
</x-mary-button>
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta charset="utf-8"/>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
||||
|
||||
<title>
|
||||
{{ 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'])
|
||||
@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