api(application): v2 backport

This commit is contained in:
Matthew Penner 2022-12-14 17:05:46 -07:00
parent 4cd0bee231
commit 67bf3e342e
No known key found for this signature in database
172 changed files with 2922 additions and 1579 deletions

View file

@ -233,7 +233,7 @@ final class Handler extends ExceptionHandler
/**
* Return an array of exceptions that should not be reported.
*/
public static function isReportable(\Exception $exception): bool
public static function isReportable(Exception $exception): bool
{
return (new static(Container::getInstance()))->shouldReport($exception);
}

View file

@ -0,0 +1,21 @@
<?php
namespace Pterodactyl\Exceptions\Http;
use Illuminate\Http\Response;
use Symfony\Component\HttpKernel\Exception\HttpException;
class QueryValueOutOfRangeHttpException extends HttpException
{
/**
* QueryValueOutOfRangeHttpException constructor.
*/
public function __construct(string $name, int $min, int $max, \Throwable $previous = null)
{
parent::__construct(
Response::HTTP_BAD_REQUEST,
'\"' . $name . '\" query parameter must be between ' . $min . ' and ' . $max,
$previous,
);
}
}

View file

@ -1,14 +0,0 @@
<?php
namespace Pterodactyl\Exceptions;
use Spatie\Ignition\Contracts\Solution;
use Spatie\Ignition\Contracts\ProvidesSolution;
class ManifestDoesNotExistException extends \Exception implements ProvidesSolution
{
public function getSolution(): Solution
{
return new Solutions\ManifestDoesNotExistSolution();
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Exceptions\Service\Egg;
use Pterodactyl\Exceptions\DisplayException;
class BadYamlFormatException extends DisplayException
{
}

View file

@ -1,25 +0,0 @@
<?php
namespace Pterodactyl\Exceptions\Solutions;
use Spatie\Ignition\Contracts\Solution;
class ManifestDoesNotExistSolution implements Solution
{
public function getSolutionTitle(): string
{
return "The manifest.json file hasn't been generated yet";
}
public function getSolutionDescription(): string
{
return 'Run yarn run build:production to build the frontend first.';
}
public function getDocumentationLinks(): array
{
return [
'Docs' => 'https://github.com/pterodactyl/panel/blob/develop/package.json',
];
}
}

View file

@ -1,9 +0,0 @@
<?php
namespace Pterodactyl\Exceptions\Transformer;
use Pterodactyl\Exceptions\PterodactylException;
class InvalidTransformerLevelException extends PterodactylException
{
}

View file

@ -3,19 +3,16 @@
namespace Pterodactyl\Http\Controllers\Api\Application;
use Illuminate\Http\Request;
use Webmozart\Assert\Assert;
use Illuminate\Http\Response;
use Illuminate\Support\Collection;
use Illuminate\Container\Container;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Extensions\Spatie\Fractalistic\Fractal;
use Pterodactyl\Transformers\Api\Application\BaseTransformer;
abstract class ApplicationApiController extends Controller
{
protected Request $request;
protected Fractal $fractal;
protected Request $request;
/**
* ApplicationApiController constructor.
@ -47,21 +44,11 @@ abstract class ApplicationApiController extends Controller
}
/**
* Return an instance of an application transformer.
*
* @template T of \Pterodactyl\Transformers\Api\Application\BaseTransformer
*
* @param class-string<T> $abstract
*
* @return T
*
* @noinspection PhpDocSignatureInspection
* Return an HTTP/201 response for the API.
*/
public function getTransformer(string $abstract)
protected function returnAccepted(): Response
{
Assert::subclassOf($abstract, BaseTransformer::class);
return $abstract::fromRequest($this->request);
return new Response('', Response::HTTP_ACCEPTED);
}
/**

View file

@ -0,0 +1,99 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Databases;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Models\DatabaseHost;
use Spatie\QueryBuilder\QueryBuilder;
use Pterodactyl\Services\Databases\Hosts\HostUpdateService;
use Pterodactyl\Services\Databases\Hosts\HostCreationService;
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
use Pterodactyl\Transformers\Api\Application\DatabaseHostTransformer;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
use Pterodactyl\Http\Requests\Api\Application\Databases\GetDatabaseRequest;
use Pterodactyl\Http\Requests\Api\Application\Databases\GetDatabasesRequest;
use Pterodactyl\Http\Requests\Api\Application\Databases\StoreDatabaseRequest;
use Pterodactyl\Http\Requests\Api\Application\Databases\DeleteDatabaseRequest;
use Pterodactyl\Http\Requests\Api\Application\Databases\UpdateDatabaseRequest;
class DatabaseController extends ApplicationApiController
{
/**
* DatabaseController constructor.
*/
public function __construct(private HostCreationService $creationService, private HostUpdateService $updateService)
{
parent::__construct();
}
/**
* Returns an array of all database hosts.
*/
public function index(GetDatabasesRequest $request): array
{
$perPage = (int) $request->query('per_page', '10');
if ($perPage < 1 || $perPage > 100) {
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
}
$databases = QueryBuilder::for(DatabaseHost::query())
->allowedFilters(['name', 'host'])
->allowedSorts(['id', 'name', 'host'])
->paginate($perPage);
return $this->fractal->collection($databases)
->transformWith(DatabaseHostTransformer::class)
->toArray();
}
/**
* Returns a single database host.
*/
public function view(GetDatabaseRequest $request, DatabaseHost $databaseHost): array
{
return $this->fractal->item($databaseHost)
->transformWith(DatabaseHostTransformer::class)
->toArray();
}
/**
* Creates a new database host.
*
* @throws \Throwable
*/
public function store(StoreDatabaseRequest $request): JsonResponse
{
$databaseHost = $this->creationService->handle($request->validated());
return $this->fractal->item($databaseHost)
->transformWith(DatabaseHostTransformer::class)
->respond(JsonResponse::HTTP_CREATED);
}
/**
* Updates a database host.
*
* @throws \Throwable
*/
public function update(UpdateDatabaseRequest $request, DatabaseHost $databaseHost): array
{
$databaseHost = $this->updateService->handle($databaseHost->id, $request->validated());
return $this->fractal->item($databaseHost)
->transformWith(DatabaseHostTransformer::class)
->toArray();
}
/**
* Deletes a database host.
*
* @throws \Exception
*/
public function delete(DeleteDatabaseRequest $request, DatabaseHost $databaseHost): Response
{
$databaseHost->delete();
return $this->returnNoContent();
}
}

View file

@ -0,0 +1,120 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Eggs;
use Ramsey\Uuid\Uuid;
use Pterodactyl\Models\Egg;
use Pterodactyl\Models\Nest;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;
use Spatie\QueryBuilder\QueryBuilder;
use Pterodactyl\Services\Eggs\Sharing\EggExporterService;
use Pterodactyl\Transformers\Api\Application\EggTransformer;
use Pterodactyl\Http\Requests\Api\Application\Eggs\GetEggRequest;
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
use Pterodactyl\Http\Requests\Api\Application\Eggs\GetEggsRequest;
use Pterodactyl\Http\Requests\Api\Application\Eggs\StoreEggRequest;
use Pterodactyl\Http\Requests\Api\Application\Eggs\DeleteEggRequest;
use Pterodactyl\Http\Requests\Api\Application\Eggs\ExportEggRequest;
use Pterodactyl\Http\Requests\Api\Application\Eggs\UpdateEggRequest;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class EggController extends ApplicationApiController
{
/**
* EggController constructor.
*/
public function __construct(private EggExporterService $eggExporterService)
{
parent::__construct();
$this->eggExporterService = $eggExporterService;
}
/**
* Return an array of all eggs on a given nest.
*/
public function index(GetEggsRequest $request, Nest $nest): array
{
$perPage = (int) $request->query('per_page', '10');
if ($perPage > 100) {
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
}
// @phpstan-ignore-next-line
$eggs = QueryBuilder::for(Egg::query())
->where('nest_id', '=', $nest->id)
->allowedFilters(['id', 'name', 'author'])
->allowedSorts(['id', 'name', 'author']);
if ($perPage > 0) {
$eggs = $eggs->paginate($perPage);
}
return $this->fractal->collection($eggs)
->transformWith(EggTransformer::class)
->toArray();
}
/**
* Returns a single egg.
*/
public function view(GetEggRequest $request, Egg $egg): array
{
return $this->fractal->item($egg)
->transformWith(EggTransformer::class)
->toArray();
}
/**
* Creates a new egg.
*/
public function store(StoreEggRequest $request): JsonResponse
{
$validated = $request->validated();
$merged = array_merge($validated, [
'uuid' => Uuid::uuid4()->toString(),
// TODO: allow this to be set in the request, and default to config value if null or not present.
'author' => config('pterodactyl.service.author'),
]);
$egg = Egg::query()->create($merged);
return $this->fractal->item($egg)
->transformWith(EggTransformer::class)
->respond(Response::HTTP_CREATED);
}
/**
* Updates an egg.
*/
public function update(UpdateEggRequest $request, Egg $egg): array
{
$egg->update($request->validated());
return $this->fractal->item($egg)
->transformWith(EggTransformer::class)
->toArray();
}
/**
* Deletes an egg.
*
* @throws \Exception
*/
public function delete(DeleteEggRequest $request, Egg $egg): Response
{
$egg->delete();
return $this->returnNoContent();
}
/**
* Exports an egg.
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function export(ExportEggRequest $request, int $eggId): JsonResponse
{
return new JsonResponse($this->eggExporterService->handle($eggId));
}
}

View file

@ -0,0 +1,75 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Eggs;
use Pterodactyl\Models\Egg;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Pterodactyl\Models\EggVariable;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Services\Eggs\Variables\VariableUpdateService;
use Pterodactyl\Services\Eggs\Variables\VariableCreationService;
use Pterodactyl\Transformers\Api\Application\EggVariableTransformer;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
use Pterodactyl\Http\Requests\Api\Application\Eggs\Variables\StoreEggVariableRequest;
use Pterodactyl\Http\Requests\Api\Application\Eggs\Variables\UpdateEggVariablesRequest;
class EggVariableController extends ApplicationApiController
{
public function __construct(
private ConnectionInterface $connection,
private VariableCreationService $variableCreationService,
private VariableUpdateService $variableUpdateService
) {
parent::__construct();
}
/**
* Creates a new egg variable.
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\BadValidationRuleException
* @throws \Pterodactyl\Exceptions\Service\Egg\Variable\ReservedVariableNameException
*/
public function store(StoreEggVariableRequest $request, Egg $egg): array
{
$variable = $this->variableCreationService->handle($egg->id, $request->validated());
return $this->fractal->item($variable)
->transformWith(EggVariableTransformer::class)
->toArray();
}
/**
* Updates multiple egg variables.
*
* @throws \Throwable
*/
public function update(UpdateEggVariablesRequest $request, Egg $egg): array
{
$validated = $request->validated();
$this->connection->transaction(function () use ($egg, $validated) {
foreach ($validated as $data) {
$this->variableUpdateService->handle($egg, $data);
}
});
return $this->fractal->collection($egg->refresh()->variables)
->transformWith(EggVariableTransformer::class)
->toArray();
}
/**
* Deletes a single egg variable.
*/
public function delete(Request $request, Egg $egg, EggVariable $eggVariable): Response
{
EggVariable::query()
->where('id', $eggVariable->id)
->where('egg_id', $egg->id)
->delete();
return $this->returnNoContent();
}
}

View file

@ -10,6 +10,7 @@ use Pterodactyl\Services\Locations\LocationUpdateService;
use Pterodactyl\Services\Locations\LocationCreationService;
use Pterodactyl\Services\Locations\LocationDeletionService;
use Pterodactyl\Transformers\Api\Application\LocationTransformer;
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationRequest;
use Pterodactyl\Http\Requests\Api\Application\Locations\GetLocationsRequest;
@ -35,13 +36,18 @@ class LocationController extends ApplicationApiController
*/
public function index(GetLocationsRequest $request): array
{
$perPage = (int) $request->query('per_page', '10');
if ($perPage < 1 || $perPage > 100) {
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
}
$locations = QueryBuilder::for(Location::query())
->allowedFilters(['short', 'long'])
->allowedSorts(['id'])
->paginate($request->query('per_page') ?? 50);
->allowedSorts(['id', 'short', 'long'])
->paginate($perPage);
return $this->fractal->collection($locations)
->transformWith($this->getTransformer(LocationTransformer::class))
->transformWith(LocationTransformer::class)
->toArray();
}
@ -51,7 +57,7 @@ class LocationController extends ApplicationApiController
public function view(GetLocationRequest $request, Location $location): array
{
return $this->fractal->item($location)
->transformWith($this->getTransformer(LocationTransformer::class))
->transformWith(LocationTransformer::class)
->toArray();
}
@ -66,12 +72,7 @@ class LocationController extends ApplicationApiController
$location = $this->creationService->handle($request->validated());
return $this->fractal->item($location)
->transformWith($this->getTransformer(LocationTransformer::class))
->addMeta([
'resource' => route('api.application.locations.view', [
'location' => $location->id,
]),
])
->transformWith(LocationTransformer::class)
->respond(201);
}
@ -86,7 +87,7 @@ class LocationController extends ApplicationApiController
$location = $this->updateService->handle($location, $request->validated());
return $this->fractal->item($location)
->transformWith($this->getTransformer(LocationTransformer::class))
->transformWith(LocationTransformer::class)
->toArray();
}
@ -99,6 +100,6 @@ class LocationController extends ApplicationApiController
{
$this->deletionService->handle($location);
return response('', 204);
return $this->returnNoContent();
}
}

View file

@ -0,0 +1,163 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Mounts;
use Illuminate\Http\Response;
use Pterodactyl\Models\Mount;
use Illuminate\Http\JsonResponse;
use Spatie\QueryBuilder\QueryBuilder;
use Pterodactyl\Transformers\Api\Application\MountTransformer;
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
use Pterodactyl\Http\Requests\Api\Application\Mounts\GetMountRequest;
use Pterodactyl\Http\Requests\Api\Application\Mounts\GetMountsRequest;
use Pterodactyl\Http\Requests\Api\Application\Mounts\MountEggsRequest;
use Pterodactyl\Http\Requests\Api\Application\Mounts\MountNodesRequest;
use Pterodactyl\Http\Requests\Api\Application\Mounts\StoreMountRequest;
use Pterodactyl\Http\Requests\Api\Application\Mounts\DeleteMountRequest;
use Pterodactyl\Http\Requests\Api\Application\Mounts\UpdateMountRequest;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class MountController extends ApplicationApiController
{
/**
* MountController constructor.
*/
public function __construct()
{
parent::__construct();
}
/**
* Returns an array of all mount.
*/
public function index(GetMountsRequest $request): array
{
$perPage = (int) $request->query('per_page', '10');
if ($perPage < 1 || $perPage > 100) {
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
}
$mounts = QueryBuilder::for(Mount::query())
->allowedFilters(['id', 'name', 'source', 'target'])
->allowedSorts(['id', 'name', 'source', 'target'])
->paginate($perPage);
return $this->fractal->collection($mounts)
->transformWith(MountTransformer::class)
->toArray();
}
/**
* Returns a single mount.
*/
public function view(GetMountRequest $request, Mount $mount): array
{
return $this->fractal->item($mount)
->transformWith(MountTransformer::class)
->toArray();
}
/**
* Creates a new mount.
*/
public function store(StoreMountRequest $request): JsonResponse
{
$mount = Mount::query()->create($request->validated());
return $this->fractal->item($mount)
->transformWith(MountTransformer::class)
->respond(JsonResponse::HTTP_CREATED);
}
/**
* Updates a mount.
*/
public function update(UpdateMountRequest $request, Mount $mount): array
{
$mount->update($request->validated());
return $this->fractal->item($mount)
->transformWith(MountTransformer::class)
->toArray();
}
/**
* Deletes a mount.
*
* @throws \Exception
*/
public function delete(DeleteMountRequest $request, Mount $mount): Response
{
$mount->delete();
return $this->returnNoContent();
}
/**
* Attaches eggs to a mount.
*/
public function addEggs(MountEggsRequest $request, Mount $mount): array
{
$data = $request->validated();
$eggs = $data['eggs'] ?? [];
if (count($eggs) > 0) {
$mount->eggs()->syncWithoutDetaching($eggs);
}
return $this->fractal->item($mount)
->transformWith(MountTransformer::class)
->toArray();
}
/**
* Attaches nodes to a mount.
*/
public function addNodes(MountNodesRequest $request, Mount $mount): array
{
$data = $request->validated();
$nodes = $data['nodes'] ?? [];
if (count($nodes) > 0) {
$mount->nodes()->syncWithoutDetaching($nodes);
}
return $this->fractal->item($mount)
->transformWith(MountTransformer::class)
->toArray();
}
/**
* Detaches eggs from a mount.
*/
public function deleteEggs(MountEggsRequest $request, Mount $mount): array
{
$data = $request->validated();
$eggs = $data['eggs'] ?? [];
if (count($eggs) > 0) {
$mount->eggs()->detach($eggs);
}
return $this->fractal->item($mount)
->transformWith(MountTransformer::class)
->toArray();
}
/**
* Detaches nodes from a mount.
*/
public function deleteNodes(MountNodesRequest $request, Mount $mount): array
{
$data = $request->validated();
$nodes = $data['nodes'] ?? [];
if (count($nodes) > 0) {
$mount->nodes()->detach($nodes);
}
return $this->fractal->item($mount)
->transformWith(MountTransformer::class)
->toArray();
}
}

View file

@ -1,33 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Nests;
use Pterodactyl\Models\Egg;
use Pterodactyl\Models\Nest;
use Pterodactyl\Transformers\Api\Application\EggTransformer;
use Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggRequest;
use Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggsRequest;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class EggController extends ApplicationApiController
{
/**
* Return all eggs that exist for a given nest.
*/
public function index(GetEggsRequest $request, Nest $nest): array
{
return $this->fractal->collection($nest->eggs)
->transformWith($this->getTransformer(EggTransformer::class))
->toArray();
}
/**
* Return a single egg that exists on the specified nest.
*/
public function view(GetEggRequest $request, Nest $nest, Egg $egg): array
{
return $this->fractal->item($egg)
->transformWith($this->getTransformer(EggTransformer::class))
->toArray();
}
}

View file

@ -3,9 +3,21 @@
namespace Pterodactyl\Http\Controllers\Api\Application\Nests;
use Pterodactyl\Models\Nest;
use Pterodactyl\Contracts\Repository\NestRepositoryInterface;
use Illuminate\Http\Response;
use Spatie\QueryBuilder\QueryBuilder;
use Pterodactyl\Services\Nests\NestUpdateService;
use Pterodactyl\Services\Nests\NestCreationService;
use Pterodactyl\Services\Nests\NestDeletionService;
use Pterodactyl\Services\Eggs\Sharing\EggImporterService;
use Pterodactyl\Transformers\Api\Application\EggTransformer;
use Pterodactyl\Transformers\Api\Application\NestTransformer;
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestRequest;
use Pterodactyl\Http\Requests\Api\Application\Eggs\ImportEggRequest;
use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestsRequest;
use Pterodactyl\Http\Requests\Api\Application\Nests\StoreNestRequest;
use Pterodactyl\Http\Requests\Api\Application\Nests\DeleteNestRequest;
use Pterodactyl\Http\Requests\Api\Application\Nests\UpdateNestRequest;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class NestController extends ApplicationApiController
@ -13,8 +25,12 @@ class NestController extends ApplicationApiController
/**
* NestController constructor.
*/
public function __construct(private NestRepositoryInterface $repository)
{
public function __construct(
private NestCreationService $nestCreationService,
private NestDeletionService $nestDeletionService,
private NestUpdateService $nestUpdateService,
private EggImporterService $eggImporterService
) {
parent::__construct();
}
@ -23,20 +39,87 @@ class NestController extends ApplicationApiController
*/
public function index(GetNestsRequest $request): array
{
$nests = $this->repository->paginated($request->query('per_page') ?? 50);
$perPage = (int) $request->query('per_page', '10');
if ($perPage > 100) {
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
}
$nests = QueryBuilder::for(Nest::query())
->allowedFilters(['id', 'name', 'author'])
->allowedSorts(['id', 'name', 'author']);
if ($perPage > 0) {
$nests = $nests->paginate($perPage);
}
return $this->fractal->collection($nests)
->transformWith($this->getTransformer(NestTransformer::class))
->transformWith(NestTransformer::class)
->toArray();
}
/**
* Return information about a single Nest model.
*/
public function view(GetNestsRequest $request, Nest $nest): array
public function view(GetNestRequest $request, Nest $nest): array
{
return $this->fractal->item($nest)
->transformWith($this->getTransformer(NestTransformer::class))
->transformWith(NestTransformer::class)
->toArray();
}
/**
* Creates a new nest.
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/
public function store(StoreNestRequest $request): array
{
$nest = $this->nestCreationService->handle($request->validated());
return $this->fractal->item($nest)
->transformWith(NestTransformer::class)
->toArray();
}
/**
* Imports an egg.
*/
public function import(ImportEggRequest $request, Nest $nest): array
{
$egg = $this->eggImporterService->handleContent(
$nest->id,
$request->getContent(),
$request->headers->get('Content-Type'),
);
return $this->fractal->item($egg)
->transformWith(EggTransformer::class)
->toArray();
}
/**
* Updates an existing nest.
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function update(UpdateNestRequest $request, Nest $nest): array
{
$this->nestUpdateService->handle($nest->id, $request->validated());
return $this->fractal->item($nest)
->transformWith(NestTransformer::class)
->toArray();
}
/**
* Deletes an existing nest.
*
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
*/
public function delete(DeleteNestRequest $request, Nest $nest): Response
{
$this->nestDeletionService->handle($nest->id);
return $this->returnNoContent();
}
}

View file

@ -3,13 +3,14 @@
namespace Pterodactyl\Http\Controllers\Api\Application\Nodes;
use Pterodactyl\Models\Node;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Response;
use Pterodactyl\Models\Allocation;
use Spatie\QueryBuilder\QueryBuilder;
use Spatie\QueryBuilder\AllowedFilter;
use Illuminate\Database\Eloquent\Builder;
use Pterodactyl\Services\Allocations\AssignmentService;
use Pterodactyl\Services\Allocations\AllocationDeletionService;
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
use Pterodactyl\Transformers\Api\Application\AllocationTransformer;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
use Pterodactyl\Http\Requests\Api\Application\Allocations\GetAllocationsRequest;
@ -33,23 +34,27 @@ class AllocationController extends ApplicationApiController
*/
public function index(GetAllocationsRequest $request, Node $node): array
{
$allocations = QueryBuilder::for($node->allocations())
->allowedFilters([
AllowedFilter::exact('ip'),
AllowedFilter::exact('port'),
'ip_alias',
AllowedFilter::callback('server_id', function (Builder $builder, $value) {
if (empty($value) || is_bool($value) || !ctype_digit((string) $value)) {
return $builder->whereNull('server_id');
$perPage = (int) $request->query('per_page', '10');
if ($perPage < 1 || $perPage > 100) {
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
}
return $builder->where('server_id', $value);
$allocations = QueryBuilder::for(Allocation::query()->where('node_id', '=', $node->id))
->allowedFilters([
'id', 'ip', 'port', 'alias',
AllowedFilter::callback('server_id', function (Builder $query, $value) {
if ($value === '0') {
$query->whereNull('server_id');
} else {
$query->where('server_id', '=', $value);
}
}),
])
->paginate($request->query('per_page') ?? 50);
->allowedSorts(['id', 'ip', 'port', 'server_id'])
->paginate($perPage);
return $this->fractal->collection($allocations)
->transformWith($this->getTransformer(AllocationTransformer::class))
->transformWith(AllocationTransformer::class)
->toArray();
}
@ -62,11 +67,11 @@ class AllocationController extends ApplicationApiController
* @throws \Pterodactyl\Exceptions\Service\Allocation\PortOutOfRangeException
* @throws \Pterodactyl\Exceptions\Service\Allocation\TooManyPortsInRangeException
*/
public function store(StoreAllocationRequest $request, Node $node): JsonResponse
public function store(StoreAllocationRequest $request, Node $node): Response
{
$this->assignmentService->handle($node, $request->validated());
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
return $this->returnNoContent();
}
/**
@ -74,10 +79,10 @@ class AllocationController extends ApplicationApiController
*
* @throws \Pterodactyl\Exceptions\Service\Allocation\ServerUsingAllocationException
*/
public function delete(DeleteAllocationRequest $request, Node $node, Allocation $allocation): JsonResponse
public function delete(DeleteAllocationRequest $request, Node $node, Allocation $allocation): Response
{
$this->deletionService->handle($allocation);
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
return $this->returnNoContent();
}
}

View file

@ -14,8 +14,12 @@ class NodeConfigurationController extends ApplicationApiController
* to remote machines so long as an API key is provided to the machine to make the request
* with, and the node is known.
*/
public function __invoke(GetNodeRequest $request, Node $node): JsonResponse
public function __invoke(GetNodeRequest $request, Node $node): JsonResponse|string
{
if ($request->query('format') === 'yaml') {
return $node->getYamlConfiguration();
}
return new JsonResponse($node->getConfiguration());
}
}

View file

@ -3,12 +3,14 @@
namespace Pterodactyl\Http\Controllers\Api\Application\Nodes;
use Pterodactyl\Models\Node;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;
use Spatie\QueryBuilder\QueryBuilder;
use Pterodactyl\Services\Nodes\NodeUpdateService;
use Pterodactyl\Services\Nodes\NodeCreationService;
use Pterodactyl\Services\Nodes\NodeDeletionService;
use Pterodactyl\Transformers\Api\Application\NodeTransformer;
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodeRequest;
use Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodesRequest;
use Pterodactyl\Http\Requests\Api\Application\Nodes\StoreNodeRequest;
@ -34,13 +36,18 @@ class NodeController extends ApplicationApiController
*/
public function index(GetNodesRequest $request): array
{
$perPage = (int) $request->query('per_page', '10');
if ($perPage < 1 || $perPage > 100) {
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
}
$nodes = QueryBuilder::for(Node::query())
->allowedFilters(['uuid', 'name', 'fqdn', 'daemon_token_id'])
->allowedSorts(['id', 'uuid', 'memory', 'disk'])
->paginate($request->query('per_page') ?? 50);
->allowedFilters(['id', 'uuid', 'name', 'fqdn', 'daemon_token_id'])
->allowedSorts(['id', 'uuid', 'name', 'location_id', 'fqdn', 'memory', 'disk'])
->paginate($perPage);
return $this->fractal->collection($nodes)
->transformWith($this->getTransformer(NodeTransformer::class))
->transformWith(NodeTransformer::class)
->toArray();
}
@ -50,7 +57,7 @@ class NodeController extends ApplicationApiController
public function view(GetNodeRequest $request, Node $node): array
{
return $this->fractal->item($node)
->transformWith($this->getTransformer(NodeTransformer::class))
->transformWith(NodeTransformer::class)
->toArray();
}
@ -65,12 +72,7 @@ class NodeController extends ApplicationApiController
$node = $this->creationService->handle($request->validated());
return $this->fractal->item($node)
->transformWith($this->getTransformer(NodeTransformer::class))
->addMeta([
'resource' => route('api.application.nodes.view', [
'node' => $node->id,
]),
])
->transformWith(NodeTransformer::class)
->respond(201);
}
@ -84,11 +86,10 @@ class NodeController extends ApplicationApiController
$node = $this->updateService->handle(
$node,
$request->validated(),
$request->input('reset_secret') === true
);
return $this->fractal->item($node)
->transformWith($this->getTransformer(NodeTransformer::class))
->transformWith(NodeTransformer::class)
->toArray();
}
@ -98,10 +99,10 @@ class NodeController extends ApplicationApiController
*
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
*/
public function delete(DeleteNodeRequest $request, Node $node): JsonResponse
public function delete(DeleteNodeRequest $request, Node $node): Response
{
$this->deletionService->handle($node);
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
return $this->returnNoContent();
}
}

View file

@ -30,10 +30,10 @@ class NodeDeploymentController extends ApplicationApiController
$nodes = $this->viableNodesService->setLocations($data['location_ids'] ?? [])
->setMemory($data['memory'])
->setDisk($data['disk'])
->handle($request->query('per_page'), $request->query('page'));
->handle($request->query('per_page'), $request->query('page')); // @phpstan-ignore-line
return $this->fractal->collection($nodes)
->transformWith($this->getTransformer(NodeTransformer::class))
->transformWith(NodeTransformer::class)
->toArray();
}
}

View file

@ -0,0 +1,47 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Nodes;
use Carbon\Carbon;
use Illuminate\Support\Str;
use Illuminate\Http\Request;
use Pterodactyl\Models\Node;
use Illuminate\Http\JsonResponse;
use Illuminate\Cache\Repository as CacheRepository;
use Pterodactyl\Repositories\Wings\DaemonConfigurationRepository;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class NodeInformationController extends ApplicationApiController
{
/**
* NodeInformationController constructor.
*/
public function __construct(private CacheRepository $cache, private DaemonConfigurationRepository $repository)
{
parent::__construct();
}
/**
* Returns system information from the node.
*
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
*/
public function __invoke(Request $request, Node $node): JsonResponse
{
$data = $this->cache
->tags(['nodes'])
->remember($node->uuid, Carbon::now()->addSeconds(30), function () use ($node) {
return $this->repository->setNode($node)->getSystemInformation();
});
return new JsonResponse([
'version' => $data['version'] ?? null,
'system' => [
'type' => Str::title($data['os'] ?? 'Unknown'),
'arch' => $data['architecture'] ?? null,
'release' => $data['kernel_version'] ?? null,
'cpus' => $data['cpu_count'] ?? null,
],
]);
}
}

View file

@ -0,0 +1,96 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Roles;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Models\AdminRole;
use Spatie\QueryBuilder\QueryBuilder;
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
use Pterodactyl\Transformers\Api\Application\AdminRoleTransformer;
use Pterodactyl\Http\Requests\Api\Application\Roles\GetRoleRequest;
use Pterodactyl\Http\Requests\Api\Application\Roles\GetRolesRequest;
use Pterodactyl\Http\Requests\Api\Application\Roles\StoreRoleRequest;
use Pterodactyl\Http\Requests\Api\Application\Roles\DeleteRoleRequest;
use Pterodactyl\Http\Requests\Api\Application\Roles\UpdateRoleRequest;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class RoleController extends ApplicationApiController
{
/**
* RoleController constructor.
*/
public function __construct()
{
parent::__construct();
}
/**
* Returns an array of all roles.
*/
public function index(GetRolesRequest $request): array
{
$perPage = (int) $request->query('per_page', '10');
if ($perPage < 1 || $perPage > 100) {
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
}
$roles = QueryBuilder::for(AdminRole::query())
->allowedFilters(['id', 'name'])
->allowedSorts(['id', 'name'])
->paginate($perPage);
return $this->fractal->collection($roles)
->transformWith(AdminRoleTransformer::class)
->toArray();
}
/**
* Returns a single role.
*/
public function view(GetRoleRequest $request, AdminRole $role): array
{
return $this->fractal->item($role)
->transformWith(AdminRoleTransformer::class)
->toArray();
}
/**
* Creates a new role.
*/
public function store(StoreRoleRequest $request): JsonResponse
{
$data = array_merge($request->validated(), [
'sort_id' => 99,
]);
$role = AdminRole::query()->create($data);
return $this->fractal->item($role)
->transformWith(AdminRoleTransformer::class)
->respond(JsonResponse::HTTP_CREATED);
}
/**
* Updates a role.
*/
public function update(UpdateRoleRequest $request, AdminRole $role): array
{
$role->update($request->validated());
return $this->fractal->item($role)
->transformWith(AdminRoleTransformer::class)
->toArray();
}
/**
* Deletes a role.
*
* @throws \Exception
*/
public function delete(DeleteRoleRequest $request, AdminRole $role): Response
{
$role->delete();
return $this->returnNoContent();
}
}

View file

@ -34,7 +34,7 @@ class DatabaseController extends ApplicationApiController
public function index(GetServerDatabasesRequest $request, Server $server): array
{
return $this->fractal->collection($server->databases)
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
->transformWith(ServerDatabaseTransformer::class)
->toArray();
}
@ -44,7 +44,7 @@ class DatabaseController extends ApplicationApiController
public function view(GetServerDatabaseRequest $request, Server $server, Database $database): array
{
return $this->fractal->item($database)
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
->transformWith(ServerDatabaseTransformer::class)
->toArray();
}
@ -53,11 +53,11 @@ class DatabaseController extends ApplicationApiController
*
* @throws \Throwable
*/
public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): JsonResponse
public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): Response
{
$this->databasePasswordService->handle($database);
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
return $this->returnNoContent();
}
/**
@ -72,23 +72,19 @@ class DatabaseController extends ApplicationApiController
]));
return $this->fractal->item($database)
->transformWith($this->getTransformer(ServerDatabaseTransformer::class))
->addMeta([
'resource' => route('api.application.servers.databases.view', [
'server' => $server->id,
'database' => $database->id,
]),
])
->transformWith(ServerDatabaseTransformer::class)
->respond(Response::HTTP_CREATED);
}
/**
* Handle a request to delete a specific server database from the Panel.
*
* @throws \Exception
*/
public function delete(ServerDatabaseWriteRequest $request, Server $server, Database $database): Response
public function delete(ServerDatabaseWriteRequest $request, Database $database): Response
{
$this->databaseManagementService->delete($database);
return response('', 204);
return $this->returnNoContent();
}
}

View file

@ -17,7 +17,7 @@ class ExternalServerController extends ApplicationApiController
$server = Server::query()->where('external_id', $external_id)->firstOrFail();
return $this->fractal->item($server)
->transformWith($this->getTransformer(ServerTransformer::class))
->transformWith(ServerTransformer::class)
->toArray();
}
}

View file

@ -8,12 +8,16 @@ use Illuminate\Http\JsonResponse;
use Spatie\QueryBuilder\QueryBuilder;
use Pterodactyl\Services\Servers\ServerCreationService;
use Pterodactyl\Services\Servers\ServerDeletionService;
use Pterodactyl\Services\Servers\BuildModificationService;
use Pterodactyl\Services\Servers\DetailsModificationService;
use Pterodactyl\Transformers\Api\Application\ServerTransformer;
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
use Pterodactyl\Http\Requests\Api\Application\Servers\GetServerRequest;
use Pterodactyl\Http\Requests\Api\Application\Servers\GetServersRequest;
use Pterodactyl\Http\Requests\Api\Application\Servers\ServerWriteRequest;
use Pterodactyl\Http\Requests\Api\Application\Servers\StoreServerRequest;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
use Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerRequest;
class ServerController extends ApplicationApiController
{
@ -21,6 +25,8 @@ class ServerController extends ApplicationApiController
* ServerController constructor.
*/
public function __construct(
private BuildModificationService $buildModificationService,
private DetailsModificationService $detailsModificationService,
private ServerCreationService $creationService,
private ServerDeletionService $deletionService
) {
@ -32,13 +38,18 @@ class ServerController extends ApplicationApiController
*/
public function index(GetServersRequest $request): array
{
$perPage = (int) $request->query('per_page', '10');
if ($perPage < 1 || $perPage > 100) {
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
}
$servers = QueryBuilder::for(Server::query())
->allowedFilters(['uuid', 'uuidShort', 'name', 'description', 'image', 'external_id'])
->allowedSorts(['id', 'uuid'])
->paginate($request->query('per_page') ?? 50);
->allowedFilters(['id', 'uuid', 'uuidShort', 'name', 'owner_id', 'node_id', 'external_id'])
->allowedSorts(['id', 'uuid', 'uuidShort', 'name', 'owner_id', 'node_id', 'status'])
->paginate($perPage);
return $this->fractal->collection($servers)
->transformWith($this->getTransformer(ServerTransformer::class))
->transformWith(ServerTransformer::class)
->toArray();
}
@ -48,18 +59,17 @@ class ServerController extends ApplicationApiController
* @throws \Throwable
* @throws \Illuminate\Validation\ValidationException
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException
*/
public function store(StoreServerRequest $request): JsonResponse
{
$server = $this->creationService->handle($request->validated(), $request->getDeploymentObject());
$server = $this->creationService->handle($request->validated());
return $this->fractal->item($server)
->transformWith($this->getTransformer(ServerTransformer::class))
->respond(201);
->transformWith(ServerTransformer::class)
->respond(Response::HTTP_CREATED);
}
/**
@ -68,7 +78,7 @@ class ServerController extends ApplicationApiController
public function view(GetServerRequest $request, Server $server): array
{
return $this->fractal->item($server)
->transformWith($this->getTransformer(ServerTransformer::class))
->transformWith(ServerTransformer::class)
->toArray();
}
@ -76,6 +86,7 @@ class ServerController extends ApplicationApiController
* Deletes a server.
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Throwable
*/
public function delete(ServerWriteRequest $request, Server $server, string $force = ''): Response
{
@ -83,4 +94,24 @@ class ServerController extends ApplicationApiController
return $this->returnNoContent();
}
/**
* Update a server.
*
* @throws \Throwable
* @throws \Illuminate\Validation\ValidationException
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableAllocationException
* @throws \Pterodactyl\Exceptions\Service\Deployment\NoViableNodeException
*/
public function update(UpdateServerRequest $request, Server $server): array
{
$server = $this->buildModificationService->handle($server, $request->validated());
$server = $this->detailsModificationService->returnUpdatedModel()->handle($server, $request->validated());
return $this->fractal->item($server)
->transformWith(ServerTransformer::class)
->toArray();
}
}

View file

@ -25,35 +25,31 @@ class ServerDetailsController extends ApplicationApiController
/**
* Update the details for a specific server.
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/
public function details(UpdateServerDetailsRequest $request, Server $server): array
{
$updated = $this->detailsModificationService->returnUpdatedModel()->handle(
$server = $this->detailsModificationService->returnUpdatedModel()->handle(
$server,
$request->validated()
);
return $this->fractal->item($updated)
->transformWith($this->getTransformer(ServerTransformer::class))
return $this->fractal->item($server)
->transformWith(ServerTransformer::class)
->toArray();
}
/**
* Update the build details for a specific server.
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/
public function build(UpdateServerBuildConfigurationRequest $request, Server $server): array
{
$server = $this->buildModificationService->handle($server, $request->validated());
return $this->fractal->item($server)
->transformWith($this->getTransformer(ServerTransformer::class))
->transformWith(ServerTransformer::class)
->toArray();
}
}

View file

@ -12,7 +12,7 @@ use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class ServerManagementController extends ApplicationApiController
{
/**
* ServerManagementController constructor.
* SuspensionController constructor.
*/
public function __construct(
private ReinstallServerService $reinstallServerService,
@ -48,9 +48,7 @@ class ServerManagementController extends ApplicationApiController
/**
* Mark a server as needing to be reinstalled.
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/
public function reinstall(ServerWriteRequest $request, Server $server): Response
{

View file

@ -22,10 +22,7 @@ class StartupController extends ApplicationApiController
/**
* Update the startup and environment settings for a specific server.
*
* @throws \Illuminate\Validation\ValidationException
* @throws \Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/
public function index(UpdateServerStartupRequest $request, Server $server): array
{
@ -34,7 +31,7 @@ class StartupController extends ApplicationApiController
->handle($server, $request->validated());
return $this->fractal->item($server)
->transformWith($this->getTransformer(ServerTransformer::class))
->transformWith(ServerTransformer::class)
->toArray();
}
}

View file

@ -17,7 +17,7 @@ class ExternalUserController extends ApplicationApiController
$user = User::query()->where('external_id', $external_id)->firstOrFail();
return $this->fractal->item($user)
->transformWith($this->getTransformer(UserTransformer::class))
->transformWith(UserTransformer::class)
->toArray();
}
}

View file

@ -2,13 +2,19 @@
namespace Pterodactyl\Http\Controllers\Api\Application\Users;
use Illuminate\Support\Arr;
use Pterodactyl\Models\User;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;
use Spatie\QueryBuilder\QueryBuilder;
use Spatie\QueryBuilder\AllowedFilter;
use Illuminate\Database\Eloquent\Builder;
use Pterodactyl\Services\Users\UserUpdateService;
use Pterodactyl\Services\Users\UserCreationService;
use Pterodactyl\Services\Users\UserDeletionService;
use Pterodactyl\Transformers\Api\Application\UserTransformer;
use Pterodactyl\Exceptions\Http\QueryValueOutOfRangeHttpException;
use Pterodactyl\Http\Requests\Api\Application\Users\GetUserRequest;
use Pterodactyl\Http\Requests\Api\Application\Users\GetUsersRequest;
use Pterodactyl\Http\Requests\Api\Application\Users\StoreUserRequest;
use Pterodactyl\Http\Requests\Api\Application\Users\DeleteUserRequest;
@ -35,24 +41,48 @@ class UserController extends ApplicationApiController
*/
public function index(GetUsersRequest $request): array
{
$perPage = (int) $request->query('per_page', '10');
if ($perPage < 1 || $perPage > 100) {
throw new QueryValueOutOfRangeHttpException('per_page', 1, 100);
}
$users = QueryBuilder::for(User::query())
->allowedFilters(['email', 'uuid', 'username', 'external_id'])
->allowedSorts(['id', 'uuid'])
->paginate($request->query('per_page') ?? 50);
->allowedFilters([
AllowedFilter::exact('id'),
AllowedFilter::exact('uuid'),
AllowedFilter::exact('external_id'),
'username',
'email',
AllowedFilter::callback('*', function (Builder $builder, $value) {
foreach (Arr::wrap($value) as $datum) {
$datum = '%' . $datum . '%';
$builder->where(function (Builder $builder) use ($datum) {
$builder->where('uuid', 'LIKE', $datum)
->orWhere('username', 'LIKE', $datum)
->orWhere('email', 'LIKE', $datum)
->orWhere('external_id', 'LIKE', $datum);
});
}
}),
])
->allowedSorts(['id', 'uuid', 'username', 'email', 'admin_role_id'])
->paginate($perPage);
return $this->fractal->collection($users)
->transformWith($this->getTransformer(UserTransformer::class))
->transformWith(UserTransformer::class)
->toArray();
}
/**
* Handle a request to view a single user. Includes any relations that
* were defined in the request.
*
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function view(GetUsersRequest $request, User $user): array
public function view(GetUserRequest $request, User $user): array
{
return $this->fractal->item($user)
->transformWith($this->getTransformer(UserTransformer::class))
->transformWith(UserTransformer::class)
->toArray();
}
@ -64,22 +94,20 @@ class UserController extends ApplicationApiController
* Revocation errors are returned under the 'revocation_errors' key in the response
* meta. If there are no errors this is an empty array.
*
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
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));
return $response->toArray();
return $this->fractal->item($user)
->transformWith(UserTransformer::class)
->toArray();
}
/**
* Store a new user on the system. Returns the created user and an HTTP/201
* Store a new user on the system. Returns the created user and a HTTP/201
* header on successful creation.
*
* @throws \Exception
@ -90,12 +118,7 @@ class UserController extends ApplicationApiController
$user = $this->creationService->handle($request->validated());
return $this->fractal->item($user)
->transformWith($this->getTransformer(UserTransformer::class))
->addMeta([
'resource' => route('api.application.users.view', [
'user' => $user->id,
]),
])
->transformWith(UserTransformer::class)
->respond(201);
}
@ -105,10 +128,10 @@ class UserController extends ApplicationApiController
*
* @throws \Pterodactyl\Exceptions\DisplayException
*/
public function delete(DeleteUserRequest $request, User $user): JsonResponse
public function delete(DeleteUserRequest $request, User $user): Response
{
$this->deletionService->handle($user);
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
return $this->returnNoContent();
}
}

View file

@ -0,0 +1,25 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Services\Helpers\SoftwareVersionService;
class VersionController extends ApplicationApiController
{
/**
* VersionController constructor.
*/
public function __construct(private SoftwareVersionService $softwareVersionService)
{
parent::__construct();
}
/**
* Returns version information.
*/
public function __invoke(): JsonResponse
{
return new JsonResponse($this->softwareVersionService->getVersionData());
}
}

View file

@ -25,7 +25,7 @@ class AccountController extends ClientApiController
public function index(Request $request): array
{
return $this->fractal->item($request->user())
->transformWith($this->getTransformer(AccountTransformer::class))
->transformWith(AccountTransformer::class)
->toArray();
}

View file

@ -24,7 +24,7 @@ class ActivityLogController extends ClientApiController
->appends($request->query());
return $this->fractal->collection($activity)
->transformWith($this->getTransformer(ActivityLogTransformer::class))
->transformWith(ActivityLogTransformer::class)
->toArray();
}
}

View file

@ -18,7 +18,7 @@ class ApiKeyController extends ClientApiController
public function index(ClientApiRequest $request): array
{
return $this->fractal->collection($request->user()->apiKeys)
->transformWith($this->getTransformer(ApiKeyTransformer::class))
->transformWith(ApiKeyTransformer::class)
->toArray();
}
@ -44,7 +44,7 @@ class ApiKeyController extends ClientApiController
->log();
return $this->fractal->item($token->accessToken)
->transformWith($this->getTransformer(ApiKeyTransformer::class))
->transformWith(ApiKeyTransformer::class)
->addMeta(['secret_token' => $token->plainTextToken])
->toArray();
}

View file

@ -3,7 +3,7 @@
namespace Pterodactyl\Http\Controllers\Api\Client;
use Webmozart\Assert\Assert;
use Pterodactyl\Transformers\Api\Client\BaseClientTransformer;
use Pterodactyl\Transformers\Api\Transformer;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
abstract class ClientApiController extends ApplicationApiController
@ -11,7 +11,7 @@ abstract class ClientApiController extends ApplicationApiController
/**
* Returns only the includes which are valid for the given transformer.
*/
protected function getIncludesForTransformer(BaseClientTransformer $transformer, array $merge = []): array
protected function getIncludesForTransformer(Transformer $transformer, array $merge = []): array
{
$filtered = array_filter($this->parseIncludes(), function ($datum) use ($transformer) {
return in_array($datum, $transformer->getAvailableIncludes());
@ -35,22 +35,4 @@ abstract class ClientApiController extends ApplicationApiController
return trim($item);
}, explode(',', $includes));
}
/**
* Return an instance of an application transformer.
*
* @template T of \Pterodactyl\Transformers\Api\Client\BaseClientTransformer
*
* @param class-string<T> $abstract
*
* @return T
*
* @noinspection PhpDocSignatureInspection
*/
public function getTransformer(string $abstract)
{
Assert::subclassOf($abstract, BaseClientTransformer::class);
return $abstract::fromRequest($this->request);
}
}

View file

@ -27,7 +27,7 @@ class ClientController extends ClientApiController
public function index(GetServersRequest $request): array
{
$user = $request->user();
$transformer = $this->getTransformer(ServerTransformer::class);
$transformer = new ServerTransformer();
// Start the query builder and ensure we eager load any requested relationships from the request.
$builder = QueryBuilder::for(

View file

@ -17,7 +17,7 @@ class SSHKeyController extends ClientApiController
public function index(ClientApiRequest $request): array
{
return $this->fractal->collection($request->user()->sshKeys)
->transformWith($this->getTransformer(UserSSHKeyTransformer::class))
->transformWith(UserSSHKeyTransformer::class)
->toArray();
}
@ -38,7 +38,7 @@ class SSHKeyController extends ClientApiController
->log();
return $this->fractal->item($model)
->transformWith($this->getTransformer(UserSSHKeyTransformer::class))
->transformWith(UserSSHKeyTransformer::class)
->toArray();
}

View file

@ -48,7 +48,7 @@ class ActivityLogController extends ClientApiController
->appends($request->query());
return $this->fractal->collection($activity)
->transformWith($this->getTransformer(ActivityLogTransformer::class))
->transformWith(ActivityLogTransformer::class)
->toArray();
}
}

View file

@ -49,7 +49,7 @@ class BackupController extends ClientApiController
$limit = min($request->query('per_page') ?? 20, 50);
return $this->fractal->collection($server->backups()->paginate($limit))
->transformWith($this->getTransformer(BackupTransformer::class))
->transformWith(BackupTransformer::class)
->addMeta([
'backup_count' => $this->repository->getNonFailedBackups($server)->count(),
])
@ -84,7 +84,7 @@ class BackupController extends ClientApiController
->log();
return $this->fractal->item($backup)
->transformWith($this->getTransformer(BackupTransformer::class))
->transformWith(BackupTransformer::class)
->toArray();
}
@ -107,7 +107,7 @@ class BackupController extends ClientApiController
Activity::event($action)->subject($backup)->property('name', $backup->name)->log();
return $this->fractal->item($backup)
->transformWith($this->getTransformer(BackupTransformer::class))
->transformWith(BackupTransformer::class)
->toArray();
}
@ -123,7 +123,7 @@ class BackupController extends ClientApiController
}
return $this->fractal->item($backup)
->transformWith($this->getTransformer(BackupTransformer::class))
->transformWith(BackupTransformer::class)
->toArray();
}

View file

@ -35,7 +35,7 @@ class DatabaseController extends ClientApiController
public function index(GetDatabasesRequest $request, Server $server): array
{
return $this->fractal->collection($server->databases)
->transformWith($this->getTransformer(DatabaseTransformer::class))
->transformWith(DatabaseTransformer::class)
->toArray();
}
@ -57,7 +57,7 @@ class DatabaseController extends ClientApiController
return $this->fractal->item($database)
->parseIncludes(['password'])
->transformWith($this->getTransformer(DatabaseTransformer::class))
->transformWith(DatabaseTransformer::class)
->toArray();
}
@ -79,7 +79,7 @@ class DatabaseController extends ClientApiController
return $this->fractal->item($database)
->parseIncludes(['password'])
->transformWith($this->getTransformer(DatabaseTransformer::class))
->transformWith(DatabaseTransformer::class)
->toArray();
}

View file

@ -47,7 +47,7 @@ class FileController extends ClientApiController
->getDirectory($request->get('directory') ?? '/');
return $this->fractal->collection($contents)
->transformWith($this->getTransformer(FileObjectTransformer::class))
->transformWith(FileObjectTransformer::class)
->toArray();
}
@ -183,7 +183,7 @@ class FileController extends ClientApiController
->log();
return $this->fractal->item($file)
->transformWith($this->getTransformer(FileObjectTransformer::class))
->transformWith(FileObjectTransformer::class)
->toArray();
}

View file

@ -36,7 +36,7 @@ class NetworkAllocationController extends ClientApiController
public function index(GetNetworkRequest $request, Server $server): array
{
return $this->fractal->collection($server->allocations)
->transformWith($this->getTransformer(AllocationTransformer::class))
->transformWith(AllocationTransformer::class)
->toArray();
}
@ -60,7 +60,7 @@ class NetworkAllocationController extends ClientApiController
}
return $this->fractal->item($allocation)
->transformWith($this->getTransformer(AllocationTransformer::class))
->transformWith(AllocationTransformer::class)
->toArray();
}
@ -80,7 +80,7 @@ class NetworkAllocationController extends ClientApiController
->log();
return $this->fractal->item($allocation)
->transformWith($this->getTransformer(AllocationTransformer::class))
->transformWith(AllocationTransformer::class)
->toArray();
}
@ -104,7 +104,7 @@ class NetworkAllocationController extends ClientApiController
->log();
return $this->fractal->item($allocation)
->transformWith($this->getTransformer(AllocationTransformer::class))
->transformWith(AllocationTransformer::class)
->toArray();
}

View file

@ -35,7 +35,7 @@ class ResourceUtilizationController extends ClientApiController
});
return $this->fractal->item($stats)
->transformWith($this->getTransformer(StatsTransformer::class))
->transformWith(StatsTransformer::class)
->toArray();
}
}

View file

@ -40,7 +40,7 @@ class ScheduleController extends ClientApiController
$schedules = $server->schedules->loadMissing('tasks');
return $this->fractal->collection($schedules)
->transformWith($this->getTransformer(ScheduleTransformer::class))
->transformWith(ScheduleTransformer::class)
->toArray();
}
@ -72,7 +72,7 @@ class ScheduleController extends ClientApiController
->log();
return $this->fractal->item($model)
->transformWith($this->getTransformer(ScheduleTransformer::class))
->transformWith(ScheduleTransformer::class)
->toArray();
}
@ -88,7 +88,7 @@ class ScheduleController extends ClientApiController
$schedule->loadMissing('tasks');
return $this->fractal->item($schedule)
->transformWith($this->getTransformer(ScheduleTransformer::class))
->transformWith(ScheduleTransformer::class)
->toArray();
}
@ -131,7 +131,7 @@ class ScheduleController extends ClientApiController
->log();
return $this->fractal->item($schedule->refresh())
->transformWith($this->getTransformer(ScheduleTransformer::class))
->transformWith(ScheduleTransformer::class)
->toArray();
}

View file

@ -64,7 +64,7 @@ class ScheduleTaskController extends ClientApiController
->log();
return $this->fractal->item($task)
->transformWith($this->getTransformer(TaskTransformer::class))
->transformWith(TaskTransformer::class)
->toArray();
}
@ -97,7 +97,7 @@ class ScheduleTaskController extends ClientApiController
->log();
return $this->fractal->item($task->refresh())
->transformWith($this->getTransformer(TaskTransformer::class))
->transformWith(TaskTransformer::class)
->toArray();
}

View file

@ -25,7 +25,7 @@ class ServerController extends ClientApiController
public function index(GetServerRequest $request, Server $server): array
{
return $this->fractal->item($server)
->transformWith($this->getTransformer(ServerTransformer::class))
->transformWith(ServerTransformer::class)
->addMeta([
'is_server_owner' => $request->user()->id === $server->owner_id,
'user_permissions' => $this->permissionsService->handle($server, $request->user()),

View file

@ -34,7 +34,7 @@ class StartupController extends ClientApiController
return $this->fractal->collection(
$server->variables()->where('user_viewable', true)->get()
)
->transformWith($this->getTransformer(EggVariableTransformer::class))
->transformWith(EggVariableTransformer::class)
->addMeta([
'startup_command' => $startup,
'docker_images' => $server->egg->docker_images,
@ -90,7 +90,7 @@ class StartupController extends ClientApiController
}
return $this->fractal->item($variable)
->transformWith($this->getTransformer(EggVariableTransformer::class))
->transformWith(EggVariableTransformer::class)
->addMeta([
'startup_command' => $startup,
'raw_startup_command' => $server->startup,

View file

@ -38,7 +38,7 @@ class SubuserController extends ClientApiController
public function index(GetSubuserRequest $request, Server $server): array
{
return $this->fractal->collection($server->subusers)
->transformWith($this->getTransformer(SubuserTransformer::class))
->transformWith(SubuserTransformer::class)
->toArray();
}
@ -50,7 +50,7 @@ class SubuserController extends ClientApiController
$subuser = $request->attributes->get('subuser');
return $this->fractal->item($subuser)
->transformWith($this->getTransformer(SubuserTransformer::class))
->transformWith(SubuserTransformer::class)
->toArray();
}
@ -76,7 +76,7 @@ class SubuserController extends ClientApiController
->log();
return $this->fractal->item($response)
->transformWith($this->getTransformer(SubuserTransformer::class))
->transformWith(SubuserTransformer::class)
->toArray();
}
@ -129,7 +129,7 @@ class SubuserController extends ClientApiController
$log->reset();
return $this->fractal->item($subuser->refresh())
->transformWith($this->getTransformer(SubuserTransformer::class))
->transformWith(SubuserTransformer::class)
->toArray();
}

View file

@ -85,7 +85,7 @@ abstract class AbstractLoginController extends Controller
'data' => [
'complete' => true,
'intended' => $this->redirectPath(),
'user' => $user->toVueObject(),
'user' => $user->toReactObject(),
],
]);
}

View file

@ -0,0 +1,83 @@
<?php
namespace Pterodactyl\Http\Requests\Api;
use Illuminate\Foundation\Http\FormRequest;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
/**
* @method \Pterodactyl\Models\User user($guard = null)
*/
abstract class ApiRequest extends FormRequest
{
/**
* Tracks if the request has been validated internally or not to avoid
* making duplicate validation calls.
*/
private bool $hasValidated = false;
/**
* Determine if the current user is authorized to perform the requested
* action against the API.
*/
public function authorize(): bool
{
return false;
}
/**
* Default set of rules to apply to API requests.
*/
public function rules(): array
{
return [];
}
/**
* Validate that the resource exists and can be accessed prior to booting
* the validator and attempting to use the data.
*
* @throws \Illuminate\Auth\Access\AuthorizationException
*/
protected function prepareForValidation()
{
if (!$this->passesAuthorization()) {
$this->failedAuthorization();
}
$this->hasValidated = true;
}
/*
* Determine if the request passes the authorization check as well
* as the exists check.
*
* @return bool
*
* @throws \Symfony\Component\HttpKernel\Exception\NotFoundHttpException
*/
protected function passesAuthorization()
{
// If we have already validated we do not need to call this function
// again. This is needed to work around Laravel's normal auth validation
// that occurs after validating the request params since we are doing auth
// validation in the prepareForValidation() function.
if ($this->hasValidated) {
return true;
}
if (!parent::passesAuthorization()) {
return false;
}
// Only let the user know that a resource does not exist if they are
// authenticated to access the endpoint. This avoids exposing that
// an item exists (or does not exist) to the user until they can prove
// that they have permission to know about it.
if ($this->attributes->get('is_missing_model', false)) {
throw new NotFoundHttpException(trans('exceptions.api.resource_not_found'));
}
return true;
}
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Allocations;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteAllocationRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS;
protected int $permission = AdminAcl::WRITE;
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Allocations;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetAllocationsRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS;
protected int $permission = AdminAcl::READ;
}

View file

@ -2,15 +2,11 @@
namespace Pterodactyl\Http\Requests\Api\Application\Allocations;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Illuminate\Support\Arr;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreAllocationRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_ALLOCATIONS;
protected int $permission = AdminAcl::WRITE;
public function rules(): array
{
return [
@ -21,14 +17,22 @@ class StoreAllocationRequest extends ApplicationApiRequest
];
}
public function validated($key = null, $default = null): array
/**
* @param string|null $key
* @param string|array|null $default
*
* @return mixed
*/
public function validated($key = null, $default = null)
{
$data = parent::validated();
return [
$response = [
'allocation_ip' => $data['ip'],
'allocation_ports' => $data['ports'],
'allocation_alias' => $data['alias'] ?? null,
];
return is_null($key) ? $response : Arr::get($response, $key, $default);
}
}

View file

@ -2,94 +2,16 @@
namespace Pterodactyl\Http\Requests\Api\Application;
use Webmozart\Assert\Assert;
use Pterodactyl\Models\ApiKey;
use Laravel\Sanctum\TransientToken;
use Illuminate\Validation\Validator;
use Illuminate\Database\Eloquent\Model;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Illuminate\Foundation\Http\FormRequest;
use Pterodactyl\Exceptions\PterodactylException;
use Pterodactyl\Http\Requests\Api\ApiRequest;
abstract class ApplicationApiRequest extends FormRequest
abstract class ApplicationApiRequest extends ApiRequest
{
/**
* The resource that should be checked when performing the authorization
* function for this request.
*/
protected ?string $resource;
/**
* The permission level that a given API key should have for accessing
* the defined $resource during the request cycle.
*/
protected int $permission = AdminAcl::NONE;
/**
* Determine if the current user is authorized to perform
* the requested action against the API.
*
* @throws PterodactylException
* This will eventually be replaced with per-request permissions checking
* on the API key and for the user.
*/
public function authorize(): bool
{
if (is_null($this->resource)) {
throw new PterodactylException('An ACL resource must be defined on API requests.');
}
$token = $this->user()->currentAccessToken();
if ($token instanceof TransientToken) {
return true;
}
/** @var ApiKey $token */
if ($token->key_type === ApiKey::TYPE_ACCOUNT) {
return true;
}
return AdminAcl::check($token, $this->resource, $this->permission);
}
/**
* Default set of rules to apply to API requests.
*/
public function rules(): array
{
return [];
}
/**
* Helper method allowing a developer to easily hook into this logic without having
* to remember what the method name is called or where to use it. By default this is
* a no-op.
*/
public function withValidator(Validator $validator): void
{
// do nothing
}
/**
* Returns the named route parameter and asserts that it is a real model that
* exists in the database.
*
* @template T of \Illuminate\Database\Eloquent\Model
*
* @param class-string<T> $expect
*
* @return T
*
* @noinspection PhpDocSignatureInspection
*/
public function parameter(string $key, string $expect)
{
/** @var ApiKey $value */
$value = $this->route()->parameter($key);
Assert::isInstanceOf($value, $expect);
Assert::isInstanceOf($value, Model::class);
Assert::true($value->exists);
/* @var T $value */
return $value;
return $this->user()->root_admin;
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Databases;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteDatabaseRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,7 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Databases;
class GetDatabaseRequest extends GetDatabasesRequest
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Databases;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetDatabasesRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,14 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Databases;
use Pterodactyl\Models\DatabaseHost;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreDatabaseRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return $rules ?? DatabaseHost::getRules();
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Databases;
use Pterodactyl\Models\DatabaseHost;
class UpdateDatabaseRequest extends StoreDatabaseRequest
{
public function rules(array $rules = null): array
{
return $rules ?? DatabaseHost::getRulesForUpdate($this->route()->parameter('databaseHost')->id);
}
}

View file

@ -0,0 +1,16 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
use Pterodactyl\Models\Egg;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteEggRequest extends ApplicationApiRequest
{
public function resourceExists(): bool
{
$egg = $this->route()->parameter('egg');
return $egg instanceof Egg && $egg->exists;
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class ExportEggRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,7 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
class GetEggRequest extends GetEggsRequest
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetEggsRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class ImportEggRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,30 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreEggRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return [
'nest_id' => 'required|bail|numeric|exists:nests,id',
'name' => 'required|string|max:191',
'description' => 'sometimes|string|nullable',
'features' => 'sometimes|array',
'docker_images' => 'required|array|min:1',
'docker_images.*' => 'required|string',
'file_denylist' => 'sometimes|array|nullable',
'file_denylist.*' => 'sometimes|string',
'config_files' => 'required|nullable|json',
'config_startup' => 'required|nullable|json',
'config_stop' => 'required|nullable|string|max:191',
// 'config_from' => 'sometimes|nullable|numeric|exists:eggs,id',
'startup' => 'required|string',
'script_container' => 'sometimes|string',
'script_entry' => 'sometimes|string',
'script_install' => 'sometimes|string',
];
}
}

View file

@ -0,0 +1,28 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
class UpdateEggRequest extends StoreEggRequest
{
public function rules(array $rules = null): array
{
return [
'nest_id' => 'sometimes|numeric|exists:nests,id',
'name' => 'sometimes|string|max:191',
'description' => 'sometimes|string|nullable',
'features' => 'sometimes|array',
'docker_images' => 'sometimes|array|min:1',
'docker_images.*' => 'sometimes|string',
'file_denylist' => 'sometimes|array|nullable',
'file_denylist.*' => 'sometimes|string',
'config_files' => 'sometimes|nullable|json',
'config_startup' => 'sometimes|nullable|json',
'config_stop' => 'sometimes|nullable|string|max:191',
// 'config_from' => 'sometimes|nullable|numeric|exists:eggs,id',
'startup' => 'sometimes|string',
'script_container' => 'sometimes|string',
'script_entry' => 'sometimes|string',
'script_install' => 'sometimes|string',
];
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs\Variables;
use Pterodactyl\Models\EggVariable;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreEggVariableRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return [
'name' => 'required|string|min:1|max:191',
'description' => 'sometimes|string|nullable',
'env_variable' => 'required|regex:/^[\w]{1,191}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES,
'default_value' => 'present',
'user_viewable' => 'required|boolean',
'user_editable' => 'required|boolean',
'rules' => 'bail|required|string',
];
}
}

View file

@ -0,0 +1,24 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs\Variables;
use Pterodactyl\Models\EggVariable;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class UpdateEggVariablesRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return [
'*' => 'array',
'*.id' => 'required|integer',
'*.name' => 'sometimes|string|min:1|max:191',
'*.description' => 'sometimes|string|nullable',
'*.env_variable' => 'sometimes|regex:/^[\w]{1,191}$/|notIn:' . EggVariable::RESERVED_ENV_NAMES,
'*.default_value' => 'sometimes|present',
'*.user_viewable' => 'sometimes|boolean',
'*.user_editable' => 'sometimes|boolean',
'*.rules' => 'sometimes|string',
];
}
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Locations;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteLocationRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS;
protected int $permission = AdminAcl::WRITE;
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Locations;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetLocationsRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS;
protected int $permission = AdminAcl::READ;
}

View file

@ -3,18 +3,10 @@
namespace Pterodactyl\Http\Requests\Api\Application\Locations;
use Pterodactyl\Models\Location;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreLocationRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_LOCATIONS;
protected int $permission = AdminAcl::WRITE;
/**
* Rules to validate the request against.
*/
public function rules(): array
{
return collect(Location::getRules())->only([
@ -23,9 +15,6 @@ class StoreLocationRequest extends ApplicationApiRequest
])->toArray();
}
/**
* Rename fields to be more clear in error messages.
*/
public function attributes(): array
{
return [

View file

@ -6,14 +6,9 @@ use Pterodactyl\Models\Location;
class UpdateLocationRequest extends StoreLocationRequest
{
/**
* Rules to validate this request against.
*/
public function rules(): array
{
/** @var Location $location */
$location = $this->route()->parameter('location');
$locationId = $location->id;
$locationId = $this->route()->parameter('location')->id;
return collect(Location::getRulesForUpdate($locationId))->only([
'short',

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteMountRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,7 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
class GetMountRequest extends GetMountsRequest
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetMountsRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class MountEggsRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return $rules ?? ['eggs' => 'required|exists:eggs,id'];
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class MountNodesRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return $rules ?? ['nodes' => 'required|exists:nodes,id'];
}
}

View file

@ -0,0 +1,14 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
use Pterodactyl\Models\Mount;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreMountRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return $rules ?? Mount::getRules();
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
use Pterodactyl\Models\Mount;
class UpdateMountRequest extends StoreMountRequest
{
public function rules(array $rules = null): array
{
return $rules ?? Mount::getRulesForUpdate($this->route()->parameter('mount')->id);
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteNestRequest extends ApplicationApiRequest
{
}

View file

@ -1,13 +0,0 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests\Eggs;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetEggRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_EGGS;
protected int $permission = AdminAcl::READ;
}

View file

@ -1,13 +0,0 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests\Eggs;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetEggsRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_EGGS;
protected int $permission = AdminAcl::READ;
}

View file

@ -0,0 +1,7 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests;
class GetNestRequest extends GetNestsRequest
{
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Nests;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetNestsRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_NESTS;
protected int $permission = AdminAcl::READ;
}

View file

@ -0,0 +1,14 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests;
use Pterodactyl\Models\Nest;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreNestRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return $rules ?? Nest::getRules();
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests;
use Pterodactyl\Models\Nest;
class UpdateNestRequest extends StoreNestRequest
{
public function rules(array $rules = null): array
{
return $rules ?? Nest::getRulesForUpdate($this->route()->parameter('nest')->id);
}
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Nodes;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteNodeRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_NODES;
protected int $permission = AdminAcl::WRITE;
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Nodes;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetNodesRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_NODES;
protected int $permission = AdminAcl::READ;
}

View file

@ -2,48 +2,50 @@
namespace Pterodactyl\Http\Requests\Api\Application\Nodes;
use Illuminate\Support\Arr;
use Pterodactyl\Models\Node;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreNodeRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_NODES;
protected int $permission = AdminAcl::WRITE;
/**
* Validation rules to apply to this request.
*/
public function rules(array $rules = null): array
{
return collect($rules ?? Node::getRules())->only([
'public',
'name',
'description',
'location_id',
'database_host_id',
'fqdn',
'scheme',
'behind_proxy',
'maintenance_mode',
'public',
'listen_port_http',
'public_port_http',
'listen_port_sftp',
'public_port_sftp',
'memory',
'memory_overallocate',
'disk',
'disk_overallocate',
'upload_size',
'daemonListen',
'daemonSFTP',
'daemonBase',
])->mapWithKeys(function ($value, $key) {
$key = ($key === 'daemonSFTP') ? 'daemonSftp' : $key;
'upload_size',
'daemon_base',
])->mapWithKeys(function ($value, $key) {
return [snake_case($key) => $value];
})->toArray();
}
/**
* Fields to rename for clarity in the API response.
*
* @return array
*/
public function attributes(): array
public function attributes()
{
return [
'daemon_base' => 'Daemon Base Path',
@ -56,15 +58,20 @@ class StoreNodeRequest extends ApplicationApiRequest
/**
* Change the formatting of some data keys in the validated response data
* to match what the application expects in the services.
*
* @param string|null $key
* @param string|array|null $default
*
* @return mixed
*/
public function validated($key = null, $default = null): array
public function validated($key = null, $default = null)
{
$response = parent::validated();
$response['daemonListen'] = $response['daemon_listen'];
$response['daemonSFTP'] = $response['daemon_sftp'];
$response['daemonBase'] = $response['daemon_base'] ?? (new Node())->getAttribute('daemonBase');
$response['daemon_base'] = $response['daemon_base'] ?? Node::DEFAULT_DAEMON_BASE;
unset($response['daemon_base'], $response['daemon_listen'], $response['daemon_sftp']);
if (!is_null($key)) {
return Arr::get($response, $key, $default);
}
return $response;
}

View file

@ -6,15 +6,8 @@ use Pterodactyl\Models\Node;
class UpdateNodeRequest extends StoreNodeRequest
{
/**
* Apply validation rules to this request. Uses the parent class rules()
* function but passes in the rules for updating rather than creating.
*/
public function rules(array $rules = null): array
{
/** @var Node $node */
$node = $this->route()->parameter('node');
return parent::rules(Node::getRulesForUpdate($node->id));
return parent::rules($rules ?? Node::getRulesForUpdate($this->route()->parameter('node')->id));
}
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Roles;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DeleteRoleRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,7 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Roles;
class GetRoleRequest extends GetRolesRequest
{
}

View file

@ -0,0 +1,9 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Roles;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetRolesRequest extends ApplicationApiRequest
{
}

View file

@ -0,0 +1,14 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Roles;
use Pterodactyl\Models\AdminRole;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreRoleRequest extends ApplicationApiRequest
{
public function rules(array $rules = null): array
{
return $rules ?? AdminRole::getRules();
}
}

View file

@ -0,0 +1,13 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Roles;
use Pterodactyl\Models\AdminRole;
class UpdateRoleRequest extends StoreRoleRequest
{
public function rules(array $rules = null): array
{
return $rules ?? AdminRole::getRulesForUpdate($this->route()->parameter('role')->id);
}
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetServerDatabaseRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES;
protected int $permission = AdminAcl::READ;
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetServerDatabasesRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES;
protected int $permission = AdminAcl::READ;
}

View file

@ -2,9 +2,6 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
use Pterodactyl\Services\Acl\Api\AdminAcl;
class ServerDatabaseWriteRequest extends GetServerDatabasesRequest
{
protected int $permission = AdminAcl::WRITE;
}

View file

@ -2,26 +2,18 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers\Databases;
use Illuminate\Support\Arr;
use Webmozart\Assert\Assert;
use Pterodactyl\Models\Server;
use Illuminate\Validation\Rule;
use Illuminate\Database\Query\Builder;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Services\Databases\DatabaseManagementService;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreServerDatabaseRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_SERVER_DATABASES;
protected int $permission = AdminAcl::WRITE;
/**
* Validation rules for database creation.
*/
public function rules(): array
{
/** @var Server $server */
$server = $this->route()->parameter('server');
return [
@ -40,20 +32,22 @@ class StoreServerDatabaseRequest extends ApplicationApiRequest
}
/**
* Return data formatted in the correct format for the service to consume.
* @param string|null $key
* @param string|array|null $default
*
* @return mixed
*/
public function validated($key = null, $default = null): array
public function validated($key = null, $default = null)
{
return [
$data = [
'database' => $this->input('database'),
'remote' => $this->input('remote'),
'database_host_id' => $this->input('host'),
];
return is_null($key) ? $data : Arr::get($data, $key, $default);
}
/**
* Format error messages in a more understandable format for API output.
*/
public function attributes(): array
{
return [
@ -63,12 +57,8 @@ class StoreServerDatabaseRequest extends ApplicationApiRequest
];
}
/**
* Returns the database name in the expected format.
*/
public function databaseName(): string
{
/** @var Server $server */
$server = $this->route()->parameter('server');
Assert::isInstanceOf($server, Server::class);

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetExternalServerRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_SERVERS;
protected int $permission = AdminAcl::READ;
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetServerRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_SERVERS;
protected int $permission = AdminAcl::READ;
}

View file

@ -2,12 +2,8 @@
namespace Pterodactyl\Http\Requests\Api\Application\Servers;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class ServerWriteRequest extends ApplicationApiRequest
{
protected ?string $resource = AdminAcl::RESOURCE_SERVERS;
protected int $permission = AdminAcl::WRITE;
}

Some files were not shown because too many files have changed in this diff Show more