diff --git a/resources/[tools]/nordi_license/server/main.lua b/resources/[tools]/nordi_license/server/main.lua index 1003b5f9d..7a01a8a88 100644 --- a/resources/[tools]/nordi_license/server/main.lua +++ b/resources/[tools]/nordi_license/server/main.lua @@ -97,7 +97,19 @@ CreateThread(function() end end) --- Lizenz aus Datenbank abrufen +-- Spieler-Name aus JSON extrahieren (MariaDB-kompatibel) +local function extractPlayerName(charinfo_json) + if not charinfo_json then return "Unbekannt" end + + local success, charinfo = pcall(json.decode, charinfo_json) + if success and charinfo and charinfo.firstname and charinfo.lastname then + return charinfo.firstname .. " " .. charinfo.lastname + end + + return "Unbekannt" +end + +-- Lizenz aus Datenbank abrufen (KORRIGIERT für MariaDB) local function getLicenseFromDB(citizenid, licenseType) debugPrint("=== getLicenseFromDB START ===") debugPrint("CitizenID: " .. tostring(citizenid)) @@ -109,11 +121,11 @@ local function getLicenseFromDB(citizenid, licenseType) return cached end + -- MariaDB-kompatible Query ohne JSON-Operatoren local query = [[ SELECT pl.*, - CONCAT(p.charinfo->>'$.firstname', ' ', p.charinfo->>'$.lastname') as holder_name, - pi.charinfo->>'$.firstname' as issued_by_firstname, - pi.charinfo->>'$.lastname' as issued_by_lastname + p.charinfo as holder_charinfo, + pi.charinfo as issued_by_charinfo FROM player_licenses pl LEFT JOIN players p ON p.citizenid = pl.citizenid LEFT JOIN players pi ON pi.citizenid = pl.issued_by @@ -127,10 +139,12 @@ local function getLicenseFromDB(citizenid, licenseType) if result and #result > 0 then local license = result[1] - -- Issued by Name zusammensetzen - if license.issued_by_firstname and license.issued_by_lastname then - license.issued_by_name = license.issued_by_firstname .. " " .. license.issued_by_lastname - else + -- Namen aus JSON extrahieren + license.holder_name = extractPlayerName(license.holder_charinfo) + license.issued_by_name = extractPlayerName(license.issued_by_charinfo) + + -- Fallback für issued_by_name + if license.issued_by_name == "Unbekannt" then license.issued_by_name = "System" end @@ -146,6 +160,10 @@ local function getLicenseFromDB(citizenid, licenseType) license.classes = {} end + -- Cleanup - charinfo nicht mehr benötigt + license.holder_charinfo = nil + license.issued_by_charinfo = nil + debugPrint("Lizenz aus DB geladen: " .. license.license_type) -- In Cache speichern @@ -158,16 +176,16 @@ local function getLicenseFromDB(citizenid, licenseType) return nil end --- Alle Lizenzen eines Spielers abrufen +-- Alle Lizenzen eines Spielers abrufen (KORRIGIERT für MariaDB) local function getAllPlayerLicenses(citizenid) debugPrint("=== getAllPlayerLicenses START ===") debugPrint("CitizenID: " .. tostring(citizenid)) + -- MariaDB-kompatible Query local query = [[ SELECT pl.*, - CONCAT(p.charinfo->>'$.firstname', ' ', p.charinfo->>'$.lastname') as holder_name, - pi.charinfo->>'$.firstname' as issued_by_firstname, - pi.charinfo->>'$.lastname' as issued_by_lastname + p.charinfo as holder_charinfo, + pi.charinfo as issued_by_charinfo FROM player_licenses pl LEFT JOIN players p ON p.citizenid = pl.citizenid LEFT JOIN players pi ON pi.citizenid = pl.issued_by @@ -186,10 +204,12 @@ local function getAllPlayerLicenses(citizenid) if not seenTypes[license.license_type] then seenTypes[license.license_type] = true - -- Issued by Name zusammensetzen - if license.issued_by_firstname and license.issued_by_lastname then - license.issued_by_name = license.issued_by_firstname .. " " .. license.issued_by_lastname - else + -- Namen aus JSON extrahieren + license.holder_name = extractPlayerName(license.holder_charinfo) + license.issued_by_name = extractPlayerName(license.issued_by_charinfo) + + -- Fallback für issued_by_name + if license.issued_by_name == "Unbekannt" then license.issued_by_name = "System" end @@ -205,6 +225,10 @@ local function getAllPlayerLicenses(citizenid) license.classes = {} end + -- Cleanup + license.holder_charinfo = nil + license.issued_by_charinfo = nil + table.insert(licenses, license) end end @@ -230,13 +254,13 @@ local function saveLicenseToDB(citizenid, licenseType, issuedBy, classes) return false end - -- Spieler-Name für das name-Feld abrufen - local holderQuery = "SELECT CONCAT(charinfo->>'$.firstname', ' ', charinfo->>'$.lastname') as name FROM players WHERE citizenid = ?" + -- Spieler-Name für das name-Feld abrufen (MariaDB-kompatibel) + local holderQuery = "SELECT charinfo FROM players WHERE citizenid = ?" local holderResult = MySQL.query.await(holderQuery, {citizenid}) local holderName = "Unbekannt" - if holderResult and #holderResult > 0 and holderResult[1].name then - holderName = holderResult[1].name + if holderResult and #holderResult > 0 and holderResult[1].charinfo then + holderName = extractPlayerName(holderResult[1].charinfo) end -- Datum berechnen @@ -255,7 +279,7 @@ local function saveLicenseToDB(citizenid, licenseType, issuedBy, classes) local deactivateQuery = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?" MySQL.query.await(deactivateQuery, {citizenid, licenseType}) - -- Neue Lizenz einfügen (MIT name-Feld) + -- Neue Lizenz einfügen local insertQuery = [[ INSERT INTO player_licenses (citizenid, license_type, name, issue_date, expire_date, issued_by, is_active, classes, created_at) @@ -265,7 +289,7 @@ local function saveLicenseToDB(citizenid, licenseType, issuedBy, classes) local insertData = { citizenid, licenseType, - holderName, -- HINZUGEFÜGT: name-Feld + holderName, issueDate, expireDate, issuedBy, @@ -277,9 +301,9 @@ local function saveLicenseToDB(citizenid, licenseType, issuedBy, classes) debugPrint("Führe INSERT-Query aus...") debugPrint("Daten: " .. json.encode(insertData)) - local result = MySQL.insert.await(insertQuery, insertData) + local success, result = pcall(MySQL.insert.await, insertQuery, insertData) - if result then + if success and result then debugPrint("Lizenz erfolgreich gespeichert. ID: " .. result) -- Cache invalidieren @@ -288,7 +312,7 @@ local function saveLicenseToDB(citizenid, licenseType, issuedBy, classes) return true else - debugPrint("^1Fehler beim Speichern der Lizenz^7") + debugPrint("^1Fehler beim Speichern der Lizenz: " .. tostring(result) .. "^7") return false end end @@ -300,9 +324,9 @@ local function revokeLicenseInDB(citizenid, licenseType) debugPrint("LicenseType: " .. tostring(licenseType)) local query = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ? AND is_active = 1" - local result = MySQL.query.await(query, {citizenid, licenseType}) + local success, result = pcall(MySQL.query.await, query, {citizenid, licenseType}) - if result then + if success and result then debugPrint("Lizenz erfolgreich entzogen") -- Cache invalidieren @@ -311,11 +335,22 @@ local function revokeLicenseInDB(citizenid, licenseType) return true else - debugPrint("^1Fehler beim Entziehen der Lizenz^7") + debugPrint("^1Fehler beim Entziehen der Lizenz: " .. tostring(result) .. "^7") return false end end +-- Erweiterte Fehlerbehandlung für Datenbankoperationen +local function safeDBOperation(operation, errorMessage) + local success, result = pcall(operation) + if not success then + debugPrint("^1DB-Fehler: " .. (errorMessage or "Unbekannt") .. "^7") + debugPrint("^1Details: " .. tostring(result) .. "^7") + return nil + end + return result +end + -- EVENT HANDLER: Lizenz anfordern RegisterNetEvent('license-system:server:requestLicense', function(targetId) local src = source @@ -340,7 +375,10 @@ RegisterNetEvent('license-system:server:requestLicense', function(targetId) -- Erste verfügbare Lizenz finden local foundLicense = nil for licenseType, _ in pairs(Config.LicenseTypes) do - local license = getLicenseFromDB(citizenid, licenseType) + local license = safeDBOperation(function() + return getLicenseFromDB(citizenid, licenseType) + end, "Fehler beim Abrufen der Lizenz") + if license and license.is_active == 1 then foundLicense = { license = license, @@ -373,7 +411,9 @@ RegisterNetEvent('license-system:server:requestMyLicense', function(licenseType) end local citizenid = Player.PlayerData.citizenid - local license = getLicenseFromDB(citizenid, licenseType) + local license = safeDBOperation(function() + return getLicenseFromDB(citizenid, licenseType) + end, "Fehler beim Abrufen der eigenen Lizenz") if license and license.is_active == 1 then local licenseData = { @@ -409,7 +449,10 @@ RegisterNetEvent('license-system:server:requestPlayerLicenses', function(targetI local citizenid = targetPlayer.PlayerData.citizenid local targetName = getPlayerName(targetId) - local licenses = getAllPlayerLicenses(citizenid) + + local licenses = safeDBOperation(function() + return getAllPlayerLicenses(citizenid) + end, "Fehler beim Abrufen aller Spieler-Lizenzen") or {} debugPrint("Sende " .. #licenses .. " Lizenzen für " .. targetName) TriggerClientEvent('license-system:client:receivePlayerLicenses', src, licenses, targetId, targetName) @@ -444,7 +487,10 @@ RegisterNetEvent('license-system:server:issueLicense', function(targetId, licens local issuerCitizenId = issuerPlayer.PlayerData.citizenid -- Prüfen ob Lizenz bereits existiert - local existingLicense = getLicenseFromDB(targetCitizenId, licenseType) + local existingLicense = safeDBOperation(function() + return getLicenseFromDB(targetCitizenId, licenseType) + end, "Fehler beim Prüfen bestehender Lizenz") + if existingLicense and existingLicense.is_active == 1 then debugPrint("Lizenz bereits vorhanden und aktiv") TriggerClientEvent('QBCore:Notify', src, 'Spieler hat bereits eine aktive ' .. (Config.LicenseTypes[licenseType].label or licenseType) .. '!', 'error') @@ -466,7 +512,9 @@ RegisterNetEvent('license-system:server:issueLicense', function(targetId, licens end -- Lizenz in Datenbank speichern - local success = saveLicenseToDB(targetCitizenId, licenseType, issuerCitizenId, classes) + local success = safeDBOperation(function() + return saveLicenseToDB(targetCitizenId, licenseType, issuerCitizenId, classes) + end, "Fehler beim Speichern der Lizenz") if success then local targetName = getPlayerName(targetId) @@ -512,7 +560,9 @@ RegisterNetEvent('license-system:server:revokeLicense', function(targetId, licen local targetCitizenId = targetPlayer.PlayerData.citizenid -- Lizenz entziehen - local success = revokeLicenseInDB(targetCitizenId, licenseType) + local success = safeDBOperation(function() + return revokeLicenseInDB(targetCitizenId, licenseType) + end, "Fehler beim Entziehen der Lizenz") if success then local targetName = getPlayerName(targetId) @@ -544,53 +594,58 @@ RegisterNetEvent('license-system:server:savePhoto', function(citizenid, photoDat debugPrint("Source: " .. src .. ", CitizenID: " .. citizenid) -- Hier könnte das Foto in der Datenbank oder im Dateisystem gespeichert werden - -- Für jetzt loggen wir es nur debugPrint("Foto-Daten erhalten für: " .. citizenid) TriggerClientEvent('QBCore:Notify', src, 'Foto gespeichert!', 'success') end) -- EXPORT FUNKTIONEN --- Prüfen ob Spieler eine bestimmte Lizenz hat exports('hasLicense', function(citizenid, licenseType) if not citizenid or not licenseType then return false end - local license = getLicenseFromDB(citizenid, licenseType) + local license = safeDBOperation(function() + return getLicenseFromDB(citizenid, licenseType) + end, "Fehler beim Prüfen der Lizenz") + return license and license.is_active == 1 end) --- Lizenz für Spieler ausstellen (für andere Resources) exports('issueLicense', function(citizenid, licenseType, issuedBy, classes) if not citizenid or not licenseType then return false end issuedBy = issuedBy or 'system' - return saveLicenseToDB(citizenid, licenseType, issuedBy, classes) + return safeDBOperation(function() + return saveLicenseToDB(citizenid, licenseType, issuedBy, classes) + end, "Fehler beim Ausstellen der Lizenz") or false end) --- Lizenz entziehen (für andere Resources) exports('revokeLicense', function(citizenid, licenseType) if not citizenid or not licenseType then return false end - return revokeLicenseInDB(citizenid, licenseType) + return safeDBOperation(function() + return revokeLicenseInDB(citizenid, licenseType) + end, "Fehler beim Entziehen der Lizenz") or false end) --- Alle Lizenzen eines Spielers abrufen (für andere Resources) exports('getPlayerLicenses', function(citizenid) if not citizenid then return {} end - return getAllPlayerLicenses(citizenid) + return safeDBOperation(function() + return getAllPlayerLicenses(citizenid) + end, "Fehler beim Abrufen der Spieler-Lizenzen") or {} end) --- Spezifische Lizenz abrufen (für andere Resources) exports('getPlayerLicense', function(citizenid, licenseType) if not citizenid or not licenseType then return nil end - return getLicenseFromDB(citizenid, licenseType) + return safeDBOperation(function() + return getLicenseFromDB(citizenid, licenseType) + end, "Fehler beim Abrufen der Lizenz") end) -- INITIALISIERUNG CreateThread(function() - debugPrint("License-System Server gestartet (korrigiert)") + debugPrint("License-System Server gestartet (MariaDB-kompatibel)") -- Warten bis QBCore geladen ist while not QBCore do @@ -600,8 +655,9 @@ CreateThread(function() debugPrint("QBCore erfolgreich geladen") -- Datenbank-Verbindung testen - local testQuery = "SELECT 1 as test" - local testResult = MySQL.query.await(testQuery) + local testResult = safeDBOperation(function() + return MySQL.query.await("SELECT 1 as test") + end, "Datenbank-Verbindungstest") if testResult then debugPrint("Datenbank-Verbindung erfolgreich") @@ -616,18 +672,51 @@ end) AddEventHandler('onResourceStop', function(resourceName) if GetCurrentResourceName() == resourceName then debugPrint("License-System Server gestoppt") - - -- Cache leeren licenseCache = {} end end) --- DEBUG COMMAND +-- DEBUG COMMANDS RegisterCommand('licensestats', function(source, args, rawCommand) if source == 0 then -- Console only + local cacheCount = 0 + for _ in pairs(licenseCache) do + cacheCount = cacheCount + 1 + end + print("=== LICENSE SYSTEM STATS ===") - print("Cache Entries: " .. #licenseCache) - print("Config License Types: " .. #Config.LicenseTypes) + print("Cache Entries: " .. cacheCount) + print("Config License Types: " .. (Config.LicenseTypes and #Config.LicenseTypes or 0)) print("============================") end end, true) + +RegisterCommand('licenseclearcache', function(source, args, rawCommand) + if source == 0 then -- Console only + local oldCount = 0 + for _ in pairs(licenseCache) do + oldCount = oldCount + 1 + end + + licenseCache = {} + print("License-Cache geleert. Entfernte Einträge: " .. oldCount) + end +end, true) + +-- Erweiterte Logging-Funktion +local function logLicenseAction(action, src, targetId, licenseType, details) + local timestamp = os.date("%Y-%m-%d %H:%M:%S") + local srcName = getPlayerName(src) + local targetName = targetId and getPlayerName(targetId) or "N/A" + + local logMessage = string.format( + "[%s] %s | Quelle: %s (%s) | Ziel: %s (%s) | Typ: %s | Details: %s", + timestamp, action, srcName, src, targetName, targetId or "N/A", licenseType or "N/A", details or "N/A" + ) + + debugPrint("LOG: " .. logMessage) + + -- Hier könnte das Log in eine Datei oder Datenbank geschrieben werden +end + +debugPrint("License-System Server vollständig geladen (MariaDB-kompatibel)")