mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-25 14:25:02 +00:00
418 lines
11 KiB
Lua
418 lines
11 KiB
Lua
-- irc_webchat/seed/scripts/db/operations.lua
|
|
-- DBAL operations for IRC entities (Channel, Message, Membership)
|
|
-- @module irc_webchat.db.operations
|
|
|
|
local M = {}
|
|
local json = require('json')
|
|
|
|
---------------------------------------------------------------------------
|
|
-- CHANNEL OPERATIONS
|
|
---------------------------------------------------------------------------
|
|
|
|
---@class IRCChannelCreateParams
|
|
---@field tenantId string
|
|
---@field name string Must start with #
|
|
---@field displayName string|nil
|
|
---@field description string|nil
|
|
---@field topic string|nil
|
|
---@field minLevel number|nil
|
|
---@field creatorId string
|
|
|
|
---Create a new IRC channel
|
|
---@param dbal table DBAL client instance
|
|
---@param params IRCChannelCreateParams
|
|
---@return table Created channel
|
|
function M.createChannel(dbal, params)
|
|
-- Validate channel name starts with #
|
|
local name = params.name
|
|
if name:sub(1, 1) ~= '#' then
|
|
name = '#' .. name
|
|
end
|
|
|
|
-- Check for duplicate channel name
|
|
local existing = dbal:findFirst('IRCChannel', {
|
|
where = { tenantId = params.tenantId, name = name },
|
|
})
|
|
|
|
if existing then
|
|
error('Channel already exists: ' .. name)
|
|
end
|
|
|
|
return dbal:create('IRCChannel', {
|
|
tenantId = params.tenantId,
|
|
name = name,
|
|
displayName = params.displayName or name:sub(2),
|
|
description = params.description,
|
|
topic = params.topic or 'Welcome to ' .. name,
|
|
minLevel = params.minLevel or 0,
|
|
isPrivate = false,
|
|
isArchived = false,
|
|
creatorId = params.creatorId,
|
|
memberCount = 0,
|
|
messageCount = 0,
|
|
createdAt = os.time() * 1000,
|
|
updatedAt = os.time() * 1000,
|
|
lastActivityAt = os.time() * 1000,
|
|
})
|
|
end
|
|
|
|
---Get channel by name
|
|
---@param dbal table
|
|
---@param tenantId string
|
|
---@param name string
|
|
---@return table|nil Channel
|
|
function M.getChannelByName(dbal, tenantId, name)
|
|
if name:sub(1, 1) ~= '#' then
|
|
name = '#' .. name
|
|
end
|
|
|
|
return dbal:findFirst('IRCChannel', {
|
|
where = { tenantId = tenantId, name = name },
|
|
})
|
|
end
|
|
|
|
---List all channels
|
|
---@param dbal table
|
|
---@param tenantId string
|
|
---@param includeArchived boolean|nil
|
|
---@return table[] Channels
|
|
function M.listChannels(dbal, tenantId, includeArchived)
|
|
local result = dbal:list('IRCChannel', {
|
|
where = { tenantId = tenantId },
|
|
orderBy = { memberCount = 'desc' },
|
|
take = 100,
|
|
})
|
|
|
|
local channels = result.items or {}
|
|
|
|
if not includeArchived then
|
|
local active = {}
|
|
for _, ch in ipairs(channels) do
|
|
if not ch.isArchived then
|
|
table.insert(active, ch)
|
|
end
|
|
end
|
|
channels = active
|
|
end
|
|
|
|
return channels
|
|
end
|
|
|
|
---Update channel topic
|
|
---@param dbal table
|
|
---@param channelId string
|
|
---@param topic string
|
|
---@param userId string Who set the topic
|
|
---@return table Updated channel
|
|
function M.setTopic(dbal, channelId, topic, userId)
|
|
return dbal:update('IRCChannel', channelId, {
|
|
topic = topic,
|
|
topicSetBy = userId,
|
|
topicSetAt = os.time() * 1000,
|
|
updatedAt = os.time() * 1000,
|
|
})
|
|
end
|
|
|
|
---Archive a channel
|
|
---@param dbal table
|
|
---@param channelId string
|
|
function M.archiveChannel(dbal, channelId)
|
|
return dbal:update('IRCChannel', channelId, {
|
|
isArchived = true,
|
|
updatedAt = os.time() * 1000,
|
|
})
|
|
end
|
|
|
|
---------------------------------------------------------------------------
|
|
-- MESSAGE OPERATIONS
|
|
---------------------------------------------------------------------------
|
|
|
|
---@class IRCMessageCreateParams
|
|
---@field tenantId string
|
|
---@field channelId string
|
|
---@field userId string
|
|
---@field username string
|
|
---@field displayName string|nil
|
|
---@field type string message|join|part|action|notice|system
|
|
---@field content string
|
|
---@field replyToId string|nil
|
|
|
|
---Send a message to a channel
|
|
---@param dbal table DBAL client instance
|
|
---@param params IRCMessageCreateParams
|
|
---@return table Created message
|
|
function M.sendMessage(dbal, params)
|
|
local message = dbal:create('IRCMessage', {
|
|
tenantId = params.tenantId,
|
|
channelId = params.channelId,
|
|
userId = params.userId,
|
|
username = params.username,
|
|
displayName = params.displayName or params.username,
|
|
type = params.type or 'message',
|
|
content = params.content,
|
|
replyToId = params.replyToId,
|
|
timestamp = os.time() * 1000,
|
|
})
|
|
|
|
-- Update channel stats
|
|
local channel = dbal:read('IRCChannel', params.channelId)
|
|
if channel then
|
|
dbal:update('IRCChannel', params.channelId, {
|
|
messageCount = (channel.messageCount or 0) + 1,
|
|
lastActivityAt = os.time() * 1000,
|
|
updatedAt = os.time() * 1000,
|
|
})
|
|
end
|
|
|
|
return message
|
|
end
|
|
|
|
---Get channel messages (most recent first)
|
|
---@param dbal table
|
|
---@param channelId string
|
|
---@param take number|nil
|
|
---@param before number|nil Timestamp - get messages before this time
|
|
---@return table[] Messages
|
|
function M.getMessages(dbal, channelId, take, before)
|
|
local result = dbal:list('IRCMessage', {
|
|
where = { channelId = channelId },
|
|
orderBy = { timestamp = 'desc' },
|
|
take = take or 50,
|
|
})
|
|
|
|
local messages = result.items or {}
|
|
|
|
-- Filter by timestamp if specified
|
|
if before then
|
|
local filtered = {}
|
|
for _, msg in ipairs(messages) do
|
|
if msg.timestamp < before then
|
|
table.insert(filtered, msg)
|
|
end
|
|
end
|
|
messages = filtered
|
|
end
|
|
|
|
-- Reverse to get chronological order for display
|
|
local reversed = {}
|
|
for i = #messages, 1, -1 do
|
|
table.insert(reversed, messages[i])
|
|
end
|
|
|
|
return reversed
|
|
end
|
|
|
|
---Send a system message
|
|
---@param dbal table
|
|
---@param tenantId string
|
|
---@param channelId string
|
|
---@param content string
|
|
function M.sendSystemMessage(dbal, tenantId, channelId, content)
|
|
return M.sendMessage(dbal, {
|
|
tenantId = tenantId,
|
|
channelId = channelId,
|
|
userId = 'system',
|
|
username = '*system*',
|
|
type = 'system',
|
|
content = content,
|
|
})
|
|
end
|
|
|
|
---Delete a message (mark as deleted)
|
|
---@param dbal table
|
|
---@param messageId string
|
|
---@param deletedBy string
|
|
function M.deleteMessage(dbal, messageId, deletedBy)
|
|
return dbal:update('IRCMessage', messageId, {
|
|
content = '[deleted by ' .. deletedBy .. ']',
|
|
type = 'system',
|
|
})
|
|
end
|
|
|
|
---------------------------------------------------------------------------
|
|
-- MEMBERSHIP OPERATIONS
|
|
---------------------------------------------------------------------------
|
|
|
|
---@class IRCMembershipParams
|
|
---@field tenantId string
|
|
---@field channelId string
|
|
---@field userId string
|
|
---@field username string
|
|
---@field role string owner|operator|voiced|member
|
|
|
|
---Join a channel
|
|
---@param dbal table DBAL client instance
|
|
---@param params IRCMembershipParams
|
|
---@return table Membership record
|
|
function M.joinChannel(dbal, params)
|
|
-- Check if already a member
|
|
local existing = dbal:findFirst('IRCMembership', {
|
|
where = {
|
|
channelId = params.channelId,
|
|
userId = params.userId,
|
|
},
|
|
})
|
|
|
|
if existing then
|
|
-- Update last seen
|
|
return dbal:update('IRCMembership', existing.id, {
|
|
isOnline = true,
|
|
lastSeenAt = os.time() * 1000,
|
|
})
|
|
end
|
|
|
|
local membership = dbal:create('IRCMembership', {
|
|
tenantId = params.tenantId,
|
|
channelId = params.channelId,
|
|
userId = params.userId,
|
|
username = params.username,
|
|
role = params.role or 'member',
|
|
isOnline = true,
|
|
joinedAt = os.time() * 1000,
|
|
lastSeenAt = os.time() * 1000,
|
|
})
|
|
|
|
-- Update channel member count
|
|
local channel = dbal:read('IRCChannel', params.channelId)
|
|
if channel then
|
|
dbal:update('IRCChannel', params.channelId, {
|
|
memberCount = (channel.memberCount or 0) + 1,
|
|
updatedAt = os.time() * 1000,
|
|
})
|
|
end
|
|
|
|
-- Send join message
|
|
M.sendMessage(dbal, {
|
|
tenantId = params.tenantId,
|
|
channelId = params.channelId,
|
|
userId = params.userId,
|
|
username = params.username,
|
|
type = 'join',
|
|
content = params.username .. ' has joined the channel',
|
|
})
|
|
|
|
return membership
|
|
end
|
|
|
|
---Leave a channel
|
|
---@param dbal table
|
|
---@param channelId string
|
|
---@param userId string
|
|
---@param message string|nil Part message
|
|
function M.leaveChannel(dbal, channelId, userId, message)
|
|
local membership = dbal:findFirst('IRCMembership', {
|
|
where = { channelId = channelId, userId = userId },
|
|
})
|
|
|
|
if not membership then
|
|
return false
|
|
end
|
|
|
|
-- Send part message
|
|
local channel = dbal:read('IRCChannel', channelId)
|
|
M.sendMessage(dbal, {
|
|
tenantId = membership.tenantId,
|
|
channelId = channelId,
|
|
userId = userId,
|
|
username = membership.username,
|
|
type = 'part',
|
|
content = membership.username .. ' has left' .. (message and (': ' .. message) or ''),
|
|
})
|
|
|
|
-- Delete membership
|
|
dbal:delete('IRCMembership', membership.id)
|
|
|
|
-- Update channel member count
|
|
if channel then
|
|
dbal:update('IRCChannel', channelId, {
|
|
memberCount = math.max(0, (channel.memberCount or 1) - 1),
|
|
updatedAt = os.time() * 1000,
|
|
})
|
|
end
|
|
|
|
return true
|
|
end
|
|
|
|
---Get channel members
|
|
---@param dbal table
|
|
---@param channelId string
|
|
---@param onlineOnly boolean|nil
|
|
---@return table[] Members
|
|
function M.getMembers(dbal, channelId, onlineOnly)
|
|
local result = dbal:list('IRCMembership', {
|
|
where = { channelId = channelId },
|
|
orderBy = { role = 'asc', username = 'asc' },
|
|
take = 500,
|
|
})
|
|
|
|
local members = result.items or {}
|
|
|
|
if onlineOnly then
|
|
local online = {}
|
|
for _, m in ipairs(members) do
|
|
if m.isOnline then
|
|
table.insert(online, m)
|
|
end
|
|
end
|
|
members = online
|
|
end
|
|
|
|
return members
|
|
end
|
|
|
|
---Set user's online status
|
|
---@param dbal table
|
|
---@param channelId string
|
|
---@param userId string
|
|
---@param isOnline boolean
|
|
function M.setOnlineStatus(dbal, channelId, userId, isOnline)
|
|
local membership = dbal:findFirst('IRCMembership', {
|
|
where = { channelId = channelId, userId = userId },
|
|
})
|
|
|
|
if membership then
|
|
dbal:update('IRCMembership', membership.id, {
|
|
isOnline = isOnline,
|
|
lastSeenAt = os.time() * 1000,
|
|
})
|
|
end
|
|
end
|
|
|
|
---Update user role in channel
|
|
---@param dbal table
|
|
---@param channelId string
|
|
---@param userId string
|
|
---@param role string owner|operator|voiced|member
|
|
function M.setRole(dbal, channelId, userId, role)
|
|
local membership = dbal:findFirst('IRCMembership', {
|
|
where = { channelId = channelId, userId = userId },
|
|
})
|
|
|
|
if membership then
|
|
dbal:update('IRCMembership', membership.id, { role = role })
|
|
end
|
|
end
|
|
|
|
---Get channels a user is in
|
|
---@param dbal table
|
|
---@param userId string
|
|
---@return table[] Channels
|
|
function M.getUserChannels(dbal, userId)
|
|
local memberships = dbal:list('IRCMembership', {
|
|
where = { userId = userId },
|
|
take = 100,
|
|
})
|
|
|
|
local channels = {}
|
|
for _, m in ipairs(memberships.items or {}) do
|
|
local channel = dbal:read('IRCChannel', m.channelId)
|
|
if channel and not channel.isArchived then
|
|
table.insert(channels, channel)
|
|
end
|
|
end
|
|
|
|
return channels
|
|
end
|
|
|
|
return M
|