Initial commit: Pireon Admin — headless admin panel and public API
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>
This commit is contained in:
136
app/Models/Repository.php
Normal file
136
app/Models/Repository.php
Normal file
@@ -0,0 +1,136 @@
|
||||
<?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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user