#pragma once #include #include #include #include #include #include #include const char *BACKEND_SERVER = "https://ninja.reptilfpv.de"; extern String licence; // Declare licence as an external variable defined elsewhere // Lokale Benutzer-Struktur struct LocalUser { String uid; String name; unsigned long timestamp; // Zeitstempel der Erstellung }; // Lokale Benutzer-Speicherung (geht bei Neustart verloren) std::vector localUsers; bool backendOnline() { Serial.println(licence); if (WiFi.status() != WL_CONNECTED) { Serial.println("No WiFi connection."); return false; } HTTPClient http; http.begin(String(BACKEND_SERVER) + "/api/v1/private/health"); http.addHeader("Authorization", String("Bearer ") + licence); int httpCode = http.GET(); bool isOnline = (httpCode == HTTP_CODE_OK); if (isOnline) { Serial.println("Database server connection successful"); } else { Serial.printf("Database server connection failed, error: %d\n", httpCode); } http.end(); return isOnline; } struct UserData { String uid; String firstname; String lastname; int alter; bool exists; }; // Forward declarations für Leaderboard-Funktionen void addLocalTime(String uid, String name, unsigned long timeMs); // Prüft, ob ein Benutzer mit der angegebenen UID in der Datenbank existiert und // gibt dessen Daten zurück. UserData checkUser(const String &uid) { UserData userData = {"", "", "", 0, false}; String upperUid = uid; upperUid.toUpperCase(); // UID in Großbuchstaben konvertieren // Lokale Benutzer durchsuchen for (const auto &user : localUsers) { String userUpperUid = user.uid; userUpperUid.toUpperCase(); if (userUpperUid == upperUid) { userData.uid = user.uid; userData.firstname = user.name; userData.lastname = ""; // Nicht mehr verwendet userData.alter = 0; // Nicht mehr verwendet userData.exists = true; Serial.println("Lokaler Benutzer gefunden: " + user.name); return userData; } } Serial.println("Benutzer mit UID " + uid + " nicht in lokaler Datenbank gefunden"); return userData; } // Holt alle Standorte aus der Datenbank und gibt sie als JSON-Dokument zurück. JsonDocument getAllLocations() { JsonDocument locations; // Allocate memory for the JSON document if (!backendOnline()) { Serial.println("No internet connection, cannot fetch locations."); return locations; // Return an empty document } HTTPClient http; http.begin(String(BACKEND_SERVER) + "/v1/private/locations"); http.addHeader("Authorization", String("Bearer ") + licence); int httpCode = http.GET(); if (httpCode == HTTP_CODE_OK) { String payload = http.getString(); DeserializationError error = deserializeJson(locations, payload); if (error) { Serial.println("Failed to parse locations JSON."); } } else { Serial.printf("Failed to fetch locations, HTTP code: %d\n", httpCode); } http.end(); return locations; // Return the populated JSON document } // Prüft, ob ein Benutzer mit der angegebenen UID existiert // (Kompatibilitätsfunktion). bool userExists(const String &uid) { return checkUser(uid).exists; } // Fügt einen neuen Benutzer in die Datenbank ein bool enterUserData(const String &uid, const String &name) { String upperUid = uid; upperUid.toUpperCase(); // UID in Großbuchstaben konvertieren // Prüfen ob Benutzer bereits existiert for (const auto &user : localUsers) { String userUpperUid = user.uid; userUpperUid.toUpperCase(); if (userUpperUid == upperUid) { Serial.println("Benutzer mit UID " + upperUid + " existiert bereits!"); return false; } } // Neuen Benutzer erstellen LocalUser newUser; newUser.uid = upperUid; // UID in Großbuchstaben speichern newUser.name = name; newUser.timestamp = millis(); // Benutzer zum lokalen Array hinzufügen localUsers.push_back(newUser); Serial.println("Benutzer lokal gespeichert:"); Serial.println("UID: " + upperUid); Serial.println("Name: " + name); Serial.println("Gespeicherte Benutzer: " + String(localUsers.size())); return true; } // Gibt alle lokalen Benutzer zurück (für Debugging) String getLocalUsersList() { String result = "Lokale Benutzer (" + String(localUsers.size()) + "):\n"; for (const auto &user : localUsers) { result += "- UID: " + user.uid + ", Name: " + user.name + ", Erstellt: " + String(user.timestamp) + "\n"; } return result; } // Richtet die HTTP-Routen für die Backend-API ein (z.B. Health-Check, User- und // Location-Abfragen). void setupBackendRoutes(AsyncWebServer &server) { server.on("/api/health", HTTP_GET, [](AsyncWebServerRequest *request) { DynamicJsonDocument doc(64); doc["status"] = backendOnline() ? "connected" : "disconnected"; String response; serializeJson(doc, response); request->send(200, "application/json", response); }); server.on("/api/users", HTTP_GET, [](AsyncWebServerRequest *request) { // Lokale Benutzer als JSON zurückgeben DynamicJsonDocument doc(2048); JsonArray usersArray = doc.createNestedArray("users"); for (const auto &user : localUsers) { JsonObject userObj = usersArray.createNestedObject(); userObj["uid"] = user.uid; userObj["name"] = user.name; userObj["timestamp"] = user.timestamp; } doc["count"] = localUsers.size(); String response; serializeJson(doc, response); request->send(200, "application/json", response); }); // Route zum Erstellen eines neuen Benutzers server.on( "/api/users/insert", HTTP_POST, [](AsyncWebServerRequest *request) { Serial.println("API: /api/users/insert aufgerufen"); }, NULL, [](AsyncWebServerRequest *request, uint8_t *data, size_t len, size_t index, size_t total) { // Diese Funktion wird für den Body aufgerufen static String bodyBuffer = ""; // Daten anhängen for (size_t i = 0; i < len; i++) { bodyBuffer += (char)data[i]; } // Wenn alle Daten empfangen wurden if (index + len == total) { Serial.println("Request Body empfangen: '" + bodyBuffer + "'"); if (bodyBuffer.length() == 0) { Serial.println("FEHLER: Request Body ist leer!"); DynamicJsonDocument response(200); response["success"] = false; response["error"] = "Request Body ist leer"; String jsonString; serializeJson(response, jsonString); request->send(400, "application/json", jsonString); bodyBuffer = ""; return; } DynamicJsonDocument doc(512); DeserializationError error = deserializeJson(doc, bodyBuffer); if (error) { Serial.println("JSON Parse Error: " + String(error.c_str())); DynamicJsonDocument response(200); response["success"] = false; response["error"] = "Invalid JSON: " + String(error.c_str()); String jsonString; serializeJson(response, jsonString); request->send(400, "application/json", jsonString); bodyBuffer = ""; return; } String uid = doc["uid"].as(); String name = doc["name"].as(); Serial.println("Extrahierte UID: " + uid); Serial.println("Extrahierter Name: " + name); if (uid.length() == 0 || name.length() == 0) { DynamicJsonDocument response(200); response["success"] = false; response["error"] = "UID und Name sind erforderlich"; String jsonString; serializeJson(response, jsonString); request->send(400, "application/json", jsonString); bodyBuffer = ""; return; } // Prüfen ob Benutzer bereits existiert bool userExists = false; for (const auto &user : localUsers) { if (user.uid == uid) { userExists = true; break; } } if (userExists) { DynamicJsonDocument response(200); response["success"] = false; response["error"] = "Benutzer bereits vorhanden"; String jsonString; serializeJson(response, jsonString); request->send(409, "application/json", jsonString); bodyBuffer = ""; return; } // Neuen Benutzer direkt in das Array einfügen LocalUser newUser; newUser.uid = uid; newUser.name = name; newUser.timestamp = millis(); localUsers.push_back(newUser); Serial.println("Benutzer über API eingefügt:"); Serial.println("UID: " + uid); Serial.println("Name: " + name); Serial.println("Gespeicherte Benutzer: " + String(localUsers.size())); DynamicJsonDocument response(200); response["success"] = true; response["message"] = "Benutzer erfolgreich erstellt"; response["uid"] = uid; response["name"] = name; String jsonString; serializeJson(response, jsonString); request->send(200, "application/json", jsonString); // Buffer zurücksetzen bodyBuffer = ""; } }); // Debug-Route für lokale Benutzer server.on("/api/debug/users", HTTP_GET, [](AsyncWebServerRequest *request) { String userList = getLocalUsersList(); request->send(200, "text/plain", userList); }); // Location routes /api/location/ server.on("/api/location/", HTTP_GET, [](AsyncWebServerRequest *request) { String result; serializeJson(getAllLocations(), result); request->send(200, "application/json", result); }); server.on("/api/set-local-location", HTTP_POST, [](AsyncWebServerRequest *request) { Serial.println("/api/set-local-location called"); String locationId; if (request->hasParam("locationId", true)) { locationId = request->getParam("locationId", true)->value(); } if (locationId.length() > 0) { saveLocationIdToPrefs(locationId); DynamicJsonDocument doc(64); doc["success"] = true; String result; serializeJson(doc, result); request->send(200, "application/json", result); } else { request->send(400, "application/json", "{\"success\":false}"); } }); server.on("/api/get-local-location", HTTP_GET, [](AsyncWebServerRequest *request) { String locationId = getLocationIdFromPrefs(); DynamicJsonDocument doc(64); doc["locationId"] = locationId; String result; serializeJson(doc, result); request->send(200, "application/json", result); // Andere Logik wie in getBestLocs }); // Lokales Leaderboard API (für Hauptseite - 6 Einträge) server.on("/api/leaderboard", HTTP_GET, [](AsyncWebServerRequest *request) { // Sortiere nach Zeit (beste zuerst) std::sort(localTimes.begin(), localTimes.end(), [](const LocalTime &a, const LocalTime &b) { return a.timeMs < b.timeMs; }); DynamicJsonDocument doc(2048); JsonArray leaderboard = doc.createNestedArray("leaderboard"); // Nimm die besten 6 int count = 0; for (const auto &time : localTimes) { if (count >= 6) break; JsonObject entry = leaderboard.createNestedObject(); entry["rank"] = count + 1; entry["name"] = time.name; entry["uid"] = time.uid; entry["time"] = time.timeMs / 1000.0; // Format time inline float seconds = time.timeMs / 1000.0; int totalSeconds = (int)seconds; int minutes = totalSeconds / 60; int remainingSeconds = totalSeconds % 60; int milliseconds = (int)((seconds - totalSeconds) * 100); String timeFormatted; if (minutes > 0) { timeFormatted = String(minutes) + ":" + (remainingSeconds < 10 ? "0" : "") + String(remainingSeconds) + "." + (milliseconds < 10 ? "0" : "") + String(milliseconds); } else { timeFormatted = String(remainingSeconds) + "." + (milliseconds < 10 ? "0" : "") + String(milliseconds); } entry["timeFormatted"] = timeFormatted; count++; } String result; serializeJson(doc, result); request->send(200, "application/json", result); }); // Erweiterte Leaderboard API (für Leaderboard-Seite - 10 Einträge) server.on( "/api/leaderboard-full", HTTP_GET, [](AsyncWebServerRequest *request) { // Sortiere nach Zeit (beste zuerst) std::sort(localTimes.begin(), localTimes.end(), [](const LocalTime &a, const LocalTime &b) { return a.timeMs < b.timeMs; }); DynamicJsonDocument doc(2048); JsonArray leaderboard = doc.createNestedArray("leaderboard"); // Nimm die besten 10 int count = 0; for (const auto &time : localTimes) { if (count >= 10) break; JsonObject entry = leaderboard.createNestedObject(); entry["rank"] = count + 1; entry["name"] = time.name; entry["uid"] = time.uid; entry["time"] = time.timeMs / 1000.0; // Format time inline float seconds = time.timeMs / 1000.0; int totalSeconds = (int)seconds; int minutes = totalSeconds / 60; int remainingSeconds = totalSeconds % 60; int milliseconds = (int)((seconds - totalSeconds) * 100); String timeFormatted; if (minutes > 0) { timeFormatted = String(minutes) + ":" + (remainingSeconds < 10 ? "0" : "") + String(remainingSeconds) + "." + (milliseconds < 10 ? "0" : "") + String(milliseconds); } else { timeFormatted = String(remainingSeconds) + "." + (milliseconds < 10 ? "0" : "") + String(milliseconds); } entry["timeFormatted"] = timeFormatted; count++; } String result; serializeJson(doc, result); request->send(200, "application/json", result); }); // Add more routes as needed } // Hilfsfunktionen um UID und Status abzufragen (aus communication.h) String getStart1UID(); String getStart2UID(); bool wasStart1FoundLocally(); bool wasStart2FoundLocally(); // Funktion um Zeit an Online-API zu senden void sendTimeToOnlineAPI(int lane, String uid, float timeInSeconds) { // Nur senden wenn User online gefunden wurde bool wasOnlineFound = (lane == 1) ? !wasStart1FoundLocally() : !wasStart2FoundLocally(); if (!wasOnlineFound) { Serial.println("Zeit nicht gesendet - User wurde lokal gefunden"); return; } if (WiFi.status() != WL_CONNECTED) { Serial.println("Keine Internetverbindung - Zeit nicht gesendet"); return; } Serial.println("Sende Zeit an Online-API für Lane " + String(lane)); HTTPClient http; http.begin(String(BACKEND_SERVER) + "/api/v1/private/create-time"); http.addHeader("Content-Type", "application/json"); http.addHeader("Authorization", String("Bearer ") + licence); // Zeit in M:SS.mmm Format konvertieren (ohne führende Null bei Minuten) int minutes = (int)(timeInSeconds / 60); int seconds = (int)timeInSeconds % 60; int milliseconds = (int)((timeInSeconds - (int)timeInSeconds) * 1000); String formattedTime = String(minutes) + ":" + (seconds < 10 ? "0" : "") + String(seconds) + "." + (milliseconds < 10 ? "00" : (milliseconds < 100 ? "0" : "")) + String(milliseconds); StaticJsonDocument<200> requestDoc; requestDoc["rfiduid"] = uid; requestDoc["location_name"] = getLocationIdFromPrefs(); // Aus den Einstellungen requestDoc["recorded_time"] = formattedTime; String requestBody; serializeJson(requestDoc, requestBody); Serial.println("API Request Body: " + requestBody); int httpCode = http.POST(requestBody); if (httpCode == HTTP_CODE_OK || httpCode == HTTP_CODE_CREATED) { String response = http.getString(); Serial.println("Zeit erfolgreich gesendet: " + response); } else { Serial.printf("Fehler beim Senden der Zeit: HTTP %d\n", httpCode); if (httpCode > 0) { String response = http.getString(); Serial.println("Response: " + response); } } http.end(); } // Funktionen für lokales Leaderboard void addLocalTime(String uid, String name, unsigned long timeMs) { // Prüfe minimale Zeit für Leaderboard-Eintrag if (timeMs < minTimeForLeaderboard) { Serial.printf( "Zeit zu kurz für Leaderboard: %s (%s) - %.2fs (Minimum: %.2fs)\n", name.c_str(), uid.c_str(), timeMs / 1000.0, minTimeForLeaderboard / 1000.0); return; // Zeit wird nicht ins Leaderboard eingetragen } LocalTime newTime; newTime.uid = uid; newTime.name = name; newTime.timeMs = timeMs; newTime.timestamp = millis(); localTimes.push_back(newTime); // Speichere das Leaderboard automatisch saveBestTimes(); Serial.printf("Lokale Zeit hinzugefügt: %s (%s) - %.2fs\n", name.c_str(), uid.c_str(), timeMs / 1000.0); } // Leert das lokale Leaderboard void clearLocalLeaderboard() { localTimes.clear(); saveBestTimes(); // Speichere das geleerte Leaderboard Serial.println("Lokales Leaderboard geleert"); }