db_hosts: add ability to link more than one node

This commit is contained in:
Matthew Penner 2021-01-15 09:20:15 -07:00
parent 79d80e8c22
commit e123367f40
11 changed files with 226 additions and 35 deletions

View file

@ -12,6 +12,7 @@ use Pterodactyl\Transformers\Api\Application\DatabaseHostTransformer;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
use Pterodactyl\Http\Requests\Api\Application\Databases\GetDatabaseRequest; use Pterodactyl\Http\Requests\Api\Application\Databases\GetDatabaseRequest;
use Pterodactyl\Http\Requests\Api\Application\Databases\GetDatabasesRequest; use Pterodactyl\Http\Requests\Api\Application\Databases\GetDatabasesRequest;
use Pterodactyl\Http\Requests\Api\Application\Databases\DatabaseNodesRequest;
use Pterodactyl\Http\Requests\Api\Application\Databases\StoreDatabaseRequest; use Pterodactyl\Http\Requests\Api\Application\Databases\StoreDatabaseRequest;
use Pterodactyl\Http\Requests\Api\Application\Databases\UpdateDatabaseRequest; use Pterodactyl\Http\Requests\Api\Application\Databases\UpdateDatabaseRequest;
use Pterodactyl\Http\Requests\Api\Application\Databases\DeleteDatabaseRequest; use Pterodactyl\Http\Requests\Api\Application\Databases\DeleteDatabaseRequest;
@ -135,4 +136,48 @@ class DatabaseController extends ApplicationApiController
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT); return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
} }
/**
* ?
*
* @param \Pterodactyl\Http\Requests\Api\Application\Databases\DatabaseNodesRequest $request
* @param \Pterodactyl\Models\Mount $mount
*
* @return array
*/
public function addNodes(DatabaseNodesRequest $request, DatabaseHost $databaseHost): array
{
$data = $request->validated();
$nodes = $data['nodes'] ?? [];
if (count($nodes) > 0) {
$databaseHost->nodes()->syncWithoutDetaching($nodes);
}
return $this->fractal->item($databaseHost)
->transformWith($this->getTransformer(DatabaseHostTransformer::class))
->toArray();
}
/**
* ?
*
* @param \Pterodactyl\Http\Requests\Api\Application\Databases\DatabaseNodesRequest $request
* @param \Pterodactyl\Models\Mount $mount
*
* @return array
*/
public function deleteNodes(DatabaseNodesRequest $request, DatabaseHost $databaseHost): array
{
$data = $request->validated();
$nodes = $data['nodes'] ?? [];
if (count($nodes) > 0) {
$databaseHost->nodes()->detach($nodes);
}
return $this->fractal->item($databaseHost)
->transformWith($this->getTransformer(DatabaseHostTransformer::class))
->toArray();
}
} }

View file

@ -9,11 +9,11 @@ use Pterodactyl\Transformers\Api\Application\MountTransformer;
use Symfony\Component\HttpKernel\Exception\BadRequestHttpException; use Symfony\Component\HttpKernel\Exception\BadRequestHttpException;
use Pterodactyl\Http\Requests\Api\Application\Mounts\GetMountRequest; use Pterodactyl\Http\Requests\Api\Application\Mounts\GetMountRequest;
use Pterodactyl\Http\Requests\Api\Application\Mounts\GetMountsRequest; use Pterodactyl\Http\Requests\Api\Application\Mounts\GetMountsRequest;
use Pterodactyl\Http\Requests\Api\Application\Mounts\MountEggsRequest;
use Pterodactyl\Http\Requests\Api\Application\Mounts\MountNodesRequest;
use Pterodactyl\Http\Requests\Api\Application\Mounts\StoreMountRequest; use Pterodactyl\Http\Requests\Api\Application\Mounts\StoreMountRequest;
use Pterodactyl\Http\Requests\Api\Application\Mounts\UpdateMountRequest; use Pterodactyl\Http\Requests\Api\Application\Mounts\UpdateMountRequest;
use Pterodactyl\Http\Requests\Api\Application\Mounts\DeleteMountRequest; use Pterodactyl\Http\Requests\Api\Application\Mounts\DeleteMountRequest;
use Pterodactyl\Http\Requests\Api\Application\Mounts\MountAddEggsRequest;
use Pterodactyl\Http\Requests\Api\Application\Mounts\MountAddNodesRequest;
use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController; use Pterodactyl\Http\Controllers\Api\Application\ApplicationApiController;
class MountController extends ApplicationApiController class MountController extends ApplicationApiController
@ -123,12 +123,12 @@ class MountController extends ApplicationApiController
/** /**
* ? * ?
* *
* @param \Pterodactyl\Http\Requests\Api\Application\Mounts\MountAddEggsRequest $request * @param \Pterodactyl\Http\Requests\Api\Application\Mounts\MountEggsRequest $request
* @param \Pterodactyl\Models\Mount $mount * @param \Pterodactyl\Models\Mount $mount
* *
* @return array * @return array
*/ */
public function addEggs(MountAddEggsRequest $request, Mount $mount): array public function addEggs(MountEggsRequest $request, Mount $mount): array
{ {
$data = $request->validated(); $data = $request->validated();
@ -145,12 +145,12 @@ class MountController extends ApplicationApiController
/** /**
* ? * ?
* *
* @param \Pterodactyl\Http\Requests\Api\Application\Mounts\MountAddNodesRequest $request * @param \Pterodactyl\Http\Requests\Api\Application\Mounts\MountNodesRequest $request
* @param \Pterodactyl\Models\Mount $mount * @param \Pterodactyl\Models\Mount $mount
* *
* @return array * @return array
*/ */
public function addNodes(MountAddNodesRequest $request, Mount $mount): array public function addNodes(MountNodesRequest $request, Mount $mount): array
{ {
$data = $request->validated(); $data = $request->validated();
@ -167,12 +167,12 @@ class MountController extends ApplicationApiController
/** /**
* ? * ?
* *
* @param \Pterodactyl\Http\Requests\Api\Application\Mounts\MountAddEggsRequest $request * @param \Pterodactyl\Http\Requests\Api\Application\Mounts\MountEggsRequest $request
* @param \Pterodactyl\Models\Mount $mount * @param \Pterodactyl\Models\Mount $mount
* *
* @return array * @return array
*/ */
public function deleteEggs(MountAddEggsRequest $request, Mount $mount): array public function deleteEggs(MountEggsRequest $request, Mount $mount): array
{ {
$data = $request->validated(); $data = $request->validated();
@ -189,12 +189,12 @@ class MountController extends ApplicationApiController
/** /**
* ? * ?
* *
* @param \Pterodactyl\Http\Requests\Api\Application\Mounts\MountAddNodesRequest $request * @param \Pterodactyl\Http\Requests\Api\Application\Mounts\MountNodesRequest $request
* @param \Pterodactyl\Models\Mount $mount * @param \Pterodactyl\Models\Mount $mount
* *
* @return array * @return array
*/ */
public function deleteNodes(MountAddNodesRequest $request, Mount $mount): array public function deleteNodes(MountNodesRequest $request, Mount $mount): array
{ {
$data = $request->validated(); $data = $request->validated();

View file

@ -0,0 +1,31 @@
<?php
namespace Pterodactyl\Http\Requests\Api\Application\Databases;
use Pterodactyl\Services\Acl\Api\AdminAcl;
use Pterodactyl\Http\Requests\Api\Application\ApplicationApiRequest;
class DatabaseNodesRequest extends ApplicationApiRequest
{
/**
* @var string
*/
protected $resource = AdminAcl::RESOURCE_DATABASE_HOSTS;
/**
* @var int
*/
protected $permission = AdminAcl::WRITE;
/**
* ?
*
* @param array|null $rules
*
* @return array
*/
public function rules(array $rules = null): array
{
return $rules ?? ['nodes' => 'required|exists:nodes,id'];
}
}

View file

@ -5,7 +5,7 @@ namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
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 MountAddEggsRequest extends ApplicationApiRequest class MountEggsRequest extends ApplicationApiRequest
{ {
/** /**
* @var string * @var string

View file

@ -5,7 +5,7 @@ namespace Pterodactyl\Http\Requests\Api\Application\Mounts;
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 MountAddNodesRequest extends ApplicationApiRequest class MountNodesRequest extends ApplicationApiRequest
{ {
/** /**
* @var string * @var string

View file

@ -8,12 +8,11 @@ namespace Pterodactyl\Models;
* @property string $host * @property string $host
* @property int $port * @property int $port
* @property string $username * @property string $username
* @property int $node_id
* @property \Carbon\Carbon|null $created_at * @property \Carbon\Carbon|null $created_at
* @property \Carbon\Carbon|null $updated_at * @property \Carbon\Carbon|null $updated_at
* *
* @property \Pterodactyl\Models\Node|null $node
* @property \Pterodactyl\Models\Database[]|\Illuminate\Database\Eloquent\Collection $databases * @property \Pterodactyl\Models\Database[]|\Illuminate\Database\Eloquent\Collection $databases
* @property \Pterodactyl\Models\Node[]|\Illuminate\Database\Eloquent\Collection $nodes
*/ */
class DatabaseHost extends Model class DatabaseHost extends Model
{ {
@ -43,7 +42,7 @@ class DatabaseHost extends Model
* @var array * @var array
*/ */
protected $fillable = [ protected $fillable = [
'name', 'host', 'port', 'username', 'password', 'max_databases', 'node_id', 'name', 'host', 'port', 'username', 'password', 'max_databases',
]; ];
/** /**
@ -54,7 +53,6 @@ class DatabaseHost extends Model
protected $casts = [ protected $casts = [
'id' => 'integer', 'id' => 'integer',
'max_databases' => 'integer', 'max_databases' => 'integer',
'node_id' => 'integer',
]; ];
/** /**
@ -68,21 +66,10 @@ class DatabaseHost extends Model
'port' => 'required|numeric|between:1,65535', 'port' => 'required|numeric|between:1,65535',
'username' => 'required|string|max:32', 'username' => 'required|string|max:32',
'password' => 'nullable|string', 'password' => 'nullable|string',
'node_id' => 'sometimes|nullable|integer|exists:nodes,id',
]; ];
/** /**
* Gets the node associated with a database host. * Gets the databases associated with a database host.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsTo
*/
public function node()
{
return $this->belongsTo(Node::class);
}
/**
* Gets the databases associated with this host.
* *
* @return \Illuminate\Database\Eloquent\Relations\HasMany * @return \Illuminate\Database\Eloquent\Relations\HasMany
*/ */
@ -90,4 +77,14 @@ class DatabaseHost extends Model
{ {
return $this->hasMany(Database::class); return $this->hasMany(Database::class);
} }
/**
* Gets the nodes associated with a database host.
*
* @return \Illuminate\Database\Eloquent\Relations\BelongsToMany
*/
public function nodes()
{
return $this->belongsToMany(Node::class);
}
} }

View file

@ -0,0 +1,27 @@
<?php
namespace Pterodactyl\Models;
use Illuminate\Database\Eloquent\Model;
/**
* @property int $node_id
* @property int $database_host_id
*/
class DatabaseHostNode extends Model
{
/**
* @var string
*/
protected $table = 'database_host_node';
/**
* @var null
*/
protected $primaryKey = null;
/**
* @var bool
*/
public $incrementing = false;
}

View file

@ -3,6 +3,7 @@
namespace Pterodactyl\Transformers\Api\Application; namespace Pterodactyl\Transformers\Api\Application;
use Cake\Chronos\Chronos; use Cake\Chronos\Chronos;
use Pterodactyl\Models\Node;
use Pterodactyl\Models\Database; use Pterodactyl\Models\Database;
use Pterodactyl\Models\DatabaseHost; use Pterodactyl\Models\DatabaseHost;
use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Services\Acl\Api\AdminAcl;
@ -12,9 +13,7 @@ class DatabaseHostTransformer extends BaseTransformer
/** /**
* @var array * @var array
*/ */
protected $availableIncludes = [ protected $availableIncludes = ['databases', 'nodes'];
'databases',
];
/** /**
* Return the resource name for the JSONAPI output. * Return the resource name for the JSONAPI output.
@ -68,6 +67,36 @@ class DatabaseHostTransformer extends BaseTransformer
$model->loadMissing('databases'); $model->loadMissing('databases');
return $this->collection($model->getRelation('databases'), $this->makeTransformer(ServerDatabaseTransformer::class), Database::RESOURCE_NAME); return $this->collection(
$model->getRelation('databases'),
$this->makeTransformer(ServerDatabaseTransformer::class),
Database::RESOURCE_NAME
);
}
/**
* Return the nodes associated with this mount.
*
* @param \Pterodactyl\Models\Mount $mount
*
* @return \League\Fractal\Resource\Collection|\League\Fractal\Resource\NullResource
* @throws \Pterodactyl\Exceptions\Transformer\InvalidTransformerLevelException
* @throws \Illuminate\Contracts\Container\BindingResolutionException
*/
public function includeNodes(DatabaseHost $model)
{
if (!$this->authorize(AdminAcl::RESOURCE_NODES)) {
return $this->null();
}
$model->loadMissing('nodes');
return $this->collection(
$model->getRelation('nodes'),
$this->makeTransformer(NodeTransformer::class),
Node::RESOURCE_NAME
);
} }
} }

View file

@ -2,7 +2,10 @@
namespace Pterodactyl\Transformers\Api\Application; namespace Pterodactyl\Transformers\Api\Application;
use Pterodactyl\Models\Egg;
use Pterodactyl\Models\Node;
use Pterodactyl\Models\Mount; use Pterodactyl\Models\Mount;
use Pterodactyl\Models\Server;
use Pterodactyl\Services\Acl\Api\AdminAcl; use Pterodactyl\Services\Acl\Api\AdminAcl;
class MountTransformer extends BaseTransformer class MountTransformer extends BaseTransformer
@ -64,7 +67,7 @@ class MountTransformer extends BaseTransformer
return $this->collection( return $this->collection(
$mount->getRelation('eggs'), $mount->getRelation('eggs'),
$this->makeTransformer(EggTransformer::class), $this->makeTransformer(EggTransformer::class),
'egg', Egg::RESOURCE_NAME
); );
} }
@ -88,7 +91,7 @@ class MountTransformer extends BaseTransformer
return $this->collection( return $this->collection(
$mount->getRelation('nodes'), $mount->getRelation('nodes'),
$this->makeTransformer(NodeTransformer::class), $this->makeTransformer(NodeTransformer::class),
'node', Node::RESOURCE_NAME
); );
} }
@ -112,7 +115,7 @@ class MountTransformer extends BaseTransformer
return $this->collection( return $this->collection(
$mount->getRelation('servers'), $mount->getRelation('servers'),
$this->makeTransformer(ServerTransformer::class), $this->makeTransformer(ServerTransformer::class),
'server', Server::RESOURCE_NAME
); );
} }
} }

View file

@ -0,0 +1,56 @@
<?php
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Schema;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;
class AddDatabaseHostNodeTable extends Migration
{
/**
* Run the migrations.
*
* @return void
*/
public function up()
{
Schema::create('database_host_node', function (Blueprint $table) {
$table->integer('node_id')->unique();
$table->integer('database_host_id');
$table->unique(['node_id', 'database_host_id']);
});
DB::transaction(function () {
foreach (DB::select('SELECT id, node_id FROM database_hosts') as $datum) {
if (! is_null($datum->node_id)) {
DB::insert('INSERT INTO database_host_node (node_id, database_host_id) VALUES (?, ?)', [
$datum->node_id,
$datum->id
]);
}
}
});
Schema::table('database_hosts', function (Blueprint $table) {
$table->dropForeign(['node_id']);
$table->dropColumn('node_id');
});
}
/**
* Reverse the migrations.
*
* @return void
*/
public function down()
{
Schema::table('database_hosts', function (Blueprint $table) {
$table->integer('node_id')->unsigned()->nullable()->after('max_databases');
$table->foreign('node_id')->references('id')->on('nodes')->onDelete('set null');
});
Schema::dropIfExists('database_host_node');
}
}

View file

@ -18,9 +18,12 @@ Route::group(['prefix' => '/databases'], function () {
Route::post('/', 'Databases\DatabaseController@store'); Route::post('/', 'Databases\DatabaseController@store');
Route::put('/{databaseHost}/nodes', 'Databases\DatabaseController@addNodes');
Route::patch('/{databaseHost}', 'Databases\DatabaseController@update'); Route::patch('/{databaseHost}', 'Databases\DatabaseController@update');
Route::delete('/{databaseHost}', 'Databases\DatabaseController@delete'); Route::delete('/{databaseHost}', 'Databases\DatabaseController@delete');
Route::delete('/{databaseHost}/nodes', 'Databases\DatabaseController@deleteNodes');
}); });
/* /*