2025-08-04 08:53:16 +02:00
|
|
|
local QBCore = exports['qb-core']:GetCoreObject()
|
|
|
|
|
|
|
|
-- Lokale Variablen
|
|
|
|
local licenseCache = {}
|
|
|
|
local cacheTimeout = 300000 -- 5 Minuten
|
|
|
|
|
2025-08-04 09:05:35 +02:00
|
|
|
-- Debug-Funktion
|
2025-08-04 08:53:16 +02:00
|
|
|
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
|
|
|
|
|
2025-08-04 09:05:35 +02:00
|
|
|
-- 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
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- Cache-Funktionen (KORRIGIERT - Aggressive Cache-Invalidierung)
|
2025-08-04 08:53:16 +02:00
|
|
|
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
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- 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
|
|
|
|
|
2025-08-04 09:05:35 +02:00
|
|
|
-- Spieler-Name aus JSON extrahieren
|
2025-08-04 08:53:16 +02:00
|
|
|
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
|
|
|
|
|
2025-08-04 09:05:35 +02:00
|
|
|
-- Sichere DB-Operation
|
2025-08-04 08:56:54 +02:00
|
|
|
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
|
2025-08-04 09:05:35 +02:00
|
|
|
Wait(1000)
|
2025-08-04 08:56:54 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- Lizenz-Status prüfen (NEUE FUNKTION)
|
|
|
|
local function isLicenseActive(license)
|
|
|
|
if not license then return false end
|
2025-08-04 08:53:16 +02:00
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- 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
|
2025-08-04 08:53:16 +02:00
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
if isActive ~= 1 then
|
|
|
|
debugPrint("Lizenz inaktiv (is_active = " .. tostring(isActive) .. ")")
|
|
|
|
return false
|
|
|
|
end
|
2025-08-04 08:53:16 +02:00
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- 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")
|
2025-08-04 08:53:16 +02:00
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- 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
|
2025-08-04 08:53:16 +02:00
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
local expireTimestamp = parseDate(expireDate)
|
|
|
|
local currentTimestamp = parseDate(currentDate)
|
|
|
|
|
|
|
|
if expireTimestamp and currentTimestamp and expireTimestamp < currentTimestamp then
|
|
|
|
debugPrint("Lizenz abgelaufen: " .. expireDate .. " < " .. currentDate)
|
|
|
|
return false
|
2025-08-04 08:56:54 +02:00
|
|
|
end
|
2025-08-04 09:05:35 +02:00
|
|
|
end
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
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
|
2025-08-04 09:05:35 +02:00
|
|
|
debugPrint("^1Keine Lizenz in DB gefunden für " .. citizenid .. " / " .. licenseType .. "^7")
|
|
|
|
return nil
|
|
|
|
end
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
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))
|
|
|
|
|
2025-08-04 09:05:35 +02:00
|
|
|
-- 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")
|
2025-08-04 08:56:54 +02:00
|
|
|
|
2025-08-04 09:05:35 +02:00
|
|
|
if issuerResult and #issuerResult > 0 then
|
|
|
|
license.issued_by_name = extractPlayerName(issuerResult[1].charinfo)
|
2025-08-04 08:56:54 +02:00
|
|
|
else
|
2025-08-04 08:53:16 +02:00
|
|
|
license.issued_by_name = "System"
|
|
|
|
end
|
2025-08-04 09:05:35 +02:00
|
|
|
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
|
2025-08-04 08:53:16 +02:00
|
|
|
else
|
|
|
|
license.classes = {}
|
|
|
|
end
|
2025-08-04 09:05:35 +02:00
|
|
|
else
|
|
|
|
license.classes = {}
|
2025-08-04 08:53:16 +02:00
|
|
|
end
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- is_active normalisieren (WICHTIG!)
|
2025-08-04 09:05:35 +02:00
|
|
|
if license.is_active == nil then
|
|
|
|
license.is_active = 1
|
2025-08-04 09:15:25 +02:00
|
|
|
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
|
2025-08-04 09:05:35 +02:00
|
|
|
end
|
|
|
|
|
|
|
|
debugPrint("Lizenz erfolgreich geladen: " .. license.license_type .. " (Active: " .. tostring(license.is_active) .. ")")
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- In Cache speichern (nur wenn aktiv)
|
|
|
|
if not skipCache then
|
|
|
|
setCachedLicense(citizenid, licenseType, license)
|
|
|
|
end
|
2025-08-04 09:05:35 +02:00
|
|
|
|
|
|
|
return license
|
2025-08-04 08:53:16 +02:00
|
|
|
end
|
|
|
|
|
2025-08-04 08:56:54 +02:00
|
|
|
-- Alle Lizenzen eines Spielers abrufen (KORRIGIERT)
|
2025-08-04 08:53:16 +02:00
|
|
|
local function getAllPlayerLicenses(citizenid)
|
|
|
|
debugPrint("=== getAllPlayerLicenses START ===")
|
|
|
|
debugPrint("CitizenID: " .. tostring(citizenid))
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- Alle Lizenzen abrufen (ohne is_active Filter)
|
|
|
|
local query = "SELECT * FROM player_licenses WHERE citizenid = ? ORDER BY license_type, created_at DESC"
|
2025-08-04 08:53:16 +02:00
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
local result = safeDBOperation(function()
|
|
|
|
return MySQL.query.await(query, {citizenid})
|
|
|
|
end, "Fehler beim Abrufen aller Lizenzen")
|
2025-08-04 09:05:35 +02:00
|
|
|
|
|
|
|
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")
|
2025-08-04 08:53:16 +02:00
|
|
|
|
2025-08-04 09:05:35 +02:00
|
|
|
if issuerResult and #issuerResult > 0 then
|
|
|
|
license.issued_by_name = extractPlayerName(issuerResult[1].charinfo)
|
2025-08-04 08:56:54 +02:00
|
|
|
else
|
2025-08-04 08:53:16 +02:00
|
|
|
license.issued_by_name = "System"
|
|
|
|
end
|
2025-08-04 09:05:35 +02:00
|
|
|
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
|
2025-08-04 08:53:16 +02:00
|
|
|
else
|
|
|
|
license.classes = {}
|
|
|
|
end
|
2025-08-04 09:05:35 +02:00
|
|
|
else
|
|
|
|
license.classes = {}
|
|
|
|
end
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- is_active normalisieren
|
2025-08-04 09:05:35 +02:00
|
|
|
if license.is_active == nil then
|
|
|
|
license.is_active = 1
|
2025-08-04 08:53:16 +02:00
|
|
|
end
|
2025-08-04 09:05:35 +02:00
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- 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
|
2025-08-04 08:53:16 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
debugPrint("Verarbeitete aktive Lizenzen: " .. #licenses)
|
2025-08-04 09:05:35 +02:00
|
|
|
return licenses
|
2025-08-04 08:53:16 +02:00
|
|
|
end
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- Lizenz in Datenbank speichern (KORRIGIERT - Explizite is_active Setzung)
|
2025-08-04 08:53:16 +02:00
|
|
|
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
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- Cache für diesen Spieler komplett invalidieren
|
|
|
|
invalidateCache(citizenid)
|
|
|
|
|
2025-08-04 09:05:35 +02:00
|
|
|
-- Spieler-Name abrufen
|
2025-08-04 08:53:16 +02:00
|
|
|
local holderQuery = "SELECT charinfo FROM players WHERE citizenid = ?"
|
2025-08-04 08:56:54 +02:00
|
|
|
local holderResult = safeDBOperation(function()
|
|
|
|
return MySQL.query.await(holderQuery, {citizenid})
|
|
|
|
end, "Fehler beim Abrufen des Spieler-Namens")
|
2025-08-04 08:53:16 +02:00
|
|
|
|
2025-08-04 08:56:54 +02:00
|
|
|
local holderName = "Unbekannt"
|
2025-08-04 08:53:16 +02:00
|
|
|
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 {})
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- WICHTIG: Alte Lizenz explizit deaktivieren
|
2025-08-04 08:53:16 +02:00
|
|
|
local deactivateQuery = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?"
|
2025-08-04 09:15:25 +02:00
|
|
|
local deactivateResult = safeDBOperation(function()
|
2025-08-04 08:56:54 +02:00
|
|
|
return MySQL.query.await(deactivateQuery, {citizenid, licenseType})
|
|
|
|
end, "Fehler beim Deaktivieren alter Lizenz")
|
2025-08-04 08:53:16 +02:00
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
debugPrint("Alte Lizenzen deaktiviert: " .. tostring(deactivateResult ~= nil))
|
|
|
|
|
|
|
|
-- Neue Lizenz einfügen (EXPLIZIT is_active = 1)
|
2025-08-04 08:53:16 +02:00
|
|
|
local insertQuery = [[
|
|
|
|
INSERT INTO player_licenses
|
|
|
|
(citizenid, license_type, name, issue_date, expire_date, issued_by, is_active, classes, created_at)
|
2025-08-04 09:15:25 +02:00
|
|
|
VALUES (?, ?, ?, ?, ?, ?, 1, ?, ?)
|
2025-08-04 08:53:16 +02:00
|
|
|
]]
|
|
|
|
|
2025-08-04 09:05:35 +02:00
|
|
|
local createdAt = os.time() -- Unix Timestamp
|
2025-08-04 08:56:54 +02:00
|
|
|
|
2025-08-04 08:53:16 +02:00
|
|
|
local insertData = {
|
|
|
|
citizenid,
|
|
|
|
licenseType,
|
|
|
|
holderName,
|
|
|
|
issueDate,
|
|
|
|
expireDate,
|
|
|
|
issuedBy,
|
2025-08-04 09:15:25 +02:00
|
|
|
-- is_active = 1 ist direkt in der Query
|
2025-08-04 08:53:16 +02:00
|
|
|
classesJson,
|
2025-08-04 08:56:54 +02:00
|
|
|
createdAt
|
2025-08-04 08:53:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
debugPrint("Führe INSERT-Query aus...")
|
|
|
|
debugPrint("Daten: " .. json.encode(insertData))
|
|
|
|
|
2025-08-04 08:56:54 +02:00
|
|
|
local result = safeDBOperation(function()
|
|
|
|
return MySQL.insert.await(insertQuery, insertData)
|
|
|
|
end, "Fehler beim Speichern der Lizenz")
|
2025-08-04 08:53:16 +02:00
|
|
|
|
2025-08-04 08:56:54 +02:00
|
|
|
if result then
|
2025-08-04 08:53:16 +02:00
|
|
|
debugPrint("Lizenz erfolgreich gespeichert. ID: " .. result)
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- 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
|
2025-08-04 08:53:16 +02:00
|
|
|
|
|
|
|
return true
|
|
|
|
else
|
2025-08-04 08:56:54 +02:00
|
|
|
debugPrint("^1Fehler beim Speichern der Lizenz^7")
|
2025-08-04 08:53:16 +02:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- Lizenz entziehen (KORRIGIERT)
|
2025-08-04 08:53:16 +02:00
|
|
|
local function revokeLicenseInDB(citizenid, licenseType)
|
|
|
|
debugPrint("=== revokeLicenseInDB START ===")
|
|
|
|
debugPrint("CitizenID: " .. tostring(citizenid))
|
|
|
|
debugPrint("LicenseType: " .. tostring(licenseType))
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
local query = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ? AND is_active = 1"
|
2025-08-04 08:53:16 +02:00
|
|
|
|
2025-08-04 08:56:54 +02:00
|
|
|
local result = safeDBOperation(function()
|
|
|
|
return MySQL.query.await(query, {citizenid, licenseType})
|
|
|
|
end, "Fehler beim Entziehen der Lizenz")
|
|
|
|
|
|
|
|
if result then
|
2025-08-04 08:53:16 +02:00
|
|
|
debugPrint("Lizenz erfolgreich entzogen")
|
|
|
|
|
|
|
|
-- Cache invalidieren
|
2025-08-04 09:15:25 +02:00
|
|
|
invalidateCache(citizenid, licenseType)
|
2025-08-04 08:53:16 +02:00
|
|
|
|
|
|
|
return true
|
|
|
|
else
|
2025-08-04 08:56:54 +02:00
|
|
|
debugPrint("^1Fehler beim Entziehen der Lizenz^7")
|
2025-08-04 08:53:16 +02:00
|
|
|
return false
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2025-08-04 08:56:54 +02:00
|
|
|
-- 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
|
2025-08-04 08:53:16 +02:00
|
|
|
end
|
|
|
|
|
2025-08-04 08:56:54 +02:00
|
|
|
-- Cache-Cleanup Thread
|
|
|
|
CreateThread(function()
|
|
|
|
while true do
|
|
|
|
Wait(300000) -- 5 Minuten
|
|
|
|
cleanupCache()
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- EVENT HANDLER: Lizenz anfordern
|
2025-08-04 08:53:16 +02:00
|
|
|
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
|
|
|
|
|
2025-08-04 09:05:35 +02:00
|
|
|
-- PRIORITÄT: Erst nach Ausweis suchen, dann andere Lizenzen
|
|
|
|
local licenseTypes = {"id_card", "driver_license", "weapon_license", "pilot_license"}
|
2025-08-04 08:53:16 +02:00
|
|
|
local foundLicense = nil
|
2025-08-04 09:05:35 +02:00
|
|
|
|
|
|
|
for _, licenseType in ipairs(licenseTypes) do
|
|
|
|
if Config.LicenseTypes[licenseType] then
|
2025-08-04 09:15:25 +02:00
|
|
|
local license = getLicenseFromDB(citizenid, licenseType, true) -- Skip Cache für frische Daten
|
2025-08-04 09:05:35 +02:00
|
|
|
|
|
|
|
if license then
|
|
|
|
foundLicense = {
|
|
|
|
license = license,
|
|
|
|
config = Config.LicenseTypes[licenseType]
|
|
|
|
}
|
|
|
|
debugPrint("Lizenz gefunden: " .. licenseType)
|
|
|
|
break
|
|
|
|
end
|
2025-08-04 08:53:16 +02:00
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
if foundLicense then
|
|
|
|
debugPrint("Sende Lizenz an Client: " .. foundLicense.license.license_type)
|
|
|
|
TriggerClientEvent('license-system:client:receiveLicense', src, foundLicense)
|
|
|
|
else
|
2025-08-04 09:05:35 +02:00
|
|
|
debugPrint("Keine Lizenz gefunden")
|
2025-08-04 08:53:16 +02:00
|
|
|
TriggerClientEvent('license-system:client:receiveLicense', src, nil)
|
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
2025-08-04 09:05:35 +02:00
|
|
|
-- EVENT HANDLER: Eigene Lizenz anfordern (KORRIGIERT)
|
2025-08-04 08:53:16 +02:00
|
|
|
RegisterNetEvent('license-system:server:requestMyLicense', function(licenseType)
|
|
|
|
local src = source
|
|
|
|
debugPrint("=== Event: requestMyLicense ===")
|
2025-08-04 09:05:35 +02:00
|
|
|
debugPrint("Source: " .. src .. ", LicenseType: " .. tostring(licenseType))
|
2025-08-04 08:53:16 +02:00
|
|
|
|
|
|
|
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
|
2025-08-04 09:05:35 +02:00
|
|
|
|
|
|
|
-- Falls kein spezifischer Typ angegeben, suche nach Ausweis
|
|
|
|
if not licenseType or licenseType == "" then
|
|
|
|
licenseType = "id_card"
|
|
|
|
debugPrint("Kein Lizenztyp angegeben, verwende: " .. licenseType)
|
|
|
|
end
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
local license = getLicenseFromDB(citizenid, licenseType, true) -- Skip Cache für frische Daten
|
2025-08-04 08:53:16 +02:00
|
|
|
|
2025-08-04 09:05:35 +02:00
|
|
|
if license then
|
2025-08-04 08:53:16 +02:00
|
|
|
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)
|
2025-08-04 08:56:54 +02:00
|
|
|
local licenses = getAllPlayerLicenses(citizenid)
|
2025-08-04 08:53:16 +02:00
|
|
|
|
|
|
|
debugPrint("Sende " .. #licenses .. " Lizenzen für " .. targetName)
|
|
|
|
TriggerClientEvent('license-system:client:receivePlayerLicenses', src, licenses, targetId, targetName)
|
|
|
|
end)
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- EVENT HANDLER: Lizenz ausstellen (KORRIGIERT)
|
2025-08-04 08:53:16 +02:00
|
|
|
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
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- Prüfen ob aktive Lizenz bereits existiert (Skip Cache)
|
|
|
|
local existingLicense = getLicenseFromDB(targetCitizenId, licenseType, true)
|
2025-08-04 08:53:16 +02:00
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
if existingLicense then
|
2025-08-04 08:53:16 +02:00
|
|
|
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
|
2025-08-04 08:56:54 +02:00
|
|
|
local success = saveLicenseToDB(targetCitizenId, licenseType, issuerCitizenId, classes)
|
2025-08-04 08:53:16 +02:00
|
|
|
|
|
|
|
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
|
2025-08-04 08:56:54 +02:00
|
|
|
local success = revokeLicenseInDB(targetCitizenId, licenseType)
|
2025-08-04 08:53:16 +02:00
|
|
|
|
|
|
|
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")
|
2025-08-04 09:48:26 +02:00
|
|
|
TriggerClientEvent('QBCore:Notify', src, 'Fehler beim Entziehen der Lizenz!', 'error')
|
2025-08-04 08:53:16 +02:00
|
|
|
end
|
|
|
|
end)
|
|
|
|
|
2025-08-04 09:48:26 +02:00
|
|
|
-- 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)
|
|
|
|
|
2025-08-04 09:59:09 +02:00
|
|
|
-- EVENT HANDLER: Manuelle Lizenz ausstellen (Erweitert)
|
2025-08-04 09:48:26 +02:00
|
|
|
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
|
|
|
|
|
2025-08-04 09:59:09 +02:00
|
|
|
-- Prüfen ob Lizenztyp gültig ist
|
2025-08-04 09:48:26 +02:00
|
|
|
if not Config.LicenseTypes[licenseData.license_type] then
|
|
|
|
TriggerClientEvent('QBCore:Notify', src, 'Ungültiger Lizenztyp!', 'error')
|
|
|
|
return
|
|
|
|
end
|
|
|
|
|
2025-08-04 09:59:09 +02:00
|
|
|
-- Foto speichern falls vorhanden
|
2025-08-04 09:48:26 +02:00
|
|
|
if licenseData.photo_url then
|
|
|
|
debugPrint("Foto für Lizenz vorhanden")
|
|
|
|
end
|
|
|
|
|
2025-08-04 09:59:09 +02:00
|
|
|
-- Alte Lizenzen deaktivieren
|
2025-08-04 09:48:26 +02:00
|
|
|
local deactivateQuery = "UPDATE player_licenses SET is_active = 0 WHERE citizenid = ? AND license_type = ?"
|
|
|
|
MySQL.query.await(deactivateQuery, {targetCitizenId, licenseData.license_type})
|
|
|
|
|
2025-08-04 09:59:09 +02:00
|
|
|
-- Klassen zu JSON konvertieren
|
|
|
|
local classesJson = json.encode(licenseData.classes or {})
|
|
|
|
|
|
|
|
-- In Datenbank einfügen mit manuellen Daten
|
2025-08-04 09:48:26 +02:00
|
|
|
local query = [[
|
|
|
|
INSERT INTO player_licenses
|
2025-08-04 09:59:09 +02:00
|
|
|
(citizenid, license_type, name, birthday, gender, issue_date, expire_date, issued_by, is_active, classes, photo_url, notes, created_at)
|
|
|
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, 1, ?, ?, ?, ?)
|
2025-08-04 09:48:26 +02:00
|
|
|
]]
|
|
|
|
|
|
|
|
local createdAt = os.time()
|
|
|
|
|
|
|
|
local insertData = {
|
|
|
|
targetCitizenId,
|
|
|
|
licenseData.license_type,
|
|
|
|
licenseData.name,
|
|
|
|
licenseData.birthday,
|
|
|
|
licenseData.gender,
|
|
|
|
licenseData.issue_date,
|
|
|
|
licenseData.expire_date,
|
|
|
|
issuerCitizenId,
|
2025-08-04 09:59:09 +02:00
|
|
|
classesJson,
|
2025-08-04 09:48:26 +02:00
|
|
|
licenseData.photo_url or '',
|
2025-08-04 09:59:09 +02:00
|
|
|
licenseData.notes or '',
|
2025-08-04 09:48:26 +02:00
|
|
|
createdAt
|
|
|
|
}
|
|
|
|
|
|
|
|
local result = MySQL.insert.await(query, insertData)
|
|
|
|
|
|
|
|
if result then
|
2025-08-04 09:59:09 +02:00
|
|
|
-- Cache invalidieren
|
2025-08-04 09:48:26 +02:00
|
|
|
invalidateCache(targetCitizenId)
|
|
|
|
|
|
|
|
local targetName = getPlayerName(targetId)
|
|
|
|
local issuerName = getPlayerName(src)
|
|
|
|
local config = Config.LicenseTypes[licenseData.license_type]
|
|
|
|
|
2025-08-04 09:59:09 +02:00
|
|
|
-- Benachrichtigungen
|
2025-08-04 09:48:26 +02:00
|
|
|
TriggerClientEvent('QBCore:Notify', src, 'Lizenz erfolgreich ausgestellt für ' .. targetName, 'success')
|
|
|
|
TriggerClientEvent('QBCore:Notify', targetId, 'Du hast eine neue Lizenz erhalten: ' .. config.label, 'success')
|
|
|
|
|
2025-08-04 09:59:09 +02:00
|
|
|
-- Events senden
|
2025-08-04 09:48:26 +02:00
|
|
|
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)
|
|
|
|
|
2025-08-04 09:59:09 +02:00
|
|
|
|
2025-08-04 09:48:26 +02:00
|
|
|
-- 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)
|
|
|
|
|
2025-08-04 09:05:35 +02:00
|
|
|
-- EXPORT FUNKTIONEN (KORRIGIERT)
|
2025-08-04 08:53:16 +02:00
|
|
|
exports('hasLicense', function(citizenid, licenseType)
|
|
|
|
if not citizenid or not licenseType then return false end
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
local license = getLicenseFromDB(citizenid, licenseType, true) -- Skip Cache für aktuelle Daten
|
|
|
|
return license ~= nil
|
2025-08-04 08:53:16 +02:00
|
|
|
end)
|
|
|
|
|
|
|
|
exports('issueLicense', function(citizenid, licenseType, issuedBy, classes)
|
|
|
|
if not citizenid or not licenseType then return false end
|
|
|
|
|
|
|
|
issuedBy = issuedBy or 'system'
|
2025-08-04 08:56:54 +02:00
|
|
|
return saveLicenseToDB(citizenid, licenseType, issuedBy, classes)
|
2025-08-04 08:53:16 +02:00
|
|
|
end)
|
|
|
|
|
|
|
|
exports('revokeLicense', function(citizenid, licenseType)
|
|
|
|
if not citizenid or not licenseType then return false end
|
|
|
|
|
2025-08-04 08:56:54 +02:00
|
|
|
return revokeLicenseInDB(citizenid, licenseType)
|
2025-08-04 08:53:16 +02:00
|
|
|
end)
|
|
|
|
|
|
|
|
exports('getPlayerLicenses', function(citizenid)
|
|
|
|
if not citizenid then return {} end
|
|
|
|
|
2025-08-04 08:56:54 +02:00
|
|
|
return getAllPlayerLicenses(citizenid)
|
2025-08-04 08:53:16 +02:00
|
|
|
end)
|
|
|
|
|
|
|
|
exports('getPlayerLicense', function(citizenid, licenseType)
|
|
|
|
if not citizenid or not licenseType then return nil end
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
return getLicenseFromDB(citizenid, licenseType, true) -- Skip Cache
|
2025-08-04 08:53:16 +02:00
|
|
|
end)
|
|
|
|
|
2025-08-04 09:05:35 +02:00
|
|
|
-- 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)
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- DEBUG COMMAND: Lizenz prüfen (ERWEITERT)
|
2025-08-04 09:05:35 +02:00
|
|
|
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]
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- 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
|
2025-08-04 09:05:35 +02:00
|
|
|
else
|
|
|
|
print("Keine Lizenz gefunden für: " .. citizenid .. " / " .. licenseType)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
end, true)
|
|
|
|
|
2025-08-04 09:15:25 +02:00
|
|
|
-- 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)
|
|
|
|
|
2025-08-04 08:53:16 +02:00
|
|
|
-- INITIALISIERUNG
|
|
|
|
CreateThread(function()
|
2025-08-04 09:15:25 +02:00
|
|
|
debugPrint("License-System Server gestartet (Status-Fix)")
|
2025-08-04 08:53:16 +02:00
|
|
|
|
|
|
|
-- 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)
|
2025-08-04 09:05:35 +02:00
|
|
|
print("Config License Types: " .. (Config.LicenseTypes and table.count(Config.LicenseTypes) or 0))
|
2025-08-04 08:53:16 +02:00
|
|
|
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)
|
|
|
|
|
2025-08-04 09:05:35 +02:00
|
|
|
-- Hilfsfunktion für table.count
|
|
|
|
function table.count(t)
|
|
|
|
local count = 0
|
|
|
|
for _ in pairs(t) do
|
|
|
|
count = count + 1
|
|
|
|
end
|
|
|
|
return count
|
|
|
|
end
|
|
|
|
|
2025-08-04 09:48:26 +02:00
|
|
|
debugPrint("License-System Server vollständig geladen (Status-Fix)")
|
2025-08-04 09:24:53 +02:00
|
|
|
|
|
|
|
|