From 21fc21b9d697ff96faf174b20222aa5b5ec093f3 Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Mon, 4 Aug 2025 08:45:50 +0200 Subject: [PATCH] ed --- resources/[tools]/nordi_license/config.lua | 2 +- .../[tools]/nordi_license/server/main.lua | 926 ++++++++++-------- 2 files changed, 495 insertions(+), 433 deletions(-) diff --git a/resources/[tools]/nordi_license/config.lua b/resources/[tools]/nordi_license/config.lua index 3e4e7d74d..facd63b09 100644 --- a/resources/[tools]/nordi_license/config.lua +++ b/resources/[tools]/nordi_license/config.lua @@ -154,7 +154,7 @@ Config.LicenseLocations = { }, ped = { model = 'a_m_m_business_01', - coords = vector4(-544.85, -204.13, 37.22, 180.0) + coords = vector4(-544.9543, -204.8450, 37.2151, 219.1676) } }, ['driving_school'] = { diff --git a/resources/[tools]/nordi_license/server/main.lua b/resources/[tools]/nordi_license/server/main.lua index 32516f03f..1003b5f9d 100644 --- a/resources/[tools]/nordi_license/server/main.lua +++ b/resources/[tools]/nordi_license/server/main.lua @@ -2,570 +2,632 @@ 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("^2[License-System Server] " .. message .. "^7") + print("^3[License-System Server] " .. message .. "^7") end end -local function formatDate(timestamp) - if not timestamp then return nil end - return os.date("%d.%m.%Y", timestamp) -end - -local function addDaysToDate(days) - return os.time() + (days * 24 * 60 * 60) -end - -local function isLicenseExpired(expireDate) - if not expireDate then return false end - local expireTime = os.time({ - year = tonumber(string.sub(expireDate, 7, 10)), - month = tonumber(string.sub(expireDate, 4, 5)), - day = tonumber(string.sub(expireDate, 1, 2)) - }) - return os.time() > expireTime +-- 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(source) - local Player = QBCore.Functions.GetPlayer(source) +local function getPlayerData(src) + local Player = QBCore.Functions.GetPlayer(src) if not Player then return nil end return { citizenid = Player.PlayerData.citizenid, - name = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname, - firstname = Player.PlayerData.charinfo.firstname, - lastname = Player.PlayerData.charinfo.lastname, - birthday = Player.PlayerData.charinfo.birthdate, - gender = Player.PlayerData.charinfo.gender, - job = Player.PlayerData.job.name, - money = Player.PlayerData.money.cash + name = getPlayerName(src), + charinfo = Player.PlayerData.charinfo, + job = Player.PlayerData.job } end -- Berechtigung prüfen -local function hasPermission(source, licenseType) - local playerData = getPlayerData(source) - if not playerData then return false end - - -- Admin-Check - if QBCore.Functions.HasPermission(source, 'admin') then - return true - end - - -- Job-Check - if Config.AuthorizedJobs[playerData.job] then - return true - end - - -- Spezifische Lizenz-Berechtigung - local licenseConfig = Config.LicenseTypes[licenseType] - if licenseConfig and licenseConfig.required_job then - return playerData.job == licenseConfig.required_job - end - - return false -end - --- Benötigte Items prüfen -local function hasRequiredItems(source, licenseType) - local Player = QBCore.Functions.GetPlayer(source) +local function hasPermission(src) + local Player = QBCore.Functions.GetPlayer(src) if not Player then return false end - local licenseConfig = Config.LicenseTypes[licenseType] - if not licenseConfig or not licenseConfig.required_items then return true end + local job = Player.PlayerData.job + if not job then return false end - for _, item in ipairs(licenseConfig.required_items) do - local hasItem = Player.Functions.GetItemByName(item) - if not hasItem or hasItem.amount < 1 then - return false + 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 - return true + if cleaned > 0 then + debugPrint("Cache bereinigt: " .. cleaned .. " Einträge") + end end --- Lizenz erstellen -local function createLicense(citizenid, licenseType, issuedBy, classes) - local licenseConfig = Config.LicenseTypes[licenseType] - if not licenseConfig then - debugPrint("^1Lizenz-Konfiguration nicht gefunden: " .. licenseType .. "^7") - return false +-- 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 issueDate = os.time() + 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 licenseConfig.can_expire and licenseConfig.validity_days then - expireDate = addDaysToDate(licenseConfig.validity_days) + if config.validity_days then + local expireTimestamp = os.time() + (config.validity_days * 24 * 60 * 60) + expireDate = os.date("%d.%m.%Y", expireTimestamp) end - local licenseData = { - citizenid = citizenid, - license_type = licenseType, - issue_date = formatDate(issueDate), - expire_date = expireDate and formatDate(expireDate) or nil, - issued_by = issuedBy, - is_active = 1, - classes = classes and json.encode(classes) or '[]', - created_at = issueDate + -- 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("Erstelle Lizenz: " .. licenseType .. " für " .. citizenid) + debugPrint("Führe INSERT-Query aus...") + debugPrint("Daten: " .. json.encode(insertData)) - MySQL.Async.insert('INSERT INTO player_licenses (citizenid, license_type, issue_date, expire_date, issued_by, is_active, classes, created_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)', { - licenseData.citizenid, - licenseData.license_type, - licenseData.issue_date, - licenseData.expire_date, - licenseData.issued_by, - licenseData.is_active, - licenseData.classes, - licenseData.created_at - }, function(insertId) - if insertId then - debugPrint("Lizenz erfolgreich erstellt mit ID: " .. insertId) - -- Cache aktualisieren - if not licenseCache[citizenid] then - licenseCache[citizenid] = {} - end - licenseCache[citizenid][licenseType] = licenseData - else - debugPrint("^1Fehler beim Erstellen der Lizenz in der Datenbank!^7") - end - end) + local result = MySQL.insert.await(insertQuery, insertData) - return true -end - --- Aussteller-Name abrufen -local function getIssuerName(citizenid, callback) - if not citizenid then - callback('System') - return + 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 - - MySQL.Async.fetchScalar('SELECT CONCAT(JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.firstname")), " ", JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.lastname"))) FROM players WHERE citizenid = ?', { - citizenid - }, function(issuerName) - callback(issuerName or 'Unbekannt') - end) end --- EVENT: Einzelne Lizenz abrufen +-- 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: " .. tostring(targetId)) + debugPrint("Source: " .. src .. ", Target: " .. targetId) - local TargetPlayer = QBCore.Functions.GetPlayer(targetId) - if not TargetPlayer then - debugPrint("^1Ziel-Spieler nicht gefunden: " .. tostring(targetId) .. "^7") + if not hasPermission(src) then + debugPrint("Keine Berechtigung für Spieler: " .. src) TriggerClientEvent('license-system:client:receiveLicense', src, nil) return end - local citizenid = TargetPlayer.PlayerData.citizenid - debugPrint("Suche Lizenz für CitizenID: " .. citizenid) + 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 - MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE citizenid = ? AND is_active = 1 ORDER BY created_at DESC LIMIT 1', { - citizenid - }, function(result) - if result and result[1] then - local license = result[1] - debugPrint("Lizenz gefunden: " .. license.license_type) - - -- Spieler-Daten hinzufügen - license.name = TargetPlayer.PlayerData.charinfo.firstname .. ' ' .. TargetPlayer.PlayerData.charinfo.lastname - license.birthday = TargetPlayer.PlayerData.charinfo.birthdate - license.gender = TargetPlayer.PlayerData.charinfo.gender - - -- Aussteller-Name abrufen - getIssuerName(license.issued_by, function(issuerName) - license.issued_by_name = issuerName - - local licenseData = { - license = license, - config = Config.LicenseTypes[license.license_type] or { - label = license.license_type, - icon = 'fas fa-id-card', - color = '#667eea' - } - } - - debugPrint("Sende Lizenz-Daten an Client") - TriggerClientEvent('license-system:client:receiveLicense', src, licenseData) - end) - else - debugPrint("Keine aktive Lizenz gefunden für: " .. citizenid) - TriggerClientEvent('license-system:client:receiveLicense', src, nil) + 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) + 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: Eigene Lizenz abrufen +-- EVENT HANDLER: Eigene Lizenz anfordern RegisterNetEvent('license-system:server:requestMyLicense', function(licenseType) local src = source debugPrint("=== Event: requestMyLicense ===") - debugPrint("Source: " .. src .. ", LicenseType: " .. tostring(licenseType)) + debugPrint("Source: " .. src .. ", LicenseType: " .. licenseType) local Player = QBCore.Functions.GetPlayer(src) if not Player then - debugPrint("^1Spieler nicht gefunden: " .. src .. "^7") + debugPrint("Spieler nicht gefunden: " .. src) TriggerClientEvent('license-system:client:receiveMyLicense', src, nil, licenseType) return end local citizenid = Player.PlayerData.citizenid - debugPrint("Suche eigene Lizenz - CitizenID: " .. citizenid .. ", Typ: " .. licenseType) + local license = getLicenseFromDB(citizenid, licenseType) - MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE citizenid = ? AND license_type = ? AND is_active = 1 ORDER BY created_at DESC LIMIT 1', { - citizenid, - licenseType - }, function(result) - if result and result[1] then - local license = result[1] - debugPrint("Eigene Lizenz gefunden: " .. license.license_type) - - -- Spieler-Daten hinzufügen - license.name = Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname - license.birthday = Player.PlayerData.charinfo.birthdate - license.gender = Player.PlayerData.charinfo.gender - - -- Aussteller-Name abrufen - getIssuerName(license.issued_by, function(issuerName) - license.issued_by_name = issuerName - - local licenseData = { - license = license, - config = Config.LicenseTypes[license.license_type] or { - label = license.license_type, - icon = 'fas fa-id-card', - color = '#667eea' - } - } - - debugPrint("Sende eigene Lizenz-Daten an Client") - TriggerClientEvent('license-system:client:receiveMyLicense', src, licenseData, licenseType) - end) - else - debugPrint("Keine eigene Lizenz vom Typ " .. licenseType .. " gefunden") - TriggerClientEvent('license-system:client:receiveMyLicense', src, nil, licenseType) - end - end) + 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: Alle Spieler-Lizenzen abrufen +-- EVENT HANDLER: Alle Spieler-Lizenzen anfordern RegisterNetEvent('license-system:server:requestPlayerLicenses', function(targetId) local src = source debugPrint("=== Event: requestPlayerLicenses ===") - debugPrint("Source: " .. src .. ", Target: " .. tostring(targetId)) + debugPrint("Source: " .. src .. ", Target: " .. targetId) - local TargetPlayer = QBCore.Functions.GetPlayer(targetId) - if not TargetPlayer then - debugPrint("^1Ziel-Spieler nicht gefunden: " .. tostring(targetId) .. "^7") + if not hasPermission(src) then + debugPrint("Keine Berechtigung für Spieler: " .. src) TriggerClientEvent('license-system:client:receivePlayerLicenses', src, {}, targetId, "Unbekannt") return end - local citizenid = TargetPlayer.PlayerData.citizenid - local targetName = TargetPlayer.PlayerData.charinfo.firstname .. ' ' .. TargetPlayer.PlayerData.charinfo.lastname - debugPrint("Suche alle Lizenzen für CitizenID: " .. citizenid) - - MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE citizenid = ? ORDER BY created_at DESC', { - citizenid - }, function(result) - local licenses = result or {} - debugPrint("Gefundene Lizenzen: " .. #licenses) - - -- Spieler-Daten zu jeder Lizenz hinzufügen - for i, license in ipairs(licenses) do - license.name = TargetPlayer.PlayerData.charinfo.firstname .. ' ' .. TargetPlayer.PlayerData.charinfo.lastname - license.birthday = TargetPlayer.PlayerData.charinfo.birthdate - license.gender = TargetPlayer.PlayerData.charinfo.gender - license.issued_by_name = 'System' -- Wird später durch echten Namen ersetzt - - debugPrint("Lizenz " .. i .. ": " .. license.license_type .. " (Aktiv: " .. tostring(license.is_active) .. ")") - end - - debugPrint("Sende " .. #licenses .. " Lizenzen an Client") - TriggerClientEvent('license-system:client:receivePlayerLicenses', src, licenses, targetId, targetName) - end) -end) - --- EVENT: Berechtigung prüfen -RegisterNetEvent('license-system:server:checkPermission', function(licenseType) - local src = source - debugPrint("=== Event: checkPermission ===") - debugPrint("Source: " .. src .. ", LicenseType: " .. tostring(licenseType)) - - local hasAuth = hasPermission(src, licenseType) - debugPrint("Berechtigung für Lizenz " .. licenseType .. ": " .. tostring(hasAuth)) - - TriggerClientEvent('license-system:client:receivePermission', src, hasAuth, licenseType) -end) - --- EVENT: Lizenz ausstellen -RegisterNetEvent('license-system:server:issueLicense', function(targetId, licenseType, classes) - local src = source - debugPrint("=== Event: issueLicense ===") - debugPrint("Von: " .. src .. ", Für: " .. targetId .. ", Typ: " .. licenseType) - - local Player = QBCore.Functions.GetPlayer(src) - local TargetPlayer = QBCore.Functions.GetPlayer(targetId) - - if not Player or not TargetPlayer then - TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_players_nearby.message, Config.Notifications.no_players_nearby.type) + 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 - -- Berechtigung prüfen - if not hasPermission(src, licenseType) then + 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 - -- Lizenz-Konfiguration prüfen - local licenseConfig = Config.LicenseTypes[licenseType] - if not licenseConfig then - TriggerClientEvent('QBCore:Notify', src, 'Unbekannter Lizenztyp!', 'error') + 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 - -- Benötigte Items prüfen - if not hasRequiredItems(targetId, licenseType) then - TriggerClientEvent('QBCore:Notify', src, Config.Notifications.missing_items.message, Config.Notifications.missing_items.type) + local issuerPlayer = QBCore.Functions.GetPlayer(src) + if not issuerPlayer then + debugPrint("Aussteller nicht gefunden: " .. src) return end - -- Geld prüfen (falls Kosten anfallen) - if licenseConfig.price > 0 then - if TargetPlayer.PlayerData.money.cash < licenseConfig.price then - TriggerClientEvent('QBCore:Notify', src, Config.Notifications.insufficient_funds.message, Config.Notifications.insufficient_funds.type) + 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 - TargetPlayer.Functions.RemoveMoney('cash', licenseConfig.price, 'license-fee') - debugPrint("Geld abgezogen: " .. licenseConfig.price .. "$ von " .. TargetPlayer.PlayerData.charinfo.firstname) + issuerPlayer.Functions.RemoveMoney('cash', config.price, 'license-issued') + TriggerClientEvent('QBCore:Notify', src, 'Lizenz-Gebühr bezahlt: $' .. config.price, 'success') end - -- Alte Lizenz deaktivieren - MySQL.Async.execute('UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?', { - TargetPlayer.PlayerData.citizenid, - licenseType - }, function(affectedRows) - debugPrint("Alte Lizenzen deaktiviert: " .. affectedRows) - end) - - -- Neue Lizenz erstellen - local success = createLicense(TargetPlayer.PlayerData.citizenid, licenseType, Player.PlayerData.citizenid, classes) + -- Lizenz in Datenbank speichern + local success = saveLicenseToDB(targetCitizenId, licenseType, issuerCitizenId, classes) if success then - TriggerClientEvent('QBCore:Notify', src, Config.Notifications.license_granted.message, Config.Notifications.license_granted.type) - TriggerClientEvent('QBCore:Notify', targetId, 'Du hast eine neue ' .. licenseConfig.label .. ' erhalten!', 'success') + local targetName = getPlayerName(targetId) + local issuerName = getPlayerName(src) - -- Client über erfolgreiche Ausstellung informieren + 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 erstellen - debugPrint(Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname .. ' hat ' .. TargetPlayer.PlayerData.charinfo.firstname .. ' ' .. TargetPlayer.PlayerData.charinfo.lastname .. ' eine ' .. licenseConfig.label .. ' ausgestellt') + -- 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: Lizenz entziehen +-- EVENT HANDLER: Lizenz entziehen RegisterNetEvent('license-system:server:revokeLicense', function(targetId, licenseType) local src = source debugPrint("=== Event: revokeLicense ===") - debugPrint("Von: " .. src .. ", Für: " .. targetId .. ", Typ: " .. licenseType) + debugPrint("Source: " .. src .. ", Target: " .. targetId .. ", Type: " .. licenseType) - local Player = QBCore.Functions.GetPlayer(src) - local TargetPlayer = QBCore.Functions.GetPlayer(targetId) - - if not Player or not TargetPlayer then - TriggerClientEvent('QBCore:Notify', src, Config.Notifications.no_players_nearby.message, Config.Notifications.no_players_nearby.type) - return - end - - -- Berechtigung prüfen - if not hasPermission(src, licenseType) then + 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 - -- Lizenz deaktivieren - MySQL.Async.execute('UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ? AND is_active = 1', { - TargetPlayer.PlayerData.citizenid, - licenseType - }, function(affectedRows) - if affectedRows > 0 then - TriggerClientEvent('QBCore:Notify', src, Config.Notifications.license_revoked.message, Config.Notifications.license_revoked.type) - TriggerClientEvent('QBCore:Notify', targetId, 'Deine ' .. (Config.LicenseTypes[licenseType] and Config.LicenseTypes[licenseType].label or licenseType) .. ' wurde entzogen!', 'error') - - -- Cache aktualisieren - if licenseCache[TargetPlayer.PlayerData.citizenid] then - licenseCache[TargetPlayer.PlayerData.citizenid][licenseType] = nil - end - - -- Client über erfolgreiche Entziehung informieren - TriggerClientEvent('license-system:client:licenseRevoked', src, targetId, licenseType) - - debugPrint(Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname .. ' hat ' .. TargetPlayer.PlayerData.charinfo.firstname .. ' ' .. TargetPlayer.PlayerData.charinfo.lastname .. ' die ' .. (Config.LicenseTypes[licenseType] and Config.LicenseTypes[licenseType].label or licenseType) .. ' entzogen') - else - TriggerClientEvent('QBCore:Notify', src, 'Keine aktive Lizenz gefunden!', 'error') - end - 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: Foto speichern +-- EVENT HANDLER: Foto speichern RegisterNetEvent('license-system:server:savePhoto', function(citizenid, photoData) local src = source debugPrint("=== Event: savePhoto ===") - debugPrint("CitizenID: " .. citizenid) + debugPrint("Source: " .. src .. ", CitizenID: " .. citizenid) - local Player = QBCore.Functions.GetPlayer(src) - if not Player then return end + -- 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) - -- Foto in der Datenbank speichern - MySQL.Async.execute('UPDATE player_licenses SET photo_url = ? WHERE citizenid = ? AND is_active = 1', { - photoData, - citizenid - }, function(affectedRows) - if affectedRows > 0 then - TriggerClientEvent('QBCore:Notify', src, Config.Notifications.photo_saved.message, Config.Notifications.photo_saved.type) - debugPrint("Foto gespeichert für: " .. citizenid) - else - TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Speichern des Fotos!', 'error') - end - end) + TriggerClientEvent('QBCore:Notify', src, 'Foto gespeichert!', 'success') end) --- Admin-Kommandos -QBCore.Commands.Add('givelicense', 'Lizenz an Spieler vergeben', { - {name = 'id', help = 'Spieler ID'}, - {name = 'type', help = 'Lizenztyp'}, - {name = 'classes', help = 'Klassen (optional)'} -}, true, function(source, args) - local targetId = tonumber(args[1]) - local licenseType = args[2] - local classes = args[3] and {args[3]} or nil +-- 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 - debugPrint("Admin-Command: givelicense - ID: " .. tostring(targetId) .. ", Typ: " .. tostring(licenseType)) - - if not targetId or not licenseType then - TriggerClientEvent('QBCore:Notify', source, 'Verwendung: /givelicense [id] [typ] [klassen]', 'error') - return - end - - if not Config.LicenseTypes[licenseType] then - TriggerClientEvent('QBCore:Notify', source, 'Unbekannter Lizenztyp!', 'error') - return - end - - TriggerEvent('license-system:server:issueLicense', targetId, licenseType, classes) -end, 'admin') + local license = getLicenseFromDB(citizenid, licenseType) + return license and license.is_active == 1 +end) -QBCore.Commands.Add('revokelicense', 'Lizenz entziehen', { - {name = 'id', help = 'Spieler ID'}, - {name = 'type', help = 'Lizenztyp'} -}, true, function(source, args) - local targetId = tonumber(args[1]) - local licenseType = args[2] +-- 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 - debugPrint("Admin-Command: revokelicense - ID: " .. tostring(targetId) .. ", Typ: " .. tostring(licenseType)) - - if not targetId or not licenseType then - TriggerClientEvent('QBCore:Notify', source, 'Verwendung: /revokelicense [id] [typ]', 'error') - return - end - - TriggerEvent('license-system:server:revokeLicense', targetId, licenseType) -end, 'admin') + issuedBy = issuedBy or 'system' + return saveLicenseToDB(citizenid, licenseType, issuedBy, classes) +end) --- Cleanup-Task für abgelaufene Lizenzen -if Config.Database.auto_cleanup then - CreateThread(function() - while true do - Wait(24 * 60 * 60 * 1000) -- Einmal täglich - - local cutoffDate = os.time() - (Config.Database.cleanup_days * 24 * 60 * 60) - - MySQL.Async.execute('DELETE FROM player_licenses WHERE is_active = 0 AND created_at < ?', { - cutoffDate - }, function(affectedRows) - if affectedRows > 0 then - debugPrint("Cleanup: " .. affectedRows .. " alte Lizenzen gelöscht") - end - end) - end - end) -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) --- Lizenz-Ablauf-Checker +-- 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() - while true do - Wait(60 * 60 * 1000) -- Jede Stunde - - MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE is_active = 1 AND expire_date IS NOT NULL', {}, function(result) - if result then - for _, license in ipairs(result) do - if isLicenseExpired(license.expire_date) then - -- Lizenz als abgelaufen markieren - MySQL.Async.execute('UPDATE player_licenses SET is_active = 0 WHERE id = ?', { - license.id - }) - - debugPrint("Lizenz abgelaufen: " .. license.license_type .. " für " .. license.citizenid) - end - end - end - end) - end -end) - --- Resource Start/Stop Events -AddEventHandler('onResourceStart', function(resourceName) - if GetCurrentResourceName() == resourceName then - debugPrint("License-System Server gestartet (Event-basiert)") - - -- Datenbank-Tabelle erstellen falls nicht vorhanden - MySQL.Async.execute([[ - CREATE TABLE IF NOT EXISTS player_licenses ( - id INT AUTO_INCREMENT PRIMARY KEY, - citizenid VARCHAR(50) NOT NULL, - license_type VARCHAR(50) NOT NULL, - issue_date VARCHAR(20) NOT NULL, - expire_date VARCHAR(20) NULL, - issued_by VARCHAR(50) NULL, - is_active TINYINT(1) DEFAULT 1, - classes TEXT NULL, - photo_url TEXT NULL, - notes TEXT NULL, - created_at BIGINT NOT NULL, - updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, - INDEX idx_citizenid (citizenid), - INDEX idx_license_type (license_type), - INDEX idx_active (is_active) - ) - ]], {}, function(success) - if success then - debugPrint("Datenbank-Tabelle erfolgreich erstellt/überprüft") - else - debugPrint("^1Fehler beim Erstellen der Datenbank-Tabelle!^7") - end - end) + 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)