Files
2026-06-02 19:19:00 +02:00

105 lines
3.4 KiB
PHP

<?php
declare(strict_types=1);
final class RuleRepository
{
public function __construct(private string $baseDir = Settings::DATA_DIR)
{
}
/** @return array<int,array<string,mixed>> */
public function all(string $username): array
{
$data = $this->read($username);
return array_values($data['rules'] ?? []);
}
/** @return array<string,mixed>|null */
public function find(string $username, string $id): ?array
{
foreach ($this->all($username) as $rule) {
if (($rule['id'] ?? '') === $id) {
return $rule;
}
}
return null;
}
/** @param array<string,mixed> $rule @return array<string,mixed> */
public function create(string $username, array $rule): array
{
$data = $this->read($username);
$now = time();
$rule['id'] = bin2hex(random_bytes(12));
$rule['created_at'] = $now;
$rule['updated_at'] = $now;
$data['rules'][] = $rule;
$this->write($username, $data);
return $rule;
}
/** @param array<string,mixed> $patch @return array<string,mixed> */
public function update(string $username, string $id, array $patch): array
{
$data = $this->read($username);
foreach ($data['rules'] as $i => $rule) {
if (($rule['id'] ?? '') === $id) {
$updated = array_merge($rule, $patch, ['id' => $id, 'updated_at' => time()]);
$data['rules'][$i] = $updated;
$this->write($username, $data);
return $updated;
}
}
throw new RuntimeException('Rule not found');
}
public function delete(string $username, string $id): void
{
$data = $this->read($username);
$before = count($data['rules']);
$data['rules'] = array_values(array_filter($data['rules'], static fn (array $rule): bool => ($rule['id'] ?? '') !== $id));
if (count($data['rules']) === $before) {
throw new RuntimeException('Rule not found');
}
$this->write($username, $data);
}
/** @return array{rules:array<int,array<string,mixed>>} */
private function read(string $username): array
{
$path = $this->path($username);
if (!is_file($path)) {
return ['rules' => []];
}
$data = json_decode((string)file_get_contents($path), true);
return is_array($data) && isset($data['rules']) && is_array($data['rules']) ? $data : ['rules' => []];
}
/** @param array{rules:array<int,array<string,mixed>>} $data */
private function write(string $username, array $data): void
{
$path = $this->path($username);
$dir = dirname($path);
if (!is_dir($dir)) {
mkdir($dir, 0700, true);
}
$tmp = $path . '.tmp.' . getmypid();
file_put_contents($tmp, json_encode($data, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES), LOCK_EX);
@chmod($tmp, 0600);
rename($tmp, $path);
}
private function path(string $username): string
{
$this->assertUsername($username);
return rtrim($this->baseDir, '/') . '/users/' . $username . '/rules.json';
}
private function assertUsername(string $username): void
{
if (!preg_match('/^[A-Za-z0-9._-]+$/', $username)) {
throw new InvalidArgumentException('Invalid username');
}
}
}