Feat: add ConnectedAppFactory, MockDataSeeder, and role-app connection logic
- Introduced `ConnectedAppFactory` for generating connected app data. - Added `MockDataSeeder` to create sample roles, users, and apps with dynamic role-app assignments. - Extended `RoleAndAppsPermissionEnum` with `ConnectApps` permission. - Updated access-manager UI to handle app-connection actions with proper authorization. - Integrated app-role connection logic in Livewire component and updated `roles-and-apps` view for permissions-based actions.
This commit is contained in:
parent
cd29191c7b
commit
d024308c2e
@ -11,4 +11,5 @@ enum RoleAndAppsPermissionEnum: string
|
|||||||
case Read = 'roles-and-apps:read';
|
case Read = 'roles-and-apps:read';
|
||||||
case Update = 'roles-and-apps:update';
|
case Update = 'roles-and-apps:update';
|
||||||
case Delete = 'roles-and-apps:delete';
|
case Delete = 'roles-and-apps:delete';
|
||||||
|
case ConnectApps = 'roles-and-apps:connect-apps';
|
||||||
}
|
}
|
||||||
|
|||||||
@ -5,6 +5,7 @@
|
|||||||
namespace App\Models;
|
namespace App\Models;
|
||||||
|
|
||||||
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||||
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||||
use Illuminate\Database\Eloquent\{Model, Relations\BelongsTo, Relations\BelongsToMany, SoftDeletes};
|
use Illuminate\Database\Eloquent\{Model, Relations\BelongsTo, Relations\BelongsToMany, SoftDeletes};
|
||||||
use Illuminate\Database\Eloquent\Relations\HasOne;
|
use Illuminate\Database\Eloquent\Relations\HasOne;
|
||||||
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
||||||
@ -24,7 +25,7 @@
|
|||||||
])]
|
])]
|
||||||
class ConnectedApp extends Model
|
class ConnectedApp extends Model
|
||||||
{
|
{
|
||||||
use LogsActivity, SoftDeletes;
|
use HasFactory, LogsActivity, SoftDeletes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return HasOne<ConnectionProtocol, $this>
|
* @return HasOne<ConnectionProtocol, $this>
|
||||||
|
|||||||
35
database/factories/ConnectedAppFactory.php
Normal file
35
database/factories/ConnectedAppFactory.php
Normal file
@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Database\Factories;
|
||||||
|
|
||||||
|
use App\Models\{ConnectedApp, ConnectionProtocol, ConnectionProvider, ConnectionStatus};
|
||||||
|
use Illuminate\Database\Eloquent\Factories\Factory;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @extends \Illuminate\Database\Eloquent\Factories\Factory<\App\Models\ConnectedApp>
|
||||||
|
*/
|
||||||
|
class ConnectedAppFactory extends Factory
|
||||||
|
{
|
||||||
|
protected $model = ConnectedApp::class;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Define the model's default state.
|
||||||
|
*
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
public function definition(): array
|
||||||
|
{
|
||||||
|
$name = $this->faker->unique()->company().' '.$this->faker->randomElement(['SSO', 'Portal', 'OAuth', 'API', 'App']);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'name' => $name,
|
||||||
|
'slug' => Str::slug($name),
|
||||||
|
'connection_protocol_id' => ConnectionProtocol::inRandomOrder()->first()?->id ?? 1,
|
||||||
|
'connection_provider_id' => ConnectionProvider::inRandomOrder()->first()?->id ?? 1,
|
||||||
|
'connection_status_id' => ConnectionStatus::inRandomOrder()->first()?->id ?? 1,
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -21,5 +21,6 @@ public function run(): void
|
|||||||
$this->call(PermissionSeeder::class);
|
$this->call(PermissionSeeder::class);
|
||||||
$this->call(DefaultRoleWithPermissionSeeder::class);
|
$this->call(DefaultRoleWithPermissionSeeder::class);
|
||||||
$this->call(ExampleUserWithRoleSeeder::class);
|
$this->call(ExampleUserWithRoleSeeder::class);
|
||||||
|
$this->call(MockDataSeeder::class);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
79
database/seeders/MockDataSeeder.php
Normal file
79
database/seeders/MockDataSeeder.php
Normal file
@ -0,0 +1,79 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use App\Data\ConnectedApp\ConnectAppRequest;
|
||||||
|
use App\Data\Role\AssignAppsToRoleData;
|
||||||
|
use App\Models\{ConnectedApp, Role, User};
|
||||||
|
use App\Services\{AppRoleService, ConnectedAppService};
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
class MockDataSeeder extends Seeder
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private AppRoleService $appRoleService,
|
||||||
|
private ConnectedAppService $connectedAppService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
// 1. Create 5 roles with different priorities
|
||||||
|
$roleNames = ['Manager', 'Editor', 'Auditor', 'Developer', 'Support'];
|
||||||
|
$priorities = [10, 20, 30, 40, 50];
|
||||||
|
$roles = [];
|
||||||
|
|
||||||
|
foreach ($roleNames as $index => $name) {
|
||||||
|
$role = Role::findOrCreate($name, 'web');
|
||||||
|
$role->update(['priority' => $priorities[$index]]);
|
||||||
|
$roles[] = $role;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. Create 5 users and assign them role combinations
|
||||||
|
$users = User::factory()->count(5)->create();
|
||||||
|
|
||||||
|
// Assign combinations
|
||||||
|
$users[0]->assignRole(['Manager', 'Editor']);
|
||||||
|
$users[1]->assignRole(['Developer', 'Support']);
|
||||||
|
$users[2]->assignRole(['Auditor']);
|
||||||
|
$users[3]->assignRole(['Manager', 'Developer']);
|
||||||
|
$users[4]->assignRole(['Support']);
|
||||||
|
|
||||||
|
// 3. Create 10 apps using ConnectedAppService to automatically register permissions
|
||||||
|
$apps = [];
|
||||||
|
$factory = ConnectedApp::factory();
|
||||||
|
|
||||||
|
for ($i = 0; $i < 10; $i++) {
|
||||||
|
$data = $factory->definition();
|
||||||
|
$request = new ConnectAppRequest(
|
||||||
|
name: $data['name'],
|
||||||
|
connectionProviderId: $data['connection_provider_id'],
|
||||||
|
connectionProtocolId: $data['connection_protocol_id'],
|
||||||
|
slug: $data['slug'],
|
||||||
|
connectionStatusId: $data['connection_status_id'],
|
||||||
|
);
|
||||||
|
|
||||||
|
$this->connectedAppService->create($request);
|
||||||
|
$apps[] = ConnectedApp::where('slug', $data['slug'])->firstOrFail();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Connect apps to roles using AppRoleService to automatically assign permissions
|
||||||
|
$roleAppsMapping = [
|
||||||
|
'Manager' => [$apps[0]->id, $apps[1]->id, $apps[2]->id],
|
||||||
|
'Editor' => [$apps[2]->id, $apps[3]->id, $apps[4]->id],
|
||||||
|
'Auditor' => [$apps[5]->id],
|
||||||
|
'Developer' => [$apps[6]->id, $apps[7]->id, $apps[8]->id],
|
||||||
|
'Support' => [$apps[8]->id, $apps[9]->id],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($roleAppsMapping as $roleName => $appIds) {
|
||||||
|
$role = Role::findByName($roleName, 'web');
|
||||||
|
$this->appRoleService->assign(new AssignAppsToRoleData(
|
||||||
|
roleId: $role->id,
|
||||||
|
validUpto: now()->addYears(5)->toDateTimeString(),
|
||||||
|
appIds: $appIds
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -3,6 +3,7 @@
|
|||||||
use App\Concerns\{Confirmation, HandlesOperations};
|
use App\Concerns\{Confirmation, HandlesOperations};
|
||||||
use App\Data\Role\{AssignAppsToRoleData, AssignUsersToRoleData, RoleAppData, UpdateRolePriorityData};
|
use App\Data\Role\{AssignAppsToRoleData, AssignUsersToRoleData, RoleAppData, UpdateRolePriorityData};
|
||||||
use App\Data\Ui\TableHeader;
|
use App\Data\Ui\TableHeader;
|
||||||
|
use App\Enums\Permissions\RoleAndAppsPermissionEnum;
|
||||||
use App\Livewire\Forms\AssignAppToRoleForm;
|
use App\Livewire\Forms\AssignAppToRoleForm;
|
||||||
use App\Models\Role;
|
use App\Models\Role;
|
||||||
use App\Services\{AppRoleService, RoleService};
|
use App\Services\{AppRoleService, RoleService};
|
||||||
@ -165,6 +166,8 @@ public function clearApps(): void
|
|||||||
|
|
||||||
public function assignApp(): void
|
public function assignApp(): void
|
||||||
{
|
{
|
||||||
|
$this->authorize(RoleAndAppsPermissionEnum::ConnectApps->value);
|
||||||
|
|
||||||
$this->attempt(
|
$this->attempt(
|
||||||
action: function (): void {
|
action: function (): void {
|
||||||
$this->assignForm->validate();
|
$this->assignForm->validate();
|
||||||
@ -183,6 +186,8 @@ public function assignApp(): void
|
|||||||
|
|
||||||
public function updateApp(): void
|
public function updateApp(): void
|
||||||
{
|
{
|
||||||
|
$this->authorize(RoleAndAppsPermissionEnum::ConnectApps->value);
|
||||||
|
|
||||||
$this->attempt(
|
$this->attempt(
|
||||||
action: function (): void {
|
action: function (): void {
|
||||||
$this->assignForm->validate();
|
$this->assignForm->validate();
|
||||||
@ -202,6 +207,8 @@ public function updateApp(): void
|
|||||||
|
|
||||||
public function detachApp(int $id): void
|
public function detachApp(int $id): void
|
||||||
{
|
{
|
||||||
|
$this->authorize(RoleAndAppsPermissionEnum::ConnectApps->value);
|
||||||
|
|
||||||
$this->attempt(
|
$this->attempt(
|
||||||
action: function () use ($id): void {
|
action: function () use ($id): void {
|
||||||
$this->appRoleService->detach($id);
|
$this->appRoleService->detach($id);
|
||||||
@ -319,9 +326,11 @@ class="btn-sm btn-ghost btn-circle"
|
|||||||
|
|
||||||
<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" spinner="openAddAppModal" icon="lucide.plus" class="btn-sm">
|
@can(RoleAndAppsPermissionEnum::ConnectApps->value)
|
||||||
Add App
|
<x-mary-button wire:click="openAddAppModal" spinner="openAddAppModal" icon="lucide.plus" class="btn-sm">
|
||||||
</x-mary-button>
|
Add App
|
||||||
|
</x-mary-button>
|
||||||
|
@endcan
|
||||||
</x-slot:actions>
|
</x-slot:actions>
|
||||||
|
|
||||||
@if ($this->apps->count())
|
@if ($this->apps->count())
|
||||||
@ -339,18 +348,20 @@ class="btn-sm btn-ghost btn-circle"
|
|||||||
|
|
||||||
@scope('actions', $row)
|
@scope('actions', $row)
|
||||||
<div class="flex gap-1">
|
<div class="flex gap-1">
|
||||||
<x-mary-button
|
@can(RoleAndAppsPermissionEnum::ConnectApps->value)
|
||||||
icon="lucide.square-pen"
|
<x-mary-button
|
||||||
wire:click="openEditAppModal({{ $row['id'] }})"
|
icon="lucide.square-pen"
|
||||||
spinner="openEditAppModal({{ $row['id'] }})"
|
wire:click="openEditAppModal({{ $row['id'] }})"
|
||||||
class="btn-sm btn-soft btn-circle"
|
spinner="openEditAppModal({{ $row['id'] }})"
|
||||||
/>
|
class="btn-sm btn-soft btn-circle"
|
||||||
<x-mary-button
|
/>
|
||||||
icon="lucide.trash"
|
<x-mary-button
|
||||||
wire:click="requireConfirmation('detachApp', {{ $row['id'] }})"
|
icon="lucide.trash"
|
||||||
spinner="requireConfirmation('detachApp', {{ $row['id'] }})"
|
wire:click="requireConfirmation('detachApp', {{ $row['id'] }})"
|
||||||
class="btn-sm btn-error btn-soft btn-circle"
|
spinner="requireConfirmation('detachApp', {{ $row['id'] }})"
|
||||||
/>
|
class="btn-sm btn-error btn-soft btn-circle"
|
||||||
|
/>
|
||||||
|
@endcan
|
||||||
</div>
|
</div>
|
||||||
@endscope
|
@endscope
|
||||||
</x-mary-table>
|
</x-mary-table>
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user