added version handling

This commit is contained in:
tracer 2024-04-19 15:11:21 +02:00
parent 176bd9dc83
commit 4f52e99a92
17 changed files with 255 additions and 130 deletions

2
TODO
View File

@ -1,3 +1,3 @@
versions, get from weekly
more UNIT tests more UNIT tests
DB migrations

View File

@ -1,8 +1,8 @@
{ {
"name": "24unix/bindapi", "name": "24unix/bindapi",
"description": "manage Bind9 DNS server via REST API", "description": "manage Bind9 DNS server via REST API",
"version": "2023.0.1", "version": "1.0.7",
"build_number": "344", "build_number": "345",
"authors": [ "authors": [
{ {
"name": "Micha Espey", "name": "Micha Espey",

View File

@ -0,0 +1,24 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
class ApiKeyCreationDate extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->table('apikeys', [
'id' => false,
'primary_key' => ['id'],
'engine' => 'InnoDB',
'encoding' => 'utf8mb4',
'collation' => 'utf8mb4_unicode_ci',
'comment' => '',
'row_format' => 'DYNAMIC',
])
->addColumn('created_at', 'timestamp', [
'null' => false,
'after' => 'apikey',
])
->save();
}
}

View File

@ -0,0 +1,24 @@
<?php
use Phinx\Db\Adapter\MysqlAdapter;
class UniqueConfigValues extends Phinx\Migration\AbstractMigration
{
public function change()
{
$this->table('config', [
'id' => false,
'primary_key' => ['name'],
'engine' => 'InnoDB',
'encoding' => 'utf8mb4',
'collation' => 'utf8mb4_general_ci',
'comment' => '',
'row_format' => 'DYNAMIC',
])
->addIndex(['name'], [
'name' => 'name',
'unique' => true,
])
->save();
}
}

View File

@ -129,7 +129,7 @@ return array (
'CHARACTER_SET_NAME' => 'utf8mb4', 'CHARACTER_SET_NAME' => 'utf8mb4',
'COLLATION_NAME' => 'utf8mb4_general_ci', 'COLLATION_NAME' => 'utf8mb4_general_ci',
'COLUMN_TYPE' => 'varchar(256)', 'COLUMN_TYPE' => 'varchar(256)',
'COLUMN_KEY' => '', 'COLUMN_KEY' => 'PRI',
'EXTRA' => '', 'EXTRA' => '',
'PRIVILEGES' => 'select,insert,update,references', 'PRIVILEGES' => 'select,insert,update,references',
'COLUMN_COMMENT' => '', 'COLUMN_COMMENT' => '',
@ -160,33 +160,27 @@ return array (
'IS_GENERATED' => 'NEVER', 'IS_GENERATED' => 'NEVER',
'GENERATION_EXPRESSION' => NULL, 'GENERATION_EXPRESSION' => NULL,
), ),
'fff' =>
array (
'TABLE_CATALOG' => 'def',
'TABLE_NAME' => 'config',
'COLUMN_NAME' => 'fff',
'ORDINAL_POSITION' => 3,
'COLUMN_DEFAULT' => NULL,
'IS_NULLABLE' => 'NO',
'DATA_TYPE' => 'int',
'CHARACTER_MAXIMUM_LENGTH' => NULL,
'CHARACTER_OCTET_LENGTH' => NULL,
'NUMERIC_PRECISION' => 10,
'NUMERIC_SCALE' => 0,
'DATETIME_PRECISION' => NULL,
'CHARACTER_SET_NAME' => NULL,
'COLLATION_NAME' => NULL,
'COLUMN_TYPE' => 'int(11)',
'COLUMN_KEY' => '',
'EXTRA' => '',
'PRIVILEGES' => 'select,insert,update,references',
'COLUMN_COMMENT' => '',
'IS_GENERATED' => 'NEVER',
'GENERATION_EXPRESSION' => NULL,
),
), ),
'indexes' => 'indexes' =>
array ( array (
'name' =>
array (
1 =>
array (
'Table' => 'config',
'Non_unique' => 0,
'Key_name' => 'name',
'Seq_in_index' => 1,
'Column_name' => 'name',
'Collation' => 'A',
'Sub_part' => NULL,
'Packed' => NULL,
'Null' => '',
'Index_type' => 'BTREE',
'Comment' => '',
'Index_comment' => '',
),
),
), ),
'foreign_keys' => NULL, 'foreign_keys' => NULL,
), ),
@ -927,6 +921,30 @@ return array (
'IS_GENERATED' => 'NEVER', 'IS_GENERATED' => 'NEVER',
'GENERATION_EXPRESSION' => NULL, 'GENERATION_EXPRESSION' => NULL,
), ),
'created_at' =>
array (
'TABLE_CATALOG' => 'def',
'TABLE_NAME' => 'apikeys',
'COLUMN_NAME' => 'created_at',
'ORDINAL_POSITION' => 5,
'COLUMN_DEFAULT' => NULL,
'IS_NULLABLE' => 'NO',
'DATA_TYPE' => 'timestamp',
'CHARACTER_MAXIMUM_LENGTH' => NULL,
'CHARACTER_OCTET_LENGTH' => NULL,
'NUMERIC_PRECISION' => NULL,
'NUMERIC_SCALE' => NULL,
'DATETIME_PRECISION' => 0,
'CHARACTER_SET_NAME' => NULL,
'COLLATION_NAME' => NULL,
'COLUMN_TYPE' => 'timestamp',
'COLUMN_KEY' => '',
'EXTRA' => '',
'PRIVILEGES' => 'select,insert,update,references',
'COLUMN_COMMENT' => '',
'IS_GENERATED' => 'NEVER',
'GENERATION_EXPRESSION' => NULL,
),
), ),
'indexes' => 'indexes' =>
array ( array (

View File

@ -26,6 +26,7 @@ use App\Repository\DomainRepository;
use App\Repository\DynDNSRepository; use App\Repository\DynDNSRepository;
use App\Repository\NameserverRepository; use App\Repository\NameserverRepository;
use App\Repository\PanelRepository; use App\Repository\PanelRepository;
use App\Repository\SettingsRepository;
use Arubacao\TldChecker\Validator; use Arubacao\TldChecker\Validator;
use Exception; use Exception;
use JsonMapper; use JsonMapper;
@ -62,11 +63,38 @@ class CLIController
private readonly PanelRepository $panelRepository, private readonly PanelRepository $panelRepository,
private readonly ConfigController $configController, private readonly ConfigController $configController,
private readonly EncryptionController $encryptionController, private readonly EncryptionController $encryptionController,
private readonly SettingsRepository $settingsRepository,
private $logger, private $logger,
private bool $quiet private bool $quiet
) )
{ {
$this->commandGroupContainer = (new CommandGroupContainer()) $this->commandGroupContainer = (new CommandGroupContainer())
->addCommandGroup(commandGroup: (new CommandGroup(name: 'apikeys', description: 'API keys to access this bindAPI'))
->addCommand(command: new Command(
name: 'list',
callback: function () {
$this->apikeysList();
}))
->addCommand(command: new Command(
name: 'create',
callback: function () {
$this->apikeysCreate();
},
mandatoryParameters: ['name']))
->addCommand(command: new Command(
name: 'update',
callback: function () {
$this->apikeysUpdate();
},
mandatoryParameters: ['ID',],
optionalParameters: ['name=<name>']))
->addCommand(command: new Command(
name: 'delete',
callback: function () {
$this->apikeysDelete();
},
mandatoryParameters: ['ID'])))
->addCommandGroup(commandGroup: (new CommandGroup(name: 'check', description: 'health checks the system can perform')) ->addCommandGroup(commandGroup: (new CommandGroup(name: 'check', description: 'health checks the system can perform'))
->addCommand(command: new Command( ->addCommand(command: new Command(
name: 'permissions', name: 'permissions',
@ -118,9 +146,9 @@ class CLIController
->addCommand(command: new Command( ->addCommand(command: new Command(
name: 'version', name: 'version',
callback: function () { callback: function () {
$this->checksVersion(); $this->checkVersion();
}, },
optionalParameters: ['major:minor:patch'], optionalParameters: ['update'],
description: 'Read or set the bindApi version in the database'))) description: 'Read or set the bindApi version in the database')))
->addCommandGroup(commandGroup: (new CommandGroup(name: 'panels', description: 'all KeyHelp systems configured')) ->addCommandGroup(commandGroup: (new CommandGroup(name: 'panels', description: 'all KeyHelp systems configured'))
->addCommand(command: new Command( ->addCommand(command: new Command(
@ -227,31 +255,6 @@ class CLIController
$this->dynDnsDelete(); $this->dynDnsDelete();
}, },
mandatoryParameters: ['ID']))) mandatoryParameters: ['ID'])))
->addCommandGroup(commandGroup: (new CommandGroup(name: 'apikeys', description: 'API keys to access this bindAPI'))
->addCommand(command: new Command(
name: 'list',
callback: function () {
$this->apikeysList();
}))
->addCommand(command: new Command(
name: 'create',
callback: function () {
$this->apikeysCreate();
},
mandatoryParameters: ['name']))
->addCommand(command: new Command(
name: 'update',
callback: function () {
$this->apikeysUpdate();
},
mandatoryParameters: ['ID',],
optionalParameters: ['name=<name>']))
->addCommand(command: new Command(
name: 'delete',
callback: function () {
$this->apikeysDelete();
},
mandatoryParameters: ['ID'])))
->addCommandGroup(commandGroup: (new CommandGroup(name: 'migrations', description: 'maintain database migrations')) ->addCommandGroup(commandGroup: (new CommandGroup(name: 'migrations', description: 'maintain database migrations'))
->addCommand(command: new Command( ->addCommand(command: new Command(
name: 'status', name: 'status',
@ -1155,21 +1158,18 @@ class CLIController
exit(0); exit(0);
} }
/**
* @return void
*/
function apikeysList(): void function apikeysList(): void
{ {
$keys = $this->apikeyRepository->findAll(); $keys = $this->apikeyRepository->findAll();
if (!empty($keys)) { if (!empty($keys)) {
echo 'All valid API keys:' . PHP_EOL;
$table = new ConsoleTable(); $table = new ConsoleTable();
$table->setHeaders(content: ['ID', 'Name', 'API key prefix']); $table->setHeaders(content: ['ID', 'Name', 'API key prefix', 'Created at']);
foreach ($keys as $key) { foreach ($keys as $key) {
$row = []; $row = [];
$row[] = $key->getID(); $row[] = $key->getID();
$row[] = $key->getName(); $row[] = $key->getName();
$row[] = $key->getApikeyPrefix(); $row[] = $key->getApikeyPrefix();
$row[] = $key->getCreatedAt();
$table->addRow(data: $row); $table->addRow(data: $row);
} }
$table->setPadding(value: 2); $table->setPadding(value: 2);
@ -2002,9 +2002,49 @@ class CLIController
echo 'Not yet implemented.' . PHP_EOL; echo 'Not yet implemented.' . PHP_EOL;
} }
private function checksVersion(): void private function checkVersion(): void
{ {
echo 'Not yet implemented.' . PHP_EOL; $update = false;
if (isset($this->arguments[1])) {
$action = $this->arguments[1];
if ($action !== 'update') {
if (!$this->quiet) {
echo 'The only allowed argument is "' . COLOR_YELLOW . 'update' . COLOR_DEFAULT . '.' . PHP_EOL;
}
exit(1);
}
$update = true;
}
$composerFile = dirname(path: __DIR__, levels: 2) . DIRECTORY_SEPARATOR . 'composer.json';
$composerJson = json_decode(json: file_get_contents(filename: $composerFile), associative: false);
$fileVersion = $composerJson->version;
$fileBuildNumber = $composerJson->build_number;
[$major, $minor, $patch] = explode(separator: '.', string: $fileVersion);
$version = [
'major' => $major,
'minor' => $minor,
'patch' => $patch
];
$json = json_encode(value: ['version' => $version]);
echo "File version:\t\t$fileVersion" . PHP_EOL;
echo "File build number:\t$fileBuildNumber" . PHP_EOL;
if ($update) {
$this->settingsRepository->set(name: 'version', value: $json);
$this->settingsRepository->set(name: 'buildnumber', value: $fileBuildNumber);
}
$currentDBVersion = $this->settingsRepository->findByName(name: 'version');
$dbVersion = json_decode($currentDBVersion);
$currentDBBuildnumber= $this->settingsRepository->findByName(name: 'buildnumber');
echo "DB version:\t\t";
echo $dbVersion->version->major . '.';
echo $dbVersion->version->minor . '.';
echo $dbVersion->version->patch . PHP_EOL;
echo "DB build number:\t$currentDBBuildnumber" . PHP_EOL;
} }
private function dynDnyUpdate(): void private function dynDnyUpdate(): void

View File

@ -2,9 +2,6 @@
namespace App\Controller; namespace App\Controller;
/**
*
*/
class ConfigController class ConfigController
{ {
private array $config; private array $config;

View File

@ -5,20 +5,16 @@ namespace App\Entity;
use App\Controller\ConfigController; use App\Controller\ConfigController;
use App\Controller\EncryptionController; use App\Controller\EncryptionController;
use Exception; use Exception;
use SodiumException;
/**
*
*/
class Apikey class Apikey
{ {
public function __construct( public function __construct(
private int $id = 0, private int $id = 0,
private string $name = '', private string $name = '',
private string $apikey = '', private string $apikey = '',
private string $apikeyPrefix = '', private string $apikeyPrefix = '',
private readonly string $passphrase = '' private readonly string $passphrase = '',
private string $createdAt = ''
) )
{ {
if ($this->passphrase) { if ($this->passphrase) {
@ -31,82 +27,45 @@ class Apikey
try { try {
$this->apikey = $encryptionController->safeEncrypt(message: $this->passphrase, key: $encryptionKey); $this->apikey = $encryptionController->safeEncrypt(message: $this->passphrase, key: $encryptionKey);
} catch (Exception|SodiumException $e) { } catch (Exception $e) {
exit($e->getMessage() . PHP_EOL); exit($e->getMessage() . PHP_EOL);
} }
} }
} }
/** public function getCreatedAt(): string
* @return string
*/
public function getPassphrase(): string
{ {
return $this->passphrase; return $this->createdAt;
} }
/**
* @return String
*/
public function getApikey(): string public function getApikey(): string
{ {
return $this->apikey; return $this->apikey;
} }
/**
* @return string
*/
public function getApikeyPrefix(): string public function getApikeyPrefix(): string
{ {
return $this->apikeyPrefix; return $this->apikeyPrefix;
} }
/**
* @return int
*/
public function getId(): int public function getId(): int
{ {
return $this->id; return $this->id;
} }
/**
* @param int $id
*/
public function setId(int $id): void
{
$this->id = $id;
}
/**
* @return String
*/
public function getName(): string public function getName(): string
{ {
return $this->name; return $this->name;
} }
/**
* @param string $apikeyPrefix
*/
public function setApikeyPrefix(string $apikeyPrefix): void
{
$this->apikeyPrefix = $apikeyPrefix;
}
/**
* @param String $apiToken
*/
public function setApikey(string $apikey): void public function setApikey(string $apikey): void
{ {
$this->apikey = $apikey; $this->apikey = $apikey;
} }
/**
* @param String $name
*/
public function setName(string $name): void public function setName(string $name): void
{ {
$this->name = $name; $this->name = $name;

View File

@ -1,6 +1,6 @@
<?php <?php
namespace App\Controller; namespace App\Provider;
//error_reporting(error_level: E_ALL); //error_reporting(error_level: E_ALL);
@ -8,6 +8,7 @@ namespace App\Controller;
use PDO; use PDO;
use PDOException; use PDOException;
use PHPUnit\Exception; use PHPUnit\Exception;
use App\Controller\ConfigController;
/** /**
* *
@ -22,6 +23,7 @@ class DatabaseConnection
const TABLE_PANELS = self::TABLE_PREFIX . "panels"; const TABLE_PANELS = self::TABLE_PREFIX . "panels";
const TABLE_APIKEYS = self::TABLE_PREFIX . "apikeys"; const TABLE_APIKEYS = self::TABLE_PREFIX . "apikeys";
const TABLE_DYNDNS = self::TABLE_PREFIX . "dyndns"; const TABLE_DYNDNS = self::TABLE_PREFIX . "dyndns";
const TABLE_SETTINGS = self::TABLE_PREFIX . 'config';
public function __construct(private readonly ConfigController $configController) public function __construct(private readonly ConfigController $configController)
{ {

View File

@ -3,7 +3,7 @@ namespace App\Repository;
error_reporting(error_level: E_ALL); error_reporting(error_level: E_ALL);
use App\Controller\DatabaseConnection; use App\Provider\DatabaseConnection;
use App\Controller\EncryptionController; use App\Controller\EncryptionController;
use App\Entity\Apikey; use App\Entity\Apikey;
use PDO; use PDO;
@ -18,27 +18,24 @@ class ApikeyRepository
{} {}
/**
* @return array|false
*/
public function findAll(): bool|array public function findAll(): bool|array
{ {
$sql = " $sql = "
SELECT id, name, apikey_prefix, apikey SELECT id, name, apikey_prefix, apikey, created_at
FROM " . DatabaseConnection::TABLE_APIKEYS; FROM " . DatabaseConnection::TABLE_APIKEYS;
try { try {
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql); $statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$statement->execute(); $statement->execute();
$apikeys = []; $apiKeys = [];
while ($result = $statement->fetch()) { while ($result = $statement->fetch()) {
$apikey = new Apikey(id: $result['id'], name: $result['name'], apikey: $result['apikey'], apikeyPrefix: $result['apikey_prefix']); $apikey = new Apikey(id: $result['id'], name: $result['name'], apikey: $result['apikey'], apikeyPrefix: $result['apikey_prefix'], createdAt: $result['created_at']);
$apikeys[] = $apikey; $apiKeys[] = $apikey;
} }
return $apikeys; return $apiKeys;
} catch (PDOException $e) { } catch (PDOException $e) {
exit($e->getMessage()); exit($e->getMessage());
} }

View File

@ -3,7 +3,7 @@
namespace App\Repository; namespace App\Repository;
use App\Controller\ConfigController; use App\Controller\ConfigController;
use App\Controller\DatabaseConnection; use App\Provider\DatabaseConnection;
use App\Entity\Domain; use App\Entity\Domain;
use Monolog\Logger; use Monolog\Logger;
use PDO; use PDO;

View File

@ -2,7 +2,7 @@
namespace App\Repository; namespace App\Repository;
use App\Controller\DatabaseConnection; use App\Provider\DatabaseConnection;
use App\Entity\DynDNS; use App\Entity\DynDNS;
use Monolog\Logger; use Monolog\Logger;
use PDO; use PDO;

View File

@ -2,7 +2,7 @@
namespace App\Repository; namespace App\Repository;
use App\Controller\DatabaseConnection; use App\Provider\DatabaseConnection;
use App\Entity\Nameserver; use App\Entity\Nameserver;
use PDO; use PDO;
use PDOException; use PDOException;

View File

@ -2,7 +2,7 @@
namespace App\Repository; namespace App\Repository;
use App\Controller\DatabaseConnection; use App\Provider\DatabaseConnection;
use App\Entity\Panel; use App\Entity\Panel;
use PDO; use PDO;
use PDOException; use PDOException;

View File

@ -0,0 +1,63 @@
<?php declare(strict_types=1);
namespace App\Repository;
error_reporting(error_level: E_ALL);
use App\Provider\DatabaseConnection;
use App\Controller\EncryptionController;
use PDO;
use PDOException;
/**
*
*/
readonly class SettingsRepository
{
public function __construct(private DatabaseConnection $databaseConnection, EncryptionController $encryptionController)
{}
public function findByName(string $name): string|bool
{
$sql = "
SELECT value
FROM " . DatabaseConnection::TABLE_SETTINGS . "
WHERE name = :name;
";
try {
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$statement->bindParam(param: ':name', var: $name);
$statement->execute();
if ($result = $statement->fetch(mode: PDO::FETCH_ASSOC)) {
return $result['value'];
} else {
return false;
}
} catch (PDOException $e) {
exit($e->getMessage());
}
}
public function set(string $name, string $value): int
{
$sql = "
INSERT INTO " . DatabaseConnection::TABLE_SETTINGS . " (name, value)
VALUES (:name, :value)
ON DUPLICATE KEY UPDATE
value = :value
";
try {
$statement = $this->databaseConnection->getConnection()->prepare(query: $sql);
$statement->bindParam(param: ':name', var: $name);
$statement->bindParam(param: ':value', var: $value);
$statement->execute();
return intval(value: $this->databaseConnection->getConnection()->lastInsertId());
} catch (PDOException $e) {
exit($e->getMessage());
}
}
}

View File

@ -1,9 +1,13 @@
<?php declare(strict_types=1); <?php declare(strict_types=1);
namespace App\Controller; namespace App\Service;
error_reporting(error_level: E_ALL); error_reporting(error_level: E_ALL);
use App\Controller\ConfigController;
use App\Controller\CLIController;
use App\Controller\DomainController;
use App\Controller\RequestController;
use App\Repository\DomainRepository; use App\Repository\DomainRepository;
use App\Repository\DynDNSRepository; use App\Repository\DynDNSRepository;
use DI\Container; use DI\Container;
@ -17,9 +21,6 @@ use Monolog\Level;
use Monolog\Logger; use Monolog\Logger;
use function DI\autowire; use function DI\autowire;
/**
*
*/
class BindAPI class BindAPI
{ {
private Logger $logger; private Logger $logger;

View File

@ -1,6 +1,6 @@
<?php <?php
use App\Controller\BindAPI; use App\Service\BindAPI;
error_reporting(error_level: E_ALL & ~E_DEPRECATED); error_reporting(error_level: E_ALL & ~E_DEPRECATED);