feat: complete jobcard client management foundation
This commit is contained in:
@@ -0,0 +1,130 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
require_once dirname(__DIR__) . '/config/bootstrap.php';
|
||||
|
||||
/**
|
||||
* Deployment checks are kept as small functions so they can be tested without
|
||||
* loading application state or requiring a live database.
|
||||
*
|
||||
* @return array<string, bool>
|
||||
*/
|
||||
function deployment_check_extensions(array $required, ?callable $loader = null): array
|
||||
{
|
||||
$loader ??= static fn(string $extension): bool => extension_loaded($extension);
|
||||
$result = [];
|
||||
foreach ($required as $extension) {
|
||||
$result[(string)$extension] = (bool)$loader((string)$extension);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool>
|
||||
*/
|
||||
function deployment_check_environment(array $required, ?callable $reader = null): array
|
||||
{
|
||||
$reader ??= static fn(string $name): mixed => getenv($name);
|
||||
$result = [];
|
||||
foreach ($required as $name) {
|
||||
$value = $reader((string)$name);
|
||||
$result[(string)$name] = $value !== false && $value !== null && trim((string)$value) !== '';
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, bool>
|
||||
*/
|
||||
function deployment_check_directories(array $directories, ?callable $checker = null): array
|
||||
{
|
||||
$checker ??= static fn(string $directory): bool => is_dir($directory) && is_writable($directory);
|
||||
$result = [];
|
||||
foreach ($directories as $directory) {
|
||||
$result[(string)$directory] = (bool)$checker((string)$directory);
|
||||
}
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a deliberately value-free report suitable for CLI output or logging.
|
||||
*
|
||||
* @param array<string, bool> $checks
|
||||
* @return array<string, string>
|
||||
*/
|
||||
function deployment_format_check_report(array $checks): array
|
||||
{
|
||||
$report = [];
|
||||
foreach ($checks as $name => $passed) {
|
||||
$report[$name] = $passed ? 'OK' : 'FAIL';
|
||||
}
|
||||
return $report;
|
||||
}
|
||||
|
||||
function deployment_check_schema(PDO $pdo): bool
|
||||
{
|
||||
foreach (['roles', 'permissions', 'role_permissions', 'users', 'clients', 'client_contacts', 'jobcard_sequences', 'technical_information', 'credentials', 'sla_agreements', 'jobcards', 'jobcard_assignments', 'jobcard_status_history', 'time_entries', 'attachments', 'notifications', 'audit_events'] as $table) {
|
||||
$quoted = '`' . str_replace('`', '``', $table) . '`';
|
||||
$pdo->query("SELECT 1 FROM {$quoted} LIMIT 1");
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function deployment_print_check(string $label, bool $passed): void
|
||||
{
|
||||
printf("[%s] %s\n", $passed ? 'OK' : 'FAIL', $label);
|
||||
}
|
||||
|
||||
function deployment_run_healthcheck(): int
|
||||
{
|
||||
$requiredExtensions = ['pdo_mysql', 'mbstring', 'openssl', 'sodium', 'json', 'fileinfo'];
|
||||
$requiredEnvironment = [
|
||||
'APP_ENV', 'APP_KEY', 'DB_HOST', 'DB_PORT', 'DB_DATABASE',
|
||||
'DB_USERNAME', 'DB_PASSWORD', 'ADMIN_EMAIL', 'ADMIN_PASSWORD',
|
||||
];
|
||||
$runtimeDirectories = [
|
||||
dirname(__DIR__) . '/storage',
|
||||
dirname(__DIR__) . '/storage/logs',
|
||||
dirname(__DIR__) . '/storage/uploads',
|
||||
];
|
||||
|
||||
fwrite(STDOUT, "JOBcard deployment health check\n");
|
||||
$allPassed = true;
|
||||
|
||||
foreach (deployment_format_check_report(deployment_check_extensions($requiredExtensions)) as $extension => $status) {
|
||||
$passed = $status === 'OK';
|
||||
deployment_print_check("PHP extension: {$extension}", $passed);
|
||||
$allPassed = $allPassed && $passed;
|
||||
}
|
||||
foreach (deployment_format_check_report(deployment_check_environment($requiredEnvironment)) as $name => $status) {
|
||||
$passed = $status === 'OK';
|
||||
// Only the variable name and status are emitted; values are never printed.
|
||||
deployment_print_check("Environment variable: {$name}", $passed);
|
||||
$allPassed = $allPassed && $passed;
|
||||
}
|
||||
foreach (deployment_format_check_report(deployment_check_directories($runtimeDirectories)) as $directory => $status) {
|
||||
$passed = $status === 'OK';
|
||||
deployment_print_check("Writable runtime directory: {$directory}", $passed);
|
||||
$allPassed = $allPassed && $passed;
|
||||
}
|
||||
|
||||
try {
|
||||
require_once dirname(__DIR__) . '/app/Domain/Credential/TechnicalInformation.php';
|
||||
require_once dirname(__DIR__) . '/app/Domain/Credential/CredentialVault.php';
|
||||
new \App\Domain\Credential\CredentialVault((string)getenv('APP_KEY'));
|
||||
require_once dirname(__DIR__) . '/config/bootstrap.php';
|
||||
deployment_check_schema(db());
|
||||
deployment_print_check('Database connection and schema: core tables available', true);
|
||||
} catch (Throwable) {
|
||||
// Database exception text can contain infrastructure details; keep the check output safe.
|
||||
deployment_print_check('Database connection and schema: roles table available', false);
|
||||
$allPassed = false;
|
||||
}
|
||||
|
||||
fwrite(STDOUT, $allPassed ? "Health check passed.\n" : "Health check failed.\n");
|
||||
return $allPassed ? 0 : 1;
|
||||
}
|
||||
|
||||
if (realpath($_SERVER['SCRIPT_FILENAME'] ?? '') === __FILE__) {
|
||||
exit(deployment_run_healthcheck());
|
||||
}
|
||||
Reference in New Issue
Block a user