Files
archived-gitea-ai-assistant/src/db/database.ts
jeffusion d5deb75231 feat(repo): add project-level review prompt with UI redesign
- Add database migration and repository for project review prompts
- Add API endpoint for setting project-level prompts
- Integrate project prompts into Agent and Codex review flows
- Redesign repository management UI with dialog-based prompt editor
- Replace flat buttons with Switch for webhook toggle and dedicated prompt button
- Add Dialog and DropdownMenu UI components from Radix UI
- Add comprehensive tests for wiring and interactions
2026-03-26 13:35:05 +08:00

140 lines
3.9 KiB
TypeScript

/**
* SQLite database initialization and migration runner.
*
* Uses bun:sqlite (zero-dependency, built into Bun runtime).
* Single file at DATA_DIR/assistant.db with WAL mode for concurrent reads.
*/
import { Database } from 'bun:sqlite';
import { mkdirSync } from 'node:fs';
import { dirname, resolve } from 'node:path';
import { migration001Init } from './migrations/001_init';
import { migration002RemoveLegacyReviewMode } from './migrations/002_remove_legacy_review_mode';
import { migration003RepositoryReviewPrompts } from './migrations/003_repository_review_prompts';
// ---------------------------------------------------------------------------
// Types
// ---------------------------------------------------------------------------
export interface Migration {
version: number;
name: string;
up(db: Database): void;
}
// ---------------------------------------------------------------------------
// Migration registry (ordered by version)
// ---------------------------------------------------------------------------
const MIGRATIONS: Migration[] = [
migration001Init,
migration002RemoveLegacyReviewMode,
migration003RepositoryReviewPrompts,
];
// ---------------------------------------------------------------------------
// Database singleton
// ---------------------------------------------------------------------------
let db: Database | null = null;
/**
* Resolve the database file path.
* Defaults to `data/assistant.db` relative to CWD, overridable via `DATABASE_PATH` env.
*/
function getDbPath(): string {
return resolve(process.env.DATABASE_PATH || './data/assistant.db');
}
/**
* Initialize the SQLite database.
* Creates the file and parent directories if needed.
* Enables WAL mode and runs pending migrations.
*
* MUST be called once at application startup.
*/
export function initDatabase(): Database {
if (db) return db;
const dbPath = getDbPath();
const dir = dirname(dbPath);
mkdirSync(dir, { recursive: true });
db = new Database(dbPath);
// Enable WAL mode for better concurrent read performance
db.exec('PRAGMA journal_mode = WAL');
// Enable foreign keys
db.exec('PRAGMA foreign_keys = ON');
// Reasonable busy timeout for concurrent writes
db.exec('PRAGMA busy_timeout = 5000');
// Run migrations
runMigrations(db);
console.log(`📦 Database initialized at ${dbPath}`);
return db;
}
/**
* Get the database instance. Throws if not initialized.
*/
export function getDatabase(): Database {
if (!db) {
throw new Error('Database not initialized. Call initDatabase() at startup.');
}
return db;
}
/**
* Close the database connection gracefully.
*/
export function closeDatabase(): void {
if (db) {
db.close();
db = null;
}
}
// ---------------------------------------------------------------------------
// Migration runner
// ---------------------------------------------------------------------------
/**
* Create the migrations tracking table if it doesn't exist,
* then run any migrations that haven't been applied yet.
*/
function runMigrations(database: Database): void {
// Create migration tracking table
database.exec(`
CREATE TABLE IF NOT EXISTS _migrations (
version INTEGER PRIMARY KEY,
name TEXT NOT NULL,
applied_at TEXT NOT NULL DEFAULT (datetime('now'))
)
`);
// Get already-applied versions
const applied = new Set<number>(
database
.query('SELECT version FROM _migrations ORDER BY version')
.all()
.map((row: any) => row.version as number)
);
// Run pending migrations in order
for (const migration of MIGRATIONS) {
if (applied.has(migration.version)) continue;
console.log(` ⬆️ Running migration ${migration.version}: ${migration.name}`);
database.transaction(() => {
migration.up(database);
database
.query('INSERT INTO _migrations (version, name) VALUES (?, ?)')
.run(migration.version, migration.name);
})();
}
}