101 خطوط
3.7 KiB
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;
|
|
});
|
|
}
|
|
}
|