Update from Git Manager GUI

This commit is contained in:
2026-02-19 22:48:59 +01:00
parent 38af7322a4
commit f8498614b7
12 changed files with 2020 additions and 0 deletions

View File

@@ -0,0 +1,763 @@
package de.ticketsystem.database;
import java.io.File;
import java.io.IOException;
import org.bukkit.configuration.file.YamlConfiguration;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import de.ticketsystem.TicketPlugin;
import de.ticketsystem.model.Ticket;
import de.ticketsystem.model.TicketStatus;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.logging.Level;
import java.io.FileReader;
import java.io.FileWriter;
import org.bukkit.Bukkit;
public class DatabaseManager {
// Test-Konstruktor für Unit-Tests (ohne Bukkit/Plugin)
public DatabaseManager(File dataFile, YamlConfiguration dataConfig) {
this.plugin = null;
this.useMySQL = false;
this.useJson = false;
this.dataFileName = dataFile.getName();
this.archiveFileName = "archive.json";
this.dataFile = dataFile;
this.dataConfig = dataConfig;
validateLoadedTickets();
}
/**
* Archiviert alle geschlossenen Tickets in eine separate Datei und entfernt sie aus dem aktiven Speicher.
* @return Anzahl archivierter Tickets
*/
public int archiveClosedTickets() {
List<Ticket> all = getAllTickets();
List<Ticket> toArchive = new ArrayList<>();
for (Ticket t : all) {
if (t.getStatus() == TicketStatus.CLOSED) toArchive.add(t);
}
if (toArchive.isEmpty()) return 0;
File archiveFile = new File(plugin.getDataFolder(), archiveFileName);
JSONArray arr = new JSONArray();
// Bestehendes Archiv laden
if (archiveFile.exists()) {
try (FileReader fr = new FileReader(archiveFile)) {
JSONParser parser = new JSONParser();
Object parsed = parser.parse(fr);
if (parsed instanceof JSONArray oldArr) arr.addAll(oldArr);
} catch (Exception ignored) {}
}
for (Ticket t : toArchive) arr.add(ticketToJson(t));
try (FileWriter fw = new FileWriter(archiveFile)) {
fw.write(arr.toJSONString());
} catch (Exception e) {
sendError("Fehler beim Archivieren: " + e.getMessage());
return 0;
}
// Entferne archivierte Tickets aus aktivem Speicher
int removed = 0;
if (useMySQL) {
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement("DELETE FROM tickets WHERE id = ?")) {
for (Ticket t : toArchive) {
ps.setInt(1, t.getId());
ps.executeUpdate();
removed++;
}
} catch (Exception e) {
sendError("Fehler beim Entfernen archivierter Tickets: " + e.getMessage());
}
} else {
for (Ticket t : toArchive) {
dataConfig.set("tickets." + t.getId(), null);
removed++;
}
try { dataConfig.save(dataFile); } catch (Exception e) { sendError("Fehler beim Speichern nach Archivierung: " + e.getMessage()); }
}
return removed;
}
/**
* Liefert Statistiken über Tickets.
*/
public TicketStats getTicketStats() {
List<Ticket> all = getAllTickets();
int open = 0, closed = 0, forwarded = 0;
java.util.Map<String, Integer> byPlayer = new java.util.HashMap<>();
for (Ticket t : all) {
switch (t.getStatus()) {
case OPEN -> open++;
case CLOSED -> closed++;
case FORWARDED -> forwarded++;
}
byPlayer.merge(t.getCreatorName(), 1, Integer::sum);
}
return new TicketStats(all.size(), open, closed, forwarded, byPlayer);
}
public static class TicketStats {
public final int total, open, closed, forwarded;
public final java.util.Map<String, Integer> byPlayer;
public TicketStats(int total, int open, int closed, int forwarded, java.util.Map<String, Integer> byPlayer) {
this.total = total; this.open = open; this.closed = closed; this.forwarded = forwarded; this.byPlayer = byPlayer;
}
}
/**
* Exportiert alle Tickets als JSON-Datei.
* @param exportFile Ziel-Datei
* @return Anzahl exportierter Tickets
*/
public int exportTickets(File exportFile) {
List<Ticket> tickets = getAllTickets();
JSONArray arr = new JSONArray();
for (Ticket t : tickets) {
arr.add(ticketToJson(t));
}
try (FileWriter fw = new FileWriter(exportFile)) {
fw.write(arr.toJSONString());
return tickets.size();
} catch (IOException e) {
sendError("Fehler beim Export: " + e.getMessage());
return 0;
}
}
/**
* Importiert Tickets aus einer JSON-Datei.
* @param importFile Quell-Datei
* @return Anzahl importierter Tickets
*/
public int importTickets(File importFile) {
int imported = 0;
try (FileReader fr = new FileReader(importFile)) {
JSONParser parser = new JSONParser();
JSONArray arr = (JSONArray) parser.parse(fr);
for (Object o : arr) {
JSONObject obj = (JSONObject) o;
Ticket t = ticketFromJson(obj);
if (t != null) {
int id = createTicket(t);
if (id != -1) imported++;
}
}
} catch (Exception e) {
sendError("Fehler beim Import: " + e.getMessage());
}
return imported;
}
/**
* Gibt alle Tickets (egal welcher Status) zurück.
*/
public List<Ticket> getAllTickets() {
List<Ticket> list = new ArrayList<>();
if (useMySQL) {
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT * FROM tickets");
while (rs.next()) list.add(mapRow(rs));
} catch (SQLException e) {
sendError("Fehler beim Abrufen aller Tickets: " + e.getMessage());
}
} else {
if (dataConfig.contains("tickets")) {
for (String key : dataConfig.getConfigurationSection("tickets").getKeys(false)) {
Ticket t = (Ticket) dataConfig.get("tickets." + key);
if (t != null) list.add(t);
}
}
}
return list;
}
// Hilfsmethoden für JSON-Konvertierung
private JSONObject ticketToJson(Ticket t) {
JSONObject obj = new JSONObject();
obj.put("id", t.getId());
obj.put("creatorUUID", t.getCreatorUUID().toString());
obj.put("creatorName", t.getCreatorName());
obj.put("message", t.getMessage());
obj.put("world", t.getWorldName());
obj.put("x", t.getX());
obj.put("y", t.getY());
obj.put("z", t.getZ());
obj.put("yaw", t.getYaw());
obj.put("pitch", t.getPitch());
obj.put("status", t.getStatus().name());
obj.put("createdAt", t.getCreatedAt() != null ? t.getCreatedAt().getTime() : null);
obj.put("claimedAt", t.getClaimedAt() != null ? t.getClaimedAt().getTime() : null);
obj.put("closedAt", t.getClosedAt() != null ? t.getClosedAt().getTime() : null);
if (t.getClaimerUUID() != null) obj.put("claimerUUID", t.getClaimerUUID().toString());
if (t.getClaimerName() != null) obj.put("claimerName", t.getClaimerName());
if (t.getForwardedToUUID() != null) obj.put("forwardedToUUID", t.getForwardedToUUID().toString());
if (t.getForwardedToName() != null) obj.put("forwardedToName", t.getForwardedToName());
return obj;
}
private Ticket ticketFromJson(JSONObject obj) {
try {
Ticket t = new Ticket();
t.setId(((Long)obj.get("id")).intValue());
t.setCreatorUUID(UUID.fromString((String)obj.get("creatorUUID")));
t.setCreatorName((String)obj.get("creatorName"));
t.setMessage((String)obj.get("message"));
t.setWorldName((String)obj.get("world"));
t.setX((Double)obj.get("x"));
t.setY((Double)obj.get("y"));
t.setZ((Double)obj.get("z"));
t.setYaw(((Double)obj.get("yaw")).floatValue());
t.setPitch(((Double)obj.get("pitch")).floatValue());
t.setStatus(TicketStatus.valueOf((String)obj.get("status")));
if (obj.get("createdAt") != null) t.setCreatedAt(new java.sql.Timestamp((Long)obj.get("createdAt")));
if (obj.get("claimedAt") != null) t.setClaimedAt(new java.sql.Timestamp((Long)obj.get("claimedAt")));
if (obj.get("closedAt") != null) t.setClosedAt(new java.sql.Timestamp((Long)obj.get("closedAt")));
if (obj.get("claimerUUID") != null) t.setClaimerUUID(UUID.fromString((String)obj.get("claimerUUID")));
if (obj.get("claimerName") != null) t.setClaimerName((String)obj.get("claimerName"));
if (obj.get("forwardedToUUID") != null) t.setForwardedToUUID(UUID.fromString((String)obj.get("forwardedToUUID")));
if (obj.get("forwardedToName") != null) t.setForwardedToName((String)obj.get("forwardedToName"));
return t;
} catch (Exception e) {
plugin.getLogger().severe("Fehler beim Parsen eines Tickets: " + e.getMessage());
return null;
}
}
/**
* Migriert alle Tickets aus data.yml nach MySQL.
*/
public int migrateToMySQL() {
if (useMySQL || dataConfig == null) return 0;
int migrated = 0;
try {
for (String key : dataConfig.getConfigurationSection("tickets").getKeys(false)) {
Ticket t = (Ticket) dataConfig.get("tickets." + key);
if (t != null) {
// Ticket in MySQL speichern
useMySQL = true;
int id = createTicket(t);
useMySQL = false;
if (id != -1) migrated++;
}
}
} catch (Exception e) {
plugin.getLogger().severe("Fehler bei Migration zu MySQL: " + e.getMessage());
}
return migrated;
}
/**
* Migriert alle Tickets aus MySQL nach data.yml.
*/
public int migrateToFile() {
if (!useMySQL) return 0;
int migrated = 0;
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery("SELECT * FROM tickets");
while (rs.next()) {
Ticket t = mapRow(rs);
if (t != null) {
useMySQL = false;
int id = createTicket(t);
useMySQL = true;
if (id != -1) migrated++;
}
}
} catch (Exception e) {
plugin.getLogger().severe("Fehler bei Migration zu Datei: " + e.getMessage());
}
return migrated;
}
private String dataFileName;
private String archiveFileName;
// Prüft geladene Tickets auf Korrektheit (Platzhalter)
private void validateLoadedTickets() {
if (dataConfig == null || !dataConfig.contains("tickets")) return;
int invalid = 0;
for (String key : dataConfig.getConfigurationSection("tickets").getKeys(false)) {
Object obj = dataConfig.get("tickets." + key);
if (!(obj instanceof Ticket t)) {
sendError("Ungültiges Ticket-Objekt bei ID: " + key);
invalid++;
continue;
}
if (t.getCreatorUUID() == null || t.getCreatorName() == null || t.getMessage() == null || t.getStatus() == null) {
sendError("Ticket mit fehlenden Pflichtfeldern: ID " + key);
invalid++;
}
try { UUID.fromString(t.getCreatorUUID().toString()); } catch (Exception e) {
sendError("Ungültige UUID bei Ticket ID: " + key);
invalid++;
}
try { TicketStatus.valueOf(t.getStatus().name()); } catch (Exception e) {
sendError("Ungültiger Status bei Ticket ID: " + key);
invalid++;
}
}
if (invalid > 0) {
String msg = plugin != null ? plugin.formatMessage("messages.validation-warning").replace("{count}", String.valueOf(invalid)) : (invalid + " ungültige Tickets beim Laden gefunden.");
sendError(msg);
}
}
// Backup der MySQL-Datenbank (Platzhalter)
private void backupMySQL() {
// TODO: Implementiere Backup-Logik für MySQL
}
// Backup der Datei-basierten Daten (Platzhalter)
private void backupDataFile() {
// TODO: Implementiere Backup-Logik für data.yml/data.json
}
private final TicketPlugin plugin;
private HikariDataSource dataSource;
private boolean useMySQL;
private boolean useJson;
private File dataFile;
private YamlConfiguration dataConfig;
private JSONArray dataJson;
public DatabaseManager(TicketPlugin plugin) {
this.plugin = plugin;
this.useMySQL = plugin.getConfig().getBoolean("use-mysql", true);
this.useJson = plugin.getConfig().getBoolean("use-json", false);
if (plugin.isDebug()) plugin.getLogger().info("[DEBUG] DatabaseManager initialisiert. useMySQL=" + useMySQL + ", useJson=" + useJson);
// Speicherpfade aus config.yml (absolut oder relativ zum Plugin-Ordner)
String dataPath = plugin.getConfig().getString("data-file", useJson ? "data.json" : "data.yml");
String archivePath = plugin.getConfig().getString("archive-file", "archive.json");
this.dataFileName = dataPath;
this.archiveFileName = archivePath;
if (!useMySQL) {
if (plugin.isDebug()) plugin.getLogger().info("[DEBUG] Datei-Speicher wird verwendet: " + dataPath);
if (useJson) {
dataFile = resolvePath(dataPath);
if (plugin.isDebug()) plugin.getLogger().info("[DEBUG] JSON-Datei: " + dataFile.getAbsolutePath());
if (!dataFile.exists()) {
try {
dataFile.getParentFile().mkdirs();
dataFile.createNewFile();
dataJson = new JSONArray();
} catch (IOException e) {
sendError("Konnte " + dataPath + " nicht erstellen: " + e.getMessage());
}
} else {
try {
JSONParser parser = new JSONParser();
dataJson = (JSONArray) parser.parse(new java.io.FileReader(dataFile));
} catch (Exception e) {
sendError("Konnte " + dataPath + " nicht laden: " + e.getMessage());
dataJson = new JSONArray();
}
}
} else {
dataFile = resolvePath(dataPath);
if (plugin.isDebug()) plugin.getLogger().info("[DEBUG] YAML-Datei: " + dataFile.getAbsolutePath());
if (!dataFile.exists()) {
try {
dataFile.getParentFile().mkdirs();
dataFile.createNewFile();
} catch (IOException e) {
sendError("Konnte " + dataPath + " nicht erstellen: " + e.getMessage());
}
}
dataConfig = YamlConfiguration.loadConfiguration(dataFile);
}
validateLoadedTickets();
}
}
// Hilfsfunktion: Absoluten oder relativen Pfad auflösen
private File resolvePath(String path) {
File f = new File(path);
if (f.isAbsolute()) return f;
return new File(plugin.getDataFolder(), path);
}
// Fehlerausgabe im Chat und Log
private void sendError(String msg) {
if (plugin != null) plugin.getLogger().severe(msg);
// Fehler an alle Admins im Chat senden
Bukkit.getOnlinePlayers().stream().filter(p -> p.hasPermission("ticket.admin")).forEach(p -> p.sendMessage("§c[TicketSystem] " + msg));
}
// ─────────────────────────── Verbindung ────────────────────────────────
public boolean connect() {
if (useMySQL) {
try {
HikariConfig config = new HikariConfig();
config.setJdbcUrl(String.format("jdbc:mysql://%s:%d/%s?useSSL=false&autoReconnect=true&characterEncoding=UTF-8",
plugin.getConfig().getString("mysql.host"),
plugin.getConfig().getInt("mysql.port"),
plugin.getConfig().getString("mysql.database")));
config.setUsername(plugin.getConfig().getString("mysql.username"));
config.setPassword(plugin.getConfig().getString("mysql.password"));
config.setMaximumPoolSize(plugin.getConfig().getInt("mysql.pool-size", 10));
config.setConnectionTimeout(plugin.getConfig().getLong("mysql.connection-timeout", 30000));
config.setPoolName("TicketSystem-Pool");
config.addDataSourceProperty("cachePrepStmts", "true");
config.addDataSourceProperty("prepStmtCacheSize", "250");
config.addDataSourceProperty("prepStmtCacheSqlLimit", "2048");
dataSource = new HikariDataSource(config);
createTables();
plugin.getLogger().info("MySQL-Verbindung erfolgreich hergestellt.");
return true;
} catch (Exception e) {
plugin.getLogger().log(Level.SEVERE, "Fehler beim Verbinden mit MySQL: " + e.getMessage(), e);
plugin.getLogger().warning("Weiche auf Datei-Speicherung (data.yml) aus!");
useMySQL = false;
// Datei-Storage initialisieren
dataFile = new File(plugin.getDataFolder(), "data.yml");
if (!dataFile.exists()) {
try {
dataFile.getParentFile().mkdirs();
dataFile.createNewFile();
} catch (IOException ex) {
plugin.getLogger().severe("Konnte data.yml nicht erstellen: " + ex.getMessage());
}
}
dataConfig = YamlConfiguration.loadConfiguration(dataFile);
return true;
}
} else {
plugin.getLogger().info("MySQL deaktiviert. Verwende Datei-Speicherung (data.yml).");
return true;
}
}
public void disconnect() {
if (useMySQL && dataSource != null && !dataSource.isClosed()) {
dataSource.close();
plugin.getLogger().info("MySQL-Verbindung getrennt.");
}
// Bei Datei-Storage nichts zu tun
}
private Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
// ─────────────────────────── Tabellen erstellen ────────────────────────
private void createTables() {
String sql = """
CREATE TABLE IF NOT EXISTS tickets (
id INT AUTO_INCREMENT PRIMARY KEY,
creator_uuid VARCHAR(36) NOT NULL,
creator_name VARCHAR(16) NOT NULL,
message VARCHAR(255) NOT NULL,
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,
status VARCHAR(16) NOT NULL DEFAULT 'OPEN',
claimer_uuid VARCHAR(36),
claimer_name VARCHAR(16),
forwarded_to_uuid VARCHAR(36),
forwarded_to_name VARCHAR(16),
created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
claimed_at TIMESTAMP,
closed_at TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
""";
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
stmt.execute(sql);
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler beim Erstellen der Tabellen: " + e.getMessage(), e);
}
}
// ─────────────────────────── CRUD ──────────────────────────────────────
/**
* Speichert ein neues Ticket in der DB und gibt die generierte ID zurück.
*/
public int createTicket(Ticket ticket) {
if (useMySQL) {
String sql = """
INSERT INTO tickets (creator_uuid, creator_name, message, world, x, y, z, yaw, pitch)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
""";
try (Connection conn = getConnection();
PreparedStatement ps = conn.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS)) {
ps.setString(1, ticket.getCreatorUUID().toString());
ps.setString(2, ticket.getCreatorName());
ps.setString(3, ticket.getMessage());
ps.setString(4, ticket.getWorldName());
ps.setDouble(5, ticket.getX());
ps.setDouble(6, ticket.getY());
ps.setDouble(7, ticket.getZ());
ps.setFloat(8, ticket.getYaw());
ps.setFloat(9, ticket.getPitch());
ps.executeUpdate();
ResultSet rs = ps.getGeneratedKeys();
if (rs.next()) {
backupMySQL();
return rs.getInt(1);
}
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler beim Erstellen des Tickets: " + e.getMessage(), e);
}
return -1;
} else {
// Datei-Storage: Ticket-ID generieren
int id = dataConfig.getInt("lastId", 0) + 1;
ticket.setId(id);
dataConfig.set("lastId", id);
dataConfig.set("tickets." + id, ticket);
try {
dataConfig.save(dataFile);
backupDataFile();
} catch (IOException e) {
plugin.getLogger().severe("Fehler beim Speichern von data.yml: " + e.getMessage());
Bukkit.getOnlinePlayers().stream().filter(p -> p.hasPermission("ticket.admin")).forEach(p -> p.sendMessage("§c[TicketSystem] Fehler beim Speichern von data.yml: " + e.getMessage()));
}
return id;
}
}
/**
* Claimt ein Ticket (Status → CLAIMED).
*/
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()
WHERE id = ? AND status = 'OPEN'
""";
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, claimerUUID.toString());
ps.setString(2, claimerName);
ps.setInt(3, ticketId);
return ps.executeUpdate() > 0;
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler beim Claimen des Tickets: " + e.getMessage(), e);
}
return false;
} else {
Ticket t = getTicketById(ticketId);
if (t == null || t.getStatus() != TicketStatus.OPEN) return false;
t.setStatus(TicketStatus.CLAIMED);
t.setClaimerUUID(claimerUUID);
t.setClaimerName(claimerName);
t.setClaimedAt(new java.sql.Timestamp(System.currentTimeMillis()));
dataConfig.set("tickets." + ticketId, t);
try {
dataConfig.save(dataFile);
backupDataFile();
} catch (IOException e) {
plugin.getLogger().severe("Fehler beim Speichern von data.yml: " + e.getMessage());
}
return true;
}
}
/**
* Schließt ein Ticket (Status → CLOSED).
*/
public boolean closeTicket(int ticketId) {
if (useMySQL) {
String sql = "UPDATE tickets SET status = 'CLOSED', closed_at = NOW() WHERE id = ? AND status != 'CLOSED'";
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, ticketId);
return ps.executeUpdate() > 0;
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler beim Schließen des Tickets: " + e.getMessage(), e);
}
return false;
} else {
Ticket t = getTicketById(ticketId);
if (t == null || t.getStatus() == TicketStatus.CLOSED) return false;
t.setStatus(TicketStatus.CLOSED);
t.setClosedAt(new java.sql.Timestamp(System.currentTimeMillis()));
dataConfig.set("tickets." + ticketId, t);
try {
dataConfig.save(dataFile);
backupDataFile();
} catch (IOException e) {
plugin.getLogger().severe("Fehler beim Speichern von data.yml: " + e.getMessage());
}
return true;
}
}
/**
* Leitet ein Ticket an einen anderen Supporter weiter (Status → FORWARDED).
*/
public boolean forwardTicket(int ticketId, UUID toUUID, String toName) {
if (useMySQL) {
String sql = """
UPDATE tickets SET status = 'FORWARDED', forwarded_to_uuid = ?, forwarded_to_name = ?
WHERE id = ? AND status != 'CLOSED'
""";
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, toUUID.toString());
ps.setString(2, toName);
ps.setInt(3, ticketId);
return ps.executeUpdate() > 0;
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler beim Weiterleiten des Tickets: " + e.getMessage(), e);
}
return false;
} else {
Ticket t = getTicketById(ticketId);
if (t == null || t.getStatus() == TicketStatus.CLOSED) return false;
t.setStatus(TicketStatus.FORWARDED);
t.setForwardedToUUID(toUUID);
t.setForwardedToName(toName);
dataConfig.set("tickets." + ticketId, t);
try {
dataConfig.save(dataFile);
backupDataFile();
} catch (IOException e) {
plugin.getLogger().severe("Fehler beim Speichern von data.yml: " + e.getMessage());
}
return true;
}
}
/**
* Gibt alle Tickets mit einem bestimmten Status zurück.
*/
public List<Ticket> getTicketsByStatus(TicketStatus... statuses) {
List<Ticket> list = new ArrayList<>();
if (statuses.length == 0) return list;
if (useMySQL) {
StringBuilder placeholders = new StringBuilder("?");
for (int i = 1; i < statuses.length; i++) placeholders.append(",?");
String sql = "SELECT * FROM tickets WHERE status IN (" + placeholders + ") ORDER BY created_at ASC";
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
for (int i = 0; i < statuses.length; i++) ps.setString(i + 1, statuses[i].name());
ResultSet rs = ps.executeQuery();
while (rs.next()) list.add(mapRow(rs));
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler beim Abrufen der Tickets: " + e.getMessage(), e);
}
return list;
} else {
// Datei-Storage: Alle Tickets filtern
if (dataConfig.contains("tickets")) {
for (String key : dataConfig.getConfigurationSection("tickets").getKeys(false)) {
Ticket t = (Ticket) dataConfig.get("tickets." + key);
for (TicketStatus status : statuses) {
if (t != null && t.getStatus() == status) list.add(t);
}
}
}
return list;
}
}
/**
* Gibt ein einzelnes Ticket anhand der ID zurück.
*/
public Ticket getTicketById(int id) {
if (useMySQL) {
String sql = "SELECT * FROM tickets WHERE id = ?";
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setInt(1, id);
ResultSet rs = ps.executeQuery();
if (rs.next()) return mapRow(rs);
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler beim Abrufen des Tickets: " + e.getMessage(), e);
}
return null;
} else {
if (dataConfig.contains("tickets." + id)) {
return (Ticket) dataConfig.get("tickets." + id);
}
return null;
}
}
/**
* Anzahl offener Tickets (OPEN + FORWARDED) für Join-Benachrichtigung.
*/
public int countOpenTickets() {
if (useMySQL) {
String sql = "SELECT COUNT(*) FROM tickets WHERE status IN ('OPEN', 'FORWARDED', 'CLAIMED')";
try (Connection conn = getConnection(); Statement stmt = conn.createStatement()) {
ResultSet rs = stmt.executeQuery(sql);
if (rs.next()) return rs.getInt(1);
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler beim Zählen der Tickets: " + e.getMessage(), e);
}
return 0;
} else {
int count = 0;
if (dataConfig.contains("tickets")) {
for (String key : dataConfig.getConfigurationSection("tickets").getKeys(false)) {
Ticket t = (Ticket) dataConfig.get("tickets." + key);
if (t != null && (t.getStatus() == TicketStatus.OPEN || t.getStatus() == TicketStatus.FORWARDED || t.getStatus() == TicketStatus.CLAIMED)) count++;
}
}
return count;
}
}
/**
* Anzahl offener Tickets eines bestimmten Spielers.
*/
public int countOpenTicketsByPlayer(UUID uuid) {
if (useMySQL) {
String sql = "SELECT COUNT(*) FROM tickets WHERE creator_uuid = ? AND status IN ('OPEN', 'CLAIMED', 'FORWARDED')";
try (Connection conn = getConnection(); PreparedStatement ps = conn.prepareStatement(sql)) {
ps.setString(1, uuid.toString());
ResultSet rs = ps.executeQuery();
if (rs.next()) return rs.getInt(1);
} catch (SQLException e) {
plugin.getLogger().log(Level.SEVERE, "Fehler: " + e.getMessage(), e);
}
return 0;
} else {
int count = 0;
if (dataConfig.contains("tickets")) {
for (String key : dataConfig.getConfigurationSection("tickets").getKeys(false)) {
Ticket t = (Ticket) dataConfig.get("tickets." + key);
if (t != null && uuid.equals(t.getCreatorUUID()) && (t.getStatus() == TicketStatus.OPEN || t.getStatus() == TicketStatus.CLAIMED || t.getStatus() == TicketStatus.FORWARDED)) count++;
}
}
return count;
}
}
// ─────────────────────────── Mapping ───────────────────────────────────
private Ticket mapRow(ResultSet rs) throws SQLException {
File archiveFile = new File(plugin.getDataFolder(), archiveFileName);
Ticket t = new Ticket();
t.setId(rs.getInt("id"));
t.setCreatorUUID(UUID.fromString(rs.getString("creator_uuid")));
t.setCreatorName(rs.getString("creator_name"));
t.setMessage(rs.getString("message"));
t.setWorldName(rs.getString("world"));
t.setX(rs.getDouble("x"));
t.setY(rs.getDouble("y"));
t.setZ(rs.getDouble("z"));
t.setYaw(rs.getFloat("yaw"));
t.setPitch(rs.getFloat("pitch"));
t.setStatus(TicketStatus.valueOf(rs.getString("status")));
t.setCreatedAt(rs.getTimestamp("created_at"));
t.setClaimedAt(rs.getTimestamp("claimed_at"));
t.setClosedAt(rs.getTimestamp("closed_at"));
String claimerUUID = rs.getString("claimer_uuid");
if (claimerUUID != null) {
t.setClaimerUUID(UUID.fromString(claimerUUID));
t.setClaimerName(rs.getString("claimer_name"));
}
String fwdUUID = rs.getString("forwarded_to_uuid");
if (fwdUUID != null) {
t.setForwardedToUUID(UUID.fromString(fwdUUID));
t.setForwardedToName(rs.getString("forwarded_to_name"));
}
return t;
}
}