local QBCore = exports['qb-core']:GetCoreObject() -- Lokale Variablen local licenseCache = {} local cacheTimeout = 300000 -- 5 Minuten -- Debug-Funktion local function debugPrint(message) if Config.Debug then print("^3[License-System Server] " .. message .. "^7") end end -- Spieler-Name abrufen local function getPlayerName(src) local Player = QBCore.Functions.GetPlayer(src) if not Player then return "Unbekannt" end local charinfo = Player.PlayerData.charinfo if charinfo and charinfo.firstname and charinfo.lastname then return charinfo.firstname .. " " .. charinfo.lastname end return "Unbekannt" 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 return Config.AuthorizedJobs[job.name] or false end -- Cache-Funktionen (KORRIGIERT - Aggressive Cache-Invalidierung) local function getCachedLicense(citizenid, licenseType) local cacheKey = citizenid .. "_" .. licenseType local cached = licenseCache[cacheKey] if cached and (os.time() * 1000 - cached.timestamp) < cacheTimeout then debugPrint("Cache-Hit für: " .. cacheKey) return cached.data end return nil end local function setCachedLicense(citizenid, licenseType, data) local cacheKey = citizenid .. "_" .. licenseType licenseCache[cacheKey] = { data = data, timestamp = os.time() * 1000 } debugPrint("Lizenz gecacht: " .. cacheKey) end -- Cache invalidieren (ERWEITERT) local function invalidateCache(citizenid, licenseType) if licenseType then -- Spezifische Lizenz invalidieren local cacheKey = citizenid .. "_" .. licenseType licenseCache[cacheKey] = nil debugPrint("Cache invalidiert für: " .. cacheKey) else -- Alle Lizenzen des Spielers invalidieren for key, _ in pairs(licenseCache) do if string.find(key, citizenid .. "_") then licenseCache[key] = nil debugPrint("Cache invalidiert für: " .. key) end end end end -- Spieler-Name aus JSON extrahieren 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 DB-Operation 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) end end return nil end -- Lizenz-Status prüfen (NEUE FUNKTION) local function isLicenseActive(license) if not license then return false end -- is_active prüfen (1 = aktiv, 0 = inaktiv, nil = aktiv per default) local isActive = license.is_active if isActive == nil then isActive = 1 -- Default: aktiv end if isActive ~= 1 then debugPrint("Lizenz inaktiv (is_active = " .. tostring(isActive) .. ")") return false end -- Ablaufdatum prüfen (falls vorhanden) if license.expire_date and license.expire_date ~= "" then local expireDate = license.expire_date local currentDate = os.date("%d.%m.%Y") -- Einfache Datumsvergleich (DD.MM.YYYY) local function parseDate(dateStr) local day, month, year = dateStr:match("(%d+)%.(%d+)%.(%d+)") if day and month and year then return os.time({year = tonumber(year), month = tonumber(month), day = tonumber(day)}) end return nil end local expireTimestamp = parseDate(expireDate) local currentTimestamp = parseDate(currentDate) if expireTimestamp and currentTimestamp and expireTimestamp < currentTimestamp then debugPrint("Lizenz abgelaufen: " .. expireDate .. " < " .. currentDate) return false end end debugPrint("Lizenz ist aktiv und gültig") return true end -- KORRIGIERTE Lizenz-Abfrage (Ohne Cache für frische Daten) local function getLicenseFromDB(citizenid, licenseType, skipCache) debugPrint("=== getLicenseFromDB START ===") debugPrint("CitizenID: " .. tostring(citizenid)) debugPrint("LicenseType: " .. tostring(licenseType)) debugPrint("SkipCache: " .. tostring(skipCache)) -- Cache prüfen (nur wenn nicht übersprungen) if not skipCache then local cached = getCachedLicense(citizenid, licenseType) if cached then debugPrint("Lizenz aus Cache geladen") return cached end end -- Direkte DB-Abfrage (VEREINFACHT - nur aktive Lizenzen) local query = [[ SELECT * FROM player_licenses WHERE citizenid = ? AND license_type = ? 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 not result or #result == 0 then debugPrint("^1Keine Lizenz in DB gefunden für " .. citizenid .. " / " .. licenseType .. "^7") return nil end local license = result[1] debugPrint("Rohe Lizenz-Daten aus DB:") debugPrint("ID: " .. tostring(license.id)) debugPrint("is_active: " .. tostring(license.is_active)) debugPrint("created_at: " .. tostring(license.created_at)) -- Spieler-Namen 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 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 -- is_active normalisieren (WICHTIG!) if license.is_active == nil then license.is_active = 1 debugPrint("is_active war nil, auf 1 gesetzt") end -- Status prüfen local isActive = isLicenseActive(license) debugPrint("Lizenz-Status-Prüfung: " .. tostring(isActive)) if not isActive then debugPrint("Lizenz ist nicht aktiv/gültig") return nil end debugPrint("Lizenz erfolgreich geladen: " .. license.license_type .. " (Active: " .. tostring(license.is_active) .. ")") -- In Cache speichern (nur wenn aktiv) if not skipCache then setCachedLicense(citizenid, licenseType, license) end return license end -- Alle Lizenzen eines Spielers abrufen (KORRIGIERT) local function getAllPlayerLicenses(citizenid) debugPrint("=== getAllPlayerLicenses START ===") debugPrint("CitizenID: " .. tostring(citizenid)) -- Alle Lizenzen abrufen (ohne is_active Filter) local query = "SELECT * FROM player_licenses WHERE citizenid = ? ORDER BY license_type, created_at DESC" local result = safeDBOperation(function() return MySQL.query.await(query, {citizenid}) end, "Fehler beim Abrufen aller Lizenzen") if not result or #result == 0 then debugPrint("Keine Lizenzen gefunden für: " .. citizenid) return {} end 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 -- is_active normalisieren if license.is_active == nil then license.is_active = 1 end -- Status prüfen und nur aktive Lizenzen hinzufügen if isLicenseActive(license) then table.insert(licenses, license) debugPrint("Aktive Lizenz hinzugefügt: " .. license.license_type) else debugPrint("Inaktive Lizenz übersprungen: " .. license.license_type) end end end debugPrint("Verarbeitete aktive Lizenzen: " .. #licenses) return licenses end -- Lizenz in Datenbank speichern (KORRIGIERT - Explizite is_active Setzung) 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 -- Cache für diesen Spieler komplett invalidieren invalidateCache(citizenid) -- Spieler-Name 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 {}) -- WICHTIG: Alte Lizenz explizit deaktivieren local deactivateQuery = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?" local deactivateResult = safeDBOperation(function() return MySQL.query.await(deactivateQuery, {citizenid, licenseType}) end, "Fehler beim Deaktivieren alter Lizenz") debugPrint("Alte Lizenzen deaktiviert: " .. tostring(deactivateResult ~= nil)) -- Neue Lizenz einfügen (EXPLIZIT is_active = 1) local insertQuery = [[ INSERT INTO player_licenses (citizenid, license_type, name, issue_date, expire_date, issued_by, is_active, classes, created_at) VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?) ]] local createdAt = os.time() -- Unix Timestamp local insertData = { citizenid, licenseType, holderName, issueDate, expireDate, issuedBy, -- is_active = 1 ist direkt in der Query 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 komplett invalidieren (sicherstellen dass neue Daten geladen werden) invalidateCache(citizenid) -- Sofort neue Lizenz aus DB laden um zu verifizieren Wait(100) -- Kurz warten local newLicense = getLicenseFromDB(citizenid, licenseType, true) -- Skip Cache if newLicense then debugPrint("Neue Lizenz erfolgreich verifiziert: is_active = " .. tostring(newLicense.is_active)) else debugPrint("^3Warnung: Neue Lizenz konnte nicht verifiziert werden^7") end return true else debugPrint("^1Fehler beim Speichern der Lizenz^7") return false end end -- Lizenz entziehen (KORRIGIERT) 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 invalidateCache(citizenid, licenseType) return true else debugPrint("^1Fehler beim Entziehen der Lizenz^7") return false end end -- Cache bereinigen local function cleanupCache() local now = os.time() * 1000 local cleaned = 0 for key, cached in pairs(licenseCache) do if (now - cached.timestamp) > cacheTimeout then licenseCache[key] = nil cleaned = cleaned + 1 end end if cleaned > 0 then debugPrint("Cache bereinigt: " .. cleaned .. " Einträge") end end -- Cache-Cleanup Thread CreateThread(function() while true do Wait(300000) -- 5 Minuten cleanupCache() end end) -- EVENT HANDLER: Lizenz anfordern RegisterNetEvent('license-system:server:requestLicense', function(targetId) local src = source debugPrint("=== Event: requestLicense ===") debugPrint("Source: " .. src .. ", Target: " .. targetId) if not hasPermission(src) then debugPrint("Keine Berechtigung für Spieler: " .. src) TriggerClientEvent('license-system:client:receiveLicense', src, nil) return end local targetPlayer = QBCore.Functions.GetPlayer(targetId) if not targetPlayer then debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) TriggerClientEvent('license-system:client:receiveLicense', src, nil) return end local citizenid = targetPlayer.PlayerData.citizenid -- PRIORITÄT: Erst nach Ausweis suchen, dann andere Lizenzen local licenseTypes = {"id_card", "driver_license", "weapon_license", "pilot_license"} local foundLicense = nil for _, licenseType in ipairs(licenseTypes) do if Config.LicenseTypes[licenseType] then local license = getLicenseFromDB(citizenid, licenseType, true) -- Skip Cache für frische Daten if license then foundLicense = { license = license, config = Config.LicenseTypes[licenseType] } debugPrint("Lizenz gefunden: " .. licenseType) break end end end if foundLicense then debugPrint("Sende Lizenz an Client: " .. foundLicense.license.license_type) TriggerClientEvent('license-system:client:receiveLicense', src, foundLicense) else debugPrint("Keine Lizenz gefunden") TriggerClientEvent('license-system:client:receiveLicense', src, nil) end end) -- EVENT HANDLER: Eigene Lizenz anfordern (KORRIGIERT) RegisterNetEvent('license-system:server:requestMyLicense', function(licenseType) local src = source debugPrint("=== Event: requestMyLicense ===") debugPrint("Source: " .. src .. ", LicenseType: " .. tostring(licenseType)) local Player = QBCore.Functions.GetPlayer(src) if not Player then debugPrint("Spieler nicht gefunden: " .. src) TriggerClientEvent('license-system:client:receiveMyLicense', src, nil, licenseType) return end local citizenid = Player.PlayerData.citizenid -- Falls kein spezifischer Typ angegeben, suche nach Ausweis if not licenseType or licenseType == "" then licenseType = "id_card" debugPrint("Kein Lizenztyp angegeben, verwende: " .. licenseType) end local license = getLicenseFromDB(citizenid, licenseType, true) -- Skip Cache für frische Daten if license then local licenseData = { license = license, config = Config.LicenseTypes[licenseType] } debugPrint("Sende eigene Lizenz an Client: " .. licenseType) TriggerClientEvent('license-system:client:receiveMyLicense', src, licenseData, licenseType) else debugPrint("Keine aktive eigene Lizenz gefunden: " .. licenseType) TriggerClientEvent('license-system:client:receiveMyLicense', src, nil, licenseType) end end) -- EVENT HANDLER: Alle Spieler-Lizenzen anfordern RegisterNetEvent('license-system:server:requestPlayerLicenses', function(targetId) local src = source debugPrint("=== Event: requestPlayerLicenses ===") debugPrint("Source: " .. src .. ", Target: " .. targetId) if not hasPermission(src) then debugPrint("Keine Berechtigung für Spieler: " .. src) TriggerClientEvent('license-system:client:receivePlayerLicenses', src, {}, targetId, "Unbekannt") return end local targetPlayer = QBCore.Functions.GetPlayer(targetId) if not targetPlayer then debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) TriggerClientEvent('license-system:client:receivePlayerLicenses', src, {}, targetId, "Unbekannt") return end local citizenid = targetPlayer.PlayerData.citizenid local targetName = getPlayerName(targetId) local licenses = getAllPlayerLicenses(citizenid) debugPrint("Sende " .. #licenses .. " Lizenzen für " .. targetName) TriggerClientEvent('license-system:client:receivePlayerLicenses', src, licenses, targetId, targetName) end) -- EVENT HANDLER: Lizenz ausstellen (KORRIGIERT) RegisterNetEvent('license-system:server:issueLicense', function(targetId, licenseType, classes) local src = source debugPrint("=== Event: issueLicense ===") debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", Type: " .. licenseType) if not hasPermission(src) then debugPrint("Keine Berechtigung für Spieler: " .. src) TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) return end local targetPlayer = QBCore.Functions.GetPlayer(targetId) if not targetPlayer then debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error') return end local issuerPlayer = QBCore.Functions.GetPlayer(src) if not issuerPlayer then debugPrint("Aussteller nicht gefunden: " .. src) return end local targetCitizenId = targetPlayer.PlayerData.citizenid local issuerCitizenId = issuerPlayer.PlayerData.citizenid -- Prüfen ob aktive Lizenz bereits existiert (Skip Cache) local existingLicense = getLicenseFromDB(targetCitizenId, licenseType, true) if existingLicense then debugPrint("Lizenz bereits vorhanden und aktiv") TriggerClientEvent('QBCore:Notify', src, 'Spieler hat bereits eine aktive ' .. (Config.LicenseTypes[licenseType].label or licenseType) .. '!', 'error') return end -- Kosten prüfen local config = Config.LicenseTypes[licenseType] if config.price and config.price > 0 then if issuerPlayer.PlayerData.money.cash < config.price then debugPrint("Nicht genug Geld für Lizenz-Ausstellung") TriggerClientEvent('QBCore:Notify', src, 'Nicht genug Bargeld! Benötigt: $' .. config.price, 'error') return end -- Geld abziehen issuerPlayer.Functions.RemoveMoney('cash', config.price, 'license-issued') TriggerClientEvent('QBCore:Notify', src, 'Lizenz-Gebühr bezahlt: $' .. config.price, 'success') end -- Lizenz in Datenbank speichern local success = saveLicenseToDB(targetCitizenId, licenseType, issuerCitizenId, classes) if success then local targetName = getPlayerName(targetId) local issuerName = getPlayerName(src) debugPrint("Lizenz erfolgreich ausgestellt") -- Benachrichtigungen TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich ausgestellt für ' .. targetName, 'success') TriggerClientEvent('QBCore:Notify', targetId, 'Du hast eine neue Lizenz erhalten: ' .. config.label, 'success') -- Events senden TriggerClientEvent('license-system:client:licenseIssued', src, targetId, licenseType) TriggerClientEvent('license-system:client:refreshMenu', src) -- Log debugPrint("Lizenz " .. licenseType .. " ausgestellt von " .. issuerName .. " für " .. targetName) else debugPrint("^1Fehler beim Ausstellen der Lizenz^7") TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Ausstellen der Lizenz!', 'error') end end) -- EVENT HANDLER: Lizenz entziehen RegisterNetEvent('license-system:server:revokeLicense', function(targetId, licenseType) local src = source debugPrint("=== Event: revokeLicense ===") debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", Type: " .. licenseType) if not hasPermission(src) then debugPrint("Keine Berechtigung für Spieler: " .. src) TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) return end local targetPlayer = QBCore.Functions.GetPlayer(targetId) if not targetPlayer then debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error') return end local targetCitizenId = targetPlayer.PlayerData.citizenid -- Lizenz entziehen local success = revokeLicenseInDB(targetCitizenId, licenseType) if success then local targetName = getPlayerName(targetId) local issuerName = getPlayerName(src) local config = Config.LicenseTypes[licenseType] debugPrint("Lizenz erfolgreich entzogen") -- Benachrichtigungen TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich entzogen von ' .. targetName, 'success') TriggerClientEvent('QBCore:Notify', targetId, 'Deine Lizenz wurde entzogen: ' .. (config.label or licenseType), 'error') -- Events senden TriggerClientEvent('license-system:client:licenseRevoked', src, targetId, licenseType) TriggerClientEvent('license-system:client:refreshMenu', src) -- Log debugPrint("Lizenz " .. licenseType .. " entzogen von " .. issuerName .. " für " .. targetName) else debugPrint("^1Fehler beim Entziehen der Lizenz^7") TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Entziehen der Lizenz!', 'error') end end) -- EXPORT FUNKTIONEN (KORRIGIERT) exports('hasLicense', function(citizenid, licenseType) if not citizenid or not licenseType then return false end local license = getLicenseFromDB(citizenid, licenseType, true) -- Skip Cache für aktuelle Daten return license ~= nil end) 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) end) exports('revokeLicense', function(citizenid, licenseType) if not citizenid or not licenseType then return false end return revokeLicenseInDB(citizenid, licenseType) end) exports('getPlayerLicenses', function(citizenid) if not citizenid then return {} end return getAllPlayerLicenses(citizenid) end) exports('getPlayerLicense', function(citizenid, licenseType) if not citizenid or not licenseType then return nil end return getLicenseFromDB(citizenid, licenseType, true) -- Skip Cache end) -- DEBUG COMMAND: Lizenz manuell erstellen RegisterCommand('createlicense', function(source, args, rawCommand) if source == 0 then -- Console only if #args < 2 then print("Usage: createlicense ") return end local citizenid = args[1] local licenseType = args[2] if not Config.LicenseTypes[licenseType] then print("Unbekannter Lizenztyp: " .. licenseType) return end local success = saveLicenseToDB(citizenid, licenseType, 'console', {}) if success then print("Lizenz erfolgreich erstellt: " .. licenseType .. " für " .. citizenid) else print("Fehler beim Erstellen der Lizenz") end end end, true) -- DEBUG COMMAND: Lizenz prüfen (ERWEITERT) RegisterCommand('checklicense', function(source, args, rawCommand) if source == 0 then -- Console only if #args < 2 then print("Usage: checklicense ") return end local citizenid = args[1] local licenseType = args[2] -- Direkte DB-Abfrage ohne Cache local query = "SELECT * FROM player_licenses WHERE citizenid = ? AND license_type = ? ORDER BY created_at DESC" local result = MySQL.query.await(query, {citizenid, licenseType}) if result and #result > 0 then print("=== ALLE LIZENZEN GEFUNDEN ===") for i, license in ipairs(result) do print("--- Lizenz " .. i .. " ---") print("ID: " .. (license.id or "N/A")) print("CitizenID: " .. (license.citizenid or "N/A")) print("Typ: " .. (license.license_type or "N/A")) print("Name: " .. (license.name or "N/A")) print("Ausstellungsdatum: " .. (license.issue_date or "N/A")) print("Ablaufdatum: " .. (license.expire_date or "N/A")) print("Ausgestellt von: " .. (license.issued_by or "N/A")) print("Aktiv (DB): " .. tostring(license.is_active)) print("Klassen: " .. (license.classes or "[]")) print("Erstellt am: " .. (license.created_at or "N/A")) print("---") end print("===============================") -- Zusätzlich: Lizenz über Funktion prüfen local license = getLicenseFromDB(citizenid, licenseType, true) if license then print("=== AKTIVE LIZENZ (über Funktion) ===") print("Typ: " .. license.license_type) print("Aktiv: " .. tostring(license.is_active)) print("Status-Check: " .. tostring(isLicenseActive(license))) print("====================================") else print("=== KEINE AKTIVE LIZENZ (über Funktion) ===") end else print("Keine Lizenz gefunden für: " .. citizenid .. " / " .. licenseType) end end end, true) -- DEBUG COMMAND: Alle Lizenzen eines Spielers anzeigen RegisterCommand('checkalllicenses', function(source, args, rawCommand) if source == 0 then -- Console only if #args < 1 then print("Usage: checkalllicenses ") return end local citizenid = args[1] -- Direkte DB-Abfrage local query = "SELECT * FROM player_licenses WHERE citizenid = ? ORDER BY license_type, created_at DESC" local result = MySQL.query.await(query, {citizenid}) if result and #result > 0 then print("=== ALLE LIZENZEN FÜR " .. citizenid .. " ===") for i, license in ipairs(result) do print(i .. ". " .. license.license_type .. " | Aktiv: " .. tostring(license.is_active) .. " | ID: " .. license.id) end print("=========================================") -- Über Funktion local activeLicenses = getAllPlayerLicenses(citizenid) print("=== AKTIVE LIZENZEN (über Funktion) ===") for i, license in ipairs(activeLicenses) do print(i .. ". " .. license.license_type .. " | Aktiv: " .. tostring(license.is_active)) end print("======================================") else print("Keine Lizenzen gefunden für: " .. citizenid) end end end, true) -- DEBUG COMMAND: Lizenz-Status forciert aktualisieren RegisterCommand('fixlicense', function(source, args, rawCommand) if source == 0 then -- Console only if #args < 2 then print("Usage: fixlicense ") return end local citizenid = args[1] local licenseType = args[2] -- Neueste Lizenz auf aktiv setzen local query = [[ UPDATE player_licenses SET is_active = 1 WHERE citizenid = ? AND license_type = ? AND id = ( SELECT id FROM ( SELECT id FROM player_licenses WHERE citizenid = ? AND license_type = ? ORDER BY created_at DESC LIMIT 1 ) as temp ) ]] local result = MySQL.query.await(query, {citizenid, licenseType, citizenid, licenseType}) if result then print("Lizenz-Status aktualisiert für: " .. citizenid .. " / " .. licenseType) -- Cache invalidieren invalidateCache(citizenid, licenseType) -- Prüfen local license = getLicenseFromDB(citizenid, licenseType, true) if license then print("Lizenz ist jetzt aktiv: " .. tostring(license.is_active)) else print("Lizenz konnte nicht geladen werden") end else print("Fehler beim Aktualisieren der Lizenz") end end end, true) -- INITIALISIERUNG CreateThread(function() debugPrint("License-System Server gestartet (Status-Fix)") -- Warten bis QBCore geladen ist while not QBCore do Wait(100) end debugPrint("QBCore erfolgreich geladen") -- Datenbank-Verbindung testen local testResult = safeDBOperation(function() return MySQL.query.await("SELECT 1 as test") end, "Datenbank-Verbindungstest") if testResult then debugPrint("Datenbank-Verbindung erfolgreich") else debugPrint("^1Datenbank-Verbindung fehlgeschlagen^7") end debugPrint("License-System Server vollständig initialisiert") end) -- CLEANUP AddEventHandler('onResourceStop', function(resourceName) if GetCurrentResourceName() == resourceName then debugPrint("License-System Server gestoppt") licenseCache = {} end end) -- 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: " .. cacheCount) print("Config License Types: " .. (Config.LicenseTypes and table.count(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) -- Hilfsfunktion für table.count function table.count(t) local count = 0 for _ in pairs(t) do count = count + 1 end return count end -- Add to server/main.lua RegisterNetEvent('license-system:server:reactivateLicense', function(targetId, licenseType) local src = source debugPrint("=== Event: reactivateLicense ===") debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", Type: " .. licenseType) -- Check if player has permission to reactivate this license type local Player = QBCore.Functions.GetPlayer(src) if not Player then return end local job = Player.PlayerData.job.name local canReactivate = false -- Check if job can reactivate this license type if Config.ReactivationPermissions and Config.ReactivationPermissions[job] then for _, allowedType in ipairs(Config.ReactivationPermissions[job]) do if allowedType == licenseType then canReactivate = true break end end end if not canReactivate then TriggerClientEvent('QBCore:Notify', src, 'Du darfst diesen Lizenztyp nicht reaktivieren!', 'error') return end -- Get target player local targetPlayer = QBCore.Functions.GetPlayer(targetId) if not targetPlayer then TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error') return end local targetCitizenId = targetPlayer.PlayerData.citizenid -- Find the most recent license of this type (even if inactive) local query = [[ SELECT * FROM player_licenses WHERE citizenid = ? AND license_type = ? ORDER BY created_at DESC LIMIT 1 ]] local result = MySQL.query.await(query, {targetCitizenId, licenseType}) if not result or #result == 0 then TriggerClientEvent('QBCore:Notify', src, 'Keine Lizenz dieses Typs gefunden!', 'error') return end -- Reactivate the license local updateQuery = "UPDATE player_licenses SET is_active = 1 WHERE id = ?" local success = MySQL.update.await(updateQuery, {result[1].id}) if success then -- Invalidate cache invalidateCache(targetCitizenId, licenseType) local targetName = getPlayerName(targetId) local issuerName = getPlayerName(src) local config = Config.LicenseTypes[licenseType] -- Notifications TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich reaktiviert für ' .. targetName, 'success') TriggerClientEvent('QBCore:Notify', targetId, 'Deine ' .. (config.label or licenseType) .. ' wurde reaktiviert!', 'success') -- Events TriggerClientEvent('license-system:client:licenseReactivated', src, targetId, licenseType) TriggerClientEvent('license-system:client:refreshMenu', src) -- Log debugPrint("Lizenz " .. licenseType .. " reaktiviert von " .. issuerName .. " für " .. targetName) else TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Reaktivieren der Lizenz!', 'error') end end) -- Add to client/main.lua -- Add a reactivation option to the player license menu local function openReactivateLicenseMenu(targetId, targetName) debugPrint("Öffne Lizenz-Reaktivierungs-Menü für: " .. targetName) -- Request all licenses including inactive ones TriggerServerEvent('license-system:server:requestAllPlayerLicenses', targetId, true) end -- New event to receive all licenses including inactive ones RegisterNetEvent('license-system:client:receiveAllPlayerLicenses', function(licenses, targetId, targetName) debugPrint("=== Event: receiveAllPlayerLicenses ===") debugPrint("Erhaltene Lizenzen: " .. #licenses) local menuOptions = {} if licenses and #licenses > 0 then for _, license in ipairs(licenses) do if license.is_active == 0 then -- Only show inactive licenses local licenseConfig = Config.LicenseTypes[license.license_type] or { label = license.license_type, icon = 'fas fa-id-card', color = '#667eea' } table.insert(menuOptions, { title = licenseConfig.label .. ' ❌', description = 'Status: Ungültig | Ausgestellt: ' .. (license.issue_date or 'Unbekannt'), icon = licenseConfig.icon, onSelect = function() -- Confirmation dialog lib.registerContext({ id = 'confirm_reactivate_license', title = 'Lizenz reaktivieren bestätigen', options = { { title = 'Spieler: ' .. targetName, disabled = true, icon = 'fas fa-user' }, { title = 'Lizenztyp: ' .. licenseConfig.label, disabled = true, icon = licenseConfig.icon }, { title = '─────────────────', disabled = true }, { title = '✅ Bestätigen', description = 'Lizenz jetzt reaktivieren', icon = 'fas fa-check', onSelect = function() debugPrint("Sende Lizenz-Reaktivierung an Server...") TriggerServerEvent('license-system:server:reactivateLicense', targetId, license.license_type) lib.hideContext() end }, { title = '❌ Abbrechen', description = 'Vorgang abbrechen', icon = 'fas fa-times', onSelect = function() openReactivateLicenseMenu(targetId, targetName) end } } }) lib.showContext('confirm_reactivate_license') end }) end end end if #menuOptions == 0 then table.insert(menuOptions, { title = 'Keine inaktiven Lizenzen', description = 'Dieser Spieler hat keine inaktiven Lizenzen', icon = 'fas fa-exclamation-triangle', disabled = true }) end table.insert(menuOptions, { title = '← Zurück', icon = 'fas fa-arrow-left', onSelect = function() openPlayerLicenseMenu(targetId, targetName) end }) lib.registerContext({ id = 'reactivate_license', title = 'Lizenz reaktivieren: ' .. targetName, options = menuOptions }) lib.showContext('reactivate_license') end) -- Add this option to the player license menu -- In openPlayerLicenseMenu function, add: table.insert(menuOptions, { title = 'Lizenz reaktivieren', description = 'Eine inaktive Lizenz wieder aktivieren', icon = 'fas fa-redo', onSelect = function() openReactivateLicenseMenu(targetId, targetName) end }) -- Add to server/main.lua RegisterNetEvent('license-system:server:issueManualLicense', function(targetId, licenseData) local src = source debugPrint("=== Event: issueManualLicense ===") debugPrint("Source: " .. src .. ", Target: " .. targetId) if not hasPermission(src) then debugPrint("Keine Berechtigung für Spieler: " .. src) TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) return end local targetPlayer = QBCore.Functions.GetPlayer(targetId) if not targetPlayer then debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) TriggerClientEvent('QBCore:Notify', src, 'Spieler nicht gefunden!', 'error') return end local issuerPlayer = QBCore.Functions.GetPlayer(src) if not issuerPlayer then debugPrint("Aussteller nicht gefunden: " .. src) return end local targetCitizenId = targetPlayer.PlayerData.citizenid local issuerCitizenId = issuerPlayer.PlayerData.citizenid -- Check if license type is valid if not Config.LicenseTypes[licenseData.license_type] then TriggerClientEvent('QBCore:Notify', src, 'Ungültiger Lizenztyp!', 'error') return end -- Save photo if provided if licenseData.photo_url then -- Here you would save the photo to your storage system -- For example, to a folder or database -- This is just a placeholder debugPrint("Foto für Lizenz vorhanden") end -- Insert into database with manual data local query = [[ INSERT INTO player_licenses (citizenid, license_type, name, birthday, gender, issue_date, expire_date, issued_by, is_active, photo_url, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?) ]] local createdAt = os.time() local insertData = { targetCitizenId, licenseData.license_type, licenseData.name, licenseData.birthday, licenseData.gender, licenseData.issue_date, licenseData.expire_date, issuerCitizenId, licenseData.photo_url or '', createdAt } -- First deactivate any existing licenses of this type local deactivateQuery = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?" MySQL.query.await(deactivateQuery, {targetCitizenId, licenseData.license_type}) -- Then insert the new license local result = MySQL.insert.await(query, insertData) if result then -- Invalidate cache invalidateCache(targetCitizenId) local targetName = getPlayerName(targetId) local issuerName = getPlayerName(src) local config = Config.LicenseTypes[licenseData.license_type] -- Notifications TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich ausgestellt für ' .. targetName, 'success') TriggerClientEvent('QBCore:Notify', targetId, 'Du hast eine neue Lizenz erhalten: ' .. config.label, 'success') -- Events TriggerClientEvent('license-system:client:licenseIssued', src, targetId, licenseData.license_type) TriggerClientEvent('license-system:client:refreshMenu', src) -- Log debugPrint("Lizenz " .. licenseData.license_type .. " manuell ausgestellt von " .. issuerName .. " für " .. targetName) else TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Ausstellen der Lizenz!', 'error') end end) -- Add a new event to get all licenses including inactive ones RegisterNetEvent('license-system:server:requestAllPlayerLicenses', function(targetId, includeInactive) local src = source debugPrint("=== Event: requestAllPlayerLicenses ===") debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", IncludeInactive: " .. tostring(includeInactive)) if not hasPermission(src) then debugPrint("Keine Berechtigung für Spieler: " .. src) TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, {}, targetId, "Unbekannt") return end local targetPlayer = QBCore.Functions.GetPlayer(targetId) if not targetPlayer then debugPrint("Ziel-Spieler nicht gefunden: " .. targetId) TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, {}, targetId, "Unbekannt") return end local citizenid = targetPlayer.PlayerData.citizenid local targetName = getPlayerName(targetId) -- Query to get all licenses, including inactive ones if requested local query = "SELECT * FROM player_licenses WHERE citizenid = ? ORDER BY license_type, created_at DESC" local result = MySQL.query.await(query, {citizenid}) if not result or #result == 0 then debugPrint("Keine Lizenzen gefunden für: " .. citizenid) TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, {}, targetId, targetName) return end -- Process licenses local licenses = {} local seenTypes = {} for _, license in ipairs(result) do -- If includeInactive is true, add all licenses -- Otherwise, only add the newest license per type local shouldAdd = includeInactive or not seenTypes[license.license_type] if shouldAdd then seenTypes[license.license_type] = true -- Add holder name license.holder_name = targetName -- Add issuer name if license.issued_by then local issuerQuery = "SELECT charinfo FROM players WHERE citizenid = ?" local issuerResult = MySQL.query.await(issuerQuery, {license.issued_by}) 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 -- Parse classes 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 -- Normalize is_active if license.is_active == nil then license.is_active = 1 end table.insert(licenses, license) end end debugPrint("Sende " .. #licenses .. " Lizenzen für " .. targetName) TriggerClientEvent('license-system:client:receiveAllPlayerLicenses', src, licenses, targetId, targetName) end) debugPrint("License-System Server vollständig geladen (Status-Fix)")