From 91f2af8b8b3fc1c67056031dc64c57cd0599b08c Mon Sep 17 00:00:00 2001 From: Nordi98 Date: Mon, 4 Aug 2025 08:12:36 +0200 Subject: [PATCH] ed --- .../[tools]/nordi_license/client/main.lua | 377 ++++++++++++------ .../[tools]/nordi_license/server/main.lua | 225 ++++++----- 2 files changed, 402 insertions(+), 200 deletions(-) diff --git a/resources/[tools]/nordi_license/client/main.lua b/resources/[tools]/nordi_license/client/main.lua index 804995a00..853b979e0 100644 --- a/resources/[tools]/nordi_license/client/main.lua +++ b/resources/[tools]/nordi_license/client/main.lua @@ -4,6 +4,7 @@ local QBCore = exports['qb-core']:GetCoreObject() local isMenuOpen = false local currentTarget = nil local nearbyPlayers = {} +local isLicenseShowing = false -- Hilfsfunktionen local function debugPrint(message) @@ -15,8 +16,10 @@ end local function safeCallback(cb, ...) if cb and type(cb) == "function" then cb(...) + return true else - debugPrint("^1Callback ist keine Funktion!^7") + debugPrint("^1FEHLER: Callback ist keine Funktion! Typ: " .. type(cb) .. "^7") + return false end end @@ -56,6 +59,7 @@ local function getNearbyPlayers(radius) return a.distance < b.distance end) + debugPrint("Gefundene Spieler in der Nähe: " .. #players) return players end @@ -64,7 +68,9 @@ local function hasPermission() local PlayerData = QBCore.Functions.GetPlayerData() if not PlayerData or not PlayerData.job then return false end - return Config.AuthorizedJobs[PlayerData.job.name] or false + local hasAuth = Config.AuthorizedJobs[PlayerData.job.name] or false + debugPrint("Berechtigung für Job " .. PlayerData.job.name .. ": " .. tostring(hasAuth)) + return hasAuth end -- Lizenz anzeigen @@ -82,7 +88,7 @@ local function showLicense(licenseData) }) SetNuiFocus(true, true) - isMenuOpen = true + isLicenseShowing = true end -- Lizenz schließen @@ -92,34 +98,72 @@ local function closeLicense() }) SetNuiFocus(false, false) - isMenuOpen = false + isLicenseShowing = false debugPrint("Lizenz geschlossen") end +-- Spieler-Lizenz anzeigen +local function showPlayerLicense(targetId) + debugPrint("Rufe Server-Callback auf für Spieler: " .. tostring(targetId)) + + QBCore.Functions.TriggerCallback('license-system:server:getLicense', function(licenseData) + debugPrint("Callback-Antwort erhalten für Spieler-Lizenz") + + if licenseData then + debugPrint("Lizenz-Daten erhalten: " .. licenseData.license.license_type) + showLicense(licenseData) + else + debugPrint("Keine Lizenz-Daten erhalten") + showNotification(Config.Notifications.license_not_found.message, Config.Notifications.license_not_found.type) + end + end, targetId) +end + +-- Eigene Lizenz anzeigen +local function showMyLicense(licenseType) + debugPrint("Rufe Server-Callback auf für eigene Lizenz: " .. tostring(licenseType)) + + QBCore.Functions.TriggerCallback('license-system:server:getMyLicense', function(licenseData) + debugPrint("Eigene Lizenz Callback-Antwort erhalten") + + if licenseData then + debugPrint("Eigene Lizenz-Daten erhalten: " .. licenseData.license.license_type) + showLicense(licenseData) + else + debugPrint("Keine eigene Lizenz gefunden") + local config = Config.LicenseTypes[licenseType] + local licenseName = config and config.label or licenseType + showNotification('Du hast keine ' .. licenseName .. '!', 'error') + end + end, licenseType) +end + -- Spieler-Lizenz-Menü local function openPlayerLicenseMenu(targetId, targetName) - debugPrint("Öffne Lizenz-Menü für Spieler: " .. targetName) + debugPrint("Öffne Lizenz-Menü für Spieler: " .. targetName .. " (ID: " .. targetId .. ")") QBCore.Functions.TriggerCallback('license-system:server:getPlayerLicenses', function(licenses) + debugPrint("Spieler-Lizenzen Callback-Antwort erhalten, Anzahl: " .. (licenses and #licenses or 0)) + local menuOptions = {} if licenses and #licenses > 0 then for _, license in ipairs(licenses) do local licenseConfig = Config.LicenseTypes[license.license_type] or { label = license.license_type, - icon = 'fas fa-id-card' + icon = 'fas fa-id-card', + color = '#667eea' } - local statusIcon = license.is_active and '✅' or '❌' - local statusText = license.is_active and 'Gültig' or 'Ungültig' + local statusIcon = license.is_active == 1 and '✅' or '❌' + local statusText = license.is_active == 1 and 'Gültig' or 'Ungültig' local expireText = license.expire_date or 'Unbegrenzt' table.insert(menuOptions, { - title = licenseConfig.label, + title = licenseConfig.label .. ' ' .. statusIcon, description = 'Status: ' .. statusText .. ' | Gültig bis: ' .. expireText, icon = licenseConfig.icon, onSelect = function() - -- Lizenz anzeigen local licenseData = { license = license, config = licenseConfig @@ -134,24 +178,31 @@ local function openPlayerLicenseMenu(targetId, targetName) } }) end - - -- Aktionen hinzufügen + else table.insert(menuOptions, { - title = '─────────────────', + title = 'Keine Lizenzen gefunden', + description = 'Dieser Spieler hat keine Lizenzen', + icon = 'fas fa-exclamation-triangle', disabled = true }) - - -- Neue Lizenz ausstellen - table.insert(menuOptions, { - title = 'Neue Lizenz ausstellen', - description = 'Eine neue Lizenz für diesen Spieler ausstellen', - icon = 'fas fa-plus', - onSelect = function() - openIssueLicenseMenu(targetId, targetName) - end - }) - - -- Lizenz entziehen + end + + -- Aktionen hinzufügen + table.insert(menuOptions, { + title = '─────────────────', + disabled = true + }) + + table.insert(menuOptions, { + title = 'Neue Lizenz ausstellen', + description = 'Eine neue Lizenz für diesen Spieler ausstellen', + icon = 'fas fa-plus', + onSelect = function() + openIssueLicenseMenu(targetId, targetName) + end + }) + + if licenses and #licenses > 0 then table.insert(menuOptions, { title = 'Lizenz entziehen', description = 'Eine bestehende Lizenz entziehen', @@ -160,26 +211,8 @@ local function openPlayerLicenseMenu(targetId, targetName) openRevokeLicenseMenu(targetId, targetName, licenses) end }) - - else - table.insert(menuOptions, { - title = 'Keine Lizenzen gefunden', - description = 'Dieser Spieler hat keine Lizenzen', - icon = 'fas fa-exclamation-triangle', - disabled = true - }) - - table.insert(menuOptions, { - title = 'Neue Lizenz ausstellen', - description = 'Eine neue Lizenz für diesen Spieler ausstellen', - icon = 'fas fa-plus', - onSelect = function() - openIssueLicenseMenu(targetId, targetName) - end - }) end - -- Zurück-Option table.insert(menuOptions, { title = '← Zurück', icon = 'fas fa-arrow-left', @@ -201,9 +234,14 @@ end -- Lizenz ausstellen Menü local function openIssueLicenseMenu(targetId, targetName) + debugPrint("Öffne Lizenz-Ausstellungs-Menü für: " .. targetName) + local menuOptions = {} for licenseType, config in pairs(Config.LicenseTypes) do + local priceText = config.price and (config.price .. ' $') or 'Kostenlos' + local validityText = config.validity_days and (config.validity_days .. ' Tage') or 'Unbegrenzt' + table.insert(menuOptions, { title = config.label, description = config.description or 'Keine Beschreibung verfügbar', @@ -216,8 +254,8 @@ local function openIssueLicenseMenu(targetId, targetName) end end, metadata = { - {label = 'Preis', value = config.price .. ' $'}, - {label = 'Gültigkeitsdauer', value = config.validity_days and (config.validity_days .. ' Tage') or 'Unbegrenzt'} + {label = 'Preis', value = priceText}, + {label = 'Gültigkeitsdauer', value = validityText} } }) end @@ -247,47 +285,49 @@ local function openDriversLicenseClassMenu(targetId, targetName, licenseType) local function updateMenu() local menuOptions = {} - for _, class in ipairs(config.classes) do - local isSelected = false - for _, selected in ipairs(selectedClasses) do - if selected == class then - isSelected = true - break - end - end - - local classDescriptions = { - ['A'] = 'Motorräder', - ['A1'] = 'Leichte Motorräder (bis 125ccm)', - ['A2'] = 'Mittlere Motorräder (bis 35kW)', - ['B'] = 'PKW (bis 3,5t)', - ['BE'] = 'PKW mit Anhänger', - ['C'] = 'LKW (über 3,5t)', - ['CE'] = 'LKW mit Anhänger', - ['D'] = 'Bus (über 8 Personen)', - ['DE'] = 'Bus mit Anhänger' - } - - table.insert(menuOptions, { - title = 'Klasse ' .. class .. (isSelected and ' ✅' or ''), - description = classDescriptions[class] or 'Keine Beschreibung', - icon = isSelected and 'fas fa-check-square' or 'far fa-square', - onSelect = function() - if isSelected then - -- Klasse entfernen - for i, selected in ipairs(selectedClasses) do - if selected == class then - table.remove(selectedClasses, i) - break - end - end - else - -- Klasse hinzufügen - table.insert(selectedClasses, class) + if config.classes then + for _, class in ipairs(config.classes) do + local isSelected = false + for _, selected in ipairs(selectedClasses) do + if selected == class then + isSelected = true + break end - updateMenu() end - }) + + local classDescriptions = { + ['A'] = 'Motorräder', + ['A1'] = 'Leichte Motorräder (bis 125ccm)', + ['A2'] = 'Mittlere Motorräder (bis 35kW)', + ['B'] = 'PKW (bis 3,5t)', + ['BE'] = 'PKW mit Anhänger', + ['C'] = 'LKW (über 3,5t)', + ['CE'] = 'LKW mit Anhänger', + ['D'] = 'Bus (über 8 Personen)', + ['DE'] = 'Bus mit Anhänger' + } + + table.insert(menuOptions, { + title = 'Klasse ' .. class .. (isSelected and ' ✅' or ''), + description = classDescriptions[class] or 'Keine Beschreibung', + icon = isSelected and 'fas fa-check-square' or 'far fa-square', + onSelect = function() + if isSelected then + -- Klasse entfernen + for i, selected in ipairs(selectedClasses) do + if selected == class then + table.remove(selectedClasses, i) + break + end + end + else + -- Klasse hinzufügen + table.insert(selectedClasses, class) + end + updateMenu() + end + }) + end end table.insert(menuOptions, { @@ -329,6 +369,9 @@ end local function confirmIssueLicense(targetId, targetName, licenseType, classes) local config = Config.LicenseTypes[licenseType] local classText = classes and table.concat(classes, ', ') or 'Keine' + local priceText = config.price and (config.price .. ' $') or 'Kostenlos' + + debugPrint("Bestätige Lizenz-Ausstellung: " .. licenseType .. " für " .. targetName) lib.registerContext({ id = 'confirm_issue_license', @@ -336,19 +379,23 @@ local function confirmIssueLicense(targetId, targetName, licenseType, classes) options = { { title = 'Spieler: ' .. targetName, - disabled = true + disabled = true, + icon = 'fas fa-user' }, { title = 'Lizenztyp: ' .. config.label, - disabled = true + disabled = true, + icon = config.icon }, { title = 'Klassen: ' .. classText, - disabled = true + disabled = true, + icon = 'fas fa-list' }, { - title = 'Kosten: ' .. config.price .. ' $', - disabled = true + title = 'Kosten: ' .. priceText, + disabled = true, + icon = 'fas fa-dollar-sign' }, { title = '─────────────────', @@ -359,6 +406,7 @@ local function confirmIssueLicense(targetId, targetName, licenseType, classes) description = 'Lizenz jetzt ausstellen', icon = 'fas fa-check', onSelect = function() + debugPrint("Sende Lizenz-Ausstellung an Server...") TriggerServerEvent('license-system:server:issueLicense', targetId, licenseType, classes) lib.hideContext() end @@ -379,10 +427,12 @@ end -- Lizenz entziehen Menü local function openRevokeLicenseMenu(targetId, targetName, licenses) + debugPrint("Öffne Lizenz-Entziehungs-Menü für: " .. targetName) + local menuOptions = {} for _, license in ipairs(licenses) do - if license.is_active then + if license.is_active == 1 then local config = Config.LicenseTypes[license.license_type] or { label = license.license_type, icon = 'fas fa-id-card' @@ -399,11 +449,13 @@ local function openRevokeLicenseMenu(targetId, targetName, licenses) options = { { title = 'Spieler: ' .. targetName, - disabled = true + disabled = true, + icon = 'fas fa-user' }, { title = 'Lizenztyp: ' .. config.label, - disabled = true + disabled = true, + icon = config.icon }, { title = '─────────────────', @@ -414,6 +466,7 @@ local function openRevokeLicenseMenu(targetId, targetName, licenses) description = 'Lizenz jetzt entziehen', icon = 'fas fa-check', onSelect = function() + debugPrint("Sende Lizenz-Entziehung an Server...") TriggerServerEvent('license-system:server:revokeLicense', targetId, license.license_type) lib.hideContext() end @@ -463,6 +516,8 @@ end -- Hauptmenü für Lizenz-System local function openLicenseMenu() + debugPrint("Öffne Hauptmenü für Lizenz-System") + if not hasPermission() then showNotification(Config.Notifications.no_permission.message, Config.Notifications.no_permission.type) return @@ -499,6 +554,8 @@ end -- Eigene Lizenzen anzeigen local function showMyLicenses() + debugPrint("Öffne Menü für eigene Lizenzen") + local menuOptions = {} for licenseType, config in pairs(Config.LicenseTypes) do @@ -507,13 +564,7 @@ local function showMyLicenses() description = 'Deine ' .. config.label .. ' anzeigen', icon = config.icon, onSelect = function() - QBCore.Functions.TriggerCallback('license-system:server:getMyLicense', function(licenseData) - if licenseData then - showLicense(licenseData) - else - showNotification('Du hast keine ' .. config.label .. '!', 'error') - end - end, licenseType) + showMyLicense(licenseType) end }) end @@ -529,47 +580,100 @@ end -- Events RegisterNetEvent('license-system:client:showLicense', function(targetId) - QBCore.Functions.TriggerCallback('license-system:server:getLicense', function(licenseData) - showLicense(licenseData) - end, targetId) + debugPrint("Event erhalten: showLicense für ID " .. tostring(targetId)) + showPlayerLicense(targetId) end) RegisterNetEvent('license-system:client:showMyLicense', function(licenseType) - QBCore.Functions.TriggerCallback('license-system:server:getMyLicense', function(licenseData) - showLicense(licenseData) - end, licenseType) + debugPrint("Event erhalten: showMyLicense für Typ " .. tostring(licenseType)) + showMyLicense(licenseType) end) RegisterNetEvent('license-system:client:openCamera', function() + debugPrint("Event erhalten: openCamera") SendNUIMessage({ action = 'openCamera' }) end) +RegisterNetEvent('license-system:client:refreshMenu', function() + debugPrint("Event erhalten: refreshMenu") + if lib.getOpenContextMenu() then + lib.hideContext() + Wait(100) + openLicenseMenu() + end +end) + -- NUI Callbacks RegisterNUICallback('closeLicense', function(data, cb) + debugPrint("NUI Callback: closeLicense") closeLicense() - safeCallback(cb, 'ok') + + if cb and type(cb) == "function" then + cb('ok') + end end) RegisterNUICallback('savePhoto', function(data, cb) + debugPrint("NUI Callback: savePhoto") + if data.photo and data.citizenid then TriggerServerEvent('license-system:server:savePhoto', data.citizenid, data.photo) - safeCallback(cb, 'ok') + + if cb and type(cb) == "function" then + cb('ok') + end else - safeCallback(cb, 'error') + debugPrint("^1Fehler: Foto-Daten unvollständig^7") + + if cb and type(cb) == "function" then + cb('error') + end + end +end) + +RegisterNUICallback('takePicture', function(data, cb) + debugPrint("NUI Callback: takePicture") + + -- Hier könnte eine Kamera-Funktion implementiert werden + if cb and type(cb) == "function" then + cb('ok') end end) -- Commands RegisterCommand(Config.Commands.license.name, function() + debugPrint("Command ausgeführt: " .. Config.Commands.license.name) openLicenseMenu() end, Config.Commands.license.restricted) RegisterCommand(Config.Commands.mylicense.name, function() + debugPrint("Command ausgeführt: " .. Config.Commands.mylicense.name) showMyLicenses() end, Config.Commands.mylicense.restricted) +-- Zusätzliche Commands für schnellen Zugriff +RegisterCommand('ausweis', function() + debugPrint("Command ausgeführt: ausweis") + showMyLicense('id_card') +end, false) + +RegisterCommand('führerschein', function() + debugPrint("Command ausgeführt: führerschein") + showMyLicense('drivers_license') +end, false) + +RegisterCommand('waffenschein', function() + debugPrint("Command ausgeführt: waffenschein") + showMyLicense('weapon_license') +end, false) + +RegisterCommand('pass', function() + debugPrint("Command ausgeführt: pass") + showMyLicense('passport') +end, false) + -- Keybinds if Config.Keybinds.open_license_menu then RegisterKeyMapping(Config.Commands.license.name, Config.Keybinds.open_license_menu.description, 'keyboard', Config.Keybinds.open_license_menu.key) @@ -579,17 +683,68 @@ if Config.Keybinds.show_my_licenses then RegisterKeyMapping(Config.Commands.mylicense.name, Config.Keybinds.show_my_licenses.description, 'keyboard', Config.Keybinds.show_my_licenses.key) end --- Cleanup +-- ESC-Taste zum Schließen der Lizenz +CreateThread(function() + while true do + Wait(0) + + if isLicenseShowing then + if IsControlJustPressed(0, 322) then -- ESC-Taste + closeLicense() + end + else + Wait(500) + end + end +end) + +-- Cleanup und Initialisierung AddEventHandler('onResourceStop', function(resourceName) if GetCurrentResourceName() == resourceName then - if isMenuOpen then + if isLicenseShowing then closeLicense() end + + if lib.getOpenContextMenu() then + lib.hideContext() + end + debugPrint("License-System Client gestoppt") end end) +AddEventHandler('onResourceStart', function(resourceName) + if GetCurrentResourceName() == resourceName then + debugPrint("License-System Client gestartet") + + -- Warten bis QBCore geladen ist + while not QBCore do + Wait(100) + end + + debugPrint("QBCore erfolgreich geladen") + end +end) + +-- Player laden Event +RegisterNetEvent('QBCore:Client:OnPlayerLoaded', function() + debugPrint("Spieler geladen - License-System bereit") +end) + +-- Job Update Event +RegisterNetEvent('QBCore:Client:OnJobUpdate', function(JobInfo) + debugPrint("Job aktualisiert: " .. JobInfo.name) +end) + -- Initialisierung CreateThread(function() - debugPrint("License-System Client gestartet") + debugPrint("License-System Client Thread gestartet") + + -- Warten bis Spieler gespawnt ist + while not NetworkIsPlayerActive(PlayerId()) do + Wait(100) + end + + debugPrint("Spieler ist aktiv - System bereit") end) + diff --git a/resources/[tools]/nordi_license/server/main.lua b/resources/[tools]/nordi_license/server/main.lua index 94fc23174..0a67ac827 100644 --- a/resources/[tools]/nordi_license/server/main.lua +++ b/resources/[tools]/nordi_license/server/main.lua @@ -1,23 +1,28 @@ local QBCore = exports['qb-core']:GetCoreObject() +-- Lokale Variablen +local licenseCache = {} + -- Hilfsfunktionen local function debugPrint(message) if Config.Debug then - print("^2[License-System] " .. message .. "^7") + print("^2[License-System Server] " .. message .. "^7") end end local function safeCallback(cb, ...) if cb and type(cb) == "function" then cb(...) + return true else - debugPrint("^1Callback ist keine Funktion!^7") + debugPrint("^1FEHLER: Callback ist keine Funktion! Typ: " .. type(cb) .. "^7") + return false end end -local function formatDate(date) - if not date then return nil end - return os.date("%d.%m.%Y", date) +local function formatDate(timestamp) + if not timestamp then return nil end + return os.date("%d.%m.%Y", timestamp) end local function addDaysToDate(days) @@ -26,13 +31,12 @@ end local function isLicenseExpired(expireDate) if not expireDate then return false end - return os.time() > expireDate -end - -local function getDaysUntilExpiry(expireDate) - if not expireDate then return nil end - local diff = expireDate - os.time() - return math.ceil(diff / (24 * 60 * 60)) + 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 end -- Spieler-Daten abrufen @@ -43,6 +47,8 @@ local function getPlayerData(source) 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, @@ -95,7 +101,10 @@ end -- Lizenz erstellen local function createLicense(citizenid, licenseType, issuedBy, classes) local licenseConfig = Config.LicenseTypes[licenseType] - if not licenseConfig then return false end + if not licenseConfig then + debugPrint("^1Lizenz-Konfiguration nicht gefunden: " .. licenseType .. "^7") + return false + end local issueDate = os.time() local expireDate = nil @@ -110,11 +119,13 @@ local function createLicense(citizenid, licenseType, issuedBy, classes) issue_date = formatDate(issueDate), expire_date = expireDate and formatDate(expireDate) or nil, issued_by = issuedBy, - is_active = true, + is_active = 1, classes = classes and json.encode(classes) or '[]', created_at = issueDate } + debugPrint("Erstelle Lizenz: " .. licenseType .. " für " .. citizenid) + 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, @@ -126,35 +137,59 @@ local function createLicense(citizenid, licenseType, issuedBy, classes) licenseData.created_at }, function(insertId) if insertId then - debugPrint("Lizenz erstellt: " .. licenseType .. " für " .. citizenid) - return true + 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!^7") - return false + debugPrint("^1Fehler beim Erstellen der Lizenz in der Datenbank!^7") end end) return true end +-- Aussteller-Name abrufen +local function getIssuerName(citizenid, callback) + if not citizenid then + callback('System') + return + 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 + -- Callbacks QBCore.Functions.CreateCallback('license-system:server:getLicense', function(source, cb, targetId) - debugPrint("getLicense aufgerufen für Spieler: " .. tostring(targetId)) + debugPrint("getLicense Callback - Source: " .. source .. ", Target: " .. tostring(targetId)) + + if not cb or type(cb) ~= "function" then + debugPrint("^1FEHLER: Ungültiger Callback in getLicense!^7") + return + end local TargetPlayer = QBCore.Functions.GetPlayer(targetId) if not TargetPlayer then - debugPrint("^1Ziel-Spieler nicht gefunden!^7") + debugPrint("^1Ziel-Spieler nicht gefunden: " .. tostring(targetId) .. "^7") safeCallback(cb, nil) return end local citizenid = TargetPlayer.PlayerData.citizenid + debugPrint("Suche Lizenz für CitizenID: " .. citizenid) - MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE citizenid = ? AND is_active = TRUE ORDER BY created_at DESC LIMIT 1', { + 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 @@ -162,61 +197,52 @@ QBCore.Functions.CreateCallback('license-system:server:getLicense', function(sou license.gender = TargetPlayer.PlayerData.charinfo.gender -- Aussteller-Name abrufen - if license.issued_by then - MySQL.Async.fetchScalar('SELECT CONCAT(JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.firstname")), " ", JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.lastname"))) FROM players WHERE citizenid = ?', { - license.issued_by - }, function(issuerName) - license.issued_by_name = issuerName or 'Unbekannt' - - local licenseData = { - license = license, - config = Config.LicenseTypes[license.license_type] or { - label = license.license_type, - icon = 'fas fa-id-card' - } - } - - debugPrint("Lizenz gefunden: " .. license.license_type) - safeCallback(cb, licenseData) - end) - else - license.issued_by_name = 'System' + 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' + icon = 'fas fa-id-card', + color = '#667eea' } } - debugPrint("Lizenz gefunden: " .. license.license_type) safeCallback(cb, licenseData) - end + end) else - debugPrint("Keine Lizenz gefunden für: " .. citizenid) + debugPrint("Keine aktive Lizenz gefunden für: " .. citizenid) safeCallback(cb, nil) end end) end) QBCore.Functions.CreateCallback('license-system:server:getMyLicense', function(source, cb, licenseType) - debugPrint("getMyLicense aufgerufen für Typ: " .. tostring(licenseType)) + debugPrint("getMyLicense Callback - Source: " .. source .. ", Typ: " .. tostring(licenseType)) + + if not cb or type(cb) ~= "function" then + debugPrint("^1FEHLER: Ungültiger Callback in getMyLicense!^7") + return + end local Player = QBCore.Functions.GetPlayer(source) if not Player then + debugPrint("^1Spieler nicht gefunden: " .. source .. "^7") safeCallback(cb, nil) return end local citizenid = Player.PlayerData.citizenid + debugPrint("Suche eigene Lizenz - CitizenID: " .. citizenid .. ", Typ: " .. licenseType) - MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE citizenid = ? AND license_type = ? AND is_active = TRUE ORDER BY created_at DESC LIMIT 1', { + 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 @@ -224,65 +250,59 @@ QBCore.Functions.CreateCallback('license-system:server:getMyLicense', function(s license.gender = Player.PlayerData.charinfo.gender -- Aussteller-Name abrufen - if license.issued_by then - MySQL.Async.fetchScalar('SELECT CONCAT(JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.firstname")), " ", JSON_UNQUOTE(JSON_EXTRACT(charinfo, "$.lastname"))) FROM players WHERE citizenid = ?', { - license.issued_by - }, function(issuerName) - license.issued_by_name = issuerName or 'Unbekannt' - - local licenseData = { - license = license, - config = Config.LicenseTypes[license.license_type] or { - label = license.license_type, - icon = 'fas fa-id-card' - } - } - - safeCallback(cb, licenseData) - end) - else - license.issued_by_name = 'System' + 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' + icon = 'fas fa-id-card', + color = '#667eea' } } safeCallback(cb, licenseData) - end + end) else - debugPrint("Keine Lizenz vom Typ " .. licenseType .. " gefunden") + debugPrint("Keine eigene Lizenz vom Typ " .. licenseType .. " gefunden") safeCallback(cb, nil) end end) end) QBCore.Functions.CreateCallback('license-system:server:getPlayerLicenses', function(source, cb, targetId) - debugPrint("getPlayerLicenses aufgerufen für Spieler: " .. tostring(targetId)) + debugPrint("getPlayerLicenses Callback - Source: " .. source .. ", Target: " .. tostring(targetId)) + + if not cb or type(cb) ~= "function" then + debugPrint("^1FEHLER: Ungültiger Callback in getPlayerLicenses!^7") + return + end local TargetPlayer = QBCore.Functions.GetPlayer(targetId) if not TargetPlayer then + debugPrint("^1Ziel-Spieler nicht gefunden: " .. tostring(targetId) .. "^7") safeCallback(cb, {}) return end local citizenid = TargetPlayer.PlayerData.citizenid + 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) if result then + debugPrint("Gefundene Lizenzen: " .. #result) + -- Spieler-Daten zu jeder Lizenz hinzufügen for i, license in ipairs(result) 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 end - debugPrint("Gefundene Lizenzen: " .. #result) safeCallback(cb, result) else debugPrint("Keine Lizenzen gefunden") @@ -292,13 +312,24 @@ QBCore.Functions.CreateCallback('license-system:server:getPlayerLicenses', funct end) QBCore.Functions.CreateCallback('license-system:server:canIssueLicense', function(source, cb, licenseType) + debugPrint("canIssueLicense Callback - Source: " .. source .. ", Typ: " .. tostring(licenseType)) + + if not cb or type(cb) ~= "function" then + debugPrint("^1FEHLER: Ungültiger Callback in canIssueLicense!^7") + return + end + local hasAuth = hasPermission(source, licenseType) + debugPrint("Berechtigung für Lizenz " .. licenseType .. ": " .. tostring(hasAuth)) + safeCallback(cb, hasAuth) end) -- Events RegisterNetEvent('license-system:server:issueLicense', function(targetId, licenseType, classes) local src = source + debugPrint("Event: issueLicense - Von: " .. src .. ", Für: " .. targetId .. ", Typ: " .. licenseType) + local Player = QBCore.Functions.GetPlayer(src) local TargetPlayer = QBCore.Functions.GetPlayer(targetId) @@ -335,13 +366,16 @@ RegisterNetEvent('license-system:server:issueLicense', function(targetId, licens -- Geld abziehen TargetPlayer.Functions.RemoveMoney('cash', licenseConfig.price, 'license-fee') + debugPrint("Geld abgezogen: " .. licenseConfig.price .. "$ von " .. TargetPlayer.PlayerData.charinfo.firstname) end -- Alte Lizenz deaktivieren - MySQL.Async.execute('UPDATE player_licenses SET is_active = FALSE WHERE citizenid = ? AND license_type = ?', { + 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) @@ -359,6 +393,8 @@ end) RegisterNetEvent('license-system:server:revokeLicense', function(targetId, licenseType) local src = source + debugPrint("Event: revokeLicense - Von: " .. src .. ", Für: " .. targetId .. ", Typ: " .. licenseType) + local Player = QBCore.Functions.GetPlayer(src) local TargetPlayer = QBCore.Functions.GetPlayer(targetId) @@ -374,16 +410,20 @@ RegisterNetEvent('license-system:server:revokeLicense', function(targetId, licen end -- Lizenz deaktivieren - MySQL.Async.execute('UPDATE player_licenses SET is_active = FALSE WHERE citizenid = ? AND license_type = ? AND is_active = TRUE', { + 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]?.label or licenseType) .. ' wurde entzogen!', 'error') + TriggerClientEvent('QBCore:Notify', targetId, 'Deine ' .. (Config.LicenseTypes[licenseType] and Config.LicenseTypes[licenseType].label or licenseType) .. ' wurde entzogen!', 'error') - -- Log erstellen - debugPrint(Player.PlayerData.charinfo.firstname .. ' ' .. Player.PlayerData.charinfo.lastname .. ' hat ' .. TargetPlayer.PlayerData.charinfo.firstname .. ' ' .. TargetPlayer.PlayerData.charinfo.lastname .. ' die ' .. (Config.LicenseTypes[licenseType]?.label or licenseType) .. ' entzogen') + -- Cache aktualisieren + if licenseCache[TargetPlayer.PlayerData.citizenid] then + licenseCache[TargetPlayer.PlayerData.citizenid][licenseType] = nil + end + + 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 @@ -392,12 +432,13 @@ end) RegisterNetEvent('license-system:server:savePhoto', function(citizenid, photoData) local src = source - local Player = QBCore.Functions.GetPlayer(src) + debugPrint("Event: savePhoto für CitizenID: " .. citizenid) + local Player = QBCore.Functions.GetPlayer(src) if not Player then return end -- Foto in der Datenbank speichern - MySQL.Async.execute('UPDATE player_licenses SET photo_url = ? WHERE citizenid = ? AND is_active = TRUE', { + MySQL.Async.execute('UPDATE player_licenses SET photo_url = ? WHERE citizenid = ? AND is_active = 1', { photoData, citizenid }, function(affectedRows) @@ -420,6 +461,8 @@ QBCore.Commands.Add('givelicense', 'Lizenz an Spieler vergeben', { local licenseType = args[2] local classes = args[3] and {args[3]} or nil + 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 @@ -440,6 +483,8 @@ QBCore.Commands.Add('revokelicense', 'Lizenz entziehen', { local targetId = tonumber(args[1]) local licenseType = args[2] + 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 @@ -456,7 +501,7 @@ if Config.Database.auto_cleanup then local cutoffDate = os.time() - (Config.Database.cleanup_days * 24 * 60 * 60) - MySQL.Async.execute('DELETE FROM player_licenses WHERE is_active = FALSE AND created_at < ?', { + MySQL.Async.execute('DELETE FROM player_licenses WHERE is_active = 0 AND created_at < ?', { cutoffDate }, function(affectedRows) if affectedRows > 0 then @@ -472,18 +517,12 @@ CreateThread(function() while true do Wait(60 * 60 * 1000) -- Jede Stunde - MySQL.Async.fetchAll('SELECT * FROM player_licenses WHERE is_active = TRUE AND expire_date IS NOT NULL', {}, function(result) + 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 - local expireTime = os.time({ - year = tonumber(string.sub(license.expire_date, 7, 10)), - month = tonumber(string.sub(license.expire_date, 4, 5)), - day = tonumber(string.sub(license.expire_date, 1, 2)) - }) - - if isLicenseExpired(expireTime) then + if isLicenseExpired(license.expire_date) then -- Lizenz als abgelaufen markieren - MySQL.Async.execute('UPDATE player_licenses SET is_active = FALSE WHERE id = ?', { + MySQL.Async.execute('UPDATE player_licenses SET is_active = 0 WHERE id = ?', { license.id }) @@ -509,7 +548,7 @@ AddEventHandler('onResourceStart', function(resourceName) issue_date VARCHAR(20) NOT NULL, expire_date VARCHAR(20) NULL, issued_by VARCHAR(50) NULL, - is_active BOOLEAN DEFAULT TRUE, + is_active TINYINT(1) DEFAULT 1, classes TEXT NULL, photo_url TEXT NULL, notes TEXT NULL, @@ -519,12 +558,20 @@ AddEventHandler('onResourceStart', function(resourceName) 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) end end) AddEventHandler('onResourceStop', function(resourceName) if GetCurrentResourceName() == resourceName then debugPrint("License-System Server gestoppt") + -- Cache leeren + licenseCache = {} end end)