Skip to content

Where people and agents ship software together. The open-source git platform for the whole job: issues, agents, checks and deploys to the edge.

g1t.sh
83 linesCodeBlameRaw
1/**
2 * The staff actions that move workspaces off Stripe's test mode before the
3 * live key: one workspace from its page, every workspace from the Stripe
4 * page. Each is previewed first (a dry run) and then confirmed by typing;
5 * billing refuses both on a live key, and checks the confirmation again.
6 */
7import type { TestStripeDetach } from "@g1t/contracts";
8import { data, redirect } from "react-router";
9
10import { text } from "./forms";
11import { admin } from "./services.server";
12import { settle } from "./settle";
13import type { Staff } from "./staff";
14import { CONFIRM_ALL, DEFAULT_EXCLUDED, parseExclude } from "./test-detach";
15
16/** What the workspace page's action returns for a detach. */
17export type WorkspaceDetachData = { testDetach: TestStripeDetach; workspace: string };
18
19/** What the Stripe page's action returns for a detach of every workspace. */
20export type EveryDetachData = { everyDetach?: TestStripeDetach; everyDetachError?: string; exclude: string };
21
22export const WORKSPACE_DETACH_INTENT = "test-detach";
23export const EVERY_DETACH_INTENTS = new Set(["test-detach-preview", "test-detach-all"]);
24
25async function detach(args: Parameters<typeof admin.detachTestStripe>[0]): Promise<{ ok: true; value: TestStripeDetach } | { ok: false; error: string }> {
26 const result = await settle(admin.detachTestStripe(args));
27 if (!result.ok) return { ok: false, error: `Billing did not answer: ${result.error}` };
28 if (!result.value.ok) return { ok: false, error: result.value.error.message };
29 return { ok: true, value: result.value.value };
30}
31
32/** One workspace: a preview, then, confirmed with its slug, the detach. */
33export async function workspaceDetachAction(form: FormData, staff: Staff, slug: string, path: string) {
34 const confirmed = text(form, "confirm") === "yes";
35 const values = { confirmation: "", cancelAtStripe: text(form, "cancelAtStripe") };
36 const failed = (error: string) => data({ error, section: "test-detach", values }, { status: 422 });
37 if (!confirmed) {
38 const preview = await detach({ workspace: slug, dryRun: true, by: staff.email });
39 if (!preview.ok) return failed(preview.error);
40 return { testDetach: preview.value, workspace: slug } satisfies WorkspaceDetachData;
41 }
42 const confirmation = text(form, "confirmation").trim();
43 if (confirmation !== slug) return failed(`Type the workspace's slug, ${slug}, exactly, to detach it.`);
44 const done = await detach({
45 workspace: slug,
46 dryRun: false,
47 confirm: confirmation,
48 cancelAtStripe: values.cancelAtStripe === "on",
49 by: staff.email,
50 });
51 if (!done.ok) return failed(done.error);
52 return redirect(`${path}?done=${done.value.workspaces.length > 0 ? "test-detach" : "test-detach-none"}#top`);
53}
54
55/** Every workspace: a preview with the exclusions, then, with "detach" typed, the detach. */
56export async function everyDetachAction(form: FormData, staff: Staff) {
57 const intent = text(form, "intent");
58 const exclude = text(form, "exclude");
59 const slugs = parseExclude(exclude);
60 if (intent === "test-detach-preview") {
61 const preview = await detach({ exclude: slugs, dryRun: true, by: staff.email });
62 if (!preview.ok) return { everyDetachError: preview.error, exclude } satisfies EveryDetachData;
63 return { everyDetach: preview.value, exclude } satisfies EveryDetachData;
64 }
65 const confirmation = text(form, "confirmation").trim();
66 if (confirmation !== CONFIRM_ALL) {
67 // Show the preview again beside the error, so nothing has to be redone.
68 const preview = await detach({ exclude: slugs, dryRun: true, by: staff.email });
69 const error = `Type ${CONFIRM_ALL} to detach every workspace listed.`;
70 return { everyDetach: preview.ok ? preview.value : undefined, everyDetachError: error, exclude } satisfies EveryDetachData;
71 }
72 const done = await detach({
73 exclude: slugs,
74 dryRun: false,
75 confirm: confirmation,
76 cancelAtStripe: text(form, "cancelAtStripe") === "on",
77 by: staff.email,
78 });
79 if (!done.ok) return { everyDetachError: done.error, exclude } satisfies EveryDetachData;
80 return { everyDetach: done.value, exclude } satisfies EveryDetachData;
81}
82
83export const DEFAULT_EXCLUDE_FIELD = DEFAULT_EXCLUDED.join(", ");