local QBCore = exports['qb-core']:GetCoreObject() -- Lokale Variablen local licenseCache = {} local cacheTimeout = 300000 -- 5 Minuten -- Hilfsfunktionen 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 -- 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 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 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) -- 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 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 -- 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") if license and license.is_active == 1 then foundLicense = { license = license, config = Config.LicenseTypes[licenseType] } break end end if foundLicense then debugPrint("Sende Lizenz an Client: " .. foundLicense.license.license_type) TriggerClientEvent('license-system:client:receiveLicense', src, foundLicense) else debugPrint("Keine aktive Lizenz gefunden") TriggerClientEvent('license-system:client:receiveLicense', src, nil) end end) -- EVENT HANDLER: Eigene Lizenz anfordern RegisterNetEvent('license-system:server:requestMyLicense', function(licenseType) local src = source debugPrint("=== Event: requestMyLicense ===") debugPrint("Source: " .. src .. ", LicenseType: " .. 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 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 = { 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 = 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) end) -- EVENT HANDLER: Lizenz ausstellen 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 Lizenz bereits existiert 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') 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 = safeDBOperation(function() return saveLicenseToDB(targetCitizenId, licenseType, issuerCitizenId, classes) end, "Fehler beim Speichern der Lizenz") 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 = safeDBOperation(function() return revokeLicenseInDB(targetCitizenId, licenseType) end, "Fehler beim Entziehen der Lizenz") 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) -- 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") return license and license.is_active == 1 end) 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 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 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 {} 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") end) -- INITIALISIERUNG CreateThread(function() debugPrint("License-System Server gestartet (MariaDB-kompatibel)") -- 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 #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)")