From 6939566cf4f21c65f0c65d089350a50c8387c38b Mon Sep 17 00:00:00 2001 From: Andyyy7666 <86536434+Andyyy7666@users.noreply.github.com> Date: Mon, 16 Mar 2026 05:42:38 +0100 Subject: [PATCH] feat: user data for db & character fetching --- database/characters.sql | 6 ++- database/users.sql | 12 ++++++ server/main.lua | 88 +++++++++++++++++++++++++++++++++++++---- server/player.lua | 47 ++++++++++++++++++---- 4 files changed, 136 insertions(+), 17 deletions(-) create mode 100644 database/users.sql diff --git a/database/characters.sql b/database/characters.sql index ef47b75..5b59b56 100644 --- a/database/characters.sql +++ b/database/characters.sql @@ -1,5 +1,6 @@ CREATE TABLE IF NOT EXISTS `nd_characters` ( `charid` INT(10) NOT NULL AUTO_INCREMENT, + `user_id` INT(11) DEFAULT NULL, `identifier` VARCHAR(200) NOT NULL DEFAULT '0', `name` VARCHAR(50) DEFAULT NULL, `firstname` VARCHAR(50) DEFAULT NULL, @@ -9,8 +10,11 @@ CREATE TABLE IF NOT EXISTS `nd_characters` ( `cash` INT(10) DEFAULT '0', `bank` INT(10) DEFAULT '0', `phonenumber` VARCHAR(20) DEFAULT NULL, + `deleted_at` DATETIME NULL DEFAULT NULL, `groups` LONGTEXT DEFAULT ('[]'), `metadata` LONGTEXT DEFAULT ('[]'), `inventory` LONGTEXT DEFAULT ('[]'), - PRIMARY KEY (`charid`) USING BTREE + PRIMARY KEY (`charid`) USING BTREE, + INDEX `idx_nd_characters_user_id` (`user_id`), + CONSTRAINT `fk_nd_characters_user_id` FOREIGN KEY (`user_id`) REFERENCES `nd_users` (`user_id`) ON UPDATE CASCADE ON DELETE SET NULL ); diff --git a/database/users.sql b/database/users.sql new file mode 100644 index 0000000..e835f44 --- /dev/null +++ b/database/users.sql @@ -0,0 +1,12 @@ +CREATE TABLE IF NOT EXISTS `nd_users` ( + `user_id` INT(11) NOT NULL AUTO_INCREMENT, + `id_steam` LONGTEXT NULL DEFAULT NULL, + `id_discord` LONGTEXT NULL DEFAULT NULL, + `id_xbl` LONGTEXT NULL DEFAULT NULL, + `id_live` LONGTEXT NULL DEFAULT NULL, + `id_license` LONGTEXT NULL DEFAULT NULL, + `id_license2` LONGTEXT NULL DEFAULT NULL, + `id_fivem` LONGTEXT NULL DEFAULT NULL, + `id_ip` LONGTEXT NULL DEFAULT NULL, + PRIMARY KEY (`user_id`) USING BTREE +) COLLATE='utf8mb4_unicode_ci'; diff --git a/server/main.lua b/server/main.lua index 195652b..4f3449c 100644 --- a/server/main.lua +++ b/server/main.lua @@ -17,6 +17,7 @@ Config = { discordActionText2 = GetConvar("core:discordActionText2", "STORE"), discordActionLink2 = GetConvar("core:discordActionLink2", "https://andyyy.tebex.io/category/fivem-scripts"), characterIdentifier = GetConvar("core:characterIdentifier", "license"), + selectIdentifiers = json.decode(GetConvar("core:selectIdentifiers", '["discord", "license", "license2", "fivem"]')), discordGuildId = GetConvar("core:discordGuildId", "false"), discordBotToken = GetConvar("core:discordBotToken", "false"), randomUnlockedVehicleChance = GetConvarInt("core:randomUnlockedVehicleChance", 30), @@ -66,6 +67,71 @@ AddEventHandler("playerJoining", function(oldId) lib.addPrincipal(("player.%s"):format(src), "group.admin") end + local identifiers = PlayersInfo[src] and PlayersInfo[src].identifiers or getIdentifierList(src) + + local whereParts = {} + local params = {} + + for identifierType, identifier in pairs(identifiers) do + local isSelected = false + for i=1, #Config.selectIdentifiers do + local selectedType = Config.selectIdentifiers[i] + if identifierType == selectedType then + isSelected = true + break + end + end + + if isSelected then + local columnName = "id_" .. identifierType + local cleanId = identifier:gsub("^[^:]*:", "") + table.insert(whereParts, columnName .. " = ?") + table.insert(params, cleanId) + end + end + + local user = nil + if #whereParts > 0 then + local query = "SELECT user_id FROM nd_users WHERE " .. table.concat(whereParts, " OR ") .. " LIMIT 1" + user = MySQL.query.await(query, params) + end + + if user and user[1] then + PlayersInfo[src].userId = user[1].user_id + + local updateParts = {} + local updateParams = {} + + for identifierType, identifier in pairs(identifiers) do + local columnName = "id_" .. identifierType + local cleanId = identifier:gsub("^[^:]*:", "") + table.insert(updateParts, columnName .. " = ?") + table.insert(updateParams, cleanId) + end + + if #updateParts > 0 then + table.insert(updateParams, user[1].user_id) + local updateQuery = "UPDATE nd_users SET " .. table.concat(updateParts, ", ") .. " WHERE user_id = ?" + MySQL.update.await(updateQuery, updateParams) + end + else + local columns = {} + local values = {} + local insertParams = {} + + for identifierType, identifier in pairs(identifiers) do + local columnName = "id_" .. identifierType + local cleanId = identifier:gsub("^[^:]*:", "") + table.insert(columns, columnName) + table.insert(values, "?") + table.insert(insertParams, cleanId) + end + + local insertQuery = "INSERT INTO nd_users (" .. table.concat(columns, ", ") .. ") VALUES (" .. table.concat(values, ", ") .. ")" + local user_id = MySQL.insert.await(insertQuery, insertParams) + PlayersInfo[src].userId = user_id + end + if Config.multiCharacter then return end Wait(3000) @@ -164,14 +230,6 @@ AddEventHandler("onResourceStop", function(name) end end) -MySQL.ready(function() - Wait(100) - NDCore.loadSQL({ - "database/characters.sql", - "database/vehicles.sql" - }, resourceName) -end) - RegisterNetEvent("ND:playerEliminated", function(info) local src = source local player = NDCore.getPlayer(src) @@ -188,3 +246,17 @@ RegisterNetEvent("ND:updateClothing", function(clothing) if not player or not clothing or type(clothing) ~= "table" then return end player.setMetadata("clothing", clothing) end) + +MySQL.ready(function() + Wait(100) + NDCore.loadSQL({ + "database/users.sql", + "database/characters.sql", + "database/vehicles.sql", + }, resourceName) +end) + +-- Hourly cron, purge soft-deleted characters older than 30 days. +lib.cron.new("0 * * * *", function() + MySQL.update.await("DELETE FROM nd_characters WHERE deleted_at IS NOT NULL AND deleted_at < DATE_SUB(NOW(), INTERVAL 30 DAY)") +end) diff --git a/server/player.lua b/server/player.lua index 3560591..97f2795 100644 --- a/server/player.lua +++ b/server/player.lua @@ -17,6 +17,7 @@ local function createCharacterTable(info) id = info.id, source = info.source, identifier = info.identifier, + user_id = info.user_id, identifiers = playerInfo.identifiers or {}, discord = playerInfo.discord or {}, name = info.name, @@ -135,9 +136,9 @@ local function createCharacterTable(info) return self.metadata end - -- Completely delete character + -- Mark character deleted function self.delete() - local result = MySQL.query.await("DELETE FROM nd_characters WHERE charid = ?", {self.id}) + local result = MySQL.update.await("UPDATE nd_characters SET deleted_at = NOW() WHERE charid = ?", {self.id}) if result and NDCore.players[self.source] then NDCore.players[self.source] = nil end @@ -201,7 +202,8 @@ local function createCharacterTable(info) return affectedRows > 0 end - local affectedRows = MySQL.update.await("UPDATE nd_characters SET name = ?, firstname = ?, lastname = ?, dob = ?, gender = ?, cash = ?, bank = ?, phonenumber = ?, `groups` = ?, metadata = ? WHERE charid = ?", { + local affectedRows = MySQL.update.await("UPDATE nd_characters SET user_id = ?, name = ?, firstname = ?, lastname = ?, dob = ?, gender = ?, cash = ?, bank = ?, phonenumber = ?, `groups` = ?, metadata = ? WHERE charid = ?", { + self.user_id, self.name, self.firstname, self.lastname, @@ -487,10 +489,13 @@ end function NDCore.newCharacter(src, info) local identifier = NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier) if not identifier then return end + + local userId = PlayersInfo[src] and PlayersInfo[src].userId local charInfo = { source = src, identifier = identifier, + user_id = userId, name = GetPlayerName(src) or "", firstname = info.firstname or "", lastname = info.lastname or "", @@ -516,8 +521,9 @@ function NDCore.newCharacter(src, info) return end - charInfo.id = MySQL.insert.await("INSERT INTO nd_characters (identifier, name, firstname, lastname, dob, gender, cash, bank, phonenumber, `groups`, metadata, inventory) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", { + charInfo.id = MySQL.insert.await("INSERT INTO nd_characters (identifier, user_id, name, firstname, lastname, dob, gender, cash, bank, phonenumber, `groups`, metadata, inventory) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", { identifier, + userId, charInfo.name, charInfo.firstname, charInfo.lastname, @@ -539,17 +545,30 @@ end function NDCore.fetchCharacter(id, src) local result if src then - result = MySQL.query.await("SELECT * FROM nd_characters WHERE charid = ? and identifier = ?", {id, NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier)}) + local identifier = NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier) + local userId = PlayersInfo[src] and PlayersInfo[src].userId + result = MySQL.query.await("SELECT * FROM nd_characters WHERE charid = ? AND (identifier = ? OR user_id = ?)", {id, identifier, userId}) else result = MySQL.query.await("SELECT * FROM nd_characters WHERE charid = ?", {id}) end - if not result then return end - local info = result[1] + local info = result?[1] + if not info then return end + + -- Check if character has user_id, if not assign it + if not info.user_id and src then + local userId = PlayersInfo[src] and PlayersInfo[src].userId + if userId then + MySQL.update.await("UPDATE nd_characters SET user_id = ? WHERE charid = ?", {userId, info.charid}) + info.user_id = userId + end + end + return createCharacterTable({ source = src, id = info.charid, identifier = info.identifier, + user_id = info.user_id, name = info.name, firstname = info.firstname, lastname = info.lastname, @@ -567,15 +586,27 @@ end ---@param src number ---@return table function NDCore.fetchAllCharacters(src) + local userId = PlayersInfo[src] and PlayersInfo[src].userId + local identifier = NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier) local characters = {} - local result = MySQL.query.await("SELECT * FROM nd_characters WHERE identifier = ?", {NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier)}) + + -- Query by either identifier OR user_id, excluding deleted characters + local result = MySQL.query.await("SELECT * FROM nd_characters WHERE (identifier = ? OR user_id = ?) AND deleted_at IS NULL", {identifier, userId}) for i=1, #result do local info = result[i] + + -- Check if character has user_id, if not assign it + if not info.user_id and userId then + MySQL.update.await("UPDATE nd_characters SET user_id = ? WHERE charid = ?", {userId, info.charid}) + info.user_id = userId + end + characters[info.charid] = createCharacterTable({ source = src, id = info.charid, identifier = info.identifier, + user_id = info.user_id, name = info.name, firstname = info.firstname, lastname = info.lastname,