506 lines
25 KiB
Java
506 lines
25 KiB
Java
package de.ticketsystem.web.handlers;
|
||
|
||
import com.sun.net.httpserver.HttpExchange;
|
||
import com.sun.net.httpserver.HttpHandler;
|
||
import de.ticketsystem.TicketPlugin;
|
||
import de.ticketsystem.database.DatabaseManager;
|
||
import de.ticketsystem.model.*;
|
||
import de.ticketsystem.web.SessionManager;
|
||
import de.ticketsystem.web.WebSession;
|
||
import org.bukkit.Bukkit;
|
||
import org.bukkit.OfflinePlayer;
|
||
|
||
import java.io.IOException;
|
||
import java.util.List;
|
||
import java.util.Map;
|
||
import java.util.UUID;
|
||
|
||
/**
|
||
* /api/* – JSON-API für AJAX-Aktionen aus dem Web-Panel.
|
||
*
|
||
* Alle Responses:
|
||
* Erfolg: {"ok":true}
|
||
* Fehler: {"ok":false,"error":"Beschreibung"}
|
||
*
|
||
* Endpunkte:
|
||
* POST /api/ticket/{id}/claim
|
||
* POST /api/ticket/{id}/close body: {comment?}
|
||
* POST /api/ticket/{id}/priority body: {priority}
|
||
* POST /api/ticket/{id}/comment body: {message}
|
||
* POST /api/ticket/{id}/forward body: {target} (admin only)
|
||
* GET /api/ticket/{id}/comments
|
||
*
|
||
* GET /api/faq
|
||
* POST /api/faq body: {question, answer, category?} (admin only)
|
||
* PUT /api/faq/{id} body: {question, answer, category?} (admin only)
|
||
* DELETE /api/faq/{id} (admin only)
|
||
*/
|
||
public class ApiHandler extends BaseHandler implements HttpHandler {
|
||
|
||
private final TicketPlugin plugin;
|
||
private final SessionManager sessionManager;
|
||
|
||
public ApiHandler(TicketPlugin plugin, SessionManager sessionManager) {
|
||
this.plugin = plugin;
|
||
this.sessionManager = sessionManager;
|
||
}
|
||
|
||
@Override
|
||
public void handle(HttpExchange ex) throws IOException {
|
||
WebSession session = requireSession(ex, sessionManager);
|
||
if (session == null) return;
|
||
|
||
String path = ex.getRequestURI().getPath(); // z.B. /api/ticket/5/claim
|
||
String method = ex.getRequestMethod().toUpperCase();
|
||
|
||
try {
|
||
// ── Ticket-Aktionen ──────────────────────────────────────────
|
||
if (path.startsWith("/api/ticket/")) {
|
||
handleTicketApi(ex, session, path, method);
|
||
return;
|
||
}
|
||
|
||
// ── Archiv-Aktionen ──────────────────────────────────────────
|
||
if (path.startsWith("/api/archive/")) {
|
||
handleArchiveApi(ex, session, path, method);
|
||
return;
|
||
}
|
||
|
||
// ── FAQ-Aktionen ─────────────────────────────────────────────
|
||
if (path.startsWith("/api/faq")) {
|
||
handleFaqApi(ex, session, path, method);
|
||
return;
|
||
}
|
||
|
||
// ── Benutzer-Verwaltung ──────────────────────────────────────
|
||
if (path.startsWith("/api/users")) {
|
||
handleUsersApi(ex, session, path, method);
|
||
return;
|
||
}
|
||
|
||
// ── Backup ───────────────────────────────────────────────────
|
||
if (path.equals("/api/backup") && method.equals("POST")) {
|
||
if (!session.isAdmin()) { sendJson(ex, 403, err("Kein Zugriff")); return; }
|
||
java.io.File backup = plugin.getDatabaseManager().createBackup();
|
||
if (backup != null) {
|
||
sendJson(ex, 200, "{\"ok\":true,\"file\":\"" + jsonEsc(backup.getName()) + "\"}");
|
||
} else {
|
||
sendJson(ex, 500, err("Backup fehlgeschlagen"));
|
||
}
|
||
return;
|
||
}
|
||
|
||
sendJson(ex, 404, "{\"ok\":false,\"error\":\"Unbekannter Endpunkt\"}");
|
||
|
||
} catch (Exception e) {
|
||
plugin.getLogger().warning("[WebPanel/API] Fehler: " + e.getMessage());
|
||
if (plugin.isDebug()) e.printStackTrace();
|
||
sendJson(ex, 500, "{\"ok\":false,\"error\":\"Interner Fehler\"}");
|
||
}
|
||
}
|
||
|
||
// ─────────────────────────── Ticket-API ────────────────────────────────
|
||
|
||
private void handleTicketApi(HttpExchange ex, WebSession session, String path, String method) throws IOException {
|
||
// /api/ticket/{id}/{action}
|
||
String[] parts = path.split("/"); // ["", "api", "ticket", "5", "claim"]
|
||
if (parts.length < 5) { sendJson(ex, 400, err("Ungültiger Pfad")); return; }
|
||
|
||
int ticketId;
|
||
try { ticketId = Integer.parseInt(parts[3]); }
|
||
catch (NumberFormatException e) { sendJson(ex, 400, err("Ungültige Ticket-ID")); return; }
|
||
|
||
String action = parts[4]; // claim | close | priority | comment | forward | comments
|
||
DatabaseManager db = plugin.getDatabaseManager();
|
||
Ticket ticket = db.getTicketById(ticketId);
|
||
|
||
if (ticket == null) { sendJson(ex, 404, err("Ticket nicht gefunden")); return; }
|
||
|
||
switch (action) {
|
||
|
||
case "claim" -> {
|
||
if (!method.equals("POST")) { sendJson(ex, 405, err("Method not allowed")); return; }
|
||
if (ticket.getStatus() != TicketStatus.OPEN) {
|
||
sendJson(ex, 400, err("Ticket ist nicht offen")); return;
|
||
}
|
||
// UUID des Web-Users → wir nutzen einen Pseudo-UUID aus dem Benutzernamen
|
||
UUID webUUID = webUserUUID(session.getUsername());
|
||
boolean ok = db.claimTicket(ticketId, webUUID, "[Web] " + session.getUsername());
|
||
if (ok) {
|
||
ticket.setClaimerName("[Web] " + session.getUsername());
|
||
plugin.getTicketManager().notifyCreatorClaimed(ticket);
|
||
plugin.getTicketCache().invalidate(ticketId);
|
||
sendJson(ex, 200, "{\"ok\":true}");
|
||
} else {
|
||
sendJson(ex, 500, err("Claim fehlgeschlagen"));
|
||
}
|
||
}
|
||
|
||
case "close" -> {
|
||
if (!method.equals("POST")) { sendJson(ex, 405, err("Method not allowed")); return; }
|
||
Map<String, String> body = parseJsonBody(ex);
|
||
String comment = body.getOrDefault("comment", "");
|
||
boolean ok = db.closeTicket(ticketId, comment.isEmpty() ? null : comment);
|
||
if (ok) {
|
||
ticket.setStatus(TicketStatus.CLOSED);
|
||
ticket.setCloseComment(comment);
|
||
plugin.getTicketManager().notifyCreatorClosed(ticket, "[Web] " + session.getUsername());
|
||
plugin.getTicketCache().invalidate(ticketId);
|
||
sendJson(ex, 200, "{\"ok\":true}");
|
||
} else {
|
||
sendJson(ex, 500, err("Schließen fehlgeschlagen"));
|
||
}
|
||
}
|
||
|
||
case "priority" -> {
|
||
if (!method.equals("POST")) { sendJson(ex, 405, err("Method not allowed")); return; }
|
||
Map<String, String> body = parseJsonBody(ex);
|
||
String prioStr = body.getOrDefault("priority", "NORMAL");
|
||
TicketPriority prio = TicketPriority.fromString(prioStr);
|
||
boolean ok = db.setTicketPriority(ticketId, prio);
|
||
if (ok) {
|
||
plugin.getTicketCache().invalidate(ticketId);
|
||
sendJson(ex, 200, "{\"ok\":true}");
|
||
} else {
|
||
sendJson(ex, 500, err("Priorität setzen fehlgeschlagen"));
|
||
}
|
||
}
|
||
|
||
case "comment" -> {
|
||
if (!method.equals("POST")) { sendJson(ex, 405, err("Method not allowed")); return; }
|
||
Map<String, String> body = parseJsonBody(ex);
|
||
String msg = body.getOrDefault("message", "").trim();
|
||
if (msg.isEmpty()) { sendJson(ex, 400, err("Nachricht leer")); return; }
|
||
|
||
UUID webUUID = webUserUUID(session.getUsername());
|
||
String authorDisplay = "[Web] " + session.getUsername();
|
||
TicketComment comment = new TicketComment(ticketId, webUUID, authorDisplay, msg);
|
||
boolean ok = db.addComment(comment);
|
||
if (ok) {
|
||
// Ersteller benachrichtigen (online oder Pending)
|
||
String notifyMsg = plugin.lang().format("comment.notify-online",
|
||
"{id}", String.valueOf(ticketId),
|
||
"{player}", authorDisplay,
|
||
"{message}", msg);
|
||
Bukkit.getScheduler().runTask(plugin, () -> {
|
||
org.bukkit.entity.Player creator = Bukkit.getPlayer(ticket.getCreatorUUID());
|
||
if (creator != null && creator.isOnline()) {
|
||
creator.sendMessage(notifyMsg);
|
||
} else {
|
||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () ->
|
||
db.addPendingNotification(ticket.getCreatorUUID(), notifyMsg));
|
||
}
|
||
});
|
||
sendJson(ex, 200, "{\"ok\":true}");
|
||
} else {
|
||
sendJson(ex, 500, err("Kommentar speichern fehlgeschlagen"));
|
||
}
|
||
}
|
||
|
||
case "forward" -> {
|
||
if (!method.equals("POST")) { sendJson(ex, 405, err("Method not allowed")); return; }
|
||
if (!session.isAdmin()) { sendJson(ex, 403, err("Kein Zugriff")); return; }
|
||
Map<String, String> body = parseJsonBody(ex);
|
||
String targetName = body.getOrDefault("target", "").trim();
|
||
if (targetName.isEmpty()) { sendJson(ex, 400, err("Ziel fehlt")); return; }
|
||
|
||
// Spieler-UUID ermitteln: erst Online-Spieler, dann Offline-Cache
|
||
OfflinePlayer target = null;
|
||
for (var p : Bukkit.getOnlinePlayers()) {
|
||
if (p.getName().equalsIgnoreCase(targetName)) { target = p; break; }
|
||
}
|
||
if (target == null) {
|
||
// getOfflinePlayer lädt ggf. aus dem Usercache – nie null, aber
|
||
// hasPlayedBefore() == false bedeutet: unbekannter Spieler
|
||
OfflinePlayer op = Bukkit.getOfflinePlayer(targetName);
|
||
if (op.hasPlayedBefore()) target = op;
|
||
}
|
||
if (target == null) { sendJson(ex, 404, err("Spieler nicht gefunden: " + targetName)); return; }
|
||
|
||
boolean ok = db.forwardTicket(ticketId, target.getUniqueId(), target.getName());
|
||
if (ok) {
|
||
ticket.setForwardedToUUID(target.getUniqueId());
|
||
ticket.setForwardedToName(target.getName());
|
||
plugin.getTicketManager().notifyForwardedTo(ticket, "[Web] " + session.getUsername());
|
||
plugin.getTicketManager().notifyCreatorForwarded(ticket);
|
||
plugin.getTicketCache().invalidate(ticketId);
|
||
sendJson(ex, 200, "{\"ok\":true}");
|
||
} else {
|
||
sendJson(ex, 500, err("Weiterleiten fehlgeschlagen"));
|
||
}
|
||
}
|
||
|
||
case "comments" -> {
|
||
if (!method.equals("GET")) { sendJson(ex, 405, err("Method not allowed")); return; }
|
||
List<TicketComment> comments = db.getComments(ticketId);
|
||
StringBuilder json = new StringBuilder("[");
|
||
for (int i = 0; i < comments.size(); i++) {
|
||
TicketComment c = comments.get(i);
|
||
if (i > 0) json.append(",");
|
||
json.append("{\"author\":\"").append(jsonEsc(c.getAuthorName()))
|
||
.append("\",\"message\":\"").append(jsonEsc(c.getMessage()))
|
||
.append("\",\"time\":\"").append(c.getCreatedAt() != null ? c.getCreatedAt().getTime() : 0)
|
||
.append("\"}");
|
||
}
|
||
json.append("]");
|
||
sendJson(ex, 200, json.toString());
|
||
}
|
||
|
||
default -> sendJson(ex, 404, err("Unbekannte Aktion: " + action));
|
||
}
|
||
}
|
||
|
||
// ─────────────────────────── FAQ-API ───────────────────────────────────
|
||
|
||
private void handleFaqApi(HttpExchange ex, WebSession session, String path, String method) throws IOException {
|
||
// GET /api/faq – Liste
|
||
if (path.equals("/api/faq") && method.equals("GET")) {
|
||
List<de.ticketsystem.model.FaqEntry> entries = plugin.getFaqManager().getAll();
|
||
StringBuilder json = new StringBuilder("[");
|
||
for (int i = 0; i < entries.size(); i++) {
|
||
var e = entries.get(i);
|
||
if (i > 0) json.append(",");
|
||
json.append("{\"id\":").append(e.getId())
|
||
.append(",\"question\":\"").append(jsonEsc(e.getQuestion()))
|
||
.append("\",\"answer\":\"").append(jsonEsc(e.getAnswer()))
|
||
.append("\",\"category\":\"").append(jsonEsc(e.getCategoryKey()))
|
||
.append("\"}");
|
||
}
|
||
json.append("]");
|
||
sendJson(ex, 200, json.toString());
|
||
return;
|
||
}
|
||
|
||
// POST /api/faq – Hinzufügen (admin only)
|
||
if (path.equals("/api/faq") && method.equals("POST")) {
|
||
if (!session.isAdmin()) { sendJson(ex, 403, err("Kein Zugriff")); return; }
|
||
Map<String, String> body = parseJsonBody(ex);
|
||
String q = body.getOrDefault("question", "").trim();
|
||
String a = body.getOrDefault("answer", "").trim();
|
||
String cat = body.getOrDefault("category", "");
|
||
if (q.isEmpty() || a.isEmpty()) { sendJson(ex, 400, err("Frage und Antwort erforderlich")); return; }
|
||
plugin.getFaqManager().add(q, a, cat.isEmpty() ? null : cat);
|
||
sendJson(ex, 200, "{\"ok\":true}");
|
||
return;
|
||
}
|
||
|
||
// PUT /api/faq/{id} – Bearbeiten (admin only)
|
||
if (path.matches("/api/faq/\\d+") && method.equals("PUT")) {
|
||
if (!session.isAdmin()) { sendJson(ex, 403, err("Kein Zugriff")); return; }
|
||
int id = Integer.parseInt(path.substring("/api/faq/".length()));
|
||
Map<String, String> body = parseJsonBody(ex);
|
||
String q = body.getOrDefault("question", "").trim();
|
||
String a = body.getOrDefault("answer", "").trim();
|
||
String cat = body.getOrDefault("category", "");
|
||
if (q.isEmpty() || a.isEmpty()) { sendJson(ex, 400, err("Frage und Antwort erforderlich")); return; }
|
||
boolean ok = plugin.getFaqManager().edit(id, q, a);
|
||
if (!cat.isEmpty()) plugin.getFaqManager().setCategory(id, cat);
|
||
sendJson(ex, ok ? 200 : 404, ok ? "{\"ok\":true}" : err("FAQ nicht gefunden"));
|
||
return;
|
||
}
|
||
|
||
// DELETE /api/faq/{id} – Löschen (admin only)
|
||
if (path.matches("/api/faq/\\d+") && method.equals("DELETE")) {
|
||
if (!session.isAdmin()) { sendJson(ex, 403, err("Kein Zugriff")); return; }
|
||
int id = Integer.parseInt(path.substring("/api/faq/".length()));
|
||
boolean ok = plugin.getFaqManager().delete(id);
|
||
sendJson(ex, ok ? 200 : 404, ok ? "{\"ok\":true}" : err("FAQ nicht gefunden"));
|
||
return;
|
||
}
|
||
|
||
sendJson(ex, 404, err("Unbekannter FAQ-Endpunkt"));
|
||
}
|
||
|
||
// ─────────────────────────── Archiv-API ────────────────────────────────
|
||
|
||
/**
|
||
* POST /api/archive/{id}/delete – Ticket permanent löschen (admin only)
|
||
* POST /api/archive/{id}/restore – Ticket wiederherstellen (admin only)
|
||
*/
|
||
private void handleArchiveApi(HttpExchange ex, WebSession session, String path, String method) throws IOException {
|
||
if (!session.canViewArchive()) { sendJson(ex, 403, err("Kein Zugriff")); return; }
|
||
if (!method.equals("POST")) { sendJson(ex, 405, err("Method not allowed")); return; }
|
||
|
||
// /api/archive/{id}/{action}
|
||
String[] parts = path.split("/");
|
||
if (parts.length < 5) { sendJson(ex, 400, err("Ungültiger Pfad")); return; }
|
||
|
||
int ticketId;
|
||
try { ticketId = Integer.parseInt(parts[3]); }
|
||
catch (NumberFormatException e) { sendJson(ex, 400, err("Ungültige ID")); return; }
|
||
|
||
String action = parts[4];
|
||
var db = plugin.getDatabaseManager();
|
||
|
||
switch (action) {
|
||
case "delete" -> {
|
||
if (!session.isAdmin()) { sendJson(ex, 403, err("Nur Admins dürfen löschen")); return; }
|
||
boolean ok = db.deleteArchivedTicket(ticketId);
|
||
sendJson(ex, ok ? 200 : 404, ok ? "{\"ok\":true}" : err("Ticket nicht gefunden"));
|
||
}
|
||
case "restore" -> {
|
||
if (!session.isAdmin()) { sendJson(ex, 403, err("Nur Admins dürfen wiederherstellen")); return; }
|
||
boolean ok = db.restoreArchivedTicket(ticketId);
|
||
sendJson(ex, ok ? 200 : 500, ok ? "{\"ok\":true}" : err("Wiederherstellen fehlgeschlagen"));
|
||
}
|
||
default -> sendJson(ex, 404, err("Unbekannte Archiv-Aktion: " + action));
|
||
}
|
||
}
|
||
|
||
// ─────────────────────────── Benutzer-API ──────────────────────────────
|
||
|
||
/**
|
||
* GET /api/users – Alle Benutzer auflisten (admin only)
|
||
* POST /api/users – Neuen Benutzer anlegen (admin only)
|
||
* body: {username, password, role}
|
||
* POST /api/users/{name}/password – Passwort ändern (admin only)
|
||
* body: {password}
|
||
* DELETE /api/users/{name} – Benutzer löschen (admin only)
|
||
*/
|
||
private void handleUsersApi(HttpExchange ex, WebSession session, String path, String method) throws IOException {
|
||
if (!session.isAdmin()) { sendJson(ex, 403, err("Kein Zugriff")); return; }
|
||
|
||
// GET /api/users – Liste
|
||
if (path.equals("/api/users") && method.equals("GET")) {
|
||
org.bukkit.configuration.ConfigurationSection users =
|
||
plugin.getConfig().getConfigurationSection("web-panel.users");
|
||
StringBuilder json = new StringBuilder("[");
|
||
if (users != null) {
|
||
boolean first = true;
|
||
for (String key : users.getKeys(false)) {
|
||
if (!first) json.append(",");
|
||
first = false;
|
||
String role = plugin.getConfig().getString("web-panel.users." + key + ".role", "supporter");
|
||
json.append("{\"username\":\"").append(jsonEsc(key))
|
||
.append("\",\"role\":\"").append(jsonEsc(role)).append("\"}");
|
||
}
|
||
}
|
||
json.append("]");
|
||
sendJson(ex, 200, json.toString());
|
||
return;
|
||
}
|
||
|
||
// POST /api/users – Anlegen
|
||
if (path.equals("/api/users") && method.equals("POST")) {
|
||
Map<String, String> body = parseJsonBody(ex);
|
||
String username = body.getOrDefault("username", "").trim();
|
||
String password = body.getOrDefault("password", "").trim();
|
||
String role = body.getOrDefault("role", "supporter").trim().toLowerCase();
|
||
|
||
if (username.isEmpty() || password.isEmpty()) {
|
||
sendJson(ex, 400, err("Benutzername und Passwort erforderlich")); return;
|
||
}
|
||
if (!role.equals("admin") && !role.equals("supporter") && !role.equals("archive_viewer")) {
|
||
sendJson(ex, 400, err("Ungültige Rolle (admin/supporter/archive_viewer)")); return;
|
||
}
|
||
// Prüfen ob Benutzer schon existiert
|
||
org.bukkit.configuration.ConfigurationSection existing =
|
||
plugin.getConfig().getConfigurationSection("web-panel.users");
|
||
if (existing != null) {
|
||
for (String k : existing.getKeys(false)) {
|
||
if (k.equalsIgnoreCase(username)) {
|
||
sendJson(ex, 409, err("Benutzer existiert bereits")); return;
|
||
}
|
||
}
|
||
}
|
||
String hash = de.ticketsystem.web.SessionManager.sha256(password);
|
||
plugin.getConfig().set("web-panel.users." + username + ".password-hash", hash);
|
||
plugin.getConfig().set("web-panel.users." + username + ".role", role);
|
||
plugin.saveConfig();
|
||
sendJson(ex, 200, "{\"ok\":true}");
|
||
return;
|
||
}
|
||
|
||
// POST /api/users/{name}/password – Passwort ändern
|
||
if (path.matches("/api/users/[^/]+/password") && method.equals("POST")) {
|
||
String targetUser = path.split("/")[3];
|
||
Map<String, String> body = parseJsonBody(ex);
|
||
String newPass = body.getOrDefault("password", "").trim();
|
||
if (newPass.isEmpty()) { sendJson(ex, 400, err("Passwort darf nicht leer sein")); return; }
|
||
|
||
String cfgPath = findUserConfigPath(targetUser);
|
||
if (cfgPath == null) { sendJson(ex, 404, err("Benutzer nicht gefunden")); return; }
|
||
|
||
String hash = de.ticketsystem.web.SessionManager.sha256(newPass);
|
||
plugin.getConfig().set(cfgPath + ".password-hash", hash);
|
||
plugin.getConfig().set(cfgPath + ".password", null);
|
||
plugin.saveConfig();
|
||
// Alle Sessions dieses Benutzers invalidieren
|
||
sessionManager.invalidateUser(targetUser);
|
||
sendJson(ex, 200, "{\"ok\":true}");
|
||
return;
|
||
}
|
||
|
||
// DELETE /api/users/{name} – Löschen
|
||
if (path.matches("/api/users/[^/]+") && method.equals("DELETE")) {
|
||
String targetUser = path.split("/")[3];
|
||
// Darf sich nicht selbst löschen
|
||
if (targetUser.equalsIgnoreCase(session.getUsername())) {
|
||
sendJson(ex, 400, err("Du kannst dich nicht selbst löschen")); return;
|
||
}
|
||
String cfgPath = findUserConfigPath(targetUser);
|
||
if (cfgPath == null) { sendJson(ex, 404, err("Benutzer nicht gefunden")); return; }
|
||
plugin.getConfig().set(cfgPath, null);
|
||
plugin.saveConfig();
|
||
sessionManager.invalidateUser(targetUser);
|
||
sendJson(ex, 200, "{\"ok\":true}");
|
||
return;
|
||
}
|
||
|
||
sendJson(ex, 404, err("Unbekannter Benutzer-Endpunkt"));
|
||
}
|
||
|
||
/** Sucht den config.yml-Pfad eines Benutzers (case-insensitiv). */
|
||
private String findUserConfigPath(String username) {
|
||
org.bukkit.configuration.ConfigurationSection users =
|
||
plugin.getConfig().getConfigurationSection("web-panel.users");
|
||
if (users == null) return null;
|
||
for (String key : users.getKeys(false)) {
|
||
if (key.equalsIgnoreCase(username)) return "web-panel.users." + key;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
// ─────────────────────────── Hilfsmethoden ─────────────────────────────
|
||
|
||
/**
|
||
* Liest einen JSON-Body und parst ihn als flaches Key-Value-Objekt.
|
||
* Kein externer JSON-Parser – minimale Eigenimplementierung.
|
||
*/
|
||
private Map<String, String> parseJsonBody(HttpExchange ex) throws IOException {
|
||
java.io.InputStream is = ex.getRequestBody();
|
||
String body = new String(is.readAllBytes(), java.nio.charset.StandardCharsets.UTF_8).trim();
|
||
Map<String, String> result = new java.util.HashMap<>();
|
||
if (body.startsWith("{") && body.endsWith("}")) {
|
||
body = body.substring(1, body.length() - 1);
|
||
// Einfaches Parsing: "key":"value", "key2":"value2"
|
||
java.util.regex.Matcher m = java.util.regex.Pattern
|
||
.compile("\"([^\"]+)\"\\s*:\\s*\"((?:[^\"\\\\]|\\\\.)*)\"")
|
||
.matcher(body);
|
||
while (m.find()) {
|
||
result.put(m.group(1), m.group(2)
|
||
.replace("\\\"", "\"")
|
||
.replace("\\\\", "\\")
|
||
.replace("\\n", "\n"));
|
||
}
|
||
}
|
||
return result;
|
||
}
|
||
|
||
/**
|
||
* Erstellt eine deterministische UUID aus einem Web-Benutzernamen.
|
||
* Damit kann der Web-User als "Autor" in DB-Einträgen gespeichert werden.
|
||
*/
|
||
private UUID webUserUUID(String username) {
|
||
return UUID.nameUUIDFromBytes(("webpanel:" + username).getBytes(java.nio.charset.StandardCharsets.UTF_8));
|
||
}
|
||
|
||
private String err(String msg) {
|
||
return "{\"ok\":false,\"error\":\"" + jsonEsc(msg) + "\"}";
|
||
}
|
||
|
||
private String jsonEsc(String s) {
|
||
if (s == null) return "";
|
||
return s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n").replace("\r", "");
|
||
}
|
||
} |