| 1 | /** |
| 2 | * The desktop app's runner, from its page (lib/desktop-runner.ts): a |
| 3 | * registration token for a workspace the person owns, which the page |
| 4 | * hands to the app and never shows, and the app's reports of how its |
| 5 | * runner is, kept for the workspace's Runners page. |
| 6 | * |
| 7 | * Signed in, same origin, and owners of the workspace only, as the |
| 8 | * Runners page's own New runner is; the actions service checks again. |
| 9 | * `-` is no workspace's name, so nothing else is ever found here. |
| 10 | * |
| 11 | * POST { "intent": "token", "workspace": "acme" } |
| 12 | * → { "token": "g1trt_…", "workspace": "acme", "url": "https://g1t.sh", "expires_at": "…" } |
| 13 | * → 403 { "error": "…", "allowed": false } |
| 14 | * POST { "intent": "report", "workspace", "machine", "backend", "version", "diagnosis", "closed" } |
| 15 | * → { "ok": true } |
| 16 | */ |
| 17 | import type { Route } from "./+types/desktop-runner"; |
| 18 | import { backendOf, diagnosisOf, text } from "../lib/desktop-runner"; |
| 19 | import { runners } from "../lib/runners.server"; |
| 20 | import { assertSameOrigin, getViewer, roleIn } from "../lib/session.server"; |
| 21 | |
| 22 | export async function action({ request, context }: Route.ActionArgs) { |
| 23 | assertSameOrigin(request); |
| 24 | const headers = { "cache-control": "no-store" }; |
| 25 | const viewer = getViewer(context); |
| 26 | if (!viewer || (viewer.kind ?? "user") !== "user") return Response.json({ error: "Sign in to g1t." }, { status: 401, headers }); |
| 27 | const body = (await request.json().catch(() => null)) as Record<string, unknown> | null; |
| 28 | const workspace = text(body?.workspace)?.toLowerCase() ?? null; |
| 29 | if (!body || !workspace) return Response.json({ error: "Name a workspace." }, { status: 400, headers }); |
| 30 | if (roleIn(viewer, workspace) !== "owner") { |
| 31 | return Response.json({ error: `You can't add runners to ${workspace}: only its owners can.`, allowed: false }, { status: 403, headers }); |
| 32 | } |
| 33 | switch (body.intent) { |
| 34 | case "token": { |
| 35 | const made = await runners.createToken(viewer, { workspace }); |
| 36 | if (!made.ok) return Response.json({ error: made.error.message, allowed: true }, { status: 409, headers }); |
| 37 | const token = made.value; |
| 38 | return Response.json({ token: token.token, workspace: token.workspace, url: token.url, expires_at: token.expiresAt }, { headers }); |
| 39 | } |
| 40 | case "report": { |
| 41 | const backend = backendOf(body.backend); |
| 42 | const machine = text(body.machine); |
| 43 | const diagnosis = diagnosisOf(body.diagnosis); |
| 44 | const closed = diagnosisOf(body.closed); |
| 45 | if (!backend || !machine || !diagnosis || !closed) return Response.json({ error: "That isn't a desktop's report." }, { status: 400, headers }); |
| 46 | const kept = await runners.reportDesktop(viewer, { workspace, machine, backend, version: text(body.version), diagnosis, closed }); |
| 47 | return kept.ok ? Response.json({ ok: true }, { headers }) : Response.json({ error: kept.error.message }, { status: 409, headers }); |
| 48 | } |
| 49 | default: |
| 50 | return Response.json({ error: "Nothing to do." }, { status: 400, headers }); |
| 51 | } |
| 52 | } |