Server: migration prompts, structured logging, heartbeat reaping, and issue-run tracking
Replace auto-migrate-if-empty with interactive migration flow that inspects pending migrations and prompts before applying. Add pino-pretty for structured console + file logging. Add reapOrphanedRuns to clean up stuck heartbeat runs on startup and periodically. Track runId through auth middleware, activity logs, and all mutation routes. Add issue-run cross-reference queries, live-run and active-run endpoints for issues, issue identifier lookup, reopen-via-comment flow, and done/cancelled -> todo status transitions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -4,6 +4,7 @@ import type { Db } from "@paperclip/db";
|
||||
import { validate } from "../middleware/validate.js";
|
||||
import { activityService } from "../services/activity.js";
|
||||
import { assertBoard, assertCompanyAccess } from "./authz.js";
|
||||
import { issueService } from "../services/index.js";
|
||||
|
||||
const createActivitySchema = z.object({
|
||||
actorType: z.enum(["agent", "user", "system"]).optional().default("system"),
|
||||
@@ -18,6 +19,7 @@ const createActivitySchema = z.object({
|
||||
export function activityRoutes(db: Db) {
|
||||
const router = Router();
|
||||
const svc = activityService(db);
|
||||
const issueSvc = issueService(db);
|
||||
|
||||
router.get("/companies/:companyId/activity", async (req, res) => {
|
||||
const companyId = req.params.companyId as string;
|
||||
@@ -43,5 +45,35 @@ export function activityRoutes(db: Db) {
|
||||
res.status(201).json(event);
|
||||
});
|
||||
|
||||
router.get("/issues/:id/activity", async (req, res) => {
|
||||
const id = req.params.id as string;
|
||||
const issue = await issueSvc.getById(id);
|
||||
if (!issue) {
|
||||
res.status(404).json({ error: "Issue not found" });
|
||||
return;
|
||||
}
|
||||
assertCompanyAccess(req, issue.companyId);
|
||||
const result = await svc.forIssue(id);
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
router.get("/issues/:id/runs", async (req, res) => {
|
||||
const id = req.params.id as string;
|
||||
const issue = await issueSvc.getById(id);
|
||||
if (!issue) {
|
||||
res.status(404).json({ error: "Issue not found" });
|
||||
return;
|
||||
}
|
||||
assertCompanyAccess(req, issue.companyId);
|
||||
const result = await svc.runsForIssue(id);
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
router.get("/heartbeat-runs/:runId/issues", async (req, res) => {
|
||||
const runId = req.params.runId as string;
|
||||
const result = await svc.issuesForRun(runId);
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
return router;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user