mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-28 15:54:56 +00:00
370 lines
10 KiB
Lua
370 lines
10 KiB
Lua
-- forum_forge/seed/scripts/db/operations.lua
|
|
-- DBAL operations for Forum entities (Category, Thread, Post)
|
|
-- @module forum_forge.db.operations
|
|
|
|
local M = {}
|
|
local json = require('json')
|
|
local prefix = require('shared.db.prefix')
|
|
|
|
-- Package ID for entity prefixing
|
|
local PACKAGE_ID = 'forum_forge'
|
|
|
|
-- Helper to get prefixed entity name
|
|
local function entity(name)
|
|
return prefix.getPrefixedName(PACKAGE_ID, name)
|
|
end
|
|
|
|
---------------------------------------------------------------------------
|
|
-- CATEGORY OPERATIONS
|
|
---------------------------------------------------------------------------
|
|
|
|
---@class ForumCategoryCreateParams
|
|
---@field tenantId string
|
|
---@field name string
|
|
---@field slug string
|
|
---@field description string|nil
|
|
---@field icon string|nil
|
|
---@field color string|nil
|
|
---@field parentId string|nil
|
|
---@field minLevel number|nil
|
|
|
|
---Create a new forum category
|
|
---@param dbal table DBAL client instance
|
|
---@param params ForumCategoryCreateParams
|
|
---@return table Created category
|
|
function M.createCategory(dbal, params)
|
|
-- Get next sort order
|
|
local existing = dbal:list(entity('ForumCategory'), {
|
|
where = { tenantId = params.tenantId },
|
|
orderBy = { sortOrder = 'desc' },
|
|
take = 1,
|
|
})
|
|
|
|
local maxOrder = 0
|
|
if existing.items and #existing.items > 0 then
|
|
maxOrder = existing.items[1].sortOrder or 0
|
|
end
|
|
|
|
return dbal:create(entity('ForumCategory'), {
|
|
tenantId = params.tenantId,
|
|
name = params.name,
|
|
slug = params.slug,
|
|
description = params.description,
|
|
icon = params.icon,
|
|
color = params.color,
|
|
parentId = params.parentId,
|
|
sortOrder = maxOrder + 1,
|
|
minLevel = params.minLevel or 0,
|
|
threadCount = 0,
|
|
postCount = 0,
|
|
createdAt = os.time() * 1000,
|
|
updatedAt = os.time() * 1000,
|
|
})
|
|
end
|
|
|
|
---List categories
|
|
---@param dbal table DBAL client instance
|
|
---@param tenantId string
|
|
---@param parentId string|nil Filter by parent (nil for root)
|
|
---@return table[] Categories
|
|
function M.listCategories(dbal, tenantId, parentId)
|
|
local where = { tenantId = tenantId }
|
|
if parentId then
|
|
where.parentId = parentId
|
|
end
|
|
|
|
local result = dbal:list(entity('ForumCategory'), {
|
|
where = where,
|
|
orderBy = { sortOrder = 'asc' },
|
|
take = 100,
|
|
})
|
|
|
|
return result.items or {}
|
|
end
|
|
|
|
---Update category
|
|
---@param dbal table
|
|
---@param categoryId string
|
|
---@param updates table
|
|
---@return table Updated category
|
|
function M.updateCategory(dbal, categoryId, updates)
|
|
updates.updatedAt = os.time() * 1000
|
|
return dbal:update(entity('ForumCategory'), categoryId, updates)
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------------
|
|
-- THREAD OPERATIONS
|
|
---------------------------------------------------------------------------
|
|
|
|
---@class ForumThreadCreateParams
|
|
---@field tenantId string
|
|
---@field categoryId string
|
|
---@field authorId string
|
|
---@field authorName string
|
|
---@field title string
|
|
---@field tags table|nil Array of tag strings
|
|
---@field isPinned boolean|nil
|
|
---@field isLocked boolean|nil
|
|
|
|
---Create a new thread
|
|
---@param dbal table DBAL client instance
|
|
---@param params ForumThreadCreateParams
|
|
---@return table Created thread
|
|
function M.createThread(dbal, params)
|
|
local thread = dbal:create(entity('ForumThread'), {
|
|
tenantId = params.tenantId,
|
|
categoryId = params.categoryId,
|
|
authorId = params.authorId,
|
|
authorName = params.authorName,
|
|
title = params.title,
|
|
slug = M._slugify(params.title),
|
|
tags = params.tags and json.encode(params.tags) or '[]',
|
|
isPinned = params.isPinned or false,
|
|
isLocked = params.isLocked or false,
|
|
viewCount = 0,
|
|
replyCount = 0,
|
|
createdAt = os.time() * 1000,
|
|
updatedAt = os.time() * 1000,
|
|
lastPostAt = os.time() * 1000,
|
|
lastPostAuthorId = params.authorId,
|
|
lastPostAuthorName = params.authorName,
|
|
})
|
|
|
|
-- Increment category thread count
|
|
local category = dbal:read(entity('ForumCategory'), params.categoryId)
|
|
if category then
|
|
dbal:update(entity('ForumCategory'), params.categoryId, {
|
|
threadCount = (category.threadCount or 0) + 1,
|
|
updatedAt = os.time() * 1000,
|
|
})
|
|
end
|
|
|
|
return thread
|
|
end
|
|
|
|
---List threads in a category
|
|
---@param dbal table DBAL client instance
|
|
---@param categoryId string
|
|
---@param take number|nil
|
|
---@param skip number|nil
|
|
---@return table List result
|
|
function M.listThreads(dbal, categoryId, take, skip)
|
|
return dbal:list(entity('ForumThread'), {
|
|
where = { categoryId = categoryId },
|
|
orderBy = { isPinned = 'desc', lastPostAt = 'desc' },
|
|
take = take or 20,
|
|
skip = skip or 0,
|
|
})
|
|
end
|
|
|
|
---Get thread by ID
|
|
---@param dbal table DBAL client instance
|
|
---@param threadId string
|
|
---@param incrementView boolean|nil Increment view count
|
|
---@return table|nil Thread
|
|
function M.getThread(dbal, threadId, incrementView)
|
|
local thread = dbal:read(entity('ForumThread'), threadId)
|
|
|
|
if thread and incrementView then
|
|
dbal:update(entity('ForumThread'), threadId, {
|
|
viewCount = (thread.viewCount or 0) + 1,
|
|
})
|
|
thread.viewCount = (thread.viewCount or 0) + 1
|
|
end
|
|
|
|
return thread
|
|
end
|
|
|
|
---Update thread
|
|
---@param dbal table
|
|
---@param threadId string
|
|
---@param updates table
|
|
---@return table Updated thread
|
|
function M.updateThread(dbal, threadId, updates)
|
|
updates.updatedAt = os.time() * 1000
|
|
return dbal:update(entity('ForumThread'), threadId, updates)
|
|
end
|
|
end
|
|
|
|
---Pin/unpin a thread
|
|
---@param dbal table
|
|
---@param threadId string
|
|
---@param isPinned boolean
|
|
function M.pinThread(dbal, threadId, isPinned)
|
|
return M.updateThread(dbal, threadId, { isPinned = isPinned })
|
|
end
|
|
|
|
---Lock/unlock a thread
|
|
---@param dbal table
|
|
---@param threadId string
|
|
---@param isLocked boolean
|
|
function M.lockThread(dbal, threadId, isLocked)
|
|
return M.updateThread(dbal, threadId, { isLocked = isLocked })
|
|
end
|
|
|
|
---------------------------------------------------------------------------
|
|
-- POST OPERATIONS
|
|
---------------------------------------------------------------------------
|
|
|
|
---@class ForumPostCreateParams
|
|
---@field tenantId string
|
|
---@field threadId string
|
|
---@field authorId string
|
|
---@field authorName string
|
|
---@field content string
|
|
---@field replyToId string|nil
|
|
|
|
---Create a new post (reply)
|
|
---@param dbal table DBAL client instance
|
|
---@param params ForumPostCreateParams
|
|
---@return table Created post
|
|
function M.createPost(dbal, params)
|
|
local thread = dbal:read(entity('ForumThread'), params.threadId)
|
|
if not thread then
|
|
error('Thread not found: ' .. params.threadId)
|
|
end
|
|
|
|
if thread.isLocked then
|
|
error('Thread is locked')
|
|
end
|
|
|
|
local post = dbal:create(entity('ForumPost'), {
|
|
tenantId = params.tenantId,
|
|
threadId = params.threadId,
|
|
authorId = params.authorId,
|
|
authorName = params.authorName,
|
|
content = params.content,
|
|
replyToId = params.replyToId,
|
|
isEdited = false,
|
|
likeCount = 0,
|
|
createdAt = os.time() * 1000,
|
|
updatedAt = os.time() * 1000,
|
|
})
|
|
|
|
-- Update thread stats
|
|
dbal:update(entity('ForumThread'), params.threadId, {
|
|
replyCount = (thread.replyCount or 0) + 1,
|
|
lastPostAt = os.time() * 1000,
|
|
lastPostAuthorId = params.authorId,
|
|
lastPostAuthorName = params.authorName,
|
|
updatedAt = os.time() * 1000,
|
|
})
|
|
|
|
-- Update category post count
|
|
local category = dbal:read(entity('ForumCategory'), thread.categoryId)
|
|
if category then
|
|
dbal:update(entity('ForumCategory'), thread.categoryId, {
|
|
postCount = (category.postCount or 0) + 1,
|
|
updatedAt = os.time() * 1000,
|
|
})
|
|
end
|
|
|
|
return post
|
|
end
|
|
|
|
---List posts in a thread
|
|
---@param dbal table DBAL client instance
|
|
---@param threadId string
|
|
---@param take number|nil
|
|
---@param skip number|nil
|
|
---@return table List result
|
|
function M.listPosts(dbal, threadId, take, skip)
|
|
return dbal:list(entity('ForumPost'), {
|
|
where = { threadId = threadId },
|
|
orderBy = { createdAt = 'asc' },
|
|
take = take or 50,
|
|
skip = skip or 0,
|
|
})
|
|
end
|
|
|
|
---Update post content
|
|
---@param dbal table
|
|
---@param postId string
|
|
---@param content string
|
|
---@return table Updated post
|
|
function M.updatePost(dbal, postId, content)
|
|
return dbal:update(entity('ForumPost'), postId, {
|
|
content = content,
|
|
isEdited = true,
|
|
editedAt = os.time() * 1000,
|
|
updatedAt = os.time() * 1000,
|
|
})
|
|
end
|
|
|
|
---Like a post
|
|
---@param dbal table
|
|
---@param postId string
|
|
---@return table Updated post
|
|
function M.likePost(dbal, postId)
|
|
local post = dbal:read(entity('ForumPost'), postId)
|
|
if post then
|
|
return dbal:update(entity('ForumPost'), postId, {
|
|
likeCount = (post.likeCount or 0) + 1,
|
|
})
|
|
end
|
|
return post
|
|
end
|
|
|
|
---Delete a post (soft delete by clearing content)
|
|
---@param dbal table
|
|
---@param postId string
|
|
---@param deletedBy string Username who deleted
|
|
---@return table Updated post
|
|
function M.deletePost(dbal, postId, deletedBy)
|
|
return dbal:update(entity('ForumPost'), postId, {
|
|
content = '[Deleted by ' .. deletedBy .. ']',
|
|
isEdited = true,
|
|
editedAt = os.time() * 1000,
|
|
updatedAt = os.time() * 1000,
|
|
})
|
|
end
|
|
end
|
|
|
|
---------------------------------------------------------------------------
|
|
-- UTILITY FUNCTIONS
|
|
---------------------------------------------------------------------------
|
|
|
|
---Generate a URL-safe slug from text
|
|
---@param text string
|
|
---@return string Slug
|
|
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, 100)
|
|
end
|
|
|
|
---Search threads by title
|
|
---@param dbal table
|
|
---@param tenantId string
|
|
---@param query string
|
|
---@param take number|nil
|
|
---@return table[] Matching threads
|
|
function M.searchThreads(dbal, tenantId, query, take)
|
|
-- Basic implementation - fetch and filter
|
|
-- TODO: Implement proper full-text search via DBAL
|
|
local result = dbal:list(entity('ForumThread'), {
|
|
where = { tenantId = tenantId },
|
|
take = 1000,
|
|
})
|
|
|
|
local matches = {}
|
|
local lowerQuery = query:lower()
|
|
|
|
for _, thread in ipairs(result.items or {}) do
|
|
if thread.title:lower():find(lowerQuery, 1, true) then
|
|
table.insert(matches, thread)
|
|
if #matches >= (take or 20) then
|
|
break
|
|
end
|
|
end
|
|
end
|
|
|
|
return matches
|
|
end
|
|
|
|
return M
|