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) -- Lizenz aus Datenbank abrufen 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 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 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] -- 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 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 local function getAllPlayerLicenses(citizenid) debugPrint("=== getAllPlayerLicenses START ===") debugPrint("CitizenID: " .. tostring(citizenid)) 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 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 -- 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 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) 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 CONCAT(charinfo->>'$.firstname', ' ', charinfo->>'$.lastname') as name 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 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 (MIT name-Feld) 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, -- HINZUGEFÜGT: name-Feld issueDate, expireDate, issuedBy, 1, classesJson, os.time() } debugPrint("Führe INSERT-Query aus...") debugPrint("Daten: " .. json.encode(insertData)) local result = MySQL.insert.await(insertQuery, insertData) 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 = MySQL.query.await(query, {citizenid, licenseType}) 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 -- 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 = getLicenseFromDB(citizenid, licenseType) 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 = getLicenseFromDB(citizenid, licenseType) 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 = getAllPlayerLicenses(citizenid) 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 = getLicenseFromDB(targetCitizenId, licenseType) 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 = 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) -- 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 -- 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) 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) 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) end) -- Alle Lizenzen eines Spielers abrufen (für andere Resources) exports('getPlayerLicenses', function(citizenid) if not citizenid then return {} end return getAllPlayerLicenses(citizenid) 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) end) -- INITIALISIERUNG CreateThread(function() debugPrint("License-System Server gestartet (korrigiert)") -- Warten bis QBCore geladen ist while not QBCore do Wait(100) end debugPrint("QBCore erfolgreich geladen") -- Datenbank-Verbindung testen local testQuery = "SELECT 1 as test" local testResult = MySQL.query.await(testQuery) 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") -- Cache leeren licenseCache = {} end end) -- DEBUG COMMAND RegisterCommand('licensestats', function(source, args, rawCommand) if source == 0 then -- Console only print("=== LICENSE SYSTEM STATS ===") print("Cache Entries: " .. #licenseCache) print("Config License Types: " .. #Config.LicenseTypes) print("============================") end end, true)