250 lines
8.2 KiB
PHP
250 lines
8.2 KiB
PHP
<?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();
|
|
}
|
|
|
|
public function searchAlerts(string $query, int $limit = 100): array
|
|
{
|
|
$stmt = $this->db->pdo()->prepare(
|
|
"SELECT a.* FROM alerts a
|
|
JOIN alerts_fts fts ON a.id = fts.rowid
|
|
WHERE alerts_fts MATCH ?
|
|
ORDER BY rank
|
|
LIMIT ?"
|
|
);
|
|
$stmt->execute([$query, $limit]);
|
|
$rows = $stmt->fetchAll();
|
|
return array_map(fn(array $r) => Alert::fromRow($r), $rows);
|
|
}
|
|
|
|
// --- Log Entries ---
|
|
|
|
public function storeLogEntry(string $line, ?int $sourceId = null, ?string $sourceName = null, ?string $level = null): void
|
|
{
|
|
$stmt = $this->db->pdo()->prepare(
|
|
"INSERT INTO log_entries (line, source_id, source_name, level) VALUES (?, ?, ?, ?)"
|
|
);
|
|
$stmt->execute([$line, $sourceId, $sourceName, $level]);
|
|
}
|
|
|
|
public function searchLogEntries(string $query, int $limit = 200, int $offset = 0): array
|
|
{
|
|
$query = trim($query);
|
|
if ($query === '') {
|
|
return [];
|
|
}
|
|
|
|
try {
|
|
$stmt = $this->db->pdo()->prepare(
|
|
"SELECT e.* FROM log_entries e
|
|
JOIN log_entries_fts fts ON e.id = fts.rowid
|
|
WHERE log_entries_fts MATCH ?
|
|
ORDER BY rank
|
|
LIMIT ? OFFSET ?"
|
|
);
|
|
$stmt->execute([$query, $limit, $offset]);
|
|
return $stmt->fetchAll();
|
|
} catch (\PDOException $e) {
|
|
return $this->searchLogEntriesLike($query, $limit, $offset);
|
|
}
|
|
}
|
|
|
|
private function searchLogEntriesLike(string $query, int $limit = 200, int $offset = 0): array
|
|
{
|
|
$like = '%' . str_replace(['%', '_'], ['\\%', '\\_'], $query) . '%';
|
|
$stmt = $this->db->pdo()->prepare(
|
|
"SELECT e.* FROM log_entries e
|
|
WHERE e.line LIKE ?
|
|
ORDER BY e.created_at DESC
|
|
LIMIT ? OFFSET ?"
|
|
);
|
|
$stmt->execute([$like, $limit, $offset]);
|
|
return $stmt->fetchAll();
|
|
}
|
|
|
|
// --- Config ---
|
|
|
|
public function getAllowedUserTokens(): array
|
|
{
|
|
$stmt = $this->db->pdo()->prepare("SELECT value FROM config WHERE key = ?");
|
|
$stmt->execute(['allowed_user_tokens']);
|
|
$row = $stmt->fetch();
|
|
if (!$row || empty($row['value'])) {
|
|
return [];
|
|
}
|
|
return json_decode($row['value'], true) ?? [];
|
|
}
|
|
|
|
public function setAllowedUserTokens(array $tokens): void
|
|
{
|
|
$stmt = $this->db->pdo()->prepare(
|
|
"INSERT INTO config (key, value) VALUES (?, ?)
|
|
ON CONFLICT(key) DO UPDATE SET value = excluded.value"
|
|
);
|
|
$stmt->execute(['allowed_user_tokens', json_encode(array_values($tokens))]);
|
|
}
|
|
|
|
// --- 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;
|
|
}
|
|
} |