Laravel 13 + Filament 5 admin panel and REST API for a non-profit developer collective, backed by the Pireon OAuth identity server. The public site is a separate, server-rendered application consuming /api/public/v1. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
137 lines
3.9 KiB
PHP
137 lines
3.9 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Models;
|
|
|
|
use App\Enums\GitProvider;
|
|
use Database\Factories\RepositoryFactory;
|
|
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
|
use Illuminate\Database\Eloquent\Builder;
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
use Illuminate\Database\Eloquent\Relations\HasMany;
|
|
|
|
/**
|
|
* A git repository belonging to the collective.
|
|
*
|
|
* Only `provider`, `full_name`, `project_id` and the two visibility flags are
|
|
* entered by hand. Everything else is a cache of what the provider reported,
|
|
* refreshed by App\Jobs\SyncRepository — which is the whole point: the site
|
|
* shows real activity without anyone updating it.
|
|
*/
|
|
#[Fillable([
|
|
'project_id', 'provider', 'full_name', 'is_primary', 'show_publicly',
|
|
])]
|
|
class Repository extends Model
|
|
{
|
|
/** @use HasFactory<RepositoryFactory> */
|
|
use HasFactory;
|
|
|
|
/**
|
|
* Columns owned by the sync process rather than by a human. Kept out of
|
|
* the fillable list above so a stray form submission cannot fake stats.
|
|
*
|
|
* @var array<int, string>
|
|
*/
|
|
public const SYNCED_COLUMNS = [
|
|
'html_url', 'description', 'homepage', 'default_branch',
|
|
'primary_language', 'license', 'topics', 'languages', 'stars',
|
|
'forks', 'watchers', 'open_issues_count', 'contributors_count',
|
|
'commits_last_year', 'is_archived', 'is_fork', 'pushed_at',
|
|
'repo_created_at',
|
|
];
|
|
|
|
protected function casts(): array
|
|
{
|
|
return [
|
|
'provider' => GitProvider::class,
|
|
'topics' => 'array',
|
|
'languages' => 'array',
|
|
'is_primary' => 'boolean',
|
|
'show_publicly' => 'boolean',
|
|
'is_archived' => 'boolean',
|
|
'is_fork' => 'boolean',
|
|
'pushed_at' => 'datetime',
|
|
'repo_created_at' => 'datetime',
|
|
'last_synced_at' => 'datetime',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* @return BelongsTo<Project, $this>
|
|
*/
|
|
public function project(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Project::class);
|
|
}
|
|
|
|
/**
|
|
* @return HasMany<RepositoryIssue, $this>
|
|
*/
|
|
public function issues(): HasMany
|
|
{
|
|
return $this->hasMany(RepositoryIssue::class);
|
|
}
|
|
|
|
/**
|
|
* Issues suitable for someone who has never contributed before.
|
|
*
|
|
* @return HasMany<RepositoryIssue, $this>
|
|
*/
|
|
public function goodFirstIssues(): HasMany
|
|
{
|
|
return $this->issues()->where('is_good_first_issue', true);
|
|
}
|
|
|
|
public function owner(): string
|
|
{
|
|
return str_contains($this->full_name, '/')
|
|
? explode('/', $this->full_name)[0]
|
|
: $this->full_name;
|
|
}
|
|
|
|
public function name(): string
|
|
{
|
|
return str_contains($this->full_name, '/')
|
|
? explode('/', $this->full_name, 2)[1]
|
|
: $this->full_name;
|
|
}
|
|
|
|
/**
|
|
* A repository is considered dormant when nothing has been pushed for a
|
|
* while. The public site uses this to avoid advertising dead work as live.
|
|
*/
|
|
public function isDormant(int $days = 180): bool
|
|
{
|
|
return $this->pushed_at === null || $this->pushed_at->lt(now()->subDays($days));
|
|
}
|
|
|
|
public function hasSyncedSuccessfully(): bool
|
|
{
|
|
return $this->last_synced_at !== null && blank($this->sync_error);
|
|
}
|
|
|
|
/**
|
|
* @param Builder<$this> $query
|
|
* @return Builder<$this>
|
|
*/
|
|
public function scopePubliclyVisible(Builder $query): Builder
|
|
{
|
|
return $query->where('show_publicly', true);
|
|
}
|
|
|
|
/**
|
|
* Repositories whose sync keeps failing, so the panel can flag them rather
|
|
* than silently showing stale numbers forever.
|
|
*
|
|
* @param Builder<$this> $query
|
|
* @return Builder<$this>
|
|
*/
|
|
public function scopeFailing(Builder $query): Builder
|
|
{
|
|
return $query->where('sync_failures', '>=', 3);
|
|
}
|
|
}
|