User Database & Utility API

Overview

The User Database & Utility API provides comprehensive functions for user management, database operations, and cross-resource communication. This system handles user creation, lookups, ban management, and unique identifier generation, utilizing the Tunnel/Proxy pattern for seamless client-server communication.

Tunnel & Proxy System

The API utilizes a modified vRP-style Tunnel and Proxy system for inter-resource communication:

  • Tunnel: Provides two-way communication between server and clients with async support

  • Proxy: Enables function calls between different resources on the same side

Basic Usage

To enable the library inside of your resource just add @frp_lib/library/linker.lua as a shared_script in your fxmanifest.lua file.

shared_scripts {
    '@frp_lib/library/linker.lua',
}

Or if the library is the only shared script you use you can do:

shared_script '@frp_lib/library/linker.lua'
-- Import the API using Proxy
local API = Proxy.getInterface("API")

-- Or using Tunnel for client-server communication
local cAPI = Tunnel.getInterface("API")

User Creation & Management

API.CreateUser

Creates a new user account with credentials in the database.

Parameters

  • playerId (number): The player's server source ID

  • mappedIdentifiers (table): Mapped player identifiers containing all platform identifiers

Returns

  • userId (number): The newly created user ID

  • nil: If creation failed

Example

local identifiers = GetPlayerIdentifiers(source)
local mappedIdentifiers = MapIdentifiers(identifiers)

local userId = API.CreateUser(source, mappedIdentifiers)
if userId then
    print("User created with ID:", userId)
else
    print("Failed to create user")
end

What it does

  • Creates user record with player name

  • Stores all platform credentials (Steam, Discord, License, etc.)

  • Integrates with external systems (prime_api)

  • Logs user creation for auditing

Database Structure

-- user table
INSERT INTO user (name) VALUES(?)

-- user_credentials table  
INSERT INTO user_credentials (
    userId, fivem, steam, ip, license, license2, 
    xbl, live, discord
) VALUES(?, ?, ?, ?, ?, ?, ?, ?, ?)

User Lookup Functions

API.GetUserIdentifiersByIdentifier

Retrieves all identifiers for a user by a specific identifier.

Parameters

  • identifier (string): The identifier value to search for

  • identifierKey (string): The type of identifier ("steam", "discord", "license", etc.)

Returns

  • credentials (table): Complete user credentials record

  • nil: If no user found

Example

local userCredentials = API.GetUserIdentifiersByIdentifier("steam:110000123456789", "steam")
if userCredentials then
    print("User ID:", userCredentials.userId)
    print("Discord:", userCredentials.discord)
    print("License:", userCredentials.license)
end

API.GetUserIdByIdentifier

Gets a user ID by a specific identifier.

Parameters

  • identifier (string): The identifier value to search for

  • identifierKey (string): The type of identifier

Returns

  • userId (number): The user's database ID

  • nil: If no user found

Example

local userId = API.GetUserIdByIdentifier("discord:123456789012345678", "discord")
if userId then
    print("Found user ID:", userId)
else
    print("User not found")
end

API.GetUserIdByIdentifiers

Gets a user ID using the primary identifier from a list of identifiers.

Parameters

  • identifiers (table): Array of player identifiers

  • name (string): Player name (currently unused)

Returns

  • userId (number): The user's database ID

  • nil: If no user found

Example

local identifiers = GetPlayerIdentifiers(source)
local userId = API.GetUserIdByIdentifiers(identifiers, GetPlayerName(source))

if userId then
    print("Existing user found:", userId)
else
    print("New user - needs to be created")
end

Character & User Data Retrieval

API.GetCharacterFromCitizenIdOffline

Retrieves character data by citizen ID from the database (offline lookup).

Parameters

  • citizenId (string): The character's citizen ID

Returns

  • character (table): Complete character database record

  • nil: If character not found

Example

local character = API.GetCharacterFromCitizenIdOffline("A1234")
if character then
    print("Character:", character.firstName, character.lastName)
    print("User ID:", character.userId)
    print("Birth Date:", character.birthDate)
end

API.GetCharacterFromCharId

Retrieves character data by character ID from the database.

Parameters

  • charId (number): The character's database ID

Returns

  • character (table): Complete character database record

  • nil: If character not found

Example

local character = API.GetCharacterFromCharId(456)
if character then
    print("Character found:", character.citizenId)
end

API.GetUserFromUserIdOffline

Retrieves user data by user ID from the database (offline lookup).

Parameters

  • userId (number): The user's database ID

Returns

  • user (table): Complete user database record

  • nil: If user not found

Example

local userData = API.GetUserFromUserIdOffline(123)
if userData then
    print("User name:", userData.name)
    print("Character slots:", userData.numCharSlots)
    print("Created at:", userData.createdAt)
end

API.GetUserCharactersOffline

Retrieves all characters for a user (offline lookup).

Parameters

  • userId (number): The user's database ID

Returns

  • characters (table): Array of character records

  • {}: Empty array if no characters found

Example

local characters = API.GetUserCharactersOffline(123)
print("User has", #characters, "characters:")

for i, char in ipairs(characters) do
    print("Character", i, ":", char.firstName, char.lastName, "(" .. char.citizenId .. ")")
end

Active User Management

API.GetUserFromUserId

Gets an active user object by user ID.

Parameters

  • userId (number): The user's database ID

Returns

  • User (table): The active user object

  • nil: If user is not online

Example

local user = API.GetUserFromUserId(123)
if user then
    print("User is online:", user:GetName())
    user:Notify("info", "Admin message!")
else
    print("User is not online")
end

API.GetUserIdFromServerId

Gets a user ID from a server source ID.

Parameters

  • source (number): The player's server source ID

Returns

  • userId (number): The user's database ID

  • nil: If user not found

Example

local userId = API.GetUserIdFromServerId(source)
if userId then
    print("Player source", source, "is user ID", userId)
end

API.GetUserFromSource

Gets an active user object by server source ID.

Parameters

  • source (number): The player's server source ID

Returns

  • User (table): The active user object

  • nil: If user not found

Example

-- Common usage in commands
RegisterCommand("balance", function(source, args)
    local user = API.GetUserFromSource(source)
    if not user then
        return
    end
    
    local character = user:GetCharacter()
    if character then
        local balance = character:GetBankBalance()
        user:Notify("info", "Balance: $" .. balance)
    end
end)

API.GetUserFromCharId

Gets an active user object by character ID.

Parameters

  • charId (number): The character's database ID

Returns

  • User (table): The active user object

  • nil: If user not online or character not found

Example

local user = API.GetUserFromCharId(789)
if user then
    print("Character owner is online:", user:GetName())
end

API.GetUserFromCitizenId

Gets an active user object by citizen ID.

Parameters

  • citizenId (string): The character's citizen ID

Returns

  • User (table): The active user object

  • nil: If user not online or character not found

Example

local user = API.GetUserFromCitizenId("A1234")
if user then
    local character = user:GetCharacter()
    print("Character is online:", character:GetFullName())
end

API.GetUserFromDiscordId

Gets an active user object by Discord ID.

Parameters

  • discordId (string): The user's Discord ID (without "discord:" prefix)

Returns

  • User (table): The active user object

  • nil: If user not online

Example

local discordId = "123456789012345678"
local user = API.GetUserFromDiscordId(discordId)

if user then
    print("Discord user is online as:", user:GetName())
    user:Notify("info", "You have a Discord message!")
end

API.GetUserIdFromCharId

Gets a user ID from a character ID (with fallback to database).

Parameters

  • charId (number): The character's database ID

Returns

  • userId (number): The user's database ID

  • nil: If character not found

Example

local userId = API.GetUserIdFromCharId(456)
if userId then
    print("Character 456 belongs to user:", userId)
end

API.GetUsers

Gets all currently active user objects.

Returns

  • users (table): Table of all active users indexed by user ID

Example

local allUsers = API.GetUsers()
local userCount = 0

for userId, user in pairs(allUsers) do
    userCount = userCount + 1
    print("User", userId, ":", user:GetName())
end

print("Total online users:", userCount)

Ban Management System

API.SetBanned

Bans a user and kicks them from the server.

Parameters

  • source (number): The player's server source ID (for kicking)

  • userId (number): The user's database ID to ban

  • reason (string): Reason for the ban

Returns

  • None

Example

RegisterCommand("ban", function(source, args)
    if not CheckAdminPermission(source) then return end
    
    local targetId = tonumber(args[1])
    local reason = table.concat(args, " ", 2) or "No reason provided"
    
    local targetUser = API.GetUserFromSource(targetId)
    if targetUser then
        API.SetBanned(targetId, targetUser:GetId(), reason)
        print("Banned user:", targetUser:GetName(), "Reason:", reason)
    end
end)

API.UnBan

Removes a ban from a user account.

Parameters

  • source (number): Command source (not used currently)

  • userId (number): The user's database ID to unban

Returns

  • None

Example

RegisterCommand("unban", function(source, args)
    if not CheckAdminPermission(source) then return end
    
    local userId = tonumber(args[1])
    if userId then
        API.UnBan(source, userId)
        print("Unbanned user ID:", userId)
    end
end)

API.IsBanned

Checks if a user is currently banned.

Parameters

  • userId (number): The user's database ID to check

Returns

  • banned (boolean): True if user is banned, false otherwise

Example

-- Check during connection
AddEventHandler('playerConnecting', function(name, setKickReason)
    local source = source
    local identifiers = GetPlayerIdentifiers(source)
    local userId = API.GetUserIdByIdentifiers(identifiers, name)
    
    if userId and API.IsBanned(userId) then
        setKickReason("You are banned from this server")
        CancelEvent()
    end
end)

Cache Management

API.ClearUserFromCache

Removes a user from all caches and tracking tables.

Parameters

  • source (number): The player's server source ID

  • userId (number): The user's database ID

Returns

  • None

Example

-- Automatic cleanup when player disconnects
AddEventHandler('playerDropped', function(reason)
    local source = source
    local user = API.GetUserFromSource(source)
    
    if user then
        API.ClearUserFromCache(source, user:GetId())
        print("Cleaned up user cache for:", user:GetName())
    end
end)

Console Command

clearUser <userId>

Clears a specific user from cache (console only).


Unique ID Generation

API.CreateCitizenId

Generates a unique citizen ID for new characters.

Returns

  • citizenId (string): A unique citizen ID (format: A1234)

Example

local citizenId = API.CreateCitizenId()
print("Generated citizen ID:", citizenId) -- Example: "A1234"

Algorithm

  • Combines random string (1 char) + random number (4 digits)

  • Ensures uniqueness by checking against existing characters

  • Uppercase format for consistency


API.GenerateCharFingerPrint

Generates a unique fingerprint for new characters.

Returns

  • fingerprint (string): A unique fingerprint string

Example

local fingerprint = API.GenerateCharFingerPrint()
print("Generated fingerprint:", fingerprint) -- Example: "AB123C45DEF6789"

Algorithm

  • Complex pattern: String(2) + Number(3) + String(1) + Number(2) + String(3) + Number(4)

  • Stored in character metadata as JSON

  • Ensures uniqueness against existing character fingerprints


Usage Examples

Complete User Connection Flow

AddEventHandler('playerConnecting', function(name, setKickReason, deferrals)
    local source = source
    local identifiers = GetPlayerIdentifiers(source)
    
    deferrals.defer()
    deferrals.update("Checking user data...")
    
    -- Check if user exists
    local userId = API.GetUserIdByIdentifiers(identifiers, name)
    
    if not userId then
        -- Create new user
        deferrals.update("Creating new user...")
        local mappedIdentifiers = MapIdentifiers(identifiers)
        userId = API.CreateUser(source, mappedIdentifiers)
        
        if not userId then
            deferrals.done("Failed to create user account")
            return
        end
    else
        -- Check if banned
        if API.IsBanned(userId) then
            deferrals.done("You are banned from this server")
            return
        end
    end
    
    deferrals.done()
end)

Admin User Management Commands

RegisterCommand("userinfo", function(source, args)
    if not CheckAdminPermission(source) then return end
    
    local targetId = tonumber(args[1])
    if not targetId then return end
    
    -- Try to get online user first
    local user = API.GetUserFromSource(targetId)
    if user then
        print("=== ONLINE USER INFO ===")
        print("Name:", user:GetName())
        print("User ID:", user:GetId())
        print("Character ID:", user:GetCharacterId())
        print("IP:", user:GetIpAddress())
        return
    end
    
    -- Get offline data
    local userId = API.GetUserIdFromServerId(targetId)
    if userId then
        local userData = API.GetUserFromUserIdOffline(userId)
        if userData then
            print("=== OFFLINE USER INFO ===")
            print("Name:", userData.name)
            print("User ID:", userData.id)
            print("Created:", userData.createdAt)
        end
    end
end)

RegisterCommand("finduser", function(source, args)
    if not CheckAdminPermission(source) then return end
    
    local searchType = args[1] -- "steam", "discord", "citizen"
    local searchValue = args[2]
    
    if not searchType or not searchValue then
        print("Usage: finduser <type> <value>")
        print("Types: steam, discord, citizen, char")
        return
    end
    
    local user = nil
    
    if searchType == "steam" then
        user = API.GetUserFromSource(API.GetUserIdByIdentifier(searchValue, "steam"))
    elseif searchType == "discord" then
        local discordId = searchValue:gsub("discord:", "")
        user = API.GetUserFromDiscordId(discordId)
    elseif searchType == "citizen" then
        user = API.GetUserFromCitizenId(searchValue)
    elseif searchType == "char" then
        local charId = tonumber(searchValue)
        user = API.GetUserFromCharId(charId)
    end
    
    if user then
        print("Found user:", user:GetName(), "ID:", user:GetId())
    else
        print("User not found or not online")
    end
end)

Character Management Integration

-- When creating a character
function CreateCharacter(source, characterData)
    local user = API.GetUserFromSource(source)
    if not user then return false end
    
    -- Generate unique IDs
    local citizenId = API.CreateCitizenId()
    local fingerprint = API.GenerateCharFingerPrint()
    
    -- Add to metadata
    characterData.metadata = characterData.metadata or {}
    characterData.metadata.fingerprint = fingerprint
    
    -- Create character with unique citizen ID
    local charId = user:CreateCharacter(
        characterData.firstName,
        characterData.lastName,
        characterData.birthDate,
        characterData.appearance,
        characterData.clothing,
        characterData.metadata
    )
    
    return charId
end

-- Cross-character messaging
RegisterCommand("message", function(source, args)
    local user = API.GetUserFromSource(source)
    if not user then return end
    
    local targetCitizenId = args[1]
    local message = table.concat(args, " ", 2)
    
    local targetUser = API.GetUserFromCitizenId(targetCitizenId)
    if targetUser then
        targetUser:Notify("info", "Message from " .. user:GetCharacter():GetFullName() .. ": " .. message)
        user:Notify("success", "Message sent to " .. targetCitizenId)
    else
        user:Notify("error", "Character not found or not online")
    end
end)

Ban Management System

RegisterCommand("ban", function(source, args)
    if not CheckAdminPermission(source) then return end
    
    local targetId = tonumber(args[1])
    local reason = table.concat(args, " ", 2) or "Violation of server rules"
    
    local targetUser = API.GetUserFromSource(targetId)
    if targetUser then
        local userId = targetUser:GetId()
        local userName = targetUser:GetName()
        
        API.SetBanned(targetId, userId, reason)
        
        -- Log the ban
        local adminUser = API.GetUserFromSource(source)
        local adminName = adminUser and adminUser:GetName() or "Console"
        
        print(("BAN: %s (ID: %s) banned %s (ID: %s) - Reason: %s"):format(
            adminName, source, userName, userId, reason
        ))
        
        -- Notify other admins
        local allUsers = API.GetUsers()
        for _, user in pairs(allUsers) do
            if user:HasGroup("admin") then
                user:Notify("warning", userName .. " was banned by " .. adminName)
            end
        end
    else
        print("Target player not found")
    end
end)

RegisterCommand("checkban", function(source, args)
    if not CheckAdminPermission(source) then return end
    
    local targetId = tonumber(args[1])
    
    -- Try multiple methods to find user ID
    local userId = nil
    
    -- Method 1: Online player
    local targetUser = API.GetUserFromSource(targetId)
    if targetUser then
        userId = targetUser:GetId()
    else
        -- Method 2: Use server ID to get user ID
        userId = API.GetUserIdFromServerId(targetId)
    end
    
    if userId then
        local isBanned = API.IsBanned(userId)
        print("User ID", userId, "ban status:", isBanned and "BANNED" or "NOT BANNED")
    else
        print("Could not find user ID for target:", targetId)
    end
end)

Performance Monitoring

-- Monitor cache sizes
RegisterCommand("cachestats", function(source, args)
    if source ~= 0 then return end -- Console only
    
    local allUsers = API.GetUsers()
    local activeUsers = 0
    local loggedInUsers = 0
    
    for userId, user in pairs(allUsers) do
        activeUsers = activeUsers + 1
        if user:GetCharacter() then
            loggedInUsers = loggedInUsers + 1
        end
    end
    
    print("=== CACHE STATISTICS ===")
    print("Active Users:", activeUsers)
    print("Logged In Users:", loggedInUsers)
    print("Memory Usage: ~" .. (activeUsers * 50) .. "KB estimated")
end)

-- Cleanup disconnected users
CreateThread(function()
    while true do
        Wait(300000) -- 5 minutes
        
        local allUsers = API.GetUsers()
        local cleanupCount = 0
        
        for userId, user in pairs(allUsers) do
            local source = user:GetSource()
            if GetPlayerName(source) == nil then
                -- Player disconnected but not cleaned up
                API.ClearUserFromCache(source, userId)
                cleanupCount = cleanupCount + 1
            end
        end
        
        if cleanupCount > 0 then
            print("Cleaned up", cleanupCount, "disconnected users from cache")
        end
    end
end)

Tunnel/Proxy Integration Examples

Server-Side API Export

-- In your main server file
local API = {}

-- Define all your API functions here...

-- Export via Proxy for other server resources
Proxy.addInterface("frp_core", API)

-- Export via Tunnel for client communication  
Tunnel.bindInterface("frp_core", API)

Client-Side Usage

-- Client-side script accessing server API
local Tunnel = module("lib/Tunnel")
local API = Tunnel.getInterface("frp_core", "your_resource_name")

-- Now you can call server functions from client
RegisterCommand("mybalance", function()
    local userId = API.GetUserIdFromServerId(GetPlayerServerId(PlayerId()))
    if userId then
        -- This calls the server and waits for response
        print("My user ID is:", userId)
    end
end)

Cross-Resource Server Usage

-- In another server resource
local Proxy = module("lib/Proxy")
local API = Proxy.getInterface("frp_core", GetCurrentResourceName())

-- Use API functions from another resource
RegisterCommand("getuser", function(source, args)
    local user = API.GetUserFromSource(source)
    if user then
        print("Found user:", user.GetName()) -- Note: Proxy calls return values directly
    end
end)

Best Practices

Error Handling

function SafeUserLookup(source)
    local success, user = pcall(API.GetUserFromSource, source)
    if not success then
        print("Error getting user for source", source, ":", user)
        return nil
    end
    return user
end

Performance Optimization

-- Cache frequently accessed data
local userIdCache = {}

function GetCachedUserId(source)
    if userIdCache[source] then
        return userIdCache[source]
    end
    
    local userId = API.GetUserIdFromServerId(source)
    if userId then
        userIdCache[source] = userId
        
        -- Clear cache when player disconnects
        local cleanup = function()
            userIdCache[source] = nil
        end
        
        -- Set cleanup timer
        SetTimeout(600000, cleanup) -- 10 minutes
    end
    
    return userId
end

Database Safety

function SafeDatabaseOperation(operation, ...)
    local success, result = pcall(operation, ...)
    if not success then
        print("Database operation failed:", result)
        return nil
    end
    return result
end

-- Usage
local user = SafeDatabaseOperation(API.GetUserFromUserIdOffline, 123)

Security Considerations

  1. Input Validation: Always validate user inputs before database queries

  2. Rate Limiting: Implement rate limits for user creation and lookups

  3. Permission Checks: Verify permissions before sensitive operations

  4. Audit Logging: Log all user management operations

  5. Cache Management: Regularly clean up disconnected users from memory

This comprehensive API provides robust user management capabilities with efficient caching, cross-resource communication, and database safety features.

Last updated