Add PWA reminders and flexible walk scoring

This commit is contained in:
2026-04-12 19:40:40 +02:00
parent 2cd00b1bf6
commit cd7526bd80
19 changed files with 1561 additions and 33 deletions
+149
View File
@@ -0,0 +1,149 @@
<?php
declare(strict_types=1);
final class NotificationRepository
{
private string $systemPath;
public function __construct()
{
$this->systemPath = storage_path('system/notifications.json');
}
public function systemConfig(): array
{
$config = decode_json_file($this->systemPath, []);
$changed = false;
if (!isset($config['cron_token']) || !is_string($config['cron_token']) || $config['cron_token'] === '') {
$config['cron_token'] = bin2hex(random_bytes(24));
$changed = true;
}
if (!isset($config['subject']) || !is_string($config['subject']) || $config['subject'] === '') {
$host = parse_url(app_origin(), PHP_URL_HOST);
$host = is_string($host) && $host !== '' ? $host : 'localhost';
$config['subject'] = 'mailto:hello@' . $host;
$changed = true;
}
if ($changed) {
$this->writeJson($this->systemPath, $config);
}
return $config;
}
public function saveVapidKeys(string $publicKey, string $privateKey): void
{
$config = $this->systemConfig();
$config['vapid_public_key'] = $publicKey;
$config['vapid_private_key'] = $privateKey;
$this->writeJson($this->systemPath, $config);
}
public function subscriptionsForUser(string $username): array
{
$payload = decode_json_file($this->subscriptionsPath($username), ['subscriptions' => []]);
return array_values(array_filter($payload['subscriptions'] ?? [], 'is_array'));
}
public function saveSubscription(string $username, array $subscription): void
{
$endpoint = trim((string) ($subscription['endpoint'] ?? ''));
if ($endpoint === '') {
throw new RuntimeException('Die Subscription ist unvollständig.');
}
$subscriptions = $this->subscriptionsForUser($username);
$saved = false;
foreach ($subscriptions as &$entry) {
if (($entry['endpoint'] ?? '') === $endpoint) {
$entry = array_merge($entry, $subscription, [
'updated_at' => date(DATE_ATOM),
]);
$saved = true;
break;
}
}
unset($entry);
if (!$saved) {
$subscription['created_at'] = date(DATE_ATOM);
$subscription['updated_at'] = date(DATE_ATOM);
$subscriptions[] = $subscription;
}
$this->writeJson($this->subscriptionsPath($username), ['subscriptions' => array_values($subscriptions)]);
}
public function removeSubscription(string $username, string $endpoint): void
{
$endpoint = trim($endpoint);
if ($endpoint === '') {
return;
}
$subscriptions = array_values(array_filter(
$this->subscriptionsForUser($username),
static fn (array $entry): bool => ($entry['endpoint'] ?? '') !== $endpoint
));
$this->writeJson($this->subscriptionsPath($username), ['subscriptions' => $subscriptions]);
}
public function removeInvalidSubscriptions(string $username, array $endpoints): void
{
foreach ($endpoints as $endpoint) {
if (is_string($endpoint) && $endpoint !== '') {
$this->removeSubscription($username, $endpoint);
}
}
}
public function reminderState(string $username): array
{
return decode_json_file($this->reminderStatePath($username), []);
}
public function saveReminderState(string $username, array $state): void
{
$this->writeJson($this->reminderStatePath($username), $state);
}
public function subscriptionCount(string $username): int
{
return count($this->subscriptionsForUser($username));
}
private function subscriptionsPath(string $username): string
{
return storage_path('users/' . normalize_username($username) . '/push-subscriptions.json');
}
private function reminderStatePath(string $username): string
{
return storage_path('users/' . normalize_username($username) . '/notification-state.json');
}
private function writeJson(string $path, array $payload): void
{
$directory = dirname($path);
if (!is_dir($directory)) {
mkdir($directory, 0775, true);
}
$bytes = file_put_contents(
$path,
json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES),
LOCK_EX
);
if ($bytes === false) {
throw new RuntimeException('Die Benachrichtigungsdaten konnten nicht gespeichert werden.');
}
}
}