asdsad
All checks were successful
Deploy / deploy-staging (push) Successful in 5s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-04-30 03:37:11 +02:00
parent e1b2f7e613
commit 10dc9bb0a7
4 changed files with 345 additions and 85 deletions

View File

@@ -10,13 +10,16 @@ final class BaseSchema
$driver = (string)$pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
if ($driver === 'pgsql') {
self::ensurePgsql($pdo);
self::seedTimezones($pdo);
return;
}
if ($driver === 'sqlite') {
self::ensureSqlite($pdo);
self::seedTimezones($pdo);
return;
}
self::ensureGeneric($pdo);
self::seedTimezones($pdo);
}
private static function ensurePgsql(\PDO $pdo): void
@@ -136,6 +139,15 @@ final class BaseSchema
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)"
);
$pdo->exec(
"CREATE TABLE IF NOT EXISTS nexus_timezones (
identifier TEXT PRIMARY KEY,
label TEXT NOT NULL,
group_name TEXT NULL,
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
)"
);
}
private static function ensureSqlite(\PDO $pdo): void
@@ -255,6 +267,15 @@ final class BaseSchema
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
)"
);
$pdo->exec(
"CREATE TABLE IF NOT EXISTS nexus_timezones (
identifier TEXT PRIMARY KEY,
label TEXT NOT NULL,
group_name TEXT NULL,
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
)"
);
}
private static function ensureGeneric(\PDO $pdo): void
@@ -374,5 +395,66 @@ final class BaseSchema
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)"
);
$pdo->exec(
"CREATE TABLE IF NOT EXISTS nexus_timezones (
identifier VARCHAR(190) PRIMARY KEY,
label VARCHAR(255) NOT NULL,
group_name VARCHAR(190) NULL,
updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP
)"
);
}
private static function seedTimezones(\PDO $pdo): void
{
try {
$count = (int) $pdo->query("SELECT COUNT(*) FROM nexus_timezones")->fetchColumn();
} catch (\Throwable) {
return;
}
if ($count > 0) {
return;
}
$driver = (string) $pdo->getAttribute(\PDO::ATTR_DRIVER_NAME);
$sql = match ($driver) {
'pgsql' => "INSERT INTO nexus_timezones (identifier, label, group_name, updated_at)
VALUES (:identifier, :label, :group_name, NOW())
ON CONFLICT (identifier) DO UPDATE SET
label = EXCLUDED.label,
group_name = EXCLUDED.group_name,
updated_at = NOW()",
'sqlite' => "INSERT INTO nexus_timezones (identifier, label, group_name, updated_at)
VALUES (:identifier, :label, :group_name, datetime('now'))
ON CONFLICT(identifier) DO UPDATE SET
label = excluded.label,
group_name = excluded.group_name,
updated_at = datetime('now')",
default => "INSERT INTO nexus_timezones (identifier, label, group_name, updated_at)
VALUES (:identifier, :label, :group_name, CURRENT_TIMESTAMP)
ON DUPLICATE KEY UPDATE
label = VALUES(label),
group_name = VALUES(group_name),
updated_at = CURRENT_TIMESTAMP",
};
$stmt = $pdo->prepare($sql);
foreach (timezone_identifiers_list() as $identifier) {
$parts = explode('/', $identifier, 2);
$group = $parts[0] ?? 'Other';
$label = str_replace('_', ' ', $identifier);
if ($identifier === 'UTC') {
$group = 'UTC';
$label = 'UTC';
}
$stmt->execute([
'identifier' => $identifier,
'label' => $label,
'group_name' => $group,
]);
}
}
}

View File

@@ -75,15 +75,16 @@ final class ModuleCronScheduler
try {
$cron = CronExpression::parse($expression);
$previousDueUtc = $cron->previousRun($nowUtc, $timezone);
$lastScheduledFor = $this->parseUtc((string) ($state['last_scheduled_for'] ?? ''));
$isDue = !empty($config['enabled'])
&& !$isLocked
&& $previousDueUtc instanceof DateTimeImmutable
&& ($lastScheduledFor === null || $previousDueUtc > $lastScheduledFor);
$nextDueUtc = $isDue
? $previousDueUtc
: $cron->nextRun($nowUtc, $timezone);
if (!empty($config['enabled'])) {
$previousDueUtc = $cron->previousRun($nowUtc, $timezone);
$lastScheduledFor = $this->parseUtc((string) ($state['last_scheduled_for'] ?? ''));
$isDue = !$isLocked
&& $previousDueUtc instanceof DateTimeImmutable
&& ($lastScheduledFor === null || $previousDueUtc > $lastScheduledFor);
$nextDueUtc = $isDue
? $previousDueUtc
: $cron->nextRun($nowUtc, $timezone);
}
} catch (\Throwable $exception) {
$parseError = $exception->getMessage();
}
@@ -94,6 +95,8 @@ final class ModuleCronScheduler
'state_key' => $stateKey,
'config' => $config,
'state' => $state,
'last_started_at_local' => ($this->parseUtc((string) ($state['last_started_at'] ?? '')))?->setTimezone($timezone)->format('Y-m-d H:i:s'),
'last_success_at_local' => ($this->parseUtc((string) ($state['last_success_at'] ?? '')))?->setTimezone($timezone)->format('Y-m-d H:i:s'),
'enabled' => !empty($config['enabled']),
'cron_expression' => $expression,
'timezone' => $timezone->getName(),

View File

@@ -128,6 +128,42 @@ final class ModuleManager
]);
}
public function timezones(): array
{
if (!$this->basePdo) {
return $this->fallbackTimezones();
}
try {
$stmt = $this->basePdo->query(
"SELECT identifier, label, group_name
FROM nexus_timezones
ORDER BY group_name ASC, identifier ASC"
);
$rows = $stmt ? $stmt->fetchAll(\PDO::FETCH_ASSOC) : [];
if (!is_array($rows) || $rows === []) {
return $this->fallbackTimezones();
}
return array_values(array_filter(array_map(
static function (array $row): ?array {
$identifier = trim((string) ($row['identifier'] ?? ''));
if ($identifier === '') {
return null;
}
return [
'value' => $identifier,
'label' => trim((string) ($row['label'] ?? $identifier)),
'group' => trim((string) ($row['group_name'] ?? '')),
];
},
$rows
)));
} catch (\Throwable) {
return $this->fallbackTimezones();
}
}
public function modulePdo(string $name, array $fallback = []): ?\PDO
{
$settings = $this->settings($name);
@@ -358,6 +394,20 @@ final class ModuleManager
return $enabledByDefault;
}
private function fallbackTimezones(): array
{
$result = [];
foreach (timezone_identifiers_list() as $identifier) {
$parts = explode('/', $identifier, 2);
$result[] = [
'value' => $identifier,
'label' => $identifier === 'UTC' ? 'UTC' : str_replace('_', ' ', $identifier),
'group' => $parts[0] ?? 'Other',
];
}
return $result;
}
public function saveAuth(string $name, array $auth): array
{
if (!preg_match('/^[a-zA-Z0-9_\-]+$/', $name)) {