From 54b6fb5ebd6fdd310dd38a46ce366f51cca1525f Mon Sep 17 00:00:00 2001 From: Dane Everitt Date: Sun, 17 Dec 2017 14:57:05 -0600 Subject: [PATCH] More work on the API utilizing Laravel 5.5 exception rendering Also corrects API format to maintain JSONAPI spec --- app/Exceptions/DisplayException.php | 28 ++++- app/Exceptions/DisplayValidationException.php | 14 --- app/Exceptions/Handler.php | 111 +++++++++++++----- .../Model/DataValidationException.php | 45 +++++-- app/Exceptions/PterodactylException.php | 7 -- .../Daemon/InvalidPowerSignalException.php | 11 +- .../DuplicateDatabaseNameException.php | 7 -- .../Repository/RecordNotFoundException.php | 21 ++-- .../Repository/RepositoryException.php | 11 +- .../API/Admin/Nodes/NodeController.php | 70 +++++++++++ .../API/Admin/Users/UserController.php | 6 +- .../Admin/AllocationTransformer.php | 82 ------------- .../Admin/LocationTransformer.php | 86 -------------- app/Transformers/Admin/NodeTransformer.php | 101 ---------------- .../Api/Admin/AllocationTransformer.php | 31 +++++ .../Api/Admin/LocationTransformer.php | 69 +++++++++++ .../Api/Admin/NodeTransformer.php | 84 +++++++++++++ .../{ => Api}/Admin/OptionTransformer.php | 0 .../{ => Api}/Admin/PackTransformer.php | 0 .../{ => Api}/Admin/ServerTransformer.php | 9 +- .../Admin/ServerVariableTransformer.php | 0 .../{ => Api}/Admin/ServiceTransformer.php | 0 .../Admin/ServiceVariableTransformer.php | 0 .../{ => Api}/Admin/SubuserTransformer.php | 0 .../{ => Api}/Admin/UserTransformer.php | 17 +-- app/Transformers/{ => Api}/ApiTransformer.php | 25 +++- config/{laravel-fractal.php => fractal.php} | 15 +++ routes/api-admin.php | 5 + 28 files changed, 464 insertions(+), 391 deletions(-) delete mode 100644 app/Exceptions/DisplayValidationException.php create mode 100644 app/Http/Controllers/API/Admin/Nodes/NodeController.php delete mode 100644 app/Transformers/Admin/AllocationTransformer.php delete mode 100644 app/Transformers/Admin/LocationTransformer.php delete mode 100644 app/Transformers/Admin/NodeTransformer.php create mode 100644 app/Transformers/Api/Admin/AllocationTransformer.php create mode 100644 app/Transformers/Api/Admin/LocationTransformer.php create mode 100644 app/Transformers/Api/Admin/NodeTransformer.php rename app/Transformers/{ => Api}/Admin/OptionTransformer.php (100%) rename app/Transformers/{ => Api}/Admin/PackTransformer.php (100%) rename app/Transformers/{ => Api}/Admin/ServerTransformer.php (95%) rename app/Transformers/{ => Api}/Admin/ServerVariableTransformer.php (100%) rename app/Transformers/{ => Api}/Admin/ServiceTransformer.php (100%) rename app/Transformers/{ => Api}/Admin/ServiceVariableTransformer.php (100%) rename app/Transformers/{ => Api}/Admin/SubuserTransformer.php (100%) rename app/Transformers/{ => Api}/Admin/UserTransformer.php (72%) rename app/Transformers/{ => Api}/ApiTransformer.php (67%) rename config/{laravel-fractal.php => fractal.php} (55%) diff --git a/app/Exceptions/DisplayException.php b/app/Exceptions/DisplayException.php index aa18a1c1b..3dd0f75d6 100644 --- a/app/Exceptions/DisplayException.php +++ b/app/Exceptions/DisplayException.php @@ -11,9 +11,12 @@ namespace Pterodactyl\Exceptions; use Log; use Throwable; +use Prologue\Alerts\AlertsMessageBag; class DisplayException extends PterodactylException { + const LEVEL_DEBUG = 'debug'; + const LEVEL_INFO = 'info'; const LEVEL_WARNING = 'warning'; const LEVEL_ERROR = 'error'; @@ -32,13 +35,13 @@ class DisplayException extends PterodactylException */ public function __construct($message, Throwable $previous = null, $level = self::LEVEL_ERROR, $code = 0) { - $this->level = $level; + parent::__construct($message, $code, $previous); if (! is_null($previous)) { Log::{$level}($previous); } - parent::__construct($message, $code, $previous); + $this->level = $level; } /** @@ -48,4 +51,25 @@ class DisplayException extends PterodactylException { return $this->level; } + + /** + * Render the exception to the user by adding a flashed message to the session + * and then redirecting them back to the page that they came from. If the + * request originated from an API hit, return the error in JSONAPI spec format. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\JsonResponse|\Illuminate\Http\RedirectResponse + */ + public function render($request) + { + if ($request->expectsJson()) { + return response()->json(Handler::convertToArray($this, [ + 'detail' => $this->getMessage(), + ]), 500); + } + + app()->make(AlertsMessageBag::class)->danger($this->getMessage())->flash(); + + return redirect()->back()->withInput(); + } } diff --git a/app/Exceptions/DisplayValidationException.php b/app/Exceptions/DisplayValidationException.php deleted file mode 100644 index 38ae082f6..000000000 --- a/app/Exceptions/DisplayValidationException.php +++ /dev/null @@ -1,14 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Exceptions; - -class DisplayValidationException extends DisplayException -{ -} diff --git a/app/Exceptions/Handler.php b/app/Exceptions/Handler.php index 9f94b003e..450328096 100644 --- a/app/Exceptions/Handler.php +++ b/app/Exceptions/Handler.php @@ -3,13 +3,11 @@ namespace Pterodactyl\Exceptions; use Exception; -use Prologue\Alerts\Facades\Alert; use Illuminate\Auth\AuthenticationException; use Illuminate\Session\TokenMismatchException; use Illuminate\Validation\ValidationException; use Illuminate\Auth\Access\AuthorizationException; use Illuminate\Database\Eloquent\ModelNotFoundException; -use Pterodactyl\Exceptions\Model\DataValidationException; use Symfony\Component\HttpKernel\Exception\HttpException; use Pterodactyl\Exceptions\Repository\RecordNotFoundException; use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler; @@ -25,8 +23,6 @@ class Handler extends ExceptionHandler AuthenticationException::class, AuthorizationException::class, DisplayException::class, - DataValidationException::class, - DisplayValidationException::class, HttpException::class, ModelNotFoundException::class, RecordNotFoundException::class, @@ -34,6 +30,16 @@ class Handler extends ExceptionHandler ValidationException::class, ]; + /** + * A list of the inputs that are never flashed for validation exceptions. + * + * @var array + */ + protected $dontFlash = [ + 'password', + 'password_confirmation', + ]; + /** * Report or log an exception. * @@ -53,41 +59,78 @@ class Handler extends ExceptionHandler * * @param \Illuminate\Http\Request $request * @param \Exception $exception - * @return \Illuminate\Http\JsonResponse|\Symfony\Component\HttpFoundation\Response + * @return \Symfony\Component\HttpFoundation\Response * * @throws \Exception */ public function render($request, Exception $exception) { - if ($request->expectsJson() || $request->isJson() || $request->is(...config('pterodactyl.json_routes'))) { - $exception = $this->prepareException($exception); + return parent::render($request, $exception); + } - if (config('app.debug') || $this->isHttpException($exception) || $exception instanceof DisplayException) { - $displayError = $exception->getMessage(); - } else { - $displayError = 'An unhandled exception was encountered with this request.'; + /** + * @param \Illuminate\Http\Request $request + * @param \Illuminate\Validation\ValidationException $exception + * @return \Illuminate\Http\JsonResponse + */ + public function invalidJson($request, ValidationException $exception) + { + $codes = collect($exception->validator->failed())->mapWithKeys(function ($reasons, $field) { + $cleaned = []; + foreach ($reasons as $reason => $attrs) { + $cleaned[] = snake_case($reason); } - $response = response()->json( - [ - 'error' => $displayError, - 'type' => (! config('app.debug')) ? null : class_basename($exception), - 'http_code' => (method_exists($exception, 'getStatusCode')) ? $exception->getStatusCode() : 500, - 'trace' => (! config('app.debug')) ? null : $exception->getTrace(), + return [$field => $cleaned]; + })->toArray(); + + $errors = collect($exception->errors())->map(function ($errors, $field) use ($codes) { + $response = []; + foreach ($errors as $key => $error) { + $response[] = [ + 'code' => array_get($codes, $field . '.' . $key), + 'detail' => $error, + 'source' => ['field' => $field], + ]; + } + + return $response; + })->flatMap(function ($errors) { + return $errors; + })->toArray(); + + return response()->json(['errors' => $errors], $exception->status); + } + + /** + * Return the exception as a JSONAPI representation for use on API requests. + * + * @param \Exception $exception + * @param array $override + * @return array + */ + public static function convertToArray(Exception $exception, array $override = []): array + { + $error = [ + 'code' => class_basename($exception), + 'status' => method_exists($exception, 'getStatusCode') ? strval($exception->getStatusCode()) : '500', + 'detail' => 'An error was encountered while processing this request.', + ]; + + if (config('app.debug')) { + $error = array_merge($error, [ + 'detail' => $exception->getMessage(), + 'source' => [ + 'line' => $exception->getLine(), + 'file' => str_replace(base_path(), '', $exception->getFile()), ], - $this->isHttpException($exception) ? $exception->getStatusCode() : 500, - $this->isHttpException($exception) ? $exception->getHeaders() : [], - JSON_UNESCAPED_SLASHES - ); - - parent::report($exception); - } elseif ($exception instanceof DisplayException) { - Alert::danger($exception->getMessage())->flash(); - - return redirect()->back()->withInput(); + 'meta' => [ + 'trace' => explode("\n", $exception->getTraceAsString()), + ], + ]); } - return (isset($response)) ? $response : parent::render($request, $exception); + return ['errors' => [array_merge($error, $override)]]; } /** @@ -105,4 +148,16 @@ class Handler extends ExceptionHandler return redirect()->guest(route('auth.login')); } + + /** + * Converts an exception into an array to render in the response. Overrides + * Laravel's built-in converter to output as a JSONAPI spec compliant object. + * + * @param \Exception $exception + * @return array + */ + protected function convertExceptionToArray(Exception $exception) + { + return self::convertToArray($exception); + } } diff --git a/app/Exceptions/Model/DataValidationException.php b/app/Exceptions/Model/DataValidationException.php index 186f6e7b9..5840e6d9c 100644 --- a/app/Exceptions/Model/DataValidationException.php +++ b/app/Exceptions/Model/DataValidationException.php @@ -1,20 +1,21 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Model; use Illuminate\Contracts\Validation\Validator; -use Illuminate\Validation\ValidationException; +use Pterodactyl\Exceptions\PterodactylException; use Illuminate\Contracts\Support\MessageProvider; +use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface; -class DataValidationException extends ValidationException implements MessageProvider +class DataValidationException extends PterodactylException implements HttpExceptionInterface, MessageProvider { + /** + * The validator instance. + * + * @var \Illuminate\Contracts\Validation\Validator + */ + public $validator; + /** * DataValidationException constructor. * @@ -22,14 +23,38 @@ class DataValidationException extends ValidationException implements MessageProv */ public function __construct(Validator $validator) { - parent::__construct($validator); + parent::__construct( + 'Data integrity exception encountered while performing database write operation. ' . $validator->errors()->toJson() + ); + + $this->validator = $validator; } /** + * Return the validator message bag. + * * @return \Illuminate\Support\MessageBag */ public function getMessageBag() { return $this->validator->errors(); } + + /** + * Return the status code for this request. + * + * @return int + */ + public function getStatusCode() + { + return 500; + } + + /** + * @return array + */ + public function getHeaders() + { + return []; + } } diff --git a/app/Exceptions/PterodactylException.php b/app/Exceptions/PterodactylException.php index 1005aab58..451ae92cb 100644 --- a/app/Exceptions/PterodactylException.php +++ b/app/Exceptions/PterodactylException.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions; diff --git a/app/Exceptions/Repository/Daemon/InvalidPowerSignalException.php b/app/Exceptions/Repository/Daemon/InvalidPowerSignalException.php index be44103ff..9c05f70cc 100644 --- a/app/Exceptions/Repository/Daemon/InvalidPowerSignalException.php +++ b/app/Exceptions/Repository/Daemon/InvalidPowerSignalException.php @@ -1,14 +1,9 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Repository\Daemon; -class InvalidPowerSignalException extends \Exception +use Pterodactyl\Exceptions\Repository\RepositoryException; + +class InvalidPowerSignalException extends RepositoryException { } diff --git a/app/Exceptions/Repository/DuplicateDatabaseNameException.php b/app/Exceptions/Repository/DuplicateDatabaseNameException.php index 9b09602f6..114695282 100644 --- a/app/Exceptions/Repository/DuplicateDatabaseNameException.php +++ b/app/Exceptions/Repository/DuplicateDatabaseNameException.php @@ -1,11 +1,4 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Repository; diff --git a/app/Exceptions/Repository/RecordNotFoundException.php b/app/Exceptions/Repository/RecordNotFoundException.php index 82f032ce3..f449faa42 100644 --- a/app/Exceptions/Repository/RecordNotFoundException.php +++ b/app/Exceptions/Repository/RecordNotFoundException.php @@ -1,21 +1,20 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Repository; -class RecordNotFoundException extends \Exception +class RecordNotFoundException extends RepositoryException { /** - * @return int + * Handle request to render this exception to a user. Returns the default + * 404 page view. + * + * @param \Illuminate\Http\Request $request + * @return \Illuminate\Http\Response */ - public function getStatusCode() + public function render($request) { - return 404; + if (! config('app.debug')) { + return response()->view('errors.404', [], 404); + } } } diff --git a/app/Exceptions/Repository/RepositoryException.php b/app/Exceptions/Repository/RepositoryException.php index 439ea4e43..d362cd423 100644 --- a/app/Exceptions/Repository/RepositoryException.php +++ b/app/Exceptions/Repository/RepositoryException.php @@ -1,14 +1,9 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ namespace Pterodactyl\Exceptions\Repository; -class RepositoryException extends \Exception +use Pterodactyl\Exceptions\PterodactylException; + +class RepositoryException extends PterodactylException { } diff --git a/app/Http/Controllers/API/Admin/Nodes/NodeController.php b/app/Http/Controllers/API/Admin/Nodes/NodeController.php new file mode 100644 index 000000000..e0500975e --- /dev/null +++ b/app/Http/Controllers/API/Admin/Nodes/NodeController.php @@ -0,0 +1,70 @@ +fractal = $fractal; + $this->repository = $repository; + } + + /** + * Return all of the nodes currently available on the Panel. + * + * @param \Illuminate\Http\Request $request + * @return array + */ + public function index(Request $request): array + { + $nodes = $this->repository->all(config('pterodactyl.paginate.api.nodes')); + + $fractal = $this->fractal->collection($nodes) + ->transformWith(new NodeTransformer($request)) + ->withResourceName('node') + ->paginateWith(new IlluminatePaginatorAdapter($nodes)); + + return $fractal->toArray(); + } + + /** + * Return data for a single instance of a node. + * + * @param \Illuminate\Http\Request $request + * @param \Pterodactyl\Models\Node $node + * @return array + */ + public function view(Request $request, Node $node): array + { + $fractal = $this->fractal->item($node) + ->transformWith(new NodeTransformer($request)) + ->withResourceName('node'); + + return $fractal->toArray(); + } +} diff --git a/app/Http/Controllers/API/Admin/Users/UserController.php b/app/Http/Controllers/API/Admin/Users/UserController.php index 68b6c0062..74bcdbee9 100644 --- a/app/Http/Controllers/API/Admin/Users/UserController.php +++ b/app/Http/Controllers/API/Admin/Users/UserController.php @@ -11,8 +11,8 @@ use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Services\Users\UserUpdateService; use Pterodactyl\Services\Users\UserCreationService; use Pterodactyl\Services\Users\UserDeletionService; -use Pterodactyl\Transformers\Admin\UserTransformer; use Pterodactyl\Http\Requests\Admin\UserFormRequest; +use Pterodactyl\Transformers\Api\Admin\UserTransformer; use League\Fractal\Pagination\IlluminatePaginatorAdapter; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; @@ -83,7 +83,7 @@ class UserController extends Controller ->withResourceName('user') ->paginateWith(new IlluminatePaginatorAdapter($users)); - if (config('pterodactyl.api.include_on_list') && $request->has('include')) { + if (config('pterodactyl.api.include_on_list') && $request->filled('include')) { $fractal->parseIncludes(explode(',', $request->input('include'))); } @@ -104,7 +104,7 @@ class UserController extends Controller ->transformWith(new UserTransformer($request)) ->withResourceName('user'); - if ($request->has('include')) { + if ($request->filled('include')) { $fractal->parseIncludes(explode(',', $request->input('include'))); } diff --git a/app/Transformers/Admin/AllocationTransformer.php b/app/Transformers/Admin/AllocationTransformer.php deleted file mode 100644 index e1395ea6b..000000000 --- a/app/Transformers/Admin/AllocationTransformer.php +++ /dev/null @@ -1,82 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Transformers\Admin; - -use Illuminate\Http\Request; -use Pterodactyl\Models\Allocation; -use League\Fractal\TransformerAbstract; - -class AllocationTransformer extends TransformerAbstract -{ - /** - * The filter to be applied to this transformer. - * - * @var bool|string - */ - protected $filter; - - /** - * The Illuminate Request object if provided. - * - * @var \Illuminate\Http\Request|bool - */ - protected $request; - - /** - * Setup request object for transformer. - * - * @param \Illuminate\Http\Request|bool $request - * @param bool $filter - */ - public function __construct($request = false, $filter = false) - { - if (! $request instanceof Request && $request !== false) { - throw new DisplayException('Request passed to constructor must be of type Request or false.'); - } - - $this->request = $request; - $this->filter = $filter; - } - - /** - * Return a generic transformed allocation array. - * - * @return array - */ - public function transform(Allocation $allocation) - { - return $this->transformWithFilter($allocation); - } - - /** - * Determine which transformer filter to apply. - * - * @return array - */ - protected function transformWithFilter(Allocation $allocation) - { - if ($this->filter === 'server') { - return $this->transformForServer($allocation); - } - - return $allocation->toArray(); - } - - /** - * Transform the allocation to only return information not duplicated - * in the server response (discard node_id and server_id). - * - * @return array - */ - protected function transformForServer(Allocation $allocation) - { - return collect($allocation)->only('id', 'ip', 'ip_alias', 'port')->toArray(); - } -} diff --git a/app/Transformers/Admin/LocationTransformer.php b/app/Transformers/Admin/LocationTransformer.php deleted file mode 100644 index 2ae26647d..000000000 --- a/app/Transformers/Admin/LocationTransformer.php +++ /dev/null @@ -1,86 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Transformers\Admin; - -use Illuminate\Http\Request; -use Pterodactyl\Models\Location; -use League\Fractal\TransformerAbstract; - -class LocationTransformer extends TransformerAbstract -{ - /** - * List of resources that can be included. - * - * @var array - */ - protected $availableIncludes = [ - 'nodes', - 'servers', - ]; - - /** - * The Illuminate Request object if provided. - * - * @var \Illuminate\Http\Request|bool - */ - protected $request; - - /** - * Setup request object for transformer. - * - * @param \Illuminate\Http\Request|bool $request - */ - public function __construct($request = false) - { - if (! $request instanceof Request && $request !== false) { - throw new DisplayException('Request passed to constructor must be of type Request or false.'); - } - - $this->request = $request; - } - - /** - * Return a generic transformed pack array. - * - * @return array - */ - public function transform(Location $location) - { - return $location->toArray(); - } - - /** - * Return the nodes associated with this location. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeServers(Location $location) - { - if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { - return; - } - - return $this->collection($location->servers, new ServerTransformer($this->request), 'server'); - } - - /** - * Return the nodes associated with this location. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeNodes(Location $location) - { - if ($this->request && ! $this->request->apiKeyHasPermission('node-list')) { - return; - } - - return $this->collection($location->nodes, new NodeTransformer($this->request), 'node'); - } -} diff --git a/app/Transformers/Admin/NodeTransformer.php b/app/Transformers/Admin/NodeTransformer.php deleted file mode 100644 index 61abf0c74..000000000 --- a/app/Transformers/Admin/NodeTransformer.php +++ /dev/null @@ -1,101 +0,0 @@ -. - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ - -namespace Pterodactyl\Transformers\Admin; - -use Illuminate\Http\Request; -use Pterodactyl\Models\Node; -use League\Fractal\TransformerAbstract; - -class NodeTransformer extends TransformerAbstract -{ - /** - * List of resources that can be included. - * - * @var array - */ - protected $availableIncludes = [ - 'allocations', - 'location', - 'servers', - ]; - - /** - * The Illuminate Request object if provided. - * - * @var \Illuminate\Http\Request|bool - */ - protected $request; - - /** - * Setup request object for transformer. - * - * @param \Illuminate\Http\Request|bool $request - */ - public function __construct($request = false) - { - if (! $request instanceof Request && $request !== false) { - throw new DisplayException('Request passed to constructor must be of type Request or false.'); - } - - $this->request = $request; - } - - /** - * Return a generic transformed pack array. - * - * @return array - */ - public function transform(Node $node) - { - return $node->toArray(); - } - - /** - * Return the nodes associated with this location. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeAllocations(Node $node) - { - if ($this->request && ! $this->request->apiKeyHasPermission('node-view')) { - return; - } - - return $this->collection($node->allocations, new AllocationTransformer($this->request), 'allocation'); - } - - /** - * Return the nodes associated with this location. - * - * @return \Leauge\Fractal\Resource\Item - */ - public function includeLocation(Node $node) - { - if ($this->request && ! $this->request->apiKeyHasPermission('location-list')) { - return; - } - - return $this->item($node->location, new LocationTransformer($this->request), 'location'); - } - - /** - * Return the nodes associated with this location. - * - * @return \Leauge\Fractal\Resource\Collection - */ - public function includeServers(Node $node) - { - if ($this->request && ! $this->request->apiKeyHasPermission('server-list')) { - return; - } - - return $this->collection($node->servers, new ServerTransformer($this->request), 'server'); - } -} diff --git a/app/Transformers/Api/Admin/AllocationTransformer.php b/app/Transformers/Api/Admin/AllocationTransformer.php new file mode 100644 index 000000000..cd466cf42 --- /dev/null +++ b/app/Transformers/Api/Admin/AllocationTransformer.php @@ -0,0 +1,31 @@ +transformWithFilter($allocation); + } + + /** + * Determine which transformer filter to apply. + * + * @param \Pterodactyl\Models\Allocation $allocation + * @return array + */ + protected function transformWithFilter(Allocation $allocation) + { + return $allocation->toArray(); + } +} diff --git a/app/Transformers/Api/Admin/LocationTransformer.php b/app/Transformers/Api/Admin/LocationTransformer.php new file mode 100644 index 000000000..27a4ae9f6 --- /dev/null +++ b/app/Transformers/Api/Admin/LocationTransformer.php @@ -0,0 +1,69 @@ +toArray(); + } + + /** + * Return the nodes associated with this location. + * + * @param \Pterodactyl\Models\Location $location + * @return bool|\League\Fractal\Resource\Collection + * + * @throws \Pterodactyl\Exceptions\PterodactylException + */ + public function includeServers(Location $location) + { + if (! $this->authorize('server-list')) { + return false; + } + + if (! $location->relationLoaded('servers')) { + $location->load('servers'); + } + + return $this->collection($location->getRelation('servers'), new ServerTransformer($this->getRequest()), 'server'); + } + + /** + * Return the nodes associated with this location. + * + * @param \Pterodactyl\Models\Location $location + * @return bool|\League\Fractal\Resource\Collection + * + * @throws \Pterodactyl\Exceptions\PterodactylException + */ + public function includeNodes(Location $location) + { + if (! $this->authorize('node-list')) { + return false; + } + + if (! $location->relationLoaded('nodes')) { + $location->load('nodes'); + } + + return $this->collection($location->getRelation('nodes'), new NodeTransformer($this->getRequest()), 'node'); + } +} diff --git a/app/Transformers/Api/Admin/NodeTransformer.php b/app/Transformers/Api/Admin/NodeTransformer.php new file mode 100644 index 000000000..b65023393 --- /dev/null +++ b/app/Transformers/Api/Admin/NodeTransformer.php @@ -0,0 +1,84 @@ +toArray(); + } + + /** + * Return the nodes associated with this location. + * + * @param \Pterodactyl\Models\Node $node + * @return \League\Fractal\Resource\Collection + */ + public function includeAllocations(Node $node) + { + if (! $node->relationLoaded('allocations')) { + $node->load('allocations'); + } + + return $this->collection($node->getRelation('allocations'), new AllocationTransformer($this->getRequest()), 'allocation'); + } + + /** + * Return the nodes associated with this location. + * + * @param \Pterodactyl\Models\Node $node + * @return bool|\League\Fractal\Resource\Item + * + * @throws \Pterodactyl\Exceptions\PterodactylException + */ + public function includeLocation(Node $node) + { + if (! $this->authorize('location-list')) { + return false; + } + + if (! $node->relationLoaded('location')) { + $node->load('location'); + } + + return $this->item($node->getRelation('location'), new LocationTransformer($this->getRequest()), 'location'); + } + + /** + * Return the nodes associated with this location. + * + * @param \Pterodactyl\Models\Node $node + * @return bool|\League\Fractal\Resource\Collection + * + * @throws \Pterodactyl\Exceptions\PterodactylException + */ + public function includeServers(Node $node) + { + if (! $this->authorize('server-list')) { + return false; + } + + if (! $node->relationLoaded('servers')) { + $node->load('servers'); + } + + return $this->collection($node->getRelation('servers'), new ServerTransformer($this->getRequest()), 'server'); + } +} diff --git a/app/Transformers/Admin/OptionTransformer.php b/app/Transformers/Api/Admin/OptionTransformer.php similarity index 100% rename from app/Transformers/Admin/OptionTransformer.php rename to app/Transformers/Api/Admin/OptionTransformer.php diff --git a/app/Transformers/Admin/PackTransformer.php b/app/Transformers/Api/Admin/PackTransformer.php similarity index 100% rename from app/Transformers/Admin/PackTransformer.php rename to app/Transformers/Api/Admin/PackTransformer.php diff --git a/app/Transformers/Admin/ServerTransformer.php b/app/Transformers/Api/Admin/ServerTransformer.php similarity index 95% rename from app/Transformers/Admin/ServerTransformer.php rename to app/Transformers/Api/Admin/ServerTransformer.php index b2fd82747..7cb722537 100644 --- a/app/Transformers/Admin/ServerTransformer.php +++ b/app/Transformers/Api/Admin/ServerTransformer.php @@ -1,13 +1,6 @@ . - * - * This software is licensed under the terms of the MIT license. - * https://opensource.org/licenses/MIT - */ -namespace Pterodactyl\Transformers\Admin; +namespace Pterodactyl\Transformers\Api\Admin; use Illuminate\Http\Request; use Pterodactyl\Models\Server; diff --git a/app/Transformers/Admin/ServerVariableTransformer.php b/app/Transformers/Api/Admin/ServerVariableTransformer.php similarity index 100% rename from app/Transformers/Admin/ServerVariableTransformer.php rename to app/Transformers/Api/Admin/ServerVariableTransformer.php diff --git a/app/Transformers/Admin/ServiceTransformer.php b/app/Transformers/Api/Admin/ServiceTransformer.php similarity index 100% rename from app/Transformers/Admin/ServiceTransformer.php rename to app/Transformers/Api/Admin/ServiceTransformer.php diff --git a/app/Transformers/Admin/ServiceVariableTransformer.php b/app/Transformers/Api/Admin/ServiceVariableTransformer.php similarity index 100% rename from app/Transformers/Admin/ServiceVariableTransformer.php rename to app/Transformers/Api/Admin/ServiceVariableTransformer.php diff --git a/app/Transformers/Admin/SubuserTransformer.php b/app/Transformers/Api/Admin/SubuserTransformer.php similarity index 100% rename from app/Transformers/Admin/SubuserTransformer.php rename to app/Transformers/Api/Admin/SubuserTransformer.php diff --git a/app/Transformers/Admin/UserTransformer.php b/app/Transformers/Api/Admin/UserTransformer.php similarity index 72% rename from app/Transformers/Admin/UserTransformer.php rename to app/Transformers/Api/Admin/UserTransformer.php index 90547948a..91d30ea6b 100644 --- a/app/Transformers/Admin/UserTransformer.php +++ b/app/Transformers/Api/Admin/UserTransformer.php @@ -1,10 +1,9 @@ request = $request; - } - /** * Return a generic transformed subuser array. * @@ -54,6 +43,6 @@ class UserTransformer extends ApiTransformer $user->load('servers'); } - return $this->collection($user->getRelation('servers'), new ServerTransformer($this->request), 'server'); + return $this->collection($user->getRelation('servers'), new ServerTransformer($this->getRequest()), 'server'); } } diff --git a/app/Transformers/ApiTransformer.php b/app/Transformers/Api/ApiTransformer.php similarity index 67% rename from app/Transformers/ApiTransformer.php rename to app/Transformers/Api/ApiTransformer.php index d2334a079..3620af4db 100644 --- a/app/Transformers/ApiTransformer.php +++ b/app/Transformers/Api/ApiTransformer.php @@ -1,7 +1,8 @@ request = $request; + } + + /** + * Return the request instance being used for this transformer. + * + * @return \Illuminate\Http\Request + */ + public function getRequest(): Request + { + return $this->request; + } /** * Determine if an API key from the request has permission to access diff --git a/config/laravel-fractal.php b/config/fractal.php similarity index 55% rename from config/laravel-fractal.php rename to config/fractal.php index 92bfe2cea..833cf2b95 100644 --- a/config/laravel-fractal.php +++ b/config/fractal.php @@ -13,4 +13,19 @@ return [ */ 'default_serializer' => League\Fractal\Serializer\JsonApiSerializer::class, + + /* + |-------------------------------------------------------------------------- + | Auto Includes + |-------------------------------------------------------------------------- + | + | If enabled Fractal will automatically add the includes who's + | names are present in the `include` request parameter. + | + */ + + 'auto_includes' => [ + 'enabled' => true, + 'request_key' => 'include', + ], ]; diff --git a/routes/api-admin.php b/routes/api-admin.php index 884b00dbc..772ec7657 100644 --- a/routes/api-admin.php +++ b/routes/api-admin.php @@ -17,3 +17,8 @@ Route::group(['prefix' => '/users'], function () { Route::delete('/{user}', 'Users\UserController@delete')->name('api.admin.user.delete'); }); + +Route::group(['prefix' => '/nodes'], function () { + Route::get('/', 'Nodes\NodeController@index')->name('api.admin.node.list'); + Route::get('/{node}', 'Nodes\NodeController@view')->name('api.admin.node.view'); +});