initial commit

This commit is contained in:
2026-05-06 11:28:41 +02:00
commit 39ee31debb
23 changed files with 1645 additions and 0 deletions
+87
View File
@@ -0,0 +1,87 @@
<?php
namespace Jakach\Logging\Storage;
class Database
{
private \PDO $pdo;
public function __construct(string $path = '/app/data/logging.db')
{
$dir = dirname($path);
if (!is_dir($dir)) {
mkdir($dir, 0755, true);
}
$this->pdo = new \PDO("sqlite:$path");
$this->pdo->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
$this->pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
$this->pdo->exec('PRAGMA journal_mode=WAL');
$this->migrate();
}
public function pdo(): \PDO
{
return $this->pdo;
}
private function migrate(): void
{
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS log_sources (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
type TEXT NOT NULL,
address TEXT NOT NULL,
labels TEXT DEFAULT '[]',
active INTEGER DEFAULT 1,
created_at TEXT DEFAULT (datetime('now'))
)
");
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS rules (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
pattern TEXT NOT NULL,
severity TEXT NOT NULL DEFAULT 'warning',
rate_limit_seconds INTEGER,
active INTEGER DEFAULT 1,
created_at TEXT DEFAULT (datetime('now'))
)
");
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS alerts (
id INTEGER PRIMARY KEY AUTOINCREMENT,
rule_id INTEGER NOT NULL,
rule_name TEXT NOT NULL,
severity TEXT NOT NULL,
status TEXT NOT NULL DEFAULT 'open',
message TEXT NOT NULL,
raw_line TEXT NOT NULL,
source_id INTEGER,
source_name TEXT,
created_at TEXT DEFAULT (datetime('now')),
FOREIGN KEY (rule_id) REFERENCES rules(id)
)
");
$this->pdo->exec("
CREATE INDEX IF NOT EXISTS idx_alerts_status ON alerts(status)
");
$this->pdo->exec("
CREATE INDEX IF NOT EXISTS idx_alerts_created ON alerts(created_at)
");
$this->pdo->exec("
CREATE TABLE IF NOT EXISTS rate_limiter (
rule_id INTEGER NOT NULL,
window_start INTEGER NOT NULL,
count INTEGER DEFAULT 0,
PRIMARY KEY (rule_id, window_start)
)
");
}
}
+169
View File
@@ -0,0 +1,169 @@
<?php
namespace Jakach\Logging\Storage;
use Jakach\Logging\Model\{LogSource, Rule, Alert, AlertStatus, LogSourceType};
class Repository
{
public function __construct(
private Database $db,
) {}
// --- Log Sources ---
public function getSources(): array
{
$rows = $this->db->pdo()->query("SELECT * FROM log_sources ORDER BY name")->fetchAll();
return array_map(fn(array $r) => LogSource::fromRow($r), $rows);
}
public function getActiveSources(): array
{
$rows = $this->db->pdo()->query("SELECT * FROM log_sources WHERE active = 1 ORDER BY name")->fetchAll();
return array_map(fn(array $r) => LogSource::fromRow($r), $rows);
}
public function getSource(int $id): ?LogSource
{
$stmt = $this->db->pdo()->prepare("SELECT * FROM log_sources WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch();
return $row ? LogSource::fromRow($row) : null;
}
public function createSource(string $name, LogSourceType $type, string $address, array $labels = []): LogSource
{
$stmt = $this->db->pdo()->prepare(
"INSERT INTO log_sources (name, type, address, labels) VALUES (?, ?, ?, ?)"
);
$stmt->execute([$name, $type->value, $address, json_encode($labels)]);
return $this->getSource((int) $this->db->pdo()->lastInsertId());
}
public function deleteSource(int $id): void
{
$this->db->pdo()->prepare("DELETE FROM log_sources WHERE id = ?")->execute([$id]);
}
// --- Rules ---
public function getRules(): array
{
$rows = $this->db->pdo()->query("SELECT * FROM rules ORDER BY name")->fetchAll();
return array_map(fn(array $r) => Rule::fromRow($r), $rows);
}
public function getActiveRules(): array
{
$rows = $this->db->pdo()->query("SELECT * FROM rules WHERE active = 1 ORDER BY name")->fetchAll();
return array_map(fn(array $r) => Rule::fromRow($r), $rows);
}
public function getRule(int $id): ?Rule
{
$stmt = $this->db->pdo()->prepare("SELECT * FROM rules WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch();
return $row ? Rule::fromRow($row) : null;
}
public function createRule(string $name, string $pattern, string $severity, ?int $rateLimitSeconds = null): Rule
{
$stmt = $this->db->pdo()->prepare(
"INSERT INTO rules (name, pattern, severity, rate_limit_seconds) VALUES (?, ?, ?, ?)"
);
$stmt->execute([$name, $pattern, $severity, $rateLimitSeconds]);
return $this->getRule((int) $this->db->pdo()->lastInsertId());
}
public function deleteRule(int $id): void
{
$this->db->pdo()->prepare("DELETE FROM rules WHERE id = ?")->execute([$id]);
}
// --- Alerts ---
public function createAlert(int $ruleId, string $ruleName, string $severity, string $message, string $rawLine, ?int $sourceId = null, ?string $sourceName = null): Alert
{
$stmt = $this->db->pdo()->prepare(
"INSERT INTO alerts (rule_id, rule_name, severity, status, message, raw_line, source_id, source_name)
VALUES (?, ?, ?, 'open', ?, ?, ?, ?)"
);
$stmt->execute([$ruleId, $ruleName, $severity, $message, $rawLine, $sourceId, $sourceName]);
$id = (int) $this->db->pdo()->lastInsertId();
return $this->getAlert($id);
}
public function getAlert(int $id): ?Alert
{
$stmt = $this->db->pdo()->prepare("SELECT * FROM alerts WHERE id = ?");
$stmt->execute([$id]);
$row = $stmt->fetch();
return $row ? Alert::fromRow($row) : null;
}
public function getAlerts(int $limit = 100, int $offset = 0, ?string $status = null, ?string $severity = null): array
{
$where = [];
$params = [];
if ($status) {
$where[] = 'status = ?';
$params[] = $status;
}
if ($severity) {
$where[] = 'severity = ?';
$params[] = $severity;
}
$sql = "SELECT * FROM alerts";
if ($where) {
$sql .= ' WHERE ' . implode(' AND ', $where);
}
$sql .= " ORDER BY created_at DESC LIMIT ? OFFSET ?";
$params[] = $limit;
$params[] = $offset;
$stmt = $this->db->pdo()->prepare($sql);
$stmt->execute($params);
$rows = $stmt->fetchAll();
return array_map(fn(array $r) => Alert::fromRow($r), $rows);
}
public function updateAlertStatus(int $id, AlertStatus $status): void
{
$stmt = $this->db->pdo()->prepare("UPDATE alerts SET status = ? WHERE id = ?");
$stmt->execute([$status->value, $id]);
}
public function getAlertCounts(): array
{
return $this->db->pdo()->query(
"SELECT status, severity, COUNT(*) as count FROM alerts GROUP BY status, severity"
)->fetchAll();
}
// --- Rate Limiting ---
public function checkRateLimit(int $ruleId, int $windowSeconds): bool
{
$now = time();
$window = intdiv($now, $windowSeconds) * $windowSeconds;
$this->db->pdo()->prepare(
"INSERT INTO rate_limiter (rule_id, window_start, count)
VALUES (?, ?, 1)
ON CONFLICT(rule_id, window_start) DO UPDATE SET count = count + 1"
)->execute([$ruleId, $window]);
$stmt = $this->db->pdo()->prepare(
"SELECT count FROM rate_limiter WHERE rule_id = ? AND window_start = ?"
);
$stmt->execute([$ruleId, $window]);
$row = $stmt->fetch();
return $row['count'] <= 1;
}
}