/** * Seed script — populates the live database with enough data to test pagination. * Run with: bun run db:seed * * Safe to run multiple times — skips existing items. */ import { Database } from "bun:sqlite"; import { mkdirSync } from "node:fs"; import path from "node:path"; import * as argon2 from "argon2"; import { ADMIN_USERNAME, DB_PATH, REPOS_DIR } from "../constants.ts"; const db = new Database(DB_PATH); db.run("PRAGMA journal_mode=WAL"); db.run("PRAGMA foreign_keys=ON"); // ── Resolve admin user ──────────────────────────────────────────────────────── const adminRow = db .query<{ id: number }, [string]>("SELECT id FROM users WHERE username = ?") .get(ADMIN_USERNAME); if (!adminRow) { console.error("Admin user not found. Run `bun run db:init` first."); process.exit(1); } const adminId = adminRow.id; // Ensure a second user "alice" exists for variety let aliceId: number; const aliceRow = db .query<{ id: number }, [string]>("SELECT id FROM users WHERE username = ?") .get("alice"); if (aliceRow) { aliceId = aliceRow.id; } else { const hash = await argon2.hash("password123"); const now = new Date().toISOString(); db.run( "INSERT INTO users (username, password_hash, created_at) VALUES (?, ?, ?)", ["alice", hash, now], ); aliceId = db .query<{ id: number }, [string]>( "SELECT id FROM users WHERE username = ?", ) .get("alice")!.id; console.log("Created user alice (password: password123)"); } // ── Helper ──────────────────────────────────────────────────────────────────── function getOrCreateRepo(name: string, description: string): number { const existing = db .query<{ id: number }, [string]>( "SELECT id FROM repositories WHERE name = ?", ) .get(name); if (existing) return existing.id; const now = new Date().toISOString(); db.run( "INSERT INTO repositories (name, description, is_private, default_branch, created_at) VALUES (?, ?, 0, 'main', ?)", [name, description, now], ); const repoPath = path.join(REPOS_DIR, `${name}.git`); mkdirSync(repoPath, { recursive: true }); // init bare repo (sync-ish via Bun.spawnSync) Bun.spawnSync(["git", "init", "--bare", "--initial-branch=main", repoPath]); console.log(`Created repo: ${name}`); return db .query<{ id: number }, [string]>( "SELECT id FROM repositories WHERE name = ?", ) .get(name)!.id; } // ── 25 extra repositories (for repo list pagination) ───────────────────────── const topics = [ "A web framework", "CLI toolkit", "Database driver", "Auth library", "Test runner", "Build system", "Linter plugin", "ORM layer", "Cache client", "Queue worker", "API gateway", "Graph engine", "ML utilities", "Crypto helpers", "File watcher", "Schema validator", "Logger library", "Rate limiter", "Metrics exporter", "Job scheduler", "Config manager", "Template engine", "Markdown parser", "Image resizer", "Email sender", ]; for (let i = 1; i <= 25; i++) { const n = String(i).padStart(2, "0"); getOrCreateRepo(`seed-repo-${n}`, topics[i - 1]!); } // ── "demo" repo — seed issues and patches ──────────────────────────────────── const demoRepoId = getOrCreateRepo( "demo", "Demo repository for pagination testing", ); // Seed 35 issues (mix of open and closed) const issueCount = db .query<{ n: number }, [number]>( "SELECT COUNT(*) as n FROM issues WHERE repo_id = ?", ) .get(demoRepoId)?.n ?? 0; if (issueCount < 35) { const start = issueCount + 1; const issueTitles = [ "Fix null pointer dereference in parser", "Add dark mode support", "Improve error messages", "Upgrade dependencies to latest", "Memory leak in connection pool", "Race condition in event loop", "Add pagination to commit log", "Slow query on large datasets", "Missing CORS headers", "Typo in README", "Refactor authentication middleware", "Support IPv6 addresses", "Add unit tests for utils", "Broken link in documentation", "Config file not loaded on Windows", "Infinite loop when input is empty", "Add rate limiting", "Log rotation broken", "Session cookie not cleared on logout", "Crash on malformed JSON input", "Add export to CSV feature", "Support custom themes", "API returns 500 on edge case", "Update license to MIT", "Improve startup time", "Handle timeout errors gracefully", "Add health check endpoint", "Support environment variables in config", "Fix XSS in search", "Stale cache after update", "Add OpenAPI spec", "Sort order wrong in list view", "Binary file detection false positive", "Column alignment off in table view", "Wrong timezone in timestamps", ]; for (let i = start; i <= 35; i++) { const titleIndex = (i - 1) % issueTitles.length; const status = i <= 25 ? "open" : "closed"; const authorId = i % 3 === 0 ? aliceId : adminId; const now = new Date(Date.now() - (35 - i) * 3600_000).toISOString(); db.run( "INSERT OR IGNORE INTO issues (repo_id, author_id, number, title, body, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?)", [ demoRepoId, authorId, i, issueTitles[titleIndex]!, `Details for issue #${i}.`, status, now, now, ], ); } console.log(`Seeded issues up to #35 in demo repo`); } // Seed 25 patches const patchCount = db .query<{ n: number }, [number]>( "SELECT COUNT(*) as n FROM patches WHERE repo_id = ?", ) .get(demoRepoId)?.n ?? 0; if (patchCount < 25) { const start = patchCount + 1; const SAMPLE_PATCH = [ "diff --git a/placeholder.txt b/placeholder.txt", "new file mode 100644", "index 0000000..e69de29", "--- /dev/null", "+++ b/placeholder.txt", "@@ -0,0 +1 @@", "+placeholder", "", ].join("\n"); const patchTitles = [ "Fix null deref", "Add dark mode", "Improve errors", "Upgrade deps", "Fix memory leak", "Fix race condition", "Add pagination", "Optimise query", "Add CORS headers", "Fix typo", "Refactor auth", "Support IPv6", "Add unit tests", "Fix broken link", "Fix Windows config", "Fix infinite loop", "Add rate limiting", "Fix log rotation", "Fix logout cookie", "Handle bad JSON", "Add CSV export", "Custom themes", "Fix 500 error", "Update license", "Improve startup", ]; for (let i = start; i <= 25; i++) { const titleIndex = (i - 1) % patchTitles.length; const status = i <= 20 ? "open" : "closed"; const authorId = i % 3 === 0 ? aliceId : adminId; const now = new Date(Date.now() - (25 - i) * 3600_000).toISOString(); db.run( "INSERT OR IGNORE INTO patches (repo_id, author_id, number, title, description, patch_content, status, created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)", [ demoRepoId, authorId, i, patchTitles[titleIndex]!, `Description for patch #${i}.`, SAMPLE_PATCH, status, now, now, ], ); } console.log(`Seeded patches up to #25 in demo repo`); } db.close(); console.log("Seed complete.");