Files
AquaMasterMQTT/src/databasebackend.h
Carsten Graf 2a832257ba Added minTime
2025-10-13 19:17:35 +02:00

558 lines
18 KiB
C++

#pragma once
#include <Arduino.h>
#include <ArduinoJson.h>
#include <ESPAsyncWebServer.h>
#include <HTTPClient.h>
#include <algorithm>
#include <preferencemanager.h>
#include <vector>
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<LocalUser> 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>();
String name = doc["name"].as<String>();
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");
}