update: packages,operations,lua (3 files)

This commit is contained in:
Richard Ward
2025-12-30 22:01:42 +00:00
parent 3dcb8ac8f2
commit d3b7e0ae9c
3 changed files with 1115 additions and 0 deletions

View File

@@ -0,0 +1,394 @@
-- arcade_lobby/seed/scripts/db/operations.lua
-- DBAL operations for Game and Leaderboard entities
-- @module arcade_lobby.db.operations
local M = {}
local json = require('json')
---------------------------------------------------------------------------
-- GAME OPERATIONS
---------------------------------------------------------------------------
---@class GameCreateParams
---@field tenantId string
---@field name string
---@field slug string
---@field description string|nil
---@field thumbnail string|nil
---@field category string
---@field minPlayers number
---@field maxPlayers number
---@field config table|nil Game configuration
---Create a new game entry
---@param dbal table DBAL client instance
---@param params GameCreateParams
---@return table Created game
function M.createGame(dbal, params)
return dbal:create('Game', {
tenantId = params.tenantId,
name = params.name,
slug = params.slug or M._slugify(params.name),
description = params.description,
thumbnail = params.thumbnail,
category = params.category,
minPlayers = params.minPlayers or 1,
maxPlayers = params.maxPlayers or 1,
config = params.config and json.encode(params.config) or '{}',
isActive = true,
playCount = 0,
createdAt = os.time() * 1000,
updatedAt = os.time() * 1000,
})
end
---Get game by ID
---@param dbal table
---@param gameId string
---@return table|nil Game
function M.getGame(dbal, gameId)
local game = dbal:read('Game', gameId)
if game and game.config then
game.config = json.decode(game.config)
end
return game
end
---Get game by slug
---@param dbal table
---@param tenantId string
---@param slug string
---@return table|nil Game
function M.getGameBySlug(dbal, tenantId, slug)
local game = dbal:findFirst('Game', {
where = { tenantId = tenantId, slug = slug },
})
if game and game.config then
game.config = json.decode(game.config)
end
return game
end
---List all games
---@param dbal table
---@param tenantId string
---@param category string|nil Filter by category
---@param take number|nil
---@return table[] Games
function M.listGames(dbal, tenantId, category, take)
local where = { tenantId = tenantId, isActive = true }
local result = dbal:list('Game', {
where = where,
orderBy = { playCount = 'desc' },
take = take or 50,
})
local games = result.items or {}
-- Filter by category if specified
if category then
local filtered = {}
for _, game in ipairs(games) do
if game.category == category then
table.insert(filtered, game)
end
end
games = filtered
end
return games
end
---Increment play count
---@param dbal table
---@param gameId string
function M.incrementPlayCount(dbal, gameId)
local game = M.getGame(dbal, gameId)
if game then
dbal:update('Game', gameId, {
playCount = (game.playCount or 0) + 1,
})
end
end
---Update game
---@param dbal table
---@param gameId string
---@param updates table
---@return table Updated game
function M.updateGame(dbal, gameId, updates)
if updates.config and type(updates.config) == 'table' then
updates.config = json.encode(updates.config)
end
updates.updatedAt = os.time() * 1000
return dbal:update('Game', gameId, updates)
end
---Deactivate game
---@param dbal table
---@param gameId string
function M.deactivateGame(dbal, gameId)
return M.updateGame(dbal, gameId, { isActive = false })
end
---------------------------------------------------------------------------
-- LEADERBOARD OPERATIONS
---------------------------------------------------------------------------
---@class LeaderboardEntryParams
---@field tenantId string
---@field gameId string
---@field userId string
---@field username string
---@field score number
---@field metadata table|nil Additional game-specific data
---Submit a score
---@param dbal table
---@param params LeaderboardEntryParams
---@return table Created entry
function M.submitScore(dbal, params)
return dbal:create('LeaderboardEntry', {
tenantId = params.tenantId,
gameId = params.gameId,
userId = params.userId,
username = params.username,
score = params.score,
metadata = params.metadata and json.encode(params.metadata) or nil,
submittedAt = os.time() * 1000,
})
end
---Get high scores for a game
---@param dbal table
---@param gameId string
---@param take number|nil
---@return table[] Sorted entries
function M.getHighScores(dbal, gameId, take)
local result = dbal:list('LeaderboardEntry', {
where = { gameId = gameId },
orderBy = { score = 'desc' },
take = take or 10,
})
return result.items or {}
end
---Get user's best score for a game
---@param dbal table
---@param gameId string
---@param userId string
---@return table|nil Best entry
function M.getUserBestScore(dbal, gameId, userId)
local result = dbal:list('LeaderboardEntry', {
where = { gameId = gameId, userId = userId },
orderBy = { score = 'desc' },
take = 1,
})
if result.items and #result.items > 0 then
return result.items[1]
end
return nil
end
---Get user's rank on leaderboard
---@param dbal table
---@param gameId string
---@param userId string
---@return number|nil Rank (1-based) or nil if no score
function M.getUserRank(dbal, gameId, userId)
local userBest = M.getUserBestScore(dbal, gameId, userId)
if not userBest then
return nil
end
local allScores = M.getHighScores(dbal, gameId, 10000)
for rank, entry in ipairs(allScores) do
if entry.userId == userId then
return rank
end
end
return nil
end
---Get leaderboard with user's position highlighted
---@param dbal table
---@param gameId string
---@param userId string
---@param around number|nil How many entries to show around user
---@return table Leaderboard data
function M.getLeaderboardWithUser(dbal, gameId, userId, around)
around = around or 3
local topScores = M.getHighScores(dbal, gameId, 10)
local userRank = M.getUserRank(dbal, gameId, userId)
return {
top = topScores,
userRank = userRank,
userBest = M.getUserBestScore(dbal, gameId, userId),
}
end
---------------------------------------------------------------------------
-- GAME SESSION OPERATIONS
---------------------------------------------------------------------------
---@class GameSessionParams
---@field tenantId string
---@field gameId string
---@field hostId string
---@field maxPlayers number
---Create a game session (multiplayer lobby)
---@param dbal table
---@param params GameSessionParams
---@return table Created session
function M.createSession(dbal, params)
return dbal:create('GameSession', {
tenantId = params.tenantId,
gameId = params.gameId,
hostId = params.hostId,
status = 'waiting',
maxPlayers = params.maxPlayers,
currentPlayers = 1,
players = json.encode({ params.hostId }),
createdAt = os.time() * 1000,
})
end
---Get session by ID
---@param dbal table
---@param sessionId string
---@return table|nil Session
function M.getSession(dbal, sessionId)
local session = dbal:read('GameSession', sessionId)
if session and session.players then
session.players = json.decode(session.players)
end
return session
end
---List active sessions for a game
---@param dbal table
---@param gameId string
---@return table[] Sessions
function M.listActiveSessions(dbal, gameId)
local result = dbal:list('GameSession', {
where = { gameId = gameId, status = 'waiting' },
orderBy = { createdAt = 'desc' },
take = 20,
})
local sessions = result.items or {}
for _, session in ipairs(sessions) do
if session.players then
session.players = json.decode(session.players)
end
end
return sessions
end
---Join a session
---@param dbal table
---@param sessionId string
---@param userId string
---@return table|nil Updated session or nil if full
function M.joinSession(dbal, sessionId, userId)
local session = M.getSession(dbal, sessionId)
if not session then
error('Session not found: ' .. sessionId)
end
if session.currentPlayers >= session.maxPlayers then
return nil -- Session full
end
local players = session.players or {}
-- Check if already in session
for _, pid in ipairs(players) do
if pid == userId then
return session
end
end
table.insert(players, userId)
return dbal:update('GameSession', sessionId, {
players = json.encode(players),
currentPlayers = #players,
})
end
---Leave a session
---@param dbal table
---@param sessionId string
---@param userId string
function M.leaveSession(dbal, sessionId, userId)
local session = M.getSession(dbal, sessionId)
if not session then
return
end
local players = session.players or {}
local newPlayers = {}
for _, pid in ipairs(players) do
if pid ~= userId then
table.insert(newPlayers, pid)
end
end
if #newPlayers == 0 then
-- No players left, delete session
dbal:delete('GameSession', sessionId)
else
dbal:update('GameSession', sessionId, {
players = json.encode(newPlayers),
currentPlayers = #newPlayers,
hostId = newPlayers[1], -- Transfer host if needed
})
end
end
---Start a session
---@param dbal table
---@param sessionId string
function M.startSession(dbal, sessionId)
return dbal:update('GameSession', sessionId, {
status = 'playing',
startedAt = os.time() * 1000,
})
end
---End a session
---@param dbal table
---@param sessionId string
function M.endSession(dbal, sessionId)
return dbal:update('GameSession', sessionId, {
status = 'finished',
endedAt = os.time() * 1000,
})
end
---------------------------------------------------------------------------
-- UTILITY FUNCTIONS
---------------------------------------------------------------------------
---Generate URL-safe slug
---@param text string
---@return string
function M._slugify(text)
local slug = text:lower()
slug = slug:gsub('[^%w%s-]', '')
slug = slug:gsub('%s+', '-')
slug = slug:gsub('-+', '-')
slug = slug:gsub('^-', ''):gsub('-$', '')
return slug:sub(1, 50)
end
return M

View File

@@ -0,0 +1,375 @@
-- dashboard/seed/scripts/db/operations.lua
-- DBAL operations for Dashboard widgets and layouts
-- @module dashboard.db.operations
local M = {}
local json = require('json')
---------------------------------------------------------------------------
-- DASHBOARD LAYOUT OPERATIONS
---------------------------------------------------------------------------
---@class DashboardLayoutParams
---@field tenantId string
---@field userId string|nil User-specific or tenant-wide
---@field name string Layout name
---@field widgets table[] Widget configurations
---@field settings table|nil Layout settings
---Save dashboard layout
---@param dbal table DBAL client instance
---@param params DashboardLayoutParams
---@return table Created/updated layout
function M.saveLayout(dbal, params)
local where = {
tenantId = params.tenantId,
name = params.name,
}
if params.userId then
where.userId = params.userId
end
local existing = dbal:findFirst('DashboardLayout', { where = where })
if existing then
return dbal:update('DashboardLayout', existing.id, {
widgets = json.encode(params.widgets),
settings = params.settings and json.encode(params.settings) or nil,
updatedAt = os.time() * 1000,
})
end
return dbal:create('DashboardLayout', {
tenantId = params.tenantId,
userId = params.userId,
name = params.name,
widgets = json.encode(params.widgets),
settings = params.settings and json.encode(params.settings) or '{}',
isDefault = false,
createdAt = os.time() * 1000,
updatedAt = os.time() * 1000,
})
end
---Get layout by name
---@param dbal table
---@param tenantId string
---@param name string
---@param userId string|nil
---@return table|nil Layout
function M.getLayout(dbal, tenantId, name, userId)
local where = { tenantId = tenantId, name = name }
if userId then
where.userId = userId
end
local layout = dbal:findFirst('DashboardLayout', { where = where })
if layout then
layout.widgets = json.decode(layout.widgets or '[]')
layout.settings = json.decode(layout.settings or '{}')
end
return layout
end
---Get user's default layout
---@param dbal table
---@param tenantId string
---@param userId string
---@return table|nil Layout
function M.getDefaultLayout(dbal, tenantId, userId)
-- First try user's default
local userDefault = dbal:findFirst('DashboardLayout', {
where = { tenantId = tenantId, userId = userId, isDefault = true },
})
if userDefault then
userDefault.widgets = json.decode(userDefault.widgets or '[]')
userDefault.settings = json.decode(userDefault.settings or '{}')
return userDefault
end
-- Fall back to tenant default
local tenantDefault = dbal:findFirst('DashboardLayout', {
where = { tenantId = tenantId, isDefault = true },
})
if tenantDefault then
tenantDefault.widgets = json.decode(tenantDefault.widgets or '[]')
tenantDefault.settings = json.decode(tenantDefault.settings or '{}')
return tenantDefault
end
return nil
end
---List all layouts
---@param dbal table
---@param tenantId string
---@param userId string|nil
---@return table[] Layouts
function M.listLayouts(dbal, tenantId, userId)
local where = { tenantId = tenantId }
if userId then
where.userId = userId
end
local result = dbal:list('DashboardLayout', {
where = where,
orderBy = { name = 'asc' },
take = 50,
})
return result.items or {}
end
---Set layout as default
---@param dbal table
---@param tenantId string
---@param layoutId string
---@param userId string|nil
function M.setDefault(dbal, tenantId, layoutId, userId)
-- Clear existing defaults
local layouts = M.listLayouts(dbal, tenantId, userId)
for _, layout in ipairs(layouts) do
if layout.isDefault then
dbal:update('DashboardLayout', layout.id, { isDefault = false })
end
end
return dbal:update('DashboardLayout', layoutId, {
isDefault = true,
updatedAt = os.time() * 1000,
})
end
---Delete layout
---@param dbal table
---@param layoutId string
---@return boolean Success
function M.deleteLayout(dbal, layoutId)
return dbal:delete('DashboardLayout', layoutId)
end
---------------------------------------------------------------------------
-- WIDGET OPERATIONS
---------------------------------------------------------------------------
---@class WidgetConfig
---@field id string Unique widget ID
---@field type string Widget type (stats, chart, list, etc.)
---@field title string Widget title
---@field x number Grid x position
---@field y number Grid y position
---@field w number Grid width
---@field h number Grid height
---@field config table Widget-specific config
---Add widget to layout
---@param dbal table
---@param layoutId string
---@param widget WidgetConfig
---@return table Updated layout
function M.addWidget(dbal, layoutId, widget)
local layout = dbal:read('DashboardLayout', layoutId)
if not layout then
error('Layout not found: ' .. layoutId)
end
local widgets = json.decode(layout.widgets or '[]')
-- Generate ID if not provided
if not widget.id then
widget.id = 'widget_' .. os.time() .. '_' .. math.random(1000, 9999)
end
table.insert(widgets, widget)
return dbal:update('DashboardLayout', layoutId, {
widgets = json.encode(widgets),
updatedAt = os.time() * 1000,
})
end
---Update widget in layout
---@param dbal table
---@param layoutId string
---@param widgetId string
---@param updates table
---@return table Updated layout
function M.updateWidget(dbal, layoutId, widgetId, updates)
local layout = dbal:read('DashboardLayout', layoutId)
if not layout then
error('Layout not found: ' .. layoutId)
end
local widgets = json.decode(layout.widgets or '[]')
for i, widget in ipairs(widgets) do
if widget.id == widgetId then
for key, value in pairs(updates) do
widgets[i][key] = value
end
break
end
end
return dbal:update('DashboardLayout', layoutId, {
widgets = json.encode(widgets),
updatedAt = os.time() * 1000,
})
end
---Remove widget from layout
---@param dbal table
---@param layoutId string
---@param widgetId string
---@return table Updated layout
function M.removeWidget(dbal, layoutId, widgetId)
local layout = dbal:read('DashboardLayout', layoutId)
if not layout then
error('Layout not found: ' .. layoutId)
end
local widgets = json.decode(layout.widgets or '[]')
local newWidgets = {}
for _, widget in ipairs(widgets) do
if widget.id ~= widgetId then
table.insert(newWidgets, widget)
end
end
return dbal:update('DashboardLayout', layoutId, {
widgets = json.encode(newWidgets),
updatedAt = os.time() * 1000,
})
end
---Update widget positions (after drag)
---@param dbal table
---@param layoutId string
---@param positions table[] Array of {id, x, y, w, h}
---@return table Updated layout
function M.updatePositions(dbal, layoutId, positions)
local layout = dbal:read('DashboardLayout', layoutId)
if not layout then
error('Layout not found: ' .. layoutId)
end
local widgets = json.decode(layout.widgets or '[]')
-- Create lookup map
local posMap = {}
for _, pos in ipairs(positions) do
posMap[pos.id] = pos
end
-- Update positions
for i, widget in ipairs(widgets) do
local pos = posMap[widget.id]
if pos then
widgets[i].x = pos.x
widgets[i].y = pos.y
widgets[i].w = pos.w or widgets[i].w
widgets[i].h = pos.h or widgets[i].h
end
end
return dbal:update('DashboardLayout', layoutId, {
widgets = json.encode(widgets),
updatedAt = os.time() * 1000,
})
end
---------------------------------------------------------------------------
-- WIDGET DATA CACHE
---------------------------------------------------------------------------
---Cache widget data
---@param dbal table
---@param tenantId string
---@param widgetType string
---@param cacheKey string
---@param data table
---@param ttlSeconds number|nil
function M.cacheWidgetData(dbal, tenantId, widgetType, cacheKey, data, ttlSeconds)
local key = widgetType .. ':' .. cacheKey
local expiresAt = ttlSeconds and ((os.time() + ttlSeconds) * 1000) or nil
local existing = dbal:findFirst('WidgetCache', {
where = { tenantId = tenantId, cacheKey = key },
})
if existing then
return dbal:update('WidgetCache', existing.id, {
data = json.encode(data),
expiresAt = expiresAt,
updatedAt = os.time() * 1000,
})
end
return dbal:create('WidgetCache', {
tenantId = tenantId,
cacheKey = key,
widgetType = widgetType,
data = json.encode(data),
expiresAt = expiresAt,
createdAt = os.time() * 1000,
updatedAt = os.time() * 1000,
})
end
---Get cached widget data
---@param dbal table
---@param tenantId string
---@param widgetType string
---@param cacheKey string
---@return table|nil Cached data or nil if expired/not found
function M.getCachedData(dbal, tenantId, widgetType, cacheKey)
local key = widgetType .. ':' .. cacheKey
local cached = dbal:findFirst('WidgetCache', {
where = { tenantId = tenantId, cacheKey = key },
})
if cached then
-- Check expiry
if cached.expiresAt and cached.expiresAt < (os.time() * 1000) then
dbal:delete('WidgetCache', cached.id)
return nil
end
return json.decode(cached.data)
end
return nil
end
---Clear expired cache entries
---@param dbal table
---@param tenantId string
---@return number Number of entries cleared
function M.clearExpiredCache(dbal, tenantId)
local now = os.time() * 1000
local result = dbal:list('WidgetCache', {
where = { tenantId = tenantId },
take = 10000,
})
local count = 0
for _, entry in ipairs(result.items or {}) do
if entry.expiresAt and entry.expiresAt < now then
dbal:delete('WidgetCache', entry.id)
count = count + 1
end
end
return count
end
return M

View File

@@ -0,0 +1,346 @@
-- data_table/seed/scripts/db/operations.lua
-- DBAL operations for generic data table management
-- Provides reusable CRUD helpers for any entity
-- @module data_table.db.operations
local M = {}
local json = require('json')
---------------------------------------------------------------------------
-- GENERIC CRUD OPERATIONS
---------------------------------------------------------------------------
---Create a record in any entity
---@param dbal table DBAL client instance
---@param entity string Entity name
---@param data table Record data
---@return table Created record
function M.create(dbal, entity, data)
-- Auto-add timestamps if not present
if not data.createdAt then
data.createdAt = os.time() * 1000
end
return dbal:create(entity, data)
end
---Read a record by ID
---@param dbal table
---@param entity string Entity name
---@param id string Record ID
---@return table|nil Record
function M.read(dbal, entity, id)
return dbal:read(entity, id)
end
---Update a record
---@param dbal table
---@param entity string Entity name
---@param id string Record ID
---@param data table Fields to update
---@return table Updated record
function M.update(dbal, entity, id, data)
-- Auto-update timestamp if not present
if not data.updatedAt then
data.updatedAt = os.time() * 1000
end
return dbal:update(entity, id, data)
end
---Delete a record
---@param dbal table
---@param entity string Entity name
---@param id string Record ID
---@return boolean Success
function M.delete(dbal, entity, id)
return dbal:delete(entity, id)
end
---List records with filtering and pagination
---@param dbal table
---@param entity string Entity name
---@param options table List options
---@return table List result with items and total
function M.list(dbal, entity, options)
return dbal:list(entity, {
where = options.where or {},
orderBy = options.orderBy or { createdAt = 'desc' },
take = options.take or 50,
skip = options.skip or 0,
})
end
---Find first matching record
---@param dbal table
---@param entity string Entity name
---@param where table Where conditions
---@return table|nil First matching record
function M.findFirst(dbal, entity, where)
return dbal:findFirst(entity, { where = where })
end
---------------------------------------------------------------------------
-- ADVANCED QUERY HELPERS
---------------------------------------------------------------------------
---Search records by text field
---@param dbal table
---@param entity string Entity name
---@param field string Field to search
---@param query string Search query
---@param tenantId string|nil Tenant filter
---@param take number|nil Max results
---@return table[] Matching records
function M.search(dbal, entity, field, query, tenantId, take)
local where = {}
if tenantId then
where.tenantId = tenantId
end
local result = dbal:list(entity, {
where = where,
take = 1000,
})
local matches = {}
local lowerQuery = query:lower()
for _, record in ipairs(result.items or {}) do
local fieldValue = record[field]
if fieldValue and type(fieldValue) == 'string' then
if fieldValue:lower():find(lowerQuery, 1, true) then
table.insert(matches, record)
if #matches >= (take or 50) then
break
end
end
end
end
return matches
end
---Count records matching conditions
---@param dbal table
---@param entity string Entity name
---@param where table|nil Where conditions
---@return number Count
function M.count(dbal, entity, where)
local result = dbal:list(entity, {
where = where or {},
take = 100000,
})
return result.total or #(result.items or {})
end
---Check if record exists
---@param dbal table
---@param entity string Entity name
---@param where table Where conditions
---@return boolean Exists
function M.exists(dbal, entity, where)
local record = M.findFirst(dbal, entity, where)
return record ~= nil
end
---Get distinct values for a field
---@param dbal table
---@param entity string Entity name
---@param field string Field name
---@param where table|nil Where conditions
---@return table[] Distinct values
function M.distinct(dbal, entity, field, where)
local result = dbal:list(entity, {
where = where or {},
take = 10000,
})
local seen = {}
local values = {}
for _, record in ipairs(result.items or {}) do
local value = record[field]
if value ~= nil and not seen[tostring(value)] then
seen[tostring(value)] = true
table.insert(values, value)
end
end
return values
end
---------------------------------------------------------------------------
-- BULK OPERATIONS
---------------------------------------------------------------------------
---Create multiple records
---@param dbal table
---@param entity string Entity name
---@param records table[] Records to create
---@return table[] Created records
function M.createMany(dbal, entity, records)
local created = {}
for _, data in ipairs(records) do
local record = M.create(dbal, entity, data)
table.insert(created, record)
end
return created
end
---Update multiple records by IDs
---@param dbal table
---@param entity string Entity name
---@param ids table[] Record IDs
---@param data table Fields to update
---@return number Count of updated records
function M.updateMany(dbal, entity, ids, data)
local count = 0
for _, id in ipairs(ids) do
M.update(dbal, entity, id, data)
count = count + 1
end
return count
end
---Delete multiple records by IDs
---@param dbal table
---@param entity string Entity name
---@param ids table[] Record IDs
---@return number Count of deleted records
function M.deleteMany(dbal, entity, ids)
local count = 0
for _, id in ipairs(ids) do
if M.delete(dbal, entity, id) then
count = count + 1
end
end
return count
end
---Delete records matching conditions
---@param dbal table
---@param entity string Entity name
---@param where table Where conditions
---@return number Count of deleted records
function M.deleteWhere(dbal, entity, where)
local result = dbal:list(entity, {
where = where,
take = 10000,
})
local count = 0
for _, record in ipairs(result.items or {}) do
if M.delete(dbal, entity, record.id) then
count = count + 1
end
end
return count
end
---------------------------------------------------------------------------
-- TABLE CONFIGURATION
---------------------------------------------------------------------------
---@class TableConfig
---@field entity string Entity name
---@field columns table[] Column definitions
---@field defaultSort table|nil Default sort order
---@field pageSize number|nil Default page size
---@field filters table[]|nil Available filters
---Save table configuration
---@param dbal table
---@param tenantId string
---@param tableKey string Unique key for this table config
---@param config TableConfig
---@return table Saved config
function M.saveTableConfig(dbal, tenantId, tableKey, config)
local existing = dbal:findFirst('TableConfig', {
where = { tenantId = tenantId, tableKey = tableKey },
})
if existing then
return dbal:update('TableConfig', existing.id, {
config = json.encode(config),
updatedAt = os.time() * 1000,
})
end
return dbal:create('TableConfig', {
tenantId = tenantId,
tableKey = tableKey,
config = json.encode(config),
createdAt = os.time() * 1000,
updatedAt = os.time() * 1000,
})
end
---Get table configuration
---@param dbal table
---@param tenantId string
---@param tableKey string
---@return TableConfig|nil
function M.getTableConfig(dbal, tenantId, tableKey)
local record = dbal:findFirst('TableConfig', {
where = { tenantId = tenantId, tableKey = tableKey },
})
if record and record.config then
return json.decode(record.config)
end
return nil
end
---------------------------------------------------------------------------
-- USER PREFERENCES
---------------------------------------------------------------------------
---Save user column preferences
---@param dbal table
---@param userId string
---@param tableKey string
---@param columns table[] Visible columns in order
---@param columnWidths table|nil Column widths
function M.saveUserPreferences(dbal, userId, tableKey, columns, columnWidths)
local existing = dbal:findFirst('UserTablePreference', {
where = { userId = userId, tableKey = tableKey },
})
local data = {
columns = json.encode(columns),
columnWidths = columnWidths and json.encode(columnWidths) or nil,
updatedAt = os.time() * 1000,
}
if existing then
return dbal:update('UserTablePreference', existing.id, data)
end
data.userId = userId
data.tableKey = tableKey
data.createdAt = os.time() * 1000
return dbal:create('UserTablePreference', data)
end
---Get user column preferences
---@param dbal table
---@param userId string
---@param tableKey string
---@return table|nil Preferences
function M.getUserPreferences(dbal, userId, tableKey)
local record = dbal:findFirst('UserTablePreference', {
where = { userId = userId, tableKey = tableKey },
})
if record then
return {
columns = record.columns and json.decode(record.columns) or nil,
columnWidths = record.columnWidths and json.decode(record.columnWidths) or nil,
}
end
return nil
end
return M