Skip to content

Commit

Permalink
PostgreSQL database support
Browse files Browse the repository at this point in the history
  • Loading branch information
PseudoResonance committed Jan 8, 2025
1 parent c57a0fe commit 219bb4e
Show file tree
Hide file tree
Showing 47 changed files with 757 additions and 173 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, pgsql, tokenizer, xml, zip
tools: composer:v2
coverage: none

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, pgsql, tokenizer, xml, zip
tools: composer:v2
coverage: none

Expand Down Expand Up @@ -134,7 +134,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, pgsql, tokenizer, xml, zip
tools: composer:v2
coverage: none

Expand Down Expand Up @@ -195,7 +195,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, pgsql, tokenizer, xml, zip
tools: composer:v2
coverage: none

Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name: Lint
on:
pull_request:
branches:
- '**'
- "**"

jobs:
pint:
Expand All @@ -17,7 +17,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, pgsql, tokenizer, xml, zip
tools: composer:v2
coverage: none

Expand All @@ -40,7 +40,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: "8.3"
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, pgsql, tokenizer, xml, zip
tools: composer:v2
coverage: none

Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/release.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ jobs:
uses: shivammathur/setup-php@v2
with:
php-version: ${{ matrix.php }}
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, tokenizer, xml, zip
extensions: bcmath, curl, gd, mbstring, mysql, openssl, pdo, pgsql, tokenizer, xml, zip
tools: composer:v2
coverage: none

Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,9 +12,9 @@ RUN touch .env
# Install dependencies
RUN apk update && apk add --no-cache \
libpng-dev libjpeg-turbo-dev freetype-dev libzip-dev icu-dev \
zip unzip curl \
zip unzip curl libpq-dev postgresql \
caddy ca-certificates supervisor \
&& docker-php-ext-install bcmath gd intl zip opcache pcntl posix pdo_mysql
&& docker-php-ext-install bcmath gd intl zip opcache pcntl posix pdo_mysql pdo_pgsql

# Install dependencies with Composer
COPY composer.json composer.lock ./
Expand Down
61 changes: 61 additions & 0 deletions app/Console/Commands/Environment/DatabaseSettingsCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ class DatabaseSettingsCommand extends Command
'sqlite' => 'SQLite (recommended)',
'mariadb' => 'MariaDB',
'mysql' => 'MySQL',
'pgsql' => 'PostgreSQL',
];

protected $description = 'Configure database settings for the Panel.';
Expand Down Expand Up @@ -181,6 +182,66 @@ public function handle(): int
'Database Path',
env('DB_DATABASE', 'database.sqlite')
);
} elseif ($this->variables['DB_CONNECTION'] === 'pgsql') {
$this->output->note(__('commands.database_settings.DB_HOST_note'));
$this->variables['DB_HOST'] = $this->option('host') ?? $this->ask(
'Database Host',
config('database.connections.pgsql.host', '127.0.0.1')
);

$this->variables['DB_PORT'] = $this->option('port') ?? $this->ask(
'Database Port',
config('database.connections.pgsql.port', 5432)
);

$this->variables['DB_DATABASE'] = $this->option('database') ?? $this->ask(
'Database Name',
config('database.connections.pgsql.database', 'panel')
);

$this->output->note(__('commands.database_settings.DB_USERNAME_note'));
$this->variables['DB_USERNAME'] = $this->option('username') ?? $this->ask(
'Database Username',
config('database.connections.pgsql.username', 'pelican')
);

$askForPGSQLPassword = true;
if (!empty(config('database.connections.pgsql.password')) && $this->input->isInteractive()) {
$this->variables['DB_PASSWORD'] = config('database.connections.pgsql.password');
$askForPGSQLPassword = $this->confirm(__('commands.database_settings.DB_PASSWORD_note'));
}

if ($askForPGSQLPassword) {
$this->variables['DB_PASSWORD'] = $this->option('password') ?? $this->secret('Database Password');
}

try {
// Test connection
config()->set('database.connections._panel_command_test', [
'driver' => 'pgsql',
'host' => $this->variables['DB_HOST'],
'port' => $this->variables['DB_PORT'],
'database' => $this->variables['DB_DATABASE'],
'username' => $this->variables['DB_USERNAME'],
'password' => $this->variables['DB_PASSWORD'],
'charset' => 'UTF8',
'collation' => 'en_US.UTF-8',
'strict' => true,
]);

$this->database->connection('_panel_command_test')->getPdo();
} catch (\PDOException $exception) {
$this->output->error(sprintf('Unable to connect to the PostgreSQL server using the provided credentials. The error returned was "%s".', $exception->getMessage()));
$this->output->error(__('commands.database_settings.DB_error_2'));

if ($this->confirm(__('commands.database_settings.go_back'))) {
$this->database->disconnect('_panel_command_test');

return $this->handle();
}

return 1;
}
}

$this->writeToEnvironment($this->variables);
Expand Down
32 changes: 22 additions & 10 deletions app/Extensions/DynamicDatabaseConnection.php
Original file line number Diff line number Diff line change
Expand Up @@ -6,30 +6,42 @@

class DynamicDatabaseConnection
{
public const DB_CHARSET = 'utf8';

public const DB_COLLATION = 'utf8_unicode_ci';

public const DB_DRIVER = 'mysql';
public const DB_DEFAULTS = [
"mysql" => [
"DB_CHARSET" => 'utf8',
"DB_COLLATION" => 'utf8_unicode_ci',
"DEFAULT_DB" => 'mysql',
],
"mariadb" => [
"DB_CHARSET" => 'utf8',
"DB_COLLATION" => 'utf8_unicode_ci',
"DEFAULT_DB" => 'mysql',
],
"pgsql" => [
"DB_CHARSET" => 'utf8',
"DB_COLLATION" => 'en_US',
"DEFAULT_DB" => 'postgres',
],
];

/**
* Adds a dynamic database connection entry to the runtime config.
*/
public function set(string $connection, DatabaseHost|int $host, string $database = 'mysql'): void
public function set(string $connection, DatabaseHost|int $host, string $database = null): void
{
if (!$host instanceof DatabaseHost) {
$host = DatabaseHost::query()->findOrFail($host);
}

config()->set('database.connections.' . $connection, [
'driver' => self::DB_DRIVER,
'driver' => $host->driver,
'host' => $host->host,
'port' => $host->port,
'database' => $database,
'database' => $database === null ? self::DB_DEFAULTS[$host->driver]['DEFAULT_DB'] : $database,
'username' => $host->username,
'password' => $host->password,
'charset' => self::DB_CHARSET,
'collation' => self::DB_COLLATION,
'charset' => self::DB_DEFAULTS[$host->driver]['DB_CHARSET'],
'collation' => self::DB_DEFAULTS[$host->driver]['DB_COLLATION'],
]);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Filament\Forms\Components\Section;
use Filament\Forms\Components\Select;
use Filament\Forms\Components\TextInput;
use Filament\Forms\Components\ToggleButtons;
use Filament\Forms\Form;
use Filament\Notifications\Notification;
use Filament\Resources\Pages\CreateRecord;
Expand Down Expand Up @@ -40,6 +41,15 @@ public function form(Form $form): Form
'lg' => 4,
])
->schema([
ToggleButtons::make('driver')
->label('Database Driver')
->inline()
->options([
'mariadb' => 'MariaDB',
'mysql' => 'MySQL',
'pgsql' => 'PostgreSQL',
])
->default('mariadb'),
TextInput::make('host')
->columnSpan(2)
->helperText('The IP address or Domain name that should be used when attempting to connect to this MySQL host from this Panel to create new databases.')
Expand Down
114 changes: 78 additions & 36 deletions app/Livewire/Installer/Steps/DatabaseStep.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ class DatabaseStep
'sqlite' => 'SQLite',
'mariadb' => 'MariaDB',
'mysql' => 'MySQL',
'pgsql' => 'PostgreSQL',
];

public static function make(PanelInstaller $installer): Step
Expand All @@ -39,15 +40,24 @@ public static function make(PanelInstaller $installer): Step
->afterStateUpdated(function ($state, Set $set, Get $get) {
$set('env_database.DB_DATABASE', $state === 'sqlite' ? 'database.sqlite' : 'panel');

if ($state === 'sqlite') {
$set('env_database.DB_HOST', null);
$set('env_database.DB_PORT', null);
$set('env_database.DB_USERNAME', null);
$set('env_database.DB_PASSWORD', null);
} else {
$set('env_database.DB_HOST', $get('env_database.DB_HOST') ?? '127.0.0.1');
$set('env_database.DB_PORT', $get('env_database.DB_PORT') ?? '3306');
$set('env_database.DB_USERNAME', $get('env_database.DB_USERNAME') ?? 'pelican');
switch ($state) {
case 'sqlite':
$set('env_database.DB_HOST', null);
$set('env_database.DB_PORT', null);
$set('env_database.DB_USERNAME', null);
$set('env_database.DB_PASSWORD', null);
break;
case 'mariadb':
case 'mysql':
$set('env_database.DB_HOST', $get('env_database.DB_HOST') ?? '127.0.0.1');
$set('env_database.DB_PORT', $get('env_database.DB_PORT') ?? '3306');
$set('env_database.DB_USERNAME', $get('env_database.DB_USERNAME') ?? 'pelican');
break;
case 'pgsql':
$set('env_database.DB_HOST', $get('env_database.DB_HOST') ?? '127.0.0.1');
$set('env_database.DB_PORT', $get('env_database.DB_PORT') ?? '5432');
$set('env_database.DB_USERNAME', $get('env_database.DB_USERNAME') ?? 'pelican');
break;
}
}),
TextInput::make('env_database.DB_DATABASE')
Expand Down Expand Up @@ -102,34 +112,66 @@ public static function make(PanelInstaller $installer): Step

private static function testConnection(string $driver, ?string $host, null|string|int $port, ?string $database, ?string $username, ?string $password): bool
{
if ($driver === 'sqlite') {
return true;
}
switch ($driver) {
case 'sqlite':
return true;

case 'mariadb':
case 'mysql':
try {
config()->set('database.connections._panel_install_test', [
'driver' => $driver,
'host' => $host,
'port' => $port,
'database' => $database,
'username' => $username,
'password' => $password,
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'strict' => true,
]);

DB::connection('_panel_install_test')->getPdo();
} catch (Exception $exception) {
DB::disconnect('_panel_install_test');

Notification::make()
->title('Database connection failed')
->body($exception->getMessage())
->danger()
->send();

return false;
}
break;

case 'pgsql':
try {
config()->set('database.connections._panel_install_test', [
'driver' => $driver,
'host' => $host,
'port' => $port,
'database' => $database,
'username' => $username,
'password' => $password,
'charset' => 'UTF8',
'collation' => 'en_US.UTF-8',
'strict' => true,
]);

DB::connection('_panel_install_test')->getPdo();
} catch (Exception $exception) {
DB::disconnect('_panel_install_test');

try {
config()->set('database.connections._panel_install_test', [
'driver' => $driver,
'host' => $host,
'port' => $port,
'database' => $database,
'username' => $username,
'password' => $password,
'charset' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'strict' => true,
]);

DB::connection('_panel_install_test')->getPdo();
} catch (Exception $exception) {
DB::disconnect('_panel_install_test');

Notification::make()
->title('Database connection failed')
->body($exception->getMessage())
->danger()
->send();

return false;
Notification::make()
->title('Database connection failed')
->body($exception->getMessage())
->danger()
->send();

return false;
}
break;
}

return true;
Expand Down
1 change: 1 addition & 0 deletions app/Livewire/Installer/Steps/RequirementsStep.php
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ public static function make(): Step
'intl' => extension_loaded('intl'),
'mbstring' => extension_loaded('mbstring'),
'MySQL' => extension_loaded('pdo_mysql'),
'PGSQL' => extension_loaded('pdo_pgsql'),
'SQLite3' => extension_loaded('pdo_sqlite'),
'XML' => extension_loaded('xml'),
'Zip' => extension_loaded('zip'),
Expand Down
Loading

0 comments on commit 219bb4e

Please sign in to comment.