Merge branch 'develop' into matthewpi/security-keys-backport

This commit is contained in:
Matthew Penner 2023-01-17 15:33:53 -07:00
commit f631ac1946
No known key found for this signature in database
1153 changed files with 25099 additions and 37002 deletions

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

@ -33,7 +33,7 @@ class NodeDeploymentController extends ApplicationApiController
->handle($request->query('per_page'), $request->query('page'));
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

@ -1,59 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Servers;
use Pterodactyl\Models\Server;
use Pterodactyl\Services\Servers\BuildModificationService;
use Pterodactyl\Services\Servers\DetailsModificationService;
use Pterodactyl\Transformers\Api\Application\ServerTransformer;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
use Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerDetailsRequest;
use Pterodactyl\Http\Requests\Api\Application\Servers\UpdateServerBuildConfigurationRequest;
class ServerDetailsController extends ApplicationApiController
{
/**
* ServerDetailsController constructor.
*/
public function __construct(
private BuildModificationService $buildModificationService,
private DetailsModificationService $detailsModificationService
) {
parent::__construct();
}
/**
* Update the details for a specific server.
*
* @throws \Pterodactyl\Exceptions\DisplayException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
*/
public function details(UpdateServerDetailsRequest $request, Server $server): array
{
$updated = $this->detailsModificationService->returnUpdatedModel()->handle(
$server,
$request->validated()
);
return $this->fractal->item($updated)
->transformWith($this->getTransformer(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
*/
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))
->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

@ -2,8 +2,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 +10,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 +34,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(
@ -40,6 +40,11 @@ class ClientController extends ClientApiController
AllowedFilter::custom('*', new MultiFieldServerFilter()),
]);
$loweredBindings = collect($builder->getBindings())
->map(fn ($f, $key) => is_string($f) ? strtolower($f) : $f)
->all();
$builder->setBindings($loweredBindings);
$type = $request->input('type');
// Either return all the servers the user has access to because they are an admin `?type=admin` or
// just return all the servers the user has access to because they are the owner or a subuser of the

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

@ -2,7 +2,6 @@
namespace Pterodactyl\Http\Controllers\Api\Client\Servers;
use Exception;
use Carbon\Carbon;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
@ -41,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();
}
@ -73,7 +72,7 @@ class ScheduleController extends ClientApiController
->log();
return $this->fractal->item($model)
->transformWith($this->getTransformer(ScheduleTransformer::class))
->transformWith(ScheduleTransformer::class)
->toArray();
}
@ -89,7 +88,7 @@ class ScheduleController extends ClientApiController
$schedule->loadMissing('tasks');
return $this->fractal->item($schedule)
->transformWith($this->getTransformer(ScheduleTransformer::class))
->transformWith(ScheduleTransformer::class)
->toArray();
}
@ -132,7 +131,7 @@ class ScheduleController extends ClientApiController
->log();
return $this->fractal->item($schedule->refresh())
->transformWith($this->getTransformer(ScheduleTransformer::class))
->transformWith(ScheduleTransformer::class)
->toArray();
}
@ -178,7 +177,7 @@ class ScheduleController extends ClientApiController
$request->input('month'),
$request->input('day_of_week')
);
} catch (Exception $exception) {
} catch (\Exception $exception) {
throw new DisplayException('The cron data provided does not evaluate to a valid expression.');
}
}

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,14 +34,22 @@ class SettingsController extends ClientApiController
*/
public function rename(RenameServerRequest $request, Server $server): JsonResponse
{
$name = $request->input('name');
$description = $request->input('description') ?? $server->description;
$this->repository->update($server->id, [
'name' => $request->input('name'),
'description' => $request->input('description') ?? '',
'name' => $name,
'description' => $description,
]);
if ($server->name !== $request->input('name')) {
if ($server->name !== $name) {
Activity::event('server:settings.rename')
->property(['old' => $server->name, 'new' => $request->input('name')])
->property(['old' => $server->name, 'new' => $name])
->log();
}
if ($server->description !== $description) {
Activity::event('server:settings.description')
->property(['old' => $server->description, 'new' => $description])
->log();
}

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,
@ -52,7 +52,6 @@ class StartupController extends ClientApiController
*/
public function update(UpdateStartupVariableRequest $request, Server $server): array
{
/** @var \Pterodactyl\Models\EggVariable $variable */
$variable = $server->variables()->where('env_variable', $request->input('key'))->first();
$original = $variable->server_value;
@ -62,6 +61,8 @@ class StartupController extends ClientApiController
throw new BadRequestHttpException('The environment variable you are trying to edit is read-only.');
}
/* @var \Pterodactyl\Models\EggVariable $variable */
// Revalidate the variable value using the egg variable specific validation rules for it.
$this->validate($request, ['value' => $variable->rules]);
@ -89,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

@ -2,9 +2,7 @@
namespace Pterodactyl\Http\Controllers\Api\Remote;
use Exception;
use Carbon\Carbon;
use DateTimeInterface;
use Illuminate\Support\Str;
use Pterodactyl\Models\User;
use Webmozart\Assert\Assert;
@ -37,11 +35,11 @@ class ActivityProcessingController extends Controller
try {
$when = Carbon::createFromFormat(
DateTimeInterface::RFC3339,
\DateTimeInterface::RFC3339,
preg_replace('/(\.\d+)Z$/', 'Z', $datum['timestamp']),
'UTC'
);
} catch (Exception $exception) {
} catch (\Exception $exception) {
Log::warning($exception, ['timestamp' => $datum['timestamp']]);
// If we cannot parse the value for some reason don't blow up this request, just go ahead

View file

@ -3,6 +3,7 @@
namespace Pterodactyl\Http\Controllers\Api\Remote\Servers;
use Illuminate\Http\Request;
use Pterodactyl\Models\Backup;
use Pterodactyl\Models\Server;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Facades\Activity;
@ -98,9 +99,11 @@ class ServerDetailsController extends Controller
if ($subject = $activity->subjects->where('subject_type', 'backup')->first()) {
// Just create a new audit entry for this event and update the server state
// so that power actions, file management, and backups can resume as normal.
/** @var Backup $actualSubject */
$actualSubject = $subject->subject;
Activity::event('server:backup.restore-failed')
->subject($server, $subject->subject)
->property('name', $subject->subject->name)
->subject($server, $actualSubject)
->property('name', $actualSubject->name)
->log();
}
}

View file

@ -48,8 +48,18 @@ class ServerInstallController extends Controller
public function store(InstallationDataRequest $request, string $uuid): JsonResponse
{
$server = $this->repository->getByUuid($uuid);
$status = null;
$status = $request->boolean('successful') ? null : Server::STATUS_INSTALL_FAILED;
// Make sure the type of failure is accurate
if (!$request->boolean('successful')) {
$status = Server::STATUS_INSTALL_FAILED;
if ($request->boolean('reinstall')) {
$status = Server::STATUS_REINSTALL_FAILED;
}
}
// Keep the server suspended if it's already suspended
if ($server->status === Server::STATUS_SUSPENDED) {
$status = Server::STATUS_SUSPENDED;
}

View file

@ -2,8 +2,6 @@
namespace Pterodactyl\Http\Controllers\Api\Remote\Servers;
use Carbon\CarbonImmutable;
use Illuminate\Http\Request;
use Illuminate\Http\Response;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Models\Allocation;
@ -11,10 +9,9 @@ use Illuminate\Support\Facades\Log;
use Pterodactyl\Models\ServerTransfer;
use Illuminate\Database\ConnectionInterface;
use Pterodactyl\Http\Controllers\Controller;
use Pterodactyl\Services\Nodes\NodeJWTService;
use Pterodactyl\Repositories\Eloquent\ServerRepository;
use Pterodactyl\Repositories\Wings\DaemonServerRepository;
use Pterodactyl\Repositories\Wings\DaemonTransferRepository;
use Symfony\Component\HttpKernel\Exception\ConflictHttpException;
use Pterodactyl\Exceptions\Http\Connection\DaemonConnectionException;
class ServerTransferController extends Controller
@ -25,52 +22,10 @@ class ServerTransferController extends Controller
public function __construct(
private ConnectionInterface $connection,
private ServerRepository $repository,
private DaemonServerRepository $daemonServerRepository,
private DaemonTransferRepository $daemonTransferRepository,
private NodeJWTService $jwtService
private DaemonServerRepository $daemonServerRepository
) {
}
/**
* The daemon notifies us about the archive status.
*
* @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException
* @throws \Throwable
*/
public function archive(Request $request, string $uuid): JsonResponse
{
$server = $this->repository->getByUuid($uuid);
// Unsuspend the server and don't continue the transfer.
if (!$request->input('successful')) {
return $this->processFailedTransfer($server->transfer);
}
$this->connection->transaction(function () use ($server) {
// This token is used by the new node the server is being transferred to. It allows
// that node to communicate with the old node during the process to initiate the
// actual file transfer.
$token = $this->jwtService
->setExpiresAt(CarbonImmutable::now()->addMinutes(15))
->setSubject($server->uuid)
->handle($server->node, $server->uuid, 'sha256');
// Update the archived field on the transfer to make clients connect to the websocket
// on the new node to be able to receive transfer logs.
$server->transfer->forceFill(['archived' => true])->saveOrFail();
// On the daemon transfer repository, make sure to set the node after the server
// because setServer() tells the repository to use the server's node and not the one
// we want to specify.
$this->daemonTransferRepository
->setServer($server)
->setNode($server->transfer->newNode)
->notify($server, $token);
});
return new JsonResponse([], Response::HTTP_NO_CONTENT);
}
/**
* The daemon notifies us about a transfer failure.
*
@ -79,8 +34,12 @@ class ServerTransferController extends Controller
public function failure(string $uuid): JsonResponse
{
$server = $this->repository->getByUuid($uuid);
$transfer = $server->transfer;
if (is_null($transfer)) {
throw new ConflictHttpException('Server is not being transferred.');
}
return $this->processFailedTransfer($server->transfer);
return $this->processFailedTransfer($transfer);
}
/**
@ -92,6 +51,9 @@ class ServerTransferController extends Controller
{
$server = $this->repository->getByUuid($uuid);
$transfer = $server->transfer;
if (is_null($transfer)) {
throw new ConflictHttpException('Server is not being transferred.');
}
/** @var \Pterodactyl\Models\Server $server */
$server = $this->connection->transaction(function () use ($server, $transfer) {