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 Update = 'roles-and-apps:update';
|
||||
case Delete = 'roles-and-apps:delete';
|
||||
case ConnectApps = 'roles-and-apps:connect-apps';
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@
|
||||
namespace App\Models;
|
||||
|
||||
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\Relations\HasOne;
|
||||
use Spatie\Activitylog\Models\Concerns\LogsActivity;
|
||||
@ -24,7 +25,7 @@
|
||||
])]
|
||||
class ConnectedApp extends Model
|
||||
{
|
||||
use LogsActivity, SoftDeletes;
|
||||
use HasFactory, LogsActivity, SoftDeletes;
|
||||
|
||||
/**
|
||||
* @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(DefaultRoleWithPermissionSeeder::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\Data\Role\{AssignAppsToRoleData, AssignUsersToRoleData, RoleAppData, UpdateRolePriorityData};
|
||||
use App\Data\Ui\TableHeader;
|
||||
use App\Enums\Permissions\RoleAndAppsPermissionEnum;
|
||||
use App\Livewire\Forms\AssignAppToRoleForm;
|
||||
use App\Models\Role;
|
||||
use App\Services\{AppRoleService, RoleService};
|
||||
@ -165,6 +166,8 @@ public function clearApps(): void
|
||||
|
||||
public function assignApp(): void
|
||||
{
|
||||
$this->authorize(RoleAndAppsPermissionEnum::ConnectApps->value);
|
||||
|
||||
$this->attempt(
|
||||
action: function (): void {
|
||||
$this->assignForm->validate();
|
||||
@ -183,6 +186,8 @@ public function assignApp(): void
|
||||
|
||||
public function updateApp(): void
|
||||
{
|
||||
$this->authorize(RoleAndAppsPermissionEnum::ConnectApps->value);
|
||||
|
||||
$this->attempt(
|
||||
action: function (): void {
|
||||
$this->assignForm->validate();
|
||||
@ -202,6 +207,8 @@ public function updateApp(): void
|
||||
|
||||
public function detachApp(int $id): void
|
||||
{
|
||||
$this->authorize(RoleAndAppsPermissionEnum::ConnectApps->value);
|
||||
|
||||
$this->attempt(
|
||||
action: function () use ($id): void {
|
||||
$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-slot:actions>
|
||||
<x-mary-button wire:click="openAddAppModal" spinner="openAddAppModal" icon="lucide.plus" class="btn-sm">
|
||||
Add App
|
||||
</x-mary-button>
|
||||
@can(RoleAndAppsPermissionEnum::ConnectApps->value)
|
||||
<x-mary-button wire:click="openAddAppModal" spinner="openAddAppModal" icon="lucide.plus" class="btn-sm">
|
||||
Add App
|
||||
</x-mary-button>
|
||||
@endcan
|
||||
</x-slot:actions>
|
||||
|
||||
@if ($this->apps->count())
|
||||
@ -339,18 +348,20 @@ class="btn-sm btn-ghost btn-circle"
|
||||
|
||||
@scope('actions', $row)
|
||||
<div class="flex gap-1">
|
||||
<x-mary-button
|
||||
icon="lucide.square-pen"
|
||||
wire:click="openEditAppModal({{ $row['id'] }})"
|
||||
spinner="openEditAppModal({{ $row['id'] }})"
|
||||
class="btn-sm btn-soft btn-circle"
|
||||
/>
|
||||
<x-mary-button
|
||||
icon="lucide.trash"
|
||||
wire:click="requireConfirmation('detachApp', {{ $row['id'] }})"
|
||||
spinner="requireConfirmation('detachApp', {{ $row['id'] }})"
|
||||
class="btn-sm btn-error btn-soft btn-circle"
|
||||
/>
|
||||
@can(RoleAndAppsPermissionEnum::ConnectApps->value)
|
||||
<x-mary-button
|
||||
icon="lucide.square-pen"
|
||||
wire:click="openEditAppModal({{ $row['id'] }})"
|
||||
spinner="openEditAppModal({{ $row['id'] }})"
|
||||
class="btn-sm btn-soft btn-circle"
|
||||
/>
|
||||
<x-mary-button
|
||||
icon="lucide.trash"
|
||||
wire:click="requireConfirmation('detachApp', {{ $row['id'] }})"
|
||||
spinner="requireConfirmation('detachApp', {{ $row['id'] }})"
|
||||
class="btn-sm btn-error btn-soft btn-circle"
|
||||
/>
|
||||
@endcan
|
||||
</div>
|
||||
@endscope
|
||||
</x-mary-table>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user