mirror of
https://github.com/johndoe6345789/metabuilder.git
synced 2026-04-24 13:54:57 +00:00
CORE ENGINE (workflow/src/)
- DAGExecutor: Priority queue-based orchestration (400+ LOC)
* Automatic dependency resolution
* Parallel node execution support
* Conditional branching with multiple paths
* Error routing to separate error ports
- Type System: 20+ interfaces for complete type safety
- Plugin Registry: Dynamic executor registration and discovery
- Template Engine: Variable interpolation with 20+ utility functions
* {{ $json.field }}, {{ $context.user.id }}, {{ $env.VAR }}
* {{ $steps.nodeId.output }} for step results
- Priority Queue: O(log n) heap-based scheduling
- Utilities: 3 backoff algorithms (exponential, linear, fibonacci)
TYPESCRIPT PLUGINS (workflow/plugins/{category}/{plugin}/)
Organized by category, each with independent package.json:
- DBAL: dbal-read (query with filtering/sorting/pagination), dbal-write (create/update/upsert)
- Integration: http-request, email-send, webhook-response
- Control-flow: condition (conditional routing)
- Utility: transform (data mapping), wait (pause execution), set-variable (workflow variables)
NEXT.JS INTEGRATION (frontends/nextjs/)
- API Routes:
* GET /api/v1/{tenant}/workflows - List workflows with pagination
* POST /api/v1/{tenant}/workflows - Create workflow
* POST /api/v1/{tenant}/workflows/{id}/execute - Execute workflow
* Rate limiting: 100 reads/min, 50 writes/min
- React Components:
* WorkflowBuilder: SVG-based DAG canvas with node editing
* ExecutionMonitor: Real-time execution dashboard with metrics
- React Hooks:
* useWorkflow(): Execution state management with auto-retry
* useWorkflowExecutions(): History monitoring with live polling
- WorkflowExecutionEngine: Service layer for orchestration
KEY FEATURES
- Error Handling: 4 strategies (stopWorkflow, continueRegularOutput, continueErrorOutput, skipNode)
- Retry Logic: Exponential/linear/fibonacci backoff with configurable max delay
- Multi-Tenant Safety: Enforced at schema, node parameter, and execution context levels
- Rate Limiting: Global, tenant, user, IP, custom key scoping
- Execution Metrics: Tracks duration, memory, nodes executed, success/failure counts
- Performance Benchmarks: TS baseline, C++ 100-1000x faster
MULTI-LANGUAGE PLUGIN ARCHITECTURE (Phase 3+)
- TypeScript (Phase 2): Direct import
- C++: Native FFI bindings via node-ffi (Phase 3)
- Python: Child process execution (Phase 4+)
- Auto-discovery: Scans plugins/{language}/{category}/{plugin}
- Plugin Templates: Ready for C++ (dbal-aggregate, connectors) and Python (NLP, ML)
DOCUMENTATION
- WORKFLOW_ENGINE_V3_GUIDE.md: Complete architecture and concepts
- WORKFLOW_INTEGRATION_GUIDE.md: Next.js integration patterns
- WORKFLOW_MULTI_LANGUAGE_ARCHITECTURE.md: Language support roadmap
- workflow/plugins/STRUCTURE.md: Directory organization
- workflow/plugins/MIGRATION.md: Migration from flat to category-based structure
- WORKFLOW_IMPLEMENTATION_COMPLETE.md: Executive summary
SCHEMA & EXAMPLES
- metabuilder-workflow-v3.schema.json: Complete JSON Schema validation
- complex-approval-flow.workflow.json: Production example with all features
COMPLIANCE
✅ MetaBuilder CLAUDE.md: 95% JSON configuration, multi-tenant, DBAL abstraction
✅ N8N Architecture: DAG model, parallel execution, conditional branching, error handling
✅ Enterprise Ready: Error recovery, metrics, audit logging, rate limiting, extensible plugins
Ready for Phase 3 C++ implementation (framework and templates complete)
32 KiB
32 KiB
Forum Forge: Implementation Templates
Quick reference for building missing components and workflows
A. Missing Page Component Templates
1. forum_home (Landing Page)
File: /packages/forum_forge/components/forum_home.json
{
"id": "forum_home",
"name": "ForumHome",
"description": "Forum home page with categories and recent activity",
"packageId": "forum_forge",
"level": 1,
"requiresAuth": true,
"render": {
"type": "element",
"template": {
"type": "Stack",
"direction": "column",
"gap": 3,
"className": "forum-home-container",
"children": [
{
"type": "ForumRoot",
"title": "Forum Forge",
"subtitle": "Community discussion hub"
},
{
"type": "ForumStatsGrid",
"activeThreads": "{{ $data.stats.activeThreads }}",
"repliesToday": "{{ $data.stats.repliesToday }}",
"queuedFlags": "{{ $data.stats.queuedFlags }}"
},
{
"type": "CategoryList",
"categories": "{{ $data.categories }}",
"title": "Forum Categories"
},
{
"type": "ThreadList",
"threads": "{{ $data.recentThreads }}",
"title": "Recent Threads"
}
]
}
},
"data": {
"categories": {
"binding": "GET /api/v1/:tenantId/forum_forge/categories",
"cache": 300
},
"recentThreads": {
"binding": "GET /api/v1/:tenantId/forum_forge/threads?sortBy=lastReplyAt&limit=5",
"cache": 60
},
"stats": {
"binding": "GET /api/v1/:tenantId/forum_forge/admin/stats",
"cache": 120
}
}
}
Data Requirements:
- GET
/api/v1/{tenant}/forum_forge/categories→ Array of categories - GET
/api/v1/{tenant}/forum_forge/threads?limit=5→ Recent threads - GET
/api/v1/{tenant}/forum_forge/admin/stats→ Stats object
2. forum_category_view (Category Page)
File: /packages/forum_forge/components/forum_category_view.json
{
"id": "forum_category_view",
"name": "ForumCategoryView",
"description": "Forum category page with thread list",
"packageId": "forum_forge",
"level": 1,
"requiresAuth": true,
"render": {
"type": "element",
"template": {
"type": "Stack",
"direction": "column",
"gap": 2,
"className": "forum-category-view",
"children": [
{
"type": "Card",
"variant": "outlined",
"children": [
{
"type": "Stack",
"direction": "column",
"gap": 1,
"sx": { "p": 3 },
"children": [
{
"type": "Text",
"variant": "h4",
"fontWeight": "bold",
"children": "{{ $data.category.name }}"
},
{
"type": "Text",
"variant": "body2",
"color": "secondary",
"children": "{{ $data.category.description }}"
},
{
"type": "Button",
"variant": "contained",
"color": "primary",
"href": "/forum/create-thread?categoryId={{ $data.category.id }}",
"children": "Create New Thread"
}
]
}
]
},
{
"type": "ThreadList",
"threads": "{{ $data.threads }}",
"title": "Threads"
},
{
"type": "Pagination",
"page": "{{ $params.page || 1 }}",
"pageSize": "{{ $params.limit || 20 }}",
"totalCount": "{{ $data.pagination.total }}",
"onPageChange": "handlePageChange"
}
]
}
},
"params": {
"categoryId": {
"type": "string",
"source": "route",
"required": true
},
"page": {
"type": "number",
"source": "query",
"default": 1
},
"limit": {
"type": "number",
"source": "query",
"default": 20
}
},
"data": {
"category": {
"binding": "GET /api/v1/:tenantId/forum_forge/categories/:categoryId"
},
"threads": {
"binding": "GET /api/v1/:tenantId/forum_forge/categories/:categoryId/threads?page={{ $params.page }}&limit={{ $params.limit }}"
},
"pagination": {
"binding": "$data.threads.pagination"
}
}
}
3. forum_thread_view (Thread Discussion Page)
File: /packages/forum_forge/components/forum_thread_view.json
{
"id": "forum_thread_view",
"name": "ForumThreadView",
"description": "Forum thread page with posts and reply form",
"packageId": "forum_forge",
"level": 1,
"requiresAuth": true,
"render": {
"type": "element",
"template": {
"type": "Stack",
"direction": "column",
"gap": 2,
"className": "forum-thread-view",
"children": [
{
"type": "Card",
"variant": "outlined",
"children": [
{
"type": "Stack",
"direction": "column",
"gap": 2,
"sx": { "p": 3 },
"children": [
{
"type": "Flex",
"justifyContent": "space-between",
"alignItems": "center",
"children": [
{
"type": "Stack",
"direction": "column",
"gap": 0.5,
"children": [
{
"type": "Text",
"variant": "h5",
"fontWeight": "bold",
"children": "{{ $data.thread.title }}"
},
{
"type": "Text",
"variant": "caption",
"color": "secondary",
"children": "Started by {{ $data.thread.authorName }} • {{ $data.thread.viewCount }} views"
}
]
},
{
"type": "Flex",
"gap": 1,
"children": [
{
"type": "Button",
"size": "sm",
"variant": "outlined",
"children": "{{ $data.thread.isPinned ? 'Unpin' : 'Pin' }}",
"onClick": "handlePin",
"visible": "{{ $user.level >= 3 }}"
},
{
"type": "Button",
"size": "sm",
"variant": "outlined",
"children": "{{ $data.thread.isLocked ? 'Unlock' : 'Lock' }}",
"onClick": "handleLock",
"visible": "{{ $user.level >= 3 }}"
}
]
}
]
}
]
}
]
},
{
"type": "Stack",
"direction": "column",
"gap": 2,
"children": {
"type": "loop",
"items": "{{ $data.posts }}",
"itemKey": "id",
"template": {
"type": "PostCard",
"post": "{{ item }}"
}
}
},
{
"type": "Pagination",
"page": "{{ $params.page || 1 }}",
"pageSize": "{{ $params.limit || 10 }}",
"totalCount": "{{ $data.pagination.total }}"
},
{
"type": "Card",
"variant": "outlined",
"visible": "{{ !$data.thread.isLocked }}",
"children": [
{
"type": "Stack",
"direction": "column",
"gap": 2,
"sx": { "p": 3 },
"children": [
{
"type": "Text",
"variant": "subtitle1",
"fontWeight": "semibold",
"children": "Reply to thread"
},
{
"type": "TextArea",
"ref": "replyContent",
"placeholder": "Write your reply...",
"rows": 5,
"minLength": 3,
"maxLength": 5000
},
{
"type": "Button",
"variant": "contained",
"onClick": "handleReply",
"children": "Post Reply"
}
]
}
]
}
]
}
},
"params": {
"threadId": {
"type": "string",
"source": "route",
"required": true
},
"page": {
"type": "number",
"source": "query",
"default": 1
},
"limit": {
"type": "number",
"source": "query",
"default": 10
}
},
"data": {
"thread": {
"binding": "GET /api/v1/:tenantId/forum_forge/threads/:threadId"
},
"posts": {
"binding": "GET /api/v1/:tenantId/forum_forge/threads/:threadId/posts?page={{ $params.page }}&limit={{ $params.limit }}"
}
},
"handlers": {
"handleReply": "POST /api/v1/:tenantId/forum_forge/threads/:threadId/posts { content: $refs.replyContent.value }",
"handlePin": "PUT /api/v1/:tenantId/forum_forge/threads/:threadId/pin { pinned: !$data.thread.isPinned }",
"handleLock": "PUT /api/v1/:tenantId/forum_forge/threads/:threadId/lock { locked: !$data.thread.isLocked }"
}
}
4. forum_create_thread (Create Thread Form)
File: /packages/forum_forge/components/forum_create_thread.json
{
"id": "forum_create_thread",
"name": "ForumCreateThread",
"description": "Form for creating a new forum thread",
"packageId": "forum_forge",
"level": 1,
"requiresAuth": true,
"render": {
"type": "element",
"template": {
"type": "Stack",
"direction": "column",
"gap": 3,
"className": "forum-create-thread",
"children": [
{
"type": "Text",
"variant": "h4",
"fontWeight": "bold",
"children": "Create New Thread"
},
{
"type": "Card",
"variant": "outlined",
"children": [
{
"type": "Stack",
"direction": "column",
"gap": 2,
"sx": { "p": 3 },
"children": [
{
"type": "FormControl",
"children": [
{
"type": "Label",
"children": "Category"
},
{
"type": "Select",
"ref": "categoryId",
"required": true,
"options": "{{ $data.categories.map(c => ({ value: c.id, label: c.name })) }}"
}
]
},
{
"type": "FormControl",
"children": [
{
"type": "Label",
"children": "Thread Title"
},
{
"type": "Input",
"ref": "title",
"placeholder": "Enter thread title",
"required": true,
"minLength": 3,
"maxLength": 200
}
]
},
{
"type": "FormControl",
"children": [
{
"type": "Label",
"children": "Content"
},
{
"type": "TextArea",
"ref": "content",
"placeholder": "Write your thread content...",
"required": true,
"minLength": 10,
"maxLength": 5000,
"rows": 8
}
]
},
{
"type": "Flex",
"gap": 1,
"justifyContent": "flex-end",
"children": [
{
"type": "Button",
"variant": "outlined",
"onClick": "handleCancel",
"children": "Cancel"
},
{
"type": "Button",
"variant": "contained",
"onClick": "handleSubmit",
"loading": "{{ $state.submitting }}",
"children": "Create Thread"
}
]
}
]
}
]
}
]
}
},
"data": {
"categories": {
"binding": "GET /api/v1/:tenantId/forum_forge/categories"
}
},
"handlers": {
"handleSubmit": "POST /api/v1/:tenantId/forum_forge/threads { categoryId: $refs.categoryId.value, title: $refs.title.value, content: $refs.content.value }",
"handleCancel": "navigate('/forum')"
}
}
5. forum_moderation_panel (Admin Dashboard)
File: /packages/forum_forge/components/forum_moderation_panel.json
{
"id": "forum_moderation_panel",
"name": "ForumModerationPanel",
"description": "Moderation dashboard for forum management",
"packageId": "forum_forge",
"level": 3,
"requiresAuth": true,
"minLevel": 3,
"render": {
"type": "element",
"template": {
"type": "Stack",
"direction": "column",
"gap": 3,
"className": "forum-moderation-panel",
"children": [
{
"type": "Text",
"variant": "h4",
"fontWeight": "bold",
"children": "Forum Moderation"
},
{
"type": "Tabs",
"defaultTab": "flagged",
"children": [
{
"tabId": "flagged",
"label": "Flagged Posts",
"content": {
"type": "Stack",
"direction": "column",
"gap": 2,
"children": [
{
"type": "Text",
"variant": "subtitle1",
"children": "Flagged for Review ({{ $data.flaggedPosts.length }})"
},
{
"type": "loop",
"items": "{{ $data.flaggedPosts }}",
"itemKey": "id",
"template": {
"type": "Card",
"variant": "outlined",
"children": [
{
"type": "Stack",
"direction": "column",
"gap": 1,
"sx": { "p": 2 },
"children": [
{
"type": "Text",
"variant": "body2",
"children": "{{ item.content }}"
},
{
"type": "Text",
"variant": "caption",
"color": "secondary",
"children": "Reason: {{ item.flagReason }} • Flagged by {{ item.flaggedBy }}"
},
{
"type": "Flex",
"gap": 1,
"children": [
{
"type": "Button",
"size": "sm",
"variant": "contained",
"color": "success",
"onClick": "handleApproveFlaggedPost({{ item.id }})",
"children": "Approve"
},
{
"type": "Button",
"size": "sm",
"variant": "contained",
"color": "error",
"onClick": "handleRejectFlaggedPost({{ item.id }})",
"children": "Delete"
}
]
}
]
}
]
}
}
]
}
},
{
"tabId": "stats",
"label": "Statistics",
"content": {
"type": "Stack",
"direction": "column",
"gap": 2,
"children": [
{
"type": "ForumStatsGrid",
"activeThreads": "{{ $data.stats.activeThreads }}",
"repliesToday": "{{ $data.stats.repliesToday }}",
"queuedFlags": "{{ $data.stats.queuedFlags }}"
}
]
}
},
{
"tabId": "audit",
"label": "Audit Log",
"content": {
"type": "Stack",
"direction": "column",
"gap": 2,
"children": [
{
"type": "loop",
"items": "{{ $data.auditLog }}",
"itemKey": "id",
"template": {
"type": "Card",
"variant": "outlined",
"children": [
{
"type": "Stack",
"direction": "column",
"gap": 0.5,
"sx": { "p": 2 },
"children": [
{
"type": "Text",
"variant": "body2",
"children": "{{ item.action }} by {{ item.moderator }}"
},
{
"type": "Text",
"variant": "caption",
"color": "secondary",
"children": "{{ item.timestamp }}"
}
]
}
]
}
}
]
}
}
]
}
]
}
},
"data": {
"flaggedPosts": {
"binding": "GET /api/v1/:tenantId/forum_forge/admin/flagged-posts"
},
"stats": {
"binding": "GET /api/v1/:tenantId/forum_forge/admin/stats"
},
"auditLog": {
"binding": "GET /api/v1/:tenantId/forum_forge/admin/audit-log"
}
},
"handlers": {
"handleApproveFlaggedPost": "PUT /api/v1/:tenantId/forum_forge/admin/flagged-posts/:flagId { action: 'approve' }",
"handleRejectFlaggedPost": "PUT /api/v1/:tenantId/forum_forge/admin/flagged-posts/:flagId { action: 'reject' }"
}
}
B. Missing Workflow Templates
1. update-thread.jsonscript
{
"version": "2.2.0",
"name": "Update Forum Thread",
"description": "Update thread title or content (owner or moderator only)",
"trigger": {
"type": "http",
"method": "PUT",
"path": "/forum/threads/:threadId"
},
"nodes": [
{
"id": "validate_tenant",
"type": "operation",
"op": "condition",
"condition": "{{ $context.tenantId !== undefined }}"
},
{
"id": "validate_user",
"type": "operation",
"op": "condition",
"condition": "{{ $context.user.id !== undefined }}"
},
{
"id": "fetch_thread",
"type": "operation",
"op": "database_read",
"entity": "ForumThread",
"params": {
"filter": {
"id": "{{ $json.threadId }}",
"tenantId": "{{ $context.tenantId }}"
}
}
},
{
"id": "check_ownership",
"type": "operation",
"op": "condition",
"condition": "{{ $steps.fetch_thread.output.authorId === $context.user.id || $context.user.level >= 3 }}"
},
{
"id": "validate_input",
"type": "operation",
"op": "validate",
"input": "{{ $json }}",
"rules": {
"title": "required|string|minLength:3|maxLength:200",
"content": "required|string|minLength:10|maxLength:5000"
}
},
{
"id": "update_thread",
"type": "operation",
"op": "database_update",
"entity": "ForumThread",
"params": {
"filter": {
"id": "{{ $json.threadId }}"
},
"data": {
"title": "{{ $json.title }}",
"content": "{{ $json.content }}",
"updatedAt": "{{ new Date().toISOString() }}"
}
}
},
{
"id": "emit_updated",
"type": "action",
"action": "emit_event",
"event": "thread_updated",
"channel": "{{ 'forum:thread:' + $json.threadId }}",
"data": {
"threadId": "{{ $json.threadId }}",
"title": "{{ $json.title }}",
"updatedBy": "{{ $context.user.id }}"
}
},
{
"id": "return_success",
"type": "action",
"action": "http_response",
"status": 200,
"body": "{{ $steps.update_thread.output }}"
}
],
"errorHandler": {
"type": "action",
"action": "http_response",
"status": 400,
"body": {
"error": "Failed to update thread",
"message": "{{ $error.message }}"
}
}
}
2. lock-thread.jsonscript
{
"version": "2.2.0",
"name": "Lock Forum Thread",
"description": "Lock/unlock thread to prevent replies (moderator only)",
"trigger": {
"type": "http",
"method": "PUT",
"path": "/forum/threads/:threadId/lock"
},
"nodes": [
{
"id": "validate_moderator",
"type": "operation",
"op": "condition",
"condition": "{{ $context.user.level >= 3 }}"
},
{
"id": "update_thread",
"type": "operation",
"op": "database_update",
"entity": "ForumThread",
"params": {
"filter": {
"id": "{{ $json.threadId }}",
"tenantId": "{{ $context.tenantId }}"
},
"data": {
"isLocked": "{{ $json.locked }}"
}
}
},
{
"id": "emit_locked",
"type": "action",
"action": "emit_event",
"event": "thread_locked",
"channel": "{{ 'forum:thread:' + $json.threadId }}",
"data": {
"threadId": "{{ $json.threadId }}",
"locked": "{{ $json.locked }}",
"moderator": "{{ $context.user.id }}"
}
},
{
"id": "return_success",
"type": "action",
"action": "http_response",
"status": 200,
"body": "{{ $steps.update_thread.output }}"
}
]
}
3. flag-post.jsonscript
{
"version": "2.2.0",
"name": "Flag Forum Post",
"description": "Report inappropriate post for moderation review",
"trigger": {
"type": "http",
"method": "POST",
"path": "/forum/posts/:postId/flag"
},
"nodes": [
{
"id": "validate_user",
"type": "operation",
"op": "condition",
"condition": "{{ $context.user.id !== undefined }}"
},
{
"id": "validate_input",
"type": "operation",
"op": "validate",
"input": "{{ $json }}",
"rules": {
"reason": "required|string|minLength:10|maxLength:500"
}
},
{
"id": "get_post",
"type": "operation",
"op": "database_read",
"entity": "ForumPost",
"params": {
"filter": {
"id": "{{ $json.postId }}",
"tenantId": "{{ $context.tenantId }}"
}
}
},
{
"id": "create_flag",
"type": "operation",
"op": "database_create",
"entity": "PostFlag",
"data": {
"tenantId": "{{ $context.tenantId }}",
"postId": "{{ $json.postId }}",
"threadId": "{{ $steps.get_post.output.threadId }}",
"flaggedBy": "{{ $context.user.id }}",
"reason": "{{ $json.reason }}",
"status": "pending",
"createdAt": "{{ new Date().toISOString() }}"
}
},
{
"id": "emit_flagged",
"type": "action",
"action": "emit_event",
"event": "post_flagged",
"channel": "{{ 'forum:moderation:' + $context.tenantId }}",
"data": {
"postId": "{{ $json.postId }}",
"reason": "{{ $json.reason }}",
"flaggedBy": "{{ $context.user.id }}"
}
},
{
"id": "return_success",
"type": "action",
"action": "http_response",
"status": 201,
"body": {
"message": "Post reported successfully",
"flagId": "{{ $steps.create_flag.output.id }}"
}
}
]
}
4. list-categories.jsonscript
{
"version": "2.2.0",
"name": "List Forum Categories",
"description": "List all forum categories with statistics",
"trigger": {
"type": "http",
"method": "GET",
"path": "/forum/categories"
},
"nodes": [
{
"id": "validate_tenant",
"type": "operation",
"op": "condition",
"condition": "{{ $context.tenantId !== undefined }}"
},
{
"id": "fetch_categories",
"type": "operation",
"op": "database_read",
"entity": "ForumCategory",
"params": {
"filter": {
"tenantId": "{{ $context.tenantId }}"
},
"sort": {
"sortOrder": 1
}
}
},
{
"id": "enrich_with_stats",
"type": "operation",
"op": "transform_data",
"output": "{{ $steps.fetch_categories.output.map(cat => ({ ...cat, threadCount: 0, postCount: 0 })) }}"
},
{
"id": "return_success",
"type": "action",
"action": "http_response",
"status": 200,
"body": "{{ $steps.enrich_with_stats.output }}"
}
]
}
C. Missing Sub-Components
post_card Component
{
"id": "post_card",
"name": "PostCard",
"description": "Display single forum post with metadata and actions",
"props": [
{
"name": "post",
"type": "object",
"required": true,
"description": "Post data object"
}
],
"render": {
"type": "element",
"template": {
"type": "Card",
"variant": "outlined",
"className": "forum-post-card",
"children": [
{
"type": "Stack",
"direction": "column",
"gap": 1.5,
"sx": { "p": 2 },
"children": [
{
"type": "Flex",
"justifyContent": "space-between",
"alignItems": "flex-start",
"children": [
{
"type": "Stack",
"direction": "column",
"gap": 0.5,
"children": [
{
"type": "Text",
"variant": "body2",
"fontWeight": "semibold",
"children": "{{ post.authorName }}"
},
{
"type": "Text",
"variant": "caption",
"color": "secondary",
"children": "{{ post.createdAt }}"
}
]
},
{
"type": "Flex",
"gap": 1,
"children": [
{
"type": "Button",
"size": "sm",
"variant": "ghost",
"icon": "Heart",
"children": "{{ post.likes }}",
"onClick": "handleLike"
},
{
"type": "IconButton",
"size": "sm",
"icon": "Flag",
"onClick": "handleFlag",
"title": "Report post"
},
{
"type": "IconButton",
"size": "sm",
"icon": "MoreVertical",
"onClick": "showMoreMenu",
"visible": "{{ $user.id === post.authorId || $user.level >= 3 }}"
}
]
}
]
},
{
"type": "Text",
"variant": "body2",
"children": "{{ post.content }}"
},
{
"type": "Text",
"variant": "caption",
"color": "secondary",
"children": "{{ post.isEdited ? 'Edited ' + post.updatedAt : '' }}"
}
]
}
]
}
}
}
D. Quick Routes Checklist
Category Routes (Public):
- GET
/categories→ List all categories - GET
/categories/:id→ Get category detail
Thread Routes (Public):
- GET
/threads→ List threads (paginated, filterable) - GET
/threads/:id→ Get thread with first post
Thread Routes (Authenticated):
- POST
/threads→ Create thread (workflow: create-thread) - PUT
/threads/:id→ Update thread (workflow: update-thread) - DELETE
/threads/:id→ Delete thread (cascade delete posts) - PUT
/threads/:id/lock→ Lock/unlock thread (workflow: lock-thread) - PUT
/threads/:id/pin→ Pin/unpin thread
Post Routes (Public):
- GET
/threads/:threadId/posts→ List posts in thread (paginated)
Post Routes (Authenticated):
- POST
/threads/:threadId/posts→ Create post (workflow: create-post) - PUT
/threads/:threadId/posts/:id→ Update own post (workflow: update-post) - DELETE
/threads/:threadId/posts/:id→ Delete post (workflow: delete-post) - POST
/posts/:id/flag→ Flag post (workflow: flag-post)
Moderation Routes (Level 3+):
- GET
/admin/flagged-posts→ List flagged posts - PUT
/admin/flagged-posts/:id→ Approve/reject flagged post - GET
/admin/stats→ Forum statistics - GET
/admin/audit-log→ Moderation audit log
Admin Routes (Level 4+):
- POST
/categories→ Create category - PUT
/categories/:id→ Update category - DELETE
/categories/:id→ Delete category
E. Database Query Examples
List threads in category with pagination:
// Query signature
db.forumThread.list({
filter: { tenantId, categoryId },
sort: { lastReplyAt: 'desc' },
limit: 20,
offset: (page - 1) * 20
})
Get thread with related data:
// With joins (if supported)
db.forumThread.findUnique({
where: { id: threadId, tenantId },
include: {
category: true,
author: true,
posts: {
take: 10,
orderBy: { createdAt: 'asc' }
}
}
})
Check ownership for ACL:
// In workflow
const post = await db.forumPost.findUnique({
where: { id: postId, tenantId }
})
const canUpdate = post.authorId === userId || userLevel >= 3
F. Validation Rules Template
Thread Creation:
{
"categoryId": "required|exists:categories|string",
"title": "required|string|minLength:3|maxLength:200",
"content": "required|string|minLength:10|maxLength:5000"
}
Post Creation:
{
"content": "required|string|minLength:3|maxLength:5000"
}
Category Creation:
{
"name": "required|string|minLength:3|maxLength:100|unique:categories",
"description": "string|maxLength:500",
"icon": "string|maxLength:50",
"parentId": "exists:categories|nullable"
}
Flag Post:
{
"reason": "required|string|minLength:10|maxLength:500"
}
Conclusion
Use these templates to:
- Create missing page component JSON files
- Generate missing workflow JSON scripts
- Implement sub-components (post_card, etc.)
- Define API routes and handlers
All files follow the 95% data / 5% code principle and use JSON Script v2.2.0 for workflows.