adeb4dad33
- Stack Swarm 'dev': redis + taskboard + frontend HMR (Vite) + backend HMR (tsx) + 5 agents - TaskBoard standalone React com Redis pub/sub e BLPOP queue - Backend API Express: /tasks /agents /health com hot reload - Agentes: 2 FE (frontend), 2 BE (backend), 1 DevOps (parallel workers) - Vault Obsidian: /root/Obsidian-Pulse/ com estrutura Inbox/Projetos/Docker/Dev/Codex/Logs/Memorias/Templates - Skill obsidian-vault-linker instalada e documentada - Caddy labels: board.octal.tec.br + api.octal.tec.br + frontend.octal.tec.br - Protocolo task queue Redis documentado em MEMORY.md
99 lines
3.4 KiB
TypeScript
99 lines
3.4 KiB
TypeScript
import express from 'express'
|
|
import cors from 'cors'
|
|
import helmet from 'helmet'
|
|
import Redis from 'ioredis'
|
|
import { v4 as uuidv4 } from 'uuid'
|
|
|
|
const app = express()
|
|
app.use(cors())
|
|
app.use(helmet())
|
|
app.use(express.json())
|
|
|
|
// ─── Redis ──────────────────────────────────────────────────────────
|
|
const redis = new Redis({ host: 'redis', port: 6379, lazyConnect: true })
|
|
redis.on('error', (err) => console.error('[Redis] error:', err.message))
|
|
redis.on('ready', () => console.log('[Redis] conectado'))
|
|
|
|
// ─── Task Queue ─────────────────────────────────────────────────────
|
|
const TASK_QUEUE = 'dev-tasks'
|
|
const LOG_CHANNEL = 'dev-logs'
|
|
|
|
function log(level, msg) {
|
|
const line = JSON.stringify({ ts: new Date().toISOString(), level, msg })
|
|
redis.publish(LOG_CHANNEL, line).catch(() => {})
|
|
console.log(`[${level}] ${msg}`)
|
|
}
|
|
|
|
// GET /tasks — lista todas
|
|
// POST /tasks — cria tarefa
|
|
// PUT /tasks/:id — atualiza status
|
|
// GET /agents — lista agentes conectados
|
|
// GET /logs/:limit — logs recentes
|
|
|
|
app.get('/tasks', async (_req, res) => {
|
|
const keys = await redis.keys('task:*')
|
|
const tasks = []
|
|
for (const k of keys) {
|
|
const t = await redis.get(k)
|
|
if (t) tasks.push(JSON.parse(t))
|
|
}
|
|
tasks.sort((a, b) => b.updated_at?.localeCompare(a.updated_at) || 0)
|
|
res.json(tasks)
|
|
})
|
|
|
|
app.post('/tasks', async (req, res) => {
|
|
const id = uuidv4()
|
|
const task = {
|
|
id, title: req.body.title, type: req.body.type || 'feature',
|
|
priority: req.body.priority || 'medium', status: 'todo',
|
|
domain: req.body.domain || 'fullstack', assignee: null,
|
|
created_at: new Date().toISOString(), updated_at: new Date().toISOString(),
|
|
}
|
|
await redis.set(`task:${id}`, JSON.stringify(task))
|
|
await redis.rpush('task-queue', id)
|
|
log('TASK', `Nova tarefa: "${task.title}" [${task.type}]`)
|
|
res.status(201).json(task)
|
|
})
|
|
|
|
app.put('/tasks/:id', async (req, res) => {
|
|
const key = `task:${req.params.id}`
|
|
const raw = await redis.get(key)
|
|
if (!raw) return res.status(404).json({ error: 'Não encontrada' })
|
|
const task = JSON.parse(raw)
|
|
Object.assign(task, req.body, { updated_at: new Date().toISOString() })
|
|
await redis.set(key, JSON.stringify(task))
|
|
log('INFO', `Tarefa atualizada: "${task.title}" status=${task.status}`)
|
|
res.json(task)
|
|
})
|
|
|
|
app.get('/agents', async (_req, res) => {
|
|
const keys = await redis.keys('agent:*')
|
|
const agents = []
|
|
for (const k of keys) {
|
|
const a = await redis.get(k)
|
|
if (a) agents.push(JSON.parse(a))
|
|
}
|
|
res.json(agents)
|
|
})
|
|
|
|
app.get('/health', (_req, res) => {
|
|
res.json({ status: 'ok', ts: new Date().toISOString() })
|
|
})
|
|
|
|
// Promover tarefa automaticamente ao criar/modificar
|
|
app.use('*', (req, res) => { res.status(404).json({ error: 'Not found' }) })
|
|
|
|
const PORT = 3001
|
|
app.listen(PORT, '0.0.0.0', () => {
|
|
// Registrar agente backend
|
|
;(async () => {
|
|
try { await redis.connect() } catch (e) { console.error('Redis connect:', e.message) }
|
|
await redis.set('agent:dev-backend', JSON.stringify({
|
|
id: 'dev-backend', role: 'backend', status: 'online',
|
|
started_at: new Date().toISOString(),
|
|
}))
|
|
log('AGENT', `Backend API online :${PORT}`)
|
|
log('AGENT', `Redis conectado — queue: ${TASK_QUEUE}`)
|
|
})()
|
|
})
|