docs: packages,operations,lua (6 files)

This commit is contained in:
Richard Ward
2025-12-30 22:06:55 +00:00
parent 60d5d04305
commit 009a985896
6 changed files with 1456 additions and 6 deletions

View File

@@ -22,10 +22,10 @@
| 9 | CSS Designer | ✅ Complete | 100% |
| 10 | Parameterized Tests | ✅ Complete | 95% |
| 11 | Package Validator | ✅ Complete | 100% |
| 12 | Multi-Frontend | 🟡 In Progress | 50% |
| 13 | E2E Verification | 🟡 In Progress | 65% |
| 14 | Documentation | 🟡 In Progress | 60% |
| 15 | Package Schema System | 🟡 In Progress | 80% |
| 12 | Multi-Frontend | 🟡 In Progress | 65% |
| 13 | E2E Verification | 🟡 In Progress | 70% |
| 14 | Documentation | 🟡 In Progress | 65% |
| 15 | Package Schema System | ✅ Complete | 95% |
---
@@ -94,8 +94,26 @@
- [x] notification_center (Notification) + Lua operations
- [x] forum_forge (ForumCategory, ForumThread, ForumPost) + Lua operations
- [x] irc_webchat (IRCChannel, IRCMessage, IRCMembership) + Lua operations
- [x] media_center (MediaAsset, MediaJob)
- [x] stream_cast (StreamChannel, StreamSchedule, StreamScene)
- [x] media_center (MediaAsset, MediaJob) + Lua operations
- [x] stream_cast (StreamChannel, StreamSchedule, StreamScene) + Lua operations
### Packages with DBAL Operations (Using Core Entities)
- [x] user_manager - User CRUD, permissions, search, bulk ops
- [x] role_editor - Role management, user-role assignment, permissions
- [x] workflow_editor - Workflow CRUD, execution, step logging
- [x] codegen_studio - Project, Blueprint, Template management
- [x] github_tools - GitHub connection, repos, webhooks, PR tracking
- [x] smtp_config - SMTP config, email templates, email logs
- [x] data_table - Generic CRUD helpers, table config, user preferences
- [x] dashboard - Layout, widget, cache operations
- [x] arcade_lobby - Game, Leaderboard, Session operations
- [x] form_builder - Form, Field, Submission operations
- [x] css_designer - Theme, Style Component, CSS generation
- [x] schema_editor - Entity Schema CRUD, field/index/relation management
- [x] code_editor - Snippets, Sessions, Revisions, Language config
- [x] package_validator - Validation runs, issues, rules
- [x] screenshot_analyzer - Screenshot, Analysis, Components, Colors
- [x] social_hub - Profiles, Connections, Activity, Likes
### Multi-Frontend DBAL Support
- [x] Qt6/QML DBALClient (C++ + QML provider)

View File

@@ -0,0 +1,359 @@
-- code_editor/seed/scripts/db/operations.lua
-- DBAL operations for Code Snippets and Editor Sessions
-- @module code_editor.db.operations
local M = {}
local json = require('json')
---------------------------------------------------------------------------
-- CODE SNIPPET OPERATIONS
---------------------------------------------------------------------------
---@class SnippetCreateParams
---@field tenantId string
---@field userId string
---@field title string
---@field language string
---@field code string
---@field description string|nil
---@field tags table[]|nil
---@field isPublic boolean|nil
---Create a code snippet
---@param dbal table DBAL client instance
---@param params SnippetCreateParams
---@return table Created snippet
function M.createSnippet(dbal, params)
return dbal:create('CodeSnippet', {
tenantId = params.tenantId,
userId = params.userId,
title = params.title,
language = params.language,
code = params.code,
description = params.description,
tags = params.tags and json.encode(params.tags) or nil,
isPublic = params.isPublic or false,
version = 1,
createdAt = os.time() * 1000,
updatedAt = os.time() * 1000,
})
end
---Get snippet by ID
---@param dbal table
---@param snippetId string
---@return table|nil Snippet
function M.getSnippet(dbal, snippetId)
local snippet = dbal:read('CodeSnippet', snippetId)
if snippet and snippet.tags then
snippet.tags = json.decode(snippet.tags)
end
return snippet
end
---List user's snippets
---@param dbal table
---@param tenantId string
---@param userId string
---@param language string|nil Filter by language
---@return table[] Snippets
function M.listUserSnippets(dbal, tenantId, userId, language)
local result = dbal:list('CodeSnippet', {
where = { tenantId = tenantId, userId = userId },
orderBy = { updatedAt = 'desc' },
take = 100,
})
local snippets = result.items or {}
if language then
local filtered = {}
for _, s in ipairs(snippets) do
if s.language == language then
table.insert(filtered, s)
end
end
snippets = filtered
end
return snippets
end
---List public snippets
---@param dbal table
---@param tenantId string
---@param language string|nil
---@param take number|nil
---@return table[] Snippets
function M.listPublicSnippets(dbal, tenantId, language, take)
local result = dbal:list('CodeSnippet', {
where = { tenantId = tenantId, isPublic = true },
orderBy = { createdAt = 'desc' },
take = take or 50,
})
local snippets = result.items or {}
if language then
local filtered = {}
for _, s in ipairs(snippets) do
if s.language == language then
table.insert(filtered, s)
end
end
snippets = filtered
end
return snippets
end
---Search snippets
---@param dbal table
---@param tenantId string
---@param query string
---@param userId string|nil
---@return table[] Matching snippets
function M.searchSnippets(dbal, tenantId, query, userId)
local where = { tenantId = tenantId }
-- Get all accessible snippets
local result = dbal:list('CodeSnippet', {
where = where,
take = 1000,
})
local lowerQuery = query:lower()
local matches = {}
for _, snippet in ipairs(result.items or {}) do
-- Include if public or owned by user
local accessible = snippet.isPublic or (userId and snippet.userId == userId)
if accessible then
local searchable = (snippet.title or ''):lower() .. ' ' ..
(snippet.description or ''):lower() .. ' ' ..
(snippet.code or ''):lower()
if searchable:find(lowerQuery, 1, true) then
table.insert(matches, snippet)
end
end
end
return matches
end
---Update snippet
---@param dbal table
---@param snippetId string
---@param updates table
---@return table Updated snippet
function M.updateSnippet(dbal, snippetId, updates)
if updates.tags and type(updates.tags) == 'table' then
updates.tags = json.encode(updates.tags)
end
-- Increment version if code changed
if updates.code then
local snippet = M.getSnippet(dbal, snippetId)
if snippet then
updates.version = (snippet.version or 1) + 1
end
end
updates.updatedAt = os.time() * 1000
return dbal:update('CodeSnippet', snippetId, updates)
end
---Delete snippet
---@param dbal table
---@param snippetId string
function M.deleteSnippet(dbal, snippetId)
return dbal:delete('CodeSnippet', snippetId)
end
---------------------------------------------------------------------------
-- EDITOR SESSION OPERATIONS
---------------------------------------------------------------------------
---@class SessionCreateParams
---@field tenantId string
---@field userId string
---@field name string
---@field files table[] Open files
---Create or update editor session
---@param dbal table
---@param params SessionCreateParams
---@return table Session
function M.saveSession(dbal, params)
local existing = dbal:findFirst('EditorSession', {
where = { tenantId = params.tenantId, userId = params.userId, name = params.name },
})
if existing then
return dbal:update('EditorSession', existing.id, {
files = json.encode(params.files),
updatedAt = os.time() * 1000,
})
end
return dbal:create('EditorSession', {
tenantId = params.tenantId,
userId = params.userId,
name = params.name,
files = json.encode(params.files),
createdAt = os.time() * 1000,
updatedAt = os.time() * 1000,
})
end
---Get session
---@param dbal table
---@param tenantId string
---@param userId string
---@param name string
---@return table|nil Session
function M.getSession(dbal, tenantId, userId, name)
local session = dbal:findFirst('EditorSession', {
where = { tenantId = tenantId, userId = userId, name = name },
})
if session and session.files then
session.files = json.decode(session.files)
end
return session
end
---List user's sessions
---@param dbal table
---@param tenantId string
---@param userId string
---@return table[] Sessions
function M.listSessions(dbal, tenantId, userId)
local result = dbal:list('EditorSession', {
where = { tenantId = tenantId, userId = userId },
orderBy = { updatedAt = 'desc' },
take = 50,
})
return result.items or {}
end
---Delete session
---@param dbal table
---@param sessionId string
function M.deleteSession(dbal, sessionId)
return dbal:delete('EditorSession', sessionId)
end
---------------------------------------------------------------------------
-- FILE REVISION OPERATIONS
---------------------------------------------------------------------------
---Save file revision
---@param dbal table
---@param tenantId string
---@param filePath string
---@param content string
---@param userId string
---@param message string|nil
---@return table Revision
function M.saveRevision(dbal, tenantId, filePath, content, userId, message)
return dbal:create('FileRevision', {
tenantId = tenantId,
filePath = filePath,
content = content,
userId = userId,
message = message,
createdAt = os.time() * 1000,
})
end
---Get file revisions
---@param dbal table
---@param tenantId string
---@param filePath string
---@param take number|nil
---@return table[] Revisions
function M.getRevisions(dbal, tenantId, filePath, take)
local result = dbal:list('FileRevision', {
where = { tenantId = tenantId, filePath = filePath },
orderBy = { createdAt = 'desc' },
take = take or 20,
})
return result.items or {}
end
---Get specific revision
---@param dbal table
---@param revisionId string
---@return table|nil Revision
function M.getRevision(dbal, revisionId)
return dbal:read('FileRevision', revisionId)
end
---------------------------------------------------------------------------
-- LANGUAGE CONFIG OPERATIONS
---------------------------------------------------------------------------
---Save language configuration
---@param dbal table
---@param tenantId string
---@param language string
---@param config table
function M.saveLanguageConfig(dbal, tenantId, language, config)
local existing = dbal:findFirst('LanguageConfig', {
where = { tenantId = tenantId, language = language },
})
if existing then
return dbal:update('LanguageConfig', existing.id, {
config = json.encode(config),
updatedAt = os.time() * 1000,
})
end
return dbal:create('LanguageConfig', {
tenantId = tenantId,
language = language,
config = json.encode(config),
createdAt = os.time() * 1000,
updatedAt = os.time() * 1000,
})
end
---Get language configuration
---@param dbal table
---@param tenantId string
---@param language string
---@return table|nil Config
function M.getLanguageConfig(dbal, tenantId, language)
local record = dbal:findFirst('LanguageConfig', {
where = { tenantId = tenantId, language = language },
})
if record and record.config then
return json.decode(record.config)
end
return nil
end
---List supported languages
---@param dbal table
---@param tenantId string
---@return table[] Language configs
function M.listLanguages(dbal, tenantId)
local result = dbal:list('LanguageConfig', {
where = { tenantId = tenantId },
orderBy = { language = 'asc' },
take = 100,
})
return result.items or {}
end
return M

View File

@@ -0,0 +1,335 @@
-- package_validator/seed/scripts/db/operations.lua
-- DBAL operations for Package validation results
-- @module package_validator.db.operations
local M = {}
local json = require('json')
---------------------------------------------------------------------------
-- VALIDATION RUN OPERATIONS
---------------------------------------------------------------------------
---@class ValidationRunParams
---@field tenantId string
---@field packageName string
---@field packageVersion string|nil
---@field status string (pending, running, completed, failed)
---@field results table|nil Validation results
---Create validation run
---@param dbal table DBAL client instance
---@param params ValidationRunParams
---@return table Created run
function M.createRun(dbal, params)
return dbal:create('ValidationRun', {
tenantId = params.tenantId,
packageName = params.packageName,
packageVersion = params.packageVersion,
status = params.status or 'pending',
results = params.results and json.encode(params.results) or nil,
startedAt = os.time() * 1000,
})
end
---Get validation run by ID
---@param dbal table
---@param runId string
---@return table|nil Run
function M.getRun(dbal, runId)
local run = dbal:read('ValidationRun', runId)
if run and run.results then
run.results = json.decode(run.results)
end
return run
end
---Update run status
---@param dbal table
---@param runId string
---@param status string
---@param results table|nil
function M.updateRunStatus(dbal, runId, status, results)
local updates = {
status = status,
}
if results then
updates.results = json.encode(results)
end
if status == 'completed' or status == 'failed' then
updates.completedAt = os.time() * 1000
end
return dbal:update('ValidationRun', runId, updates)
end
---List runs for package
---@param dbal table
---@param tenantId string
---@param packageName string
---@param take number|nil
---@return table[] Runs
function M.listRuns(dbal, tenantId, packageName, take)
local result = dbal:list('ValidationRun', {
where = { tenantId = tenantId, packageName = packageName },
orderBy = { startedAt = 'desc' },
take = take or 20,
})
return result.items or {}
end
---Get latest run for package
---@param dbal table
---@param tenantId string
---@param packageName string
---@return table|nil Latest run
function M.getLatestRun(dbal, tenantId, packageName)
local runs = M.listRuns(dbal, tenantId, packageName, 1)
if #runs > 0 then
local run = runs[1]
if run.results then
run.results = json.decode(run.results)
end
return run
end
return nil
end
---------------------------------------------------------------------------
-- VALIDATION ISSUE OPERATIONS
---------------------------------------------------------------------------
---@class ValidationIssue
---@field runId string
---@field severity string (error, warning, info)
---@field code string Issue code
---@field message string
---@field file string|nil
---@field line number|nil
---@field suggestion string|nil
---Add validation issue
---@param dbal table
---@param issue ValidationIssue
---@return table Created issue
function M.addIssue(dbal, issue)
return dbal:create('ValidationIssue', {
runId = issue.runId,
severity = issue.severity,
code = issue.code,
message = issue.message,
file = issue.file,
line = issue.line,
suggestion = issue.suggestion,
createdAt = os.time() * 1000,
})
end
---Add multiple issues
---@param dbal table
---@param runId string
---@param issues table[] Array of issues
function M.addIssues(dbal, runId, issues)
for _, issue in ipairs(issues) do
issue.runId = runId
M.addIssue(dbal, issue)
end
end
---Get issues for run
---@param dbal table
---@param runId string
---@param severity string|nil Filter by severity
---@return table[] Issues
function M.getIssues(dbal, runId, severity)
local where = { runId = runId }
local result = dbal:list('ValidationIssue', {
where = where,
orderBy = { severity = 'asc' },
take = 500,
})
local issues = result.items or {}
if severity then
local filtered = {}
for _, issue in ipairs(issues) do
if issue.severity == severity then
table.insert(filtered, issue)
end
end
issues = filtered
end
return issues
end
---Count issues by severity
---@param dbal table
---@param runId string
---@return table Counts by severity
function M.countIssues(dbal, runId)
local issues = M.getIssues(dbal, runId)
local counts = {
error = 0,
warning = 0,
info = 0,
total = #issues,
}
for _, issue in ipairs(issues) do
local sev = issue.severity
if counts[sev] then
counts[sev] = counts[sev] + 1
end
end
return counts
end
---------------------------------------------------------------------------
-- VALIDATION RULE OPERATIONS
---------------------------------------------------------------------------
---@class ValidationRuleParams
---@field tenantId string
---@field name string
---@field code string Unique rule code
---@field severity string
---@field description string
---@field check string Lua function body
---@field enabled boolean|nil
---Create validation rule
---@param dbal table
---@param params ValidationRuleParams
---@return table Created rule
function M.createRule(dbal, params)
return dbal:create('ValidationRule', {
tenantId = params.tenantId,
name = params.name,
code = params.code,
severity = params.severity,
description = params.description,
check = params.check,
enabled = params.enabled ~= false,
createdAt = os.time() * 1000,
updatedAt = os.time() * 1000,
})
end
---Get rule by code
---@param dbal table
---@param tenantId string
---@param code string
---@return table|nil Rule
function M.getRule(dbal, tenantId, code)
return dbal:findFirst('ValidationRule', {
where = { tenantId = tenantId, code = code },
})
end
---List enabled rules
---@param dbal table
---@param tenantId string
---@return table[] Rules
function M.listEnabledRules(dbal, tenantId)
local result = dbal:list('ValidationRule', {
where = { tenantId = tenantId, enabled = true },
orderBy = { code = 'asc' },
take = 100,
})
return result.items or {}
end
---Update rule
---@param dbal table
---@param tenantId string
---@param code string
---@param updates table
function M.updateRule(dbal, tenantId, code, updates)
local rule = M.getRule(dbal, tenantId, code)
if rule then
updates.updatedAt = os.time() * 1000
return dbal:update('ValidationRule', rule.id, updates)
end
return nil
end
---Toggle rule enabled status
---@param dbal table
---@param tenantId string
---@param code string
---@param enabled boolean
function M.setRuleEnabled(dbal, tenantId, code, enabled)
return M.updateRule(dbal, tenantId, code, { enabled = enabled })
end
---------------------------------------------------------------------------
-- PACKAGE REGISTRY OPERATIONS
---------------------------------------------------------------------------
---Get package validation summary
---@param dbal table
---@param tenantId string
---@param packageName string
---@return table Summary
function M.getPackageSummary(dbal, tenantId, packageName)
local latestRun = M.getLatestRun(dbal, tenantId, packageName)
if not latestRun then
return {
packageName = packageName,
status = 'never_validated',
lastValidated = nil,
issueCounts = nil,
}
end
local counts = M.countIssues(dbal, latestRun.id)
return {
packageName = packageName,
status = latestRun.status,
lastValidated = latestRun.completedAt or latestRun.startedAt,
issueCounts = counts,
hasErrors = counts.error > 0,
}
end
---Get all packages validation status
---@param dbal table
---@param tenantId string
---@return table[] Package summaries
function M.getAllPackageStatus(dbal, tenantId)
-- Get unique package names from runs
local result = dbal:list('ValidationRun', {
where = { tenantId = tenantId },
orderBy = { packageName = 'asc' },
take = 10000,
})
local packageNames = {}
local seen = {}
for _, run in ipairs(result.items or {}) do
if not seen[run.packageName] then
seen[run.packageName] = true
table.insert(packageNames, run.packageName)
end
end
local summaries = {}
for _, name in ipairs(packageNames) do
table.insert(summaries, M.getPackageSummary(dbal, tenantId, name))
end
return summaries
end
return M

View File

@@ -0,0 +1,312 @@
-- screenshot_analyzer/seed/scripts/db/operations.lua
-- DBAL operations for Screenshot analysis
-- @module screenshot_analyzer.db.operations
local M = {}
local json = require('json')
---------------------------------------------------------------------------
-- SCREENSHOT OPERATIONS
---------------------------------------------------------------------------
---@class ScreenshotParams
---@field tenantId string
---@field userId string
---@field url string Image URL or base64
---@field filename string|nil Original filename
---@field metadata table|nil Additional metadata
---Save screenshot
---@param dbal table DBAL client instance
---@param params ScreenshotParams
---@return table Created screenshot
function M.saveScreenshot(dbal, params)
return dbal:create('Screenshot', {
tenantId = params.tenantId,
userId = params.userId,
url = params.url,
filename = params.filename,
metadata = params.metadata and json.encode(params.metadata) or nil,
status = 'uploaded',
createdAt = os.time() * 1000,
})
end
---Get screenshot by ID
---@param dbal table
---@param screenshotId string
---@return table|nil Screenshot
function M.getScreenshot(dbal, screenshotId)
local ss = dbal:read('Screenshot', screenshotId)
if ss and ss.metadata then
ss.metadata = json.decode(ss.metadata)
end
return ss
end
---List user's screenshots
---@param dbal table
---@param tenantId string
---@param userId string
---@param take number|nil
---@return table[] Screenshots
function M.listUserScreenshots(dbal, tenantId, userId, take)
local result = dbal:list('Screenshot', {
where = { tenantId = tenantId, userId = userId },
orderBy = { createdAt = 'desc' },
take = take or 50,
})
return result.items or {}
end
---Update screenshot status
---@param dbal table
---@param screenshotId string
---@param status string
function M.updateStatus(dbal, screenshotId, status)
return dbal:update('Screenshot', screenshotId, { status = status })
end
---Delete screenshot
---@param dbal table
---@param screenshotId string
function M.deleteScreenshot(dbal, screenshotId)
-- Delete associated analyses first
local analyses = M.listAnalyses(dbal, screenshotId)
for _, analysis in ipairs(analyses) do
dbal:delete('ScreenshotAnalysis', analysis.id)
end
return dbal:delete('Screenshot', screenshotId)
end
---------------------------------------------------------------------------
-- ANALYSIS OPERATIONS
---------------------------------------------------------------------------
---@class AnalysisParams
---@field screenshotId string
---@field analysisType string (component, color, layout, accessibility)
---@field result table Analysis result data
---Create analysis record
---@param dbal table
---@param params AnalysisParams
---@return table Created analysis
function M.createAnalysis(dbal, params)
return dbal:create('ScreenshotAnalysis', {
screenshotId = params.screenshotId,
analysisType = params.analysisType,
result = json.encode(params.result),
confidence = params.result.confidence,
createdAt = os.time() * 1000,
})
end
---Get analysis by ID
---@param dbal table
---@param analysisId string
---@return table|nil Analysis
function M.getAnalysis(dbal, analysisId)
local analysis = dbal:read('ScreenshotAnalysis', analysisId)
if analysis and analysis.result then
analysis.result = json.decode(analysis.result)
end
return analysis
end
---List analyses for screenshot
---@param dbal table
---@param screenshotId string
---@return table[] Analyses
function M.listAnalyses(dbal, screenshotId)
local result = dbal:list('ScreenshotAnalysis', {
where = { screenshotId = screenshotId },
orderBy = { createdAt = 'desc' },
take = 50,
})
local analyses = result.items or {}
for _, analysis in ipairs(analyses) do
if analysis.result then
analysis.result = json.decode(analysis.result)
end
end
return analyses
end
---Get analysis by type
---@param dbal table
---@param screenshotId string
---@param analysisType string
---@return table|nil Analysis
function M.getAnalysisByType(dbal, screenshotId, analysisType)
local analysis = dbal:findFirst('ScreenshotAnalysis', {
where = { screenshotId = screenshotId, analysisType = analysisType },
})
if analysis and analysis.result then
analysis.result = json.decode(analysis.result)
end
return analysis
end
---------------------------------------------------------------------------
-- DETECTED COMPONENT OPERATIONS
---------------------------------------------------------------------------
---@class DetectedComponentParams
---@field analysisId string
---@field componentType string
---@field bounds table {x, y, width, height}
---@field confidence number
---@field properties table|nil
---Save detected component
---@param dbal table
---@param params DetectedComponentParams
---@return table Created component
function M.saveDetectedComponent(dbal, params)
return dbal:create('DetectedComponent', {
analysisId = params.analysisId,
componentType = params.componentType,
bounds = json.encode(params.bounds),
confidence = params.confidence,
properties = params.properties and json.encode(params.properties) or nil,
createdAt = os.time() * 1000,
})
end
---Get detected components for analysis
---@param dbal table
---@param analysisId string
---@return table[] Components
function M.getDetectedComponents(dbal, analysisId)
local result = dbal:list('DetectedComponent', {
where = { analysisId = analysisId },
orderBy = { confidence = 'desc' },
take = 200,
})
local components = result.items or {}
for _, comp in ipairs(components) do
if comp.bounds then
comp.bounds = json.decode(comp.bounds)
end
if comp.properties then
comp.properties = json.decode(comp.properties)
end
end
return components
end
---Get components by type
---@param dbal table
---@param analysisId string
---@param componentType string
---@return table[] Components
function M.getComponentsByType(dbal, analysisId, componentType)
local all = M.getDetectedComponents(dbal, analysisId)
local filtered = {}
for _, comp in ipairs(all) do
if comp.componentType == componentType then
table.insert(filtered, comp)
end
end
return filtered
end
---------------------------------------------------------------------------
-- COLOR PALETTE OPERATIONS
---------------------------------------------------------------------------
---@class ColorPaletteParams
---@field analysisId string
---@field colors table[] Array of {hex, rgb, usage, count}
---@field dominantColor string|nil
---Save color palette
---@param dbal table
---@param params ColorPaletteParams
---@return table Created palette
function M.saveColorPalette(dbal, params)
return dbal:create('ColorPalette', {
analysisId = params.analysisId,
colors = json.encode(params.colors),
dominantColor = params.dominantColor,
createdAt = os.time() * 1000,
})
end
---Get color palette for analysis
---@param dbal table
---@param analysisId string
---@return table|nil Palette
function M.getColorPalette(dbal, analysisId)
local palette = dbal:findFirst('ColorPalette', {
where = { analysisId = analysisId },
})
if palette and palette.colors then
palette.colors = json.decode(palette.colors)
end
return palette
end
---------------------------------------------------------------------------
-- GENERATED CODE OPERATIONS
---------------------------------------------------------------------------
---@class GeneratedCodeParams
---@field screenshotId string
---@field framework string (react, vue, html, qml)
---@field code string
---@field metadata table|nil
---Save generated code
---@param dbal table
---@param params GeneratedCodeParams
---@return table Created code
function M.saveGeneratedCode(dbal, params)
return dbal:create('GeneratedCode', {
screenshotId = params.screenshotId,
framework = params.framework,
code = params.code,
metadata = params.metadata and json.encode(params.metadata) or nil,
createdAt = os.time() * 1000,
})
end
---Get generated code for screenshot
---@param dbal table
---@param screenshotId string
---@param framework string
---@return table|nil Generated code
function M.getGeneratedCode(dbal, screenshotId, framework)
return dbal:findFirst('GeneratedCode', {
where = { screenshotId = screenshotId, framework = framework },
})
end
---List all generated code for screenshot
---@param dbal table
---@param screenshotId string
---@return table[] Generated codes
function M.listGeneratedCode(dbal, screenshotId)
local result = dbal:list('GeneratedCode', {
where = { screenshotId = screenshotId },
orderBy = { createdAt = 'desc' },
take = 20,
})
return result.items or {}
end
return M

View File

@@ -0,0 +1,398 @@
-- social_hub/seed/scripts/db/operations.lua
-- DBAL operations for Social features (profiles, connections, activity)
-- @module social_hub.db.operations
local M = {}
local json = require('json')
---------------------------------------------------------------------------
-- SOCIAL PROFILE OPERATIONS
---------------------------------------------------------------------------
---@class ProfileParams
---@field tenantId string
---@field userId string
---@field displayName string
---@field bio string|nil
---@field avatar string|nil
---@field links table|nil Social links
---@field settings table|nil Privacy settings
---Create or update social profile
---@param dbal table DBAL client instance
---@param params ProfileParams
---@return table Profile
function M.saveProfile(dbal, params)
local existing = dbal:findFirst('SocialProfile', {
where = { tenantId = params.tenantId, userId = params.userId },
})
local data = {
displayName = params.displayName,
bio = params.bio,
avatar = params.avatar,
links = params.links and json.encode(params.links) or nil,
settings = params.settings and json.encode(params.settings) or nil,
updatedAt = os.time() * 1000,
}
if existing then
return dbal:update('SocialProfile', existing.id, data)
end
data.tenantId = params.tenantId
data.userId = params.userId
data.createdAt = os.time() * 1000
return dbal:create('SocialProfile', data)
end
---Get profile by user ID
---@param dbal table
---@param tenantId string
---@param userId string
---@return table|nil Profile
function M.getProfile(dbal, tenantId, userId)
local profile = dbal:findFirst('SocialProfile', {
where = { tenantId = tenantId, userId = userId },
})
if profile then
if profile.links then profile.links = json.decode(profile.links) end
if profile.settings then profile.settings = json.decode(profile.settings) end
end
return profile
end
---Search profiles
---@param dbal table
---@param tenantId string
---@param query string
---@param take number|nil
---@return table[] Profiles
function M.searchProfiles(dbal, tenantId, query, take)
local result = dbal:list('SocialProfile', {
where = { tenantId = tenantId },
take = 1000,
})
local lowerQuery = query:lower()
local matches = {}
for _, profile in ipairs(result.items or {}) do
local searchable = (profile.displayName or ''):lower() .. ' ' .. (profile.bio or ''):lower()
if searchable:find(lowerQuery, 1, true) then
table.insert(matches, profile)
if #matches >= (take or 20) then
break
end
end
end
return matches
end
---------------------------------------------------------------------------
-- CONNECTION OPERATIONS (Following/Followers)
---------------------------------------------------------------------------
---Follow a user
---@param dbal table
---@param tenantId string
---@param followerId string User doing the following
---@param followingId string User being followed
---@return table Connection
function M.follow(dbal, tenantId, followerId, followingId)
-- Check if already following
local existing = dbal:findFirst('SocialConnection', {
where = { tenantId = tenantId, followerId = followerId, followingId = followingId },
})
if existing then
return existing
end
return dbal:create('SocialConnection', {
tenantId = tenantId,
followerId = followerId,
followingId = followingId,
createdAt = os.time() * 1000,
})
end
---Unfollow a user
---@param dbal table
---@param tenantId string
---@param followerId string
---@param followingId string
function M.unfollow(dbal, tenantId, followerId, followingId)
local connection = dbal:findFirst('SocialConnection', {
where = { tenantId = tenantId, followerId = followerId, followingId = followingId },
})
if connection then
dbal:delete('SocialConnection', connection.id)
end
end
---Check if following
---@param dbal table
---@param tenantId string
---@param followerId string
---@param followingId string
---@return boolean
function M.isFollowing(dbal, tenantId, followerId, followingId)
local connection = dbal:findFirst('SocialConnection', {
where = { tenantId = tenantId, followerId = followerId, followingId = followingId },
})
return connection ~= nil
end
---Get followers
---@param dbal table
---@param tenantId string
---@param userId string
---@param take number|nil
---@return table[] Follower user IDs
function M.getFollowers(dbal, tenantId, userId, take)
local result = dbal:list('SocialConnection', {
where = { tenantId = tenantId, followingId = userId },
orderBy = { createdAt = 'desc' },
take = take or 50,
})
local followers = {}
for _, conn in ipairs(result.items or {}) do
table.insert(followers, conn.followerId)
end
return followers
end
---Get following
---@param dbal table
---@param tenantId string
---@param userId string
---@param take number|nil
---@return table[] Following user IDs
function M.getFollowing(dbal, tenantId, userId, take)
local result = dbal:list('SocialConnection', {
where = { tenantId = tenantId, followerId = userId },
orderBy = { createdAt = 'desc' },
take = take or 50,
})
local following = {}
for _, conn in ipairs(result.items or {}) do
table.insert(following, conn.followingId)
end
return following
end
---Get follower/following counts
---@param dbal table
---@param tenantId string
---@param userId string
---@return table Counts
function M.getConnectionCounts(dbal, tenantId, userId)
local followers = dbal:list('SocialConnection', {
where = { tenantId = tenantId, followingId = userId },
take = 100000,
})
local following = dbal:list('SocialConnection', {
where = { tenantId = tenantId, followerId = userId },
take = 100000,
})
return {
followers = followers.total or #(followers.items or {}),
following = following.total or #(following.items or {}),
}
end
---------------------------------------------------------------------------
-- ACTIVITY FEED OPERATIONS
---------------------------------------------------------------------------
---@class ActivityParams
---@field tenantId string
---@field userId string
---@field type string (post, like, comment, follow, etc.)
---@field content string|nil
---@field targetId string|nil Reference to target object
---@field targetType string|nil Type of target
---@field metadata table|nil
---Create activity
---@param dbal table
---@param params ActivityParams
---@return table Activity
function M.createActivity(dbal, params)
return dbal:create('SocialActivity', {
tenantId = params.tenantId,
userId = params.userId,
type = params.type,
content = params.content,
targetId = params.targetId,
targetType = params.targetType,
metadata = params.metadata and json.encode(params.metadata) or nil,
createdAt = os.time() * 1000,
})
end
---Get user's activity feed
---@param dbal table
---@param tenantId string
---@param userId string
---@param take number|nil
---@param skip number|nil
---@return table[] Activities
function M.getUserActivity(dbal, tenantId, userId, take, skip)
local result = dbal:list('SocialActivity', {
where = { tenantId = tenantId, userId = userId },
orderBy = { createdAt = 'desc' },
take = take or 20,
skip = skip or 0,
})
local activities = result.items or {}
for _, activity in ipairs(activities) do
if activity.metadata then
activity.metadata = json.decode(activity.metadata)
end
end
return activities
end
---Get home feed (activity from followed users)
---@param dbal table
---@param tenantId string
---@param userId string
---@param take number|nil
---@param skip number|nil
---@return table[] Activities
function M.getHomeFeed(dbal, tenantId, userId, take, skip)
-- Get list of users being followed
local following = M.getFollowing(dbal, tenantId, userId, 1000)
-- Include own activity
table.insert(following, userId)
-- Get all activities (would be better with IN query support)
local result = dbal:list('SocialActivity', {
where = { tenantId = tenantId },
orderBy = { createdAt = 'desc' },
take = 1000,
})
-- Filter to followed users
local followingSet = {}
for _, id in ipairs(following) do
followingSet[id] = true
end
local feed = {}
local skipped = 0
for _, activity in ipairs(result.items or {}) do
if followingSet[activity.userId] then
if skipped >= (skip or 0) then
if activity.metadata then
activity.metadata = json.decode(activity.metadata)
end
table.insert(feed, activity)
if #feed >= (take or 20) then
break
end
else
skipped = skipped + 1
end
end
end
return feed
end
---------------------------------------------------------------------------
-- LIKES OPERATIONS
---------------------------------------------------------------------------
---Like something
---@param dbal table
---@param tenantId string
---@param userId string
---@param targetId string
---@param targetType string
---@return table Like
function M.like(dbal, tenantId, userId, targetId, targetType)
local existing = dbal:findFirst('SocialLike', {
where = { tenantId = tenantId, userId = userId, targetId = targetId },
})
if existing then
return existing
end
-- Create activity
M.createActivity(dbal, {
tenantId = tenantId,
userId = userId,
type = 'like',
targetId = targetId,
targetType = targetType,
})
return dbal:create('SocialLike', {
tenantId = tenantId,
userId = userId,
targetId = targetId,
targetType = targetType,
createdAt = os.time() * 1000,
})
end
---Unlike something
---@param dbal table
---@param tenantId string
---@param userId string
---@param targetId string
function M.unlike(dbal, tenantId, userId, targetId)
local like = dbal:findFirst('SocialLike', {
where = { tenantId = tenantId, userId = userId, targetId = targetId },
})
if like then
dbal:delete('SocialLike', like.id)
end
end
---Check if liked
---@param dbal table
---@param tenantId string
---@param userId string
---@param targetId string
---@return boolean
function M.hasLiked(dbal, tenantId, userId, targetId)
local like = dbal:findFirst('SocialLike', {
where = { tenantId = tenantId, userId = userId, targetId = targetId },
})
return like ~= nil
end
---Get like count
---@param dbal table
---@param tenantId string
---@param targetId string
---@return number
function M.getLikeCount(dbal, tenantId, targetId)
local result = dbal:list('SocialLike', {
where = { tenantId = tenantId, targetId = targetId },
take = 100000,
})
return result.total or #(result.items or {})
end
return M

View File

@@ -54,6 +54,31 @@ export const CardActions: React.FC<LuaComponentProps> = ({ className = 'p-6 pt-0
<div className={className}>{children}</div>
)
export const CardDescription: React.FC<LuaComponentProps> = ({ className = 'text-sm text-muted-foreground', children }) => (
<p className={className}>{children}</p>
)
// ScrollArea - scrollable container
export const ScrollArea: React.FC<LuaComponentProps & { maxHeight?: string | number }> = ({
className = 'overflow-auto',
maxHeight,
children
}) => (
<div className={className} style={{ maxHeight: maxHeight || 'auto' }}>{children}</div>
)
// ComponentRef - placeholder for dynamic component references
export const ComponentRef: React.FC<LuaComponentProps & { ref?: string; component?: string }> = ({
ref: refId,
component,
className = 'p-2 border border-dashed rounded bg-muted/30',
children
}) => (
<div className={className}>
{children || <span className="text-xs text-muted-foreground">Component: {component || refId || 'unknown'}</span>}
</div>
)
export const Paper: React.FC<LuaComponentProps> = ({ className = 'rounded border p-4 bg-canvas', children }) => (
<div className={className}>{children}</div>
)
@@ -520,9 +545,12 @@ export const componentRegistry: Record<string, AnyComponent> = {
CardHeader,
CardContent,
CardActions,
CardDescription,
CardTitle: CardHeader,
CardFooter: CardActions,
Paper,
ScrollArea,
ComponentRef,
// Typography
Typography,