*/ use HasFactory, Notifiable; use HasUlids; // generates ULIDs automatically protected $keyType = 'string'; public $incrementing = false; /** * The attributes that are mass assignable. * * @var list */ protected $fillable = [ 'firstname', 'lastname', 'displayname', 'name', 'email', 'timezone', 'phone', 'locale', ]; /** * The attributes that should be hidden for serialization. * * @var list */ protected $hidden = [ 'password', 'remember_token', ]; /** * Get the attributes that should be cast. * * @return array */ protected function casts(): array { return [ 'email_verified_at' => 'datetime', 'password' => 'hashed', ]; } /** * Expose a Breeze-compatible "name" attribute without a physical column. * Preference: displayname (explicit override), then first + last, then email. */ public function getNameAttribute(): string { $displayname = is_string($this->displayname) ? trim($this->displayname) : ''; if ($displayname !== '') { return $displayname; } $first = is_string($this->firstname) ? trim($this->firstname) : ''; $last = is_string($this->lastname) ? trim($this->lastname) : ''; $full = trim($first . ' ' . $last); if ($full !== '') { return $full; } return (string) ($this->email ?? ''); } /** * Map "name" writes to first/last names, keeping displayname optional. */ public function setNameAttribute(?string $value): void { $incoming = trim((string) $value); $currentFirst = is_string($this->attributes['firstname'] ?? null) ? trim((string) $this->attributes['firstname']) : ''; $currentLast = is_string($this->attributes['lastname'] ?? null) ? trim((string) $this->attributes['lastname']) : ''; $currentGenerated = trim($currentFirst . ' ' . $currentLast); if ($incoming === '') { $this->attributes['firstname'] = null; $this->attributes['lastname'] = null; return; } $parts = preg_split('/\s+/', $incoming, 2); $this->attributes['firstname'] = $parts[0] ?? null; $this->attributes['lastname'] = $parts[1] ?? null; $displayname = is_string($this->attributes['displayname'] ?? null) ? trim((string) $this->attributes['displayname']) : ''; if ($displayname !== '' && $displayname === $currentGenerated) { $this->attributes['displayname'] = $incoming; } } /** * user can own many calendars */ public function calendars(): HasMany { return $this->hasMany(Calendar::class); } /** * get the current user's principal uri */ public function getPrincipalUriAttribute(): string { return 'principals/' . $this->email; } /** * get all user addresses */ public function addresses() { return $this->hasMany(\App\Models\UserAddress::class, 'user_id', 'id'); } /** * get the user's billing address */ public function billingAddress() { return $this->addresses() ->where('kind', 'billing') ->where('is_primary', 1) ->first(); } /** * user can have many settings */ public function userSettings(): HasMany { return $this->hasMany(UserSetting::class, 'user_id'); } /** * get a user setting by key */ public function getSetting(string $key, mixed $default = null): mixed { // avoid repeated queries if the relationship is already eager loaded if ($this->relationLoaded('userSettings')) { $match = $this->userSettings->firstWhere('key', $key); return $match?->value ?? $default; } $value = $this->userSettings() ->where('key', $key) ->value('value'); return $value ?? $default; } /** * set a user setting by key */ public function setSetting(string $key, mixed $value): void { // store everything as a string in the value column $stringValue = is_null($value) ? null : (string) $value; $this->userSettings()->updateOrCreate( ['key' => $key], ['value' => $stringValue], ); // keep in-memory relation in sync if it was loaded if ($this->relationLoaded('userSettings')) { $existing = $this->userSettings->firstWhere('key', $key); if ($existing) { $existing->value = $stringValue; } else { $this->userSettings->push(new UserSetting([ 'user_id' => $this->getKey(), 'key' => $key, 'value' => $stringValue, ])); } } } /** * convenience: set many settings at once */ public function setSettings(array $pairs): void { foreach ($pairs as $key => $value) { $this->setSetting($key, $value); } } }