diff --git a/.gitignore b/.gitignore index 6ee131a..523cd3b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,6 @@ -<<<<<<< HEAD +.codex-temp/ php-server.log php-server.err.log php-server-error.log -======= -.codex-temp/ -php-server-error.log ->>>>>>> fc3264cc2fa76249c40e422de2381eb7c6920c28 +__pycache__/ +*.pyc diff --git a/docs/README.md b/docs/README.md index 531a1b6..467e0bf 100644 --- a/docs/README.md +++ b/docs/README.md @@ -10,6 +10,8 @@ Hier liegen die internen Anleitungen und technischen Hilfen für das Projekt. Lokale Startanleitung für den PHP-Webserver auf den bekannten Arbeitsplätzen - [`docs/README_RECHTESYSTEM.md`](README_RECHTESYSTEM.md) Beschreibung des aktuellen Rollen- und Seitenrechtesystems +- [`docs/README_DATENBANKABFRAGEN.md`](README_DATENBANKABFRAGEN.md) + Kurzanleitung fuer lokale Datenbankabfragen mit der vorhandenen Projektkonfiguration ## Hinweis diff --git a/docs/README_DATENBANKABFRAGEN.md b/docs/README_DATENBANKABFRAGEN.md new file mode 100644 index 0000000..853089d --- /dev/null +++ b/docs/README_DATENBANKABFRAGEN.md @@ -0,0 +1,83 @@ +# Datenbankabfragen + +Diese Notiz beschreibt, wie in diesem Projekt lokale Datenbankabfragen ausgefuehrt werden koennen. + +## Zugangsdaten + +Die Verbindungsdaten liegen in: + +- `config/database.php` + +Dort stehen `host`, `dbname`, `user`, `pass` und `charset`. + +## Empfohlener Weg + +Am einfachsten ist eine kurze PHP-Datei oder ein kurzes PHP-Skript, das die vorhandene Projektkonfiguration verwendet. + +Beispiel: + +```php + PDO::ERRMODE_EXCEPTION, + PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, + ] +); + +$stmt = $pdo->query("SELECT id, mp_number, designation_text FROM minorplanets_mpc WHERE mp_number = 1 LIMIT 1"); +$row = $stmt->fetch(); + +echo json_encode($row, JSON_PRETTY_PRINT | JSON_UNESCAPED_UNICODE | JSON_UNESCAPED_SLASHES); +``` + +## Ausfuehrung + +Wenn `php` nicht im Systempfad liegt, kann die lokale `php.exe` direkt verwendet werden. + +Beispiel von diesem Rechner: + +```powershell +C:\laragon\bin\php\php-8.4.5-nts-Win32-vs17-x64\php.exe .codex-temp\check_defaults.php +``` + +## Praktische Beispielabfragen + +`Ceres` pruefen: + +```sql +SELECT id, mp_number, designation_text, desig_packed +FROM minorplanets_mpc +WHERE mp_number = 1 +LIMIT 1; +``` + +`Halley` pruefen: + +```sql +SELECT id, designation_and_name, orbit_type +FROM comets_mpc +WHERE designation_and_name LIKE '%Halley%' +ORDER BY designation_and_name ASC, id ASC +LIMIT 10; +``` + +## Hinweise + +- Temporaere Pruefskripte am besten in `.codex-temp/` anlegen und danach wieder loeschen. +- Fuer Projektcode immer die vorhandene `config/database.php` verwenden und keine Zugangsdaten hart in Dateien eintragen. +- Wenn nur geprueft werden soll, reichen `SELECT`-Abfragen ohne schreibende Operationen. diff --git a/public/my_favorites.php b/public/my_favorites.php index e66f49a..89a7f29 100644 --- a/public/my_favorites.php +++ b/public/my_favorites.php @@ -358,18 +358,26 @@ function renderTrackedMinorplanetsSection(array $trackedMinorplanets, bool $oob -
- - - -
-
- - - -
+ + Favorit bleibt gesetzt + +
+ + + +
+ + 1): ?> +
+ + + +
+ + Letzter Kleinplanet + @@ -470,18 +478,26 @@ function renderTrackedCometsSection(array $trackedComets, bool $oob = false): st -
- - - -
-
- - - -
+ + Favorit bleibt gesetzt + +
+ + + +
+ + 1): ?> +
+ + + +
+ + Letzter Komet + @@ -566,6 +582,35 @@ function setExclusiveFavorite(PDO $pdo, string $tableName, string $idColumn, int ':entity_id' => $entityId, ]); + if ($newValue === 0) { + $stmtNextFavorite = $pdo->prepare(" + SELECT `{$idColumn}` + FROM `{$tableName}` + WHERE `user_id` = :user_id + AND `{$idColumn}` <> :entity_id + ORDER BY `created_at` ASC, `{$idColumn}` ASC + LIMIT 1 + "); + $stmtNextFavorite->execute([ + ':user_id' => $userId, + ':entity_id' => $entityId, + ]); + $nextEntityId = $stmtNextFavorite->fetchColumn(); + + if ($nextEntityId !== false) { + $stmtPromote = $pdo->prepare(" + UPDATE `{$tableName}` + SET `is_favorite` = 1 + WHERE `user_id` = :user_id + AND `{$idColumn}` = :entity_id + "); + $stmtPromote->execute([ + ':user_id' => $userId, + ':entity_id' => (int) $nextEntityId, + ]); + } + } + $pdo->commit(); return true; } catch (Throwable $e) { @@ -590,6 +635,51 @@ function countUserRows(PDO $pdo, string $tableName, int $userId): int return (int) $stmtCount->fetchColumn(); } +function ensureFavoriteIfNeeded(PDO $pdo, string $tableName, string $idColumn, int $userId): void +{ + $stmtFavoriteCount = $pdo->prepare(" + SELECT COUNT(*) + FROM `{$tableName}` + WHERE `user_id` = :user_id + AND `is_favorite` = 1 + "); + $stmtFavoriteCount->execute([ + ':user_id' => $userId, + ]); + $favoriteCount = (int) $stmtFavoriteCount->fetchColumn(); + + if ($favoriteCount > 0) { + return; + } + + $stmtNextFavorite = $pdo->prepare(" + SELECT `{$idColumn}` + FROM `{$tableName}` + WHERE `user_id` = :user_id + ORDER BY `created_at` ASC, `{$idColumn}` ASC + LIMIT 1 + "); + $stmtNextFavorite->execute([ + ':user_id' => $userId, + ]); + $nextEntityId = $stmtNextFavorite->fetchColumn(); + + if ($nextEntityId === false) { + return; + } + + $stmtPromote = $pdo->prepare(" + UPDATE `{$tableName}` + SET `is_favorite` = 1 + WHERE `user_id` = :user_id + AND `{$idColumn}` = :entity_id + "); + $stmtPromote->execute([ + ':user_id' => $userId, + ':entity_id' => (int) $nextEntityId, + ]); +} + function validateLocationForm(array $input, array &$errors): array { $name = trim((string) ($input['name'] ?? '')); @@ -879,6 +969,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $errors[] = 'Ungültiger Kleinplanet.'; } + if (empty($errors)) { + $minorplanetCount = countUserRows($pdo, 'app_user_minorplanets', $userId); + if ($minorplanetCount <= 1) { + $errors[] = 'Der letzte Kleinplanet kann nicht gelöscht werden.'; + } + } + if (empty($errors)) { $stmtDelete = $pdo->prepare(" DELETE FROM `app_user_minorplanets` @@ -901,6 +998,32 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $errors[] = 'Ungültiger Kleinplanet.'; } + if (empty($errors)) { + $stmtCurrentMinorplanet = $pdo->prepare(" + SELECT `is_favorite` + FROM `app_user_minorplanets` + WHERE `user_id` = :user_id + AND `minorplanet_id` = :minorplanet_id + LIMIT 1 + "); + $stmtCurrentMinorplanet->execute([ + ':user_id' => $userId, + ':minorplanet_id' => $minorplanetId, + ]); + $currentMinorplanetFavorite = $stmtCurrentMinorplanet->fetchColumn(); + + if ($currentMinorplanetFavorite === false) { + $errors[] = 'Der Kleinplanet ist nicht in deiner Liste vorhanden.'; + } + } + + if (empty($errors)) { + $minorplanetCount = countUserRows($pdo, 'app_user_minorplanets', $userId); + if ($minorplanetCount <= 1 && (int) $currentMinorplanetFavorite === 1) { + $errors[] = 'Beim letzten Kleinplaneten kann der Favorit nicht entfernt werden.'; + } + } + if (empty($errors)) { if (!setExclusiveFavorite($pdo, 'app_user_minorplanets', 'minorplanet_id', $userId, $minorplanetId)) { $errors[] = 'Der Kleinplanet ist nicht in deiner Liste vorhanden.'; @@ -965,6 +1088,13 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $errors[] = 'Ungueltiger Komet.'; } + if (empty($errors)) { + $cometCount = countUserRows($pdo, 'app_user_comets', $userId); + if ($cometCount <= 1) { + $errors[] = 'Der letzte Komet kann nicht gelöscht werden.'; + } + } + if (empty($errors)) { $stmtDelete = $pdo->prepare(" DELETE FROM `app_user_comets` @@ -987,6 +1117,32 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { $errors[] = 'Ungueltiger Komet.'; } + if (empty($errors)) { + $stmtCurrentComet = $pdo->prepare(" + SELECT `is_favorite` + FROM `app_user_comets` + WHERE `user_id` = :user_id + AND `comet_id` = :comet_id + LIMIT 1 + "); + $stmtCurrentComet->execute([ + ':user_id' => $userId, + ':comet_id' => $cometId, + ]); + $currentCometFavorite = $stmtCurrentComet->fetchColumn(); + + if ($currentCometFavorite === false) { + $errors[] = 'Der Komet ist nicht in deiner Liste vorhanden.'; + } + } + + if (empty($errors)) { + $cometCount = countUserRows($pdo, 'app_user_comets', $userId); + if ($cometCount <= 1 && (int) $currentCometFavorite === 1) { + $errors[] = 'Beim letzten Kometen kann der Favorit nicht entfernt werden.'; + } + } + if (empty($errors)) { if (!setExclusiveFavorite($pdo, 'app_user_comets', 'comet_id', $userId, $cometId)) { $errors[] = 'Der Komet ist nicht in deiner Liste vorhanden.'; @@ -1329,6 +1485,11 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { } } +if ($_SERVER['REQUEST_METHOD'] === 'POST') { + ensureFavoriteIfNeeded($pdo, 'app_user_minorplanets', 'minorplanet_id', $userId); + ensureFavoriteIfNeeded($pdo, 'app_user_comets', 'comet_id', $userId); +} + $stmtLocations = $pdo->prepare(" SELECT `id`, @@ -1987,18 +2148,26 @@ $loggedIn = true; -
- - - -
-
- - - -
+ + Favorit bleibt gesetzt + +
+ + + +
+ + 1): ?> +
+ + + +
+ + Letzter Kleinplanet + diff --git a/public/register.php b/public/register.php index 8d4781b..a8a27e6 100644 --- a/public/register.php +++ b/public/register.php @@ -33,6 +33,8 @@ const DEFAULT_LOCATION_LONGITUDE = 11.189; const DEFAULT_LOCATION_ELEVATION = 640.0; const DEFAULT_LOCATION_TIMEZONE = 'Europe/Berlin'; const DEFAULT_SATELLITE_ID = 61; +const DEFAULT_MINORPLANET_NUMBER = 1; +const DEFAULT_COMET_SEARCH = 'Halley'; function sendRegistrationNotification(string $username, string $email, ?string $displayName): void { @@ -77,6 +79,39 @@ function textLength(string $value): int return strlen($value); } +function findDefaultMinorplanetId(PDO $pdo): ?int +{ + $stmt = $pdo->prepare(" + SELECT `id` + FROM `minorplanets_mpc` + WHERE `mp_number` = :mp_number + LIMIT 1 + "); + $stmt->execute([ + ':mp_number' => DEFAULT_MINORPLANET_NUMBER, + ]); + $id = $stmt->fetchColumn(); + + return $id !== false ? (int) $id : null; +} + +function findDefaultCometId(PDO $pdo): ?int +{ + $stmt = $pdo->prepare(" + SELECT `id` + FROM `comets_mpc` + WHERE `designation_and_name` LIKE :search + ORDER BY `designation_and_name` ASC, `id` ASC + LIMIT 1 + "); + $stmt->execute([ + ':search' => '%' . DEFAULT_COMET_SEARCH . '%', + ]); + $id = $stmt->fetchColumn(); + + return $id !== false ? (int) $id : null; +} + if ($_SERVER['REQUEST_METHOD'] === 'POST') { $username = trim($_POST['username'] ?? ''); $email = trim($_POST['email'] ?? ''); @@ -131,6 +166,19 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { if (empty($errors)) { $passwordHash = password_hash($password, PASSWORD_DEFAULT); + $defaultMinorplanetId = findDefaultMinorplanetId($pdo); + $defaultCometId = findDefaultCometId($pdo); + + if ($defaultMinorplanetId === null) { + $errors[] = 'Der Standard-Kleinplanet Ceres wurde in der Datenbank nicht gefunden.'; + } + + if ($defaultCometId === null) { + $errors[] = 'Der Standard-Komet Halley wurde in der Datenbank nicht gefunden.'; + } + } + + if (empty($errors)) { $pdo->beginTransaction(); try { @@ -215,6 +263,42 @@ if ($_SERVER['REQUEST_METHOD'] === 'POST') { ':satellite_id' => DEFAULT_SATELLITE_ID, ]); + $stmtInsertMinorplanet = $pdo->prepare(" + INSERT INTO `app_user_minorplanets` ( + `user_id`, + `minorplanet_id`, + `is_favorite`, + `created_at` + ) VALUES ( + :user_id, + :minorplanet_id, + 1, + NOW() + ) + "); + $stmtInsertMinorplanet->execute([ + ':user_id' => $userId, + ':minorplanet_id' => $defaultMinorplanetId, + ]); + + $stmtInsertComet = $pdo->prepare(" + INSERT INTO `app_user_comets` ( + `user_id`, + `comet_id`, + `is_favorite`, + `created_at` + ) VALUES ( + :user_id, + :comet_id, + 1, + NOW() + ) + "); + $stmtInsertComet->execute([ + ':user_id' => $userId, + ':comet_id' => $defaultCometId, + ]); + $pdo->commit(); } catch (Throwable $e) { if ($pdo->inTransaction()) {