CluPilotCloud/app/Models/Customer.php

83 lines
2.4 KiB
PHP

<?php
namespace App\Models;
use App\Models\Concerns\HasUuid;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\UniqueConstraintViolationException;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Str;
use RuntimeException;
class Customer extends Model
{
/** @use HasFactory<\Database\Factories\CustomerFactory> */
use HasFactory, HasUuid;
protected $fillable = ['user_id', 'name', 'email', 'locale', 'stripe_customer_id', 'status'];
public function user(): BelongsTo
{
return $this->belongsTo(User::class);
}
public function orders(): HasMany
{
return $this->hasMany(Order::class);
}
public function instances(): HasMany
{
return $this->hasMany(Instance::class);
}
/**
* Find or create the portal login account for this customer and link it.
* Race-safe against the unique email index, and never links an operator
* account — that would carry admin authorization into an impersonated
* "customer" session.
*/
public function ensureUser(): User
{
if ($this->user) {
return $this->user;
}
if (($existing = User::query()->where('email', $this->email)->first()) !== null) {
$this->assertNotAdmin($existing);
$this->update(['user_id' => $existing->id]);
return $existing;
}
try {
$user = User::query()->create([
'email' => $this->email,
'name' => $this->name,
'password' => Hash::make(Str::random(40)),
'is_admin' => false,
]);
} catch (UniqueConstraintViolationException) {
// Concurrent first-time creation — re-fetch and link the winner.
$user = User::query()->where('email', $this->email)->firstOrFail();
$this->assertNotAdmin($user);
}
$this->update(['user_id' => $user->id]);
return $user->refresh();
}
private function assertNotAdmin(User $user): void
{
if ($user->is_admin) {
throw new RuntimeException(
"Refusing to link admin account {$user->email} as a portal login for customer {$this->id}.",
);
}
}
}