diff --git a/app/Http/Controllers/Api/Application/Eggs/EggController.php b/app/Http/Controllers/Api/Application/Eggs/EggController.php new file mode 100644 index 000000000..43fc5c0bb --- /dev/null +++ b/app/Http/Controllers/Api/Application/Eggs/EggController.php @@ -0,0 +1,122 @@ +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); + } +} diff --git a/app/Http/Controllers/Api/Application/Nests/EggController.php b/app/Http/Controllers/Api/Application/Nests/EggController.php deleted file mode 100644 index f475afbf6..000000000 --- a/app/Http/Controllers/Api/Application/Nests/EggController.php +++ /dev/null @@ -1,67 +0,0 @@ -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(); - } -} diff --git a/app/Http/Controllers/Api/Application/Nests/NestController.php b/app/Http/Controllers/Api/Application/Nests/NestController.php index 1477db681..89d40c6a1 100644 --- a/app/Http/Controllers/Api/Application/Nests/NestController.php +++ b/app/Http/Controllers/Api/Application/Nests/NestController.php @@ -12,9 +12,9 @@ use Pterodactyl\Transformers\Api\Application\NestTransformer; use Pterodactyl\Http\Requests\Api\Application\Nests\GetNestRequest; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; 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\DeleteNestRequest; +use Pterodactyl\Http\Requests\Api\Application\Nests\DeleteEggRequest; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; class NestController extends ApplicationApiController @@ -105,13 +105,13 @@ class NestController extends ApplicationApiController /** * Creates a new nest. * - * @param \Pterodactyl\Http\Requests\Api\Application\Nests\StoreNestRequest $request + * @param \Pterodactyl\Http\Requests\Api\Application\Nests\StoreEggRequest $request * * @return array * @throws \Illuminate\Contracts\Container\BindingResolutionException * @throws \Pterodactyl\Exceptions\Model\DataValidationException */ - public function store(StoreNestRequest $request): array + public function store(StoreEggRequest $request): array { $nest = $this->nestCreationService->handle($request->validated()); @@ -143,13 +143,13 @@ class NestController extends ApplicationApiController /** * 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 * * @return \Illuminate\Http\JsonResponse * @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); diff --git a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php b/app/Http/Requests/Api/Application/Eggs/DeleteEggRequest.php similarity index 51% rename from app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php rename to app/Http/Requests/Api/Application/Eggs/DeleteEggRequest.php index fb364d1ae..20a4d109f 100644 --- a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggRequest.php +++ b/app/Http/Requests/Api/Application/Eggs/DeleteEggRequest.php @@ -1,13 +1,12 @@ getModel(Nest::class)->id === $this->getModel(Egg::class)->nest_id; + $egg = $this->route()->parameter('egg'); + + return $egg instanceof Egg && $egg->exists; } } diff --git a/app/Http/Requests/Api/Application/Eggs/GetEggRequest.php b/app/Http/Requests/Api/Application/Eggs/GetEggRequest.php new file mode 100644 index 000000000..9405cb809 --- /dev/null +++ b/app/Http/Requests/Api/Application/Eggs/GetEggRequest.php @@ -0,0 +1,20 @@ +route()->parameter('egg'); + + return $egg instanceof Egg && $egg->exists; + } +} diff --git a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php b/app/Http/Requests/Api/Application/Eggs/GetEggsRequest.php similarity index 84% rename from app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php rename to app/Http/Requests/Api/Application/Eggs/GetEggsRequest.php index a6aadf904..8323a369d 100644 --- a/app/Http/Requests/Api/Application/Nests/Eggs/GetEggsRequest.php +++ b/app/Http/Requests/Api/Application/Eggs/GetEggsRequest.php @@ -1,6 +1,6 @@ route()->parameter('egg')->id); + } +} diff --git a/app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php b/app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php index 13bc6a5e4..16843389b 100644 --- a/app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php +++ b/app/Http/Requests/Api/Application/Nests/DeleteNestRequest.php @@ -19,7 +19,7 @@ class DeleteNestRequest extends ApplicationApiRequest protected $permission = AdminAcl::WRITE; /** - * Determine if the requested role exists on the Panel. + * Determine if the requested nest exists on the Panel. * * @return bool */ diff --git a/app/Http/Requests/Api/Application/Nests/GetNestRequest.php b/app/Http/Requests/Api/Application/Nests/GetNestRequest.php index 958541362..db6be4f84 100644 --- a/app/Http/Requests/Api/Application/Nests/GetNestRequest.php +++ b/app/Http/Requests/Api/Application/Nests/GetNestRequest.php @@ -7,7 +7,7 @@ use Pterodactyl\Models\Nest; class GetNestRequest extends GetNestsRequest { /** - * Determine if the requested role exists on the Panel. + * Determine if the requested nest exists on the Panel. * * @return bool */ diff --git a/app/Http/Requests/Api/Application/Nests/UpdateNestRequest.php b/app/Http/Requests/Api/Application/Nests/UpdateNestRequest.php index 7f425ddf5..59536a8f8 100644 --- a/app/Http/Requests/Api/Application/Nests/UpdateNestRequest.php +++ b/app/Http/Requests/Api/Application/Nests/UpdateNestRequest.php @@ -4,7 +4,7 @@ namespace Pterodactyl\Http\Requests\Api\Application\Nests; use Pterodactyl\Models\Nest; -class UpdateNestRequest extends StoreNestRequest +class UpdateNestRequest extends StoreEggRequest { /** * ? diff --git a/app/Policies/.gitkeep b/app/Policies/.gitkeep deleted file mode 100644 index e69de29bb..000000000 diff --git a/resources/scripts/api/admin/nests/eggs/createEgg.ts b/resources/scripts/api/admin/eggs/createEgg.ts similarity index 83% rename from resources/scripts/api/admin/nests/eggs/createEgg.ts rename to resources/scripts/api/admin/eggs/createEgg.ts index 8843a19ea..023183700 100644 --- a/resources/scripts/api/admin/nests/eggs/createEgg.ts +++ b/resources/scripts/api/admin/eggs/createEgg.ts @@ -1,5 +1,5 @@ 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 => { return new Promise((resolve, reject) => { diff --git a/resources/scripts/api/admin/nests/eggs/deleteEgg.ts b/resources/scripts/api/admin/eggs/deleteEgg.ts similarity index 100% rename from resources/scripts/api/admin/nests/eggs/deleteEgg.ts rename to resources/scripts/api/admin/eggs/deleteEgg.ts diff --git a/resources/scripts/api/admin/nests/eggs/getEggs.ts b/resources/scripts/api/admin/eggs/getEgg.ts similarity index 89% rename from resources/scripts/api/admin/nests/eggs/getEggs.ts rename to resources/scripts/api/admin/eggs/getEgg.ts index 3ef36a52b..5ceb3659f 100644 --- a/resources/scripts/api/admin/nests/eggs/getEggs.ts +++ b/resources/scripts/api/admin/eggs/getEgg.ts @@ -48,10 +48,10 @@ export const rawDataToEgg = ({ attributes }: FractalResponseData): Egg => ({ updatedAt: new Date(attributes.updated_at), }); -export default (nestId: number): Promise => { +export default (id: number): Promise => { return new Promise((resolve, reject) => { - http.get(`/api/application/nests/${nestId}`) - .then(({ data }) => resolve((data.data || []).map(rawDataToEgg))) + http.get(`/api/application/eggs/${id}`) + .then(({ data }) => resolve(rawDataToEgg(data))) .catch(reject); }); }; diff --git a/resources/scripts/api/admin/nests/eggs/getEgg.ts b/resources/scripts/api/admin/nests/eggs/getEgg.ts deleted file mode 100644 index 4afc630c3..000000000 --- a/resources/scripts/api/admin/nests/eggs/getEgg.ts +++ /dev/null @@ -1,10 +0,0 @@ -import http from '@/api/http'; -import { Egg, rawDataToEgg } from '@/api/admin/nests/eggs/getEggs'; - -export default (id: number): Promise => { - return new Promise((resolve, reject) => { - http.get(`/api/application/eggs/${id}`) - .then(({ data }) => resolve(rawDataToEgg(data))) - .catch(reject); - }); -}; diff --git a/resources/scripts/api/admin/nests/getEggs.ts b/resources/scripts/api/admin/nests/getEggs.ts new file mode 100644 index 000000000..be5703625 --- /dev/null +++ b/resources/scripts/api/admin/nests/getEggs.ts @@ -0,0 +1,10 @@ +import http from '@/api/http'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; + +export default (nestId: number): Promise => { + return new Promise((resolve, reject) => { + http.get(`/api/application/nests/${nestId}/eggs`) + .then(({ data }) => resolve((data.data || []).map(rawDataToEgg))) + .catch(reject); + }); +}; diff --git a/resources/scripts/api/admin/nests/getNest.ts b/resources/scripts/api/admin/nests/getNest.ts index 2867fc888..23f1cf782 100644 --- a/resources/scripts/api/admin/nests/getNest.ts +++ b/resources/scripts/api/admin/nests/getNest.ts @@ -1,9 +1,9 @@ import http from '@/api/http'; import { Nest, rawDataToNest } from '@/api/admin/nests/getNests'; -export default (id: number): Promise => { +export default (id: number, include: string[]): Promise => { 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))) .catch(reject); }); diff --git a/resources/scripts/api/admin/nests/getNests.ts b/resources/scripts/api/admin/nests/getNests.ts index da1ef41b3..32bc97d39 100644 --- a/resources/scripts/api/admin/nests/getNests.ts +++ b/resources/scripts/api/admin/nests/getNests.ts @@ -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 useSWR from 'swr'; +import { Egg, rawDataToEgg } from '@/api/admin/eggs/getEgg'; export interface Nest { id: number; @@ -10,6 +11,10 @@ export interface Nest { description: string | null; createdAt: Date; updatedAt: Date; + + relations: { + eggs: Egg[] | undefined; + }, } export const rawDataToNest = ({ attributes }: FractalResponseData): Nest => ({ @@ -20,6 +25,10 @@ export const rawDataToNest = ({ attributes }: FractalResponseData): Nest => ({ description: attributes.description, createdAt: new Date(attributes.created_at), updatedAt: new Date(attributes.updated_at), + + relations: { + eggs: ((attributes.relationships?.eggs as FractalResponseList | undefined)?.data || []).map(rawDataToEgg), + }, }); interface ctx { diff --git a/resources/scripts/api/admin/servers/getServer.ts b/resources/scripts/api/admin/servers/getServer.ts index 4e6b0b5c8..be10f0216 100644 --- a/resources/scripts/api/admin/servers/getServer.ts +++ b/resources/scripts/api/admin/servers/getServer.ts @@ -1,9 +1,9 @@ import http from '@/api/http'; import { Server, rawDataToServer } from '@/api/admin/servers/getServers'; -export default (id: number): Promise => { +export default (id: number, include: string[]): Promise => { 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))) .catch(reject); }); diff --git a/resources/scripts/components/admin/nests/NestEditContainer.tsx b/resources/scripts/components/admin/nests/NestEditContainer.tsx index c541988b3..78e034f72 100644 --- a/resources/scripts/components/admin/nests/NestEditContainer.tsx +++ b/resources/scripts/components/admin/nests/NestEditContainer.tsx @@ -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 () => { + const match = useRouteMatch<{ nestId?: string }>(); + + const { clearFlashes, clearAndAddHttpError } = useFlash(); + const [ loading, setLoading ] = useState(true); + + const [ nest, setNest ] = useState(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 ( + +
+ +
+ + +
+ ); + } + return ( - <> - + +
+
+

{nest.name}

+

{nest.description}

+
+
+ +

{JSON.stringify(nest.relations.eggs)}

+
); }; diff --git a/resources/scripts/routers/AdminRouter.tsx b/resources/scripts/routers/AdminRouter.tsx index bd7e408a4..abeb121ef 100644 --- a/resources/scripts/routers/AdminRouter.tsx +++ b/resources/scripts/routers/AdminRouter.tsx @@ -233,7 +233,7 @@ const AdminRouter = ({ location, match }: RouteComponentProps) => { diff --git a/routes/api-application.php b/routes/api-application.php index ecaa5d729..f6554be7e 100644 --- a/routes/api-application.php +++ b/routes/api-application.php @@ -23,6 +23,24 @@ Route::group(['prefix' => '/databases'], function () { 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 @@ -72,18 +90,13 @@ Route::group(['prefix' => '/mounts'], function () { Route::group(['prefix' => '/nests'], function () { Route::get('/', 'Nests\NestController@index')->name('api.application.nests'); Route::get('/{nest}', 'Nests\NestController@view')->name('api.application.nests.view'); + Route::get('/{nest}/eggs', 'Eggs\EggController@index'); Route::post('/', 'Nests\NestController@store'); Route::patch('/{nest}', 'Nests\NestController@update'); 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'); - }); }); /*