feat: user data for db & character fetching

This commit is contained in:
Andyyy7666
2026-03-16 05:42:38 +01:00
parent 075fb087f2
commit 6939566cf4
4 changed files with 136 additions and 17 deletions

View File

@@ -1,5 +1,6 @@
CREATE TABLE IF NOT EXISTS `nd_characters` ( CREATE TABLE IF NOT EXISTS `nd_characters` (
`charid` INT(10) NOT NULL AUTO_INCREMENT, `charid` INT(10) NOT NULL AUTO_INCREMENT,
`user_id` INT(11) DEFAULT NULL,
`identifier` VARCHAR(200) NOT NULL DEFAULT '0', `identifier` VARCHAR(200) NOT NULL DEFAULT '0',
`name` VARCHAR(50) DEFAULT NULL, `name` VARCHAR(50) DEFAULT NULL,
`firstname` 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', `cash` INT(10) DEFAULT '0',
`bank` INT(10) DEFAULT '0', `bank` INT(10) DEFAULT '0',
`phonenumber` VARCHAR(20) DEFAULT NULL, `phonenumber` VARCHAR(20) DEFAULT NULL,
`deleted_at` DATETIME NULL DEFAULT NULL,
`groups` LONGTEXT DEFAULT ('[]'), `groups` LONGTEXT DEFAULT ('[]'),
`metadata` LONGTEXT DEFAULT ('[]'), `metadata` LONGTEXT DEFAULT ('[]'),
`inventory` 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
); );

12
database/users.sql Normal file
View File

@@ -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';

View File

@@ -17,6 +17,7 @@ Config = {
discordActionText2 = GetConvar("core:discordActionText2", "STORE"), discordActionText2 = GetConvar("core:discordActionText2", "STORE"),
discordActionLink2 = GetConvar("core:discordActionLink2", "https://andyyy.tebex.io/category/fivem-scripts"), discordActionLink2 = GetConvar("core:discordActionLink2", "https://andyyy.tebex.io/category/fivem-scripts"),
characterIdentifier = GetConvar("core:characterIdentifier", "license"), characterIdentifier = GetConvar("core:characterIdentifier", "license"),
selectIdentifiers = json.decode(GetConvar("core:selectIdentifiers", '["discord", "license", "license2", "fivem"]')),
discordGuildId = GetConvar("core:discordGuildId", "false"), discordGuildId = GetConvar("core:discordGuildId", "false"),
discordBotToken = GetConvar("core:discordBotToken", "false"), discordBotToken = GetConvar("core:discordBotToken", "false"),
randomUnlockedVehicleChance = GetConvarInt("core:randomUnlockedVehicleChance", 30), randomUnlockedVehicleChance = GetConvarInt("core:randomUnlockedVehicleChance", 30),
@@ -66,6 +67,71 @@ AddEventHandler("playerJoining", function(oldId)
lib.addPrincipal(("player.%s"):format(src), "group.admin") lib.addPrincipal(("player.%s"):format(src), "group.admin")
end 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 if Config.multiCharacter then return end
Wait(3000) Wait(3000)
@@ -164,14 +230,6 @@ AddEventHandler("onResourceStop", function(name)
end end
end) end)
MySQL.ready(function()
Wait(100)
NDCore.loadSQL({
"database/characters.sql",
"database/vehicles.sql"
}, resourceName)
end)
RegisterNetEvent("ND:playerEliminated", function(info) RegisterNetEvent("ND:playerEliminated", function(info)
local src = source local src = source
local player = NDCore.getPlayer(src) 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 if not player or not clothing or type(clothing) ~= "table" then return end
player.setMetadata("clothing", clothing) player.setMetadata("clothing", clothing)
end) 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)

View File

@@ -17,6 +17,7 @@ local function createCharacterTable(info)
id = info.id, id = info.id,
source = info.source, source = info.source,
identifier = info.identifier, identifier = info.identifier,
user_id = info.user_id,
identifiers = playerInfo.identifiers or {}, identifiers = playerInfo.identifiers or {},
discord = playerInfo.discord or {}, discord = playerInfo.discord or {},
name = info.name, name = info.name,
@@ -135,9 +136,9 @@ local function createCharacterTable(info)
return self.metadata return self.metadata
end end
-- Completely delete character -- Mark character deleted
function self.delete() 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 if result and NDCore.players[self.source] then
NDCore.players[self.source] = nil NDCore.players[self.source] = nil
end end
@@ -201,7 +202,8 @@ local function createCharacterTable(info)
return affectedRows > 0 return affectedRows > 0
end 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.name,
self.firstname, self.firstname,
self.lastname, self.lastname,
@@ -487,10 +489,13 @@ end
function NDCore.newCharacter(src, info) function NDCore.newCharacter(src, info)
local identifier = NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier) local identifier = NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier)
if not identifier then return end if not identifier then return end
local userId = PlayersInfo[src] and PlayersInfo[src].userId
local charInfo = { local charInfo = {
source = src, source = src,
identifier = identifier, identifier = identifier,
user_id = userId,
name = GetPlayerName(src) or "", name = GetPlayerName(src) or "",
firstname = info.firstname or "", firstname = info.firstname or "",
lastname = info.lastname or "", lastname = info.lastname or "",
@@ -516,8 +521,9 @@ function NDCore.newCharacter(src, info)
return return
end 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, identifier,
userId,
charInfo.name, charInfo.name,
charInfo.firstname, charInfo.firstname,
charInfo.lastname, charInfo.lastname,
@@ -539,17 +545,30 @@ end
function NDCore.fetchCharacter(id, src) function NDCore.fetchCharacter(id, src)
local result local result
if src then 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 else
result = MySQL.query.await("SELECT * FROM nd_characters WHERE charid = ?", {id}) result = MySQL.query.await("SELECT * FROM nd_characters WHERE charid = ?", {id})
end 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({ return createCharacterTable({
source = src, source = src,
id = info.charid, id = info.charid,
identifier = info.identifier, identifier = info.identifier,
user_id = info.user_id,
name = info.name, name = info.name,
firstname = info.firstname, firstname = info.firstname,
lastname = info.lastname, lastname = info.lastname,
@@ -567,15 +586,27 @@ end
---@param src number ---@param src number
---@return table ---@return table
function NDCore.fetchAllCharacters(src) function NDCore.fetchAllCharacters(src)
local userId = PlayersInfo[src] and PlayersInfo[src].userId
local identifier = NDCore.getPlayerIdentifierByType(src, Config.characterIdentifier)
local characters = {} 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 for i=1, #result do
local info = result[i] 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({ characters[info.charid] = createCharacterTable({
source = src, source = src,
id = info.charid, id = info.charid,
identifier = info.identifier, identifier = info.identifier,
user_id = info.user_id,
name = info.name, name = info.name,
firstname = info.firstname, firstname = info.firstname,
lastname = info.lastname, lastname = info.lastname,