import { env } from "cloudflare:workers";
import { all, chunks, first, placeholders } from "../db";
import { accountDb } from "../scoped-db";
import { notFoundError } from "../errors";
import { hostingSuspendedKey } from "./metering";
import { purgeAccountHostingCache } from "./cache-purge";
export type HostingSuspension = { accountId: string; name: string; suspendedAt: number };
async function accountByName(name: string): Promise<{ id: string; name: string }> {
const account = await first<{ id: string; name: string }>(
"SELECT id, name FROM accounts WHERE name = ? COLLATE NOCASE",
name,
);
if (!account) throw notFoundError("No such account");
return account;
}
/** Operator action: stops every Site of the account until resumed; plan changes leave it set. */
export async function suspendHosting(name: string): Promise<string> {
const { id } = await accountByName(name);
await suspendHostingFor(id);
return id;
}
export async function suspendHostingFor(accountId: string): Promise<void> {
const now = Date.now();
await accountDb(accountId).run(
`INSERT INTO account_billing (account_id, hosting_suspended_at, updated_at) VALUES (?1, ?2, ?2)
ON CONFLICT (account_id) DO UPDATE SET hosting_suspended_at = excluded.hosting_suspended_at, updated_at = excluded.updated_at`,
accountId,
now,
);
await env.KV.put(hostingSuspendedKey(accountId), String(now), { metadata: { suspendedAt: now } });
// KV reads are edge-cached for up to a minute; purging the CDN at least stops cached assets from outliving that.
await purgeAccountHostingCache(accountId).catch((error: unknown) =>
console.warn("Hosting cache purge failed", accountId, error),
);
}