All files / src/lib/db project-aliases.ts

100% Statements 29/29
100% Branches 11/11
100% Functions 6/6
100% Lines 29/29

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118                                    107x   107x               356x 356x   356x 356x   356x           356x 646x                     356x           194x   194x       194x 87x     107x   107x 107x 246x           107x               390x   390x       390x 123x       267x         57x     210x   210x             143x 143x    
/**
 * Project aliases storage (A, B, C -> org/project) for short issue ID resolution.
 */
 
import type { ProjectAliasEntry } from "../../types/index.js";
import { getDatabase, maybeCleanupCaches } from "./index.js";
import { touchCacheEntry } from "./utils.js";
 
type ProjectAliasRow = {
  alias: string;
  org_slug: string;
  project_slug: string;
  dsn_fingerprint: string | null;
  cached_at: number;
  last_accessed: number;
};
 
function touchAliasEntries(): void {
  const db = getDatabase();
  // biome-ignore lint/plugin: global touch — updates ALL rows, not a single keyed entry
  db.query("UPDATE project_aliases SET last_accessed = ?").run(Date.now());
}
 
/** Set project aliases, replacing all existing ones. */
export function setProjectAliases(
  aliases: Record<string, ProjectAliasEntry>,
  dsnFingerprint?: string
): void {
  const db = getDatabase();
  const now = Date.now();
 
  db.transaction(() => {
    db.query("DELETE FROM project_aliases").run();
 
    const insertStmt = db.query(`
      INSERT INTO project_aliases 
      (alias, org_slug, project_slug, dsn_fingerprint, cached_at, last_accessed)
      VALUES (?, ?, ?, ?, ?, ?)
    `);
 
    for (const [alias, entry] of Object.entries(aliases)) {
      insertStmt.run(
        alias.toLowerCase(),
        entry.orgSlug,
        entry.projectSlug,
        dsnFingerprint ?? null,
        now,
        now
      );
    }
  })();
 
  maybeCleanupCaches();
}
 
export function getProjectAliases():
  | Record<string, ProjectAliasEntry>
  | undefined {
  const db = getDatabase();
 
  const rows = db
    .query("SELECT * FROM project_aliases")
    .all() as ProjectAliasRow[];
 
  if (rows.length === 0) {
    return;
  }
 
  touchAliasEntries();
 
  const aliases: Record<string, ProjectAliasEntry> = {};
  for (const row of rows) {
    aliases[row.alias] = {
      orgSlug: row.org_slug,
      projectSlug: row.project_slug,
    };
  }
 
  return aliases;
}
 
/** Get project by alias. Validates DSN fingerprint if both current and cached are present. */
export function getProjectByAlias(
  alias: string,
  currentFingerprint?: string
): ProjectAliasEntry | undefined {
  const db = getDatabase();
 
  const row = db
    .query("SELECT * FROM project_aliases WHERE alias = ?")
    .get(alias.toLowerCase()) as ProjectAliasRow | undefined;
 
  if (!row) {
    return;
  }
 
  // Empty string is a valid fingerprint (no SaaS DSNs)
  if (
    currentFingerprint !== undefined &&
    row.dsn_fingerprint !== null &&
    currentFingerprint !== row.dsn_fingerprint
  ) {
    return;
  }
 
  touchCacheEntry("project_aliases", "alias", alias.toLowerCase());
 
  return {
    orgSlug: row.org_slug,
    projectSlug: row.project_slug,
  };
}
 
export function clearProjectAliases(): void {
  const db = getDatabase();
  db.query("DELETE FROM project_aliases").run();
}