diff --git a/app/Console/Commands/User/DeleteUserCommand.php b/app/Console/Commands/User/DeleteUserCommand.php index 5f2a50e62..f458b51dc 100644 --- a/app/Console/Commands/User/DeleteUserCommand.php +++ b/app/Console/Commands/User/DeleteUserCommand.php @@ -10,6 +10,7 @@ namespace Pterodactyl\Console\Commands\User; use Webmozart\Assert\Assert; +use Pterodactyl\Models\User; use Illuminate\Console\Command; use Pterodactyl\Services\Users\UserDeletionService; use Pterodactyl\Contracts\Repository\UserRepositoryInterface; @@ -40,16 +41,11 @@ class DeleteUserCommand extends Command * DeleteUserCommand constructor. * * @param \Pterodactyl\Services\Users\UserDeletionService $deletionService - * @param \Pterodactyl\Contracts\Repository\UserRepositoryInterface $repository */ - public function __construct( - UserDeletionService $deletionService, - UserRepositoryInterface $repository - ) { + public function __construct(UserDeletionService $deletionService) { parent::__construct(); $this->deletionService = $deletionService; - $this->repository = $repository; } /** @@ -59,9 +55,13 @@ class DeleteUserCommand extends Command public function handle() { $search = $this->option('user') ?? $this->ask(trans('command/messages.user.search_users')); - Assert::notEmpty($search, 'Search term must be a non-null value, received %s.'); + Assert::notEmpty($search, 'Search term should be an email address, got: %s.'); + + $results = User::query() + ->where('email', 'LIKE', "$search%") + ->where('username', 'LIKE', "$search%") + ->get(); - $results = $this->repository->setSearchTerm($search)->all(); if (count($results) < 1) { $this->error(trans('command/messages.user.no_users_found')); if ($this->input->isInteractive()) { @@ -95,5 +95,7 @@ class DeleteUserCommand extends Command $this->deletionService->handle($deleteUser); $this->info(trans('command/messages.user.deleted')); } + + return; } } diff --git a/app/Contracts/Repository/NodeRepositoryInterface.php b/app/Contracts/Repository/NodeRepositoryInterface.php index b56314aa6..227989bab 100644 --- a/app/Contracts/Repository/NodeRepositoryInterface.php +++ b/app/Contracts/Repository/NodeRepositoryInterface.php @@ -29,13 +29,6 @@ interface NodeRepositoryInterface extends RepositoryInterface */ public function getUsageStatsRaw(Node $node): array; - /** - * Return all available nodes with a searchable interface. - * - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function getNodeListingData(): LengthAwarePaginator; - /** * Return a single node with location and server information. * diff --git a/app/Contracts/Repository/ServerRepositoryInterface.php b/app/Contracts/Repository/ServerRepositoryInterface.php index 602c33ca2..d70584b50 100644 --- a/app/Contracts/Repository/ServerRepositoryInterface.php +++ b/app/Contracts/Repository/ServerRepositoryInterface.php @@ -8,14 +8,6 @@ use Illuminate\Contracts\Pagination\LengthAwarePaginator; interface ServerRepositoryInterface extends RepositoryInterface { - /** - * Returns a listing of all servers that exist including relationships. - * - * @param int $paginate - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function getAllServers(int $paginate): LengthAwarePaginator; - /** * Load the egg relations onto the server model. * diff --git a/app/Contracts/Repository/UserRepositoryInterface.php b/app/Contracts/Repository/UserRepositoryInterface.php index 564b3beac..5f80be708 100644 --- a/app/Contracts/Repository/UserRepositoryInterface.php +++ b/app/Contracts/Repository/UserRepositoryInterface.php @@ -8,13 +8,6 @@ use Pterodactyl\Contracts\Repository\Attributes\SearchableInterface; interface UserRepositoryInterface extends RepositoryInterface { - /** - * Return all users with counts of servers and subusers of servers. - * - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function getAllUsersWithCounts(): LengthAwarePaginator; - /** * Return all matching models for a user in a format that can be used for dropdowns. * diff --git a/app/Http/Controllers/Admin/Nodes/NodeController.php b/app/Http/Controllers/Admin/Nodes/NodeController.php index 5e285a8c6..5a7bc1e30 100644 --- a/app/Http/Controllers/Admin/Nodes/NodeController.php +++ b/app/Http/Controllers/Admin/Nodes/NodeController.php @@ -3,6 +3,8 @@ namespace Pterodactyl\Http\Controllers\Admin\Nodes; use Illuminate\Http\Request; +use Pterodactyl\Models\Node; +use Spatie\QueryBuilder\QueryBuilder; use Illuminate\Contracts\View\Factory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Repositories\Eloquent\NodeRepository; @@ -39,10 +41,13 @@ class NodeController extends Controller */ public function index(Request $request) { - $nodes = $this->repository - ->setSearchTerm($request->input('query')) - ->getNodeListingData(); + $nodes = QueryBuilder::for( + Node::query()->with('location')->withCount('servers') + ) + ->allowedFilters(['uuid', 'name']) + ->allowedSorts(['id']) + ->paginate(25); - return $this->view->make('admin.nodes.index', compact('nodes')); + return $this->view->make('admin.nodes.index', ['nodes' => $nodes]); } } diff --git a/app/Http/Controllers/Admin/Servers/ServerController.php b/app/Http/Controllers/Admin/Servers/ServerController.php index 31bee2cdc..90d2321f6 100644 --- a/app/Http/Controllers/Admin/Servers/ServerController.php +++ b/app/Http/Controllers/Admin/Servers/ServerController.php @@ -3,6 +3,8 @@ namespace Pterodactyl\Http\Controllers\Admin\Servers; use Illuminate\Http\Request; +use Pterodactyl\Models\Server; +use Spatie\QueryBuilder\QueryBuilder; use Illuminate\Contracts\View\Factory; use Pterodactyl\Http\Controllers\Controller; use Pterodactyl\Repositories\Eloquent\ServerRepository; @@ -42,10 +44,11 @@ class ServerController extends Controller */ public function index(Request $request) { - return $this->view->make('admin.servers.index', [ - 'servers' => $this->repository->setSearchTerm($request->input('query'))->getAllServers( - config()->get('pterodactyl.paginate.admin.servers') - ), - ]); + $servers = QueryBuilder::for(Server::query()->with('node', 'user', 'allocation')) + ->allowedIncludes(['uuid', 'name', 'image']) + ->allowedSorts(['id', 'uuid']) + ->paginate(config()->get('pterodactyl.paginate.admin.servers')); + + return $this->view->make('admin.servers.index', ['servers' => $servers]); } } diff --git a/app/Http/Controllers/Admin/UserController.php b/app/Http/Controllers/Admin/UserController.php index 18e7e3b43..c712d7c02 100644 --- a/app/Http/Controllers/Admin/UserController.php +++ b/app/Http/Controllers/Admin/UserController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Admin; use Illuminate\Http\Request; use Pterodactyl\Models\User; use Prologue\Alerts\AlertsMessageBag; +use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Exceptions\DisplayException; use Pterodactyl\Http\Controllers\Controller; use Illuminate\Contracts\Translation\Translator; @@ -83,7 +84,10 @@ class UserController extends Controller */ public function index(Request $request) { - $users = $this->repository->setSearchTerm($request->input('query'))->getAllUsersWithCounts(); + $users = QueryBuilder::for(User::query()->withCount('servers')) + ->allowedIncludes(['username', 'email', 'uuid']) + ->allowedSorts(['id', 'uuid']) + ->paginate(50); return view('admin.users.index', ['users' => $users]); } diff --git a/app/Http/Controllers/Api/Application/Nodes/NodeController.php b/app/Http/Controllers/Api/Application/Nodes/NodeController.php index e6b4be6a2..7198611ba 100644 --- a/app/Http/Controllers/Api/Application/Nodes/NodeController.php +++ b/app/Http/Controllers/Api/Application/Nodes/NodeController.php @@ -3,8 +3,8 @@ 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; @@ -69,7 +69,10 @@ class NodeController extends ApplicationApiController */ public function index(GetNodesRequest $request): array { - $nodes = $this->repository->setSearchTerm($request->input('search'))->paginated(50); + $nodes = QueryBuilder::for(Node::query()) + ->allowedFilters(['uuid', 'name', 'fqdn', 'daemon_token_id']) + ->allowedSorts(['id', 'uuid', 'memory', 'disk']) + ->paginate(100); return $this->fractal->collection($nodes) ->transformWith($this->getTransformer(NodeTransformer::class)) @@ -80,11 +83,12 @@ class NodeController extends ApplicationApiController * Return data for a single instance of a node. * * @param \Pterodactyl\Http\Requests\Api\Application\Nodes\GetNodeRequest $request + * @param \Pterodactyl\Models\Node $node * @return array */ - public function view(GetNodeRequest $request): array + public function view(GetNodeRequest $request, Node $node): array { - return $this->fractal->item($request->getModel(Node::class)) + return $this->fractal->item($node) ->transformWith($this->getTransformer(NodeTransformer::class)) ->toArray(); } @@ -116,16 +120,15 @@ class NodeController extends ApplicationApiController * Update an existing node on the Panel. * * @param \Pterodactyl\Http\Requests\Api\Application\Nodes\UpdateNodeRequest $request + * @param \Pterodactyl\Models\Node $node * @return array * - * @throws \Pterodactyl\Exceptions\DisplayException - * @throws \Pterodactyl\Exceptions\Model\DataValidationException - * @throws \Pterodactyl\Exceptions\Repository\RecordNotFoundException + * @throws \Throwable */ - public function update(UpdateNodeRequest $request): array + public function update(UpdateNodeRequest $request, Node $node): array { $node = $this->updateService->handle( - $request->getModel(Node::class), $request->validated(), $request->input('reset_secret') === true + $node, $request->validated(), $request->input('reset_secret') === true ); return $this->fractal->item($node) @@ -138,14 +141,15 @@ class NodeController extends ApplicationApiController * currently attached to it. * * @param \Pterodactyl\Http\Requests\Api\Application\Nodes\DeleteNodeRequest $request - * @return \Illuminate\Http\Response + * @param \Pterodactyl\Models\Node $node + * @return \Illuminate\Http\JsonResponse * * @throws \Pterodactyl\Exceptions\Service\HasActiveServersException */ - public function delete(DeleteNodeRequest $request): Response + public function delete(DeleteNodeRequest $request, Node $node): JsonResponse { - $this->deletionService->handle($request->getModel(Node::class)); + $this->deletionService->handle($node); - return response('', 204); + return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); } } diff --git a/app/Http/Controllers/Api/Application/Servers/ServerController.php b/app/Http/Controllers/Api/Application/Servers/ServerController.php index 69f2706ac..126c91921 100644 --- a/app/Http/Controllers/Api/Application/Servers/ServerController.php +++ b/app/Http/Controllers/Api/Application/Servers/ServerController.php @@ -5,6 +5,7 @@ namespace Pterodactyl\Http\Controllers\Api\Application\Servers; use Illuminate\Http\Response; use Pterodactyl\Models\Server; use Illuminate\Http\JsonResponse; +use Spatie\QueryBuilder\QueryBuilder; use Pterodactyl\Services\Servers\ServerCreationService; use Pterodactyl\Services\Servers\ServerDeletionService; use Pterodactyl\Contracts\Repository\ServerRepositoryInterface; @@ -59,7 +60,10 @@ class ServerController extends ApplicationApiController */ public function index(GetServersRequest $request): array { - $servers = $this->repository->setSearchTerm($request->input('search'))->paginated(50); + $servers = QueryBuilder::for(Server::query()) + ->allowedFilters(['uuid', 'name', 'image', 'external_id']) + ->allowedSorts(['id', 'uuid']) + ->paginate(100); return $this->fractal->collection($servers) ->transformWith($this->getTransformer(ServerTransformer::class)) diff --git a/app/Repositories/Concerns/Searchable.php b/app/Repositories/Concerns/Searchable.php deleted file mode 100644 index 2ae0b82a7..000000000 --- a/app/Repositories/Concerns/Searchable.php +++ /dev/null @@ -1,32 +0,0 @@ -searchTerm = $term; - - return $clone; - } -} diff --git a/app/Repositories/Eloquent/NodeRepository.php b/app/Repositories/Eloquent/NodeRepository.php index 489782fb8..b7463001e 100644 --- a/app/Repositories/Eloquent/NodeRepository.php +++ b/app/Repositories/Eloquent/NodeRepository.php @@ -82,16 +82,6 @@ class NodeRepository extends EloquentRepository implements NodeRepositoryInterfa })->toArray(); } - /** - * Return all available nodes with a searchable interface. - * - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function getNodeListingData(): LengthAwarePaginator - { - return $this->getBuilder()->with('location')->withCount('servers')->paginate(25, $this->getColumns()); - } - /** * Return a single node with location and server information. * diff --git a/app/Repositories/Eloquent/ServerRepository.php b/app/Repositories/Eloquent/ServerRepository.php index d49a68994..5e884a661 100644 --- a/app/Repositories/Eloquent/ServerRepository.php +++ b/app/Repositories/Eloquent/ServerRepository.php @@ -22,17 +22,6 @@ class ServerRepository extends EloquentRepository implements ServerRepositoryInt return Server::class; } - /** - * Returns a listing of all servers that exist including relationships. - * - * @param int $paginate - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function getAllServers(int $paginate): LengthAwarePaginator - { - return $this->getBuilder()->with('node', 'user', 'allocation')->paginate($paginate, $this->getColumns()); - } - /** * Load the egg relations onto the server model. * diff --git a/app/Repositories/Eloquent/UserRepository.php b/app/Repositories/Eloquent/UserRepository.php index 693bbb2a0..af6ebd710 100644 --- a/app/Repositories/Eloquent/UserRepository.php +++ b/app/Repositories/Eloquent/UserRepository.php @@ -19,16 +19,6 @@ class UserRepository extends EloquentRepository implements UserRepositoryInterfa return User::class; } - /** - * Return all users with counts of servers and subusers of servers. - * - * @return \Illuminate\Contracts\Pagination\LengthAwarePaginator - */ - public function getAllUsersWithCounts(): LengthAwarePaginator - { - return $this->getBuilder()->withCount('servers')->paginate(50, $this->getColumns()); - } - /** * Return all matching models for a user in a format that can be used for dropdowns. * diff --git a/tests/Unit/Commands/User/DeleteUserCommandTest.php b/tests/Unit/Commands/User/DeleteUserCommandTest.php deleted file mode 100644 index 8263c2241..000000000 --- a/tests/Unit/Commands/User/DeleteUserCommandTest.php +++ /dev/null @@ -1,188 +0,0 @@ -deletionService = m::mock(UserDeletionService::class); - $this->repository = m::mock(UserRepositoryInterface::class); - - $this->command = new DeleteUserCommand($this->deletionService, $this->repository); - $this->command->setLaravel($this->app); - } - - /** - * Test that a user can be deleted using a normal pathway. - */ - public function testCommandWithNoOptions() - { - $users = collect([ - $user1 = factory(User::class)->make(), - $user2 = factory(User::class)->make(), - ]); - - $this->repository->shouldReceive('setSearchTerm')->with($user1->username)->once()->andReturnSelf() - ->shouldReceive('all')->withNoArgs()->once()->andReturn($users); - $this->deletionService->shouldReceive('handle')->with($user1->id)->once()->andReturnNull(); - - $display = $this->runCommand($this->command, [], [$user1->username, $user1->id, 'yes']); - - $this->assertNotEmpty($display); - $this->assertTableContains($user1->id, $display); - $this->assertTableContains($user1->email, $display); - $this->assertTableContains($user1->name, $display); - $this->assertStringContainsString(trans('command/messages.user.deleted'), $display); - } - - /** - * Test a bad first user search followed by a good second search. - */ - public function testCommandWithInvalidInitialSearch() - { - $users = collect([ - $user1 = factory(User::class)->make(), - ]); - - $this->repository->shouldReceive('setSearchTerm')->with('noResults')->once()->andReturnSelf() - ->shouldReceive('all')->withNoArgs()->once()->andReturn(collect()); - $this->repository->shouldReceive('setSearchTerm')->with($user1->username)->once()->andReturnSelf() - ->shouldReceive('all')->withNoArgs()->once()->andReturn($users); - $this->deletionService->shouldReceive('handle')->with($user1->id)->once()->andReturnNull(); - - $display = $this->runCommand($this->command, [], ['noResults', $user1->username, $user1->id, 'yes']); - - $this->assertNotEmpty($display); - $this->assertStringContainsString(trans('command/messages.user.no_users_found'), $display); - $this->assertTableContains($user1->id, $display); - $this->assertTableContains($user1->email, $display); - $this->assertTableContains($user1->name, $display); - $this->assertStringContainsString(trans('command/messages.user.deleted'), $display); - } - - /** - * Test the ability to re-do a search for a user account. - */ - public function testReSearchAbility() - { - $users = collect([ - $user1 = factory(User::class)->make(), - ]); - - $this->repository->shouldReceive('setSearchTerm')->with($user1->username)->twice()->andReturnSelf() - ->shouldReceive('all')->withNoArgs()->twice()->andReturn($users); - $this->deletionService->shouldReceive('handle')->with($user1->id)->once()->andReturnNull(); - - $display = $this->runCommand($this->command, [], [$user1->username, 0, $user1->username, $user1->id, 'yes']); - - $this->assertNotEmpty($display); - $this->assertStringContainsString(trans('command/messages.user.select_search_user'), $display); - $this->assertTableContains($user1->id, $display); - $this->assertTableContains($user1->email, $display); - $this->assertTableContains($user1->name, $display); - $this->assertStringContainsString(trans('command/messages.user.deleted'), $display); - } - - /** - * Test that answering no works as expected when confirming deletion of account. - */ - public function testAnsweringNoToDeletionConfirmationWillNotDeleteUser() - { - $users = collect([ - $user1 = factory(User::class)->make(), - ]); - - $this->repository->shouldReceive('setSearchTerm')->with($user1->username)->once()->andReturnSelf() - ->shouldReceive('all')->withNoArgs()->once()->andReturn($users); - $this->deletionService->shouldNotReceive('handle'); - - $display = $this->runCommand($this->command, [], [$user1->username, $user1->id, 'no']); - - $this->assertNotEmpty($display); - $this->assertStringNotContainsString(trans('command/messages.user.deleted'), $display); - } - - /** - * Test a single result is deleted if there is no interaction setup. - */ - public function testNoInteractionWithSingleResult() - { - $users = collect([ - $user1 = factory(User::class)->make(), - ]); - - $this->repository->shouldReceive('setSearchTerm')->with($user1->username)->once()->andReturnSelf() - ->shouldReceive('all')->withNoArgs()->once()->andReturn($users); - $this->deletionService->shouldReceive('handle')->with($user1)->once()->andReturnNull(); - - $display = $this->withoutInteraction()->runCommand($this->command, ['--user' => $user1->username]); - - $this->assertNotEmpty($display); - $this->assertStringContainsString(trans('command/messages.user.deleted'), $display); - } - - /** - * Test that an error is returned if there is no interaction but multiple results. - */ - public function testNoInteractionWithMultipleResults() - { - $users = collect([ - $user1 = factory(User::class)->make(), - $user2 = factory(User::class)->make(), - ]); - - $this->repository->shouldReceive('setSearchTerm')->with($user1->username)->once()->andReturnSelf() - ->shouldReceive('all')->withNoArgs()->once()->andReturn($users); - $this->deletionService->shouldNotReceive('handle'); - - $display = $this->withoutInteraction()->runCommand($this->command, ['--user' => $user1->username]); - - $this->assertNotEmpty($display); - $this->assertStringContainsString(trans('command/messages.user.multiple_found'), $display); - } - - /** - * Test that an error is returned if there is no interaction and no results returned. - */ - public function testNoInteractionWithNoResults() - { - $this->repository->shouldReceive('setSearchTerm')->with(123456)->once()->andReturnSelf() - ->shouldReceive('all')->withNoArgs()->once()->andReturn(collect()); - - $display = $this->withoutInteraction()->runCommand($this->command, ['--user' => 123456]); - - $this->assertNotEmpty($display); - $this->assertStringContainsString(trans('command/messages.user.no_users_found'), $display); - } -}