import { all, batch, first, stmt } from "./db";
import { badRequest, conflict, forbidden, notFoundError } from "./errors";
import { canAdministerAccount, loadRepoById } from "./repos";
import { deleteRepository } from "./repo-ops";
import { deleteRunnerRow } from "./ci/runner-registration";
import { disableSite } from "./sites/site";
import { forgetSite, siteDb, sitesOwnedBy } from "./sites/store";
import { accountDb } from "./scoped-db";
import { dropAccountState } from "./account-scope";
import { cancelDomainAddon } from "./billing/domain-addon";
import type { Viewer } from "~/lib/types";
import { assertMayDeleteOrg } from "./verified-writes";
export const MAX_ORG_REPOS_DELETED = 100;
/**
* Deletes an organization. Owners only, with the org name typed as confirmation. Refuses while repositories
* remain unless `deleteRepos`, in which case each goes through `deleteRepository`. The org's current and
* previous names are retired so they can never be re-registered (GIT-9); everything keyed to the account
* (memberships, runners, registration tokens, secrets, sites, projects, LFS usage) cascades with the row.
*/
export async function deleteOrganization(input: {
org: string;
confirm: string;
deleteRepos: boolean;
viewer: Viewer;
}): Promise<{ deletedRepos: string[] }> {
const org = await first<{ id: string; name: string; disabled_at: number | null }>(
"SELECT id, name, disabled_at FROM accounts WHERE name = ? AND kind = 'org'",
input.org,
);
if (!org) throw notFoundError("Organization not found");
if (org.disabled_at && !input.viewer.isAdmin) throw forbidden("This organization is disabled and can't be deleted");
await assertMayDeleteOrg(org.id, input.viewer);
if (!(await canAdministerAccount(org.id, input.viewer))) {
const member = await first(
"SELECT 1 AS ok FROM org_members WHERE org_id = ? AND user_id = ?",
org.id,
input.viewer.id,
);