Remove node repository

This commit is contained in:
Lance Pioch 2024-03-16 21:34:09 -04:00
parent f988cc8cfa
commit 50fa260a38
15 changed files with 98 additions and 260 deletions

View File

@ -1,38 +0,0 @@
<?php
namespace App\Contracts\Repository;
use App\Models\Node;
use Illuminate\Support\Collection;
interface NodeRepositoryInterface extends RepositoryInterface
{
public const THRESHOLD_PERCENTAGE_LOW = 75;
public const THRESHOLD_PERCENTAGE_MEDIUM = 90;
/**
* Return the usage stats for a single node.
*/
public function getUsageStats(Node $node): array;
/**
* Return the usage stats for a single node.
*/
public function getUsageStatsRaw(Node $node): array;
/**
* Return a single node with location and server information.
*/
public function loadServerCount(Node $node, bool $refresh = false): Node;
/**
* Attach a paginated set of allocations to a node mode including
* any servers that are also attached to those allocations.
*/
public function loadNodeAllocations(Node $node, bool $refresh = false): Node;
/**
* Return a collection of nodes for all locations to use in server creation UI.
*/
public function getNodesForServerCreation(): Collection;
}

View File

@ -9,7 +9,6 @@ use Illuminate\Support\Collection;
use App\Models\Allocation; use App\Models\Allocation;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use Illuminate\Contracts\View\Factory as ViewFactory; use Illuminate\Contracts\View\Factory as ViewFactory;
use App\Repositories\Eloquent\NodeRepository;
use App\Traits\Controllers\JavascriptInjection; use App\Traits\Controllers\JavascriptInjection;
use App\Services\Helpers\SoftwareVersionService; use App\Services\Helpers\SoftwareVersionService;
@ -17,11 +16,13 @@ class NodeViewController extends Controller
{ {
use JavascriptInjection; use JavascriptInjection;
public const THRESHOLD_PERCENTAGE_LOW = 75;
public const THRESHOLD_PERCENTAGE_MEDIUM = 90;
/** /**
* NodeViewController constructor. * NodeViewController constructor.
*/ */
public function __construct( public function __construct(
private NodeRepository $repository,
private SoftwareVersionService $versionService, private SoftwareVersionService $versionService,
private ViewFactory $view private ViewFactory $view
) { ) {
@ -32,11 +33,37 @@ class NodeViewController extends Controller
*/ */
public function index(Request $request, Node $node): View public function index(Request $request, Node $node): View
{ {
$node = $this->repository->loadServerCount($node); $node->load('location')->loadCount('servers');
$stats = Node::query()
->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
->join('servers', 'servers.node_id', '=', 'nodes.id')
->where('node_id', '=', $node->id)
->first();
$usageStats = Collection::make(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])
->mapWithKeys(function ($value, $key) use ($node) {
$maxUsage = $node->{$key};
if ($node->{$key . '_overallocate'} > 0) {
$maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100));
}
$percent = ($value / $maxUsage) * 100;
return [
$key => [
'value' => number_format($value),
'max' => number_format($maxUsage),
'percent' => $percent,
'css' => ($percent <= self::THRESHOLD_PERCENTAGE_LOW) ? 'green' : (($percent > self::THRESHOLD_PERCENTAGE_MEDIUM) ? 'red' : 'yellow'),
],
];
})
->toArray();
return $this->view->make('admin.nodes.view.index', [ return $this->view->make('admin.nodes.view.index', [
'node' => $node, 'node' => $node,
'stats' => $this->repository->getUsageStats($node), 'stats' => $stats,
'version' => $this->versionService, 'version' => $this->versionService,
]); ]);
} }
@ -64,7 +91,15 @@ class NodeViewController extends Controller
*/ */
public function allocations(Request $request, Node $node): View public function allocations(Request $request, Node $node): View
{ {
$node = $this->repository->loadNodeAllocations($node); $node->setRelation(
'allocations',
$node->allocations()
->orderByRaw('server_id IS NOT NULL DESC, server_id IS NULL')
->orderByRaw('INET_ATON(ip) ASC')
->orderBy('port')
->with('server:id,name')
->paginate(50)
);
$this->plainInject(['node' => Collection::wrap($node)->only(['id'])]); $this->plainInject(['node' => Collection::wrap($node)->only(['id'])]);

View File

@ -18,7 +18,6 @@ use App\Services\Nodes\NodeDeletionService;
use App\Services\Allocations\AssignmentService; use App\Services\Allocations\AssignmentService;
use App\Services\Helpers\SoftwareVersionService; use App\Services\Helpers\SoftwareVersionService;
use App\Http\Requests\Admin\Node\NodeFormRequest; use App\Http\Requests\Admin\Node\NodeFormRequest;
use App\Contracts\Repository\NodeRepositoryInterface;
use App\Http\Requests\Admin\Node\AllocationFormRequest; use App\Http\Requests\Admin\Node\AllocationFormRequest;
use App\Services\Allocations\AllocationDeletionService; use App\Services\Allocations\AllocationDeletionService;
use App\Http\Requests\Admin\Node\AllocationAliasFormRequest; use App\Http\Requests\Admin\Node\AllocationAliasFormRequest;
@ -35,7 +34,6 @@ class NodesController extends Controller
protected CacheRepository $cache, protected CacheRepository $cache,
protected NodeCreationService $creationService, protected NodeCreationService $creationService,
protected NodeDeletionService $deletionService, protected NodeDeletionService $deletionService,
protected NodeRepositoryInterface $repository,
protected NodeUpdateService $updateService, protected NodeUpdateService $updateService,
protected SoftwareVersionService $versionService, protected SoftwareVersionService $versionService,
protected ViewFactory $view protected ViewFactory $view

View File

@ -9,7 +9,6 @@ use Illuminate\Http\RedirectResponse;
use Prologue\Alerts\AlertsMessageBag; use Prologue\Alerts\AlertsMessageBag;
use Illuminate\View\Factory as ViewFactory; use Illuminate\View\Factory as ViewFactory;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Repositories\Eloquent\NodeRepository;
use App\Http\Requests\Admin\ServerFormRequest; use App\Http\Requests\Admin\ServerFormRequest;
use App\Services\Servers\ServerCreationService; use App\Services\Servers\ServerCreationService;
@ -20,7 +19,6 @@ class CreateServerController extends Controller
*/ */
public function __construct( public function __construct(
private AlertsMessageBag $alert, private AlertsMessageBag $alert,
private NodeRepository $nodeRepository,
private ServerCreationService $creationService, private ServerCreationService $creationService,
private ViewFactory $view private ViewFactory $view
) { ) {
@ -43,7 +41,7 @@ class CreateServerController extends Controller
$eggs = Egg::with('variables')->get(); $eggs = Egg::with('variables')->get();
\JavaScript::put([ \JavaScript::put([
'nodeData' => $this->nodeRepository->getNodesForServerCreation(), 'nodeData' => Node::getForServerCreation(),
'eggs' => $eggs->keyBy('id'), 'eggs' => $eggs->keyBy('id'),
]); ]);

View File

@ -17,7 +17,6 @@ use App\Models\ServerTransfer;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Services\Nodes\NodeJWTService; use App\Services\Nodes\NodeJWTService;
use App\Repositories\Eloquent\NodeRepository;
class ServerTransferController extends Controller class ServerTransferController extends Controller
{ {
@ -28,7 +27,6 @@ class ServerTransferController extends Controller
private AlertsMessageBag $alert, private AlertsMessageBag $alert,
private ConnectionInterface $connection, private ConnectionInterface $connection,
private NodeJWTService $nodeJWTService, private NodeJWTService $nodeJWTService,
private NodeRepository $nodeRepository
) { ) {
} }
@ -69,7 +67,13 @@ class ServerTransferController extends Controller
$additional_allocations = array_map('intval', $validatedData['allocation_additional'] ?? []); $additional_allocations = array_map('intval', $validatedData['allocation_additional'] ?? []);
// Check if the node is viable for the transfer. // Check if the node is viable for the transfer.
$node = $this->nodeRepository->getNodeWithResourceUsage($node_id); $node = Node::query()
->select(['nodes.id', 'nodes.fqdn', 'nodes.scheme', 'nodes.daemon_token', 'nodes.daemonListen', 'nodes.memory', 'nodes.disk', 'nodes.memory_overallocate', 'nodes.disk_overallocate'])
->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
->leftJoin('servers', 'servers.node_id', '=', 'nodes.id')
->where('nodes.id', $node_id)
->first();
if (!$node->isViable($server->memory, $server->disk)) { if (!$node->isViable($server->memory, $server->disk)) {
$this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash(); $this->alert->danger(trans('admin/server.alerts.transfer_not_viable'))->flash();

View File

@ -11,7 +11,6 @@ use App\Exceptions\DisplayException;
use App\Http\Controllers\Controller; use App\Http\Controllers\Controller;
use App\Services\Servers\EnvironmentService; use App\Services\Servers\EnvironmentService;
use Illuminate\Contracts\View\Factory as ViewFactory; use Illuminate\Contracts\View\Factory as ViewFactory;
use App\Repositories\Eloquent\NodeRepository;
use App\Repositories\Eloquent\MountRepository; use App\Repositories\Eloquent\MountRepository;
use App\Traits\Controllers\JavascriptInjection; use App\Traits\Controllers\JavascriptInjection;
use App\Repositories\Eloquent\DatabaseHostRepository; use App\Repositories\Eloquent\DatabaseHostRepository;
@ -26,7 +25,6 @@ class ServerViewController extends Controller
public function __construct( public function __construct(
private DatabaseHostRepository $databaseHostRepository, private DatabaseHostRepository $databaseHostRepository,
private MountRepository $mountRepository, private MountRepository $mountRepository,
private NodeRepository $nodeRepository,
private EnvironmentService $environmentService, private EnvironmentService $environmentService,
private ViewFactory $view private ViewFactory $view
) { ) {
@ -118,14 +116,14 @@ class ServerViewController extends Controller
} }
// Check if the panel doesn't have at least 2 nodes configured. // Check if the panel doesn't have at least 2 nodes configured.
$nodes = $this->nodeRepository->all(); $nodeCount = Node::query()->count();
$canTransfer = false; $canTransfer = false;
if (count($nodes) >= 2) { if ($nodeCount >= 2) {
$canTransfer = true; $canTransfer = true;
} }
\JavaScript::put([ \JavaScript::put([
'nodeData' => $this->nodeRepository->getNodesForServerCreation(), 'nodeData' => Node::getForServerCreation(),
]); ]);
return $this->view->make('admin.servers.view.manage', [ return $this->view->make('admin.servers.view.manage', [

View File

@ -2,11 +2,10 @@
namespace App\Http\Middleware\Api\Daemon; namespace App\Http\Middleware\Api\Daemon;
use App\Models\Node;
use Illuminate\Http\Request; use Illuminate\Http\Request;
use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Contracts\Encryption\Encrypter;
use App\Repositories\Eloquent\NodeRepository;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
use App\Exceptions\Repository\RecordNotFoundException;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException; use Symfony\Component\HttpKernel\Exception\AccessDeniedHttpException;
@ -22,7 +21,7 @@ class DaemonAuthenticate
/** /**
* DaemonAuthenticate constructor. * DaemonAuthenticate constructor.
*/ */
public function __construct(private Encrypter $encrypter, private NodeRepository $repository) public function __construct(private Encrypter $encrypter)
{ {
} }
@ -42,24 +41,18 @@ class DaemonAuthenticate
} }
$parts = explode('.', $bearer); $parts = explode('.', $bearer);
// Ensure that all of the correct parts are provided in the header. // Ensure that all the correct parts are provided in the header.
if (count($parts) !== 2 || empty($parts[0]) || empty($parts[1])) { if (count($parts) !== 2 || empty($parts[0]) || empty($parts[1])) {
throw new BadRequestHttpException('The Authorization header provided was not in a valid format.'); throw new BadRequestHttpException('The Authorization header provided was not in a valid format.');
} }
try { /** @var Node $node */
/** @var \App\Models\Node $node */ $node = Node::query()->where('daemon_token_id', $parts[0])->firstOrFail();
$node = $this->repository->findFirstWhere([
'daemon_token_id' => $parts[0],
]);
if (hash_equals((string) $this->encrypter->decrypt($node->daemon_token), $parts[1])) { if (hash_equals((string) $this->encrypter->decrypt($node->daemon_token), $parts[1])) {
$request->attributes->set('node', $node); $request->attributes->set('node', $node);
return $next($request); return $next($request);
}
} catch (RecordNotFoundException $exception) {
// Do nothing, we don't want to expose a node not existing at all.
} }
throw new AccessDeniedHttpException('You are not authorized to access this resource.'); throw new AccessDeniedHttpException('You are not authorized to access this resource.');

View File

@ -221,4 +221,26 @@ class Node extends Model
return ($this->sum_memory + $memory) <= $memoryLimit && ($this->sum_disk + $disk) <= $diskLimit; return ($this->sum_memory + $memory) <= $memoryLimit && ($this->sum_disk + $disk) <= $diskLimit;
} }
public static function getForServerCreation()
{
return self::with('allocations')->get()->map(function (Node $item) {
$filtered = $item->getRelation('allocations')->where('server_id', null)->map(function ($map) {
return collect($map)->only(['id', 'ip', 'port']);
});
$item->ports = $filtered->map(function ($map) {
return [
'id' => $map['id'],
'text' => sprintf('%s:%s', $map['ip'], $map['port']),
];
})->values();
return [
'id' => $item->id,
'text' => $item->name,
'allocations' => $item->ports,
];
})->values();
}
} }

View File

@ -3,10 +3,8 @@
namespace App\Providers; namespace App\Providers;
use Illuminate\Support\ServiceProvider; use Illuminate\Support\ServiceProvider;
use App\Repositories\Eloquent\NodeRepository;
use App\Repositories\Eloquent\SubuserRepository; use App\Repositories\Eloquent\SubuserRepository;
use App\Repositories\Eloquent\EggVariableRepository; use App\Repositories\Eloquent\EggVariableRepository;
use App\Contracts\Repository\NodeRepositoryInterface;
use App\Repositories\Eloquent\DatabaseHostRepository; use App\Repositories\Eloquent\DatabaseHostRepository;
use App\Contracts\Repository\SubuserRepositoryInterface; use App\Contracts\Repository\SubuserRepositoryInterface;
use App\Contracts\Repository\EggVariableRepositoryInterface; use App\Contracts\Repository\EggVariableRepositoryInterface;
@ -22,7 +20,6 @@ class RepositoryServiceProvider extends ServiceProvider
// Eloquent Repositories // Eloquent Repositories
$this->app->bind(DatabaseHostRepositoryInterface::class, DatabaseHostRepository::class); $this->app->bind(DatabaseHostRepositoryInterface::class, DatabaseHostRepository::class);
$this->app->bind(EggVariableRepositoryInterface::class, EggVariableRepository::class); $this->app->bind(EggVariableRepositoryInterface::class, EggVariableRepository::class);
$this->app->bind(NodeRepositoryInterface::class, NodeRepository::class);
$this->app->bind(SubuserRepositoryInterface::class, SubuserRepository::class); $this->app->bind(SubuserRepositoryInterface::class, SubuserRepository::class);
} }
} }

View File

@ -1,148 +0,0 @@
<?php
namespace App\Repositories\Eloquent;
use App\Models\Node;
use Illuminate\Support\Collection;
use App\Contracts\Repository\NodeRepositoryInterface;
class NodeRepository extends EloquentRepository implements NodeRepositoryInterface
{
/**
* Return the model backing this repository.
*/
public function model(): string
{
return Node::class;
}
/**
* Return the usage stats for a single node.
*/
public function getUsageStats(Node $node): array
{
$stats = $this->getBuilder()
->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
->join('servers', 'servers.node_id', '=', 'nodes.id')
->where('node_id', '=', $node->id)
->first();
return Collection::make(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])
->mapWithKeys(function ($value, $key) use ($node) {
$maxUsage = $node->{$key};
if ($node->{$key . '_overallocate'} > 0) {
$maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100));
}
$percent = ($value / $maxUsage) * 100;
return [
$key => [
'value' => number_format($value),
'max' => number_format($maxUsage),
'percent' => $percent,
'css' => ($percent <= self::THRESHOLD_PERCENTAGE_LOW) ? 'green' : (($percent > self::THRESHOLD_PERCENTAGE_MEDIUM) ? 'red' : 'yellow'),
],
];
})
->toArray();
}
/**
* Return the usage stats for a single node.
*/
public function getUsageStatsRaw(Node $node): array
{
$stats = $this->getBuilder()->select(
$this->getBuilder()->raw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
)->join('servers', 'servers.node_id', '=', 'nodes.id')->where('node_id', $node->id)->first();
return collect(['disk' => $stats->sum_disk, 'memory' => $stats->sum_memory])->mapWithKeys(function ($value, $key) use ($node) {
$maxUsage = $node->{$key};
if ($node->{$key . '_overallocate'} > 0) {
$maxUsage = $node->{$key} * (1 + ($node->{$key . '_overallocate'} / 100));
}
return [
$key => [
'value' => $value,
'max' => $maxUsage,
],
];
})->toArray();
}
/**
* Return a single node with server information.
*/
public function loadServerCount(Node $node, bool $refresh = false): Node
{
// This is quite ugly and can probably be improved down the road.
// And by probably, I mean it should.
if (is_null($node->servers_count) || $refresh) {
$node->load('servers');
$node->setRelation('servers_count', count($node->getRelation('servers')));
unset($node->servers);
}
return $node;
}
/**
* Attach a paginated set of allocations to a node mode including
* any servers that are also attached to those allocations.
*/
public function loadNodeAllocations(Node $node, bool $refresh = false): Node
{
$node->setRelation(
'allocations',
$node->allocations()
->orderByRaw('server_id IS NOT NULL DESC, server_id IS NULL')
->orderByRaw('INET_ATON(ip) ASC')
->orderBy('port')
->with('server:id,name')
->paginate(50)
);
return $node;
}
/**
* Return a collection of nodes for all locations to use in server creation UI.
*/
public function getNodesForServerCreation(): Collection
{
return $this->getBuilder()->with('allocations')->get()->map(function (Node $item) {
$filtered = $item->getRelation('allocations')->where('server_id', null)->map(function ($map) {
return collect($map)->only(['id', 'ip', 'port']);
});
$item->ports = $filtered->map(function ($map) {
return [
'id' => $map['id'],
'text' => sprintf('%s:%s', $map['ip'], $map['port']),
];
})->values();
return [
'id' => $item->id,
'text' => $item->name,
'allocations' => $item->ports,
];
})->values();
}
/**
* Returns a node with the given id with the Node's resource usage.
*/
public function getNodeWithResourceUsage(int $node_id): Node
{
$instance = $this->getBuilder()
->select(['nodes.id', 'nodes.fqdn', 'nodes.scheme', 'nodes.daemon_token', 'nodes.daemonListen', 'nodes.memory', 'nodes.disk', 'nodes.memory_overallocate', 'nodes.disk_overallocate'])
->selectRaw('IFNULL(SUM(servers.memory), 0) as sum_memory, IFNULL(SUM(servers.disk), 0) as sum_disk')
->leftJoin('servers', 'servers.node_id', '=', 'nodes.id')
->where('nodes.id', $node_id);
return $instance->first();
}
}

View File

@ -9,7 +9,6 @@ use App\Helpers\Utilities;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Contracts\Encryption\Encrypter;
use App\Extensions\DynamicDatabaseConnection; use App\Extensions\DynamicDatabaseConnection;
use App\Repositories\Eloquent\DatabaseRepository;
use App\Exceptions\Repository\DuplicateDatabaseNameException; use App\Exceptions\Repository\DuplicateDatabaseNameException;
use App\Exceptions\Service\Database\TooManyDatabasesException; use App\Exceptions\Service\Database\TooManyDatabasesException;
use App\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException; use App\Exceptions\Service\Database\DatabaseClientFeatureNotEnabledException;

View File

@ -6,17 +6,9 @@ use Ramsey\Uuid\Uuid;
use Illuminate\Support\Str; use Illuminate\Support\Str;
use App\Models\Node; use App\Models\Node;
use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Contracts\Encryption\Encrypter;
use App\Contracts\Repository\NodeRepositoryInterface;
class NodeCreationService class NodeCreationService
{ {
/**
* NodeCreationService constructor.
*/
public function __construct(protected NodeRepositoryInterface $repository)
{
}
/** /**
* Create a new node on the panel. * Create a new node on the panel.
* *
@ -28,6 +20,6 @@ class NodeCreationService
$data['daemon_token'] = app(Encrypter::class)->encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH)); $data['daemon_token'] = app(Encrypter::class)->encrypt(Str::random(Node::DAEMON_TOKEN_LENGTH));
$data['daemon_token_id'] = Str::random(Node::DAEMON_TOKEN_ID_LENGTH); $data['daemon_token_id'] = Str::random(Node::DAEMON_TOKEN_ID_LENGTH);
return $this->repository->create($data, true, true); return Node::query()->create($data);
} }
} }

View File

@ -4,7 +4,6 @@ namespace App\Services\Nodes;
use App\Models\Node; use App\Models\Node;
use Illuminate\Contracts\Translation\Translator; use Illuminate\Contracts\Translation\Translator;
use App\Contracts\Repository\NodeRepositoryInterface;
use App\Exceptions\Service\HasActiveServersException; use App\Exceptions\Service\HasActiveServersException;
class NodeDeletionService class NodeDeletionService
@ -13,7 +12,6 @@ class NodeDeletionService
* NodeDeletionService constructor. * NodeDeletionService constructor.
*/ */
public function __construct( public function __construct(
protected NodeRepositoryInterface $repository,
protected Translator $translator protected Translator $translator
) { ) {
} }
@ -33,6 +31,6 @@ class NodeDeletionService
throw new HasActiveServersException($this->translator->get('exceptions.node.servers_attached')); throw new HasActiveServersException($this->translator->get('exceptions.node.servers_attached'));
} }
return $this->repository->delete($node); return $node->delete();
} }
} }

View File

@ -7,7 +7,6 @@ use App\Models\Node;
use Illuminate\Support\Facades\Log; use Illuminate\Support\Facades\Log;
use Illuminate\Database\ConnectionInterface; use Illuminate\Database\ConnectionInterface;
use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Contracts\Encryption\Encrypter;
use App\Repositories\Eloquent\NodeRepository;
use App\Repositories\Daemon\DaemonConfigurationRepository; use App\Repositories\Daemon\DaemonConfigurationRepository;
use App\Exceptions\Http\Connection\DaemonConnectionException; use App\Exceptions\Http\Connection\DaemonConnectionException;
use App\Exceptions\Service\Node\ConfigurationNotPersistedException; use App\Exceptions\Service\Node\ConfigurationNotPersistedException;
@ -21,7 +20,6 @@ class NodeUpdateService
private ConnectionInterface $connection, private ConnectionInterface $connection,
private DaemonConfigurationRepository $configurationRepository, private DaemonConfigurationRepository $configurationRepository,
private Encrypter $encrypter, private Encrypter $encrypter,
private NodeRepository $repository
) { ) {
} }
@ -39,7 +37,7 @@ class NodeUpdateService
[$updated, $exception] = $this->connection->transaction(function () use ($data, $node) { [$updated, $exception] = $this->connection->transaction(function () use ($data, $node) {
/** @var \App\Models\Node $updated */ /** @var \App\Models\Node $updated */
$updated = $this->repository->withFreshModel()->update($node->id, $data, true, true); $updated = $node->replicate()->forceFill($data)->save();
try { try {
// If we're changing the FQDN for the node, use the newly provided FQDN for the connection // If we're changing the FQDN for the node, use the newly provided FQDN for the connection

View File

@ -2,11 +2,11 @@
namespace App\Tests\Unit\Http\Middleware\Api\Daemon; namespace App\Tests\Unit\Http\Middleware\Api\Daemon;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Mockery as m; use Mockery as m;
use Mockery\MockInterface; use Mockery\MockInterface;
use App\Models\Node; use App\Models\Node;
use Illuminate\Contracts\Encryption\Encrypter; use Illuminate\Contracts\Encryption\Encrypter;
use App\Repositories\Eloquent\NodeRepository;
use Symfony\Component\HttpKernel\Exception\HttpException; use Symfony\Component\HttpKernel\Exception\HttpException;
use App\Exceptions\Repository\RecordNotFoundException; use App\Exceptions\Repository\RecordNotFoundException;
use App\Http\Middleware\Api\Daemon\DaemonAuthenticate; use App\Http\Middleware\Api\Daemon\DaemonAuthenticate;
@ -18,8 +18,6 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
{ {
private MockInterface $encrypter; private MockInterface $encrypter;
private MockInterface $repository;
/** /**
* Setup tests. * Setup tests.
*/ */
@ -28,11 +26,10 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
parent::setUp(); parent::setUp();
$this->encrypter = m::mock(Encrypter::class); $this->encrypter = m::mock(Encrypter::class);
$this->repository = m::mock(NodeRepository::class);
} }
/** /**
* Test that if we are accessing the daemon.configuration route this middleware is not * Test that if we are accessing the daemon configuration route this middleware is not
* applied in order to allow an unauthenticated request to use a token to grab data. * applied in order to allow an unauthenticated request to use a token to grab data.
*/ */
public function testResponseShouldContinueIfRouteIsExempted() public function testResponseShouldContinueIfRouteIsExempted()
@ -83,16 +80,14 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
*/ */
public function testResponseShouldFailIfTokenIsNotValid() public function testResponseShouldFailIfTokenIsNotValid()
{ {
$node = Node::factory()->create();
$this->expectException(AccessDeniedHttpException::class); $this->expectException(AccessDeniedHttpException::class);
/** @var \App\Models\Node $model */
$model = Node::factory()->make();
$this->request->expects('route->getName')->withNoArgs()->andReturn('random.route'); $this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
$this->request->expects('bearerToken')->withNoArgs()->andReturn($model->daemon_token_id . '.random_string_123'); $this->request->expects('bearerToken')->withNoArgs()->andReturn($node->daemon_token_id . '.random_string_123');
$this->repository->expects('findFirstWhere')->with(['daemon_token_id' => $model->daemon_token_id])->andReturn($model); $this->encrypter->expects('decrypt')->with($node->daemon_token)->andReturns(decrypt($node->daemon_token));
$this->encrypter->expects('decrypt')->with($model->daemon_token)->andReturns(decrypt($model->daemon_token));
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
} }
@ -103,13 +98,11 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
*/ */
public function testResponseShouldFailIfNodeIsNotFound() public function testResponseShouldFailIfNodeIsNotFound()
{ {
$this->expectException(AccessDeniedHttpException::class); $this->expectException(ModelNotFoundException::class);
$this->request->expects('route->getName')->withNoArgs()->andReturn('random.route'); $this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
$this->request->expects('bearerToken')->withNoArgs()->andReturn('abcd1234.random_string_123'); $this->request->expects('bearerToken')->withNoArgs()->andReturn('abcd1234.random_string_123');
$this->repository->expects('findFirstWhere')->with(['daemon_token_id' => 'abcd1234'])->andThrow(RecordNotFoundException::class);
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
} }
@ -118,18 +111,17 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
*/ */
public function testSuccessfulMiddlewareProcess() public function testSuccessfulMiddlewareProcess()
{ {
/** @var \App\Models\Node $model */ $node = Node::factory()->create();
$model = Node::factory()->make(); $node->daemon_token = encrypt('the_same');
$node->save();
$this->request->expects('route->getName')->withNoArgs()->andReturn('random.route'); $this->request->expects('route->getName')->withNoArgs()->andReturn('random.route');
$this->request->expects('bearerToken')->withNoArgs()->andReturn($model->daemon_token_id . '.' . decrypt($model->daemon_token)); $this->request->expects('bearerToken')->withNoArgs()->andReturn($node->daemon_token_id . '.the_same');
$this->encrypter->expects('decrypt')->with($node->daemon_token)->andReturns(decrypt($node->daemon_token));
$this->repository->expects('findFirstWhere')->with(['daemon_token_id' => $model->daemon_token_id])->andReturn($model);
$this->encrypter->expects('decrypt')->with($model->daemon_token)->andReturns(decrypt($model->daemon_token));
$this->getMiddleware()->handle($this->request, $this->getClosureAssertions()); $this->getMiddleware()->handle($this->request, $this->getClosureAssertions());
$this->assertRequestHasAttribute('node'); $this->assertRequestHasAttribute('node');
$this->assertRequestAttributeEquals($model, 'node'); $this->assertRequestAttributeEquals($node->fresh(), 'node');
} }
/** /**
@ -156,6 +148,6 @@ class DaemonAuthenticateTest extends MiddlewareTestCase
*/ */
private function getMiddleware(): DaemonAuthenticate private function getMiddleware(): DaemonAuthenticate
{ {
return new DaemonAuthenticate($this->encrypter, $this->repository); return new DaemonAuthenticate($this->encrypter);
} }
} }