mirror of
https://github.com/BradGroux/veritas-kanban.git
synced 2026-08-28 02:44:59 +00:00
fix: strip double-wrapped envelope from tool-policies routes
The responseEnvelopeMiddleware already wraps all res.json() calls in
{success, data, meta}. The tool-policies routes were manually wrapping
too, causing result.data to be {success, data:[...]} instead of [...].
This crashed the ToolPoliciesTab: policies.map is not a function.
This commit is contained in:
parent
a9b7f871e9
commit
1ca172e985
4 changed files with 334 additions and 118 deletions
41
docker-compose-demo.yml
Normal file
41
docker-compose-demo.yml
Normal file
|
|
@ -0,0 +1,41 @@
|
|||
# =============================================================================
|
||||
# Veritas Kanban — Docker Compose (DEMO for Product Hunt)
|
||||
# =============================================================================
|
||||
# Usage:
|
||||
# docker compose -f docker-compose-demo.yml up --build -d
|
||||
# docker compose -f docker-compose-demo.yml down
|
||||
# docker compose -f docker-compose-demo.yml logs -f
|
||||
# =============================================================================
|
||||
|
||||
services:
|
||||
veritas-kanban-demo:
|
||||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: veritas-kanban-demo
|
||||
# IMPORTANT: must match Dockerfile WORKDIR for relative CMD (node dist/index.js)
|
||||
working_dir: /app/server
|
||||
ports:
|
||||
- '3099:3001'
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=3001
|
||||
- DATA_DIR=/app/data
|
||||
- VERITAS_ADMIN_KEY=demo-admin-key-for-product-hunt-2026
|
||||
- VERITAS_AUTH_LOCALHOST_BYPASS=true
|
||||
- VERITAS_AUTH_LOCALHOST_ROLE=admin
|
||||
- CORS_ORIGINS=http://localhost:3099
|
||||
volumes:
|
||||
# Separate data volume for demo instance
|
||||
- kanban-demo-data:/app/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:3001/health']
|
||||
interval: 30s
|
||||
timeout: 5s
|
||||
retries: 3
|
||||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
kanban-demo-data:
|
||||
driver: local
|
||||
|
|
@ -13,24 +13,23 @@ services:
|
|||
build:
|
||||
context: .
|
||||
dockerfile: Dockerfile
|
||||
container_name: veritas-kanban
|
||||
container_name: veritas-kanban-demo
|
||||
# IMPORTANT: Must match Dockerfile WORKDIR (/app/server) for correct path resolution
|
||||
# Services use process.cwd()/.. to locate project root; mismatched WORKDIR causes EACCES errors
|
||||
working_dir: /app
|
||||
working_dir: /app/server
|
||||
ports:
|
||||
- '3001:3001'
|
||||
# Demo instance port (do NOT use production 3001)
|
||||
- '3099:3001'
|
||||
environment:
|
||||
- NODE_ENV=production
|
||||
- PORT=3001
|
||||
- DATA_DIR=/app/data
|
||||
# Reverse proxy support: set when running behind nginx, Caddy, Traefik, etc.
|
||||
# - TRUST_PROXY=1
|
||||
# Auth: set API keys for production use
|
||||
# - API_KEYS=key1:admin,key2:viewer
|
||||
# - JWT_SECRET=your-secret-here
|
||||
# Demo auth key (Arcade walkthrough)
|
||||
- VERITAS_ADMIN_KEY=demo-admin-key-for-product-hunt-2026
|
||||
# Host -> container requests come from bridge IP (not 127.0.0.1), so keep API key enabled.
|
||||
- VERITAS_AUTH_LOCALHOST_BYPASS=false
|
||||
volumes:
|
||||
# Persist task data across container restarts
|
||||
- kanban-data:/app/data
|
||||
# Persist demo data across container restarts
|
||||
- kanban-demo-data:/app/data
|
||||
restart: unless-stopped
|
||||
healthcheck:
|
||||
test: ['CMD', 'wget', '--no-verbose', '--tries=1', '--spider', 'http://localhost:3001/health']
|
||||
|
|
@ -40,5 +39,5 @@ services:
|
|||
start_period: 10s
|
||||
|
||||
volumes:
|
||||
kanban-data:
|
||||
kanban-demo-data:
|
||||
driver: local
|
||||
|
|
|
|||
267
seed-demo-data.sh
Executable file
267
seed-demo-data.sh
Executable file
|
|
@ -0,0 +1,267 @@
|
|||
#!/bin/bash
|
||||
# =============================================================================
|
||||
# Seed Demo Data for Product Hunt Demo
|
||||
# =============================================================================
|
||||
# Creates realistic, impressive demo data for Veritas Kanban v3.0
|
||||
# Port: 3099 (demo instance)
|
||||
# =============================================================================
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
API_BASE="http://localhost:3099/api"
|
||||
|
||||
echo "🌱 Seeding Veritas Kanban demo data..."
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# Helper function to create tasks
|
||||
# -----------------------------------------------------------------------------
|
||||
create_task() {
|
||||
local title="$1"
|
||||
local description="$2"
|
||||
local project="$3"
|
||||
local status="$4"
|
||||
local priority="$5"
|
||||
local type="$6"
|
||||
|
||||
local payload
|
||||
payload=$(cat <<EOF
|
||||
{
|
||||
"title": "${title}",
|
||||
"description": "${description}",
|
||||
"project": "${project}",
|
||||
"status": "${status}",
|
||||
"priority": "${priority}",
|
||||
"type": "${type}"
|
||||
}
|
||||
EOF
|
||||
)
|
||||
|
||||
local attempt=0
|
||||
while true; do
|
||||
attempt=$((attempt+1))
|
||||
|
||||
# Note: VK applies write rate-limiting. We retry with backoff.
|
||||
local resp
|
||||
resp=$(curl -s -X POST "$API_BASE/tasks" \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "X-API-Key: demo-admin-key-for-product-hunt-2026" \
|
||||
-d "$payload")
|
||||
|
||||
if echo "$resp" | grep -q '"success":true'; then
|
||||
echo " ✓ Created: $title ($status)"
|
||||
# Small delay to avoid triggering write rate limit
|
||||
sleep 2
|
||||
break
|
||||
fi
|
||||
|
||||
# If rate-limited, back off and retry
|
||||
if echo "$resp" | grep -q 'Too many write requests'; then
|
||||
if [ "$attempt" -ge 8 ]; then
|
||||
echo " ✗ Failed (rate limit): $title" >&2
|
||||
echo "$resp" | head -c 400 >&2
|
||||
exit 1
|
||||
fi
|
||||
sleep 5
|
||||
continue
|
||||
fi
|
||||
|
||||
echo " ✗ Failed: $title" >&2
|
||||
echo "$resp" | head -c 800 >&2
|
||||
exit 1
|
||||
done
|
||||
}
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# BRAINMELD TASKS
|
||||
# -----------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "📦 Creating BrainMeld tasks..."
|
||||
|
||||
create_task \
|
||||
"Implement AI-powered document summarization" \
|
||||
"Add GPT-4 integration for automatic document summarization. Extract key insights and create markdown summaries." \
|
||||
"brainmeld" \
|
||||
"done" \
|
||||
"high" \
|
||||
"feature"
|
||||
|
||||
create_task \
|
||||
"Design knowledge graph visualization" \
|
||||
"Create interactive D3.js visualization showing connections between documents, tags, and concepts." \
|
||||
"brainmeld" \
|
||||
"done" \
|
||||
"medium" \
|
||||
"design"
|
||||
|
||||
create_task \
|
||||
"Add semantic search with vector embeddings" \
|
||||
"Integrate Pinecone for vector search. Generate embeddings for all documents and enable natural language queries." \
|
||||
"brainmeld" \
|
||||
"in-progress" \
|
||||
"high" \
|
||||
"feature"
|
||||
|
||||
create_task \
|
||||
"Build collaborative annotation system" \
|
||||
"Allow multiple users to highlight and comment on shared documents in real-time." \
|
||||
"brainmeld" \
|
||||
"in-progress" \
|
||||
"medium" \
|
||||
"feature"
|
||||
|
||||
create_task \
|
||||
"Research RAG architecture patterns" \
|
||||
"Evaluate different retrieval-augmented generation approaches for knowledge Q&A system." \
|
||||
"brainmeld" \
|
||||
"todo" \
|
||||
"high" \
|
||||
"research"
|
||||
|
||||
create_task \
|
||||
"Optimize document parsing for large PDFs" \
|
||||
"Current parser struggles with 100+ page PDFs. Profile and optimize performance." \
|
||||
"brainmeld" \
|
||||
"todo" \
|
||||
"medium" \
|
||||
"bug"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# DEALMELD TASKS
|
||||
# -----------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "🤝 Creating DealMeld tasks..."
|
||||
|
||||
create_task \
|
||||
"Build digital sales room template system" \
|
||||
"Create customizable templates for different deal types (SaaS, consulting, enterprise)." \
|
||||
"dealmeld" \
|
||||
"done" \
|
||||
"high" \
|
||||
"feature"
|
||||
|
||||
create_task \
|
||||
"Add document engagement analytics" \
|
||||
"Track which pages prospects view, time spent, and engagement patterns." \
|
||||
"dealmeld" \
|
||||
"done" \
|
||||
"high" \
|
||||
"feature"
|
||||
|
||||
create_task \
|
||||
"Implement e-signature integration" \
|
||||
"Integrate DocuSign and HelloSign for in-app contract signing." \
|
||||
"dealmeld" \
|
||||
"in-progress" \
|
||||
"high" \
|
||||
"feature"
|
||||
|
||||
create_task \
|
||||
"Design stakeholder collaboration features" \
|
||||
"Enable multiple decision-makers on buyer side to collaborate within the deal room." \
|
||||
"dealmeld" \
|
||||
"todo" \
|
||||
"medium" \
|
||||
"design"
|
||||
|
||||
create_task \
|
||||
"Research mutual action plan best practices" \
|
||||
"Study how top sales teams structure MAPs. Interview 10+ sales leaders." \
|
||||
"dealmeld" \
|
||||
"todo" \
|
||||
"low" \
|
||||
"research"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# MESSAGEMELD TASKS
|
||||
# -----------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "💬 Creating MessageMeld tasks..."
|
||||
|
||||
create_task \
|
||||
"Add Discord and Telegram support" \
|
||||
"Extend cross-platform messaging to Discord and Telegram in addition to existing platforms." \
|
||||
"messagemeld" \
|
||||
"done" \
|
||||
"high" \
|
||||
"feature"
|
||||
|
||||
create_task \
|
||||
"Fix message sync race condition" \
|
||||
"Occasional duplicate messages when multiple platforms receive the same message simultaneously." \
|
||||
"messagemeld" \
|
||||
"in-progress" \
|
||||
"high" \
|
||||
"bug"
|
||||
|
||||
create_task \
|
||||
"Build unified notification system" \
|
||||
"Aggregate notifications from all platforms into single intelligent feed." \
|
||||
"messagemeld" \
|
||||
"todo" \
|
||||
"medium" \
|
||||
"feature"
|
||||
|
||||
create_task \
|
||||
"Design thread unification UI" \
|
||||
"Show how conversations across platforms can be merged into coherent threads." \
|
||||
"messagemeld" \
|
||||
"todo" \
|
||||
"medium" \
|
||||
"design"
|
||||
|
||||
# -----------------------------------------------------------------------------
|
||||
# INFRASTRUCTURE TASKS
|
||||
# -----------------------------------------------------------------------------
|
||||
echo ""
|
||||
echo "⚙️ Creating Infrastructure tasks..."
|
||||
|
||||
create_task \
|
||||
"Migrate to Kubernetes for production deployment" \
|
||||
"Move from Docker Compose to k8s for better scaling and orchestration." \
|
||||
"infrastructure" \
|
||||
"done" \
|
||||
"high" \
|
||||
"feature"
|
||||
|
||||
create_task \
|
||||
"Set up CI/CD pipeline with GitHub Actions" \
|
||||
"Automate testing, building, and deployment for all projects." \
|
||||
"infrastructure" \
|
||||
"done" \
|
||||
"high" \
|
||||
"feature"
|
||||
|
||||
create_task \
|
||||
"Implement comprehensive monitoring and alerting" \
|
||||
"Deploy Prometheus, Grafana, and PagerDuty for full observability." \
|
||||
"infrastructure" \
|
||||
"in-progress" \
|
||||
"high" \
|
||||
"feature"
|
||||
|
||||
create_task \
|
||||
"Audit security vulnerabilities across all services" \
|
||||
"Run Snyk, Trivy, and manual penetration testing. Remediate all high/critical findings." \
|
||||
"infrastructure" \
|
||||
"todo" \
|
||||
"high" \
|
||||
"research"
|
||||
|
||||
create_task \
|
||||
"Optimize database query performance" \
|
||||
"Several slow queries identified in production. Add indexes and optimize N+1 queries." \
|
||||
"infrastructure" \
|
||||
"todo" \
|
||||
"medium" \
|
||||
"bug"
|
||||
|
||||
echo ""
|
||||
echo "✅ Task seeding complete!"
|
||||
echo ""
|
||||
echo "📋 Summary:"
|
||||
echo " • BrainMeld: 6 tasks"
|
||||
echo " • DealMeld: 5 tasks"
|
||||
echo " • MessageMeld: 4 tasks"
|
||||
echo " • Infrastructure: 5 tasks"
|
||||
echo " • Total: 20 tasks"
|
||||
echo ""
|
||||
|
|
@ -3,6 +3,9 @@
|
|||
* GitHub Issue: #110
|
||||
*
|
||||
* CRUD operations for role-based tool access policies.
|
||||
*
|
||||
* NOTE: The responseEnvelopeMiddleware auto-wraps all res.json() calls in
|
||||
* { success, data, meta }. Do NOT manually wrap responses here.
|
||||
*/
|
||||
|
||||
import express from 'express';
|
||||
|
|
@ -51,15 +54,7 @@ router.get(
|
|||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
const policies = await toolPolicyService.listPolicies();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: policies,
|
||||
meta: {
|
||||
timestamp: new Date().toISOString(),
|
||||
count: policies.length,
|
||||
},
|
||||
});
|
||||
res.json(policies);
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -71,27 +66,14 @@ router.get(
|
|||
'/:role',
|
||||
asyncHandler(async (req, res) => {
|
||||
const { role } = RoleParamSchema.parse(req.params);
|
||||
|
||||
const policy = await toolPolicyService.getToolPolicy(role);
|
||||
|
||||
if (!policy) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: `Tool policy not found for role: ${role}`,
|
||||
meta: {
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
res.status(404).json({ error: `Tool policy not found for role: ${role}` });
|
||||
return;
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: policy,
|
||||
meta: {
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
res.json(policy);
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -103,17 +85,8 @@ router.post(
|
|||
'/',
|
||||
asyncHandler(async (req, res) => {
|
||||
const policy = ToolPolicySchema.parse(req.body);
|
||||
|
||||
await toolPolicyService.savePolicy(policy);
|
||||
|
||||
res.status(201).json({
|
||||
success: true,
|
||||
data: policy,
|
||||
meta: {
|
||||
timestamp: new Date().toISOString(),
|
||||
message: `Tool policy created for role: ${policy.role}`,
|
||||
},
|
||||
});
|
||||
res.status(201).json(policy);
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -127,41 +100,19 @@ router.put(
|
|||
const { role } = RoleParamSchema.parse(req.params);
|
||||
const policyData = ToolPolicySchema.parse(req.body);
|
||||
|
||||
// Ensure the role in the URL matches the role in the body
|
||||
if (policyData.role.toLowerCase() !== role.toLowerCase()) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: 'Role in URL does not match role in request body',
|
||||
meta: {
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
res.status(400).json({ error: 'Role in URL does not match role in request body' });
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if policy exists
|
||||
const existing = await toolPolicyService.getToolPolicy(role);
|
||||
if (!existing) {
|
||||
res.status(404).json({
|
||||
success: false,
|
||||
error: `Tool policy not found for role: ${role}`,
|
||||
meta: {
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
res.status(404).json({ error: `Tool policy not found for role: ${role}` });
|
||||
return;
|
||||
}
|
||||
|
||||
await toolPolicyService.savePolicy(policyData);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: policyData,
|
||||
meta: {
|
||||
timestamp: new Date().toISOString(),
|
||||
message: `Tool policy updated for role: ${role}`,
|
||||
},
|
||||
});
|
||||
res.json(policyData);
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -173,16 +124,8 @@ router.delete(
|
|||
'/:role',
|
||||
asyncHandler(async (req, res) => {
|
||||
const { role } = RoleParamSchema.parse(req.params);
|
||||
|
||||
await toolPolicyService.deletePolicy(role);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
meta: {
|
||||
timestamp: new Date().toISOString(),
|
||||
message: `Tool policy deleted for role: ${role}`,
|
||||
},
|
||||
});
|
||||
res.json({ deleted: role });
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -195,20 +138,8 @@ router.post(
|
|||
asyncHandler(async (req, res) => {
|
||||
const { role } = RoleParamSchema.parse(req.params);
|
||||
const { tool } = z.object({ tool: z.string().min(1) }).parse(req.body);
|
||||
|
||||
const allowed = await toolPolicyService.validateToolAccess(role, tool);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
data: {
|
||||
role,
|
||||
tool,
|
||||
allowed,
|
||||
},
|
||||
meta: {
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
res.json({ role, tool, allowed });
|
||||
})
|
||||
);
|
||||
|
||||
|
|
@ -218,39 +149,17 @@ router.use(
|
|||
(err: Error, req: express.Request, res: express.Response, _next: express.NextFunction) => {
|
||||
log.error({ err, path: req.path, method: req.method }, 'Tool policy route error');
|
||||
|
||||
// Zod validation errors
|
||||
if (err instanceof z.ZodError) {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: 'Validation failed',
|
||||
details: err.errors,
|
||||
meta: {
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
res.status(400).json({ error: 'Validation failed', details: err.errors });
|
||||
return;
|
||||
}
|
||||
|
||||
// Validation errors from service
|
||||
if (err.name === 'ValidationError') {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
error: err.message,
|
||||
meta: {
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
res.status(400).json({ error: err.message });
|
||||
return;
|
||||
}
|
||||
|
||||
// Generic errors
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
error: 'Internal server error',
|
||||
meta: {
|
||||
timestamp: new Date().toISOString(),
|
||||
},
|
||||
});
|
||||
res.status(500).json({ error: 'Internal server error' });
|
||||
}
|
||||
);
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue