From 009a985896dd3dc06291ec2f604f081b031300f5 Mon Sep 17 00:00:00 2001 From: Richard Ward Date: Tue, 30 Dec 2025 22:06:55 +0000 Subject: [PATCH] docs: packages,operations,lua (6 files) --- EXECUTION_TRACKER.md | 30 +- .../seed/scripts/db/operations.lua | 359 ++++++++++++++++ .../seed/scripts/db/operations.lua | 335 +++++++++++++++ .../seed/scripts/db/operations.lua | 312 ++++++++++++++ .../social_hub/seed/scripts/db/operations.lua | 398 ++++++++++++++++++ storybook/src/components/registry.tsx | 28 ++ 6 files changed, 1456 insertions(+), 6 deletions(-) create mode 100644 packages/code_editor/seed/scripts/db/operations.lua create mode 100644 packages/package_validator/seed/scripts/db/operations.lua create mode 100644 packages/screenshot_analyzer/seed/scripts/db/operations.lua create mode 100644 packages/social_hub/seed/scripts/db/operations.lua diff --git a/EXECUTION_TRACKER.md b/EXECUTION_TRACKER.md index 0b4b958cb..074fb6f88 100644 --- a/EXECUTION_TRACKER.md +++ b/EXECUTION_TRACKER.md @@ -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) diff --git a/packages/code_editor/seed/scripts/db/operations.lua b/packages/code_editor/seed/scripts/db/operations.lua new file mode 100644 index 000000000..4337c93ab --- /dev/null +++ b/packages/code_editor/seed/scripts/db/operations.lua @@ -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 diff --git a/packages/package_validator/seed/scripts/db/operations.lua b/packages/package_validator/seed/scripts/db/operations.lua new file mode 100644 index 000000000..a8199e93f --- /dev/null +++ b/packages/package_validator/seed/scripts/db/operations.lua @@ -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 diff --git a/packages/screenshot_analyzer/seed/scripts/db/operations.lua b/packages/screenshot_analyzer/seed/scripts/db/operations.lua new file mode 100644 index 000000000..de0253cfe --- /dev/null +++ b/packages/screenshot_analyzer/seed/scripts/db/operations.lua @@ -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 diff --git a/packages/social_hub/seed/scripts/db/operations.lua b/packages/social_hub/seed/scripts/db/operations.lua new file mode 100644 index 000000000..3a36f151d --- /dev/null +++ b/packages/social_hub/seed/scripts/db/operations.lua @@ -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 diff --git a/storybook/src/components/registry.tsx b/storybook/src/components/registry.tsx index 0621e855f..5a4212eac 100644 --- a/storybook/src/components/registry.tsx +++ b/storybook/src/components/registry.tsx @@ -54,6 +54,31 @@ export const CardActions: React.FC = ({ className = 'p-6 pt-0
{children}
) +export const CardDescription: React.FC = ({ className = 'text-sm text-muted-foreground', children }) => ( +

{children}

+) + +// ScrollArea - scrollable container +export const ScrollArea: React.FC = ({ + className = 'overflow-auto', + maxHeight, + children +}) => ( +
{children}
+) + +// ComponentRef - placeholder for dynamic component references +export const ComponentRef: React.FC = ({ + ref: refId, + component, + className = 'p-2 border border-dashed rounded bg-muted/30', + children +}) => ( +
+ {children || Component: {component || refId || 'unknown'}} +
+) + export const Paper: React.FC = ({ className = 'rounded border p-4 bg-canvas', children }) => (
{children}
) @@ -520,9 +545,12 @@ export const componentRegistry: Record = { CardHeader, CardContent, CardActions, + CardDescription, CardTitle: CardHeader, CardFooter: CardActions, Paper, + ScrollArea, + ComponentRef, // Typography Typography,