Add Putzliga mood import

This commit is contained in:
2026-05-21 20:16:21 +02:00
parent 714198059b
commit 8ad8ca28af
6 changed files with 294 additions and 1 deletions
+108 -1
View File
@@ -40,7 +40,7 @@ final class App
$this->triggerReminderCheckFromTraffic($method, $path);
$hasUsers = $this->users->hasAnyUsers();
$isAuthenticated = $this->auth->check();
$systemPaths = ['/reminders/run', '/api/health'];
$systemPaths = ['/reminders/run', '/api/health', '/api/putzliga'];
// A failed setup must never leave the app in a half-authenticated redirect loop.
if (!$hasUsers && $isAuthenticated) {
@@ -157,6 +157,14 @@ final class App
$this->handleHealthImportStatus();
return;
case '/api/putzliga':
if ($method !== 'POST') {
json_response(['ok' => false, 'message' => 'Method Not Allowed'], 405);
}
$this->handlePutzligaImport();
return;
default:
http_response_code(404);
View::render('not-found', [
@@ -348,6 +356,81 @@ final class App
]);
}
private function handlePutzligaImport(): void
{
$token = $this->healthImportBearerToken();
if ($token === '') {
json_response(['ok' => false, 'message' => 'Bearer-Token fehlt.'], 401);
}
$user = $this->users->findByPutzligaImportToken($token);
if ($user === null) {
json_response(['ok' => false, 'message' => 'Bearer-Token ist ungültig.'], 401);
}
$payload = $this->decodeHealthImportPayload((string) file_get_contents('php://input'));
$date = (string) ($payload['date'] ?? '');
$tasksPayload = is_array($payload['tasks'] ?? null) ? $payload['tasks'] : [];
$tasks = array_values(array_filter(array_map(
static fn (mixed $task): string => trim((string) $task),
$tasksPayload
), static fn (string $task): bool => $task !== ''));
if (!$this->isValidDate($date)) {
json_response(['ok' => false, 'message' => 'Datum fehlt oder ist ungültig.'], 400);
}
if (count($tasks) < 3) {
json_response(['ok' => false, 'message' => 'Mindestens 3 erledigte Aufgaben sind nötig.'], 400);
}
$username = (string) ($user['username'] ?? '');
$settings = $this->hydrateSettings($this->settings->forUser($username));
$entries = $this->entries->all($username);
$entryMap = [];
foreach ($entries as $entry) {
if (is_array($entry) && $this->isValidDate((string) ($entry['date'] ?? ''))) {
$entryMap[(string) $entry['date']] = $entry;
}
}
$entry = $entryMap[$date] ?? $this->scoring->normalize([
'date' => $date,
'pain_enabled' => !empty($settings['tracking']['pain_enabled']),
'walk_mode' => (string) ($settings['walk']['mode'] ?? 'time'),
'summary' => ['comment' => '', 'mood' => 0, 'energy' => 0, 'stress' => 0, 'alcohol' => false],
'events' => [],
'background_image' => '',
]);
$importID = 'putzliga-' . $date;
$events = array_values(array_filter(
is_array($entry['events'] ?? null) ? $entry['events'] : [],
static fn (array $event): bool => (string) ($event['import_id'] ?? '') !== $importID
));
$events[] = [
'id' => $importID,
'type' => 'event',
'time' => (string) ($payload['time'] ?? ''),
'comment' => 'Du warst fleißig',
'value' => 0,
'unit' => '',
'mood' => 0,
'energy' => 1,
'stress' => 0,
'source' => 'putzliga',
'import_id' => $importID,
'task_titles' => array_slice(array_values(array_unique($tasks)), 0, 20),
];
$entry['events'] = $events;
$entryMap[$date] = $entry;
$this->persistUserEntries($username, $settings, array_values($entryMap));
$this->users->recordPutzligaImport($username, 'ok', count($tasks) . ' Aufgaben synchronisiert.');
json_response(['ok' => true, 'message' => 'Putzliga-Moment aktualisiert.', 'tasks' => count($tasks)]);
}
private function healthImportBearerToken(): string
{
$header = trim((string) ($_SERVER['HTTP_AUTHORIZATION'] ?? $_SERVER['REDIRECT_HTTP_AUTHORIZATION'] ?? ''));
@@ -2394,6 +2477,14 @@ final class App
$healthImportToken = null;
}
$pendingPutzligaTokens = is_array($_SESSION['_putzliga_import_token'] ?? null) ? $_SESSION['_putzliga_import_token'] : [];
$putzligaImportToken = $pendingPutzligaTokens[$user['username']] ?? null;
if (is_string($putzligaImportToken)) {
unset($_SESSION['_putzliga_import_token'][$user['username']]);
} else {
$putzligaImportToken = null;
}
$optionsOpenPanel = trim((string) ($_GET['panel'] ?? ''));
if ($optionsOpenPanel === 'score') {
$optionsOpenPanel = '';
@@ -2414,6 +2505,9 @@ final class App
'healthImportConfig' => $this->users->healthImportConfig($user['username']),
'healthImportToken' => $healthImportToken,
'healthImportUrl' => app_origin() . '/api/health',
'putzligaImportConfig' => $this->users->putzligaImportConfig($user['username']),
'putzligaImportToken' => $putzligaImportToken,
'putzligaImportUrl' => app_origin() . '/api/putzliga',
'backupAvailable' => class_exists('ZipArchive'),
'aiConfig' => $user['is_admin'] ? $this->aiConfig->get() : null,
'aiStatus' => $user['is_admin'] ? $this->openAi->configuration() : null,
@@ -2499,6 +2593,19 @@ final class App
redirect('/options?panel=health');
}
if ($form === 'putzliga_import_token') {
$token = $this->users->issuePutzligaImportToken($user['username']);
$_SESSION['_putzliga_import_token'][$user['username']] = $token;
flash('success', 'Der Putzliga-Token wurde erstellt. Kopiere ihn in Putzliga.');
redirect('/options?panel=health');
}
if ($form === 'putzliga_import_revoke') {
$this->users->revokePutzligaImportToken($user['username']);
flash('success', 'Der Putzliga-Token wurde deaktiviert.');
redirect('/options?panel=health');
}
if ($form === 'password') {
$current = (string) ($_POST['current_password'] ?? '');
$new = (string) ($_POST['new_password'] ?? '');