mirror of
https://github.com/tiennm99/coolify.git
synced 2026-10-14 02:15:51 +00:00
chore(v5): archive V5 implementation and remove runtime integration
Move V5 source, migrations, UI, scripts, and tests into documentation, then remove V5 routes, models, jobs, configuration, dependencies, and application hooks.
This commit is contained in:
1 parent
1440d35750
commit
76030a30d4
237 files changed
+288
-7079
No files matched your search
@@ -2,9 +2,6 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\V5\Application as V5Application;
|
||||
use App\Models\V5\ResourceConnection as V5ResourceConnection;
|
||||
use App\Support\V5\V5Feature;
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -57,11 +54,7 @@ class Environment extends BaseModel
|
||||
|
||||
public function isEmpty()
|
||||
{
|
||||
return (! V5Feature::enabled() || (
|
||||
! V5Application::query()->where('environment_id', $this->id)->exists() &&
|
||||
! V5ResourceConnection::query()->where('environment_id', $this->id)->exists()
|
||||
)) &&
|
||||
$this->applications()->count() == 0 &&
|
||||
return $this->applications()->count() == 0 &&
|
||||
$this->redis()->count() == 0 &&
|
||||
$this->postgresqls()->count() == 0 &&
|
||||
$this->mysqls()->count() == 0 &&
|
||||
@@ -83,11 +76,6 @@ class Environment extends BaseModel
|
||||
return $this->hasMany(Application::class);
|
||||
}
|
||||
|
||||
public function v5Applications()
|
||||
{
|
||||
return $this->hasMany(V5Application::class);
|
||||
}
|
||||
|
||||
public function postgresqls()
|
||||
{
|
||||
return $this->hasMany(StandalonePostgresql::class);
|
||||
|
||||
@@ -2,9 +2,6 @@
|
||||
|
||||
namespace App\Models;
|
||||
|
||||
use App\Models\V5\Application as V5Application;
|
||||
use App\Models\V5\ResourceConnection as V5ResourceConnection;
|
||||
use App\Support\V5\V5Feature;
|
||||
use App\Traits\ClearsGlobalSearchCache;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
@@ -147,11 +144,7 @@ class Project extends BaseModel
|
||||
|
||||
public function isEmpty()
|
||||
{
|
||||
return (! V5Feature::enabled() || (
|
||||
! V5Application::query()->where('project_id', $this->id)->exists() &&
|
||||
! V5ResourceConnection::query()->where('project_id', $this->id)->exists()
|
||||
)) &&
|
||||
$this->applications()->count() == 0 &&
|
||||
return $this->applications()->count() == 0 &&
|
||||
$this->redis()->count() == 0 &&
|
||||
$this->postgresqls()->count() == 0 &&
|
||||
$this->mysqls()->count() == 0 &&
|
||||
|
||||
@@ -4,12 +4,10 @@ namespace App\Models;
|
||||
|
||||
use App\Actions\User\RevokeUserTeamTokens;
|
||||
use App\Events\ServerReachabilityChanged;
|
||||
use App\Jobs\V5TeardownTeamJob;
|
||||
use App\Notifications\Channels\SendsDiscord;
|
||||
use App\Notifications\Channels\SendsEmail;
|
||||
use App\Notifications\Channels\SendsPushover;
|
||||
use App\Notifications\Channels\SendsSlack;
|
||||
use App\Support\V5\V5Feature;
|
||||
use App\Traits\HasNotificationSettings;
|
||||
use App\Traits\HasSafeStringAttribute;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
@@ -81,20 +79,6 @@ class Team extends Model implements SendsDiscord, SendsEmail, SendsPushover, Sen
|
||||
});
|
||||
|
||||
static::deleting(function (Team $team) {
|
||||
// Best-effort on-host teardown of this team's v5 resources BEFORE the
|
||||
// DB cascade removes the servers/applications/private keys. Captured
|
||||
// synchronously into a queued job so an unreachable host cannot block
|
||||
// or fail the team deletion (see V5TeardownTeamJob). Guarded so a v5
|
||||
// teardown problem never breaks v4 team deletion. This is disabled
|
||||
// with the rest of v5 outside development environments.
|
||||
if (V5Feature::enabled()) {
|
||||
try {
|
||||
V5TeardownTeamJob::dispatchForTeam($team);
|
||||
} catch (\Throwable $exception) {
|
||||
report($exception);
|
||||
}
|
||||
}
|
||||
|
||||
RevokeUserTeamTokens::forTeam($team->id);
|
||||
|
||||
foreach ($team->privateKeys as $key) {
|
||||
|
||||
@@ -1,97 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\V5;
|
||||
|
||||
use App\Enums\V5\ApplicationStatus;
|
||||
use App\Events\V5CanvasResourceUpdated;
|
||||
use App\Models\Environment;
|
||||
use App\Models\Project;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class Application extends V5Model
|
||||
{
|
||||
protected $table = 'v5_applications';
|
||||
|
||||
protected $fillable = [
|
||||
'uuid',
|
||||
'team_id',
|
||||
'project_id',
|
||||
'environment_id',
|
||||
'server_id',
|
||||
'created_by_user_id',
|
||||
'name',
|
||||
'image',
|
||||
'container_name',
|
||||
'status',
|
||||
'status_message',
|
||||
'status_observed_at',
|
||||
'runtime_container_id',
|
||||
'mesh_namespace',
|
||||
'ingress_enabled',
|
||||
'internal_port',
|
||||
'canvas_x',
|
||||
'canvas_y',
|
||||
];
|
||||
|
||||
protected $attributes = [
|
||||
'status' => ApplicationStatus::Creating->value,
|
||||
'mesh_namespace' => 'default',
|
||||
'ingress_enabled' => false,
|
||||
'canvas_x' => 0,
|
||||
'canvas_y' => 0,
|
||||
];
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::updated(function (self $application): void {
|
||||
if ($application->wasChanged(['status', 'status_message', 'runtime_container_id'])) {
|
||||
DB::afterCommit(fn () => V5CanvasResourceUpdated::dispatch($application->team_id, $application->id));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'status_observed_at' => 'datetime',
|
||||
'ingress_enabled' => 'boolean',
|
||||
'internal_port' => 'integer',
|
||||
'canvas_x' => 'integer',
|
||||
'canvas_y' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
public function team(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Team::class);
|
||||
}
|
||||
|
||||
public function server(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
|
||||
public function project(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
|
||||
public function environment(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Environment::class);
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by_user_id');
|
||||
}
|
||||
|
||||
public function domains(): HasMany
|
||||
{
|
||||
return $this->hasMany(ApplicationDomain::class);
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\V5;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ApplicationDomain extends V5Model
|
||||
{
|
||||
protected $table = 'v5_application_domains';
|
||||
|
||||
protected bool $hasUuidColumn = false;
|
||||
|
||||
protected $fillable = [
|
||||
'application_id',
|
||||
'domain',
|
||||
];
|
||||
|
||||
public function application(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Application::class);
|
||||
}
|
||||
}
|
||||
@@ -1,118 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\V5;
|
||||
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
|
||||
class Cluster extends V5Model
|
||||
{
|
||||
protected $table = 'v5_clusters';
|
||||
|
||||
/**
|
||||
* Single source of truth for cluster defaults: `$attributes` below is
|
||||
* built from these consts, and the column defaults in
|
||||
* 2026_06_16_130649_v5_create_clusters_table mirror them (kept there for
|
||||
* historical rows only — update both when changing a default).
|
||||
*/
|
||||
public const DEFAULT_WIREGUARD_INTERFACE = 'wg0';
|
||||
|
||||
public const DEFAULT_WIREGUARD_MANAGEMENT_POOL = '100.64.0.0/16';
|
||||
|
||||
public const DEFAULT_WIREGUARD_LISTEN_PORT = 51820;
|
||||
|
||||
public const DEFAULT_CONTAINER_NETWORK_POOL = '10.210.0.0/16';
|
||||
|
||||
public const DEFAULT_CONTAINER_NETWORK_PREFIX = 24;
|
||||
|
||||
public const DEFAULT_NAMESPACES = ['default'];
|
||||
|
||||
public const DEFAULT_COOLD_VERSION = 'nightly';
|
||||
|
||||
public const DEFAULT_CORROSION_VERSION = 'v1.0.0';
|
||||
|
||||
public const DEFAULT_CORROSION_GOSSIP_PORT = 8787;
|
||||
|
||||
public const DEFAULT_CORROSION_API_PORT = 8080;
|
||||
|
||||
public const DEFAULT_BUILDER_CAPACITY = 2;
|
||||
|
||||
public const DEFAULT_BUILDER_CPU_QUOTA = '200%';
|
||||
|
||||
public const DEFAULT_BUILDER_MEMORY_MAX = '2G';
|
||||
|
||||
public const DEFAULT_BUILDER_TIMEOUT_SECS = 1800;
|
||||
|
||||
protected $fillable = [
|
||||
'uuid',
|
||||
'team_id',
|
||||
'created_by_user_id',
|
||||
'name',
|
||||
'description',
|
||||
'wireguard_interface',
|
||||
'wireguard_management_pool',
|
||||
'wireguard_listen_port',
|
||||
'container_network_pool',
|
||||
'container_network_prefix',
|
||||
'namespaces',
|
||||
'default_deny_containers',
|
||||
'coold_version',
|
||||
'corrosion_version',
|
||||
'corrosion_gossip_port',
|
||||
'corrosion_api_port',
|
||||
'builder_enabled',
|
||||
'builder_capacity',
|
||||
'builder_cpu_quota',
|
||||
'builder_memory_max',
|
||||
'builder_timeout_secs',
|
||||
'last_cli_action',
|
||||
'last_cli_status',
|
||||
'last_cli_summary',
|
||||
'last_cli_ran_at',
|
||||
];
|
||||
|
||||
protected $attributes = [
|
||||
'wireguard_interface' => self::DEFAULT_WIREGUARD_INTERFACE,
|
||||
'wireguard_management_pool' => self::DEFAULT_WIREGUARD_MANAGEMENT_POOL,
|
||||
'wireguard_listen_port' => self::DEFAULT_WIREGUARD_LISTEN_PORT,
|
||||
'container_network_pool' => self::DEFAULT_CONTAINER_NETWORK_POOL,
|
||||
'container_network_prefix' => self::DEFAULT_CONTAINER_NETWORK_PREFIX,
|
||||
'default_deny_containers' => true,
|
||||
'coold_version' => self::DEFAULT_COOLD_VERSION,
|
||||
'corrosion_version' => self::DEFAULT_CORROSION_VERSION,
|
||||
'corrosion_gossip_port' => self::DEFAULT_CORROSION_GOSSIP_PORT,
|
||||
'corrosion_api_port' => self::DEFAULT_CORROSION_API_PORT,
|
||||
'builder_enabled' => true,
|
||||
'builder_capacity' => self::DEFAULT_BUILDER_CAPACITY,
|
||||
'builder_cpu_quota' => self::DEFAULT_BUILDER_CPU_QUOTA,
|
||||
'builder_memory_max' => self::DEFAULT_BUILDER_MEMORY_MAX,
|
||||
'builder_timeout_secs' => self::DEFAULT_BUILDER_TIMEOUT_SECS,
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'namespaces' => 'array',
|
||||
'default_deny_containers' => 'boolean',
|
||||
'builder_enabled' => 'boolean',
|
||||
'last_cli_ran_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function team(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Team::class);
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by_user_id');
|
||||
}
|
||||
|
||||
public function servers(): HasMany
|
||||
{
|
||||
return $this->hasMany(Server::class);
|
||||
}
|
||||
}
|
||||
@@ -1,43 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\V5;
|
||||
|
||||
use App\Models\Team;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
class ContainerStatus extends V5Model
|
||||
{
|
||||
protected $table = 'v5_container_statuses';
|
||||
|
||||
protected bool $hasUuidColumn = false;
|
||||
|
||||
protected $fillable = [
|
||||
'team_id',
|
||||
'server_id',
|
||||
'container_id',
|
||||
'container_name',
|
||||
'image',
|
||||
'status',
|
||||
'status_message',
|
||||
'status_observed_at',
|
||||
'last_seen_at',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'status_observed_at' => 'datetime',
|
||||
'last_seen_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function team(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Team::class);
|
||||
}
|
||||
|
||||
public function server(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
}
|
||||
@@ -1,64 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\V5;
|
||||
|
||||
use App\Models\Environment;
|
||||
use App\Models\Project;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\HasMany;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class ResourceConnection extends V5Model
|
||||
{
|
||||
protected $table = 'v5_resource_connections';
|
||||
|
||||
protected $fillable = [
|
||||
'uuid',
|
||||
'team_id',
|
||||
'project_id',
|
||||
'environment_id',
|
||||
'resource_one_type',
|
||||
'resource_one_id',
|
||||
'resource_two_type',
|
||||
'resource_two_id',
|
||||
'resource_pair_key',
|
||||
'created_by_user_id',
|
||||
];
|
||||
|
||||
public function team(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Team::class);
|
||||
}
|
||||
|
||||
public function project(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Project::class);
|
||||
}
|
||||
|
||||
public function environment(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Environment::class);
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by_user_id');
|
||||
}
|
||||
|
||||
public function resourceOne(): MorphTo
|
||||
{
|
||||
return $this->morphTo('resource_one');
|
||||
}
|
||||
|
||||
public function resourceTwo(): MorphTo
|
||||
{
|
||||
return $this->morphTo('resource_two');
|
||||
}
|
||||
|
||||
public function rules(): HasMany
|
||||
{
|
||||
return $this->hasMany(ResourceConnectionRule::class, 'connection_id');
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\V5;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Database\Eloquent\Relations\MorphTo;
|
||||
|
||||
class ResourceConnectionRule extends V5Model
|
||||
{
|
||||
protected $table = 'v5_resource_connection_rules';
|
||||
|
||||
protected bool $hasUuidColumn = false;
|
||||
|
||||
protected $fillable = [
|
||||
'connection_id',
|
||||
'source_resource_type',
|
||||
'source_resource_id',
|
||||
'target_resource_type',
|
||||
'target_resource_id',
|
||||
'protocol',
|
||||
'port',
|
||||
];
|
||||
|
||||
protected $attributes = [
|
||||
'protocol' => 'tcp',
|
||||
];
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'port' => 'integer',
|
||||
];
|
||||
}
|
||||
|
||||
public function connection(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(ResourceConnection::class, 'connection_id');
|
||||
}
|
||||
|
||||
public function sourceResource(): MorphTo
|
||||
{
|
||||
return $this->morphTo('source_resource');
|
||||
}
|
||||
|
||||
public function targetResource(): MorphTo
|
||||
{
|
||||
return $this->morphTo('target_resource');
|
||||
}
|
||||
}
|
||||
@@ -1,45 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\V5;
|
||||
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
|
||||
/**
|
||||
* A host-agent JWT that has been revoked (typically on server destroy/re-home).
|
||||
*
|
||||
* flux does not yet consult this list (see AgentTokenIssuer::revoke docblock);
|
||||
* it exists so the Laravel side owns the data and API needed for revocation the
|
||||
* moment flux gains a revocation check.
|
||||
*/
|
||||
class RevokedAgentToken extends V5Model
|
||||
{
|
||||
protected bool $hasUuidColumn = false;
|
||||
|
||||
protected $table = 'v5_revoked_agent_tokens';
|
||||
|
||||
protected $fillable = [
|
||||
'jti',
|
||||
'server_id',
|
||||
'revoked_at',
|
||||
'expires_at',
|
||||
];
|
||||
|
||||
/**
|
||||
* @return array<string, string>
|
||||
*/
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'revoked_at' => 'datetime',
|
||||
'expires_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return BelongsTo<Server, $this>
|
||||
*/
|
||||
public function server(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Server::class);
|
||||
}
|
||||
}
|
||||
@@ -1,204 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\V5;
|
||||
|
||||
use App\Enums\V5\IngressStatus;
|
||||
use App\Events\V5CanvasResourceUpdated;
|
||||
use App\Events\V5ClusterUpdated;
|
||||
use App\Models\PrivateKey;
|
||||
use App\Models\Team;
|
||||
use App\Models\User;
|
||||
use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
class Server extends V5Model
|
||||
{
|
||||
protected $table = 'v5_servers';
|
||||
|
||||
protected $fillable = [
|
||||
'uuid',
|
||||
'team_id',
|
||||
'cluster_id',
|
||||
'created_by_user_id',
|
||||
'private_key_id',
|
||||
'name',
|
||||
'host',
|
||||
'ssh_user',
|
||||
'ssh_port',
|
||||
'status',
|
||||
'status_observed_at',
|
||||
'ingress_type',
|
||||
'ingress_status',
|
||||
'capabilities',
|
||||
'has_coold',
|
||||
'is_ingress',
|
||||
'builder_enabled',
|
||||
'builder_capacity',
|
||||
'builder_cpu_quota',
|
||||
'node_address',
|
||||
'wireguard_listen_port_override',
|
||||
'wireguard_endpoint_override',
|
||||
'wireguard_management_ip',
|
||||
'wireguard_public_key',
|
||||
'coold_version',
|
||||
'agent_token_jti',
|
||||
'agent_token_expires_at',
|
||||
'container_subnets',
|
||||
'canvas_x',
|
||||
'canvas_y',
|
||||
'last_bootstrapped_at',
|
||||
'last_bootstrap_action',
|
||||
'last_bootstrap_status',
|
||||
'last_bootstrap_output',
|
||||
'last_bootstrap_ran_at',
|
||||
'last_status_check',
|
||||
'last_status_output',
|
||||
'last_status_checked_at',
|
||||
];
|
||||
|
||||
protected static function booted(): void
|
||||
{
|
||||
static::updated(function (self $server): void {
|
||||
if (
|
||||
! $server->wasChanged('status')
|
||||
&& ! $server->wasChanged('ingress_type')
|
||||
&& ! $server->wasChanged('ingress_status')
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ($server->wasChanged('status') && $server->cluster_id !== null) {
|
||||
DB::afterCommit(fn () => V5ClusterUpdated::dispatch($server->team_id, $server->cluster_id));
|
||||
}
|
||||
|
||||
if ($server->wasChanged('status')) {
|
||||
DB::afterCommit(fn () => V5CanvasResourceUpdated::dispatch(
|
||||
$server->team_id,
|
||||
null,
|
||||
$server->isIngress() ? $server->id : null,
|
||||
$server->id,
|
||||
));
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if ($server->isIngress()) {
|
||||
DB::afterCommit(fn () => V5CanvasResourceUpdated::dispatch($server->team_id, null, $server->id));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected function casts(): array
|
||||
{
|
||||
return [
|
||||
'has_coold' => 'boolean',
|
||||
'is_ingress' => 'boolean',
|
||||
'builder_enabled' => 'boolean',
|
||||
'container_subnets' => 'array',
|
||||
'canvas_x' => 'integer',
|
||||
'canvas_y' => 'integer',
|
||||
'status_observed_at' => 'datetime',
|
||||
'agent_token_expires_at' => 'datetime',
|
||||
'last_bootstrapped_at' => 'datetime',
|
||||
'last_bootstrap_ran_at' => 'datetime',
|
||||
'last_status_checked_at' => 'datetime',
|
||||
];
|
||||
}
|
||||
|
||||
public function fluxHostId(): string
|
||||
{
|
||||
return (string) $this->uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Virtual attribute kept for wire-format compatibility: capabilities are
|
||||
* stored as the indexed has_coold / is_ingress booleans, but reads and
|
||||
* writes of `capabilities` keep working with the historical string array.
|
||||
* Unknown capability names are dropped on write.
|
||||
*
|
||||
* The dropped `capabilities` column intentionally stays in `$fillable`:
|
||||
* call sites still mass-assign it, and this mutator maps those writes
|
||||
* onto the boolean columns.
|
||||
*/
|
||||
protected function capabilities(): Attribute
|
||||
{
|
||||
return Attribute::make(
|
||||
get: fn () => array_values(array_filter([
|
||||
$this->has_coold ? 'coold' : null,
|
||||
$this->is_ingress ? 'ingress' : null,
|
||||
])),
|
||||
set: fn (?array $capabilities) => [
|
||||
'has_coold' => in_array('coold', $capabilities ?? [], true),
|
||||
'is_ingress' => in_array('ingress', $capabilities ?? [], true),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
public function hasCapability(string $capability): bool
|
||||
{
|
||||
return match ($capability) {
|
||||
'coold' => (bool) $this->has_coold,
|
||||
'ingress' => (bool) $this->is_ingress,
|
||||
default => false,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function withCapability(string $capability): array
|
||||
{
|
||||
return collect($this->capabilities)
|
||||
->push($capability)
|
||||
->unique()
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<int, string>
|
||||
*/
|
||||
public function withoutCapability(string $capability): array
|
||||
{
|
||||
return collect($this->capabilities)
|
||||
->reject(fn (string $existingCapability) => $existingCapability === $capability)
|
||||
->values()
|
||||
->all();
|
||||
}
|
||||
|
||||
public function isIngress(): bool
|
||||
{
|
||||
return (bool) $this->is_ingress;
|
||||
}
|
||||
|
||||
public function ingressStatus(): string
|
||||
{
|
||||
return $this->ingress_status ?? IngressStatus::Unknown->value;
|
||||
}
|
||||
|
||||
public function ingressType(): string
|
||||
{
|
||||
return $this->ingress_type ?? 'caddy';
|
||||
}
|
||||
|
||||
public function cluster(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Cluster::class);
|
||||
}
|
||||
|
||||
public function team(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(Team::class);
|
||||
}
|
||||
|
||||
public function creator(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(User::class, 'created_by_user_id');
|
||||
}
|
||||
|
||||
public function privateKey(): BelongsTo
|
||||
{
|
||||
return $this->belongsTo(PrivateKey::class);
|
||||
}
|
||||
}
|
||||
@@ -1,57 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\V5;
|
||||
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
|
||||
abstract class V5Model extends Model
|
||||
{
|
||||
/**
|
||||
* Whether the model's table has a `uuid` column. Models without one (set
|
||||
* this to false there) skip public-id generation and route on the primary
|
||||
* key instead.
|
||||
*/
|
||||
protected bool $hasUuidColumn = true;
|
||||
|
||||
public function getRouteKeyName(): string
|
||||
{
|
||||
return $this->hasUuidColumn ? 'uuid' : $this->getKeyName();
|
||||
}
|
||||
|
||||
protected static function boot(): void
|
||||
{
|
||||
parent::boot();
|
||||
|
||||
static::creating(function (self $model): void {
|
||||
if ($model->hasUuidColumn && ! $model->getAttribute('uuid')) {
|
||||
$model->setAttribute('uuid', $model->newUniquePublicId());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate a public id, regenerating (up to three candidates) when one is
|
||||
* already taken. A concurrent insert between this exists() check and our
|
||||
* own insert can still collide; the unique index then rejects the insert,
|
||||
* which is an acceptable residual race for these cheap, retryable writes.
|
||||
*/
|
||||
protected function newUniquePublicId(): string
|
||||
{
|
||||
$attempts = 0;
|
||||
|
||||
do {
|
||||
$candidate = $this->newPublicIdCandidate();
|
||||
$attempts++;
|
||||
} while (
|
||||
$attempts < 3
|
||||
&& $this->newModelQuery()->where('uuid', $candidate)->exists()
|
||||
);
|
||||
|
||||
return $candidate;
|
||||
}
|
||||
|
||||
protected function newPublicIdCandidate(): string
|
||||
{
|
||||
return new_public_id();
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user