Update from Git Manager GUI
This commit is contained in:
@@ -161,6 +161,7 @@ public class DatabaseManager {
|
||||
|
||||
private void createTables() {
|
||||
// Haupt-Tickets-Tabelle
|
||||
// BungeeCord: server_name speichert auf welchem Server das Ticket erstellt wurde
|
||||
String ticketsSql = """
|
||||
CREATE TABLE IF NOT EXISTS tickets (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
@@ -186,7 +187,9 @@ public class DatabaseManager {
|
||||
category VARCHAR(16) NOT NULL DEFAULT 'GENERAL',
|
||||
priority VARCHAR(10) NOT NULL DEFAULT 'NORMAL',
|
||||
player_rating VARCHAR(16) NULL,
|
||||
claimer_notified BOOLEAN DEFAULT FALSE
|
||||
claimer_notified BOOLEAN DEFAULT FALSE,
|
||||
close_notified BOOLEAN DEFAULT FALSE,
|
||||
server_name VARCHAR(64) NOT NULL DEFAULT 'unknown'
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
""";
|
||||
|
||||
@@ -225,11 +228,50 @@ public class DatabaseManager {
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
""";
|
||||
|
||||
// Persistente Statistik-Tabelle – überlebt das Löschen/Archivieren von Tickets.
|
||||
// Wird beim Schließen eines Tickets befüllt und beim Bewerten aktualisiert.
|
||||
// So gehen keine Zahlen verloren wenn das Archiv geleert wird.
|
||||
String statsSql = """
|
||||
CREATE TABLE IF NOT EXISTS ticket_stats (
|
||||
id INT AUTO_INCREMENT PRIMARY KEY,
|
||||
ticket_id INT NOT NULL,
|
||||
claimer_uuid VARCHAR(36) NULL,
|
||||
claimer_name VARCHAR(16) NULL,
|
||||
creator_uuid VARCHAR(36) NOT NULL,
|
||||
creator_name VARCHAR(16) NOT NULL,
|
||||
category VARCHAR(16) NOT NULL DEFAULT 'general',
|
||||
priority VARCHAR(10) NOT NULL DEFAULT 'NORMAL',
|
||||
server_name VARCHAR(64) NOT NULL DEFAULT 'unknown',
|
||||
player_rating VARCHAR(16) NULL,
|
||||
closed_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
|
||||
INDEX idx_claimer_uuid (claimer_uuid),
|
||||
INDEX idx_closed_at (closed_at)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
""";
|
||||
|
||||
// Ausstehende BungeeCord-Teleport-Aufträge.
|
||||
// Wird gesetzt wenn ein Admin via GUI/Command auf einen anderen Server teleportiert.
|
||||
// PlayerJoinListener liest den Eintrag beim Ankommen, teleportiert, löscht ihn dann.
|
||||
String pendingTeleportSql = """
|
||||
CREATE TABLE IF NOT EXISTS ticket_pending_teleport (
|
||||
player_uuid VARCHAR(36) NOT NULL PRIMARY KEY,
|
||||
world VARCHAR(64) NOT NULL,
|
||||
x DOUBLE NOT NULL,
|
||||
y DOUBLE NOT NULL,
|
||||
z DOUBLE NOT NULL,
|
||||
yaw FLOAT NOT NULL DEFAULT 0,
|
||||
pitch FLOAT NOT NULL DEFAULT 0,
|
||||
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
|
||||
""";
|
||||
|
||||
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
|
||||
stmt.execute(ticketsSql);
|
||||
stmt.execute(commentsSql);
|
||||
stmt.execute(blacklistSql);
|
||||
stmt.execute(notifSql);
|
||||
stmt.execute(statsSql);
|
||||
stmt.execute(pendingTeleportSql);
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Fehler beim Erstellen der Tabellen: " + e.getMessage(), e);
|
||||
}
|
||||
@@ -237,6 +279,7 @@ public class DatabaseManager {
|
||||
|
||||
/**
|
||||
* Ergänzt fehlende Spalten in bestehenden Datenbanken automatisch.
|
||||
* Wichtig für Upgrades von älteren Versionen.
|
||||
*/
|
||||
private void ensureColumns() {
|
||||
ensureColumn("close_comment", "ALTER TABLE tickets ADD COLUMN close_comment VARCHAR(500) NULL");
|
||||
@@ -245,6 +288,13 @@ public class DatabaseManager {
|
||||
ensureColumn("priority", "ALTER TABLE tickets ADD COLUMN priority VARCHAR(10) NOT NULL DEFAULT 'NORMAL'");
|
||||
ensureColumn("player_rating", "ALTER TABLE tickets ADD COLUMN player_rating VARCHAR(16) NULL");
|
||||
ensureColumn("claimer_notified", "ALTER TABLE tickets ADD COLUMN claimer_notified BOOLEAN DEFAULT FALSE");
|
||||
// Bug-Fix: close_notified verhindert Duplikat-Discord-Nachrichten und doppelte Offline-Benachrichtigungen bei Server-Wechsel
|
||||
ensureColumn("close_notified", "ALTER TABLE tickets ADD COLUMN close_notified BOOLEAN DEFAULT FALSE");
|
||||
// BungeeCord: Server-Name-Spalte für bestehende Datenbanken nachrüsten
|
||||
ensureColumn("server_name", "ALTER TABLE tickets ADD COLUMN server_name VARCHAR(64) NOT NULL DEFAULT 'unknown'");
|
||||
|
||||
// ticket_stats: Spalte player_rating nachrüsten falls Tabelle vor diesem Feature existiert
|
||||
ensureStatsColumn("player_rating", "ALTER TABLE ticket_stats ADD COLUMN player_rating VARCHAR(16) NULL");
|
||||
}
|
||||
|
||||
private void ensureColumn(String columnName, String alterSql) {
|
||||
@@ -268,13 +318,36 @@ public class DatabaseManager {
|
||||
}
|
||||
}
|
||||
|
||||
private void ensureStatsColumn(String columnName, String alterSql) {
|
||||
String checkSql = """
|
||||
SELECT COUNT(*) FROM information_schema.COLUMNS
|
||||
WHERE TABLE_SCHEMA = DATABASE()
|
||||
AND TABLE_NAME = 'ticket_stats'
|
||||
AND COLUMN_NAME = ?
|
||||
""";
|
||||
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(checkSql)) {
|
||||
ps.setString(1, columnName);
|
||||
ResultSet rs = ps.executeQuery();
|
||||
if (rs.next() && rs.getInt(1) == 0) {
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
stmt.execute(alterSql);
|
||||
plugin.getLogger().info("[TicketSystem] Stats-Spalte '" + columnName + "' wurde hinzugefügt.");
|
||||
}
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Fehler bei ensureStatsColumn(" + columnName + "): " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────── CRUD Tickets ──────────────────────────────
|
||||
|
||||
public int createTicket(Ticket ticket) {
|
||||
if (useMySQL) {
|
||||
// BungeeCord: server_name wird ebenfalls gespeichert
|
||||
String sql = """
|
||||
INSERT INTO tickets (creator_uuid, creator_name, message, world, x, y, z, yaw, pitch, category, priority)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
INSERT INTO tickets (creator_uuid, creator_name, message, world, x, y, z, yaw, pitch,
|
||||
category, priority, server_name)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
||||
""";
|
||||
try (Connection conn = getConnection();
|
||||
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
|
||||
@@ -289,6 +362,7 @@ public class DatabaseManager {
|
||||
ps.setFloat(9, ticket.getPitch());
|
||||
ps.setString(10, ticket.getCategoryKey());
|
||||
ps.setString(11, ticket.getPriority().name());
|
||||
ps.setString(12, ticket.getServerName()); // BungeeCord
|
||||
ps.executeUpdate();
|
||||
ResultSet rs = ps.getGeneratedKeys();
|
||||
if (rs.next()) return rs.getInt(1);
|
||||
@@ -306,13 +380,21 @@ public class DatabaseManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ── BUG FIX #1 ──────────────────────────────────────────────────────────
|
||||
// Vorher: WHERE id = ? AND status = 'OPEN'
|
||||
// Problem: Ein FORWARDED-Ticket konnte nicht geclaimed werden – das UPDATE
|
||||
// schlug lautlos fehl, claimer_uuid/claimer_name wurden nie geschrieben.
|
||||
// Fix: WHERE id = ? AND status != 'CLOSED'
|
||||
// Damit können sowohl OPEN als auch FORWARDED Tickets korrekt
|
||||
// angenommen werden und claimer_uuid/claimer_name werden immer gesetzt.
|
||||
// ────────────────────────────────────────────────────────────────────────
|
||||
public boolean claimTicket(int ticketId, UUID claimerUUID, String claimerName) {
|
||||
if (useMySQL) {
|
||||
String sql = """
|
||||
UPDATE tickets
|
||||
SET status = 'CLAIMED', claimer_uuid = ?, claimer_name = ?,
|
||||
claimed_at = NOW(), player_deleted = FALSE
|
||||
WHERE id = ? AND status = 'OPEN'
|
||||
WHERE id = ? AND status != 'CLOSED'
|
||||
""";
|
||||
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setString(1, claimerUUID.toString());
|
||||
@@ -325,7 +407,7 @@ public class DatabaseManager {
|
||||
return false;
|
||||
} else {
|
||||
Ticket t = getTicketById(ticketId);
|
||||
if (t == null || t.getStatus() != TicketStatus.OPEN) return false;
|
||||
if (t == null || t.getStatus() == TicketStatus.CLOSED) return false;
|
||||
t.setStatus(TicketStatus.CLAIMED);
|
||||
t.setClaimerUUID(claimerUUID);
|
||||
t.setClaimerName(claimerName);
|
||||
@@ -452,11 +534,8 @@ public class DatabaseManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────── [NEW] Claim-Benachrichtigung markieren ────
|
||||
// ─────────────────────────── Claim-Benachrichtigung markieren ──────────
|
||||
|
||||
/**
|
||||
* Setzt claimer_notified = TRUE für ein Ticket (persistiert in DB/Datei).
|
||||
*/
|
||||
public void markClaimerNotified(int ticketId) {
|
||||
if (useMySQL) {
|
||||
String sql = "UPDATE tickets SET claimer_notified = TRUE WHERE id = ?";
|
||||
@@ -476,21 +555,42 @@ public class DatabaseManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────── [NEW] Bewertung ───────────────────────────
|
||||
// ─────────────────────────── Schließ-Benachrichtigung markieren ────────
|
||||
|
||||
/**
|
||||
* Speichert die Bewertung eines Spielers für sein geschlossenes Ticket.
|
||||
* @param ticketId ID des Tickets
|
||||
* @param rating "THUMBS_UP" oder "THUMBS_DOWN"
|
||||
* @return true bei Erfolg
|
||||
* Setzt close_notified = TRUE für ein Ticket (persistiert in DB/Datei).
|
||||
* Verhindert Duplikat-Benachrichtigungen und doppelte Discord-Nachrichten
|
||||
* bei Server-Wechseln in BungeeCord-Netzwerken.
|
||||
*/
|
||||
public void markCloseNotified(int ticketId) {
|
||||
if (useMySQL) {
|
||||
String sql = "UPDATE tickets SET close_notified = TRUE WHERE id = ?";
|
||||
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setInt(1, ticketId);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Fehler bei markCloseNotified: " + e.getMessage(), e);
|
||||
}
|
||||
} else {
|
||||
Ticket t = getTicketById(ticketId);
|
||||
if (t != null) {
|
||||
t.setCloseNotified(true);
|
||||
dataConfig.set("tickets." + ticketId, t);
|
||||
saveDataConfig();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean rateTicket(int ticketId, String rating) {
|
||||
if (useMySQL) {
|
||||
String sql = "UPDATE tickets SET player_rating = ? WHERE id = ? AND status = 'CLOSED' AND player_rating IS NULL";
|
||||
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setString(1, rating);
|
||||
ps.setInt(2, ticketId);
|
||||
return ps.executeUpdate() > 0;
|
||||
boolean updated = ps.executeUpdate() > 0;
|
||||
// Bewertung auch in die persistente Stats-Tabelle übertragen
|
||||
if (updated) updateStatsRating(ticketId, rating);
|
||||
return updated;
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Fehler bei rateTicket: " + e.getMessage(), e);
|
||||
}
|
||||
@@ -505,11 +605,191 @@ public class DatabaseManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────── [NEW] Kommentare ──────────────────────────
|
||||
/**
|
||||
* Schreibt einen Eintrag in ticket_stats wenn ein Ticket geschlossen wird.
|
||||
* Diese Tabelle bleibt dauerhaft erhalten – auch wenn das Ticket später
|
||||
* gelöscht oder archiviert wird. So gehen Statistiken nie verloren.
|
||||
*
|
||||
* @param ticket Das gerade geschlossene Ticket-Objekt
|
||||
* @param closerName Name des Admins/Supporters der das Ticket geschlossen hat
|
||||
* (kann vom claimer_name abweichen wenn ein Admin fremde Tickets schließt)
|
||||
*/
|
||||
public void recordClosedTicket(Ticket ticket, String closerName) {
|
||||
if (!useMySQL) return;
|
||||
|
||||
// closer_name bevorzugen – falls null, auf claimer_name zurückfallen
|
||||
String effectiveCloser = (closerName != null && !closerName.isEmpty())
|
||||
? closerName : ticket.getClaimerName();
|
||||
String effectiveCloserUuid = null;
|
||||
// UUID nur setzen wenn closer == claimer (sonst haben wir die UUID des Admins nicht direkt)
|
||||
if (effectiveCloser != null && effectiveCloser.equals(ticket.getClaimerName())
|
||||
&& ticket.getClaimerUUID() != null) {
|
||||
effectiveCloserUuid = ticket.getClaimerUUID().toString();
|
||||
}
|
||||
|
||||
String sql = """
|
||||
INSERT INTO ticket_stats
|
||||
(ticket_id, claimer_uuid, claimer_name, creator_uuid, creator_name,
|
||||
category, priority, server_name, player_rating, closed_at)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, NOW())
|
||||
ON DUPLICATE KEY UPDATE closed_at = closed_at
|
||||
""";
|
||||
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setInt(1, ticket.getId());
|
||||
ps.setString(2, effectiveCloserUuid);
|
||||
ps.setString(3, effectiveCloser);
|
||||
ps.setString(4, ticket.getCreatorUUID().toString());
|
||||
ps.setString(5, ticket.getCreatorName());
|
||||
ps.setString(6, ticket.getCategoryKey());
|
||||
ps.setString(7, ticket.getPriority().name());
|
||||
ps.setString(8, ticket.getServerName());
|
||||
ps.setString(9, ticket.getPlayerRating());
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Fehler bei recordClosedTicket: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Speichert einen neuen Kommentar/Reply auf ein Ticket.
|
||||
* Aktualisiert die Bewertung in ticket_stats wenn ein Spieler sein Ticket bewertet.
|
||||
* Wird von rateTicket() intern aufgerufen.
|
||||
*/
|
||||
private void updateStatsRating(int ticketId, String rating) {
|
||||
String sql = "UPDATE ticket_stats SET player_rating = ? WHERE ticket_id = ?";
|
||||
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setString(1, rating);
|
||||
ps.setInt(2, ticketId);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Fehler bei updateStatsRating: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gibt eine Liste aller Support-Mitarbeiter mit ihren Bewertungsstatistiken zurück.
|
||||
* Liest aus ticket_stats – unabhängig davon ob die Tickets noch in der DB existieren.
|
||||
*
|
||||
* Rückgabe: Liste von String-Arrays mit
|
||||
* [0] claimer_name, [1] thumbsUp, [2] thumbsDown, [3] total, [4] prozent
|
||||
*/
|
||||
public List<String[]> getStaffRatings() {
|
||||
List<String[]> result = new ArrayList<>();
|
||||
if (!useMySQL) return result;
|
||||
|
||||
String sql = """
|
||||
SELECT
|
||||
claimer_name,
|
||||
SUM(player_rating = 'THUMBS_UP') AS thumbs_up,
|
||||
SUM(player_rating = 'THUMBS_DOWN') AS thumbs_down,
|
||||
COUNT(*) AS total_closed
|
||||
FROM ticket_stats
|
||||
WHERE claimer_name IS NOT NULL
|
||||
GROUP BY claimer_uuid, claimer_name
|
||||
ORDER BY total_closed DESC
|
||||
""";
|
||||
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
|
||||
ResultSet rs = stmt.executeQuery(sql);
|
||||
while (rs.next()) {
|
||||
int up = rs.getInt("thumbs_up");
|
||||
int down = rs.getInt("thumbs_down");
|
||||
int total = rs.getInt("total_closed");
|
||||
int rated = up + down;
|
||||
String percent = rated > 0 ? Math.round(up * 100.0 / rated) + "%" : "–";
|
||||
result.add(new String[]{
|
||||
rs.getString("claimer_name"),
|
||||
String.valueOf(up),
|
||||
String.valueOf(down),
|
||||
String.valueOf(total),
|
||||
percent
|
||||
});
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Fehler bei getStaffRatings: " + e.getMessage(), e);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
// ─────────────────────────── BungeeCord Pending-Teleport ───────────────
|
||||
|
||||
/**
|
||||
* Speichert einen ausstehenden Teleport-Auftrag für einen Admin/Supporter.
|
||||
* Wird aufgerufen bevor der Spieler via BungeeCord auf den Ziel-Server
|
||||
* geschickt wird. Der PlayerJoinListener liest den Eintrag beim Ankommen.
|
||||
*/
|
||||
public void setPendingTeleport(UUID playerUUID, String world,
|
||||
double x, double y, double z,
|
||||
float yaw, float pitch) {
|
||||
if (!useMySQL) return;
|
||||
|
||||
String sql = """
|
||||
INSERT INTO ticket_pending_teleport
|
||||
(player_uuid, world, x, y, z, yaw, pitch)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?)
|
||||
ON DUPLICATE KEY UPDATE
|
||||
world = VALUES(world), x = VALUES(x), y = VALUES(y),
|
||||
z = VALUES(z), yaw = VALUES(yaw), pitch = VALUES(pitch),
|
||||
created_at = CURRENT_TIMESTAMP
|
||||
""";
|
||||
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
|
||||
ps.setString(1, playerUUID.toString());
|
||||
ps.setString(2, world);
|
||||
ps.setDouble(3, x);
|
||||
ps.setDouble(4, y);
|
||||
ps.setDouble(5, z);
|
||||
ps.setFloat(6, yaw);
|
||||
ps.setFloat(7, pitch);
|
||||
ps.executeUpdate();
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Fehler bei setPendingTeleport: " + e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Liest und löscht einen ausstehenden Teleport-Auftrag in einem Schritt.
|
||||
* Gibt null zurück wenn kein Auftrag vorhanden ist.
|
||||
*
|
||||
* Rückgabe: double[] { x, y, z, yaw, pitch } + world als Index 0 im String-Array
|
||||
* Vereinfacht: gibt ein Object[] zurück: [String world, double x, y, z, float yaw, pitch]
|
||||
*/
|
||||
public PendingTeleport consumePendingTeleport(UUID playerUUID) {
|
||||
if (!useMySQL) return null;
|
||||
|
||||
String selectSql = "SELECT * FROM ticket_pending_teleport WHERE player_uuid = ?";
|
||||
String deleteSql = "DELETE FROM ticket_pending_teleport WHERE player_uuid = ?";
|
||||
|
||||
try (Connection conn = getConnection();
|
||||
PreparedStatement sel = conn.prepareStatement(selectSql)) {
|
||||
sel.setString(1, playerUUID.toString());
|
||||
ResultSet rs = sel.executeQuery();
|
||||
if (!rs.next()) return null;
|
||||
|
||||
PendingTeleport pt = new PendingTeleport(
|
||||
rs.getString("world"),
|
||||
rs.getDouble("x"),
|
||||
rs.getDouble("y"),
|
||||
rs.getDouble("z"),
|
||||
rs.getFloat("yaw"),
|
||||
rs.getFloat("pitch")
|
||||
);
|
||||
|
||||
// Sofort löschen damit kein zweites Mal teleportiert wird
|
||||
try (PreparedStatement del = conn.prepareStatement(deleteSql)) {
|
||||
del.setString(1, playerUUID.toString());
|
||||
del.executeUpdate();
|
||||
}
|
||||
return pt;
|
||||
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Fehler bei consumePendingTeleport: " + e.getMessage(), e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/** Einfaches Daten-Objekt für einen ausstehenden Teleport-Auftrag. */
|
||||
public record PendingTeleport(String world, double x, double y, double z, float yaw, float pitch) {}
|
||||
|
||||
|
||||
|
||||
public boolean addComment(TicketComment comment) {
|
||||
if (useMySQL) {
|
||||
String sql = """
|
||||
@@ -527,7 +807,6 @@ public class DatabaseManager {
|
||||
}
|
||||
return false;
|
||||
} else {
|
||||
// YAML: comments.<ticketId>.<index>
|
||||
int index = dataConfig.getInt("comments." + comment.getTicketId() + ".count", 0);
|
||||
String base = "comments." + comment.getTicketId() + "." + index + ".";
|
||||
dataConfig.set(base + "authorUUID", comment.getAuthorUUID().toString());
|
||||
@@ -540,9 +819,6 @@ public class DatabaseManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt alle Kommentare für ein Ticket, sortiert nach Datum.
|
||||
*/
|
||||
public List<TicketComment> getComments(int ticketId) {
|
||||
List<TicketComment> list = new ArrayList<>();
|
||||
if (useMySQL) {
|
||||
@@ -582,12 +858,8 @@ public class DatabaseManager {
|
||||
return list;
|
||||
}
|
||||
|
||||
// ─────────────────────────── Pending Notifications ────────────────────
|
||||
// ─────────────────────────── Pending Notifications ─────────────────────
|
||||
|
||||
/**
|
||||
* Speichert eine Benachrichtigung für einen offline Spieler.
|
||||
* Wird beim nächsten Login angezeigt.
|
||||
*/
|
||||
public void addPendingNotification(UUID playerUUID, String rawMessage) {
|
||||
if (useMySQL) {
|
||||
String sql = "INSERT INTO ticket_pending_notifications (player_uuid, message) VALUES (?, ?)";
|
||||
@@ -607,9 +879,6 @@ public class DatabaseManager {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Lädt alle ausstehenden Benachrichtigungen für einen Spieler.
|
||||
*/
|
||||
public List<String> getPendingNotifications(UUID playerUUID) {
|
||||
List<String> messages = new ArrayList<>();
|
||||
if (useMySQL) {
|
||||
@@ -627,9 +896,6 @@ public class DatabaseManager {
|
||||
return messages;
|
||||
}
|
||||
|
||||
/**
|
||||
* Löscht alle ausstehenden Benachrichtigungen eines Spielers nach dem Anzeigen.
|
||||
*/
|
||||
public void clearPendingNotifications(UUID playerUUID) {
|
||||
if (useMySQL) {
|
||||
String sql = "DELETE FROM ticket_pending_notifications WHERE player_uuid = ?";
|
||||
@@ -645,7 +911,7 @@ public class DatabaseManager {
|
||||
}
|
||||
}
|
||||
|
||||
// ─────────────────────────── [NEW] Blacklist ───────────────────────────
|
||||
// ─────────────────────────── Blacklist ─────────────────────────────────
|
||||
|
||||
public boolean isBlacklisted(UUID uuid) {
|
||||
if (useMySQL) {
|
||||
@@ -705,7 +971,6 @@ public class DatabaseManager {
|
||||
}
|
||||
}
|
||||
|
||||
/** Gibt alle gesperrten Spieler als Liste von String-Arrays {uuid, name, reason, bannedBy} zurück. */
|
||||
public List<String[]> getBlacklist() {
|
||||
List<String[]> list = new ArrayList<>();
|
||||
if (useMySQL) {
|
||||
@@ -890,28 +1155,69 @@ public class DatabaseManager {
|
||||
// ─────────────────────────── Statistiken ───────────────────────────────
|
||||
|
||||
public TicketStats getTicketStats() {
|
||||
// Aktuelle Live-Daten aus der tickets-Tabelle
|
||||
List<Ticket> all = getAllTickets();
|
||||
int open = 0, claimed = 0, forwarded = 0, closed = 0, thumbsUp = 0, thumbsDown = 0;
|
||||
int open = 0, claimed = 0, forwarded = 0, closedLive = 0;
|
||||
java.util.Map<String, Integer> byPlayer = new java.util.HashMap<>();
|
||||
java.util.Map<String, Integer> byServer = new java.util.HashMap<>();
|
||||
for (Ticket t : all) {
|
||||
switch (t.getStatus()) {
|
||||
case OPEN -> open++;
|
||||
case CLAIMED -> claimed++;
|
||||
case FORWARDED -> forwarded++;
|
||||
case CLOSED -> closed++;
|
||||
case CLOSED -> closedLive++;
|
||||
}
|
||||
if ("THUMBS_UP".equals(t.getPlayerRating())) thumbsUp++;
|
||||
if ("THUMBS_DOWN".equals(t.getPlayerRating())) thumbsDown++;
|
||||
byPlayer.merge(t.getCreatorName(), 1, Integer::sum);
|
||||
byServer.merge(t.getServerName(), 1, Integer::sum);
|
||||
}
|
||||
return new TicketStats(all.size(), open, closed, forwarded, thumbsUp, thumbsDown, byPlayer);
|
||||
|
||||
// Historische Bewertungen aus der persistenten Stats-Tabelle lesen
|
||||
// (enthält auch Daten von bereits gelöschten/archivierten Tickets)
|
||||
int thumbsUp = 0, thumbsDown = 0, totalClosedEver = closedLive;
|
||||
if (useMySQL) {
|
||||
String sql = """
|
||||
SELECT
|
||||
COUNT(*) AS total,
|
||||
SUM(player_rating = 'THUMBS_UP') AS up,
|
||||
SUM(player_rating = 'THUMBS_DOWN') AS down
|
||||
FROM ticket_stats
|
||||
""";
|
||||
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
|
||||
ResultSet rs = stmt.executeQuery(sql);
|
||||
if (rs.next()) {
|
||||
totalClosedEver = rs.getInt("total");
|
||||
thumbsUp = rs.getInt("up");
|
||||
thumbsDown = rs.getInt("down");
|
||||
}
|
||||
} catch (SQLException e) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Fehler beim Laden der persistenten Stats: " + e.getMessage(), e);
|
||||
// Fallback: Bewertungen aus Live-Daten lesen
|
||||
for (Ticket t : all) {
|
||||
if ("THUMBS_UP".equals(t.getPlayerRating())) thumbsUp++;
|
||||
if ("THUMBS_DOWN".equals(t.getPlayerRating())) thumbsDown++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Datei-Modus: nur Live-Daten verfügbar
|
||||
for (Ticket t : all) {
|
||||
if ("THUMBS_UP".equals(t.getPlayerRating())) thumbsUp++;
|
||||
if ("THUMBS_DOWN".equals(t.getPlayerRating())) thumbsDown++;
|
||||
}
|
||||
}
|
||||
|
||||
return new TicketStats(all.size(), open, totalClosedEver, forwarded, thumbsUp, thumbsDown, byPlayer, byServer);
|
||||
}
|
||||
|
||||
public static class TicketStats {
|
||||
public final int total, open, closed, forwarded, thumbsUp, thumbsDown;
|
||||
public final java.util.Map<String, Integer> byPlayer;
|
||||
/** BungeeCord: Anzahl Tickets pro Server */
|
||||
public final java.util.Map<String, Integer> byServer;
|
||||
|
||||
public TicketStats(int total, int open, int closed, int forwarded,
|
||||
int thumbsUp, int thumbsDown, java.util.Map<String, Integer> byPlayer) {
|
||||
int thumbsUp, int thumbsDown,
|
||||
java.util.Map<String, Integer> byPlayer,
|
||||
java.util.Map<String, Integer> byServer) {
|
||||
this.total = total;
|
||||
this.open = open;
|
||||
this.closed = closed;
|
||||
@@ -919,6 +1225,7 @@ public class DatabaseManager {
|
||||
this.thumbsUp = thumbsUp;
|
||||
this.thumbsDown = thumbsDown;
|
||||
this.byPlayer = byPlayer;
|
||||
this.byServer = byServer;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -998,6 +1305,10 @@ public class DatabaseManager {
|
||||
try { t.setPriority(TicketPriority.fromString(rs.getString("priority"))); } catch (SQLException ignored) {}
|
||||
try { t.setPlayerRating(rs.getString("player_rating")); } catch (SQLException ignored) {}
|
||||
try { t.setClaimerNotified(rs.getBoolean("claimer_notified")); } catch (SQLException ignored) {}
|
||||
// Bug-Fix: close_notified für duplikat-freie Schließ-Benachrichtigungen
|
||||
try { t.setCloseNotified(rs.getBoolean("close_notified")); } catch (SQLException ignored) {}
|
||||
// BungeeCord: Server-Name einlesen
|
||||
try { t.setServerName(rs.getString("server_name")); } catch (SQLException ignored) {}
|
||||
return t;
|
||||
}
|
||||
|
||||
@@ -1032,6 +1343,9 @@ public class DatabaseManager {
|
||||
obj.put("priority", t.getPriority().name());
|
||||
if (t.getPlayerRating() != null) obj.put("playerRating", t.getPlayerRating());
|
||||
obj.put("claimerNotified", t.isClaimerNotified());
|
||||
obj.put("closeNotified", t.isCloseNotified());
|
||||
// BungeeCord: Server-Name im JSON-Export
|
||||
obj.put("serverName", t.getServerName());
|
||||
return obj;
|
||||
}
|
||||
|
||||
@@ -1060,6 +1374,9 @@ public class DatabaseManager {
|
||||
if (obj.containsKey("priority")) t.setPriority(TicketPriority.fromString((String) obj.get("priority")));
|
||||
if (obj.containsKey("playerRating")) t.setPlayerRating((String) obj.get("playerRating"));
|
||||
if (obj.containsKey("claimerNotified"))t.setClaimerNotified((Boolean) obj.get("claimerNotified"));
|
||||
if (obj.containsKey("closeNotified")) t.setCloseNotified((Boolean) obj.get("closeNotified"));
|
||||
// BungeeCord: Server-Name aus JSON
|
||||
if (obj.containsKey("serverName")) t.setServerName((String) obj.get("serverName"));
|
||||
return t;
|
||||
} catch (Exception e) {
|
||||
if (plugin != null) plugin.getLogger().severe("Fehler beim Parsen eines Tickets: " + e.getMessage());
|
||||
|
||||
Reference in New Issue
Block a user