79 lines · 2.6 KB
import { env } from "cloudflare:workers";
import { batch, first, stmt } from "./db";
import { hashPassword, randomId } from "./crypto";
import { badRequest, conflict } from "./errors";

export async function accountNameTaken(name: string): Promise<boolean> {
  return (await first<{ id: string }>("SELECT id FROM accounts WHERE name = ?", name)) !== null;
}

/** A claimed name stops redirecting to its previous owner. */
function releaseNameRedirects(name: string): D1PreparedStatement[] {
  return [
    stmt("DELETE FROM account_redirects WHERE old_name = ?", name),
    stmt("DELETE FROM repo_redirects WHERE owner_name = ?", name),
  ];
}

export function signupsEnabled(): boolean {
  return env.SIGNUPS_ENABLED !== "false";
}

export function githubOAuthEnabled(): boolean {
  return Boolean(env.GITHUB_CLIENT_ID && env.GITHUB_CLIENT_SECRET);
}

export async function createUser(input: {
  name: string;
  email: string;
  password: string | null;
  githubId: string | null;
  displayName: string | null;
}): Promise<string> {
  if (await accountNameTaken(input.name)) throw conflict("That username is already taken");
  if (await first<{ account_id: string }>("SELECT account_id FROM users WHERE email = ?", input.email)) {
    throw conflict("An account with that email already exists");
  }
  const isFirstUser = (await first<{ count: number }>("SELECT COUNT(*) AS count FROM users"))?.count === 0;
  if (!isFirstUser && !signupsEnabled()) throw badRequest("Sign ups are disabled on this instance");
  const id = randomId();
  const passwordHash = input.password ? await hashPassword(input.password) : null;