api(application): relocate egg endpoints

This commit is contained in:
Matthew Penner 2021-01-08 09:25:40 -07:00
parent 5946210e18
commit 58cfa98b9c
23 changed files with 309 additions and 112 deletions

View file

@ -0,0 +1,122 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Eggs;
use Pterodactyl\Models\Egg;
use Pterodactyl\Models\Nest;
use Illuminate\Http\JsonResponse;
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
use Pterodactyl\Transformers\Api\Application\EggTransformer;
use Pterodactyl\Http\Requests\Api\Application\Eggs\GetEggRequest;
use Pterodactyl\Http\Requests\Api\Application\Eggs\GetEggsRequest;
use Pterodactyl\Http\Requests\Api\Application\Eggs\StoreEggRequest;
use Pterodactyl\Http\Requests\Api\Application\Eggs\UpdateEggRequest;
use Pterodactyl\Http\Requests\Api\Application\Eggs\DeleteEggRequest;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class EggController extends ApplicationApiController
{
/**
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface
*/
private $repository;
/**
* EggController constructor.
*
* @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository
*/
public function __construct(EggRepositoryInterface $repository)
{
parent::__construct();
$this->repository = $repository;
}
/**
* Return an array of all eggs on a given nest.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Eggs\GetEggsRequest $request
* @param \Pterodactyl\Models\Nest $nest
*
* @return array
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function index(GetEggsRequest $request, Nest $nest): array
{
$eggs = $this->repository->findWhere([
['nest_id', '=', $nest->id],
]);
return $this->fractal->collection($eggs)
->transformWith($this->getTransformer(EggTransformer::class))
->toArray();
}
/**
* Returns a single egg.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Eggs\GetEggRequest $request
* @param \Pterodactyl\Models\Egg $egg
*
* @return array
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function view(GetEggRequest $request, Egg $egg): array
{
return $this->fractal->item($egg)
->transformWith($this->getTransformer(EggTransformer::class))
->toArray();
}
/**
* Creates a new egg.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Eggs\StoreEggRequest $request
*
* @return \Illuminate\Http\JsonResponse
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function store(StoreEggRequest $request): JsonResponse
{
$egg = Egg::query()->create($request->validated());
return $this->fractal->item($egg)
->transformWith($this->getTransformer(EggTransformer::class))
->respond(JsonResponse::HTTP_CREATED);
}
/**
* Updates an egg.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Eggs\UpdateEggRequest $request
* @param \Pterodactyl\Models\Egg $egg
*
* @return array
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function update(UpdateEggRequest $request, Egg $egg): array
{
$egg->update($request->validated());
return $this->fractal->item($egg)
->transformWith($this->getTransformer(EggTransformer::class))
->toArray();
}
/**
* Deletes an egg.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Eggs\DeleteEggRequest $request
* @param \Pterodactyl\Models\Egg $egg
*
* @return \Illuminate\Http\JsonResponse
* @throws \Exception
*/
public function delete(DeleteEggRequest $request, Egg $egg): JsonResponse
{
$egg->delete();
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
}
}

View file

@ -1,67 +0,0 @@
<?php
namespace Pterodactyl\Http\Controllers\Api\Application\Nests;
use Pterodactyl\Models\Egg;
use Pterodactyl\Models\Nest;
use Pterodactyl\Contracts\Repository\EggRepositoryInterface;
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
{
/**
* @var \Pterodactyl\Contracts\Repository\EggRepositoryInterface
*/
private $repository;
/**
* EggController constructor.
*
* @param \Pterodactyl\Contracts\Repository\EggRepositoryInterface $repository
*/
public function __construct(EggRepositoryInterface $repository)
{
parent::__construct();
$this->repository = $repository;
}
/**
* Return all eggs that exist for a given nest.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggsRequest $request
* @param \Pterodactyl\Models\Nest $nest
*
* @return array
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function index(GetEggsRequest $request, Nest $nest): array
{
$eggs = $this->repository->findWhere([
['nest_id', '=', $nest->id],
]);
return $this->fractal->collection($eggs)
->transformWith($this->getTransformer(EggTransformer::class))
->toArray();
}
/**
* Return a single egg that exists on the specified nest.
*
* @param \Pterodactyl\Http\Requests\Api\Application\Nests\Eggs\GetEggRequest $request
* @param \Pterodactyl\Models\Egg $egg
*
* @return array
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function view(GetEggRequest $request, Egg $egg): array
{
return $this->fractal->item($egg)
->transformWith($this->getTransformer(EggTransformer::class))
->toArray();
}
}

View file

@ -12,9 +12,9 @@ use Pterodactyl\Transformers\Api\Application\NestTransformer;
use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestRequest; use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestRequest;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestsRequest; use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestsRequest;
use Pterodactyl\Http\Requests\Api\Application\Nests\StoreNestRequest; use Pterodactyl\Http\Requests\Api\Application\Nests\StoreEggRequest;
use Pterodactyl\Http\Requests\Api\Application\Nests\UpdateNestRequest; use Pterodactyl\Http\Requests\Api\Application\Nests\UpdateNestRequest;
use Pterodactyl\Http\Requests\Api\Application\Nests\DeleteNestRequest; use Pterodactyl\Http\Requests\Api\Application\Nests\DeleteEggRequest;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class NestController extends ApplicationApiController class NestController extends ApplicationApiController
@ -105,13 +105,13 @@ class NestController extends ApplicationApiController
/** /**
* Creates a new nest. * Creates a new nest.
* *
* @param \Pterodactyl\Http\Requests\Api\Application\Nests\StoreNestRequest $request * @param \Pterodactyl\Http\Requests\Api\Application\Nests\StoreEggRequest $request
* *
* @return array * @return array
* @throws \Illuminate\Contracts\Container\BindingResolutionException * @throws \Illuminate\Contracts\Container\BindingResolutionException
* @throws \Pterodactyl\Exceptions\Model\DataValidationException * @throws \Pterodactyl\Exceptions\Model\DataValidationException
*/ */
public function store(StoreNestRequest $request): array public function store(StoreEggRequest $request): array
{ {
$nest = $this->nestCreationService->handle($request->validated()); $nest = $this->nestCreationService->handle($request->validated());
@ -143,13 +143,13 @@ class NestController extends ApplicationApiController
/** /**
* Deletes an existing nest. * Deletes an existing nest.
* *
* @param \Pterodactyl\Http\Requests\Api\Application\Nests\DeleteNestRequest $request * @param \Pterodactyl\Http\Requests\Api\Application\Nests\DeleteEggRequest $request
* @param \Pterodactyl\Models\Nest $nest * @param \Pterodactyl\Models\Nest $nest
* *
* @return \Illuminate\Http\JsonResponse * @return \Illuminate\Http\JsonResponse
* @throws \Pterodactyl\Exceptions\Service\HasActiveServersException * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException
*/ */
public function delete(DeleteNestRequest $request, Nest $nest): JsonResponse public function delete(DeleteEggRequest $request, Nest $nest): JsonResponse
{ {
$this->nestDeletionService->handle($nest->id); $this->nestDeletionService->handle($nest->id);

View file

@ -1,13 +1,12 @@
<?php <?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests\Eggs; namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
use Pterodactyl\Models\Egg; use Pterodactyl\Models\Egg;
use Pterodactyl\Models\Nest;
use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class GetEggRequest extends ApplicationApiRequest class DeleteEggRequest extends ApplicationApiRequest
{ {
/** /**
* @var string * @var string
@ -17,15 +16,17 @@ class GetEggRequest extends ApplicationApiRequest
/** /**
* @var int * @var int
*/ */
protected $permission = AdminAcl::READ; protected $permission = AdminAcl::WRITE;
/** /**
* Determine if the requested egg exists for the selected nest. * Determine if the requested egg exists on the Panel.
* *
* @return bool * @return bool
*/ */
public function resourceExists(): bool public function resourceExists(): bool
{ {
return $this->getModel(Nest::class)->id === $this->getModel(Egg::class)->nest_id; $egg = $this->route()->parameter('egg');
return $egg instanceof Egg && $egg->exists;
} }
} }

View file

@ -0,0 +1,20 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
use Pterodactyl\Models\Egg;
class GetEggRequest extends GetEggsRequest
{
/**
* Determine if the requested egg exists on the Panel.
*
* @return bool
*/
public function resourceExists(): bool
{
$egg = $this->route()->parameter('egg');
return $egg instanceof Egg && $egg->exists;
}
}

View file

@ -1,6 +1,6 @@
<?php <?php
namespace Pterodactyl\Http\Requests\Api\Application\Nests\Eggs; namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest; use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;

View file

@ -0,0 +1,32 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
use Pterodactyl\Models\Egg;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class StoreEggRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_EGGS;
/**
* @var int
*/
protected $permission = AdminAcl::WRITE;
/**
* ?
*
* @param array|null $rules
*
* @return array
*/
public function rules(array $rules = null): array
{
return $rules ?? Egg::getRules();
}
}

View file

@ -0,0 +1,20 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Eggs;
use Pterodactyl\Models\Egg;
class UpdateEggRequest extends StoreEggRequest
{
/**
* ?
*
* @param array|null $rules
*
* @return array
*/
public function rules(array $rules = null): array
{
return $rules ?? Egg::getRulesForUpdate($this->route()->parameter('egg')->id);
}
}

View file

@ -19,7 +19,7 @@ class DeleteNestRequest extends ApplicationApiRequest
protected $permission = AdminAcl::WRITE; protected $permission = AdminAcl::WRITE;
/** /**
* Determine if the requested role exists on the Panel. * Determine if the requested nest exists on the Panel.
* *
* @return bool * @return bool
*/ */

View file

@ -7,7 +7,7 @@ use Pterodactyl\Models\Nest;
class GetNestRequest extends GetNestsRequest class GetNestRequest extends GetNestsRequest
{ {
/** /**
* Determine if the requested role exists on the Panel. * Determine if the requested nest exists on the Panel.
* *
* @return bool * @return bool
*/ */

View file

@ -4,7 +4,7 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nests;
use Pterodactyl\Models\Nest; use Pterodactyl\Models\Nest;
class UpdateNestRequest extends StoreNestRequest class UpdateNestRequest extends StoreEggRequest
{ {
/** /**
* ? * ?

View file

View file

@ -1,5 +1,5 @@
import http from '@/api/http'; import http from '@/api/http';
import { Egg, rawDataToEgg } from '@/api/admin/nests/eggs/getEggs'; import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg';
export default (nestId: number, name: string): Promise<Egg> => { export default (nestId: number, name: string): Promise<Egg> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {

View file

@ -48,10 +48,10 @@ export const rawDataToEgg = ({ attributes }: FractalResponseData): Egg => ({
updatedAt: new Date(attributes.updated_at), updatedAt: new Date(attributes.updated_at),
}); });
export default (nestId: number): Promise<Egg[]> => { export default (id: number): Promise<Egg> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
http.get(`/api/application/nests/${nestId}`) http.get(`/api/application/eggs/${id}`)
.then(({ data }) => resolve((data.data || []).map(rawDataToEgg))) .then(({ data }) => resolve(rawDataToEgg(data)))
.catch(reject); .catch(reject);
}); });
}; };

View file

@ -1,10 +0,0 @@
import http from '@/api/http';
import { Egg, rawDataToEgg } from '@/api/admin/nests/eggs/getEggs';
export default (id: number): Promise<Egg> => {
return new Promise((resolve, reject) => {
http.get(`/api/application/eggs/${id}`)
.then(({ data }) => resolve(rawDataToEgg(data)))
.catch(reject);
});
};

View file

@ -0,0 +1,10 @@
import http from '@/api/http';
import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg';
export default (nestId: number): Promise<Egg[]> => {
return new Promise((resolve, reject) => {
http.get(`/api/application/nests/${nestId}/eggs`)
.then(({ data }) => resolve((data.data || []).map(rawDataToEgg)))
.catch(reject);
});
};

View file

@ -1,9 +1,9 @@
import http from '@/api/http'; import http from '@/api/http';
import { Nest, rawDataToNest } from '@/api/admin/nests/getNests'; import { Nest, rawDataToNest } from '@/api/admin/nests/getNests';
export default (id: number): Promise<Nest> => { export default (id: number, include: string[]): Promise<Nest> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
http.get(`/api/application/nests/${id}`) http.get(`/api/application/nests/${id}`, { params: { include: include.join(',') } })
.then(({ data }) => resolve(rawDataToNest(data))) .then(({ data }) => resolve(rawDataToNest(data)))
.catch(reject); .catch(reject);
}); });

View file

@ -1,6 +1,7 @@
import http, { FractalResponseData, getPaginationSet, PaginatedResult } from '@/api/http'; import http, { FractalResponseData, FractalResponseList, getPaginationSet, PaginatedResult } from '@/api/http';
import { createContext, useContext } from 'react'; import { createContext, useContext } from 'react';
import useSWR from 'swr'; import useSWR from 'swr';
import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg';
export interface Nest { export interface Nest {
id: number; id: number;
@ -10,6 +11,10 @@ export interface Nest {
description: string | null; description: string | null;
createdAt: Date; createdAt: Date;
updatedAt: Date; updatedAt: Date;
relations: {
eggs: Egg[] | undefined;
},
} }
export const rawDataToNest = ({ attributes }: FractalResponseData): Nest => ({ export const rawDataToNest = ({ attributes }: FractalResponseData): Nest => ({
@ -20,6 +25,10 @@ export const rawDataToNest = ({ attributes }: FractalResponseData): Nest => ({
description: attributes.description, description: attributes.description,
createdAt: new Date(attributes.created_at), createdAt: new Date(attributes.created_at),
updatedAt: new Date(attributes.updated_at), updatedAt: new Date(attributes.updated_at),
relations: {
eggs: ((attributes.relationships?.eggs as FractalResponseList | undefined)?.data || []).map(rawDataToEgg),
},
}); });
interface ctx { interface ctx {

View file

@ -1,9 +1,9 @@
import http from '@/api/http'; import http from '@/api/http';
import { Server, rawDataToServer } from '@/api/admin/servers/getServers'; import { Server, rawDataToServer } from '@/api/admin/servers/getServers';
export default (id: number): Promise<Server> => { export default (id: number, include: string[]): Promise<Server> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
http.get(`/api/application/servers/${id}`) http.get(`/api/application/servers/${id}`, { params: { include: include.join(',') } })
.then(({ data }) => resolve(rawDataToServer(data))) .then(({ data }) => resolve(rawDataToServer(data)))
.catch(reject); .catch(reject);
}); });

View file

@ -1,8 +1,55 @@
import React from 'react'; import React, { useEffect, useState } from 'react';
import { useRouteMatch } from 'react-router-dom';
import tw from 'twin.macro';
import useFlash from '@/plugins/useFlash';
import AdminContentBlock from '@/components/admin/AdminContentBlock';
import Spinner from '@/components/elements/Spinner';
import FlashMessageRender from '@/components/FlashMessageRender';
import { Nest } from '@/api/admin/nests/getNests';
import getNest from '@/api/admin/nests/getNest';
export default () => { export default () => {
const match = useRouteMatch<{ nestId?: string }>();
const { clearFlashes, clearAndAddHttpError } = useFlash();
const [ loading, setLoading ] = useState(true);
const [ nest, setNest ] = useState<Nest | undefined>(undefined);
useEffect(() => {
clearFlashes('nest');
getNest(Number(match.params?.nestId), [ 'eggs' ])
.then(nest => setNest(nest))
.catch(error => {
console.error(error);
clearAndAddHttpError(error);
})
.then(() => setLoading(false));
}, []);
if (loading || nest === undefined) {
return (
<AdminContentBlock title={'Nests'}>
<div css={tw`w-full flex flex-col items-center justify-center`} style={{ height: '24rem' }}>
<Spinner size={'base'}/>
</div>
<FlashMessageRender byKey={'nest'} css={tw`mb-4`}/>
</AdminContentBlock>
);
}
return ( return (
<> <AdminContentBlock title={'Nests - ' + nest.name}>
</> <div css={tw`w-full flex flex-row items-center mb-8`}>
<div css={tw`flex flex-col`}>
<h2 css={tw`text-2xl text-neutral-50 font-header font-medium`}>{nest.name}</h2>
<p css={tw`text-base text-neutral-400`}>{nest.description}</p>
</div>
</div>
<p>{JSON.stringify(nest.relations.eggs)}</p>
</AdminContentBlock>
); );
}; };

View file

@ -233,7 +233,7 @@ const AdminRouter = ({ location, match }: RouteComponentProps) => {
<Route path={`${match.path}/nests`} component={NestsContainer} exact/> <Route path={`${match.path}/nests`} component={NestsContainer} exact/>
<Route <Route
path={`${match.path}/nests/:id`} path={`${match.path}/nests/:nestId`}
component={NestEditContainer} component={NestEditContainer}
exact exact
/> />

View file

@ -23,6 +23,24 @@ Route::group(['prefix' => '/databases'], function () {
Route::delete('/{database}', 'Databases\DatabaseController@delete'); Route::delete('/{database}', 'Databases\DatabaseController@delete');
}); });
/*
|--------------------------------------------------------------------------
| Egg Controller Routes
|--------------------------------------------------------------------------
|
| Endpoint: /api/application/eggs
|
*/
Route::group(['prefix' => '/eggs'], function () {
Route::get('/{egg}', 'Eggs\EggController@view');
Route::post('/', 'Eggs\EggController@store');
Route::patch('/{egg}', 'Eggs\EggController@update');
Route::delete('/{egg}', 'Eggs\EggController@delete');
});
/* /*
|-------------------------------------------------------------------------- |--------------------------------------------------------------------------
| Location Controller Routes | Location Controller Routes
@ -72,18 +90,13 @@ Route::group(['prefix' => '/mounts'], function () {
Route::group(['prefix' => '/nests'], function () { Route::group(['prefix' => '/nests'], function () {
Route::get('/', 'Nests\NestController@index')->name('api.application.nests'); Route::get('/', 'Nests\NestController@index')->name('api.application.nests');
Route::get('/{nest}', 'Nests\NestController@view')->name('api.application.nests.view'); Route::get('/{nest}', 'Nests\NestController@view')->name('api.application.nests.view');
Route::get('/{nest}/eggs', 'Eggs\EggController@index');
Route::post('/', 'Nests\NestController@store'); Route::post('/', 'Nests\NestController@store');
Route::patch('/{nest}', 'Nests\NestController@update'); Route::patch('/{nest}', 'Nests\NestController@update');
Route::delete('/{nest}', 'Nests\NestController@delete'); Route::delete('/{nest}', 'Nests\NestController@delete');
// Egg Management Endpoint
Route::group(['prefix' => '/{nest}/eggs'], function () {
Route::get('/', 'Nests\EggController@index')->name('api.application.nests.eggs');
Route::get('/{egg}', 'Nests\EggController@view')->name('api.application.nests.eggs.view');
});
}); });
/* /*