New_Micro_Learning/backend/app/Modules/Identity/Application/InvitationService.php

101 خطوط
3.7 KiB
PHP

<?php
namespace App\Modules\Identity\Application;
use App\Models\User;
use App\Modules\Identity\Domain\Enums\AccountStatus;
use App\Modules\Identity\Domain\Enums\UserRole;
use App\Modules\Identity\Domain\UserInvitation;
use App\Modules\Identity\Notifications\UserInvited;
use App\Modules\Organizations\Domain\Organization;
use App\Modules\Subscriptions\Application\SeatQuota;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Notification;
use Illuminate\Support\Str;
use Illuminate\Validation\ValidationException;
final readonly class InvitationService
{
public function __construct(private SeatQuota $seatQuota) {}
public function invite(Organization $organization, User $actor, string $email, UserRole $role, array $profile = []): UserInvitation
{
$email = mb_strtolower(trim($email));
$existing = User::query()->where('email', $email)->first();
if ($existing) {
throw ValidationException::withMessages(['email' => ['A user with this email already exists.']]);
}
$previous = UserInvitation::query()
->where('organization_id', $organization->getKey())
->where('email', $email)
->first();
if (! $previous || ! $previous->isUsable()) {
$this->seatQuota->assertAvailable($organization->getKey());
}
$token = Str::random(64);
$invitation = UserInvitation::query()->updateOrCreate(
['organization_id' => $organization->getKey(), 'email' => $email],
[
'role' => $role,
'profile' => $profile,
'token_hash' => hash('sha256', $token),
'expires_at' => now()->addHours(72),
'invited_by' => $actor->getKey(),
'accepted_by' => null,
'accepted_at' => null,
'revoked_at' => null,
],
);
Notification::route('mail', $email)->notify(new UserInvited($organization->name, $token));
return $invitation;
}
public function accept(string $token, string $name, string $password): User
{
return DB::transaction(function () use ($token, $name, $password) {
$invitation = UserInvitation::query()
->where('token_hash', hash('sha256', $token))
->lockForUpdate()
->first();
if (! $invitation || ! $invitation->isUsable()) {
throw ValidationException::withMessages(['token' => ['The invitation is invalid or expired.']]);
}
$profile = $invitation->profile ?? [];
$user = User::query()->create([
'organization_id' => $invitation->organization_id,
'name' => trim(implode(' ', array_filter([$profile['firstName'] ?? null, $profile['lastName'] ?? null]))) ?: $name,
'first_name' => $profile['firstName'] ?? null,
'last_name' => $profile['lastName'] ?? null,
'department' => $profile['department'] ?? null,
'job_level' => $profile['jobLevel'] ?? null,
'direct_manager_id' => $profile['directManagerId'] ?? null,
'email' => $invitation->email,
'email_verified_at' => now(),
'password' => Hash::make($password),
'role' => $invitation->role,
'status' => AccountStatus::Active,
'locale' => 'fa',
'timezone' => 'Asia/Tehran',
]);
if (($profile['teamIds'] ?? []) !== []) {
$user->teams()->sync($profile['teamIds']);
}
$invitation->update(['accepted_by' => $user->getKey(), 'accepted_at' => now()]);
return $user;
});
}
}