Files
system_update/server/routes/actionRequests.ts
T
gilles edb22a59c7 feat(docker): apply/prune/down + socle action_requests (tâche 2 SJ-6)
- migration 0005 : tables docker_image_events + action_requests
- templates apply-compose (up -d --remove-orphans), prune-images (safe/agressif),
  down-compose (sans volumes/rmi)
- dockerApply: parsers TDD (apply recreated/running/exited, prune images+bytes,
  down removed, parseHumanBytes) + orchestration applyStack/pruneImages/downStack
  réservée aux stacks enabled, insère docker_image_events
- actionRequests: create/approve/reject/list — actions destructives validées
  explicitement (Hermes propose, opérateur approuve, run en arrière-plan) ;
  hors API directe (POST /:id/actions reste passif uniquement)
- routes /machines/:id/action-requests + /action-requests/:id[/approve|/reject]
- execute: RunActionOpts.aggressive, branches apply/prune/down, helper
  archiveExecution mutualisant le boilerplate d'archivage

tsc 0 erreur · 91 tests · build OK · boot OK (migrations 0000→0005).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-06 06:05:59 +02:00

64 lines
2.1 KiB
TypeScript

// server/routes/actionRequests.ts
import { Hono } from "hono";
import {
createActionRequest,
getActionRequest,
listActionRequests,
approveActionRequest,
rejectActionRequest,
} from "../services/actionRequests.js";
import type { ActionType } from "@shared/types.js";
export const actionRequestsRoutes = new Hono();
// Crée une demande d'action destructive (pending). Hermes/UI proposent ; aucune exécution ici.
actionRequestsRoutes.post("/machines/:id/action-requests", async (c) => {
const body = (await c.req.json()) as {
action: ActionType;
stackId?: string;
aggressive?: boolean;
summary?: string;
requestedByType?: "user" | "hermes" | "schedule";
};
try {
const req = createActionRequest({
machineId: c.req.param("id"),
action: body.action,
requestedByType: body.requestedByType,
summary: body.summary,
payload: { stackId: body.stackId, aggressive: body.aggressive },
});
return c.json(req, 201);
} catch (err) {
return c.json({ error: (err as Error).message }, 400);
}
});
actionRequestsRoutes.get("/machines/:id/action-requests", (c) =>
c.json(listActionRequests(c.req.param("id"))),
);
actionRequestsRoutes.get("/action-requests/:reqId", (c) => {
const req = getActionRequest(c.req.param("reqId"));
return req ? c.json(req) : c.json({ error: "Demande introuvable" }, 404);
});
// Validation opérateur → déclenche l'exécution en arrière-plan.
actionRequestsRoutes.post("/action-requests/:reqId/approve", async (c) => {
const body = (await c.req.json().catch(() => ({}))) as { approvedBy?: string };
try {
return c.json(approveActionRequest(c.req.param("reqId"), body.approvedBy), 202);
} catch (err) {
return c.json({ error: (err as Error).message }, 400);
}
});
actionRequestsRoutes.post("/action-requests/:reqId/reject", async (c) => {
const body = (await c.req.json().catch(() => ({}))) as { by?: string };
try {
return c.json(rejectActionRequest(c.req.param("reqId"), body.by));
} catch (err) {
return c.json({ error: (err as Error).message }, 400);
}
});