225 lines · 9.3 KB
import { env } from "cloudflare:workers";
import { all, batch, chunks, first, placeholders, run, stmt } from "../db";
import { HttpError } from "../errors";
import { loadRepoById } from "../repos";
import { customDomainsEnabled, deleteCustomHostname } from "../pages/domains";
import { customHostKey, labelUrl, metaKey, publishMeta, sitesDomain, slotKey } from "./config";
import { assertLabelFree, loadSiteById, loadSiteForRepo } from "./deploy";
import { siteLabel } from "./naming";
import { refreshSiteDomain } from "./site";
import { unpublish as unpublishPages } from "../pages/deploy";
import type { SiteMeta, SiteRow } from "./config";

const OWNER_BATCH = 200;
const RELABEL_BLOCKED = "Site host";
const RECONCILE_BATCH = 100;
const DOMAIN_BATCH = 10;
const META_PAGE = 500;
const CURSOR_KEY = "sites:reconcile:cursor";
const META_CURSOR_KEY = "sites:reconcile:meta-cursor";

/** Unpublishes everything a site serves (meta, every label, custom host, SaaS hostname) from KV-only state. */
async function teardown(
  meta: Pick<SiteMeta, "id" | "customDomain" | "customHostnameId">,
  labels: string[],
): Promise<void> {
  await Promise.all(labels.map((label) => env.KV.delete(slotKey(label))));
  if (meta.customDomain) await env.KV.delete(customHostKey(meta.customDomain));
  if (meta.customHostnameId && customDomainsEnabled()) {
    await deleteCustomHostname(meta.customHostnameId).catch((error: unknown) =>
      console.warn("Custom hostname cleanup failed", meta.id, error),
    );
  }
  await env.KV.delete(metaKey(meta.id));
}

async function servedLabels(site: SiteRow): Promise<string[]> {
  const rows = await all<{ label: string }>(
    "SELECT DISTINCT label FROM site_deployments WHERE site_id = ? AND status = 'active' LIMIT 500",
    site.id,
  );