diff --git a/resources/[tools]/nordi_license/server/main.lua b/resources/[tools]/nordi_license/server/main.lua index 7a01a8a88..3c22cf16f 100644 --- a/resources/[tools]/nordi_license/server/main.lua +++ b/resources/[tools]/nordi_license/server/main.lua @@ -24,32 +24,6 @@ local function getPlayerName(src) return "Unbekannt" end --- Spieler-Daten abrufen -local function getPlayerData(src) - local Player = QBCore.Functions.GetPlayer(src) - if not Player then return nil end - - return { - citizenid = Player.PlayerData.citizenid, - name = getPlayerName(src), - charinfo = Player.PlayerData.charinfo, - job = Player.PlayerData.job - } -end - --- Berechtigung prüfen -local function hasPermission(src) - local Player = QBCore.Functions.GetPlayer(src) - if not Player then return false end - - local job = Player.PlayerData.job - if not job then return false end - - local hasAuth = Config.AuthorizedJobs[job.name] or false - debugPrint("Berechtigung für " .. job.name .. ": " .. tostring(hasAuth)) - return hasAuth -end - -- Cache-Funktionen local function getCachedLicense(citizenid, licenseType) local cacheKey = citizenid .. "_" .. licenseType @@ -72,6 +46,325 @@ local function setCachedLicense(citizenid, licenseType, data) debugPrint("Lizenz gecacht: " .. cacheKey) end +-- Berechtigung prüfen +local function hasPermission(src) + local Player = QBCore.Functions.GetPlayer(src) + if not Player then return false end + + local job = Player.PlayerData.job + if not job then return false end + + local hasAuth = Config.AuthorizedJobs[job.name] or false + debugPrint("Berechtigung für " .. job.name .. ": " .. tostring(hasAuth)) + return hasAuth +end + +-- Spieler-Name aus JSON extrahieren (Collation-sicher) +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 + +-- Sichere Datenbankoperationen mit Retry-Mechanismus +local function safeDBOperation(operation, errorMessage, maxRetries) + maxRetries = maxRetries or 3 + local retries = 0 + + while retries < maxRetries do + retries = retries + 1 + local success, result = pcall(operation) + + if success then + return result + else + debugPrint("^3DB-Retry " .. retries .. "/" .. maxRetries .. ": " .. tostring(result) .. "^7") + if retries >= maxRetries then + debugPrint("^1DB-Fehler: " .. (errorMessage or "Unbekannt") .. "^7") + debugPrint("^1Details: " .. tostring(result) .. "^7") + return nil + end + Wait(1000) -- 1 Sekunde warten vor Retry + end + end + + return nil +end + +-- Lizenz aus Datenbank abrufen (KORRIGIERT - Collation-sicher) +local function getLicenseFromDB(citizenid, licenseType) + debugPrint("=== getLicenseFromDB START ===") + debugPrint("CitizenID: " .. tostring(citizenid)) + debugPrint("LicenseType: " .. tostring(licenseType)) + + -- Cache prüfen + local cached = getCachedLicense(citizenid, licenseType) + if cached then + return cached + end + + -- Einfache Query ohne JOINs um Collation-Probleme zu vermeiden + local query = [[ + SELECT * FROM player_licenses + WHERE citizenid = ? AND license_type = ? AND is_active = 1 + ORDER BY created_at DESC + LIMIT 1 + ]] + + local result = safeDBOperation(function() + return MySQL.query.await(query, {citizenid, licenseType}) + end, "Fehler beim Abrufen der Lizenz") + + if result and #result > 0 then + local license = result[1] + + -- Spieler-Namen separat abrufen + local holderQuery = "SELECT charinfo FROM players WHERE citizenid = ?" + local holderResult = safeDBOperation(function() + return MySQL.query.await(holderQuery, {citizenid}) + end, "Fehler beim Abrufen des Spieler-Namens") + + if holderResult and #holderResult > 0 then + license.holder_name = extractPlayerName(holderResult[1].charinfo) + else + license.holder_name = "Unbekannt" + end + + -- Aussteller-Namen separat abrufen + if license.issued_by then + local issuerQuery = "SELECT charinfo FROM players WHERE citizenid = ?" + local issuerResult = safeDBOperation(function() + return MySQL.query.await(issuerQuery, {license.issued_by}) + end, "Fehler beim Abrufen des Aussteller-Namens") + + if issuerResult and #issuerResult > 0 then + license.issued_by_name = extractPlayerName(issuerResult[1].charinfo) + else + license.issued_by_name = "System" + end + else + license.issued_by_name = "System" + end + + -- Classes parsen + if license.classes then + local success, classes = pcall(json.decode, license.classes) + if success and type(classes) == "table" then + license.classes = classes + else + license.classes = {} + end + else + license.classes = {} + end + + debugPrint("Lizenz aus DB geladen: " .. license.license_type) + + -- In Cache speichern + setCachedLicense(citizenid, licenseType, license) + + return license + end + + debugPrint("Keine Lizenz in DB gefunden") + return nil +end + +-- Alle Lizenzen eines Spielers abrufen (KORRIGIERT) +local function getAllPlayerLicenses(citizenid) + debugPrint("=== getAllPlayerLicenses START ===") + debugPrint("CitizenID: " .. tostring(citizenid)) + + -- Einfache Query ohne JOINs + local query = [[ + SELECT * FROM player_licenses + WHERE citizenid = ? AND is_active = 1 + ORDER BY license_type, created_at DESC + ]] + + local result = safeDBOperation(function() + return MySQL.query.await(query, {citizenid}) + end, "Fehler beim Abrufen aller Lizenzen") + + if result and #result > 0 then + local licenses = {} + local seenTypes = {} + + -- Spieler-Namen einmal abrufen + local holderQuery = "SELECT charinfo FROM players WHERE citizenid = ?" + local holderResult = safeDBOperation(function() + return MySQL.query.await(holderQuery, {citizenid}) + end, "Fehler beim Abrufen des Spieler-Namens") + + local holderName = "Unbekannt" + if holderResult and #holderResult > 0 then + holderName = extractPlayerName(holderResult[1].charinfo) + end + + for _, license in ipairs(result) do + -- Nur die neueste Lizenz pro Typ nehmen + if not seenTypes[license.license_type] then + seenTypes[license.license_type] = true + + license.holder_name = holderName + + -- Aussteller-Namen abrufen + if license.issued_by then + local issuerQuery = "SELECT charinfo FROM players WHERE citizenid = ?" + local issuerResult = safeDBOperation(function() + return MySQL.query.await(issuerQuery, {license.issued_by}) + end, "Fehler beim Abrufen des Aussteller-Namens") + + if issuerResult and #issuerResult > 0 then + license.issued_by_name = extractPlayerName(issuerResult[1].charinfo) + else + license.issued_by_name = "System" + end + else + license.issued_by_name = "System" + end + + -- Classes parsen + if license.classes then + local success, classes = pcall(json.decode, license.classes) + if success and type(classes) == "table" then + license.classes = classes + else + license.classes = {} + end + else + license.classes = {} + end + + table.insert(licenses, license) + end + end + + debugPrint("Gefundene Lizenzen: " .. #licenses) + return licenses + end + + debugPrint("Keine Lizenzen gefunden") + return {} +end + +-- Lizenz in Datenbank speichern (KORRIGIERT - DateTime-Fix) +local function saveLicenseToDB(citizenid, licenseType, issuedBy, classes) + debugPrint("=== saveLicenseToDB START ===") + debugPrint("CitizenID: " .. tostring(citizenid)) + debugPrint("LicenseType: " .. tostring(licenseType)) + debugPrint("IssuedBy: " .. tostring(issuedBy)) + + local config = Config.LicenseTypes[licenseType] + if not config then + debugPrint("^1Fehler: Unbekannter Lizenztyp: " .. licenseType .. "^7") + return false + end + + -- Spieler-Name für das name-Feld abrufen + local holderQuery = "SELECT charinfo FROM players WHERE citizenid = ?" + local holderResult = safeDBOperation(function() + return MySQL.query.await(holderQuery, {citizenid}) + end, "Fehler beim Abrufen des Spieler-Namens") + + local holderName = "Unbekannt" + if holderResult and #holderResult > 0 and holderResult[1].charinfo then + holderName = extractPlayerName(holderResult[1].charinfo) + end + + -- Datum berechnen + local issueDate = os.date("%d.%m.%Y") + local expireDate = nil + + if config.validity_days then + local expireTimestamp = os.time() + (config.validity_days * 24 * 60 * 60) + expireDate = os.date("%d.%m.%Y", expireTimestamp) + end + + -- Classes zu JSON konvertieren + local classesJson = json.encode(classes or {}) + + -- Alte Lizenz deaktivieren + local deactivateQuery = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?" + safeDBOperation(function() + return MySQL.query.await(deactivateQuery, {citizenid, licenseType}) + end, "Fehler beim Deaktivieren alter Lizenz") + + -- Neue Lizenz einfügen (KORRIGIERT - DateTime als BIGINT oder korrektes Format) + local insertQuery = [[ + INSERT INTO player_licenses + (citizenid, license_type, name, issue_date, expire_date, issued_by, is_active, classes, created_at) + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) + ]] + + -- created_at als BIGINT (Unix Timestamp) oder als DATETIME + local createdAt = os.time() -- Unix Timestamp für BIGINT + -- Alternativ für DATETIME: local createdAt = os.date("%Y-%m-%d %H:%M:%S") + + local insertData = { + citizenid, + licenseType, + holderName, + issueDate, + expireDate, + issuedBy, + 1, + classesJson, + createdAt + } + + debugPrint("Führe INSERT-Query aus...") + debugPrint("Daten: " .. json.encode(insertData)) + + local result = safeDBOperation(function() + return MySQL.insert.await(insertQuery, insertData) + end, "Fehler beim Speichern der Lizenz") + + if result then + debugPrint("Lizenz erfolgreich gespeichert. ID: " .. result) + + -- Cache invalidieren + local cacheKey = citizenid .. "_" .. licenseType + licenseCache[cacheKey] = nil + + return true + else + debugPrint("^1Fehler beim Speichern der Lizenz^7") + return false + end +end + +-- Lizenz entziehen +local function revokeLicenseInDB(citizenid, licenseType) + debugPrint("=== revokeLicenseInDB START ===") + debugPrint("CitizenID: " .. tostring(citizenid)) + debugPrint("LicenseType: " .. tostring(licenseType)) + + local query = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ? AND is_active = 1" + + local result = safeDBOperation(function() + return MySQL.query.await(query, {citizenid, licenseType}) + end, "Fehler beim Entziehen der Lizenz") + + if result then + debugPrint("Lizenz erfolgreich entzogen") + + -- Cache invalidieren + local cacheKey = citizenid .. "_" .. licenseType + licenseCache[cacheKey] = nil + + return true + else + debugPrint("^1Fehler beim Entziehen der Lizenz^7") + return false + end +end + -- Cache bereinigen local function cleanupCache() local now = os.time() * 1000 @@ -97,260 +390,6 @@ CreateThread(function() end end) --- 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)) - debugPrint("LicenseType: " .. tostring(licenseType)) - - -- Cache prüfen - local cached = getCachedLicense(citizenid, licenseType) - if cached then - return cached - end - - -- MariaDB-kompatible Query ohne JSON-Operatoren - local query = [[ - SELECT pl.*, - 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 - WHERE pl.citizenid = ? AND pl.license_type = ? - ORDER BY pl.created_at DESC - LIMIT 1 - ]] - - local result = MySQL.query.await(query, {citizenid, licenseType}) - - if result and #result > 0 then - local license = result[1] - - -- 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 - - -- Classes parsen - if license.classes then - local success, classes = pcall(json.decode, license.classes) - if success and type(classes) == "table" then - license.classes = classes - else - license.classes = {} - end - else - 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 - setCachedLicense(citizenid, licenseType, license) - - return license - end - - debugPrint("Keine Lizenz in DB gefunden") - return nil -end - --- 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.*, - 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 - WHERE pl.citizenid = ? - ORDER BY pl.license_type, pl.created_at DESC - ]] - - local result = MySQL.query.await(query, {citizenid}) - - if result and #result > 0 then - local licenses = {} - local seenTypes = {} - - for _, license in ipairs(result) do - -- Nur die neueste Lizenz pro Typ nehmen - if not seenTypes[license.license_type] then - seenTypes[license.license_type] = true - - -- 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 - - -- Classes parsen - if license.classes then - local success, classes = pcall(json.decode, license.classes) - if success and type(classes) == "table" then - license.classes = classes - else - license.classes = {} - end - else - license.classes = {} - end - - -- Cleanup - license.holder_charinfo = nil - license.issued_by_charinfo = nil - - table.insert(licenses, license) - end - end - - debugPrint("Gefundene Lizenzen: " .. #licenses) - return licenses - end - - debugPrint("Keine Lizenzen gefunden") - return {} -end - --- Lizenz in Datenbank speichern (KORRIGIERT) -local function saveLicenseToDB(citizenid, licenseType, issuedBy, classes) - debugPrint("=== saveLicenseToDB START ===") - debugPrint("CitizenID: " .. tostring(citizenid)) - debugPrint("LicenseType: " .. tostring(licenseType)) - debugPrint("IssuedBy: " .. tostring(issuedBy)) - - local config = Config.LicenseTypes[licenseType] - if not config then - debugPrint("^1Fehler: Unbekannter Lizenztyp: " .. licenseType .. "^7") - return false - end - - -- 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].charinfo then - holderName = extractPlayerName(holderResult[1].charinfo) - end - - -- Datum berechnen - local issueDate = os.date("%d.%m.%Y") - local expireDate = nil - - if config.validity_days then - local expireTimestamp = os.time() + (config.validity_days * 24 * 60 * 60) - expireDate = os.date("%d.%m.%Y", expireTimestamp) - end - - -- Classes zu JSON konvertieren - local classesJson = json.encode(classes or {}) - - -- Alte Lizenz deaktivieren - local deactivateQuery = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?" - MySQL.query.await(deactivateQuery, {citizenid, licenseType}) - - -- 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) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) - ]] - - local insertData = { - citizenid, - licenseType, - holderName, - issueDate, - expireDate, - issuedBy, - 1, - classesJson, - os.time() - } - - debugPrint("Führe INSERT-Query aus...") - debugPrint("Daten: " .. json.encode(insertData)) - - local success, result = pcall(MySQL.insert.await, insertQuery, insertData) - - if success and result then - debugPrint("Lizenz erfolgreich gespeichert. ID: " .. result) - - -- Cache invalidieren - local cacheKey = citizenid .. "_" .. licenseType - licenseCache[cacheKey] = nil - - return true - else - debugPrint("^1Fehler beim Speichern der Lizenz: " .. tostring(result) .. "^7") - return false - end -end - --- Lizenz entziehen -local function revokeLicenseInDB(citizenid, licenseType) - debugPrint("=== revokeLicenseInDB START ===") - debugPrint("CitizenID: " .. tostring(citizenid)) - debugPrint("LicenseType: " .. tostring(licenseType)) - - local query = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ? AND is_active = 1" - local success, result = pcall(MySQL.query.await, query, {citizenid, licenseType}) - - if success and result then - debugPrint("Lizenz erfolgreich entzogen") - - -- Cache invalidieren - local cacheKey = citizenid .. "_" .. licenseType - licenseCache[cacheKey] = nil - - return true - else - 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 @@ -375,9 +414,7 @@ RegisterNetEvent('license-system:server:requestLicense', function(targetId) -- Erste verfügbare Lizenz finden local foundLicense = nil for licenseType, _ in pairs(Config.LicenseTypes) do - local license = safeDBOperation(function() - return getLicenseFromDB(citizenid, licenseType) - end, "Fehler beim Abrufen der Lizenz") + local license = getLicenseFromDB(citizenid, licenseType) if license and license.is_active == 1 then foundLicense = { @@ -411,9 +448,7 @@ RegisterNetEvent('license-system:server:requestMyLicense', function(licenseType) end local citizenid = Player.PlayerData.citizenid - local license = safeDBOperation(function() - return getLicenseFromDB(citizenid, licenseType) - end, "Fehler beim Abrufen der eigenen Lizenz") + local license = getLicenseFromDB(citizenid, licenseType) if license and license.is_active == 1 then local licenseData = { @@ -449,10 +484,7 @@ RegisterNetEvent('license-system:server:requestPlayerLicenses', function(targetI local citizenid = targetPlayer.PlayerData.citizenid local targetName = getPlayerName(targetId) - - local licenses = safeDBOperation(function() - return getAllPlayerLicenses(citizenid) - end, "Fehler beim Abrufen aller Spieler-Lizenzen") or {} + local licenses = getAllPlayerLicenses(citizenid) debugPrint("Sende " .. #licenses .. " Lizenzen für " .. targetName) TriggerClientEvent('license-system:client:receivePlayerLicenses', src, licenses, targetId, targetName) @@ -487,9 +519,7 @@ RegisterNetEvent('license-system:server:issueLicense', function(targetId, licens local issuerCitizenId = issuerPlayer.PlayerData.citizenid -- Prüfen ob Lizenz bereits existiert - local existingLicense = safeDBOperation(function() - return getLicenseFromDB(targetCitizenId, licenseType) - end, "Fehler beim Prüfen bestehender Lizenz") + local existingLicense = getLicenseFromDB(targetCitizenId, licenseType) if existingLicense and existingLicense.is_active == 1 then debugPrint("Lizenz bereits vorhanden und aktiv") @@ -512,9 +542,7 @@ RegisterNetEvent('license-system:server:issueLicense', function(targetId, licens end -- Lizenz in Datenbank speichern - local success = safeDBOperation(function() - return saveLicenseToDB(targetCitizenId, licenseType, issuerCitizenId, classes) - end, "Fehler beim Speichern der Lizenz") + local success = saveLicenseToDB(targetCitizenId, licenseType, issuerCitizenId, classes) if success then local targetName = getPlayerName(targetId) @@ -560,9 +588,7 @@ RegisterNetEvent('license-system:server:revokeLicense', function(targetId, licen local targetCitizenId = targetPlayer.PlayerData.citizenid -- Lizenz entziehen - local success = safeDBOperation(function() - return revokeLicenseInDB(targetCitizenId, licenseType) - end, "Fehler beim Entziehen der Lizenz") + local success = revokeLicenseInDB(targetCitizenId, licenseType) if success then local targetName = getPlayerName(targetId) @@ -587,26 +613,11 @@ RegisterNetEvent('license-system:server:revokeLicense', function(targetId, licen end end) --- EVENT HANDLER: Foto speichern -RegisterNetEvent('license-system:server:savePhoto', function(citizenid, photoData) - local src = source - debugPrint("=== Event: savePhoto ===") - debugPrint("Source: " .. src .. ", CitizenID: " .. citizenid) - - -- Hier könnte das Foto in der Datenbank oder im Dateisystem gespeichert werden - debugPrint("Foto-Daten erhalten für: " .. citizenid) - - TriggerClientEvent('QBCore:Notify', src, 'Foto gespeichert!', 'success') -end) - -- EXPORT FUNKTIONEN exports('hasLicense', function(citizenid, licenseType) if not citizenid or not licenseType then return false end - local license = safeDBOperation(function() - return getLicenseFromDB(citizenid, licenseType) - end, "Fehler beim Prüfen der Lizenz") - + local license = getLicenseFromDB(citizenid, licenseType) return license and license.is_active == 1 end) @@ -614,38 +625,30 @@ exports('issueLicense', function(citizenid, licenseType, issuedBy, classes) if not citizenid or not licenseType then return false end issuedBy = issuedBy or 'system' - return safeDBOperation(function() - return saveLicenseToDB(citizenid, licenseType, issuedBy, classes) - end, "Fehler beim Ausstellen der Lizenz") or false + return saveLicenseToDB(citizenid, licenseType, issuedBy, classes) end) exports('revokeLicense', function(citizenid, licenseType) if not citizenid or not licenseType then return false end - return safeDBOperation(function() - return revokeLicenseInDB(citizenid, licenseType) - end, "Fehler beim Entziehen der Lizenz") or false + return revokeLicenseInDB(citizenid, licenseType) end) exports('getPlayerLicenses', function(citizenid) if not citizenid then return {} end - return safeDBOperation(function() - return getAllPlayerLicenses(citizenid) - end, "Fehler beim Abrufen der Spieler-Lizenzen") or {} + return getAllPlayerLicenses(citizenid) end) exports('getPlayerLicense', function(citizenid, licenseType) if not citizenid or not licenseType then return nil end - return safeDBOperation(function() - return getLicenseFromDB(citizenid, licenseType) - end, "Fehler beim Abrufen der Lizenz") + return getLicenseFromDB(citizenid, licenseType) end) -- INITIALISIERUNG CreateThread(function() - debugPrint("License-System Server gestartet (MariaDB-kompatibel)") + debugPrint("License-System Server gestartet (Collation & DateTime Fix)") -- Warten bis QBCore geladen ist while not QBCore do @@ -703,20 +706,4 @@ RegisterCommand('licenseclearcache', function(source, args, rawCommand) 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)") +debugPrint("License-System Server vollständig geladen (Collation & DateTime Fix)")