2020-06-13 23:58:59 +02:00
< ? php
2023-06-11 18:59:07 +02:00
declare ( strict_types = 1 );
2022-11-29 21:21:26 +01:00
/*
* This file is part of Part - DB ( https :// github . com / Part - DB / Part - DB - symfony ) .
*
* Copyright ( C ) 2019 - 2022 Jan Böhmer ( https :// github . com / jbtronics )
*
* This program is free software : you can redistribute it and / or modify
* it under the terms of the GNU Affero General Public License as published
* by the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
*
* This program is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU Affero General Public License for more details .
*
* You should have received a copy of the GNU Affero General Public License
* along with this program . If not , see < https :// www . gnu . org / licenses />.
*/
2020-07-04 23:38:18 +02:00
namespace App\Migration ;
2020-06-13 23:58:59 +02:00
2020-07-04 23:38:18 +02:00
use Doctrine\DBAL\Connection ;
2022-03-04 13:03:12 +01:00
use Doctrine\DBAL\Exception ;
2022-03-04 18:51:58 +01:00
use Doctrine\DBAL\Platforms\AbstractMySQLPlatform ;
2024-06-06 23:11:11 +02:00
use Doctrine\DBAL\Platforms\PostgreSQLPlatform ;
2024-06-09 23:36:00 +02:00
use Doctrine\DBAL\Platforms\SQLitePlatform ;
2020-06-13 23:58:59 +02:00
use Doctrine\DBAL\Schema\Schema ;
use Doctrine\Migrations\AbstractMigration ;
2020-07-04 23:38:18 +02:00
use Psr\Log\LoggerInterface ;
2020-06-13 23:58:59 +02:00
abstract class AbstractMultiPlatformMigration extends AbstractMigration
{
2023-06-11 14:15:46 +02:00
final public const ADMIN_PW_LENGTH = 10 ;
2025-09-23 20:32:58 +02:00
protected ? string $admin_pw = null ;
protected ? string $admin_api_token = null ;
2020-06-14 21:17:57 +02:00
2024-03-03 19:57:31 +01:00
/** @ noinspection SenselessProxyMethodInspection
* This method is required to redefine the logger type hint to protected
*/
2023-06-11 14:15:46 +02:00
public function __construct ( Connection $connection , protected LoggerInterface $logger )
2020-07-04 23:38:18 +02:00
{
2023-04-15 22:05:29 +02:00
parent :: __construct ( $connection , $logger );
2020-07-04 23:38:18 +02:00
}
2020-06-13 23:58:59 +02:00
public function up ( Schema $schema ) : void
{
2022-03-04 18:51:58 +01:00
$db_type = $this -> getDatabaseType ();
2020-06-13 23:58:59 +02:00
2023-06-11 14:15:46 +02:00
match ( $db_type ) {
'mysql' => $this -> mySQLUp ( $schema ),
'sqlite' => $this -> sqLiteUp ( $schema ),
2024-06-06 23:11:11 +02:00
'postgresql' => $this -> postgreSQLUp ( $schema ),
2023-06-11 14:15:46 +02:00
default => $this -> abortIf ( true , " Database type ' $db_type ' is not supported! " ),
};
2020-06-13 23:58:59 +02:00
}
public function down ( Schema $schema ) : void
{
2022-03-04 18:51:58 +01:00
$db_type = $this -> getDatabaseType ();
2020-06-13 23:58:59 +02:00
2023-06-11 14:15:46 +02:00
match ( $db_type ) {
'mysql' => $this -> mySQLDown ( $schema ),
'sqlite' => $this -> sqLiteDown ( $schema ),
2024-06-06 23:11:11 +02:00
'postgresql' => $this -> postgreSQLDown ( $schema ),
2023-06-11 14:15:46 +02:00
default => $this -> abortIf ( true , " Database type is not supported! " ),
};
2020-06-13 23:58:59 +02:00
}
/**
2023-04-15 23:14:53 +02:00
* Gets the legacy Part - DB version number . Returns 0 , if target database is not a legacy Part - DB database .
2020-06-13 23:58:59 +02:00
*/
public function getOldDBVersion () : int
{
2022-03-04 18:51:58 +01:00
if ( 'mysql' !== $this -> getDatabaseType ()) {
2020-06-13 23:58:59 +02:00
//Old Part-DB version only supported MySQL therefore only
return 0 ;
}
try {
2022-03-04 13:03:12 +01:00
$version = $this -> connection -> fetchOne ( " SELECT keyValue AS version FROM `internal` WHERE `keyName` = 'dbVersion' " );
if ( is_bool ( $version )) {
return 0 ;
}
return ( int ) $version ;
2023-06-11 14:15:46 +02:00
} catch ( Exception ) {
2020-06-13 23:58:59 +02:00
//when the table was not found, we can proceed, because we have an empty DB!
return 0 ;
}
}
2020-06-14 22:29:15 +02:00
/**
* Returns the hash of a new random password , created for the initial admin user , which can be written to DB .
* The plaintext version of the password will be outputed to user after this migration .
*/
2020-06-13 23:58:59 +02:00
public function getInitalAdminPW () : string
{
2025-09-23 20:32:58 +02:00
if ( $this -> admin_pw === null ) {
2020-06-14 22:29:15 +02:00
if ( ! empty ( $_ENV [ 'INITIAL_ADMIN_PW' ])) {
$this -> admin_pw = $_ENV [ 'INITIAL_ADMIN_PW' ];
} else {
$this -> admin_pw = substr ( md5 ( random_bytes ( 10 )), 0 , static :: ADMIN_PW_LENGTH );
}
}
2023-04-15 23:14:53 +02:00
//As we don't have access to container, just use the default PHP pw hash function
2023-06-11 14:15:46 +02:00
return password_hash (( string ) $this -> admin_pw , PASSWORD_DEFAULT );
2020-06-13 23:58:59 +02:00
}
2025-09-07 08:12:25 -06:00
/**
* Returns the initial admin API token if configured via environment variable .
* If not configured , returns empty string ( no token will be created ) .
*/
public function getInitialAdminApiToken () : string
{
2025-09-23 20:32:58 +02:00
if ( $this -> admin_api_token === null ) {
$apiKey = $_ENV ( 'INITIAL_ADMIN_API_KEY' );
2025-09-07 08:12:25 -06:00
if ( ! empty ( $apiKey )) {
2025-09-23 20:32:58 +02:00
//Ensure the length of the API key is correct
if ( strlen ( $apiKey ) < 64 ) {
$this -> abortIf ( true , 'The provided INITIAL_ADMIN_API_KEY is too short! It must be at least 64 characters long! You can generate a valid key with "openssl rand -hex 32"' );
}
2025-09-07 08:12:25 -06:00
// Use the provided API key directly (should be generated with openssl rand -hex 32)
$this -> admin_api_token = $apiKey ;
}
}
return $this -> admin_api_token ;
}
2020-06-14 21:17:57 +02:00
public function postUp ( Schema $schema ) : void
{
parent :: postUp ( $schema );
2020-06-14 22:29:15 +02:00
2023-06-11 18:59:07 +02:00
if ( $this -> admin_pw !== '' ) {
2020-07-04 23:38:18 +02:00
$this -> logger -> warning ( '' );
2020-08-21 21:36:22 +02:00
$this -> logger -> warning ( '<bg=yellow;fg=black>The initial password for the "admin" user is: ' . $this -> admin_pw . '</>' );
2020-07-04 23:38:18 +02:00
$this -> logger -> warning ( '' );
2020-06-14 22:29:15 +02:00
}
2025-09-07 08:12:25 -06:00
if ( $this -> admin_api_token !== '' ) {
$this -> logger -> warning ( '' );
$this -> logger -> warning ( '<bg=green;fg=black>Initial admin API token has been created with the provided key</>' );
$this -> logger -> warning ( '<bg=yellow;fg=black>Use this token in Authorization header: Bearer tcp_' . $this -> admin_api_token . '</>' );
$this -> logger -> warning ( '' );
}
2020-06-14 21:17:57 +02:00
}
2023-04-08 23:27:10 +02:00
/**
* Checks if a foreign key on a table exists in the database .
* This method is only supported for MySQL / MariaDB databases yet !
* @ return bool Returns true , if the foreign key exists
* @ throws Exception
*/
public function doesFKExists ( string $table , string $fk_name ) : bool
{
$db_type = $this -> getDatabaseType ();
if ( $db_type !== 'mysql' ) {
throw new \RuntimeException ( 'This method is only supported for MySQL/MariaDB databases!' );
}
$sql = " SELECT COUNT(*) FROM information_schema.TABLE_CONSTRAINTS WHERE CONSTRAINT_SCHEMA = DATABASE() AND CONSTRAINT_NAME = ' $fk_name ' AND TABLE_NAME = ' $table ' AND CONSTRAINT_TYPE = 'FOREIGN KEY' " ;
2023-04-08 23:32:38 +02:00
$result = ( int ) $this -> connection -> fetchOne ( $sql );
2023-04-08 23:27:10 +02:00
return $result > 0 ;
}
2023-09-09 23:04:50 +02:00
/**
* Checks if a column exists in a table .
* @ return bool Returns true , if the column exists
* @ throws Exception
*/
public function doesColumnExist ( string $table , string $column_name ) : bool
{
$db_type = $this -> getDatabaseType ();
if ( $db_type !== 'mysql' ) {
throw new \RuntimeException ( 'This method is only supported for MySQL/MariaDB databases!' );
}
$sql = " SELECT COUNT(*) FROM information_schema.COLUMNS WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME = ' $table ' AND COLUMN_NAME = ' $column_name ' " ;
$result = ( int ) $this -> connection -> fetchOne ( $sql );
return $result > 0 ;
}
2022-03-04 18:51:58 +01:00
/**
* Returns the database type of the used database .
* @ return string | null Returns 'mysql' for MySQL / MariaDB and 'sqlite' for SQLite . Returns null if unknown type
*/
public function getDatabaseType () : ? string
{
2022-07-17 23:45:17 +02:00
if ( $this -> connection -> getDatabasePlatform () instanceof AbstractMySQLPlatform ) {
2022-03-04 18:51:58 +01:00
return 'mysql' ;
}
2024-06-09 23:36:00 +02:00
if ( $this -> connection -> getDatabasePlatform () instanceof SQLitePlatform ) {
2022-03-04 18:51:58 +01:00
return 'sqlite' ;
}
2024-06-06 23:11:11 +02:00
if ( $this -> connection -> getDatabasePlatform () instanceof PostgreSqlPlatform ) {
return 'postgresql' ;
}
2022-03-04 18:51:58 +01:00
return null ;
}
2020-06-13 23:58:59 +02:00
abstract public function mySQLUp ( Schema $schema ) : void ;
abstract public function mySQLDown ( Schema $schema ) : void ;
abstract public function sqLiteUp ( Schema $schema ) : void ;
abstract public function sqLiteDown ( Schema $schema ) : void ;
2024-06-06 23:11:11 +02:00
abstract public function postgreSQLUp ( Schema $schema ) : void ;
abstract public function postgreSQLDown ( Schema $schema ) : void ;
2020-08-21 21:36:22 +02:00
}