105 lines
3.4 KiB
PHP
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');
|
|
}
|
|
}
|
|
}
|