Files
2026-04-17 13:55:14 -03:00

221 lines
6.7 KiB
PHP

<?php
declare(strict_types=1);
namespace App\Models\List;
use App\Enums\Http\Api\Filter\ComparisonOperator;
use App\Enums\Models\List\ExternalProfileSite;
use App\Enums\Models\List\ExternalProfileVisibility;
use App\Events\List\ExternalProfile\ExternalProfileCreated;
use App\Events\List\ExternalProfile\ExternalProfileDeleted;
use App\Events\List\ExternalProfile\ExternalProfileUpdated;
use App\Jobs\List\SyncExternalProfileJob;
use App\Models\Auth\User;
use App\Models\BaseModel;
use App\Models\List\External\ExternalEntry;
use App\Models\List\External\ExternalToken;
use App\Scout\Elasticsearch\Models\List\ExternalProfileElasticModel;
use App\Scout\Typesense\Models\List\ExternalProfileTypesenseModel;
use Database\Factories\List\ExternalProfileFactory;
use Elastic\ScoutDriverPlus\Searchable;
use Illuminate\Database\Eloquent\Attributes\Table;
use Illuminate\Database\Eloquent\Builder;
use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Prunable;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\HasMany;
use Illuminate\Database\Eloquent\Relations\HasOne;
use Illuminate\Support\Carbon;
use Illuminate\Support\Collection;
use Illuminate\Support\Facades\Config;
use Illuminate\Support\Facades\Date;
use Illuminate\Support\Str;
use Illuminate\Support\Uri;
use RuntimeException;
/**
* @property int $profile_id
* @property Collection<int, ExternalEntry> $externalentries
* @property ExternalToken|null $externaltoken
* @property int|null $external_user_id
* @property string $name
* @property ExternalProfileSite $site
* @property Carbon|null $synced_at
* @property int|null $user_id
* @property User|null $user
* @property ExternalProfileVisibility $visibility
*
* @method static ExternalProfileFactory factory(...$parameters)
*/
#[Table(ExternalProfile::TABLE, ExternalProfile::ATTRIBUTE_ID)]
class ExternalProfile extends BaseModel
{
use HasFactory;
use Prunable;
use Searchable;
final public const string TABLE = 'external_profiles';
final public const string ATTRIBUTE_ID = 'profile_id';
final public const string ATTRIBUTE_EXTERNAL_USER_ID = 'external_user_id';
final public const string ATTRIBUTE_NAME = 'name';
final public const string ATTRIBUTE_SITE = 'site';
final public const string ATTRIBUTE_VISIBILITY = 'visibility';
final public const string ATTRIBUTE_USER = 'user_id';
final public const string ATTRIBUTE_SYNCED_AT = 'synced_at';
final public const string RELATION_ANIMES = 'externalentries.anime';
final public const string RELATION_EXTERNAL_ENTRIES = 'externalentries';
final public const string RELATION_EXTERNAL_TOKEN = 'externaltoken';
final public const string RELATION_USER = 'user';
/**
* The event map for the model.
*
* Allows for object-based events for native Eloquent events.
*
* @var array<string, class-string>
*/
protected $dispatchesEvents = [
'created' => ExternalProfileCreated::class,
'deleted' => ExternalProfileDeleted::class,
'updated' => ExternalProfileUpdated::class,
];
/**
* The attributes that are mass assignable.
*
* @var list<string>
*/
protected $fillable = [
ExternalProfile::ATTRIBUTE_EXTERNAL_USER_ID,
ExternalProfile::ATTRIBUTE_NAME,
ExternalProfile::ATTRIBUTE_SITE,
ExternalProfile::ATTRIBUTE_SYNCED_AT,
ExternalProfile::ATTRIBUTE_VISIBILITY,
ExternalProfile::ATTRIBUTE_USER,
];
/**
* Get the attributes that should be cast.
*
* @return array<string, string>
*/
protected function casts(): array
{
return [
ExternalProfile::ATTRIBUTE_EXTERNAL_USER_ID => 'int',
ExternalProfile::ATTRIBUTE_NAME => 'string',
ExternalProfile::ATTRIBUTE_SITE => ExternalProfileSite::class,
ExternalProfile::ATTRIBUTE_SYNCED_AT => 'datetime',
ExternalProfile::ATTRIBUTE_VISIBILITY => ExternalProfileVisibility::class,
ExternalProfile::ATTRIBUTE_USER => 'int',
];
}
public function getName(): string
{
return $this->name;
}
public function getSubtitle(): string
{
return $this->user === null ? $this->getName() : $this->user->getName();
}
/**
* Get the indexable data array for the model.
*/
public function toSearchableArray(): array
{
return match ($driver = Config::get('scout.driver')) {
'collection',
'elastic' => ExternalProfileElasticModel::toSearchableArray($this),
'typesense' => ExternalProfileTypesenseModel::toSearchableArray($this),
default => throw new RuntimeException("Unsupported {$driver} search driver configured."),
};
}
/**
* Get the index name for the model when searching.
*/
public function searchableAs(): string
{
return 'profiles';
}
/**
* Determine if the model should be searchable.
*/
public function shouldBeSearchable(): bool
{
return $this->visibility === ExternalProfileVisibility::PUBLIC;
}
/**
* Check if the profile was created through username case.
*/
public function isClaimed(): bool
{
return (bool) $this->user_id;
}
public function canBeSynced(): bool
{
return ! $this->synced_at || $this->synced_at->addHours(3)->isPast();
}
public function dispatchSyncJob(): void
{
SyncExternalProfileJob::dispatch($this);
}
/**
* Format: https://animethemes.moe/external/{mal|anilist}/{profile_name}.
*/
public function getClientUrl(): Uri
{
return Uri::of(Config::get('wiki.external_profile'))
->withPath(Str::lower($this->site->name).'/'.$this->getName());
}
/**
* @return HasMany<ExternalEntry, $this>
*/
public function externalentries(): HasMany
{
return $this->hasMany(ExternalEntry::class, ExternalEntry::ATTRIBUTE_PROFILE);
}
/**
* @return BelongsTo<User, $this>
*/
public function user(): BelongsTo
{
return $this->belongsTo(User::class, ExternalProfile::ATTRIBUTE_USER);
}
/**
* @return HasOne<ExternalToken, $this>
*/
public function externaltoken(): HasOne
{
return $this->hasOne(ExternalToken::class, ExternalToken::ATTRIBUTE_PROFILE);
}
/**
* Get the prunable model query.
*/
public function prunable(): Builder
{
return static::query()
->whereDoesntHave(ExternalProfile::RELATION_USER)
->where(
BaseModel::ATTRIBUTE_CREATED_AT,
ComparisonOperator::LTE->value,
Date::now()->subWeek()
);
}
}