92 lines · 3.9 KB
import { all } from "./db";
import { accountDb, repoDb } from "./scoped-db";
import { backfillDone, backfillRepos } from "./backfill";

/**
 * Attachments live in each repository's RepoState but quotas are per account. Each account's AccountState keeps
 * `attachment_bytes`: per repository, the bytes it owns (quota owner) and uploaded. Entries are recomputed from the
 * repository's rows (never incremented), so a retry or the backfill can't double-count.
 */
export const ATTACHMENT_BYTES = "attachment-bytes";

/** Recomputes `accountIds`' entries for one repository from its attachment rows. */
export async function syncAttachmentBytes(repoId: string, accountIds: Iterable<string>): Promise<void> {
  const db = repoDb(repoId);
  await Promise.all(
    [...new Set(accountIds)].map(async (accountId) => {
      const row = await db.first<{ owned: number | null; uploaded: number | null }>(
        `SELECT (SELECT SUM(size) FROM attachments WHERE owner_id = ?1) AS owned,
                (SELECT SUM(size) FROM attachments WHERE uploader_id = ?1) AS uploaded`,
        accountId,
      );
      const owned = row?.owned ?? 0;
      const uploaded = row?.uploaded ?? 0;
      const account = accountDb(accountId);
      if (owned === 0 && uploaded === 0) {
        await account.run("DELETE FROM attachment_bytes WHERE repo_id = ?", repoId);
        return;
      }
      await account.run(
        `INSERT INTO attachment_bytes (repo_id, owned, uploaded) VALUES (?, ?, ?)
         ON CONFLICT (repo_id) DO UPDATE SET owned = excluded.owned, uploaded = excluded.uploaded`,
        repoId,
        owned,
        uploaded,
      );
    }),
  );
}

/** Before a repository's object is destroyed: its bytes stop counting for everyone who had them. */