From 51e2bee266cbb18c0bf119e8fe3262f7528eeee0 Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Tue, 18 Aug 2026 01:38:18 -0400 Subject: [PATCH 1/3] Replace the Fractal transformers with laravel-data wire objects Adds an owned envelope layer under app/Data/Api: ApiResource is the laravel-data base for the wire objects, Envelope renders the panel's {object, attributes} format with includes nested under relationships, IncludeContext carries the request and include paths with the ported authorize checks, and PanelResponse is the drop-in for the injected Fractal wrapper with League's include parsing, pagination, and addMeta semantics reproduced exactly. Every application and client transformer now has a Data class, and every controller call site runs through the new layer, verified by the contract freeze suite with zero snapshot changes plus the pre-existing Integration suites. The old transformers, the Fractal extension classes, and the deprecated egg config and script and server transfer include declarations are intentionally still present; teardown happens in the next layer. Requesting one of those three includes used to 500 because they were declared but never implemented, and it is now silently ignored like any other unknown include. --- app/Data/Api/ApiResource.php | 61 ++++++ app/Data/Api/Application/AllocationData.php | 61 ++++++ app/Data/Api/Application/DatabaseHostData.php | 70 +++++++ app/Data/Api/Application/EggData.php | 112 +++++++++++ app/Data/Api/Application/EggVariableData.php | 33 ++++ app/Data/Api/Application/MountData.php | 72 +++++++ app/Data/Api/Application/NodeData.php | 77 ++++++++ app/Data/Api/Application/PluginData.php | 62 ++++++ app/Data/Api/Application/RoleData.php | 46 +++++ .../Api/Application/RolePermissionData.php | 29 +++ app/Data/Api/Application/ServerData.php | 170 ++++++++++++++++ .../Api/Application/ServerDatabaseData.php | 66 +++++++ .../Api/Application/ServerVariableData.php | 52 +++++ app/Data/Api/Application/SubuserData.php | 65 +++++++ app/Data/Api/Application/UserData.php | 83 ++++++++ app/Data/Api/Client/ActivityLogData.php | 68 +++++++ app/Data/Api/Client/AllocationData.php | 35 ++++ app/Data/Api/Client/ApiKeyData.php | 36 ++++ app/Data/Api/Client/BackupData.php | 46 +++++ app/Data/Api/Client/DatabaseData.php | 63 ++++++ app/Data/Api/Client/EggData.php | 27 +++ app/Data/Api/Client/EggVariableData.php | 43 ++++ app/Data/Api/Client/FileObjectData.php | 45 +++++ app/Data/Api/Client/ScheduleData.php | 68 +++++++ app/Data/Api/Client/ServerData.php | 136 +++++++++++++ app/Data/Api/Client/StatsData.php | 42 ++++ app/Data/Api/Client/SubuserData.php | 52 +++++ app/Data/Api/Client/TaskData.php | 41 ++++ app/Data/Api/Client/UserData.php | 46 +++++ app/Data/Api/Client/UserSSHKeyData.php | 31 +++ app/Data/Api/Envelope.php | 89 +++++++++ app/Data/Api/IncludeContext.php | 114 +++++++++++ app/Data/Api/PanelResponse.php | 183 ++++++++++++++++++ .../Application/ApplicationApiController.php | 9 +- .../DatabaseHosts/DatabaseHostController.php | 18 +- .../Api/Application/Eggs/EggController.php | 14 +- .../Application/Mounts/MountController.php | 48 ++--- .../Nodes/AllocationController.php | 6 +- .../Api/Application/Nodes/NodeController.php | 18 +- .../Nodes/NodeDeploymentController.php | 6 +- .../Application/Plugins/PluginController.php | 30 +-- .../Api/Application/Roles/RoleController.php | 18 +- .../Servers/DatabaseController.php | 14 +- .../Servers/ExternalServerController.php | 6 +- .../Application/Servers/ServerController.php | 14 +- .../Servers/ServerDetailsController.php | 10 +- .../Application/Servers/StartupController.php | 6 +- .../Users/ExternalUserController.php | 6 +- .../Api/Application/Users/UserController.php | 26 +-- .../Api/Client/AccountController.php | 6 +- .../Api/Client/ActivityLogController.php | 6 +- .../Api/Client/ApiKeyController.php | 10 +- .../Api/Client/ClientController.php | 10 +- .../Api/Client/SSHKeyController.php | 10 +- .../Client/Servers/ActivityLogController.php | 6 +- .../Api/Client/Servers/BackupController.php | 22 +-- .../Api/Client/Servers/DatabaseController.php | 14 +- .../Api/Client/Servers/FileController.php | 10 +- .../Servers/NetworkAllocationController.php | 18 +- .../Servers/ResourceUtilizationController.php | 6 +- .../Api/Client/Servers/ScheduleController.php | 18 +- .../Client/Servers/ScheduleTaskController.php | 10 +- .../Api/Client/Servers/ServerController.php | 6 +- .../Api/Client/Servers/StartupController.php | 10 +- .../Api/Client/Servers/SubuserController.php | 18 +- 65 files changed, 2443 insertions(+), 210 deletions(-) create mode 100644 app/Data/Api/ApiResource.php create mode 100644 app/Data/Api/Application/AllocationData.php create mode 100644 app/Data/Api/Application/DatabaseHostData.php create mode 100644 app/Data/Api/Application/EggData.php create mode 100644 app/Data/Api/Application/EggVariableData.php create mode 100644 app/Data/Api/Application/MountData.php create mode 100644 app/Data/Api/Application/NodeData.php create mode 100644 app/Data/Api/Application/PluginData.php create mode 100644 app/Data/Api/Application/RoleData.php create mode 100644 app/Data/Api/Application/RolePermissionData.php create mode 100644 app/Data/Api/Application/ServerData.php create mode 100644 app/Data/Api/Application/ServerDatabaseData.php create mode 100644 app/Data/Api/Application/ServerVariableData.php create mode 100644 app/Data/Api/Application/SubuserData.php create mode 100644 app/Data/Api/Application/UserData.php create mode 100644 app/Data/Api/Client/ActivityLogData.php create mode 100644 app/Data/Api/Client/AllocationData.php create mode 100644 app/Data/Api/Client/ApiKeyData.php create mode 100644 app/Data/Api/Client/BackupData.php create mode 100644 app/Data/Api/Client/DatabaseData.php create mode 100644 app/Data/Api/Client/EggData.php create mode 100644 app/Data/Api/Client/EggVariableData.php create mode 100644 app/Data/Api/Client/FileObjectData.php create mode 100644 app/Data/Api/Client/ScheduleData.php create mode 100644 app/Data/Api/Client/ServerData.php create mode 100644 app/Data/Api/Client/StatsData.php create mode 100644 app/Data/Api/Client/SubuserData.php create mode 100644 app/Data/Api/Client/TaskData.php create mode 100644 app/Data/Api/Client/UserData.php create mode 100644 app/Data/Api/Client/UserSSHKeyData.php create mode 100644 app/Data/Api/Envelope.php create mode 100644 app/Data/Api/IncludeContext.php create mode 100644 app/Data/Api/PanelResponse.php diff --git a/app/Data/Api/ApiResource.php b/app/Data/Api/ApiResource.php new file mode 100644 index 0000000000..e6166a8452 --- /dev/null +++ b/app/Data/Api/ApiResource.php @@ -0,0 +1,61 @@ +> + */ + public static function includes(): array + { + return []; + } + + /** + * Return an ISO-8601 formatted timestamp to use in the API response. + */ + protected static function formatTimestamp(string $timestamp): string + { + return CarbonImmutable::createFromFormat(CarbonInterface::DEFAULT_TO_STRING_FORMAT, $timestamp) + ->setTimezone(self::RESPONSE_TIMEZONE) + ->toAtomString(); + } +} diff --git a/app/Data/Api/Application/AllocationData.php b/app/Data/Api/Application/AllocationData.php new file mode 100644 index 0000000000..a4e5b4f7c7 --- /dev/null +++ b/app/Data/Api/Application/AllocationData.php @@ -0,0 +1,61 @@ +id, + ip: $model->ip, + alias: $model->ip_alias, + port: $model->port, + notes: $model->notes, + assigned: !is_null($model->server_id), + ); + } + + public static function includes(): array + { + return [ + 'node' => function (Allocation $allocation, IncludeContext $context): array { + if (!$context->allowsAdmin(Node::RESOURCE_NAME)) { + return $context->null(); + } + + return $context->item($allocation->node, NodeData::class); + }, + 'server' => function (Allocation $allocation, IncludeContext $context): array { + if (!$context->allowsAdmin(Server::RESOURCE_NAME) || !$allocation->server) { + return $context->null(); + } + + return $context->item($allocation->server, ServerData::class); + }, + ]; + } +} diff --git a/app/Data/Api/Application/DatabaseHostData.php b/app/Data/Api/Application/DatabaseHostData.php new file mode 100644 index 0000000000..951bc687e2 --- /dev/null +++ b/app/Data/Api/Application/DatabaseHostData.php @@ -0,0 +1,70 @@ +id, + name: $model->name, + host: $model->host, + port: $model->port, + username: $model->username, + created_at: $model->created_at->toAtomString(), + updated_at: $model->updated_at->toAtomString(), + ); + } + + public static function includes(): array + { + return [ + 'databases' => function (DatabaseHost $host, IncludeContext $context): array { + if (!$context->allowsAdmin(Database::RESOURCE_NAME)) { + return $context->null(); + } + + $host->loadMissing('databases'); + + return $context->collection($host->getRelation('databases'), ServerDatabaseData::class); + }, + 'nodes' => function (DatabaseHost $host, IncludeContext $context): array { + if (!$context->allowsAdmin(Node::RESOURCE_NAME)) { + return $context->null(); + } + + $host->loadMissing('nodes'); + + return $context->collection($host->getRelation('nodes'), NodeData::class); + }, + ]; + } +} diff --git a/app/Data/Api/Application/EggData.php b/app/Data/Api/Application/EggData.php new file mode 100644 index 0000000000..df0b525485 --- /dev/null +++ b/app/Data/Api/Application/EggData.php @@ -0,0 +1,112 @@ + $config + * @param array $script + */ + public function __construct( + public int $id, + public string $uuid, + public string $name, + public ?string $author, + public ?string $description, + public ?string $icon, + public mixed $features, + public mixed $tags, + public string $docker_image, + public mixed $docker_images, + public array $config, + public string $startup, + public mixed $startup_commands, + public array $script, + public string $created_at, + public string $updated_at, + ) {} + + public static function getResourceName(): string + { + return Egg::RESOURCE_NAME; + } + + public static function fromModel(Egg $model): static + { + $model->loadMissing('configFrom'); + + $files = json_decode($model->inherit_config_files ?: '{}', true, 512, JSON_THROW_ON_ERROR); + + $model->loadMissing('scriptFrom'); + + return new static( + id: $model->id, + uuid: $model->uuid, + name: $model->name, + author: $model->author, + description: $model->description, + icon: $model->icon, + features: $model->features, + tags: $model->tags, + docker_image: Arr::first($model->docker_images, default: ''), // deprecated, use docker_images + docker_images: $model->docker_images, + config: [ + 'files' => $files, + 'startup' => json_decode($model->inherit_config_startup ?: '{}', true), + 'stop' => $model->inherit_config_stop, + 'logs' => json_decode($model->inherit_config_logs ?: '{}', true), + 'file_denylist' => $model->inherit_file_denylist, + 'extends' => $model->config_from, + ], + startup: Arr::first($model->startup_commands, default: ''), // deprecated, use startup_commands + startup_commands: $model->startup_commands, + script: [ + 'privileged' => $model->script_is_privileged, + 'install' => $model->copy_script_install, + 'entry' => $model->copy_script_entry, + 'container' => $model->copy_script_container, + 'extends' => $model->copy_script_from, + ], + created_at: static::formatTimestamp($model->created_at), + updated_at: static::formatTimestamp($model->updated_at), + ); + } + + public static function includes(): array + { + return [ + 'servers' => function (Egg $egg, IncludeContext $context): array { + if (!$context->allowsAdmin(Server::RESOURCE_NAME)) { + return $context->null(); + } + + $egg->loadMissing('servers'); + + return $context->collection($egg->getRelation('servers'), ServerData::class); + }, + 'variables' => function (Egg $egg, IncludeContext $context): array { + if (!$context->allowsAdmin(Egg::RESOURCE_NAME)) { + return $context->null(); + } + + $egg->loadMissing('variables'); + + return $context->collection($egg->getRelation('variables'), EggVariableData::class, EggVariable::RESOURCE_NAME); + }, + ]; + } +} diff --git a/app/Data/Api/Application/EggVariableData.php b/app/Data/Api/Application/EggVariableData.php new file mode 100644 index 0000000000..116dc3886c --- /dev/null +++ b/app/Data/Api/Application/EggVariableData.php @@ -0,0 +1,33 @@ + $attributes + */ + public function __construct(public array $attributes) {} + + public static function getResourceName(): string + { + return Egg::RESOURCE_NAME; + } + + public static function fromModel(EggVariable $model): static + { + return new static($model->toArray()); + } + + /** + * @return array + */ + public function toArray(): array + { + return $this->attributes; + } +} diff --git a/app/Data/Api/Application/MountData.php b/app/Data/Api/Application/MountData.php new file mode 100644 index 0000000000..7862adbb1f --- /dev/null +++ b/app/Data/Api/Application/MountData.php @@ -0,0 +1,72 @@ + $attributes + */ + public function __construct(public array $attributes) {} + + public static function getResourceName(): string + { + return Mount::RESOURCE_NAME; + } + + public static function fromModel(Mount $model): static + { + return new static($model->toArray()); + } + + /** + * @return array + */ + public function toArray(): array + { + return $this->attributes; + } + + public static function includes(): array + { + return [ + 'eggs' => function (Mount $mount, IncludeContext $context): array { + if (!$context->allowsAdmin(Egg::RESOURCE_NAME)) { + return $context->null(); + } + + $mount->loadMissing('eggs'); + + return $context->collection($mount->getRelation('eggs'), EggData::class); + }, + 'nodes' => function (Mount $mount, IncludeContext $context): array { + if (!$context->allowsAdmin(Node::RESOURCE_NAME)) { + return $context->null(); + } + + $mount->loadMissing('nodes'); + + return $context->collection($mount->getRelation('nodes'), NodeData::class); + }, + 'servers' => function (Mount $mount, IncludeContext $context): array { + if (!$context->allowsAdmin(Server::RESOURCE_NAME)) { + return $context->null(); + } + + $mount->loadMissing('servers'); + + return $context->collection($mount->getRelation('servers'), ServerData::class); + }, + ]; + } +} diff --git a/app/Data/Api/Application/NodeData.php b/app/Data/Api/Application/NodeData.php new file mode 100644 index 0000000000..2aa5c5f9f6 --- /dev/null +++ b/app/Data/Api/Application/NodeData.php @@ -0,0 +1,77 @@ + $attributes + */ + public function __construct(public array $attributes) {} + + public static function getResourceName(): string + { + return Node::RESOURCE_NAME; + } + + public static function fromModel(Node $model): static + { + $attributes = collect($model->toArray()) + ->mapWithKeys(fn ($value, $key) => [snake_case($key) => $value]) + ->toArray(); + + $attributes[$model->getUpdatedAtColumn()] = static::formatTimestamp($model->updated_at); + $attributes[$model->getCreatedAtColumn()] = static::formatTimestamp($model->created_at); + + $resources = $model->servers()->select(['memory', 'disk', 'cpu'])->get(); + + $attributes['allocated_resources'] = [ + 'memory' => $resources->sum('memory'), + 'disk' => $resources->sum('disk'), + 'cpu' => $resources->sum('cpu'), + ]; + + return new static($attributes); + } + + /** + * @return array + */ + public function toArray(): array + { + return $this->attributes; + } + + public static function includes(): array + { + return [ + 'allocations' => function (Node $node, IncludeContext $context): array { + if (!$context->allowsAdmin(Allocation::RESOURCE_NAME)) { + return $context->null(); + } + + $node->loadMissing('allocations'); + + return $context->collection($node->getRelation('allocations'), AllocationData::class); + }, + 'servers' => function (Node $node, IncludeContext $context): array { + if (!$context->allowsAdmin(Server::RESOURCE_NAME)) { + return $context->null(); + } + + $node->loadMissing('servers'); + + return $context->collection($node->getRelation('servers'), ServerData::class); + }, + ]; + } +} diff --git a/app/Data/Api/Application/PluginData.php b/app/Data/Api/Application/PluginData.php new file mode 100644 index 0000000000..e6d17a7dee --- /dev/null +++ b/app/Data/Api/Application/PluginData.php @@ -0,0 +1,62 @@ + $meta + */ + public function __construct( + public string $id, + public string $name, + public string $author, + public string $version, + public ?string $description, + public mixed $category, + public ?string $url, + public ?string $update_url, + public string $namespace, + public string $class, + public ?array $panels, + public ?string $panel_version, + public mixed $composer_packages, + public array $meta, + ) {} + + public static function getResourceName(): string + { + return Plugin::RESOURCE_NAME; + } + + public static function fromModel(Plugin $model): static + { + return new static( + id: $model->id, + name: $model->name, + author: $model->author, + version: $model->version, + description: $model->description, + category: $model->category, + url: $model->url, + update_url: $model->update_url, + namespace: $model->namespace, + class: $model->class, + panels: $model->panels ? explode(',', $model->panels) : null, + panel_version: $model->panel_version, + composer_packages: $model->composer_packages ? json_decode($model->composer_packages, true, 512, JSON_THROW_ON_ERROR) : null, + meta: [ + 'status' => $model->status, + 'status_message' => $model->status_message, + 'load_order' => $model->load_order, + 'is_compatible' => $model->isCompatible(), + 'update_available' => $model->isUpdateAvailable(), + 'can_enable' => $model->canEnable(), + 'can_disable' => $model->canDisable(), + ], + ); + } +} diff --git a/app/Data/Api/Application/RoleData.php b/app/Data/Api/Application/RoleData.php new file mode 100644 index 0000000000..8f868e8d60 --- /dev/null +++ b/app/Data/Api/Application/RoleData.php @@ -0,0 +1,46 @@ +id, + name: $model->name, + created_at: $model->created_at->toAtomString(), + updated_at: $model->updated_at->toAtomString(), + ); + } + + public static function includes(): array + { + return [ + 'permissions' => function (Role $role, IncludeContext $context): array { + $role->loadMissing('permissions'); + + return $context->collection($role->getRelation('permissions'), RolePermissionData::class); + }, + ]; + } +} diff --git a/app/Data/Api/Application/RolePermissionData.php b/app/Data/Api/Application/RolePermissionData.php new file mode 100644 index 0000000000..d2f26301c7 --- /dev/null +++ b/app/Data/Api/Application/RolePermissionData.php @@ -0,0 +1,29 @@ +name, + created_at: $model->created_at->toAtomString(), + updated_at: $model->updated_at->toAtomString(), + ); + } +} diff --git a/app/Data/Api/Application/ServerData.php b/app/Data/Api/Application/ServerData.php new file mode 100644 index 0000000000..dec7b65452 --- /dev/null +++ b/app/Data/Api/Application/ServerData.php @@ -0,0 +1,170 @@ + $limits + * @param array $feature_limits + * @param array $container + */ + public function __construct( + public int $id, + public ?string $external_id, + public string $uuid, + public string $identifier, + public string $name, + public ?string $description, + public mixed $status, + public bool $suspended, + public array $limits, + public array $feature_limits, + public int $user, + public int $node, + public ?int $allocation, + public int $egg, + public array $container, + public string $updated_at, + public string $created_at, + ) {} + + public static function getResourceName(): string + { + return Server::RESOURCE_NAME; + } + + public static function fromModel(Server $model, EnvironmentService $environmentService): static + { + return new static( + id: $model->getKey(), + external_id: $model->external_id, + uuid: $model->uuid, + identifier: $model->uuid_short, + name: $model->name, + description: $model->description, + status: $model->status, + // This field is deprecated, please use "status". + suspended: $model->isSuspended(), + limits: [ + 'memory' => $model->memory, + 'swap' => $model->swap, + 'disk' => $model->disk, + 'io' => $model->io, + 'cpu' => $model->cpu, + 'threads' => $model->threads, + // This field is deprecated, please use "oom_killer". + 'oom_disabled' => !$model->oom_killer, + 'oom_killer' => $model->oom_killer, + ], + feature_limits: [ + 'databases' => $model->database_limit, + 'allocations' => $model->allocation_limit, + 'backups' => $model->backup_limit, + ], + user: $model->owner_id, + node: $model->node_id, + allocation: $model->allocation_id, + egg: $model->egg_id, + container: [ + 'startup_command' => $model->startup, + 'image' => $model->image, + // This field is deprecated, please use "status". + 'installed' => $model->isInstalled() ? 1 : 0, + 'environment' => $environmentService->handle($model), + ], + updated_at: static::formatTimestamp($model->updated_at), + created_at: static::formatTimestamp($model->created_at), + ); + } + + public static function includes(): array + { + return [ + 'allocations' => function (Server $server, IncludeContext $context): array { + if (!$context->allowsAdmin(Allocation::RESOURCE_NAME)) { + return $context->null(); + } + + $server->loadMissing('allocations'); + + return $context->collection($server->getRelation('allocations'), AllocationData::class); + }, + 'user' => function (Server $server, IncludeContext $context): array { + if (!$context->allowsAdmin(User::RESOURCE_NAME)) { + return $context->null(); + } + + $server->loadMissing('user'); + + return $context->item($server->getRelation('user'), UserData::class); + }, + 'subusers' => function (Server $server, IncludeContext $context): array { + if (!$context->allowsAdmin(User::RESOURCE_NAME)) { + return $context->null(); + } + + $server->loadMissing('subusers'); + + return $context->collection($server->getRelation('subusers'), SubuserData::class, 'subuser'); + }, + 'egg' => function (Server $server, IncludeContext $context): array { + if (!$context->allowsAdmin(Egg::RESOURCE_NAME)) { + return $context->null(); + } + + $server->loadMissing('egg'); + + return $context->item($server->getRelation('egg'), EggData::class); + }, + 'variables' => function (Server $server, IncludeContext $context): array { + if (!$context->allowsAdmin(Server::RESOURCE_NAME)) { + return $context->null(); + } + + $server->loadMissing('variables'); + + return $context->collection($server->getRelation('variables'), ServerVariableData::class, 'server_variable'); + }, + 'node' => function (Server $server, IncludeContext $context): array { + if (!$context->allowsAdmin(Node::RESOURCE_NAME)) { + return $context->null(); + } + + $server->loadMissing('node'); + + return $context->item($server->getRelation('node'), NodeData::class); + }, + 'databases' => function (Server $server, IncludeContext $context): array { + if (!$context->allowsAdmin(Database::RESOURCE_NAME)) { + return $context->null(); + } + + $server->loadMissing('databases'); + + return $context->collection($server->getRelation('databases'), ServerDatabaseData::class, 'databases'); + }, + ]; + } +} diff --git a/app/Data/Api/Application/ServerDatabaseData.php b/app/Data/Api/Application/ServerDatabaseData.php new file mode 100644 index 0000000000..d229e99d97 --- /dev/null +++ b/app/Data/Api/Application/ServerDatabaseData.php @@ -0,0 +1,66 @@ +id, + server: $model->server_id, + host: $model->database_host_id, + database: $model->database, + username: $model->username, + remote: $model->remote, + max_connections: $model->max_connections, + created_at: $model->created_at->toAtomString(), + updated_at: $model->updated_at->toAtomString(), + ); + } + + public static function includes(): array + { + return [ + 'password' => function (Database $database, IncludeContext $context): array { + return $context->rawItem([ + 'password' => $database->password, + ], 'database_password'); + }, + 'host' => function (Database $database, IncludeContext $context): array { + if (!$context->allowsAdmin(DatabaseHost::RESOURCE_NAME)) { + return $context->null(); + } + + $database->loadMissing('host'); + + return $context->item($database->getRelation('host'), DatabaseHostData::class); + }, + ]; + } +} diff --git a/app/Data/Api/Application/ServerVariableData.php b/app/Data/Api/Application/ServerVariableData.php new file mode 100644 index 0000000000..4ee057503d --- /dev/null +++ b/app/Data/Api/Application/ServerVariableData.php @@ -0,0 +1,52 @@ + $attributes + */ + public function __construct(public array $attributes) {} + + public static function getResourceName(): string + { + return EggVariable::RESOURCE_NAME; + } + + public static function fromModel(EggVariable $model): static + { + return new static($model->toArray()); + } + + /** + * @return array + */ + public function toArray(): array + { + return $this->attributes; + } + + public static function includes(): array + { + return [ + 'parent' => function (EggVariable $variable, IncludeContext $context): array { + if (!$context->allowsAdmin(Egg::RESOURCE_NAME)) { + return $context->null(); + } + + $variable->loadMissing('variable'); + + return $context->item($variable->getRelation('variable'), EggVariableData::class, 'variable'); + }, + ]; + } +} diff --git a/app/Data/Api/Application/SubuserData.php b/app/Data/Api/Application/SubuserData.php new file mode 100644 index 0000000000..5d169e17e1 --- /dev/null +++ b/app/Data/Api/Application/SubuserData.php @@ -0,0 +1,65 @@ +id, + user_id: $model->user_id, + server_id: $model->server_id, + permissions: $model->permissions, + created_at: static::formatTimestamp($model->created_at), + updated_at: static::formatTimestamp($model->updated_at), + ); + } + + public static function includes(): array + { + return [ + 'user' => function (Subuser $subuser, IncludeContext $context): array { + if (!$context->allowsAdmin(User::RESOURCE_NAME)) { + return $context->null(); + } + + $subuser->loadMissing('user'); + + return $context->item($subuser->getRelation('user'), UserData::class); + }, + 'server' => function (Subuser $subuser, IncludeContext $context): array { + if (!$context->allowsAdmin(Server::RESOURCE_NAME)) { + return $context->null(); + } + + $subuser->loadMissing('server'); + + return $context->item($subuser->getRelation('server'), ServerData::class); + }, + ]; + } +} diff --git a/app/Data/Api/Application/UserData.php b/app/Data/Api/Application/UserData.php new file mode 100644 index 0000000000..fc08c0cd71 --- /dev/null +++ b/app/Data/Api/Application/UserData.php @@ -0,0 +1,83 @@ +id, + external_id: $model->external_id, + is_managed_externally: $model->is_managed_externally, + uuid: $model->uuid, + username: $model->username, + email: $model->email, + language: $model->language, + root_admin: $model->isRootAdmin(), + twoFactorEnabled: filled($model->mfa_app_secret), + twoFactorLegacy: filled($model->mfa_app_secret), // deprecated, use "2fa_enabled" + created_at: static::formatTimestamp($model->created_at), + updated_at: static::formatTimestamp($model->updated_at), + ); + } + + public static function includes(): array + { + return [ + 'servers' => function (User $user, IncludeContext $context): array { + if (!$context->allowsAdmin(Server::RESOURCE_NAME)) { + return $context->null(); + } + + $user->loadMissing('servers'); + + return $context->collection($user->getRelation('servers'), ServerData::class); + }, + 'roles' => function (User $user, IncludeContext $context): array { + if (!$context->allowsAdmin(Role::RESOURCE_NAME)) { + return $context->null(); + } + + $user->loadMissing('roles'); + + return $context->collection($user->getRelation('roles'), RoleData::class); + }, + ]; + } +} diff --git a/app/Data/Api/Client/ActivityLogData.php b/app/Data/Api/Client/ActivityLogData.php new file mode 100644 index 0000000000..b2cefa8938 --- /dev/null +++ b/app/Data/Api/Client/ActivityLogData.php @@ -0,0 +1,68 @@ + $properties + */ + public function __construct( + public string $id, + public string $event, + public bool $is_api, + public ?string $ip, + public ?string $description, + public array $properties, + public bool $has_additional_metadata, + public string $timestamp, + ) {} + + public static function getResourceName(): string + { + return ActivityLog::RESOURCE_NAME; + } + + public static function fromModel(ActivityLog $model, Request $request): static + { + // Whether the user can view the IP address in the output, either because they are + // the actor that performed the action or because they are an administrator. + $canViewIp = $model->actor?->is($request->user()) || $request->user()->can('seeIps activityLog'); + + return new static( + // This is not for security, it is only to provide a unique identifier to + // the front-end for each entry to improve rendering performance since there + // is nothing else sufficiently unique to key off at this point. + id: sha1((string) $model->id), + event: $model->event, + is_api: !is_null($model->api_key_id), + ip: $canViewIp ? $model->ip : null, + description: $model->description, + properties: $model->wrapProperties(), + has_additional_metadata: $model->hasAdditionalMetadata(), + timestamp: $model->timestamp->toAtomString(), + ); + } + + public static function includes(): array + { + return [ + 'actor' => function (ActivityLog $model, IncludeContext $context): array { + if (!$model->actor instanceof User) { + return $context->null(); + } + + return $context->item($model->actor, UserData::class); + }, + ]; + } +} diff --git a/app/Data/Api/Client/AllocationData.php b/app/Data/Api/Client/AllocationData.php new file mode 100644 index 0000000000..76e16da095 --- /dev/null +++ b/app/Data/Api/Client/AllocationData.php @@ -0,0 +1,35 @@ +id, + ip: $model->ip, + ip_alias: $model->ip_alias, + port: $model->port, + notes: $model->notes, + is_default: $model->server->allocation_id === $model->id, + ); + } +} diff --git a/app/Data/Api/Client/ApiKeyData.php b/app/Data/Api/Client/ApiKeyData.php new file mode 100644 index 0000000000..5fc0b0edeb --- /dev/null +++ b/app/Data/Api/Client/ApiKeyData.php @@ -0,0 +1,36 @@ +identifier, + description: $model->memo, + allowed_ips: $model->allowed_ips, + last_used_at: $model->last_used_at ? $model->last_used_at->toAtomString() : null, + created_at: $model->created_at->toAtomString(), + ); + } +} diff --git a/app/Data/Api/Client/BackupData.php b/app/Data/Api/Client/BackupData.php new file mode 100644 index 0000000000..34ec9cfc35 --- /dev/null +++ b/app/Data/Api/Client/BackupData.php @@ -0,0 +1,46 @@ +uuid, + is_successful: $model->is_successful, + is_locked: $model->is_locked, + is_scheduled: $model->is_scheduled, + name: $model->name, + ignored_files: $model->ignored_files, + checksum: $model->checksum, + bytes: $model->bytes, + created_at: $model->created_at->toAtomString(), + completed_at: $model->completed_at ? $model->completed_at->toAtomString() : null, + ); + } +} diff --git a/app/Data/Api/Client/DatabaseData.php b/app/Data/Api/Client/DatabaseData.php new file mode 100644 index 0000000000..aec4ff4a31 --- /dev/null +++ b/app/Data/Api/Client/DatabaseData.php @@ -0,0 +1,63 @@ +loadMissing('host'); + + return new static( + id: $model->id, + host: [ + 'address' => $model->getRelation('host')->host, + 'port' => $model->getRelation('host')->port, + ], + name: $model->database, + username: $model->username, + connections_from: $model->remote, + max_connections: $model->max_connections, + ); + } + + public static function includes(): array + { + return [ + 'password' => function (Database $database, IncludeContext $context): array { + if (!$context->allowsClient(SubuserPermission::DatabaseViewPassword->value, $database->server)) { + return $context->null(); + } + + return $context->rawItem([ + 'password' => $database->password, + ], 'database_password'); + }, + ]; + } +} diff --git a/app/Data/Api/Client/EggData.php b/app/Data/Api/Client/EggData.php new file mode 100644 index 0000000000..60a3ad5ce9 --- /dev/null +++ b/app/Data/Api/Client/EggData.php @@ -0,0 +1,27 @@ +uuid, + name: $model->name, + ); + } +} diff --git a/app/Data/Api/Client/EggVariableData.php b/app/Data/Api/Client/EggVariableData.php new file mode 100644 index 0000000000..f2e70b7acb --- /dev/null +++ b/app/Data/Api/Client/EggVariableData.php @@ -0,0 +1,43 @@ +user_viewable, new BadMethodCallException('Cannot transform a hidden egg variable in a client transformer.')); + + return new static( + name: $model->name, + description: $model->description, + env_variable: $model->env_variable, + default_value: $model->default_value, + server_value: $model->server_value, + is_editable: $model->user_editable, + rules: implode('|', $model->rules), + ); + } +} diff --git a/app/Data/Api/Client/FileObjectData.php b/app/Data/Api/Client/FileObjectData.php new file mode 100644 index 0000000000..6b4d82fe5f --- /dev/null +++ b/app/Data/Api/Client/FileObjectData.php @@ -0,0 +1,45 @@ + $model + */ + public static function fromModel(array $model): static + { + return new static( + name: Arr::get($model, 'name'), + mode: Arr::get($model, 'mode'), + mode_bits: Arr::get($model, 'mode_bits'), + size: Arr::get($model, 'size'), + is_file: Arr::get($model, 'file', true), + is_symlink: Arr::get($model, 'symlink', false), + mimetype: Arr::get($model, 'mime', 'application/octet-stream'), + created_at: Carbon::parse(Arr::get($model, 'created', ''))->toAtomString(), + modified_at: Carbon::parse(Arr::get($model, 'modified', ''))->toAtomString(), + ); + } +} diff --git a/app/Data/Api/Client/ScheduleData.php b/app/Data/Api/Client/ScheduleData.php new file mode 100644 index 0000000000..c93b6efe2e --- /dev/null +++ b/app/Data/Api/Client/ScheduleData.php @@ -0,0 +1,68 @@ +id, + name: $model->name, + cron: [ + 'day_of_week' => $model->cron_day_of_week, + 'day_of_month' => $model->cron_day_of_month, + 'month' => $model->cron_month, + 'hour' => $model->cron_hour, + 'minute' => $model->cron_minute, + ], + is_active: $model->is_active, + is_processing: $model->is_processing, + only_when_online: $model->only_when_online, + last_run_at: $model->last_run_at?->toAtomString(), + next_run_at: $model->next_run_at?->toAtomString(), + created_at: $model->created_at->toAtomString(), + updated_at: $model->updated_at->toAtomString(), + ); + } + + public static function includes(): array + { + return [ + 'tasks' => function (Schedule $model, IncludeContext $context): array { + return $context->collection($model->tasks, TaskData::class); + }, + ]; + } +} diff --git a/app/Data/Api/Client/ServerData.php b/app/Data/Api/Client/ServerData.php new file mode 100644 index 0000000000..fb2a9ab6ea --- /dev/null +++ b/app/Data/Api/Client/ServerData.php @@ -0,0 +1,136 @@ + $limits + * @param string[]|null $egg_features + * @param array $feature_limits + */ + public function __construct( + public bool $server_owner, + public string $identifier, + public int $internal_id, + public string $uuid, + public string $name, + public string $node, + public bool $is_node_under_maintenance, + public array $sftp_details, + public string|null|Optional $description, + public array $limits, + public string $invocation, + public string $docker_image, + public ?array $egg_features, + public array $feature_limits, + public ?ServerState $status, + public bool $is_suspended, + public bool $is_installing, + public bool $is_transferring, + ) {} + + public static function getResourceName(): string + { + return Server::RESOURCE_NAME; + } + + public static function fromModel(Server $model, StartupCommandService $service, Request $request): static + { + $user = $request->user(); + + return new static( + server_owner: $user->id === $model->owner_id, + identifier: $model->uuid_short, + internal_id: $model->id, + uuid: $model->uuid, + name: $model->name, + node: $model->node->name, + is_node_under_maintenance: $model->node->isUnderMaintenance(), + sftp_details: [ + 'ip' => $model->node->fqdn, + 'alias' => $model->node->daemon_sftp_alias, + 'port' => $model->node->daemon_sftp, + ], + description: config('panel.editable_server_descriptions') ? $model->description : Optional::create(), + limits: [ + 'memory' => $model->memory, + 'swap' => $model->swap, + 'disk' => $model->disk, + 'io' => $model->io, + 'cpu' => $model->cpu, + 'threads' => $model->threads, + // This field is deprecated, please use "oom_killer". + 'oom_disabled' => !$model->oom_killer, + 'oom_killer' => $model->oom_killer, + ], + invocation: $service->handle($model, hideAllValues: !$user->can(SubuserPermission::StartupRead, $model)), + docker_image: $model->image, + egg_features: $model->egg->inherit_features, + feature_limits: [ + 'databases' => $model->database_limit, + 'allocations' => $model->allocation_limit, + 'backups' => $model->backup_limit, + ], + status: $model->status, + // This field is deprecated, please use "status". + is_suspended: $model->isSuspended(), + // This field is deprecated, please use "status". + is_installing: !$model->isInstalled(), + is_transferring: !is_null($model->transfer), + ); + } + + public static function includes(): array + { + return [ + // If the user doesn't have read permissions for the allocations we only return + // the primary server allocation, and any notes associated with it are hidden. + // This avoids permission regression without hiding information the frontend + // needs to make sense when browsing or searching results. + 'allocations' => function (Server $server, IncludeContext $context): array { + if (!$context->allowsClient(SubuserPermission::AllocationRead->value, $server)) { + $primary = clone $server->allocation; + $primary->notes = null; + + return $context->collection([$primary], AllocationData::class); + } + + return $context->collection($server->allocations, AllocationData::class); + }, + 'variables' => function (Server $server, IncludeContext $context): array { + if (!$context->allowsClient(SubuserPermission::StartupRead->value, $server)) { + return $context->null(); + } + + return $context->collection($server->variables->where('user_viewable', true), EggVariableData::class); + }, + 'egg' => function (Server $server, IncludeContext $context): array { + return $context->item($server->egg, EggData::class); + }, + 'subusers' => function (Server $server, IncludeContext $context): array { + if (!$context->allowsClient(SubuserPermission::UserRead->value, $server)) { + return $context->null(); + } + + return $context->collection($server->subusers, SubuserData::class); + }, + ]; + } +} diff --git a/app/Data/Api/Client/StatsData.php b/app/Data/Api/Client/StatsData.php new file mode 100644 index 0000000000..bc237e7535 --- /dev/null +++ b/app/Data/Api/Client/StatsData.php @@ -0,0 +1,42 @@ + $resources + */ + public function __construct( + public string $current_state, + public bool $is_suspended, + public array $resources, + ) {} + + public static function getResourceName(): string + { + return 'stats'; + } + + /** + * @param array $model + */ + public static function fromModel(array $model): static + { + return new static( + current_state: Arr::get($model, 'state', 'stopped'), + is_suspended: Arr::get($model, 'is_suspended', false), + resources: [ + 'memory_bytes' => Arr::get($model, 'utilization.memory_bytes', 0), + 'cpu_absolute' => Arr::get($model, 'utilization.cpu_absolute', 0), + 'disk_bytes' => Arr::get($model, 'utilization.disk_bytes', 0), + 'network_rx_bytes' => Arr::get($model, 'utilization.network.rx_bytes', 0), + 'network_tx_bytes' => Arr::get($model, 'utilization.network.tx_bytes', 0), + 'uptime' => Arr::get($model, 'utilization.uptime', 0), + ], + ); + } +} diff --git a/app/Data/Api/Client/SubuserData.php b/app/Data/Api/Client/SubuserData.php new file mode 100644 index 0000000000..9da02314c9 --- /dev/null +++ b/app/Data/Api/Client/SubuserData.php @@ -0,0 +1,52 @@ +user); + + return new static( + uuid: $user->uuid, + username: $user->username, + email: $user->email, + language: $user->language, + image: $user->image, + admin: $user->admin, + root_admin: $user->root_admin, + two_factor_enabled: $user->two_factor_enabled, + created_at: $user->created_at, + updated_at: $user->updated_at, + permissions: $model->permissions, + ); + } +} diff --git a/app/Data/Api/Client/TaskData.php b/app/Data/Api/Client/TaskData.php new file mode 100644 index 0000000000..44c3bdfacd --- /dev/null +++ b/app/Data/Api/Client/TaskData.php @@ -0,0 +1,41 @@ +id, + sequence_id: $model->sequence_id, + action: $model->action, + payload: $model->payload, + time_offset: $model->time_offset, + is_queued: $model->is_queued, + continue_on_failure: $model->continue_on_failure, + created_at: $model->created_at->toAtomString(), + updated_at: $model->updated_at->toAtomString(), + ); + } +} diff --git a/app/Data/Api/Client/UserData.php b/app/Data/Api/Client/UserData.php new file mode 100644 index 0000000000..f5725ec026 --- /dev/null +++ b/app/Data/Api/Client/UserData.php @@ -0,0 +1,46 @@ +uuid, + username: $model->username, + email: $model->email, + language: $model->language, + image: 'https://gravatar.com/avatar/' . md5(Str::lower($model->email)), // deprecated + admin: $model->isRootAdmin(), // deprecated, use "root_admin" + root_admin: $model->isRootAdmin(), + two_factor_enabled: filled($model->mfa_app_secret), + created_at: static::formatTimestamp($model->created_at), + updated_at: static::formatTimestamp($model->updated_at), + ); + } +} diff --git a/app/Data/Api/Client/UserSSHKeyData.php b/app/Data/Api/Client/UserSSHKeyData.php new file mode 100644 index 0000000000..35fc0c4fc9 --- /dev/null +++ b/app/Data/Api/Client/UserSSHKeyData.php @@ -0,0 +1,31 @@ +name, + fingerprint: $model->fingerprint, + public_key: $model->public_key, + created_at: $model->created_at->toAtomString(), + ); + } +} diff --git a/app/Data/Api/Envelope.php b/app/Data/Api/Envelope.php new file mode 100644 index 0000000000..fa4fb42aaf --- /dev/null +++ b/app/Data/Api/Envelope.php @@ -0,0 +1,89 @@ + $dataClass + * @return array + */ + public static function item(mixed $model, string $dataClass, IncludeContext $context, ?string $resourceKey = null): array + { + if (is_null($model)) { + return $context->null(); + } + + return [ + 'object' => $resourceKey ?? $dataClass::getResourceName(), + 'attributes' => self::attributes($model, $dataClass, $context), + ]; + } + + /** + * @param iterable $models + * @param class-string $dataClass + * @return array{object: string, data: array} + */ + public static function collection(iterable $models, string $dataClass, IncludeContext $context, ?string $resourceKey = null): array + { + $key = $resourceKey ?? $dataClass::getResourceName(); + + $data = []; + foreach ($models as $model) { + $data[] = [ + 'object' => $key, + 'attributes' => self::attributes($model, $dataClass, $context), + ]; + } + + return [ + 'object' => 'list', + 'data' => $data, + ]; + } + + /** + * Hydrate the resource through the container so fromModel can take extra typed + * dependencies, then attach default includes and any requested includes that the + * resource declares available. + * + * @param class-string $dataClass + * @return array + */ + private static function attributes(mixed $model, string $dataClass, IncludeContext $context): array + { + $resource = Container::getInstance()->call([$dataClass, 'fromModel'], ['model' => $model]); + + $attributes = $resource->toArray(); + + $names = $dataClass::$defaultIncludes; + foreach ($dataClass::$availableIncludes as $name) { + if (!in_array($name, $names, true) && $context->isRequested($name)) { + $names[] = $name; + } + } + + $relationships = []; + $resolvers = $names === [] ? [] : $dataClass::includes(); + foreach ($names as $name) { + if (isset($resolvers[$name])) { + $relationships[$name] = $resolvers[$name]($model, $context->descend($name)); + } + } + + if ($relationships !== []) { + $attributes['relationships'] = $relationships; + } + + return $attributes; + } +} diff --git a/app/Data/Api/IncludeContext.php b/app/Data/Api/IncludeContext.php new file mode 100644 index 0000000000..a473617237 --- /dev/null +++ b/app/Data/Api/IncludeContext.php @@ -0,0 +1,114 @@ +request->user(); + } + + /** + * Whether the API key on the request may touch the given resource, mirroring + * what BaseTransformer::authorize did for application API includes. + */ + public function allowsAdmin(string $resource): bool + { + $token = $this->user()?->currentAccessToken(); + if (!$token instanceof ApiKey || !in_array($token->key_type, [ApiKey::TYPE_ACCOUNT, ApiKey::TYPE_APPLICATION])) { + return false; + } + + if ($token->key_type === ApiKey::TYPE_ACCOUNT) { + return $this->user()->isRootAdmin(); + } + + return AdminAcl::check($token, $resource); + } + + /** + * Whether the requesting user holds the given ability on the server, mirroring + * BaseClientTransformer::authorize for client API includes. + */ + public function allowsClient(string $ability, ?Server $server = null): bool + { + return (bool) $this->user()?->can($ability, [$server]); + } + + public function isRequested(string $include): bool + { + return in_array(implode('.', [...$this->path, $include]), $this->requestedIncludes, true); + } + + public function descend(string $include): self + { + return new self($this->request, $this->requestedIncludes, [...$this->path, $include]); + } + + /** + * @param class-string $dataClass + * @return array + */ + public function item(mixed $model, string $dataClass, ?string $resourceKey = null): array + { + return Envelope::item($model, $dataClass, $this, $resourceKey); + } + + /** + * @param iterable $models + * @param class-string $dataClass + * @return array + */ + public function collection(iterable $models, string $dataClass, ?string $resourceKey = null): array + { + return Envelope::collection($models, $dataClass, $this, $resourceKey); + } + + /** + * Wrap already transformed attributes, for includes that have no backing model + * like the database password. + * + * @param array $attributes + * @return array + */ + public function rawItem(array $attributes, string $resourceKey): array + { + return [ + 'object' => $resourceKey, + 'attributes' => $attributes, + ]; + } + + /** + * @return array{object: string, attributes: null} + */ + public function null(): array + { + return [ + 'object' => 'null_resource', + 'attributes' => null, + ]; + } +} diff --git a/app/Data/Api/PanelResponse.php b/app/Data/Api/PanelResponse.php new file mode 100644 index 0000000000..10f327ec5d --- /dev/null +++ b/app/Data/Api/PanelResponse.php @@ -0,0 +1,183 @@ +|null */ + protected ?string $dataClass = null; + + /** @var string[] */ + protected array $rawIncludes = []; + + protected int $recursionLimit = 10; + + /** @var array */ + protected array $meta = []; + + public function __construct(protected Request $request) {} + + public function item(mixed $data): static + { + $this->data = $data; + $this->isCollection = false; + + return $this; + } + + public function collection(mixed $data): static + { + $this->data = $data; + $this->isCollection = true; + + return $this; + } + + /** + * @param class-string $dataClass + */ + public function transformWith(string $dataClass): static + { + $this->dataClass = $dataClass; + + return $this; + } + + /** + * @param string[]|string $includes + */ + public function parseIncludes(array|string $includes): static + { + $includes = is_string($includes) ? explode(',', $includes) : $includes; + + $this->rawIncludes = array_merge($this->rawIncludes, $includes); + + return $this; + } + + public function limitRecursion(int $limit): static + { + $this->recursionLimit = $limit; + + return $this; + } + + /** + * Union semantics matching Fractalistic: a key set by an earlier call is not + * overwritten by a later one. + * + * @param array ...$blocks + */ + public function addMeta(array ...$blocks): static + { + foreach ($blocks as $block) { + $this->meta += $block; + } + + return $this; + } + + /** + * @return array + */ + public function toArray(): array + { + $context = new IncludeContext($this->request, $this->requestedIncludes()); + + $meta = $this->meta; + + if ($this->data instanceof LengthAwarePaginator) { + $envelope = Envelope::collection($this->data, $this->dataClass, $context); + $meta['pagination'] = $this->pagination($this->data); + } elseif (is_null($this->data)) { + $envelope = $context->null(); + } elseif ($this->isCollection) { + $envelope = Envelope::collection($this->data, $this->dataClass, $context); + } else { + $envelope = Envelope::item($this->data, $this->dataClass, $context); + } + + if ($meta !== []) { + $envelope['meta'] = $meta; + } + + return $envelope; + } + + public function respond(int $status = 200): JsonResponse + { + return new JsonResponse($this->toArray(), $status); + } + + /** + * Normalize the raw include names the way League parsed them: strip parameter + * suffixes, trim each dot path to the recursion limit, and imply every parent + * of a nested path. + * + * @return string[] + */ + protected function requestedIncludes(): array + { + $paths = []; + + foreach ($this->rawIncludes as $include) { + $name = trim(explode(':', $include, 2)[0]); + if ($name === '') { + continue; + } + + $segments = array_slice(explode('.', $name), 0, $this->recursionLimit); + for ($depth = 1; $depth <= count($segments); $depth++) { + $paths[] = implode('.', array_slice($segments, 0, $depth)); + } + } + + return array_values(array_unique($paths)); + } + + /** + * The exact meta.pagination block League's ArraySerializer produced, including + * the empty object when there are no previous or next links. + * + * @return array + */ + protected function pagination(LengthAwarePaginator $paginator): array + { + $pagination = [ + 'total' => $paginator->total(), + 'count' => count($paginator->items()), + 'per_page' => $paginator->perPage(), + 'current_page' => $paginator->currentPage(), + 'total_pages' => $paginator->lastPage(), + ]; + + $links = []; + if ($paginator->currentPage() > 1) { + $links['previous'] = $paginator->url($paginator->currentPage() - 1); + } + if ($paginator->currentPage() < $paginator->lastPage()) { + $links['next'] = $paginator->url($paginator->currentPage() + 1); + } + + $pagination['links'] = $links === [] ? (object) [] : $links; + + return $pagination; + } +} diff --git a/app/Http/Controllers/Api/Application/ApplicationApiController.php b/app/Http/Controllers/Api/Application/ApplicationApiController.php index 9caac19822..e435f13a3e 100644 --- a/app/Http/Controllers/Api/Application/ApplicationApiController.php +++ b/app/Http/Controllers/Api/Application/ApplicationApiController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Application; +use App\Data\Api\PanelResponse; use App\Extensions\Spatie\Fractalistic\Fractal; use App\Http\Controllers\Controller; use App\Transformers\Api\Application\BaseTransformer; @@ -17,6 +18,8 @@ abstract class ApplicationApiController extends Controller protected Fractal $fractal; + protected PanelResponse $response; + /** * ApplicationApiController constructor. */ @@ -34,15 +37,19 @@ public function __construct() $this->fractal->parseIncludes($includes); $this->fractal->limitRecursion(2); + + $this->response->parseIncludes($includes); + $this->response->limitRecursion(2); } /** * Perform dependency injection of certain classes needed for core functionality * without littering the constructors of classes that extend this abstract. */ - public function loadDependencies(Fractal $fractal, Request $request): void + public function loadDependencies(Fractal $fractal, PanelResponse $response, Request $request): void { $this->fractal = $fractal; + $this->response = $response; $this->request = $request; } diff --git a/app/Http/Controllers/Api/Application/DatabaseHosts/DatabaseHostController.php b/app/Http/Controllers/Api/Application/DatabaseHosts/DatabaseHostController.php index 08e01a3545..d682b5bb03 100644 --- a/app/Http/Controllers/Api/Application/DatabaseHosts/DatabaseHostController.php +++ b/app/Http/Controllers/Api/Application/DatabaseHosts/DatabaseHostController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Application\DatabaseHosts; +use App\Data\Api\Application\DatabaseHostData; use App\Http\Controllers\Api\Application\ApplicationApiController; use App\Http\Requests\Api\Application\DatabaseHosts\DeleteDatabaseHostRequest; use App\Http\Requests\Api\Application\DatabaseHosts\GetDatabaseHostRequest; @@ -10,7 +11,6 @@ use App\Models\DatabaseHost; use App\Services\Databases\Hosts\HostCreationService; use App\Services\Databases\Hosts\HostUpdateService; -use App\Transformers\Api\Application\DatabaseHostTransformer; use Exception; use Illuminate\Http\JsonResponse; use Illuminate\Http\Response; @@ -43,8 +43,8 @@ public function index(GetDatabaseHostRequest $request): array ->allowedSorts(['id', 'name', 'host']) ->paginate($request->query('per_page') ?? 10); - return $this->fractal->collection($databases) - ->transformWith($this->getTransformer(DatabaseHostTransformer::class)) + return $this->response->collection($databases) + ->transformWith(DatabaseHostData::class) ->toArray(); } @@ -57,8 +57,8 @@ public function index(GetDatabaseHostRequest $request): array */ public function view(GetDatabaseHostRequest $request, DatabaseHost $databaseHost): array { - return $this->fractal->item($databaseHost) - ->transformWith($this->getTransformer(DatabaseHostTransformer::class)) + return $this->response->item($databaseHost) + ->transformWith(DatabaseHostData::class) ->toArray(); } @@ -74,8 +74,8 @@ public function store(StoreDatabaseHostRequest $request): JsonResponse { $databaseHost = $this->creationService->handle($request->validated()); - return $this->fractal->item($databaseHost) - ->transformWith($this->getTransformer(DatabaseHostTransformer::class)) + return $this->response->item($databaseHost) + ->transformWith(DatabaseHostData::class) ->addMeta([ 'resource' => route('api.application.databasehosts.view', [ 'database_host' => $databaseHost->id, @@ -97,8 +97,8 @@ public function update(UpdateDatabaseHostRequest $request, DatabaseHost $databas { $databaseHost = $this->updateService->handle($databaseHost->id, $request->validated()); - return $this->fractal->item($databaseHost) - ->transformWith($this->getTransformer(DatabaseHostTransformer::class)) + return $this->response->item($databaseHost) + ->transformWith(DatabaseHostData::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Eggs/EggController.php b/app/Http/Controllers/Api/Application/Eggs/EggController.php index c3ce0c0266..9cb70e329d 100644 --- a/app/Http/Controllers/Api/Application/Eggs/EggController.php +++ b/app/Http/Controllers/Api/Application/Eggs/EggController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Application\Eggs; +use App\Data\Api\Application\EggData; use App\Enums\EggFormat; use App\Http\Controllers\Api\Application\ApplicationApiController; use App\Http\Requests\Api\Application\Eggs\ExportEggRequest; @@ -11,7 +12,6 @@ use App\Models\Egg; use App\Services\Eggs\Sharing\EggExporterService; use App\Services\Eggs\Sharing\EggImporterService; -use App\Transformers\Api\Application\EggTransformer; use Exception; use Illuminate\Http\JsonResponse; use Illuminate\Http\Response; @@ -36,8 +36,8 @@ public function __construct( */ public function index(GetEggsRequest $request): array { - return $this->fractal->collection(Egg::all()) - ->transformWith($this->getTransformer(EggTransformer::class)) + return $this->response->collection(Egg::all()) + ->transformWith(EggData::class) ->toArray(); } @@ -50,8 +50,8 @@ public function index(GetEggsRequest $request): array */ public function view(GetEggRequest $request, Egg $egg): array { - return $this->fractal->item($egg) - ->transformWith($this->getTransformer(EggTransformer::class)) + return $this->response->item($egg) + ->transformWith(EggData::class) ->toArray(); } @@ -98,8 +98,8 @@ public function import(ImportEggRequest $request): JsonResponse { $egg = $this->importService->fromContent($request->getContent()); - return $this->fractal->item($egg) - ->transformWith($this->getTransformer(EggTransformer::class)) + return $this->response->item($egg) + ->transformWith(EggData::class) ->respond(201); } } diff --git a/app/Http/Controllers/Api/Application/Mounts/MountController.php b/app/Http/Controllers/Api/Application/Mounts/MountController.php index 27600253d8..f99ac80cc5 100644 --- a/app/Http/Controllers/Api/Application/Mounts/MountController.php +++ b/app/Http/Controllers/Api/Application/Mounts/MountController.php @@ -2,6 +2,10 @@ namespace App\Http\Controllers\Api\Application\Mounts; +use App\Data\Api\Application\EggData; +use App\Data\Api\Application\MountData; +use App\Data\Api\Application\NodeData; +use App\Data\Api\Application\ServerData; use App\Exceptions\Model\DataValidationException; use App\Exceptions\Service\HasActiveServersException; use App\Http\Controllers\Api\Application\ApplicationApiController; @@ -17,10 +21,6 @@ use App\Http\Requests\Api\Application\Nodes\GetNodesRequest; use App\Http\Requests\Api\Application\Servers\GetServerRequest; use App\Models\Mount; -use App\Transformers\Api\Application\EggTransformer; -use App\Transformers\Api\Application\MountTransformer; -use App\Transformers\Api\Application\NodeTransformer; -use App\Transformers\Api\Application\ServerTransformer; use Illuminate\Http\JsonResponse; use Ramsey\Uuid\Uuid; use Spatie\QueryBuilder\QueryBuilder; @@ -42,8 +42,8 @@ public function index(GetMountRequest $request): array ->allowedSorts(['id', 'uuid']) ->paginate($request->query('per_page') ?? 50); - return $this->fractal->collection($mounts) - ->transformWith($this->getTransformer(MountTransformer::class)) + return $this->response->collection($mounts) + ->transformWith(MountData::class) ->toArray(); } @@ -56,8 +56,8 @@ public function index(GetMountRequest $request): array */ public function view(GetMountRequest $request, Mount $mount): array { - return $this->fractal->item($mount) - ->transformWith($this->getTransformer(MountTransformer::class)) + return $this->response->item($mount) + ->transformWith(MountData::class) ->toArray(); } @@ -77,8 +77,8 @@ public function store(StoreMountRequest $request): JsonResponse $model->saveOrFail(); $mount = $model->fresh(); - return $this->fractal->item($mount) - ->transformWith($this->getTransformer(MountTransformer::class)) + return $this->response->item($mount) + ->transformWith(MountData::class) ->addMeta([ 'resource' => route('api.application.mounts.view', [ 'mount' => $mount->id, @@ -100,8 +100,8 @@ public function update(UpdateMountRequest $request, Mount $mount): array { $mount->forceFill($request->validated())->save(); - return $this->fractal->item($mount) - ->transformWith($this->getTransformer(MountTransformer::class)) + return $this->response->item($mount) + ->transformWith(MountData::class) ->toArray(); } @@ -132,8 +132,8 @@ public function delete(DeleteMountRequest $request, Mount $mount): JsonResponse */ public function getEggs(GetEggsRequest $request, Mount $mount): array { - return $this->fractal->collection($mount->eggs) - ->transformWith($this->getTransformer(EggTransformer::class)) + return $this->response->collection($mount->eggs) + ->transformWith(EggData::class) ->toArray(); } @@ -147,8 +147,8 @@ public function getEggs(GetEggsRequest $request, Mount $mount): array */ public function getNodes(GetNodesRequest $request, Mount $mount): array { - return $this->fractal->collection($mount->nodes) - ->transformWith($this->getTransformer(NodeTransformer::class)) + return $this->response->collection($mount->nodes) + ->transformWith(NodeData::class) ->toArray(); } @@ -161,8 +161,8 @@ public function getNodes(GetNodesRequest $request, Mount $mount): array */ public function getServers(GetServerRequest $request, Mount $mount): array { - return $this->fractal->collection($mount->servers) - ->transformWith($this->getTransformer(ServerTransformer::class)) + return $this->response->collection($mount->servers) + ->transformWith(ServerData::class) ->toArray(); } @@ -177,8 +177,8 @@ public function addEggs(UpdateMountEggsRequest $request, Mount $mount): array { $mount->eggs()->attach($request->validated('eggs')); - return $this->fractal->item($mount) - ->transformWith($this->getTransformer(MountTransformer::class)) + return $this->response->item($mount) + ->transformWith(MountData::class) ->toArray(); } @@ -193,8 +193,8 @@ public function addNodes(UpdateMountNodesRequest $request, Mount $mount): array { $mount->nodes()->attach($request->validated('nodes')); - return $this->fractal->item($mount) - ->transformWith($this->getTransformer(MountTransformer::class)) + return $this->response->item($mount) + ->transformWith(MountData::class) ->toArray(); } @@ -209,8 +209,8 @@ public function addServers(UpdateMountServersRequest $request, Mount $mount): ar { $mount->servers()->attach($request->validated('servers')); - return $this->fractal->item($mount) - ->transformWith($this->getTransformer(MountTransformer::class)) + return $this->response->item($mount) + ->transformWith(MountData::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php index 2654dd0a4e..89f5e067bf 100644 --- a/app/Http/Controllers/Api/Application/Nodes/AllocationController.php +++ b/app/Http/Controllers/Api/Application/Nodes/AllocationController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Application\Nodes; +use App\Data\Api\Application\AllocationData; use App\Exceptions\DisplayException; use App\Exceptions\Service\Allocation\CidrOutOfRangeException; use App\Exceptions\Service\Allocation\InvalidPortMappingException; @@ -14,7 +15,6 @@ use App\Models\Allocation; use App\Models\Node; use App\Services\Allocations\AssignmentService; -use App\Transformers\Api\Application\AllocationTransformer; use Dedoc\Scramble\Attributes\Group; use Illuminate\Database\Eloquent\Builder; use Illuminate\Http\JsonResponse; @@ -57,8 +57,8 @@ public function index(GetAllocationsRequest $request, Node $node): array ]) ->paginate($request->query('per_page') ?? 50); - return $this->fractal->collection($allocations) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + return $this->response->collection($allocations) + ->transformWith(AllocationData::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeController.php b/app/Http/Controllers/Api/Application/Nodes/NodeController.php index cda25179e1..666da7c918 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Application\Nodes; +use App\Data\Api\Application\NodeData; use App\Exceptions\Model\DataValidationException; use App\Exceptions\Service\HasActiveServersException; use App\Http\Controllers\Api\Application\ApplicationApiController; @@ -13,7 +14,6 @@ use App\Models\Node; use App\Services\Nodes\NodeDeletionService; use App\Services\Nodes\NodeUpdateService; -use App\Transformers\Api\Application\NodeTransformer; use Dedoc\Scramble\Attributes\Group; use Exception; use Illuminate\Http\JsonResponse; @@ -47,8 +47,8 @@ public function index(GetNodesRequest $request): array ->allowedSorts(['id', 'uuid', 'memory', 'disk', 'cpu']) ->paginate($request->query('per_page') ?? 50); - return $this->fractal->collection($nodes) - ->transformWith($this->getTransformer(NodeTransformer::class)) + return $this->response->collection($nodes) + ->transformWith(NodeData::class) ->toArray(); } @@ -61,8 +61,8 @@ public function index(GetNodesRequest $request): array */ public function view(GetNodeRequest $request, Node $node): array { - return $this->fractal->item($node) - ->transformWith($this->getTransformer(NodeTransformer::class)) + return $this->response->item($node) + ->transformWith(NodeData::class) ->toArray(); } @@ -78,8 +78,8 @@ public function store(StoreNodeRequest $request): JsonResponse { $node = Node::create($request->validated()); - return $this->fractal->item($node) - ->transformWith($this->getTransformer(NodeTransformer::class)) + return $this->response->item($node) + ->transformWith(NodeData::class) ->addMeta([ 'resource' => route('api.application.nodes.view', [ 'node' => $node->id, @@ -109,8 +109,8 @@ public function update(UpdateNodeRequest $request, Node $node): array report($exception); } - return $this->fractal->item($node) - ->transformWith($this->getTransformer(NodeTransformer::class)) + return $this->response->item($node) + ->transformWith(NodeData::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php index b61d079f98..e9559687a2 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeDeploymentController.php @@ -2,10 +2,10 @@ namespace App\Http\Controllers\Api\Application\Nodes; +use App\Data\Api\Application\NodeData; use App\Http\Controllers\Api\Application\ApplicationApiController; use App\Http\Requests\Api\Application\Nodes\GetDeployableNodesRequest; use App\Services\Deployment\FindViableNodesService; -use App\Transformers\Api\Application\NodeTransformer; use Dedoc\Scramble\Attributes\Group; #[Group('Node', weight: 2)] @@ -36,8 +36,8 @@ public function __invoke(GetDeployableNodesRequest $request): array $data['tags'] ?? $data['location_ids'] ?? [], ); - return $this->fractal->collection($nodes) - ->transformWith($this->getTransformer(NodeTransformer::class)) + return $this->response->collection($nodes) + ->transformWith(NodeData::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Plugins/PluginController.php b/app/Http/Controllers/Api/Application/Plugins/PluginController.php index ed921f4e91..3542c68f2f 100644 --- a/app/Http/Controllers/Api/Application/Plugins/PluginController.php +++ b/app/Http/Controllers/Api/Application/Plugins/PluginController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Application\Plugins; +use App\Data\Api\Application\PluginData; use App\Enums\PluginStatus; use App\Exceptions\PanelException; use App\Http\Controllers\Api\Application\ApplicationApiController; @@ -11,7 +12,6 @@ use App\Http\Requests\Api\Application\Plugins\WritePluginRequest; use App\Models\Plugin; use App\Services\Helpers\PluginService; -use App\Transformers\Api\Application\PluginTransformer; use Exception; use Illuminate\Http\Response; use Spatie\QueryBuilder\QueryBuilder; @@ -40,8 +40,8 @@ public function index(ReadPluginRequest $request): array ->allowedSorts(['id', 'name', 'author', 'category']) ->paginate($request->query('per_page') ?? 10); - return $this->fractal->collection($plugins) - ->transformWith($this->getTransformer(PluginTransformer::class)) + return $this->response->collection($plugins) + ->transformWith(PluginData::class) ->toArray(); } @@ -54,8 +54,8 @@ public function index(ReadPluginRequest $request): array */ public function view(ReadPluginRequest $request, Plugin $plugin): array { - return $this->fractal->item($plugin) - ->transformWith($this->getTransformer(PluginTransformer::class)) + return $this->response->item($plugin) + ->transformWith(PluginData::class) ->toArray(); } @@ -104,8 +104,8 @@ public function install(WritePluginRequest $request, Plugin $plugin): array $this->pluginService->installPlugin($plugin); - return $this->fractal->item($plugin) - ->transformWith($this->getTransformer(PluginTransformer::class)) + return $this->response->item($plugin) + ->transformWith(PluginData::class) ->toArray(); } @@ -124,8 +124,8 @@ public function update(WritePluginRequest $request, Plugin $plugin): array $this->pluginService->updatePlugin($plugin); - return $this->fractal->item($plugin) - ->transformWith($this->getTransformer(PluginTransformer::class)) + return $this->response->item($plugin) + ->transformWith(PluginData::class) ->toArray(); } @@ -144,8 +144,8 @@ public function uninstall(UninstallPluginRequest $request, Plugin $plugin): arra $this->pluginService->uninstallPlugin($plugin, $request->boolean('delete')); - return $this->fractal->item($plugin) - ->transformWith($this->getTransformer(PluginTransformer::class)) + return $this->response->item($plugin) + ->transformWith(PluginData::class) ->toArray(); } @@ -164,8 +164,8 @@ public function enable(WritePluginRequest $request, Plugin $plugin): array $this->pluginService->enablePlugin($plugin); - return $this->fractal->item($plugin) - ->transformWith($this->getTransformer(PluginTransformer::class)) + return $this->response->item($plugin) + ->transformWith(PluginData::class) ->toArray(); } @@ -184,8 +184,8 @@ public function disable(WritePluginRequest $request, Plugin $plugin): array $this->pluginService->disablePlugin($plugin); - return $this->fractal->item($plugin) - ->transformWith($this->getTransformer(PluginTransformer::class)) + return $this->response->item($plugin) + ->transformWith(PluginData::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Roles/RoleController.php b/app/Http/Controllers/Api/Application/Roles/RoleController.php index 076279d980..033ea3255a 100644 --- a/app/Http/Controllers/Api/Application/Roles/RoleController.php +++ b/app/Http/Controllers/Api/Application/Roles/RoleController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Application\Roles; +use App\Data\Api\Application\RoleData; use App\Exceptions\PanelException; use App\Http\Controllers\Api\Application\ApplicationApiController; use App\Http\Requests\Api\Application\Roles\DeleteRoleRequest; @@ -9,7 +10,6 @@ use App\Http\Requests\Api\Application\Roles\StoreRoleRequest; use App\Http\Requests\Api\Application\Roles\UpdateRoleRequest; use App\Models\Role; -use App\Transformers\Api\Application\RoleTransformer; use Exception; use Illuminate\Http\JsonResponse; use Illuminate\Http\Response; @@ -32,8 +32,8 @@ public function index(GetRoleRequest $request): array ->allowedSorts(['id', 'name']) ->paginate($request->query('per_page') ?? 10); - return $this->fractal->collection($roles) - ->transformWith($this->getTransformer(RoleTransformer::class)) + return $this->response->collection($roles) + ->transformWith(RoleData::class) ->toArray(); } @@ -46,8 +46,8 @@ public function index(GetRoleRequest $request): array */ public function view(GetRoleRequest $request, Role $role): array { - return $this->fractal->item($role) - ->transformWith($this->getTransformer(RoleTransformer::class)) + return $this->response->item($role) + ->transformWith(RoleData::class) ->toArray(); } @@ -63,8 +63,8 @@ public function store(StoreRoleRequest $request): JsonResponse { $role = Role::create($request->validated()); - return $this->fractal->item($role) - ->transformWith($this->getTransformer(RoleTransformer::class)) + return $this->response->item($role) + ->transformWith(RoleData::class) ->addMeta([ 'resource' => route('api.application.roles.view', [ 'role' => $role->id, @@ -88,8 +88,8 @@ public function update(UpdateRoleRequest $request, Role $role): array $role->update($request->validated()); - return $this->fractal->item($role) - ->transformWith($this->getTransformer(RoleTransformer::class)) + return $this->response->item($role) + ->transformWith(RoleData::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php index f65c420bbb..cc7a140ed3 100644 --- a/app/Http/Controllers/Api/Application/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Application/Servers/DatabaseController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Application\Servers; +use App\Data\Api\Application\ServerDatabaseData; use App\Http\Controllers\Api\Application\ApplicationApiController; use App\Http\Requests\Api\Application\Servers\Databases\GetServerDatabaseRequest; use App\Http\Requests\Api\Application\Servers\Databases\GetServerDatabasesRequest; @@ -10,7 +11,6 @@ use App\Models\Database; use App\Models\Server; use App\Services\Databases\DatabaseManagementService; -use App\Transformers\Api\Application\ServerDatabaseTransformer; use Dedoc\Scramble\Attributes\Group; use Illuminate\Http\JsonResponse; use Illuminate\Http\Response; @@ -37,8 +37,8 @@ public function __construct( */ public function index(GetServerDatabasesRequest $request, Server $server): array { - return $this->fractal->collection($server->databases) - ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) + return $this->response->collection($server->databases) + ->transformWith(ServerDatabaseData::class) ->toArray(); } @@ -51,8 +51,8 @@ public function index(GetServerDatabasesRequest $request, Server $server): array */ public function view(GetServerDatabaseRequest $request, Server $server, Database $database): array { - return $this->fractal->item($database) - ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) + return $this->response->item($database) + ->transformWith(ServerDatabaseData::class) ->toArray(); } @@ -83,8 +83,8 @@ public function store(StoreServerDatabaseRequest $request, Server $server): Json 'database' => $request->databaseName(), ])); - return $this->fractal->item($database) - ->transformWith($this->getTransformer(ServerDatabaseTransformer::class)) + return $this->response->item($database) + ->transformWith(ServerDatabaseData::class) ->addMeta([ 'resource' => route('api.application.servers.databases.view', [ 'server' => $server->id, diff --git a/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php b/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php index 0130212a63..a48c0860fc 100644 --- a/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ExternalServerController.php @@ -2,10 +2,10 @@ namespace App\Http\Controllers\Api\Application\Servers; +use App\Data\Api\Application\ServerData; use App\Http\Controllers\Api\Application\ApplicationApiController; use App\Http\Requests\Api\Application\Servers\GetExternalServerRequest; use App\Models\Server; -use App\Transformers\Api\Application\ServerTransformer; use Dedoc\Scramble\Attributes\Group; #[Group('Server', weight: 1)] @@ -22,8 +22,8 @@ public function index(GetExternalServerRequest $request, string $external_id): a { $server = Server::query()->where('external_id', $external_id)->firstOrFail(); - return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + return $this->response->item($server) + ->transformWith(ServerData::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerController.php b/app/Http/Controllers/Api/Application/Servers/ServerController.php index 096f034467..84b7c40100 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Application\Servers; +use App\Data\Api\Application\ServerData; use App\Exceptions\DisplayException; use App\Exceptions\Model\DataValidationException; use App\Exceptions\Service\Deployment\NoViableAllocationException; @@ -13,7 +14,6 @@ use App\Models\Server; use App\Services\Servers\ServerCreationService; use App\Services\Servers\ServerDeletionService; -use App\Transformers\Api\Application\ServerTransformer; use Dedoc\Scramble\Attributes\Group; use Illuminate\Http\JsonResponse; use Illuminate\Http\Response; @@ -48,8 +48,8 @@ public function index(GetServersRequest $request): array ->allowedSorts(['id', 'uuid']) ->paginate($request->query('per_page') ?? 50); - return $this->fractal->collection($servers) - ->transformWith($this->getTransformer(ServerTransformer::class)) + return $this->response->collection($servers) + ->transformWith(ServerData::class) ->toArray(); } @@ -68,8 +68,8 @@ public function store(StoreServerRequest $request): JsonResponse { $server = $this->creationService->handle($request->validated(), $request->getDeploymentObject()); - return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + return $this->response->item($server) + ->transformWith(ServerData::class) ->respond(201); } @@ -82,8 +82,8 @@ public function store(StoreServerRequest $request): JsonResponse */ public function view(GetServerRequest $request, Server $server): array { - return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + return $this->response->item($server) + ->transformWith(ServerData::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php index f5aaede210..0928cd6c0e 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerDetailsController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Application\Servers; +use App\Data\Api\Application\ServerData; use App\Exceptions\DisplayException; use App\Exceptions\Model\DataValidationException; use App\Http\Controllers\Api\Application\ApplicationApiController; @@ -10,7 +11,6 @@ use App\Models\Server; use App\Services\Servers\BuildModificationService; use App\Services\Servers\DetailsModificationService; -use App\Transformers\Api\Application\ServerTransformer; use Dedoc\Scramble\Attributes\Group; #[Group('Server', weight: 2)] @@ -46,8 +46,8 @@ public function details(UpdateServerDetailsRequest $request, Server $server): ar $validated, ); - return $this->fractal->item($updated) - ->transformWith($this->getTransformer(ServerTransformer::class)) + return $this->response->item($updated) + ->transformWith(ServerData::class) ->toArray(); } @@ -65,8 +65,8 @@ public function build(UpdateServerBuildConfigurationRequest $request, Server $se { $server = $this->buildModificationService->handle($server, $request->validated()); - return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + return $this->response->item($server) + ->transformWith(ServerData::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Servers/StartupController.php b/app/Http/Controllers/Api/Application/Servers/StartupController.php index 68d8bcf88d..7538d056e8 100644 --- a/app/Http/Controllers/Api/Application/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Application/Servers/StartupController.php @@ -2,13 +2,13 @@ namespace App\Http\Controllers\Api\Application\Servers; +use App\Data\Api\Application\ServerData; use App\Exceptions\Model\DataValidationException; use App\Http\Controllers\Api\Application\ApplicationApiController; use App\Http\Requests\Api\Application\Servers\UpdateServerStartupRequest; use App\Models\Server; use App\Models\User; use App\Services\Servers\StartupModificationService; -use App\Transformers\Api\Application\ServerTransformer; use Dedoc\Scramble\Attributes\Group; use Illuminate\Http\Client\ConnectionException; use Illuminate\Validation\ValidationException; @@ -41,8 +41,8 @@ public function index(UpdateServerStartupRequest $request, Server $server): arra ->setUserLevel(User::USER_LEVEL_ADMIN) ->handle($server, $request->validated()); - return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + return $this->response->item($server) + ->transformWith(ServerData::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Users/ExternalUserController.php b/app/Http/Controllers/Api/Application/Users/ExternalUserController.php index c376c5cb4e..de1915cb42 100644 --- a/app/Http/Controllers/Api/Application/Users/ExternalUserController.php +++ b/app/Http/Controllers/Api/Application/Users/ExternalUserController.php @@ -2,10 +2,10 @@ namespace App\Http\Controllers\Api\Application\Users; +use App\Data\Api\Application\UserData; use App\Http\Controllers\Api\Application\ApplicationApiController; use App\Http\Requests\Api\Application\Users\GetExternalUserRequest; use App\Models\User; -use App\Transformers\Api\Application\UserTransformer; use Dedoc\Scramble\Attributes\Group; #[Group('User', weight: 1)] @@ -22,8 +22,8 @@ public function index(GetExternalUserRequest $request, string $externalId): arra { $user = User::query()->where('external_id', $externalId)->firstOrFail(); - return $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)) + return $this->response->item($user) + ->transformWith(UserData::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Application/Users/UserController.php b/app/Http/Controllers/Api/Application/Users/UserController.php index 6a3b722180..3667064661 100644 --- a/app/Http/Controllers/Api/Application/Users/UserController.php +++ b/app/Http/Controllers/Api/Application/Users/UserController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Application\Users; +use App\Data\Api\Application\UserData; use App\Exceptions\Model\DataValidationException; use App\Http\Controllers\Api\Application\ApplicationApiController; use App\Http\Requests\Api\Application\Users\AssignUserRolesRequest; @@ -13,7 +14,6 @@ use App\Models\User; use App\Services\Users\UserCreationService; use App\Services\Users\UserUpdateService; -use App\Transformers\Api\Application\UserTransformer; use Dedoc\Scramble\Attributes\Group; use Exception; use Illuminate\Http\JsonResponse; @@ -48,8 +48,8 @@ public function index(GetUsersRequest $request): array ->allowedSorts(['id', 'uuid']) ->paginate($request->query('per_page') ?? 50); - return $this->fractal->collection($users) - ->transformWith($this->getTransformer(UserTransformer::class)) + return $this->response->collection($users) + ->transformWith(UserData::class) ->toArray(); } @@ -63,8 +63,8 @@ public function index(GetUsersRequest $request): array */ public function view(GetUsersRequest $request, User $user): array { - return $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)) + return $this->response->item($user) + ->transformWith(UserData::class) ->toArray(); } @@ -87,8 +87,8 @@ public function update(UpdateUserRequest $request, User $user): array $this->updateService->setUserLevel(User::USER_LEVEL_ADMIN); $user = $this->updateService->handle($user, $request->validated()); - $response = $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)); + $response = $this->response->item($user) + ->transformWith(UserData::class); return $response->toArray(); } @@ -113,8 +113,8 @@ public function assignRoles(AssignUserRolesRequest $request, User $user): array } } - $response = $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)); + $response = $this->response->item($user) + ->transformWith(UserData::class); return $response->toArray(); } @@ -139,8 +139,8 @@ public function removeRoles(AssignUserRolesRequest $request, User $user): array } } - $response = $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)); + $response = $this->response->item($user) + ->transformWith(UserData::class); return $response->toArray(); } @@ -158,8 +158,8 @@ public function store(StoreUserRequest $request): JsonResponse { $user = $this->creationService->handle($request->validated()); - return $this->fractal->item($user) - ->transformWith($this->getTransformer(UserTransformer::class)) + return $this->response->item($user) + ->transformWith(UserData::class) ->addMeta([ 'resource' => route('api.application.users.view', [ 'user' => $user->id, diff --git a/app/Http/Controllers/Api/Client/AccountController.php b/app/Http/Controllers/Api/Client/AccountController.php index 645a55fb0e..884923ff5b 100644 --- a/app/Http/Controllers/Api/Client/AccountController.php +++ b/app/Http/Controllers/Api/Client/AccountController.php @@ -2,12 +2,12 @@ namespace App\Http\Controllers\Api\Client; +use App\Data\Api\Client\UserData; use App\Facades\Activity; use App\Http\Requests\Api\Client\Account\UpdateEmailRequest; use App\Http\Requests\Api\Client\Account\UpdatePasswordRequest; use App\Http\Requests\Api\Client\Account\UpdateUsernameRequest; use App\Services\Users\UserUpdateService; -use App\Transformers\Api\Client\UserTransformer; use Illuminate\Auth\AuthManager; use Illuminate\Auth\SessionGuard; use Illuminate\Http\JsonResponse; @@ -41,8 +41,8 @@ public function __construct(private AuthManager $manager, private UserUpdateServ */ public function index(Request $request): array { - return $this->fractal->item($request->user()) - ->transformWith($this->getTransformer(UserTransformer::class)) + return $this->response->item($request->user()) + ->transformWith(UserData::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/ActivityLogController.php b/app/Http/Controllers/Api/Client/ActivityLogController.php index fdbd626343..7bac83c0f8 100644 --- a/app/Http/Controllers/Api/Client/ActivityLogController.php +++ b/app/Http/Controllers/Api/Client/ActivityLogController.php @@ -2,9 +2,9 @@ namespace App\Http\Controllers\Api\Client; +use App\Data\Api\Client\ActivityLogData; use App\Http\Requests\Api\Client\ClientApiRequest; use App\Models\ActivityLog; -use App\Transformers\Api\Client\ActivityLogTransformer; use Spatie\QueryBuilder\AllowedFilter; use Spatie\QueryBuilder\QueryBuilder; @@ -27,8 +27,8 @@ public function __invoke(ClientApiRequest $request): array ->paginate(min($request->query('per_page', '25'), 100)) ->appends($request->query()); - return $this->fractal->collection($activity) - ->transformWith($this->getTransformer(ActivityLogTransformer::class)) + return $this->response->collection($activity) + ->transformWith(ActivityLogData::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Client/ApiKeyController.php b/app/Http/Controllers/Api/Client/ApiKeyController.php index e6c738bb9c..6f183143cd 100644 --- a/app/Http/Controllers/Api/Client/ApiKeyController.php +++ b/app/Http/Controllers/Api/Client/ApiKeyController.php @@ -2,12 +2,12 @@ namespace App\Http\Controllers\Api\Client; +use App\Data\Api\Client\ApiKeyData; use App\Exceptions\DisplayException; use App\Facades\Activity; use App\Http\Requests\Api\Client\Account\StoreApiKeyRequest; use App\Http\Requests\Api\Client\ClientApiRequest; use App\Models\ApiKey; -use App\Transformers\Api\Client\ApiKeyTransformer; use Illuminate\Http\JsonResponse; class ApiKeyController extends ClientApiController @@ -21,8 +21,8 @@ class ApiKeyController extends ClientApiController */ public function index(ClientApiRequest $request): array { - return $this->fractal->collection($request->user()->apiKeys) - ->transformWith($this->getTransformer(ApiKeyTransformer::class)) + return $this->response->collection($request->user()->apiKeys) + ->transformWith(ApiKeyData::class) ->toArray(); } @@ -49,8 +49,8 @@ public function store(StoreApiKeyRequest $request): array ->property('identifier', $token->accessToken->identifier) ->log(); - return $this->fractal->item($token->accessToken) - ->transformWith($this->getTransformer(ApiKeyTransformer::class)) + return $this->response->item($token->accessToken) + ->transformWith(ApiKeyData::class) ->addMeta(['secret_token' => $token->plainTextToken]) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/ClientController.php b/app/Http/Controllers/Api/Client/ClientController.php index 42327ca182..498f8b2ba8 100644 --- a/app/Http/Controllers/Api/Client/ClientController.php +++ b/app/Http/Controllers/Api/Client/ClientController.php @@ -2,11 +2,11 @@ namespace App\Http\Controllers\Api\Client; +use App\Data\Api\Client\ServerData; use App\Http\Requests\Api\Client\GetServersRequest; use App\Models\Filters\MultiFieldServerFilter; use App\Models\Server; use App\Models\Subuser; -use App\Transformers\Api\Client\ServerTransformer; use Dedoc\Scramble\Attributes\Group; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Model; @@ -35,10 +35,12 @@ public function __construct() public function index(GetServersRequest $request): array { $user = $request->user(); - $transformer = $this->getTransformer(ServerTransformer::class); + + // Only eager load the relationships that map to includes the resource honors. + $includes = array_values(array_intersect($this->parseIncludes(), ServerData::$availableIncludes)); /** @var Builder $query */ - $query = Server::query()->with($this->getIncludesForTransformer($transformer, ['node'])); + $query = Server::query()->with(array_merge($includes, ['node'])); // Start the query builder and ensure we eager load any requested relationships from the request. $builder = QueryBuilder::for($query)->allowedFilters([ @@ -72,7 +74,7 @@ public function index(GetServersRequest $request): array $servers = $builder->paginate(min($request->query('per_page', '50'), 100))->appends($request->query()); - return $this->fractal->transformWith($transformer)->collection($servers)->toArray(); + return $this->response->transformWith(ServerData::class)->collection($servers)->toArray(); } /** diff --git a/app/Http/Controllers/Api/Client/SSHKeyController.php b/app/Http/Controllers/Api/Client/SSHKeyController.php index 445c2c7b84..93f40ab532 100644 --- a/app/Http/Controllers/Api/Client/SSHKeyController.php +++ b/app/Http/Controllers/Api/Client/SSHKeyController.php @@ -2,11 +2,11 @@ namespace App\Http\Controllers\Api\Client; +use App\Data\Api\Client\UserSSHKeyData; use App\Facades\Activity; use App\Http\Requests\Api\Client\Account\StoreSSHKeyRequest; use App\Http\Requests\Api\Client\ClientApiRequest; use App\Models\UserSSHKey; -use App\Transformers\Api\Client\UserSSHKeyTransformer; use Illuminate\Http\JsonResponse; class SSHKeyController extends ClientApiController @@ -20,8 +20,8 @@ class SSHKeyController extends ClientApiController */ public function index(ClientApiRequest $request): array { - return $this->fractal->collection($request->user()->sshKeys) - ->transformWith($this->getTransformer(UserSSHKeyTransformer::class)) + return $this->response->collection($request->user()->sshKeys) + ->transformWith(UserSSHKeyData::class) ->toArray(); } @@ -45,8 +45,8 @@ public function store(StoreSSHKeyRequest $request): array ->property('fingerprint', $request->getKeyFingerprint()) ->log(); - return $this->fractal->item($model) - ->transformWith($this->getTransformer(UserSSHKeyTransformer::class)) + return $this->response->item($model) + ->transformWith(UserSSHKeyData::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ActivityLogController.php b/app/Http/Controllers/Api/Client/Servers/ActivityLogController.php index 4fcdf1e73f..ea383cca04 100644 --- a/app/Http/Controllers/Api/Client/Servers/ActivityLogController.php +++ b/app/Http/Controllers/Api/Client/Servers/ActivityLogController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Client\Servers; +use App\Data\Api\Client\ActivityLogData; use App\Enums\SubuserPermission; use App\Http\Controllers\Api\Client\ClientApiController; use App\Http\Requests\Api\Client\ClientApiRequest; @@ -9,7 +10,6 @@ use App\Models\Role; use App\Models\Server; use App\Models\User; -use App\Transformers\Api\Client\ActivityLogTransformer; use Dedoc\Scramble\Attributes\Group; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Query\JoinClause; @@ -56,8 +56,8 @@ public function __invoke(ClientApiRequest $request, Server $server): array ->paginate(min($request->query('per_page', '25'), 100)) ->appends($request->query()); - return $this->fractal->collection($activity) - ->transformWith($this->getTransformer(ActivityLogTransformer::class)) + return $this->response->collection($activity) + ->transformWith(ActivityLogData::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Client/Servers/BackupController.php b/app/Http/Controllers/Api/Client/Servers/BackupController.php index 9419414f06..bd8ba88fd7 100644 --- a/app/Http/Controllers/Api/Client/Servers/BackupController.php +++ b/app/Http/Controllers/Api/Client/Servers/BackupController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Client\Servers; +use App\Data\Api\Client\BackupData; use App\Enums\ServerState; use App\Enums\SubuserPermission; use App\Extensions\BackupAdapter\BackupAdapterService; @@ -16,7 +17,6 @@ use App\Services\Backups\DeleteBackupService; use App\Services\Backups\DownloadLinkService; use App\Services\Backups\InitiateBackupService; -use App\Transformers\Api\Client\BackupTransformer; use Dedoc\Scramble\Attributes\Group; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Http\JsonResponse; @@ -54,8 +54,8 @@ public function index(Request $request, Server $server): array $limit = min($request->query('per_page') ?? 20, 50); - return $this->fractal->collection($server->backups()->paginate($limit)) - ->transformWith($this->getTransformer(BackupTransformer::class)) + return $this->response->collection($server->backups()->paginate($limit)) + ->transformWith(BackupData::class) ->addMeta([ 'backup_count' => $server->backups()->nonFailed()->count(), ]) @@ -99,8 +99,8 @@ public function store(StoreBackupRequest $request, Server $server): array return $backup; }); - return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) + return $this->response->item($backup) + ->transformWith(BackupData::class) ->toArray(); } @@ -124,8 +124,8 @@ public function toggleLock(Request $request, Server $server, Backup $backup): ar Activity::event($action)->subject($backup)->property('name', $backup->name)->log(); - return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) + return $this->response->item($backup) + ->transformWith(BackupData::class) ->toArray(); } @@ -142,8 +142,8 @@ public function view(Request $request, Server $server, Backup $backup): array { throw_unless($request->user()->can(SubuserPermission::BackupRead, $server), new AuthorizationException()); - return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) + return $this->response->item($backup) + ->transformWith(BackupData::class) ->toArray(); } @@ -222,8 +222,8 @@ public function rename(RenameBackupRequest $request, Server $server, Backup $bac ->log(); } - return $this->fractal->item($backup) - ->transformWith($this->getTransformer(BackupTransformer::class)) + return $this->response->item($backup) + ->transformWith(BackupData::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php index c741544410..1ce66c6b35 100644 --- a/app/Http/Controllers/Api/Client/Servers/DatabaseController.php +++ b/app/Http/Controllers/Api/Client/Servers/DatabaseController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Client\Servers; +use App\Data\Api\Client\DatabaseData; use App\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException; use App\Exceptions\Service\Database\TooManyDatabasesException; use App\Facades\Activity; @@ -14,7 +15,6 @@ use App\Models\Server; use App\Services\Databases\DatabaseManagementService; use App\Services\Databases\DeployServerDatabaseService; -use App\Transformers\Api\Client\DatabaseTransformer; use Dedoc\Scramble\Attributes\Group; use Illuminate\Http\Response; use Throwable; @@ -41,8 +41,8 @@ public function __construct( */ public function index(GetDatabasesRequest $request, Server $server): array { - return $this->fractal->collection($server->databases) - ->transformWith($this->getTransformer(DatabaseTransformer::class)) + return $this->response->collection($server->databases) + ->transformWith(DatabaseData::class) ->toArray(); } @@ -69,9 +69,9 @@ public function store(StoreDatabaseRequest $request, Server $server): array return $database; }); - return $this->fractal->item($database) + return $this->response->item($database) ->parseIncludes(['password']) - ->transformWith($this->getTransformer(DatabaseTransformer::class)) + ->transformWith(DatabaseData::class) ->toArray(); } @@ -92,9 +92,9 @@ public function rotatePassword(RotatePasswordRequest $request, Server $server, D ->property('name', $database->database) ->transaction(fn () => $this->managementService->rotatePassword($database)); - return $this->fractal->item($database->refresh()) + return $this->response->item($database->refresh()) ->parseIncludes(['password']) - ->transformWith($this->getTransformer(DatabaseTransformer::class)) + ->transformWith(DatabaseData::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/FileController.php b/app/Http/Controllers/Api/Client/Servers/FileController.php index ecc1610eb9..2799b69d3e 100644 --- a/app/Http/Controllers/Api/Client/Servers/FileController.php +++ b/app/Http/Controllers/Api/Client/Servers/FileController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Client\Servers; +use App\Data\Api\Client\FileObjectData; use App\Enums\NodeJwtScope; use App\Facades\Activity; use App\Http\Controllers\Api\Client\ClientApiController; @@ -19,7 +20,6 @@ use App\Models\Server; use App\Repositories\Daemon\DaemonFileRepository; use App\Services\Nodes\NodeJWTService; -use App\Transformers\Api\Client\FileObjectTransformer; use Carbon\CarbonImmutable; use Dedoc\Scramble\Attributes\Group; use Illuminate\Http\Client\ConnectionException; @@ -55,8 +55,8 @@ public function directory(ListFilesRequest $request, Server $server): array ->setServer($server) ->getDirectory($request->get('directory') ?? '/'); - return $this->fractal->collection($contents) - ->transformWith($this->getTransformer(FileObjectTransformer::class)) + return $this->response->collection($contents) + ->transformWith(FileObjectData::class) ->toArray(); } @@ -227,8 +227,8 @@ public function compress(CompressFilesRequest $request, Server $server): array ->property('files', $request->input('files')) ->log(); - return $this->fractal->item($file) - ->transformWith($this->getTransformer(FileObjectTransformer::class)) + return $this->response->item($file) + ->transformWith(FileObjectData::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php index 8f43e0028a..1f47627486 100644 --- a/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php +++ b/app/Http/Controllers/Api/Client/Servers/NetworkAllocationController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Client\Servers; +use App\Data\Api\Client\AllocationData; use App\Exceptions\DisplayException; use App\Exceptions\Model\DataValidationException; use App\Facades\Activity; @@ -14,7 +15,6 @@ use App\Models\Allocation; use App\Models\Server; use App\Services\Allocations\FindAssignableAllocationService; -use App\Transformers\Api\Client\AllocationTransformer; use Dedoc\Scramble\Attributes\Group; use Illuminate\Http\JsonResponse; @@ -40,8 +40,8 @@ public function __construct( */ public function index(GetNetworkRequest $request, Server $server): array { - return $this->fractal->collection($server->allocations) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + return $this->response->collection($server->allocations) + ->transformWith(AllocationData::class) ->toArray(); } @@ -67,8 +67,8 @@ public function update(UpdateAllocationRequest $request, Server $server, Allocat ->log(); } - return $this->fractal->item($allocation) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + return $this->response->item($allocation) + ->transformWith(AllocationData::class) ->toArray(); } @@ -91,8 +91,8 @@ public function setPrimary(SetPrimaryAllocationRequest $request, Server $server, ->property('allocation', $allocation->address) ->log(); - return $this->fractal->item($allocation) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + return $this->response->item($allocation) + ->transformWith(AllocationData::class) ->toArray(); } @@ -119,8 +119,8 @@ public function store(NewAllocationRequest $request, Server $server): array return $allocation; }); - return $this->fractal->item($allocation) - ->transformWith($this->getTransformer(AllocationTransformer::class)) + return $this->response->item($allocation) + ->transformWith(AllocationData::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php index f731c7967d..d0da7cec30 100644 --- a/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php +++ b/app/Http/Controllers/Api/Client/Servers/ResourceUtilizationController.php @@ -2,11 +2,11 @@ namespace App\Http\Controllers\Api\Client\Servers; +use App\Data\Api\Client\StatsData; use App\Http\Controllers\Api\Client\ClientApiController; use App\Http\Requests\Api\Client\Servers\GetServerRequest; use App\Models\Server; use App\Repositories\Daemon\DaemonServerRepository; -use App\Transformers\Api\Client\StatsTransformer; use Dedoc\Scramble\Attributes\Group; use Illuminate\Cache\Repository; use Illuminate\Http\Client\ConnectionException; @@ -41,8 +41,8 @@ public function __invoke(GetServerRequest $request, Server $server): array return $this->repository->setServer($server)->getDetails(); }); - return $this->fractal->item($stats) - ->transformWith($this->getTransformer(StatsTransformer::class)) + return $this->response->item($stats) + ->transformWith(StatsData::class) ->toArray(); } } diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php index 2a270c60b3..d1b595aa51 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Client\Servers; +use App\Data\Api\Client\ScheduleData; use App\Exceptions\DisplayException; use App\Exceptions\Model\DataValidationException; use App\Facades\Activity; @@ -15,7 +16,6 @@ use App\Models\Schedule; use App\Models\Server; use App\Services\Schedules\ProcessScheduleService; -use App\Transformers\Api\Client\ScheduleTransformer; use Dedoc\Scramble\Attributes\Group; use Exception; use Illuminate\Http\JsonResponse; @@ -47,8 +47,8 @@ public function index(ViewScheduleRequest $request, Server $server): array { $schedules = $server->schedules->loadMissing('tasks'); - return $this->fractal->collection($schedules) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + return $this->response->collection($schedules) + ->transformWith(ScheduleData::class) ->toArray(); } @@ -83,8 +83,8 @@ public function store(StoreScheduleRequest $request, Server $server): array ->property('name', $model->name) ->log(); - return $this->fractal->item($model) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + return $this->response->item($model) + ->transformWith(ScheduleData::class) ->toArray(); } @@ -101,8 +101,8 @@ public function view(ViewScheduleRequest $request, Server $server, Schedule $sch $schedule->loadMissing('tasks'); - return $this->fractal->item($schedule) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + return $this->response->item($schedule) + ->transformWith(ScheduleData::class) ->toArray(); } @@ -145,8 +145,8 @@ public function update(UpdateScheduleRequest $request, Server $server, Schedule ->property(['name' => $schedule->name, 'active' => $active]) ->log(); - return $this->fractal->item($schedule->refresh()) - ->transformWith($this->getTransformer(ScheduleTransformer::class)) + return $this->response->item($schedule->refresh()) + ->transformWith(ScheduleData::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php index 085588343b..5241cd622b 100644 --- a/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php +++ b/app/Http/Controllers/Api/Client/Servers/ScheduleTaskController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Client\Servers; +use App\Data\Api\Client\TaskData; use App\Enums\SubuserPermission; use App\Exceptions\Http\HttpForbiddenException; use App\Exceptions\Model\DataValidationException; @@ -13,7 +14,6 @@ use App\Models\Schedule; use App\Models\Server; use App\Models\Task; -use App\Transformers\Api\Client\TaskTransformer; use Dedoc\Scramble\Attributes\Group; use Exception; use Illuminate\Database\ConnectionInterface; @@ -89,8 +89,8 @@ public function store(StoreTaskRequest $request, Server $server, Schedule $sched ->property(['name' => $schedule->name, 'action' => $task->action, 'payload' => $task->payload]) ->log(); - return $this->fractal->item($task) - ->transformWith($this->getTransformer(TaskTransformer::class)) + return $this->response->item($task) + ->transformWith(TaskData::class) ->toArray(); } @@ -143,8 +143,8 @@ public function update(StoreTaskRequest $request, Server $server, Schedule $sche ->property(['name' => $schedule->name, 'action' => $task->action, 'payload' => $task->payload]) ->log(); - return $this->fractal->item($task->refresh()) - ->transformWith($this->getTransformer(TaskTransformer::class)) + return $this->response->item($task->refresh()) + ->transformWith(TaskData::class) ->toArray(); } diff --git a/app/Http/Controllers/Api/Client/Servers/ServerController.php b/app/Http/Controllers/Api/Client/Servers/ServerController.php index b524c59bca..2a98bf40b3 100644 --- a/app/Http/Controllers/Api/Client/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Client/Servers/ServerController.php @@ -2,11 +2,11 @@ namespace App\Http\Controllers\Api\Client\Servers; +use App\Data\Api\Client\ServerData; use App\Http\Controllers\Api\Client\ClientApiController; use App\Http\Requests\Api\Client\Servers\GetServerRequest; use App\Models\Server; use App\Services\Servers\GetUserPermissionsService; -use App\Transformers\Api\Client\ServerTransformer; use Dedoc\Scramble\Attributes\Group; #[Group('Server', weight: 0)] @@ -26,8 +26,8 @@ public function __construct(private GetUserPermissionsService $permissionsServic */ public function index(GetServerRequest $request, Server $server): array { - return $this->fractal->item($server) - ->transformWith($this->getTransformer(ServerTransformer::class)) + return $this->response->item($server) + ->transformWith(ServerData::class) ->addMeta([ 'is_server_owner' => $request->user()->id === $server->owner_id, 'user_permissions' => $this->permissionsService->handle($server, $request->user()), diff --git a/app/Http/Controllers/Api/Client/Servers/StartupController.php b/app/Http/Controllers/Api/Client/Servers/StartupController.php index 89e59d7139..75b44eca98 100644 --- a/app/Http/Controllers/Api/Client/Servers/StartupController.php +++ b/app/Http/Controllers/Api/Client/Servers/StartupController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Client\Servers; +use App\Data\Api\Client\EggVariableData; use App\Exceptions\Model\DataValidationException; use App\Facades\Activity; use App\Http\Controllers\Api\Client\ClientApiController; @@ -10,7 +11,6 @@ use App\Models\Server; use App\Models\ServerVariable; use App\Services\Servers\StartupCommandService; -use App\Transformers\Api\Client\EggVariableTransformer; use Dedoc\Scramble\Attributes\Group; use Illuminate\Validation\ValidationException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; @@ -38,10 +38,10 @@ public function index(GetStartupRequest $request, Server $server): array { $startup = $this->startupCommandService->handle($server); - return $this->fractal->collection( + return $this->response->collection( $server->variables()->where('user_viewable', true)->orderBy('sort')->get() ) - ->transformWith($this->getTransformer(EggVariableTransformer::class)) + ->transformWith(EggVariableData::class) ->addMeta([ 'startup_command' => $startup, 'docker_images' => $server->egg->docker_images, @@ -96,8 +96,8 @@ public function update(UpdateStartupVariableRequest $request, Server $server): a ->log(); } - return $this->fractal->item($variable) - ->transformWith($this->getTransformer(EggVariableTransformer::class)) + return $this->response->item($variable) + ->transformWith(EggVariableData::class) ->addMeta([ 'startup_command' => $startup, 'raw_startup_command' => $server->startup, diff --git a/app/Http/Controllers/Api/Client/Servers/SubuserController.php b/app/Http/Controllers/Api/Client/Servers/SubuserController.php index 2a19a35a98..14d92199e6 100644 --- a/app/Http/Controllers/Api/Client/Servers/SubuserController.php +++ b/app/Http/Controllers/Api/Client/Servers/SubuserController.php @@ -2,6 +2,7 @@ namespace App\Http\Controllers\Api\Client\Servers; +use App\Data\Api\Client\SubuserData; use App\Enums\SubuserPermission; use App\Exceptions\Model\DataValidationException; use App\Exceptions\Service\Subuser\ServerSubuserExistsException; @@ -18,7 +19,6 @@ use App\Services\Subusers\SubuserCreationService; use App\Services\Subusers\SubuserDeletionService; use App\Services\Subusers\SubuserUpdateService; -use App\Transformers\Api\Client\SubuserTransformer; use Dedoc\Scramble\Attributes\Group; use Illuminate\Http\JsonResponse; use Illuminate\Http\Request; @@ -47,8 +47,8 @@ public function __construct( */ public function index(GetSubuserRequest $request, Server $server): array { - return $this->fractal->collection($server->subusers) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + return $this->response->collection($server->subusers) + ->transformWith(SubuserData::class) ->toArray(); } @@ -63,8 +63,8 @@ public function view(GetSubuserRequest $request, Server $server, User $user): ar { $subuser = $request->attributes->get('subuser'); - return $this->fractal->item($subuser) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + return $this->response->item($subuser) + ->transformWith(SubuserData::class) ->toArray(); } @@ -92,8 +92,8 @@ public function store(StoreSubuserRequest $request, Server $server): array ->property(['email' => $email, 'permissions' => $subuser->permissions]) ->log(); - return $this->fractal->item($subuser) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + return $this->response->item($subuser) + ->transformWith(SubuserData::class) ->toArray(); } @@ -113,8 +113,8 @@ public function update(UpdateSubuserRequest $request, Server $server, User $user $this->updateService->handle($subuser, $server, $this->getCleanedPermissions($request)); - return $this->fractal->item($subuser->refresh()) - ->transformWith($this->getTransformer(SubuserTransformer::class)) + return $this->response->item($subuser->refresh()) + ->transformWith(SubuserData::class) ->toArray(); } From 62a375fccef3c4456c5d4301e6bc7cecfe5e2562 Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Tue, 18 Aug 2026 08:47:33 -0400 Subject: [PATCH 2/3] Satisfy PHPStan on the new Data layer The concrete Data classes are final now, which makes their new static calls in fromModel provably safe, PluginData documents its panels element type, and the paginated branch of PanelResponse hands Envelope::collection the paginator's items directly since the paginator contract itself is not iterable. --- app/Data/Api/Application/AllocationData.php | 2 +- app/Data/Api/Application/DatabaseHostData.php | 2 +- app/Data/Api/Application/EggData.php | 2 +- app/Data/Api/Application/EggVariableData.php | 2 +- app/Data/Api/Application/MountData.php | 2 +- app/Data/Api/Application/NodeData.php | 2 +- app/Data/Api/Application/PluginData.php | 3 ++- app/Data/Api/Application/RoleData.php | 2 +- app/Data/Api/Application/RolePermissionData.php | 2 +- app/Data/Api/Application/ServerData.php | 2 +- app/Data/Api/Application/ServerDatabaseData.php | 2 +- app/Data/Api/Application/ServerVariableData.php | 2 +- app/Data/Api/Application/SubuserData.php | 2 +- app/Data/Api/Application/UserData.php | 2 +- app/Data/Api/Client/ActivityLogData.php | 2 +- app/Data/Api/Client/AllocationData.php | 2 +- app/Data/Api/Client/ApiKeyData.php | 2 +- app/Data/Api/Client/BackupData.php | 2 +- app/Data/Api/Client/DatabaseData.php | 2 +- app/Data/Api/Client/EggData.php | 2 +- app/Data/Api/Client/EggVariableData.php | 2 +- app/Data/Api/Client/FileObjectData.php | 2 +- app/Data/Api/Client/ScheduleData.php | 2 +- app/Data/Api/Client/ServerData.php | 2 +- app/Data/Api/Client/StatsData.php | 2 +- app/Data/Api/Client/SubuserData.php | 2 +- app/Data/Api/Client/TaskData.php | 2 +- app/Data/Api/Client/UserData.php | 2 +- app/Data/Api/Client/UserSSHKeyData.php | 2 +- app/Data/Api/PanelResponse.php | 2 +- 30 files changed, 31 insertions(+), 30 deletions(-) diff --git a/app/Data/Api/Application/AllocationData.php b/app/Data/Api/Application/AllocationData.php index a4e5b4f7c7..fd569a1428 100644 --- a/app/Data/Api/Application/AllocationData.php +++ b/app/Data/Api/Application/AllocationData.php @@ -8,7 +8,7 @@ use App\Models\Node; use App\Models\Server; -class AllocationData extends ApiResource +final class AllocationData extends ApiResource { /** @var string[] */ public static array $availableIncludes = ['node', 'server']; diff --git a/app/Data/Api/Application/DatabaseHostData.php b/app/Data/Api/Application/DatabaseHostData.php index 951bc687e2..8bbdb9a7e6 100644 --- a/app/Data/Api/Application/DatabaseHostData.php +++ b/app/Data/Api/Application/DatabaseHostData.php @@ -8,7 +8,7 @@ use App\Models\DatabaseHost; use App\Models\Node; -class DatabaseHostData extends ApiResource +final class DatabaseHostData extends ApiResource { /** @var string[] */ public static array $availableIncludes = [ diff --git a/app/Data/Api/Application/EggData.php b/app/Data/Api/Application/EggData.php index df0b525485..5b1df2ee4d 100644 --- a/app/Data/Api/Application/EggData.php +++ b/app/Data/Api/Application/EggData.php @@ -9,7 +9,7 @@ use App\Models\Server; use Illuminate\Support\Arr; -class EggData extends ApiResource +final class EggData extends ApiResource { /** @var string[] */ public static array $availableIncludes = [ diff --git a/app/Data/Api/Application/EggVariableData.php b/app/Data/Api/Application/EggVariableData.php index 116dc3886c..cafc0fecc9 100644 --- a/app/Data/Api/Application/EggVariableData.php +++ b/app/Data/Api/Application/EggVariableData.php @@ -6,7 +6,7 @@ use App\Models\Egg; use App\Models\EggVariable; -class EggVariableData extends ApiResource +final class EggVariableData extends ApiResource { /** * @param array $attributes diff --git a/app/Data/Api/Application/MountData.php b/app/Data/Api/Application/MountData.php index 7862adbb1f..930b8b99b4 100644 --- a/app/Data/Api/Application/MountData.php +++ b/app/Data/Api/Application/MountData.php @@ -9,7 +9,7 @@ use App\Models\Node; use App\Models\Server; -class MountData extends ApiResource +final class MountData extends ApiResource { /** @var string[] */ public static array $availableIncludes = ['eggs', 'nodes', 'servers']; diff --git a/app/Data/Api/Application/NodeData.php b/app/Data/Api/Application/NodeData.php index 2aa5c5f9f6..73d797a764 100644 --- a/app/Data/Api/Application/NodeData.php +++ b/app/Data/Api/Application/NodeData.php @@ -8,7 +8,7 @@ use App\Models\Node; use App\Models\Server; -class NodeData extends ApiResource +final class NodeData extends ApiResource { /** @var string[] */ public static array $availableIncludes = ['allocations', 'servers']; diff --git a/app/Data/Api/Application/PluginData.php b/app/Data/Api/Application/PluginData.php index e6d17a7dee..dbc13b6eba 100644 --- a/app/Data/Api/Application/PluginData.php +++ b/app/Data/Api/Application/PluginData.php @@ -5,10 +5,11 @@ use App\Data\Api\ApiResource; use App\Models\Plugin; -class PluginData extends ApiResource +final class PluginData extends ApiResource { /** * @param array $meta + * @param string[]|null $panels */ public function __construct( public string $id, diff --git a/app/Data/Api/Application/RoleData.php b/app/Data/Api/Application/RoleData.php index 8f868e8d60..8fed642a18 100644 --- a/app/Data/Api/Application/RoleData.php +++ b/app/Data/Api/Application/RoleData.php @@ -6,7 +6,7 @@ use App\Data\Api\IncludeContext; use App\Models\Role; -class RoleData extends ApiResource +final class RoleData extends ApiResource { /** @var string[] */ public static array $availableIncludes = ['permissions']; diff --git a/app/Data/Api/Application/RolePermissionData.php b/app/Data/Api/Application/RolePermissionData.php index d2f26301c7..19deff004e 100644 --- a/app/Data/Api/Application/RolePermissionData.php +++ b/app/Data/Api/Application/RolePermissionData.php @@ -5,7 +5,7 @@ use App\Data\Api\ApiResource; use Spatie\Permission\Models\Permission; -class RolePermissionData extends ApiResource +final class RolePermissionData extends ApiResource { public function __construct( public string $name, diff --git a/app/Data/Api/Application/ServerData.php b/app/Data/Api/Application/ServerData.php index dec7b65452..71ff7be045 100644 --- a/app/Data/Api/Application/ServerData.php +++ b/app/Data/Api/Application/ServerData.php @@ -12,7 +12,7 @@ use App\Models\User; use App\Services\Servers\EnvironmentService; -class ServerData extends ApiResource +final class ServerData extends ApiResource { /** @var string[] */ public static array $availableIncludes = [ diff --git a/app/Data/Api/Application/ServerDatabaseData.php b/app/Data/Api/Application/ServerDatabaseData.php index d229e99d97..b72bd0c6ab 100644 --- a/app/Data/Api/Application/ServerDatabaseData.php +++ b/app/Data/Api/Application/ServerDatabaseData.php @@ -7,7 +7,7 @@ use App\Models\Database; use App\Models\DatabaseHost; -class ServerDatabaseData extends ApiResource +final class ServerDatabaseData extends ApiResource { /** @var string[] */ public static array $availableIncludes = ['password', 'host']; diff --git a/app/Data/Api/Application/ServerVariableData.php b/app/Data/Api/Application/ServerVariableData.php index 4ee057503d..a8baabe056 100644 --- a/app/Data/Api/Application/ServerVariableData.php +++ b/app/Data/Api/Application/ServerVariableData.php @@ -7,7 +7,7 @@ use App\Models\Egg; use App\Models\EggVariable; -class ServerVariableData extends ApiResource +final class ServerVariableData extends ApiResource { /** @var string[] */ public static array $availableIncludes = ['parent']; diff --git a/app/Data/Api/Application/SubuserData.php b/app/Data/Api/Application/SubuserData.php index 5d169e17e1..1e21c6cab1 100644 --- a/app/Data/Api/Application/SubuserData.php +++ b/app/Data/Api/Application/SubuserData.php @@ -8,7 +8,7 @@ use App\Models\Subuser; use App\Models\User; -class SubuserData extends ApiResource +final class SubuserData extends ApiResource { /** @var string[] */ public static array $availableIncludes = ['user', 'server']; diff --git a/app/Data/Api/Application/UserData.php b/app/Data/Api/Application/UserData.php index fc08c0cd71..56688d6c64 100644 --- a/app/Data/Api/Application/UserData.php +++ b/app/Data/Api/Application/UserData.php @@ -9,7 +9,7 @@ use App\Models\User; use Spatie\LaravelData\Attributes\MapOutputName; -class UserData extends ApiResource +final class UserData extends ApiResource { /** @var string[] */ public static array $availableIncludes = [ diff --git a/app/Data/Api/Client/ActivityLogData.php b/app/Data/Api/Client/ActivityLogData.php index b2cefa8938..6124341161 100644 --- a/app/Data/Api/Client/ActivityLogData.php +++ b/app/Data/Api/Client/ActivityLogData.php @@ -8,7 +8,7 @@ use App\Models\User; use Illuminate\Http\Request; -class ActivityLogData extends ApiResource +final class ActivityLogData extends ApiResource { /** @var string[] */ public static array $availableIncludes = ['actor']; diff --git a/app/Data/Api/Client/AllocationData.php b/app/Data/Api/Client/AllocationData.php index 76e16da095..48ef5aa936 100644 --- a/app/Data/Api/Client/AllocationData.php +++ b/app/Data/Api/Client/AllocationData.php @@ -5,7 +5,7 @@ use App\Data\Api\ApiResource; use App\Models\Allocation; -class AllocationData extends ApiResource +final class AllocationData extends ApiResource { public function __construct( public int $id, diff --git a/app/Data/Api/Client/ApiKeyData.php b/app/Data/Api/Client/ApiKeyData.php index 5fc0b0edeb..e630d08e10 100644 --- a/app/Data/Api/Client/ApiKeyData.php +++ b/app/Data/Api/Client/ApiKeyData.php @@ -5,7 +5,7 @@ use App\Data\Api\ApiResource; use App\Models\ApiKey; -class ApiKeyData extends ApiResource +final class ApiKeyData extends ApiResource { /** * @param string[] $allowed_ips diff --git a/app/Data/Api/Client/BackupData.php b/app/Data/Api/Client/BackupData.php index 34ec9cfc35..45a759034a 100644 --- a/app/Data/Api/Client/BackupData.php +++ b/app/Data/Api/Client/BackupData.php @@ -5,7 +5,7 @@ use App\Data\Api\ApiResource; use App\Models\Backup; -class BackupData extends ApiResource +final class BackupData extends ApiResource { /** * @param string[] $ignored_files diff --git a/app/Data/Api/Client/DatabaseData.php b/app/Data/Api/Client/DatabaseData.php index aec4ff4a31..517554cf08 100644 --- a/app/Data/Api/Client/DatabaseData.php +++ b/app/Data/Api/Client/DatabaseData.php @@ -7,7 +7,7 @@ use App\Enums\SubuserPermission; use App\Models\Database; -class DatabaseData extends ApiResource +final class DatabaseData extends ApiResource { /** @var string[] */ public static array $availableIncludes = ['password']; diff --git a/app/Data/Api/Client/EggData.php b/app/Data/Api/Client/EggData.php index 60a3ad5ce9..131bbabfa2 100644 --- a/app/Data/Api/Client/EggData.php +++ b/app/Data/Api/Client/EggData.php @@ -5,7 +5,7 @@ use App\Data\Api\ApiResource; use App\Models\Egg; -class EggData extends ApiResource +final class EggData extends ApiResource { public function __construct( public string $uuid, diff --git a/app/Data/Api/Client/EggVariableData.php b/app/Data/Api/Client/EggVariableData.php index f2e70b7acb..96435d366e 100644 --- a/app/Data/Api/Client/EggVariableData.php +++ b/app/Data/Api/Client/EggVariableData.php @@ -6,7 +6,7 @@ use App\Models\EggVariable; use BadMethodCallException; -class EggVariableData extends ApiResource +final class EggVariableData extends ApiResource { public function __construct( public string $name, diff --git a/app/Data/Api/Client/FileObjectData.php b/app/Data/Api/Client/FileObjectData.php index 6b4d82fe5f..3933255a5f 100644 --- a/app/Data/Api/Client/FileObjectData.php +++ b/app/Data/Api/Client/FileObjectData.php @@ -6,7 +6,7 @@ use Illuminate\Support\Arr; use Illuminate\Support\Carbon; -class FileObjectData extends ApiResource +final class FileObjectData extends ApiResource { public function __construct( public ?string $name, diff --git a/app/Data/Api/Client/ScheduleData.php b/app/Data/Api/Client/ScheduleData.php index c93b6efe2e..2acbf14648 100644 --- a/app/Data/Api/Client/ScheduleData.php +++ b/app/Data/Api/Client/ScheduleData.php @@ -6,7 +6,7 @@ use App\Data\Api\IncludeContext; use App\Models\Schedule; -class ScheduleData extends ApiResource +final class ScheduleData extends ApiResource { /** @var string[] */ public static array $availableIncludes = ['tasks']; diff --git a/app/Data/Api/Client/ServerData.php b/app/Data/Api/Client/ServerData.php index fb2a9ab6ea..98bed70cdd 100644 --- a/app/Data/Api/Client/ServerData.php +++ b/app/Data/Api/Client/ServerData.php @@ -11,7 +11,7 @@ use Illuminate\Http\Request; use Spatie\LaravelData\Optional; -class ServerData extends ApiResource +final class ServerData extends ApiResource { /** @var string[] */ public static array $availableIncludes = ['egg', 'subusers']; diff --git a/app/Data/Api/Client/StatsData.php b/app/Data/Api/Client/StatsData.php index bc237e7535..138b71c215 100644 --- a/app/Data/Api/Client/StatsData.php +++ b/app/Data/Api/Client/StatsData.php @@ -5,7 +5,7 @@ use App\Data\Api\ApiResource; use Illuminate\Support\Arr; -class StatsData extends ApiResource +final class StatsData extends ApiResource { /** * @param array $resources diff --git a/app/Data/Api/Client/SubuserData.php b/app/Data/Api/Client/SubuserData.php index 9da02314c9..d6cf4e2ef2 100644 --- a/app/Data/Api/Client/SubuserData.php +++ b/app/Data/Api/Client/SubuserData.php @@ -6,7 +6,7 @@ use App\Models\Subuser; use Spatie\LaravelData\Attributes\MapOutputName; -class SubuserData extends ApiResource +final class SubuserData extends ApiResource { /** * @param string[] $permissions diff --git a/app/Data/Api/Client/TaskData.php b/app/Data/Api/Client/TaskData.php index 44c3bdfacd..4e23170d75 100644 --- a/app/Data/Api/Client/TaskData.php +++ b/app/Data/Api/Client/TaskData.php @@ -5,7 +5,7 @@ use App\Data\Api\ApiResource; use App\Models\Task; -class TaskData extends ApiResource +final class TaskData extends ApiResource { public function __construct( public int $id, diff --git a/app/Data/Api/Client/UserData.php b/app/Data/Api/Client/UserData.php index f5725ec026..95a4ffafea 100644 --- a/app/Data/Api/Client/UserData.php +++ b/app/Data/Api/Client/UserData.php @@ -7,7 +7,7 @@ use Illuminate\Support\Str; use Spatie\LaravelData\Attributes\MapOutputName; -class UserData extends ApiResource +final class UserData extends ApiResource { public function __construct( public string $uuid, diff --git a/app/Data/Api/Client/UserSSHKeyData.php b/app/Data/Api/Client/UserSSHKeyData.php index 35fc0c4fc9..d07eecd2eb 100644 --- a/app/Data/Api/Client/UserSSHKeyData.php +++ b/app/Data/Api/Client/UserSSHKeyData.php @@ -5,7 +5,7 @@ use App\Data\Api\ApiResource; use App\Models\UserSSHKey; -class UserSSHKeyData extends ApiResource +final class UserSSHKeyData extends ApiResource { public function __construct( public string $name, diff --git a/app/Data/Api/PanelResponse.php b/app/Data/Api/PanelResponse.php index 10f327ec5d..725aad29a1 100644 --- a/app/Data/Api/PanelResponse.php +++ b/app/Data/Api/PanelResponse.php @@ -104,7 +104,7 @@ public function toArray(): array $meta = $this->meta; if ($this->data instanceof LengthAwarePaginator) { - $envelope = Envelope::collection($this->data, $this->dataClass, $context); + $envelope = Envelope::collection($this->data->items(), $this->dataClass, $context); $meta['pagination'] = $this->pagination($this->data); } elseif (is_null($this->data)) { $envelope = $context->null(); From f844c01e5e480cf533616f2bfc705c5f6badc447 Mon Sep 17 00:00:00 2001 From: Lance Pioch Date: Tue, 18 Aug 2026 09:21:24 -0400 Subject: [PATCH 3/3] Appease Pint's final-class rules Sealing the Data classes activated self_static_accessor and the brace placement rules, so new static became new self and the formatting nits are settled. --- app/Data/Api/Application/AllocationData.php | 2 +- app/Data/Api/Application/DatabaseHostData.php | 2 +- app/Data/Api/Application/EggData.php | 6 +++--- app/Data/Api/Application/EggVariableData.php | 2 +- app/Data/Api/Application/MountData.php | 2 +- app/Data/Api/Application/NodeData.php | 6 +++--- app/Data/Api/Application/PluginData.php | 2 +- app/Data/Api/Application/RoleData.php | 2 +- app/Data/Api/Application/RolePermissionData.php | 2 +- app/Data/Api/Application/ServerData.php | 6 +++--- app/Data/Api/Application/ServerDatabaseData.php | 2 +- app/Data/Api/Application/ServerVariableData.php | 2 +- app/Data/Api/Application/SubuserData.php | 6 +++--- app/Data/Api/Application/UserData.php | 6 +++--- app/Data/Api/Client/ActivityLogData.php | 2 +- app/Data/Api/Client/AllocationData.php | 2 +- app/Data/Api/Client/ApiKeyData.php | 2 +- app/Data/Api/Client/BackupData.php | 2 +- app/Data/Api/Client/DatabaseData.php | 2 +- app/Data/Api/Client/EggData.php | 2 +- app/Data/Api/Client/EggVariableData.php | 2 +- app/Data/Api/Client/FileObjectData.php | 2 +- app/Data/Api/Client/ScheduleData.php | 2 +- app/Data/Api/Client/ServerData.php | 2 +- app/Data/Api/Client/StatsData.php | 2 +- app/Data/Api/Client/SubuserData.php | 2 +- app/Data/Api/Client/TaskData.php | 2 +- app/Data/Api/Client/UserData.php | 6 +++--- app/Data/Api/Client/UserSSHKeyData.php | 2 +- 29 files changed, 41 insertions(+), 41 deletions(-) diff --git a/app/Data/Api/Application/AllocationData.php b/app/Data/Api/Application/AllocationData.php index fd569a1428..9e008c24f4 100644 --- a/app/Data/Api/Application/AllocationData.php +++ b/app/Data/Api/Application/AllocationData.php @@ -29,7 +29,7 @@ public static function getResourceName(): string public static function fromModel(Allocation $model): static { - return new static( + return new self( id: $model->id, ip: $model->ip, alias: $model->ip_alias, diff --git a/app/Data/Api/Application/DatabaseHostData.php b/app/Data/Api/Application/DatabaseHostData.php index 8bbdb9a7e6..5c6cda45ea 100644 --- a/app/Data/Api/Application/DatabaseHostData.php +++ b/app/Data/Api/Application/DatabaseHostData.php @@ -33,7 +33,7 @@ public static function getResourceName(): string public static function fromModel(DatabaseHost $model): static { - return new static( + return new self( id: $model->id, name: $model->name, host: $model->host, diff --git a/app/Data/Api/Application/EggData.php b/app/Data/Api/Application/EggData.php index 5b1df2ee4d..432673ccdc 100644 --- a/app/Data/Api/Application/EggData.php +++ b/app/Data/Api/Application/EggData.php @@ -53,7 +53,7 @@ public static function fromModel(Egg $model): static $model->loadMissing('scriptFrom'); - return new static( + return new self( id: $model->id, uuid: $model->uuid, name: $model->name, @@ -81,8 +81,8 @@ public static function fromModel(Egg $model): static 'container' => $model->copy_script_container, 'extends' => $model->copy_script_from, ], - created_at: static::formatTimestamp($model->created_at), - updated_at: static::formatTimestamp($model->updated_at), + created_at: self::formatTimestamp($model->created_at), + updated_at: self::formatTimestamp($model->updated_at), ); } diff --git a/app/Data/Api/Application/EggVariableData.php b/app/Data/Api/Application/EggVariableData.php index cafc0fecc9..b7f71c36a1 100644 --- a/app/Data/Api/Application/EggVariableData.php +++ b/app/Data/Api/Application/EggVariableData.php @@ -20,7 +20,7 @@ public static function getResourceName(): string public static function fromModel(EggVariable $model): static { - return new static($model->toArray()); + return new self($model->toArray()); } /** diff --git a/app/Data/Api/Application/MountData.php b/app/Data/Api/Application/MountData.php index 930b8b99b4..7489d7399a 100644 --- a/app/Data/Api/Application/MountData.php +++ b/app/Data/Api/Application/MountData.php @@ -26,7 +26,7 @@ public static function getResourceName(): string public static function fromModel(Mount $model): static { - return new static($model->toArray()); + return new self($model->toArray()); } /** diff --git a/app/Data/Api/Application/NodeData.php b/app/Data/Api/Application/NodeData.php index 73d797a764..6a8025646d 100644 --- a/app/Data/Api/Application/NodeData.php +++ b/app/Data/Api/Application/NodeData.php @@ -29,8 +29,8 @@ public static function fromModel(Node $model): static ->mapWithKeys(fn ($value, $key) => [snake_case($key) => $value]) ->toArray(); - $attributes[$model->getUpdatedAtColumn()] = static::formatTimestamp($model->updated_at); - $attributes[$model->getCreatedAtColumn()] = static::formatTimestamp($model->created_at); + $attributes[$model->getUpdatedAtColumn()] = self::formatTimestamp($model->updated_at); + $attributes[$model->getCreatedAtColumn()] = self::formatTimestamp($model->created_at); $resources = $model->servers()->select(['memory', 'disk', 'cpu'])->get(); @@ -40,7 +40,7 @@ public static function fromModel(Node $model): static 'cpu' => $resources->sum('cpu'), ]; - return new static($attributes); + return new self($attributes); } /** diff --git a/app/Data/Api/Application/PluginData.php b/app/Data/Api/Application/PluginData.php index dbc13b6eba..50d4d3a387 100644 --- a/app/Data/Api/Application/PluginData.php +++ b/app/Data/Api/Application/PluginData.php @@ -35,7 +35,7 @@ public static function getResourceName(): string public static function fromModel(Plugin $model): static { - return new static( + return new self( id: $model->id, name: $model->name, author: $model->author, diff --git a/app/Data/Api/Application/RoleData.php b/app/Data/Api/Application/RoleData.php index 8fed642a18..8c71656285 100644 --- a/app/Data/Api/Application/RoleData.php +++ b/app/Data/Api/Application/RoleData.php @@ -25,7 +25,7 @@ public static function getResourceName(): string public static function fromModel(Role $model): static { - return new static( + return new self( id: $model->id, name: $model->name, created_at: $model->created_at->toAtomString(), diff --git a/app/Data/Api/Application/RolePermissionData.php b/app/Data/Api/Application/RolePermissionData.php index 19deff004e..e6860ea9d3 100644 --- a/app/Data/Api/Application/RolePermissionData.php +++ b/app/Data/Api/Application/RolePermissionData.php @@ -20,7 +20,7 @@ public static function getResourceName(): string public static function fromModel(Permission $model): static { - return new static( + return new self( name: $model->name, created_at: $model->created_at->toAtomString(), updated_at: $model->updated_at->toAtomString(), diff --git a/app/Data/Api/Application/ServerData.php b/app/Data/Api/Application/ServerData.php index 71ff7be045..4de8126783 100644 --- a/app/Data/Api/Application/ServerData.php +++ b/app/Data/Api/Application/ServerData.php @@ -57,7 +57,7 @@ public static function getResourceName(): string public static function fromModel(Server $model, EnvironmentService $environmentService): static { - return new static( + return new self( id: $model->getKey(), external_id: $model->external_id, uuid: $model->uuid, @@ -94,8 +94,8 @@ public static function fromModel(Server $model, EnvironmentService $environmentS 'installed' => $model->isInstalled() ? 1 : 0, 'environment' => $environmentService->handle($model), ], - updated_at: static::formatTimestamp($model->updated_at), - created_at: static::formatTimestamp($model->created_at), + updated_at: self::formatTimestamp($model->updated_at), + created_at: self::formatTimestamp($model->created_at), ); } diff --git a/app/Data/Api/Application/ServerDatabaseData.php b/app/Data/Api/Application/ServerDatabaseData.php index b72bd0c6ab..c0227c0946 100644 --- a/app/Data/Api/Application/ServerDatabaseData.php +++ b/app/Data/Api/Application/ServerDatabaseData.php @@ -31,7 +31,7 @@ public static function getResourceName(): string public static function fromModel(Database $model): static { - return new static( + return new self( id: $model->id, server: $model->server_id, host: $model->database_host_id, diff --git a/app/Data/Api/Application/ServerVariableData.php b/app/Data/Api/Application/ServerVariableData.php index a8baabe056..ae06ebf98d 100644 --- a/app/Data/Api/Application/ServerVariableData.php +++ b/app/Data/Api/Application/ServerVariableData.php @@ -24,7 +24,7 @@ public static function getResourceName(): string public static function fromModel(EggVariable $model): static { - return new static($model->toArray()); + return new self($model->toArray()); } /** diff --git a/app/Data/Api/Application/SubuserData.php b/app/Data/Api/Application/SubuserData.php index 1e21c6cab1..723df7dbb6 100644 --- a/app/Data/Api/Application/SubuserData.php +++ b/app/Data/Api/Application/SubuserData.php @@ -29,13 +29,13 @@ public static function getResourceName(): string public static function fromModel(Subuser $model): static { - return new static( + return new self( id: $model->id, user_id: $model->user_id, server_id: $model->server_id, permissions: $model->permissions, - created_at: static::formatTimestamp($model->created_at), - updated_at: static::formatTimestamp($model->updated_at), + created_at: self::formatTimestamp($model->created_at), + updated_at: self::formatTimestamp($model->updated_at), ); } diff --git a/app/Data/Api/Application/UserData.php b/app/Data/Api/Application/UserData.php index 56688d6c64..6f690fa708 100644 --- a/app/Data/Api/Application/UserData.php +++ b/app/Data/Api/Application/UserData.php @@ -41,7 +41,7 @@ public static function getResourceName(): string public static function fromModel(User $model): static { - return new static( + return new self( id: $model->id, external_id: $model->external_id, is_managed_externally: $model->is_managed_externally, @@ -52,8 +52,8 @@ public static function fromModel(User $model): static root_admin: $model->isRootAdmin(), twoFactorEnabled: filled($model->mfa_app_secret), twoFactorLegacy: filled($model->mfa_app_secret), // deprecated, use "2fa_enabled" - created_at: static::formatTimestamp($model->created_at), - updated_at: static::formatTimestamp($model->updated_at), + created_at: self::formatTimestamp($model->created_at), + updated_at: self::formatTimestamp($model->updated_at), ); } diff --git a/app/Data/Api/Client/ActivityLogData.php b/app/Data/Api/Client/ActivityLogData.php index 6124341161..6ded8f6dd3 100644 --- a/app/Data/Api/Client/ActivityLogData.php +++ b/app/Data/Api/Client/ActivityLogData.php @@ -38,7 +38,7 @@ public static function fromModel(ActivityLog $model, Request $request): static // the actor that performed the action or because they are an administrator. $canViewIp = $model->actor?->is($request->user()) || $request->user()->can('seeIps activityLog'); - return new static( + return new self( // This is not for security, it is only to provide a unique identifier to // the front-end for each entry to improve rendering performance since there // is nothing else sufficiently unique to key off at this point. diff --git a/app/Data/Api/Client/AllocationData.php b/app/Data/Api/Client/AllocationData.php index 48ef5aa936..f2af32cdf9 100644 --- a/app/Data/Api/Client/AllocationData.php +++ b/app/Data/Api/Client/AllocationData.php @@ -23,7 +23,7 @@ public static function getResourceName(): string public static function fromModel(Allocation $model): static { - return new static( + return new self( id: $model->id, ip: $model->ip, ip_alias: $model->ip_alias, diff --git a/app/Data/Api/Client/ApiKeyData.php b/app/Data/Api/Client/ApiKeyData.php index e630d08e10..693eab2e24 100644 --- a/app/Data/Api/Client/ApiKeyData.php +++ b/app/Data/Api/Client/ApiKeyData.php @@ -25,7 +25,7 @@ public static function getResourceName(): string public static function fromModel(ApiKey $model): static { - return new static( + return new self( identifier: $model->identifier, description: $model->memo, allowed_ips: $model->allowed_ips, diff --git a/app/Data/Api/Client/BackupData.php b/app/Data/Api/Client/BackupData.php index 45a759034a..577acab4f6 100644 --- a/app/Data/Api/Client/BackupData.php +++ b/app/Data/Api/Client/BackupData.php @@ -30,7 +30,7 @@ public static function getResourceName(): string public static function fromModel(Backup $model): static { - return new static( + return new self( uuid: $model->uuid, is_successful: $model->is_successful, is_locked: $model->is_locked, diff --git a/app/Data/Api/Client/DatabaseData.php b/app/Data/Api/Client/DatabaseData.php index 517554cf08..c7e740f8f1 100644 --- a/app/Data/Api/Client/DatabaseData.php +++ b/app/Data/Api/Client/DatabaseData.php @@ -33,7 +33,7 @@ public static function fromModel(Database $model): static { $model->loadMissing('host'); - return new static( + return new self( id: $model->id, host: [ 'address' => $model->getRelation('host')->host, diff --git a/app/Data/Api/Client/EggData.php b/app/Data/Api/Client/EggData.php index 131bbabfa2..e5cd90ad31 100644 --- a/app/Data/Api/Client/EggData.php +++ b/app/Data/Api/Client/EggData.php @@ -19,7 +19,7 @@ public static function getResourceName(): string public static function fromModel(Egg $model): static { - return new static( + return new self( uuid: $model->uuid, name: $model->name, ); diff --git a/app/Data/Api/Client/EggVariableData.php b/app/Data/Api/Client/EggVariableData.php index 96435d366e..889cdd2d55 100644 --- a/app/Data/Api/Client/EggVariableData.php +++ b/app/Data/Api/Client/EggVariableData.php @@ -30,7 +30,7 @@ public static function fromModel(EggVariable $model): static // pathway since you should never be exposing these types of variables to a client. throw_unless($model->user_viewable, new BadMethodCallException('Cannot transform a hidden egg variable in a client transformer.')); - return new static( + return new self( name: $model->name, description: $model->description, env_variable: $model->env_variable, diff --git a/app/Data/Api/Client/FileObjectData.php b/app/Data/Api/Client/FileObjectData.php index 3933255a5f..3405a3605b 100644 --- a/app/Data/Api/Client/FileObjectData.php +++ b/app/Data/Api/Client/FileObjectData.php @@ -30,7 +30,7 @@ public static function getResourceName(): string */ public static function fromModel(array $model): static { - return new static( + return new self( name: Arr::get($model, 'name'), mode: Arr::get($model, 'mode'), mode_bits: Arr::get($model, 'mode_bits'), diff --git a/app/Data/Api/Client/ScheduleData.php b/app/Data/Api/Client/ScheduleData.php index 2acbf14648..67e5bffee7 100644 --- a/app/Data/Api/Client/ScheduleData.php +++ b/app/Data/Api/Client/ScheduleData.php @@ -37,7 +37,7 @@ public static function getResourceName(): string public static function fromModel(Schedule $model): static { - return new static( + return new self( id: $model->id, name: $model->name, cron: [ diff --git a/app/Data/Api/Client/ServerData.php b/app/Data/Api/Client/ServerData.php index 98bed70cdd..282ee32f97 100644 --- a/app/Data/Api/Client/ServerData.php +++ b/app/Data/Api/Client/ServerData.php @@ -55,7 +55,7 @@ public static function fromModel(Server $model, StartupCommandService $service, { $user = $request->user(); - return new static( + return new self( server_owner: $user->id === $model->owner_id, identifier: $model->uuid_short, internal_id: $model->id, diff --git a/app/Data/Api/Client/StatsData.php b/app/Data/Api/Client/StatsData.php index 138b71c215..bf33e1728c 100644 --- a/app/Data/Api/Client/StatsData.php +++ b/app/Data/Api/Client/StatsData.php @@ -26,7 +26,7 @@ public static function getResourceName(): string */ public static function fromModel(array $model): static { - return new static( + return new self( current_state: Arr::get($model, 'state', 'stopped'), is_suspended: Arr::get($model, 'is_suspended', false), resources: [ diff --git a/app/Data/Api/Client/SubuserData.php b/app/Data/Api/Client/SubuserData.php index d6cf4e2ef2..4c701ec3e4 100644 --- a/app/Data/Api/Client/SubuserData.php +++ b/app/Data/Api/Client/SubuserData.php @@ -35,7 +35,7 @@ public static function fromModel(Subuser $model): static { $user = UserData::fromModel($model->user); - return new static( + return new self( uuid: $user->uuid, username: $user->username, email: $user->email, diff --git a/app/Data/Api/Client/TaskData.php b/app/Data/Api/Client/TaskData.php index 4e23170d75..5feaeec57b 100644 --- a/app/Data/Api/Client/TaskData.php +++ b/app/Data/Api/Client/TaskData.php @@ -26,7 +26,7 @@ public static function getResourceName(): string public static function fromModel(Task $model): static { - return new static( + return new self( id: $model->id, sequence_id: $model->sequence_id, action: $model->action, diff --git a/app/Data/Api/Client/UserData.php b/app/Data/Api/Client/UserData.php index 95a4ffafea..faa2edc361 100644 --- a/app/Data/Api/Client/UserData.php +++ b/app/Data/Api/Client/UserData.php @@ -30,7 +30,7 @@ public static function getResourceName(): string public static function fromModel(User $model): static { - return new static( + return new self( uuid: $model->uuid, username: $model->username, email: $model->email, @@ -39,8 +39,8 @@ public static function fromModel(User $model): static admin: $model->isRootAdmin(), // deprecated, use "root_admin" root_admin: $model->isRootAdmin(), two_factor_enabled: filled($model->mfa_app_secret), - created_at: static::formatTimestamp($model->created_at), - updated_at: static::formatTimestamp($model->updated_at), + created_at: self::formatTimestamp($model->created_at), + updated_at: self::formatTimestamp($model->updated_at), ); } } diff --git a/app/Data/Api/Client/UserSSHKeyData.php b/app/Data/Api/Client/UserSSHKeyData.php index d07eecd2eb..ea7a1dc047 100644 --- a/app/Data/Api/Client/UserSSHKeyData.php +++ b/app/Data/Api/Client/UserSSHKeyData.php @@ -21,7 +21,7 @@ public static function getResourceName(): string public static function fromModel(UserSSHKey $model): static { - return new static( + return new self( name: $model->name, fingerprint: $model->fingerprint, public_key: $model->public_key,