mirror of
https://github.com/pelican-dev/panel.git
synced 2025-09-07 13:08:36 +02:00
Refactor & Catch DatabaseManagementService (#1671)
Co-authored-by: notCharles <charles@pelican.dev>
This commit is contained in:
parent
420730ba1f
commit
2ef81eae1a
@ -687,8 +687,22 @@ class EditServer extends EditRecord
|
||||
->modalHeading(trans('admin/server.delete_db_heading'))
|
||||
->modalSubmitActionLabel(trans('filament-actions::delete.single.label'))
|
||||
->modalDescription(fn (Get $get) => trans('admin/server.delete_db', ['name' => $get('database')]))
|
||||
->action(function (DatabaseManagementService $databaseManagementService, $record) {
|
||||
$databaseManagementService->delete($record);
|
||||
->action(function (DatabaseManagementService $service, $record) {
|
||||
try {
|
||||
$service->delete($record);
|
||||
|
||||
Notification::make()
|
||||
->title(trans('server/database.delete_notification', ['database' => $record->database]))
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->title(trans('server/database.delete_notification_fail', ['database' => $record->database]))
|
||||
->body($e->getMessage())
|
||||
->danger()
|
||||
->persistent()->send();
|
||||
}
|
||||
|
||||
$this->fillForm();
|
||||
})
|
||||
),
|
||||
@ -737,21 +751,22 @@ class EditServer extends EditRecord
|
||||
->label(fn () => DatabaseHost::query()->count() < 1 ? trans('admin/server.no_db_hosts') : trans('admin/server.create_database'))
|
||||
->color(fn () => DatabaseHost::query()->count() < 1 ? 'danger' : 'primary')
|
||||
->modalSubmitActionLabel(trans('admin/server.create_database'))
|
||||
->action(function (array $data, DatabaseManagementService $service, Server $server, RandomWordService $randomWordService) {
|
||||
if (empty($data['database'])) {
|
||||
$data['database'] = $randomWordService->word() . random_int(1, 420);
|
||||
}
|
||||
if (empty($data['remote'])) {
|
||||
$data['remote'] = '%';
|
||||
}
|
||||
->action(function (array $data, DatabaseManagementService $service, Server $server) {
|
||||
$data['database'] ??= str_random(12);
|
||||
$data['remote'] ??= '%';
|
||||
|
||||
$data['database'] = $service->generateUniqueDatabaseName($data['database'], $server->id);
|
||||
|
||||
try {
|
||||
$service->setValidateDatabaseLimit(false)->create($server, $data);
|
||||
|
||||
Notification::make()
|
||||
->title(trans('server/database.create_notification', ['database' => $data['database']]))
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $e) {
|
||||
Notification::make()
|
||||
->title(trans('admin/server.failed_to_create'))
|
||||
->title(trans('server/database.create_notification_fail', ['database' => $data['database']]))
|
||||
->body($e->getMessage())
|
||||
->danger()
|
||||
->persistent()->send();
|
||||
|
@ -4,9 +4,10 @@ namespace App\Filament\Components\Forms\Actions;
|
||||
|
||||
use App\Facades\Activity;
|
||||
use App\Models\Database;
|
||||
use App\Services\Databases\DatabasePasswordService;
|
||||
use App\Services\Databases\DatabaseManagementService;
|
||||
use Exception;
|
||||
use Filament\Actions\StaticAction;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Actions\Action;
|
||||
use Filament\Forms\Set;
|
||||
use Filament\Notifications\Notification;
|
||||
@ -36,10 +37,9 @@ class RotateDatabasePasswordAction extends Action
|
||||
|
||||
$this->requiresConfirmation();
|
||||
|
||||
$this->action(function (DatabasePasswordService $service, Database $database, Set $set) {
|
||||
$this->action(function (DatabaseManagementService $service, Database $database, Set $set) {
|
||||
try {
|
||||
$service->handle($database);
|
||||
|
||||
$service->rotatePassword($database);
|
||||
$database->refresh();
|
||||
|
||||
$set('password', $database->password);
|
||||
@ -57,7 +57,7 @@ class RotateDatabasePasswordAction extends Action
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title(trans('admin/databasehost.rotate_error'))
|
||||
->body($exception->getMessage())
|
||||
->body(fn () => auth()->user()->canAccessPanel(Filament::getPanel('admin')) ? $exception->getMessage() : null)
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
|
@ -15,9 +15,11 @@ use App\Traits\Filament\CanCustomizeRelations;
|
||||
use App\Traits\Filament\CanModifyForm;
|
||||
use App\Traits\Filament\CanModifyTable;
|
||||
use App\Traits\Filament\HasLimitBadge;
|
||||
use Exception;
|
||||
use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Forms\Form;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\PageRegistration;
|
||||
use Filament\Resources\Resource;
|
||||
use Filament\Tables\Actions\DeleteAction;
|
||||
@ -122,7 +124,23 @@ class DatabaseResource extends Resource
|
||||
ViewAction::make()
|
||||
->modalHeading(fn (Database $database) => trans('server/database.viewing', ['database' => $database->database])),
|
||||
DeleteAction::make()
|
||||
->using(fn (Database $database, DatabaseManagementService $service) => $service->delete($database)),
|
||||
->using(function (Database $database, DatabaseManagementService $service) {
|
||||
try {
|
||||
$service->delete($database);
|
||||
|
||||
Notification::make()
|
||||
->title(trans('server/database.delete_notification', ['database' => $database->database]))
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title(trans('server/database.delete_notification_fail', ['database' => $database->database]))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
report($exception);
|
||||
}
|
||||
}),
|
||||
]);
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,7 @@ use App\Models\Server;
|
||||
use App\Services\Databases\DatabaseManagementService;
|
||||
use App\Traits\Filament\CanCustomizeHeaderActions;
|
||||
use App\Traits\Filament\CanCustomizeHeaderWidgets;
|
||||
use Exception;
|
||||
use Filament\Actions\Action;
|
||||
use Filament\Actions\ActionGroup;
|
||||
use Filament\Actions\CreateAction;
|
||||
@ -15,6 +16,7 @@ use Filament\Facades\Filament;
|
||||
use Filament\Forms\Components\Grid;
|
||||
use Filament\Forms\Components\Select;
|
||||
use Filament\Forms\Components\TextInput;
|
||||
use Filament\Notifications\Notification;
|
||||
use Filament\Resources\Pages\ListRecords;
|
||||
use Filament\Support\Enums\IconSize;
|
||||
use Illuminate\Support\Str;
|
||||
@ -63,12 +65,24 @@ class ListDatabases extends ListRecords
|
||||
]),
|
||||
])
|
||||
->action(function ($data, DatabaseManagementService $service) use ($server) {
|
||||
if (empty($data['database'])) {
|
||||
$data['database'] = Str::random(12);
|
||||
}
|
||||
$data['database'] = 's'. $server->id . '_' . $data['database'];
|
||||
$data['database'] ??= Str::random(12);
|
||||
$data['database'] = $service->generateUniqueDatabaseName($data['database'], $server->id);
|
||||
|
||||
$service->create($server, $data);
|
||||
try {
|
||||
$service->create($server, $data);
|
||||
|
||||
Notification::make()
|
||||
->title(trans('server/database.create_notification', ['database' => $data['database']]))
|
||||
->success()
|
||||
->send();
|
||||
} catch (Exception $exception) {
|
||||
Notification::make()
|
||||
->title(trans('server/database.create_notification_fail', ['database' => $data['database']]))
|
||||
->danger()
|
||||
->send();
|
||||
|
||||
report($exception);
|
||||
}
|
||||
}),
|
||||
];
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ use Illuminate\Http\Response;
|
||||
use App\Models\Server;
|
||||
use App\Models\Database;
|
||||
use Illuminate\Http\JsonResponse;
|
||||
use App\Services\Databases\DatabasePasswordService;
|
||||
use App\Services\Databases\DatabaseManagementService;
|
||||
use App\Transformers\Api\Application\ServerDatabaseTransformer;
|
||||
use App\Http\Controllers\Api\Application\ApplicationApiController;
|
||||
@ -24,7 +23,6 @@ class DatabaseController extends ApplicationApiController
|
||||
*/
|
||||
public function __construct(
|
||||
private DatabaseManagementService $databaseManagementService,
|
||||
private DatabasePasswordService $databasePasswordService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
@ -66,7 +64,7 @@ class DatabaseController extends ApplicationApiController
|
||||
*/
|
||||
public function resetPassword(ServerDatabaseWriteRequest $request, Server $server, Database $database): JsonResponse
|
||||
{
|
||||
$this->databasePasswordService->handle($database);
|
||||
$this->databaseManagementService->rotatePassword($database);
|
||||
|
||||
return new JsonResponse([], JsonResponse::HTTP_NO_CONTENT);
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ use Illuminate\Http\Response;
|
||||
use App\Models\Server;
|
||||
use App\Models\Database;
|
||||
use App\Facades\Activity;
|
||||
use App\Services\Databases\DatabasePasswordService;
|
||||
use App\Transformers\Api\Client\DatabaseTransformer;
|
||||
use App\Services\Databases\DatabaseManagementService;
|
||||
use App\Services\Databases\DeployServerDatabaseService;
|
||||
@ -26,7 +25,6 @@ class DatabaseController extends ClientApiController
|
||||
public function __construct(
|
||||
private DeployServerDatabaseService $deployDatabaseService,
|
||||
private DatabaseManagementService $managementService,
|
||||
private DatabasePasswordService $passwordService
|
||||
) {
|
||||
parent::__construct();
|
||||
}
|
||||
@ -83,7 +81,7 @@ class DatabaseController extends ClientApiController
|
||||
*/
|
||||
public function rotatePassword(RotatePasswordRequest $request, Server $server, Database $database): array
|
||||
{
|
||||
$this->passwordService->handle($database);
|
||||
$this->managementService->rotatePassword($database);
|
||||
$database->refresh();
|
||||
|
||||
Activity::event('server:database.rotate-password')
|
||||
|
@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Casts\Attribute;
|
||||
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||||
use Illuminate\Database\Eloquent\Model;
|
||||
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||||
use PDOException;
|
||||
|
||||
/**
|
||||
* @property int $id
|
||||
@ -97,71 +98,97 @@ class Database extends Model implements Validatable
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PDOException
|
||||
*
|
||||
* Run the provided statement against the database on a given connection.
|
||||
*/
|
||||
private function run(string $statement): bool
|
||||
private function run(string $statement): void
|
||||
{
|
||||
return $this->host->buildConnection()->statement($statement);
|
||||
$this->host->buildConnection()->statement($statement);
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PDOException
|
||||
*
|
||||
* Create a new database on a given connection.
|
||||
*/
|
||||
public function createDatabase(string $database): bool
|
||||
public function createDatabase(): self
|
||||
{
|
||||
return $this->run(sprintf('CREATE DATABASE IF NOT EXISTS `%s`', $database));
|
||||
$this->run(sprintf('CREATE DATABASE IF NOT EXISTS `%s`', $this->database));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PDOException
|
||||
*
|
||||
* Create a new database user on a given connection.
|
||||
*/
|
||||
public function createUser(string $username, string $remote, string $password, ?int $max_connections): bool
|
||||
public function createUser(): self
|
||||
{
|
||||
$args = [$username, $remote, $password];
|
||||
$args = [$this->username, $this->remote, $this->password];
|
||||
$command = 'CREATE USER `%s`@`%s` IDENTIFIED BY \'%s\'';
|
||||
|
||||
if (!empty($max_connections)) {
|
||||
$args[] = $max_connections;
|
||||
if (!empty($this->max_connections)) {
|
||||
$args[] = $this->max_connections;
|
||||
$command .= ' WITH MAX_USER_CONNECTIONS %s';
|
||||
}
|
||||
|
||||
return $this->run(sprintf($command, ...$args));
|
||||
$this->run(sprintf($command, ...$args));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PDOException
|
||||
*
|
||||
* Give a specific user access to a given database.
|
||||
*/
|
||||
public function assignUserToDatabase(string $database, string $username, string $remote): bool
|
||||
public function assignUserToDatabase(): self
|
||||
{
|
||||
return $this->run(sprintf(
|
||||
$this->run(sprintf(
|
||||
'GRANT SELECT, INSERT, UPDATE, DELETE, CREATE, DROP, ALTER, REFERENCES, INDEX, LOCK TABLES, CREATE ROUTINE, ALTER ROUTINE, EXECUTE, CREATE TEMPORARY TABLES, CREATE VIEW, SHOW VIEW, EVENT, TRIGGER ON `%s`.* TO `%s`@`%s`',
|
||||
$database,
|
||||
$username,
|
||||
$remote
|
||||
$this->database,
|
||||
$this->username,
|
||||
$this->remote
|
||||
));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PDOException
|
||||
*
|
||||
* Flush the privileges for a given connection.
|
||||
*/
|
||||
public function flush(): bool
|
||||
public function flushPrivileges(): self
|
||||
{
|
||||
return $this->run('FLUSH PRIVILEGES');
|
||||
$this->run('FLUSH PRIVILEGES');
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PDOException
|
||||
*
|
||||
* Drop a given database on a specific connection.
|
||||
*/
|
||||
public function dropDatabase(string $database): bool
|
||||
public function dropDatabase(): self
|
||||
{
|
||||
return $this->run(sprintf('DROP DATABASE IF EXISTS `%s`', $database));
|
||||
$this->run(sprintf('DROP DATABASE IF EXISTS `%s`', $this->database));
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws PDOException
|
||||
*
|
||||
* Drop a given user on a specific connection.
|
||||
*/
|
||||
public function dropUser(string $username, string $remote): bool
|
||||
public function dropUser(): self
|
||||
{
|
||||
return $this->run(sprintf('DROP USER IF EXISTS `%s`@`%s`', $username, $remote));
|
||||
$this->run(sprintf('DROP USER IF EXISTS `%s`@`%s`', $this->username, $this->remote));
|
||||
|
||||
return $this;
|
||||
}
|
||||
}
|
||||
|
@ -93,15 +93,11 @@ class DatabaseManagementService
|
||||
return $this->connection->transaction(function () use ($data) {
|
||||
$database = $this->createModel($data);
|
||||
|
||||
$database->createDatabase($database->database);
|
||||
$database->createUser(
|
||||
$database->username,
|
||||
$database->remote,
|
||||
$database->password,
|
||||
$database->max_connections
|
||||
);
|
||||
$database->assignUserToDatabase($database->database, $database->username, $database->remote);
|
||||
$database->flush();
|
||||
$database
|
||||
->createDatabase()
|
||||
->createUser()
|
||||
->assignUserToDatabase()
|
||||
->flushPrivileges();
|
||||
|
||||
Activity::event('server:database.create')
|
||||
->subject($database)
|
||||
@ -115,14 +111,15 @@ class DatabaseManagementService
|
||||
/**
|
||||
* Delete a database from the given host server.
|
||||
*
|
||||
* @throws \Exception
|
||||
* @throws \Throwable
|
||||
*/
|
||||
public function delete(Database $database): ?bool
|
||||
{
|
||||
return $this->connection->transaction(function () use ($database) {
|
||||
$database->dropDatabase($database->database);
|
||||
$database->dropUser($database->username, $database->remote);
|
||||
$database->flush();
|
||||
$database
|
||||
->dropDatabase()
|
||||
->dropUser()
|
||||
->flushPrivileges();
|
||||
|
||||
Activity::event('server:database.delete')
|
||||
->subject($database)
|
||||
@ -133,6 +130,28 @@ class DatabaseManagementService
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Updates a password for a given database.
|
||||
*
|
||||
* @throws \Exception
|
||||
*/
|
||||
public function rotatePassword(Database $database): void
|
||||
{
|
||||
$password = Utilities::randomStringWithSpecialCharacters(24);
|
||||
|
||||
$this->connection->transaction(function () use ($database, $password) {
|
||||
$database->update([
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$database
|
||||
->dropUser()
|
||||
->createUser()
|
||||
->assignUserToDatabase()
|
||||
->flushPrivileges();
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Create the database if there is not an identical match in the DB. While you can technically
|
||||
* have the same name across multiple hosts, for the sake of keeping this logic easy to understand
|
||||
|
@ -1,40 +0,0 @@
|
||||
<?php
|
||||
|
||||
namespace App\Services\Databases;
|
||||
|
||||
use App\Models\Database;
|
||||
use App\Helpers\Utilities;
|
||||
use Illuminate\Database\ConnectionInterface;
|
||||
|
||||
class DatabasePasswordService
|
||||
{
|
||||
/**
|
||||
* DatabasePasswordService constructor.
|
||||
*/
|
||||
public function __construct(
|
||||
private ConnectionInterface $connection,
|
||||
) {}
|
||||
|
||||
/**
|
||||
* Updates a password for a given database.
|
||||
*/
|
||||
public function handle(Database|int $database): void
|
||||
{
|
||||
if (is_int($database)) {
|
||||
$database = Database::query()->findOrFail($database);
|
||||
}
|
||||
|
||||
$password = Utilities::randomStringWithSpecialCharacters(24);
|
||||
|
||||
$this->connection->transaction(function () use ($database, $password) {
|
||||
$database->update([
|
||||
'password' => $password,
|
||||
]);
|
||||
|
||||
$database->dropUser($database->username, $database->remote);
|
||||
$database->createUser($database->username, $database->remote, $password, $database->max_connections);
|
||||
$database->assignUserToDatabase($database->database, $database->username, $database->remote);
|
||||
$database->flush();
|
||||
});
|
||||
}
|
||||
}
|
@ -19,4 +19,8 @@ return [
|
||||
'database_host' => 'Database Host',
|
||||
'database_host_select' => 'Select Database Host',
|
||||
'jdbc' => 'JDBC Connection String',
|
||||
'create_notification' => 'Created :database',
|
||||
'create_notification_fail' => 'Failed to create :database',
|
||||
'delete_notification' => 'Deleted :database',
|
||||
'delete_notification_fail' => 'Failed to delete :database',
|
||||
];
|
||||
|
@ -5,7 +5,6 @@ namespace App\Tests\Integration\Api\Client\Server\Database;
|
||||
use App\Models\Subuser;
|
||||
use App\Models\Database;
|
||||
use App\Models\DatabaseHost;
|
||||
use App\Services\Databases\DatabasePasswordService;
|
||||
use App\Services\Databases\DatabaseManagementService;
|
||||
use App\Tests\Integration\Api\Client\ClientApiIntegrationTestCase;
|
||||
use PHPUnit\Framework\Attributes\DataProvider;
|
||||
@ -33,8 +32,8 @@ class DatabaseAuthorizationTest extends ClientApiIntegrationTestCase
|
||||
$database3 = Database::factory()->create(['server_id' => $server3->id, 'database_host_id' => $host->id]);
|
||||
|
||||
$this
|
||||
->mock($method === 'POST' ? DatabasePasswordService::class : DatabaseManagementService::class)
|
||||
->expects($method === 'POST' ? 'handle' : 'delete')
|
||||
->mock(DatabaseManagementService::class)
|
||||
->expects($method === 'POST' ? 'rotatePassword' : 'delete')
|
||||
->andReturn($method === 'POST' ? 'foo' : null);
|
||||
|
||||
// This is the only valid call for this test, accessing the database for the same
|
||||
|
Loading…
x
Reference in New Issue
Block a user