Files
timmy-tower/artifacts/api-server/src/routes/index.ts

34 lines
969 B
TypeScript
Raw Normal View History

2026-03-13 23:21:55 +00:00
import { Router, type IRouter } from "express";
Task #3: MVP API — payment-gated jobs + demo endpoint OpenAPI spec (lib/api-spec/openapi.yaml) - Added POST /jobs, GET /jobs/{id}, GET /demo endpoints - Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse, InvoiceInfo, JobState, DemoResponse, ErrorResponse - Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc. Jobs router (artifacts/api-server/src/routes/jobs.ts) - POST /jobs: validates body, creates LNbits eval invoice, inserts job + invoice in a DB transaction, returns { jobId, evalInvoice } - GET /jobs/:id: fetches job, calls advanceJob() helper, returns state- appropriate payload (eval/work invoice, reason, result, errorMessage) - advanceJob() state machine: - awaiting_eval_payment: checks LNbits, atomically marks paid + advances state via optimistic WHERE state='awaiting_eval_payment'; runs AgentService.evaluateRequest, branches to awaiting_work_payment or rejected - awaiting_work_payment: same pattern for work invoice, runs AgentService.executeWork, advances to complete - Any agent/LNbits error transitions job to failed Demo router (artifacts/api-server/src/routes/demo.ts) - GET /demo?request=...: in-memory rate limiter (5 req/hour per IP) - Explicit guard for missing request param (coerce.string() workaround) - Calls AgentService.executeWork directly, returns { result } Dev router (artifacts/api-server/src/routes/dev.ts) - POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory - Only mounted when NODE_ENV !== 'production' Route index updated to mount all three routers replit.md: documented full curl flow with all 6 steps, demo endpoint, and dev stub-pay trigger End-to-end verified with curl: - Full flow: create → eval pay → evaluating → work pay → executing → complete - Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
import healthRouter from "./health.js";
import jobsRouter from "./jobs.js";
Task #5: Lightning-gated node bootstrap (proof-of-concept) Pay a Lightning invoice → Timmy auto-provisions a Bitcoin full node on DO. New: lib/db/src/schema/bootstrap-jobs.ts - bootstrap_jobs table: id, state, amountSats, paymentHash, paymentRequest, dropletId, nodeIp, tailscaleHostname, lnbitsUrl, sshPrivateKey, sshKeyDelivered (bool), errorMessage, createdAt, updatedAt - States: awaiting_payment | provisioning | ready | failed - Payment data stored inline (no FK to jobs/invoices tables — separate entity) - db:push applied to create table in Postgres New: artifacts/api-server/src/lib/provisioner.ts - ProvisionerService: stubs when DO_API_TOKEN absent, real otherwise - Stub mode: generates a real RSA 4096-bit SSH keypair via ssh-keygen, returns RFC 5737 test IP + fake Tailscale hostname after 2s delay - Real mode: upload SSH public key to DO → generate Tailscale auth key → create DO droplet with cloud-init user_data → poll for public IP (2 min) - buildCloudInitScript(): non-interactive bash that installs Docker + Tailscale + UFW + Bitcoin Knots via docker-compose; joins Tailscale if authkey provided - provision() designed as fire-and-forget (void); updates DB to ready/failed New: artifacts/api-server/src/routes/bootstrap.ts - POST /api/bootstrap: create job + LNbits invoice, return paymentRequest - GET /api/bootstrap/:id: poll-driven state machine * awaiting_payment: checks payment, fires provisioner on confirm * provisioning: returns progress message * ready: delivers credentials; SSH private key delivered once then cleared * failed: returns error message - Stub mode message includes the exact /dev/stub/pay URL for easy testing - nextSteps array guides user through post-provision setup Updated: artifacts/api-server/src/lib/pricing.ts - Added bootstrapFee field reading BOOTSTRAP_FEE_SATS env var (default 10000) - calculateBootstrapFeeSats() method Updated: artifacts/api-server/src/routes/index.ts - Mounts bootstrapRouter Updated: replit.md - Documents all 7 new env vars (DO_API_TOKEN, DO_REGION, DO_SIZE, etc.) - Full curl-based flow example with annotated response shape End-to-end verified in stub mode: POST → pay → provisioning → ready (SSH key) → second GET clears key and shows sshKeyNote
2026-03-18 18:47:48 +00:00
import bootstrapRouter from "./bootstrap.js";
import sessionsRouter from "./sessions.js";
Task #3: MVP API — payment-gated jobs + demo endpoint OpenAPI spec (lib/api-spec/openapi.yaml) - Added POST /jobs, GET /jobs/{id}, GET /demo endpoints - Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse, InvoiceInfo, JobState, DemoResponse, ErrorResponse - Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc. Jobs router (artifacts/api-server/src/routes/jobs.ts) - POST /jobs: validates body, creates LNbits eval invoice, inserts job + invoice in a DB transaction, returns { jobId, evalInvoice } - GET /jobs/:id: fetches job, calls advanceJob() helper, returns state- appropriate payload (eval/work invoice, reason, result, errorMessage) - advanceJob() state machine: - awaiting_eval_payment: checks LNbits, atomically marks paid + advances state via optimistic WHERE state='awaiting_eval_payment'; runs AgentService.evaluateRequest, branches to awaiting_work_payment or rejected - awaiting_work_payment: same pattern for work invoice, runs AgentService.executeWork, advances to complete - Any agent/LNbits error transitions job to failed Demo router (artifacts/api-server/src/routes/demo.ts) - GET /demo?request=...: in-memory rate limiter (5 req/hour per IP) - Explicit guard for missing request param (coerce.string() workaround) - Calls AgentService.executeWork directly, returns { result } Dev router (artifacts/api-server/src/routes/dev.ts) - POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory - Only mounted when NODE_ENV !== 'production' Route index updated to mount all three routers replit.md: documented full curl flow with all 6 steps, demo endpoint, and dev stub-pay trigger End-to-end verified with curl: - Full flow: create → eval pay → evaluating → work pay → executing → complete - Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
import demoRouter from "./demo.js";
import devRouter from "./dev.js";
import testkitRouter from "./testkit.js";
import uiRouter from "./ui.js";
import nodeDiagnosticsRouter from "./node-diagnostics.js";
import metricsRouter from "./metrics.js";
import worldRouter from "./world.js";
import identityRouter from "./identity.js";
2026-03-13 23:21:55 +00:00
const router: IRouter = Router();
router.use(healthRouter);
router.use(metricsRouter);
Task #3: MVP API — payment-gated jobs + demo endpoint OpenAPI spec (lib/api-spec/openapi.yaml) - Added POST /jobs, GET /jobs/{id}, GET /demo endpoints - Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse, InvoiceInfo, JobState, DemoResponse, ErrorResponse - Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc. Jobs router (artifacts/api-server/src/routes/jobs.ts) - POST /jobs: validates body, creates LNbits eval invoice, inserts job + invoice in a DB transaction, returns { jobId, evalInvoice } - GET /jobs/:id: fetches job, calls advanceJob() helper, returns state- appropriate payload (eval/work invoice, reason, result, errorMessage) - advanceJob() state machine: - awaiting_eval_payment: checks LNbits, atomically marks paid + advances state via optimistic WHERE state='awaiting_eval_payment'; runs AgentService.evaluateRequest, branches to awaiting_work_payment or rejected - awaiting_work_payment: same pattern for work invoice, runs AgentService.executeWork, advances to complete - Any agent/LNbits error transitions job to failed Demo router (artifacts/api-server/src/routes/demo.ts) - GET /demo?request=...: in-memory rate limiter (5 req/hour per IP) - Explicit guard for missing request param (coerce.string() workaround) - Calls AgentService.executeWork directly, returns { result } Dev router (artifacts/api-server/src/routes/dev.ts) - POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory - Only mounted when NODE_ENV !== 'production' Route index updated to mount all three routers replit.md: documented full curl flow with all 6 steps, demo endpoint, and dev stub-pay trigger End-to-end verified with curl: - Full flow: create → eval pay → evaluating → work pay → executing → complete - Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
router.use(jobsRouter);
Task #5: Lightning-gated node bootstrap (proof-of-concept) Pay a Lightning invoice → Timmy auto-provisions a Bitcoin full node on DO. New: lib/db/src/schema/bootstrap-jobs.ts - bootstrap_jobs table: id, state, amountSats, paymentHash, paymentRequest, dropletId, nodeIp, tailscaleHostname, lnbitsUrl, sshPrivateKey, sshKeyDelivered (bool), errorMessage, createdAt, updatedAt - States: awaiting_payment | provisioning | ready | failed - Payment data stored inline (no FK to jobs/invoices tables — separate entity) - db:push applied to create table in Postgres New: artifacts/api-server/src/lib/provisioner.ts - ProvisionerService: stubs when DO_API_TOKEN absent, real otherwise - Stub mode: generates a real RSA 4096-bit SSH keypair via ssh-keygen, returns RFC 5737 test IP + fake Tailscale hostname after 2s delay - Real mode: upload SSH public key to DO → generate Tailscale auth key → create DO droplet with cloud-init user_data → poll for public IP (2 min) - buildCloudInitScript(): non-interactive bash that installs Docker + Tailscale + UFW + Bitcoin Knots via docker-compose; joins Tailscale if authkey provided - provision() designed as fire-and-forget (void); updates DB to ready/failed New: artifacts/api-server/src/routes/bootstrap.ts - POST /api/bootstrap: create job + LNbits invoice, return paymentRequest - GET /api/bootstrap/:id: poll-driven state machine * awaiting_payment: checks payment, fires provisioner on confirm * provisioning: returns progress message * ready: delivers credentials; SSH private key delivered once then cleared * failed: returns error message - Stub mode message includes the exact /dev/stub/pay URL for easy testing - nextSteps array guides user through post-provision setup Updated: artifacts/api-server/src/lib/pricing.ts - Added bootstrapFee field reading BOOTSTRAP_FEE_SATS env var (default 10000) - calculateBootstrapFeeSats() method Updated: artifacts/api-server/src/routes/index.ts - Mounts bootstrapRouter Updated: replit.md - Documents all 7 new env vars (DO_API_TOKEN, DO_REGION, DO_SIZE, etc.) - Full curl-based flow example with annotated response shape End-to-end verified in stub mode: POST → pay → provisioning → ready (SSH key) → second GET clears key and shows sshKeyNote
2026-03-18 18:47:48 +00:00
router.use(bootstrapRouter);
router.use(sessionsRouter);
router.use(identityRouter);
Task #3: MVP API — payment-gated jobs + demo endpoint OpenAPI spec (lib/api-spec/openapi.yaml) - Added POST /jobs, GET /jobs/{id}, GET /demo endpoints - Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse, InvoiceInfo, JobState, DemoResponse, ErrorResponse - Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc. Jobs router (artifacts/api-server/src/routes/jobs.ts) - POST /jobs: validates body, creates LNbits eval invoice, inserts job + invoice in a DB transaction, returns { jobId, evalInvoice } - GET /jobs/:id: fetches job, calls advanceJob() helper, returns state- appropriate payload (eval/work invoice, reason, result, errorMessage) - advanceJob() state machine: - awaiting_eval_payment: checks LNbits, atomically marks paid + advances state via optimistic WHERE state='awaiting_eval_payment'; runs AgentService.evaluateRequest, branches to awaiting_work_payment or rejected - awaiting_work_payment: same pattern for work invoice, runs AgentService.executeWork, advances to complete - Any agent/LNbits error transitions job to failed Demo router (artifacts/api-server/src/routes/demo.ts) - GET /demo?request=...: in-memory rate limiter (5 req/hour per IP) - Explicit guard for missing request param (coerce.string() workaround) - Calls AgentService.executeWork directly, returns { result } Dev router (artifacts/api-server/src/routes/dev.ts) - POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory - Only mounted when NODE_ENV !== 'production' Route index updated to mount all three routers replit.md: documented full curl flow with all 6 steps, demo endpoint, and dev stub-pay trigger End-to-end verified with curl: - Full flow: create → eval pay → evaluating → work pay → executing → complete - Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
router.use(demoRouter);
router.use(testkitRouter);
router.use(uiRouter);
router.use(nodeDiagnosticsRouter);
router.use(worldRouter);
Task #3: MVP API — payment-gated jobs + demo endpoint OpenAPI spec (lib/api-spec/openapi.yaml) - Added POST /jobs, GET /jobs/{id}, GET /demo endpoints - Added schemas: CreateJobRequest, CreateJobResponse, JobStatusResponse, InvoiceInfo, JobState, DemoResponse, ErrorResponse - Ran codegen: generated CreateJobBody, GetJobResponse, RunDemoQueryParams etc. Jobs router (artifacts/api-server/src/routes/jobs.ts) - POST /jobs: validates body, creates LNbits eval invoice, inserts job + invoice in a DB transaction, returns { jobId, evalInvoice } - GET /jobs/:id: fetches job, calls advanceJob() helper, returns state- appropriate payload (eval/work invoice, reason, result, errorMessage) - advanceJob() state machine: - awaiting_eval_payment: checks LNbits, atomically marks paid + advances state via optimistic WHERE state='awaiting_eval_payment'; runs AgentService.evaluateRequest, branches to awaiting_work_payment or rejected - awaiting_work_payment: same pattern for work invoice, runs AgentService.executeWork, advances to complete - Any agent/LNbits error transitions job to failed Demo router (artifacts/api-server/src/routes/demo.ts) - GET /demo?request=...: in-memory rate limiter (5 req/hour per IP) - Explicit guard for missing request param (coerce.string() workaround) - Calls AgentService.executeWork directly, returns { result } Dev router (artifacts/api-server/src/routes/dev.ts) - POST /dev/stub/pay/:paymentHash: marks stub invoice paid in-memory - Only mounted when NODE_ENV !== 'production' Route index updated to mount all three routers replit.md: documented full curl flow with all 6 steps, demo endpoint, and dev stub-pay trigger End-to-end verified with curl: - Full flow: create → eval pay → evaluating → work pay → executing → complete - Error cases: 400 on missing body/param, 404 on unknown job
2026-03-18 15:31:26 +00:00
if (process.env.NODE_ENV !== "production") {
router.use(devRouter);
}
2026-03-13 23:21:55 +00:00
export default router;