diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 2cbc4feb1..00f5b28a6 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -13,7 +13,7 @@ jobs: strategy: fail-fast: false matrix: - php: [8.2, 8.3] + php: [8.2, 8.3, 8.4] database: ["mysql:8"] services: database: @@ -86,7 +86,7 @@ jobs: strategy: fail-fast: false matrix: - php: [8.2, 8.3] + php: [8.2, 8.3, 8.4] database: ["mariadb:10.6", "mariadb:10.11", "mariadb:11.4"] services: database: @@ -159,7 +159,7 @@ jobs: strategy: fail-fast: false matrix: - php: [8.2, 8.3] + php: [8.2, 8.3, 8.4] env: APP_ENV: testing APP_DEBUG: "false" diff --git a/app/Contracts/Validatable.php b/app/Contracts/Validatable.php new file mode 100644 index 000000000..dedf3cbc6 --- /dev/null +++ b/app/Contracts/Validatable.php @@ -0,0 +1,16 @@ +user()->currentAccessToken(); - if ($token instanceof TransientToken) { - return true; - } /** @var ApiKey $token */ if ($token->key_type === ApiKey::TYPE_ACCOUNT) { diff --git a/app/Models/ActivityLog.php b/app/Models/ActivityLog.php index d7ce7530a..c4c9e39b5 100644 --- a/app/Models/ActivityLog.php +++ b/app/Models/ActivityLog.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Traits\HasValidation; use Carbon\Carbon; use Illuminate\Support\Facades\Event; use App\Events\ActivityLogged; @@ -12,7 +13,7 @@ use Illuminate\Database\Eloquent\MassPrunable; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\MorphTo; -use Illuminate\Database\Eloquent\Model as IlluminateModel; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Str; /** @@ -28,12 +29,12 @@ use Illuminate\Support\Str; * @property int|null $api_key_id * @property \Illuminate\Support\Collection|null $properties * @property \Carbon\Carbon $timestamp - * @property IlluminateModel|\Eloquent $actor + * @property Model|\Eloquent $actor * @property \Illuminate\Database\Eloquent\Collection|\App\Models\ActivityLogSubject[] $subjects * @property int|null $subjects_count * @property \App\Models\ApiKey|null $apiKey * - * @method static Builder|ActivityLog forActor(\Illuminate\Database\Eloquent\Model $actor) + * @method static Builder|ActivityLog forActor(Model $actor) * @method static Builder|ActivityLog forEvent(string $action) * @method static Builder|ActivityLog newModelQuery() * @method static Builder|ActivityLog newQuery() @@ -51,6 +52,7 @@ use Illuminate\Support\Str; */ class ActivityLog extends Model implements HasIcon, HasLabel { + use HasValidation; use MassPrunable; public const RESOURCE_NAME = 'activity_log'; @@ -109,7 +111,7 @@ class ActivityLog extends Model implements HasIcon, HasLabel /** * Scopes a query to only return results where the actor is a given model. */ - public function scopeForActor(Builder $builder, IlluminateModel $actor): Builder + public function scopeForActor(Builder $builder, Model $actor): Builder { return $builder->whereMorphedTo('actor', $actor); } diff --git a/app/Models/Allocation.php b/app/Models/Allocation.php index b8bbeb571..702e8b5b8 100644 --- a/app/Models/Allocation.php +++ b/app/Models/Allocation.php @@ -3,7 +3,10 @@ namespace App\Models; use App\Exceptions\Service\Allocation\ServerUsingAllocationException; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsTo; /** @@ -40,17 +43,15 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; */ class Allocation extends Model { + use HasFactory; + use HasValidation; + /** * The resource name for this model when it is transformed into an * API representation using fractal. Also used as name for api key permissions. */ public const RESOURCE_NAME = 'allocation'; - /** - * The table associated with the model. - */ - protected $table = 'allocations'; - /** * Fields that are not mass assignable. */ @@ -81,11 +82,6 @@ class Allocation extends Model ]; } - public function getRouteKeyName(): string - { - return $this->getKeyName(); - } - /** * Accessor to automatically provide the IP alias if defined. */ diff --git a/app/Models/ApiKey.php b/app/Models/ApiKey.php index 299ff7d37..c270a61a2 100644 --- a/app/Models/ApiKey.php +++ b/app/Models/ApiKey.php @@ -3,7 +3,10 @@ namespace App\Models; use App\Services\Acl\Api\AdminAcl; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Support\Str; +use Laravel\Sanctum\PersonalAccessToken; use Webmozart\Assert\Assert; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -47,8 +50,11 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereUpdatedAt($value) * @method static \Illuminate\Database\Eloquent\Builder|ApiKey whereUserId($value) */ -class ApiKey extends Model +class ApiKey extends PersonalAccessToken { + use HasFactory; + use HasValidation; + /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -75,11 +81,6 @@ class ApiKey extends Model */ public const KEY_LENGTH = 32; - /** - * The table associated with the model. - */ - protected $table = 'api_keys'; - /** * Fields that are mass assignable. */ @@ -148,13 +149,9 @@ class ApiKey extends Model return $this->belongsTo(User::class); } - /** - * Required for support with Laravel Sanctum. - * - * @see \Laravel\Sanctum\Guard::supportsTokens() - */ - public function tokenable(): BelongsTo + public function tokenable() { + // @phpstan-ignore-next-line return $this->user(); } @@ -178,11 +175,11 @@ class ApiKey extends Model Role::RESOURCE_NAME, ]; - private static array $customResourceNames = []; + protected static array $customResourceNames = []; public static function registerCustomResourceName(string $resourceName): void { - $customResourceNames[] = $resourceName; + static::$customResourceNames[] = $resourceName; } /** @@ -195,11 +192,14 @@ class ApiKey extends Model /** * Finds the model matching the provided token. + * + * @param string $token */ - public static function findToken(string $token): ?self + public static function findToken($token): ?self { $identifier = substr($token, 0, self::IDENTIFIER_LENGTH); + /** @var static|null $model */ $model = static::where('identifier', $identifier)->first(); if (!is_null($model) && $model->token === substr($token, strlen($identifier))) { return $model; diff --git a/app/Models/AuditLog.php b/app/Models/AuditLog.php index 452933673..ebd8ad5a5 100644 --- a/app/Models/AuditLog.php +++ b/app/Models/AuditLog.php @@ -2,6 +2,10 @@ namespace App\Models; +use App\Contracts\Validatable; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Ramsey\Uuid\Uuid; use Illuminate\Http\Request; use Illuminate\Container\Container; @@ -10,8 +14,11 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; /** * @deprecated — this class will be dropped in a future version, use the activity log */ -class AuditLog extends Model +class AuditLog extends Model implements Validatable { + use HasFactory; + use HasValidation; + public const UPDATED_AT = null; public static array $validationRules = [ @@ -24,8 +31,6 @@ class AuditLog extends Model 'metadata' => 'array', ]; - protected $table = 'audit_logs'; - protected $guarded = [ 'id', 'created_at', diff --git a/app/Models/Backup.php b/app/Models/Backup.php index e214f8bd0..58f8da2eb 100644 --- a/app/Models/Backup.php +++ b/app/Models/Backup.php @@ -2,6 +2,10 @@ namespace App\Models; +use App\Contracts\Validatable; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use App\Eloquent\BackupQueryBuilder; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -25,8 +29,10 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property \App\Models\Server $server * @property \App\Models\AuditLog[] $audits */ -class Backup extends Model +class Backup extends Model implements Validatable { + use HasFactory; + use HasValidation; use SoftDeletes; public const RESOURCE_NAME = 'backup'; @@ -35,8 +41,6 @@ class Backup extends Model public const ADAPTER_AWS_S3 = 's3'; - protected $table = 'backups'; - protected $attributes = [ 'is_successful' => false, 'is_locked' => false, diff --git a/app/Models/Database.php b/app/Models/Database.php index 795fa28af..b7aaac0b7 100644 --- a/app/Models/Database.php +++ b/app/Models/Database.php @@ -2,7 +2,11 @@ namespace App\Models; +use App\Contracts\Validatable; +use App\Traits\HasValidation; use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Facades\DB; @@ -21,8 +25,11 @@ use Illuminate\Support\Facades\DB; * @property \App\Models\Server $server * @property \App\Models\DatabaseHost $host */ -class Database extends Model +class Database extends Model implements Validatable { + use HasFactory; + use HasValidation; + /** * The resource name for this model when it is transformed into an * API representation using fractal. Also used as name for api key permissions. @@ -31,11 +38,6 @@ class Database extends Model public const DEFAULT_CONNECTION_NAME = 'dynamic'; - /** - * The table associated with the model. - */ - protected $table = 'databases'; - /** * The attributes excluded from the model's JSON form. */ @@ -68,11 +70,6 @@ class Database extends Model ]; } - public function getRouteKeyName(): string - { - return $this->getKeyName(); - } - /** * Gets the host database server associated with a database. */ diff --git a/app/Models/DatabaseHost.php b/app/Models/DatabaseHost.php index 1163959d2..e76192cff 100644 --- a/app/Models/DatabaseHost.php +++ b/app/Models/DatabaseHost.php @@ -2,6 +2,10 @@ namespace App\Models; +use App\Contracts\Validatable; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -21,19 +25,17 @@ use Illuminate\Database\Eloquent\Relations\HasMany; * @property \Illuminate\Database\Eloquent\Collection|\App\Models\Database[] $databases * @property int|null $databases_count */ -class DatabaseHost extends Model +class DatabaseHost extends Model implements Validatable { + use HasFactory; + use HasValidation; + /** * The resource name for this model when it is transformed into an * API representation using fractal. Also used as name for api key permissions. */ public const RESOURCE_NAME = 'database_host'; - /** - * The table associated with the model. - */ - protected $table = 'database_hosts'; - /** * The attributes excluded from the model's JSON form. */ @@ -70,11 +72,6 @@ class DatabaseHost extends Model ]; } - public function getRouteKeyName(): string - { - return 'id'; - } - public function nodes(): BelongsToMany { return $this->belongsToMany(Node::class); diff --git a/app/Models/Egg.php b/app/Models/Egg.php index 5ebc16575..1862d8066 100644 --- a/app/Models/Egg.php +++ b/app/Models/Egg.php @@ -2,8 +2,12 @@ namespace App\Models; +use App\Contracts\Validatable; use App\Exceptions\Service\Egg\HasChildrenException; use App\Exceptions\Service\HasActiveServersException; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Support\Str; @@ -49,8 +53,11 @@ use Illuminate\Support\Str; * @property \App\Models\Egg|null $scriptFrom * @property \App\Models\Egg|null $configFrom */ -class Egg extends Model +class Egg extends Model implements Validatable { + use HasFactory; + use HasValidation; + /** * The resource name for this model when it is transformed into an * API representation using fractal. Also used as name for api key permissions. @@ -75,11 +82,6 @@ class Egg extends Model public const FEATURE_FASTDL = 'fastdl'; - /** - * The table associated with the model. - */ - protected $table = 'eggs'; - /** * Fields that are not mass assignable. */ @@ -167,11 +169,6 @@ class Egg extends Model }); } - public function getRouteKeyName(): string - { - return 'id'; - } - /** * Returns the install script for the egg; if egg is copying from another * it will return the copied script. diff --git a/app/Models/EggVariable.php b/app/Models/EggVariable.php index 6d998470d..6991a6f9a 100644 --- a/app/Models/EggVariable.php +++ b/app/Models/EggVariable.php @@ -2,6 +2,10 @@ namespace App\Models; +use App\Contracts\Validatable; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\HasMany; @@ -26,8 +30,11 @@ use Illuminate\Database\Eloquent\Relations\HasMany; * using the server relationship. * @property string|null $server_value */ -class EggVariable extends Model +class EggVariable extends Model implements Validatable { + use HasFactory; + use HasValidation; + /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -39,11 +46,6 @@ class EggVariable extends Model */ public const RESERVED_ENV_NAMES = 'P_SERVER_UUID,P_SERVER_ALLOCATION_LIMIT,SERVER_MEMORY,SERVER_IP,SERVER_PORT,ENV,HOME,USER,STARTUP,MODIFIED_STARTUP,SERVER_UUID,UUID,INTERNAL_IP'; - /** - * The table associated with the model. - */ - protected $table = 'egg_variables'; - /** * Fields that are not mass assignable. */ diff --git a/app/Models/File.php b/app/Models/File.php index bb582884c..e79812a1f 100644 --- a/app/Models/File.php +++ b/app/Models/File.php @@ -7,6 +7,7 @@ use App\Repositories\Daemon\DaemonFileRepository; use Carbon\Carbon; use Exception; use Illuminate\Database\Eloquent\Builder; +use Illuminate\Database\Eloquent\Model; use Sushi\Sushi; /** diff --git a/app/Models/Mount.php b/app/Models/Mount.php index 60cb2b436..86d374bc7 100644 --- a/app/Models/Mount.php +++ b/app/Models/Mount.php @@ -2,6 +2,9 @@ namespace App\Models; +use App\Contracts\Validatable; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Model; use Illuminate\Validation\Rules\NotIn; use Illuminate\Database\Eloquent\Relations\BelongsToMany; @@ -18,19 +21,16 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; * @property \App\Models\Node[]|\Illuminate\Database\Eloquent\Collection $nodes * @property \App\Models\Server[]|\Illuminate\Database\Eloquent\Collection $servers */ -class Mount extends Model +class Mount extends Model implements Validatable { + use HasValidation { getRules as getValidationRules; } + /** * The resource name for this model when it is transformed into an * API representation using fractal. */ public const RESOURCE_NAME = 'mount'; - /** - * The table associated with the model. - */ - protected $table = 'mounts'; - /** * Fields that are not mass assignable. */ @@ -54,7 +54,7 @@ class Mount extends Model */ public static function getRules(): array { - $rules = parent::getRules(); + $rules = self::getValidationRules(); $rules['source'][] = new NotIn(Mount::$invalidSourcePaths); $rules['target'][] = new NotIn(Mount::$invalidTargetPaths); @@ -115,9 +115,4 @@ class Mount extends Model { return $this->belongsToMany(Server::class); } - - public function getRouteKeyName(): string - { - return 'id'; - } } diff --git a/app/Models/Node.php b/app/Models/Node.php index d8dbab90d..caf614354 100644 --- a/app/Models/Node.php +++ b/app/Models/Node.php @@ -2,9 +2,13 @@ namespace App\Models; +use App\Contracts\Validatable; use App\Exceptions\Service\HasActiveServersException; use App\Repositories\Daemon\DaemonConfigurationRepository; +use App\Traits\HasValidation; use Exception; +use Illuminate\Database\Eloquent\Model; +use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\HasManyThrough; @@ -47,8 +51,10 @@ use Symfony\Component\Yaml\Yaml; * @property \App\Models\Allocation[]|\Illuminate\Database\Eloquent\Collection $allocations * @property int|null $allocations_count */ -class Node extends Model +class Node extends Model implements Validatable { + use HasFactory; + use HasValidation; use Notifiable; /** @@ -61,11 +67,6 @@ class Node extends Model public const DAEMON_TOKEN_LENGTH = 64; - /** - * The table associated with the model. - */ - protected $table = 'nodes'; - /** * The attributes excluded from the model's JSON form. */ @@ -146,11 +147,6 @@ class Node extends Model public int $servers_sum_cpu = 0; - public function getRouteKeyName(): string - { - return 'id'; - } - protected static function booted(): void { static::creating(function (self $node) { diff --git a/app/Models/Permission.php b/app/Models/Permission.php index 29cbd826d..59bb97913 100644 --- a/app/Models/Permission.php +++ b/app/Models/Permission.php @@ -2,10 +2,15 @@ namespace App\Models; +use App\Contracts\Validatable; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Model; use Illuminate\Support\Collection; -class Permission extends Model +class Permission extends Model implements Validatable { + use HasValidation; + /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -95,16 +100,8 @@ class Permission extends Model public const ACTION_ACTIVITY_READ = 'activity.read'; - /** - * Should timestamps be used on this model. - */ public $timestamps = false; - /** - * The table associated with the model. - */ - protected $table = 'permissions'; - /** * Fields that are not mass assignable. */ diff --git a/app/Models/RecoveryToken.php b/app/Models/RecoveryToken.php index c4fe8c8ae..809c735d4 100644 --- a/app/Models/RecoveryToken.php +++ b/app/Models/RecoveryToken.php @@ -2,6 +2,9 @@ namespace App\Models; +use App\Contracts\Validatable; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; /** @@ -11,8 +14,10 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property \Carbon\CarbonImmutable $created_at * @property \App\Models\User $user */ -class RecoveryToken extends Model +class RecoveryToken extends Model implements Validatable { + use HasValidation; + /** * There are no updates to this model, only inserts and deletes. */ diff --git a/app/Models/Schedule.php b/app/Models/Schedule.php index d6d19a8e1..ba85428b8 100644 --- a/app/Models/Schedule.php +++ b/app/Models/Schedule.php @@ -2,7 +2,11 @@ namespace App\Models; +use App\Contracts\Validatable; use App\Helpers\Utilities; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -25,19 +29,17 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property \App\Models\Server $server * @property \App\Models\Task[]|\Illuminate\Support\Collection $tasks */ -class Schedule extends Model +class Schedule extends Model implements Validatable { + use HasFactory; + use HasValidation; + /** * The resource name for this model when it is transformed into an * API representation using fractal. */ public const RESOURCE_NAME = 'server_schedule'; - /** - * The table associated with the model. - */ - protected $table = 'schedules'; - /** * Always return the tasks associated with this schedule. */ @@ -101,11 +103,6 @@ class Schedule extends Model ]; } - public function getRouteKeyName(): string - { - return $this->getKeyName(); - } - /** * Returns the schedule's execution crontab entry as a string. * diff --git a/app/Models/Server.php b/app/Models/Server.php index 39e1ef3be..53fe692b0 100644 --- a/app/Models/Server.php +++ b/app/Models/Server.php @@ -2,12 +2,16 @@ namespace App\Models; +use App\Contracts\Validatable; use App\Enums\ContainerStatus; use App\Enums\ServerResourceType; use App\Enums\ServerState; use App\Repositories\Daemon\DaemonServerRepository; +use App\Traits\HasValidation; use Carbon\CarbonInterface; use Illuminate\Database\Eloquent\Casts\Attribute; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Http\Client\ConnectionException; use Illuminate\Notifications\Notifiable; @@ -121,8 +125,10 @@ use App\Services\Subusers\SubuserDeletionService; * @method static \Illuminate\Database\Eloquent\Builder|Server wherePorts($value) * @method static \Illuminate\Database\Eloquent\Builder|Server whereUuidShort($value) */ -class Server extends Model +class Server extends Model implements Validatable { + use HasFactory; + use HasValidation; use Notifiable; /** @@ -131,11 +137,6 @@ class Server extends Model */ public const RESOURCE_NAME = 'server'; - /** - * The table associated with the model. - */ - protected $table = 'servers'; - /** * Default values when creating the model. We want to switch to disabling OOM killer * on server instances unless the user specifies otherwise in the request. @@ -363,11 +364,6 @@ class Server extends Model return $this->morphToMany(ActivityLog::class, 'subject', 'activity_log_subjects'); } - public function getRouteKeyName(): string - { - return 'id'; - } - public function resolveRouteBinding($value, $field = null): ?self { return match ($field) { diff --git a/app/Models/ServerTransfer.php b/app/Models/ServerTransfer.php index abfef3eb6..ce8b29f2f 100644 --- a/app/Models/ServerTransfer.php +++ b/app/Models/ServerTransfer.php @@ -2,6 +2,9 @@ namespace App\Models; +use App\Contracts\Validatable; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasOne; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -22,19 +25,16 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property \App\Models\Node $oldNode * @property \App\Models\Node $newNode */ -class ServerTransfer extends Model +class ServerTransfer extends Model implements Validatable { + use HasValidation; + /** * The resource name for this model when it is transformed into an * API representation using fractal. */ public const RESOURCE_NAME = 'server_transfer'; - /** - * The table associated with the model. - */ - protected $table = 'server_transfers'; - /** * Fields that are not mass assignable. */ diff --git a/app/Models/ServerVariable.php b/app/Models/ServerVariable.php index 0c7eeffc5..6721b87ed 100644 --- a/app/Models/ServerVariable.php +++ b/app/Models/ServerVariable.php @@ -2,6 +2,9 @@ namespace App\Models; +use App\Contracts\Validatable; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsTo; /** @@ -14,16 +17,16 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property \App\Models\EggVariable $variable * @property \App\Models\Server $server */ -class ServerVariable extends Model +class ServerVariable extends Model implements Validatable { + use HasValidation; + /** * The resource name for this model when it is transformed into an * API representation using fractal. */ public const RESOURCE_NAME = 'server_variable'; - protected $table = 'server_variables'; - protected $guarded = ['id', 'created_at', 'updated_at']; public static array $validationRules = [ diff --git a/app/Models/Subuser.php b/app/Models/Subuser.php index 566c8a741..0276b8bc9 100644 --- a/app/Models/Subuser.php +++ b/app/Models/Subuser.php @@ -2,6 +2,10 @@ namespace App\Models; +use App\Contracts\Validatable; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Notifications\Notifiable; use Illuminate\Database\Eloquent\Relations\HasMany; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -16,8 +20,10 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property \App\Models\User $user * @property \App\Models\Server $server */ -class Subuser extends Model +class Subuser extends Model implements Validatable { + use HasFactory; + use HasValidation; use Notifiable; /** @@ -26,11 +32,6 @@ class Subuser extends Model */ public const RESOURCE_NAME = 'server_subuser'; - /** - * The table associated with the model. - */ - protected $table = 'subusers'; - /** * Fields that are not mass assignable. */ diff --git a/app/Models/Task.php b/app/Models/Task.php index 202a3165a..69ad93336 100644 --- a/app/Models/Task.php +++ b/app/Models/Task.php @@ -2,6 +2,10 @@ namespace App\Models; +use App\Contracts\Validatable; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\HasOneThrough; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -19,8 +23,11 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; * @property \App\Models\Schedule $schedule * @property \App\Models\Server $server */ -class Task extends Model +class Task extends Model implements Validatable { + use HasFactory; + use HasValidation; + /** * The resource name for this model when it is transformed into an * API representation using fractal. @@ -38,11 +45,6 @@ class Task extends Model public const ACTION_DELETE_FILES = 'delete_files'; - /** - * The table associated with the model. - */ - protected $table = 'tasks'; - /** * Relationships to be updated when this model is updated. */ @@ -92,11 +94,6 @@ class Task extends Model ]; } - public function getRouteKeyName(): string - { - return $this->getKeyName(); - } - /** * Return the schedule that a task belongs to. */ diff --git a/app/Models/User.php b/app/Models/User.php index b2d3b3c29..e93dddf9d 100644 --- a/app/Models/User.php +++ b/app/Models/User.php @@ -2,15 +2,19 @@ namespace App\Models; +use App\Contracts\Validatable; use App\Exceptions\DisplayException; use App\Rules\Username; use App\Facades\Activity; +use App\Traits\HasValidation; use DateTimeZone; use Filament\Models\Contracts\FilamentUser; use Filament\Models\Contracts\HasAvatar; use Filament\Models\Contracts\HasName; use Filament\Models\Contracts\HasTenants; use Filament\Panel; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Support\Collection; use Illuminate\Support\Str; @@ -28,7 +32,6 @@ use Illuminate\Contracts\Auth\Access\Authorizable as AuthorizableContract; use Illuminate\Contracts\Auth\CanResetPassword as CanResetPasswordContract; use App\Notifications\SendPasswordReset as ResetPasswordNotification; use Filament\Facades\Filament; -use Illuminate\Database\Eloquent\Model as IlluminateModel; use ResourceBundle; use Spatie\Permission\Traits\HasRoles; @@ -85,13 +88,15 @@ use Spatie\Permission\Traits\HasRoles; * @method static Builder|User whereUsername($value) * @method static Builder|User whereUuid($value) */ -class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, FilamentUser, HasAvatar, HasName, HasTenants +class User extends Model implements AuthenticatableContract, AuthorizableContract, CanResetPasswordContract, FilamentUser, HasAvatar, HasName, HasTenants, Validatable { use Authenticatable; - use Authorizable {can as protected canned; } + use Authorizable { can as protected canned; } use CanResetPassword; use HasAccessTokens; + use HasFactory; use HasRoles; + use HasValidation { getRules as getValidationRules; } use Notifiable; public const USER_LEVEL_USER = 0; @@ -104,16 +109,6 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac */ public const RESOURCE_NAME = 'user'; - /** - * Level of servers to display when using access() on a user. - */ - protected string $accessLevel = 'all'; - - /** - * The table associated with the model. - */ - protected $table = 'users'; - /** * A list of mass-assignable variables. */ @@ -191,18 +186,13 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac }); } - public function getRouteKeyName(): string - { - return 'id'; - } - /** * Implement language verification by overriding Eloquence's gather * rules function. */ public static function getRules(): array { - $rules = parent::getRules(); + $rules = self::getValidationRules(); $rules['language'][] = new In(array_values(array_filter(ResourceBundle::getLocales(''), fn ($lang) => preg_match('/^[a-z]{2}$/', $lang)))); $rules['timezone'][] = new In(DateTimeZone::listIdentifiers()); @@ -407,7 +397,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac return 'https://gravatar.com/avatar/' . md5(strtolower($this->email)); } - public function canTarget(IlluminateModel $user): bool + public function canTarget(Model $user): bool { if ($this->isRootAdmin()) { return true; @@ -421,7 +411,7 @@ class User extends Model implements AuthenticatableContract, AuthorizableContrac return $this->accessibleServers()->get(); } - public function canAccessTenant(IlluminateModel $tenant): bool + public function canAccessTenant(Model $tenant): bool { if ($tenant instanceof Server) { if ($this->canned('view server', $tenant) || $tenant->owner_id === $this->id) { diff --git a/app/Models/UserSSHKey.php b/app/Models/UserSSHKey.php index f5cf02e4a..e07b52e3b 100644 --- a/app/Models/UserSSHKey.php +++ b/app/Models/UserSSHKey.php @@ -2,6 +2,9 @@ namespace App\Models; +use App\Traits\HasValidation; +use Illuminate\Database\Eloquent\Factories\HasFactory; +use Illuminate\Database\Eloquent\Model; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Database\Eloquent\Relations\BelongsTo; @@ -36,6 +39,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; */ class UserSSHKey extends Model { + use HasFactory; + use HasValidation; use SoftDeletes; public const RESOURCE_NAME = 'ssh_key'; diff --git a/app/Observers/ValidationObserver.php b/app/Observers/ValidationObserver.php new file mode 100644 index 000000000..f5537569c --- /dev/null +++ b/app/Observers/ValidationObserver.php @@ -0,0 +1,20 @@ +validate(); + } catch (ValidationException $exception) { + throw new DataValidationException($exception->validator, $model); + } + } +} diff --git a/app/Models/Model.php b/app/Traits/HasValidation.php similarity index 62% rename from app/Models/Model.php rename to app/Traits/HasValidation.php index ca36fbefe..8cb3617be 100644 --- a/app/Models/Model.php +++ b/app/Traits/HasValidation.php @@ -1,69 +1,21 @@ make(ValidationFactory::class); - - static::saving(function (Model $model) { - try { - $model->validate(); - } catch (ValidationException $exception) { - throw new DataValidationException($exception->validator, $model); - } - - return true; - }); - } - - /** - * Returns the model key to use for route model binding. By default, we'll - * assume every model uses a UUID field for this. If the model does not have - * a UUID and is using a different key it should be specified on the model - * itself. - * - * You may also optionally override this on a per-route basis by declaring - * the key name in the URL definition, like "{user:id}". - */ - public function getRouteKeyName(): string - { - return 'uuid'; - } - /** * Returns the validator instance used by this model. */ @@ -71,7 +23,9 @@ abstract class Model extends IlluminateModel { $rules = $this->exists ? static::getRulesForUpdate($this) : static::getRules(); - return static::$validatorFactory->make([], $rules); + $validatorFactory = Container::getInstance()->make(ValidationFactory::class); + + return $validatorFactory->make([], $rules); } /** @@ -132,7 +86,7 @@ abstract class Model extends IlluminateModel */ public function validate(): void { - if ($this->skipValidation) { + if (isset($this->skipValidation)) { return; } diff --git a/composer.json b/composer.json index b2d2fec92..1147bed59 100644 --- a/composer.json +++ b/composer.json @@ -2,7 +2,7 @@ "name": "pelican-dev/panel", "description": "The free, open-source game management panel. Supporting Minecraft, Spigot, BungeeCord, and SRCDS servers.", "require": { - "php": "^8.2 || ^8.3", + "php": "^8.2 || ^8.3 || ^8.4", "ext-intl": "*", "ext-json": "*", "ext-mbstring": "*", diff --git a/composer.lock b/composer.lock index 2b63b57a5..f75429533 100644 --- a/composer.lock +++ b/composer.lock @@ -2951,16 +2951,16 @@ }, { "name": "laravel/framework", - "version": "v11.37.0", + "version": "v11.40.0", "source": { "type": "git", "url": "https://github.com/laravel/framework.git", - "reference": "6cb103d2024b087eae207654b3f4b26646119ba5" + "reference": "599a28196d284fee158cc10086fd56ac625ad7a3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/framework/zipball/6cb103d2024b087eae207654b3f4b26646119ba5", - "reference": "6cb103d2024b087eae207654b3f4b26646119ba5", + "url": "https://api.github.com/repos/laravel/framework/zipball/599a28196d284fee158cc10086fd56ac625ad7a3", + "reference": "599a28196d284fee158cc10086fd56ac625ad7a3", "shasum": "" }, "require": { @@ -2986,7 +2986,7 @@ "league/flysystem-local": "^3.25.1", "league/uri": "^7.5.1", "monolog/monolog": "^3.0", - "nesbot/carbon": "^2.72.2|^3.4", + "nesbot/carbon": "^2.72.6|^3.8.4", "nunomaduro/termwind": "^2.0", "php": "^8.2", "psr/container": "^1.1.1|^2.0.1", @@ -3061,6 +3061,7 @@ "fakerphp/faker": "^1.24", "guzzlehttp/promises": "^2.0.3", "guzzlehttp/psr7": "^2.4", + "laravel/pint": "^1.18", "league/flysystem-aws-s3-v3": "^3.25.1", "league/flysystem-ftp": "^3.25.1", "league/flysystem-path-prefixing": "^3.25.1", @@ -3161,7 +3162,7 @@ "issues": "https://github.com/laravel/framework/issues", "source": "https://github.com/laravel/framework" }, - "time": "2025-01-02T20:10:21+00:00" + "time": "2025-01-24T16:17:42+00:00" }, { "name": "laravel/helpers", @@ -3281,16 +3282,16 @@ }, { "name": "laravel/sanctum", - "version": "v4.0.3", + "version": "v4.0.7", "source": { "type": "git", "url": "https://github.com/laravel/sanctum.git", - "reference": "54aea9d13743ae8a6cdd3c28dbef128a17adecab" + "reference": "698064236a46df016e64a7eb059b1414e0b281df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/laravel/sanctum/zipball/54aea9d13743ae8a6cdd3c28dbef128a17adecab", - "reference": "54aea9d13743ae8a6cdd3c28dbef128a17adecab", + "url": "https://api.github.com/repos/laravel/sanctum/zipball/698064236a46df016e64a7eb059b1414e0b281df", + "reference": "698064236a46df016e64a7eb059b1414e0b281df", "shasum": "" }, "require": { @@ -3341,7 +3342,7 @@ "issues": "https://github.com/laravel/sanctum/issues", "source": "https://github.com/laravel/sanctum" }, - "time": "2024-09-27T14:55:41+00:00" + "time": "2024-12-11T16:40:21+00:00" }, { "name": "laravel/serializable-closure", @@ -10881,16 +10882,16 @@ "packages-dev": [ { "name": "barryvdh/laravel-ide-helper", - "version": "v3.2.2", + "version": "v3.3.0", "source": { "type": "git", "url": "https://github.com/barryvdh/laravel-ide-helper.git", - "reference": "07e3bd8796f3d1414801a03d3783f9d3ec9efc08" + "reference": "b7675670f75914bf34afdea52a6c2fe3781f7c44" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/07e3bd8796f3d1414801a03d3783f9d3ec9efc08", - "reference": "07e3bd8796f3d1414801a03d3783f9d3ec9efc08", + "url": "https://api.github.com/repos/barryvdh/laravel-ide-helper/zipball/b7675670f75914bf34afdea52a6c2fe3781f7c44", + "reference": "b7675670f75914bf34afdea52a6c2fe3781f7c44", "shasum": "" }, "require": { @@ -10921,13 +10922,13 @@ }, "type": "library", "extra": { - "branch-alias": { - "dev-master": "3.2-dev" - }, "laravel": { "providers": [ "Barryvdh\\LaravelIdeHelper\\IdeHelperServiceProvider" ] + }, + "branch-alias": { + "dev-master": "3.2-dev" } }, "autoload": { @@ -10959,7 +10960,7 @@ ], "support": { "issues": "https://github.com/barryvdh/laravel-ide-helper/issues", - "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.2.2" + "source": "https://github.com/barryvdh/laravel-ide-helper/tree/v3.3.0" }, "funding": [ { @@ -10971,7 +10972,7 @@ "type": "github" } ], - "time": "2024-10-29T14:00:16+00:00" + "time": "2024-12-18T08:24:19+00:00" }, { "name": "barryvdh/reflection-docblock", @@ -13969,7 +13970,7 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": {}, + "stability-flags": [], "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -13980,6 +13981,6 @@ "ext-pdo": "*", "ext-zip": "*" }, - "platform-dev": {}, + "platform-dev": [], "plugin-api-version": "2.6.0" } diff --git a/database/migrations/2024_11_04_185326_revamp_api_keys_permissions.php b/database/migrations/2024_11_04_185326_revamp_api_keys_permissions.php index ee8b64974..9c44d3d72 100644 --- a/database/migrations/2024_11_04_185326_revamp_api_keys_permissions.php +++ b/database/migrations/2024_11_04_185326_revamp_api_keys_permissions.php @@ -25,7 +25,7 @@ return new class extends Migration $table->json('permissions'); }); - foreach (ApiKey::query() as $apiKey) { + foreach (ApiKey::all() as $apiKey) { $permissions = [ Server::RESOURCE_NAME => intval($apiKey->r_servers ?? 0), Node::RESOURCE_NAME => intval($apiKey->r_nodes ?? 0), diff --git a/routes/api-client.php b/routes/api-client.php index ed1190af6..0feceeb78 100644 --- a/routes/api-client.php +++ b/routes/api-client.php @@ -115,11 +115,11 @@ Route::prefix('/servers/{server:uuid}')->middleware([ServerSubject::class, Authe Route::prefix('/backups')->group(function () { Route::get('/', [Client\Servers\BackupController::class, 'index']); Route::post('/', [Client\Servers\BackupController::class, 'store']); - Route::get('/{backup}', [Client\Servers\BackupController::class, 'view']); - Route::get('/{backup}/download', [Client\Servers\BackupController::class, 'download']); - Route::post('/{backup}/lock', [Client\Servers\BackupController::class, 'toggleLock']); - Route::post('/{backup}/restore', [Client\Servers\BackupController::class, 'restore']); - Route::delete('/{backup}', [Client\Servers\BackupController::class, 'delete']); + Route::get('/{backup:uuid}', [Client\Servers\BackupController::class, 'view']); + Route::get('/{backup:uuid}/download', [Client\Servers\BackupController::class, 'download']); + Route::post('/{backup:uuid}/lock', [Client\Servers\BackupController::class, 'toggleLock']); + Route::post('/{backup:uuid}/restore', [Client\Servers\BackupController::class, 'restore']); + Route::delete('/{backup:uuid}', [Client\Servers\BackupController::class, 'delete']); }); Route::prefix('/startup')->group(function () { diff --git a/routes/api-remote.php b/routes/api-remote.php index d2c9a893d..df69d1c30 100644 --- a/routes/api-remote.php +++ b/routes/api-remote.php @@ -24,7 +24,7 @@ Route::prefix('/servers/{server:uuid}')->group(function () { }); Route::prefix('/backups')->group(function () { - Route::get('/{backup}', Remote\Backups\BackupRemoteUploadController::class); - Route::post('/{backup}', [Remote\Backups\BackupStatusController::class, 'index']); - Route::post('/{backup}/restore', [Remote\Backups\BackupStatusController::class, 'restore']); + Route::get('/{backup:uuid}', Remote\Backups\BackupRemoteUploadController::class); + Route::post('/{backup:uuid}', [Remote\Backups\BackupStatusController::class, 'index']); + Route::post('/{backup:uuid}/restore', [Remote\Backups\BackupStatusController::class, 'restore']); }); diff --git a/tests/Integration/Api/Client/TwoFactorControllerTest.php b/tests/Integration/Api/Client/TwoFactorControllerTest.php index 24d8a2684..2b2c38c99 100644 --- a/tests/Integration/Api/Client/TwoFactorControllerTest.php +++ b/tests/Integration/Api/Client/TwoFactorControllerTest.php @@ -100,7 +100,7 @@ class TwoFactorControllerTest extends ClientApiIntegrationTestCase $tokens = RecoveryToken::query()->where('user_id', $user->id)->get(); $this->assertCount(10, $tokens); - $this->assertStringStartsWith('$2y$10$', $tokens[0]->token); + $this->assertStringStartsWith('$2y$', $tokens[0]->token); // Ensure the recovery tokens that were created include a "created_at" timestamp value on them. $this->assertNotNull($tokens[0]->created_at);