feat(admin): implement CRUD for administrators with role management and tenant restrictions
This commit is contained in:
99
app/Domains/Administrator/Services/AdministratorService.php
Normal file
99
app/Domains/Administrator/Services/AdministratorService.php
Normal file
@@ -0,0 +1,99 @@
|
||||
<?php
|
||||
|
||||
namespace App\Domains\Administrator\Services;
|
||||
|
||||
use App\Domains\Auth\Models\ResetPasswordAttempt;
|
||||
use App\Domains\Auth\Models\User;
|
||||
use App\Domains\Auth\Services\ResetPasswordAttemptService;
|
||||
use App\Domains\Authorization\Enums\RoleCode;
|
||||
use App\Domains\Tenant\Models\Tenant;
|
||||
use Illuminate\Database\Eloquent\Builder;
|
||||
use Illuminate\Database\Eloquent\Collection;
|
||||
use Illuminate\Support\Arr;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
use Illuminate\Support\Str;
|
||||
use Illuminate\Validation\ValidationException;
|
||||
|
||||
class AdministratorService
|
||||
{
|
||||
public function __construct(private readonly ResetPasswordAttemptService $resetPasswordAttemptService) {}
|
||||
|
||||
/** @return Collection<int, User> */
|
||||
public function list(Tenant $tenant, ?string $search = null): Collection
|
||||
{
|
||||
return $this->query($tenant)->with('role')
|
||||
->when($search, fn (Builder $query, string $search) => $query->where(function (Builder $query) use ($search): void {
|
||||
$query->where('nombre_apellido', 'like', "%{$search}%")
|
||||
->orWhere('dni', 'like', "%{$search}%")
|
||||
->orWhere('email', 'like', "%{$search}%");
|
||||
}))
|
||||
->orderBy('nombre_apellido')->get();
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $data */
|
||||
public function create(Tenant $tenant, array $data): User
|
||||
{
|
||||
return DB::transaction(function () use ($tenant, $data): User {
|
||||
$administrator = User::query()->create([
|
||||
...$this->attributes($data),
|
||||
'password' => Str::random(64),
|
||||
'rol_codigo' => RoleCode::AdminApp->value,
|
||||
'tenant_codigo' => $tenant->codigo,
|
||||
]);
|
||||
$this->resetPasswordAttemptService->createForAdminAppEmail(
|
||||
$administrator->email,
|
||||
ResetPasswordAttempt::REASON_ADMINISTRATOR_CREATED,
|
||||
);
|
||||
|
||||
return $administrator->load('role');
|
||||
});
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $data */
|
||||
public function update(Tenant $tenant, int $administratorId, array $data): User
|
||||
{
|
||||
return DB::transaction(function () use ($tenant, $administratorId, $data): User {
|
||||
$administrator = $this->query($tenant)->lockForUpdate()->findOrFail($administratorId);
|
||||
$administrator->update($this->attributes($data));
|
||||
|
||||
return $administrator->load('role');
|
||||
});
|
||||
}
|
||||
|
||||
public function delete(Tenant $tenant, int $administratorId, User $actor): void
|
||||
{
|
||||
DB::transaction(function () use ($tenant, $administratorId, $actor): void {
|
||||
// Serialize deletions for this tenant, including requests already authenticated
|
||||
// when another administrator removes their account.
|
||||
Tenant::query()->whereKey($tenant->getKey())->lockForUpdate()->firstOrFail();
|
||||
$administrator = $this->query($tenant)->lockForUpdate()->findOrFail($administratorId);
|
||||
if ($administrator->is($actor)) {
|
||||
throw ValidationException::withMessages(['administrator' => 'No podés eliminar tu propio usuario.']);
|
||||
}
|
||||
$activeAdministrators = $this->query($tenant)->lockForUpdate()->get();
|
||||
if ($activeAdministrators->count() <= 1) {
|
||||
throw ValidationException::withMessages(['administrator' => 'El tenant debe conservar al menos un administrador.']);
|
||||
}
|
||||
abort_unless($activeAdministrators->contains('id', $actor->id), 403);
|
||||
$administrator->tokens()->delete();
|
||||
$administrator->delete();
|
||||
});
|
||||
}
|
||||
|
||||
private function query(Tenant $tenant): Builder
|
||||
{
|
||||
return User::query()->where('tenant_codigo', $tenant->codigo)
|
||||
->where('rol_codigo', RoleCode::AdminApp->value);
|
||||
}
|
||||
|
||||
/** @param array<string, mixed> $data
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function attributes(array $data): array
|
||||
{
|
||||
return [
|
||||
...Arr::only($data, ['nombre_apellido', 'dni']),
|
||||
'email' => mb_strtolower(trim((string) $data['email'])),
|
||||
];
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user