Replaces all Jetstream model trait functions and relations

This commit is contained in:
Constantin Graf
2026-05-29 12:40:06 +02:00
committed by Constantin Graf
parent 5eb1a6a006
commit 734aab6353
26 changed files with 194 additions and 88 deletions

View File

@@ -43,7 +43,7 @@ use OwenIt\Auditing\Contracts\Auditable as AuditableContract;
* @property Carbon|null $updated_at
* @property Collection<int, User> $users
* @property Collection<int, User> $realUsers
* @property-read Collection<int, OrganizationInvitation> $teamInvitations
* @property-read Collection<int, OrganizationInvitation> $organizationInvitations
* @property Member $membership
* @property NumberFormat $number_format
* @property CurrencyFormat $currency_format
@@ -51,7 +51,6 @@ use OwenIt\Auditing\Contracts\Auditable as AuditableContract;
* @property IntervalFormat $interval_format
* @property TimeFormat $time_format
*
* @method HasMany<OrganizationInvitation, $this> teamInvitations()
* @method static OrganizationFactory factory()
*/
class Organization extends JetstreamTeam implements AuditableContract
@@ -111,23 +110,6 @@ class Organization extends JetstreamTeam implements AuditableContract
protected $attributes = [
];
/**
* Get all the non-placeholder users of the organization including its owner.
*
* @return Collection<int, User>
*/
public function allRealUsers(): Collection
{
return $this->realUsers->merge([$this->owner]);
}
public function hasRealUserWithEmail(string $email): bool
{
return $this->allRealUsers()->contains(function (User $user) use ($email): bool {
return $user->email === $email;
});
}
/**
* Get all the users that belong to the team.
*
@@ -172,6 +154,14 @@ class Organization extends JetstreamTeam implements AuditableContract
->where('is_placeholder', false);
}
/**
* @return HasMany<OrganizationInvitation, $this>
*/
public function organizationInvitations(): HasMany
{
return $this->hasMany(OrganizationInvitation::class, 'organization_id');
}
/**
* This method prevents an unhandled exception when the ID is not a UUID.
* Normally this can be fixed with a route pattern, but Jetstream does not use route model binding.

View File

@@ -4,6 +4,7 @@ declare(strict_types=1);
namespace App\Models;
use App\Enums\Role;
use App\Enums\Weekday;
use App\Models\Concerns\CustomAuditable;
use App\Models\Concerns\HasUuids;
@@ -52,6 +53,7 @@ use OwenIt\Auditing\Contracts\Auditable as AuditableContract;
* @property Carbon|null $updated_at
* @property string|null $current_team_id
* @property Collection<int, Organization> $organizations
* @property Collection<int, Organization> $ownedOrganizations
* @property Collection<int, TimeEntry> $timeEntries
* @property Member $membership
*
@@ -131,16 +133,39 @@ class User extends Authenticatable implements AuditableContract, FilamentUser, M
{
return Attribute::get(function (): string {
return $this->profile_photo_path
? Storage::disk($this->profilePhotoDisk())->url($this->profile_photo_path)
? Storage::disk(config('filesystems.public'))->url($this->profile_photo_path)
: $this->defaultProfilePhotoUrl();
});
}
/**
* Get the default profile photo URL if no profile photo has been uploaded.
*/
protected function defaultProfilePhotoUrl(): string
{
$name = trim(collect(explode(' ', $this->name))->map(function ($segment) {
return mb_substr($segment, 0, 1);
})->join(' '));
return 'https://ui-avatars.com/api/?name='.urlencode($name).'&color=7F9CF5&background=EBF4FF';
}
public function canAccessPanel(Panel $panel): bool
{
return in_array($this->email, config('auth.super_admins', []), true) && $this->hasVerifiedEmail();
}
public function isMemberOfOrganization(Organization $organization): bool
{
if ($this->relationLoaded('organizations')) {
return $this->organizations->contains(function (Organization $o) use ($organization): bool {
return $o->getKey() === $organization->getKey();
});
}
return $this->organizations()->whereKey($organization->getKey())->exists();
}
public function canBeImpersonated(): bool
{
return $this->is_placeholder === false;
@@ -161,6 +186,14 @@ class User extends Authenticatable implements AuditableContract, FilamentUser, M
->as('membership');
}
/**
* @return BelongsToMany<Organization, $this, Pivot, 'membership'>
*/
public function ownedOrganizations(): BelongsToMany
{
return $this->organizations()->wherePivot('role', Role::Owner->value);
}
/**
* @return HasMany<TimeEntry, $this>
*/
@@ -215,12 +248,8 @@ class User extends Authenticatable implements AuditableContract, FilamentUser, M
*/
public function scopeBelongsToOrganization(Builder $builder, Organization $organization): Builder
{
return $builder->where(function (Builder $builder) use ($organization): Builder {
return $builder->whereHas('organizations', function (Builder $query) use ($organization): void {
$query->whereKey($organization->getKey());
})->orWhereHas('ownedTeams', function (Builder $query) use ($organization): void {
$query->whereKey($organization->getKey());
});
return $builder->whereHas('organizations', function (Builder $query) use ($organization): void {
$query->whereKey($organization->getKey());
});
}
}