1
0
Fork 0
forked from Simnation/Main
Main/resources/[tools]/nordi_license/server/main.lua
2025-08-04 09:48:26 +02:00

1246 lines
44 KiB
Lua

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