Files
web2app/server/db/index.ts

132 lines
3.2 KiB
TypeScript

import Database from "better-sqlite3";
import fs from "node:fs";
import path from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = path.dirname(fileURLToPath(import.meta.url));
export type BuildStatus =
| "pending"
| "queued"
| "in_progress"
| "completed"
| "failed";
export interface BuildRecord {
id: string;
app_name: string;
app_name_en: string;
app_identifier: string;
status: BuildStatus;
workflow_run_id: number | null;
windows_url: string | null;
android_url: string | null;
error: string | null;
created_at: string;
updated_at: string;
}
let db: Database.Database | null = null;
export function getDb(): Database.Database {
if (db) return db;
const dataDir = path.resolve(__dirname, "../../data");
fs.mkdirSync(dataDir, { recursive: true });
const dbPath = path.join(dataDir, "web2app.db");
db = new Database(dbPath);
const schema = fs.readFileSync(path.join(__dirname, "schema.sql"), "utf8");
db.exec(schema);
migrateBuildsTable(db);
return db;
}
function migrateBuildsTable(database: Database.Database) {
const columns = database
.prepare("PRAGMA table_info(builds)")
.all() as Array<{ name: string }>;
if (!columns.some((column) => column.name === "app_name_en")) {
database.exec("ALTER TABLE builds ADD COLUMN app_name_en TEXT NOT NULL DEFAULT ''");
}
}
export function insertBuild(record: {
id: string;
appName: string;
appNameEn: string;
appIdentifier: string;
}): BuildRecord {
const database = getDb();
database
.prepare(
`INSERT INTO builds (id, app_name, app_name_en, app_identifier, status)
VALUES (@id, @appName, @appNameEn, @appIdentifier, 'pending')`,
)
.run(record);
return getBuild(record.id)!;
}
export function getBuild(id: string): BuildRecord | null {
const row = getDb()
.prepare("SELECT * FROM builds WHERE id = ?")
.get(id) as BuildRecord | undefined;
return row ?? null;
}
export function listBuilds(limit = 20): BuildRecord[] {
return getDb()
.prepare("SELECT * FROM builds ORDER BY created_at DESC LIMIT ?")
.all(limit) as BuildRecord[];
}
export function updateBuild(
id: string,
patch: Partial<
Pick<
BuildRecord,
| "status"
| "workflow_run_id"
| "windows_url"
| "android_url"
| "error"
>
>,
): BuildRecord | null {
const fields: string[] = [];
const params: Record<string, unknown> = { id };
for (const [key, value] of Object.entries(patch)) {
if (value !== undefined) {
fields.push(`${key} = @${key}`);
params[key] = value;
}
}
if (fields.length === 0) return getBuild(id);
fields.push("updated_at = datetime('now')");
getDb()
.prepare(`UPDATE builds SET ${fields.join(", ")} WHERE id = @id`)
.run(params);
return getBuild(id);
}
export function toPublicBuild(record: BuildRecord) {
return {
id: record.id,
appName: record.app_name,
appNameEn: record.app_name_en || record.app_name,
appIdentifier: record.app_identifier,
status: record.status,
workflowRunId: record.workflow_run_id,
windowsUrl: record.windows_url,
androidUrl: record.android_url,
error: record.error,
createdAt: record.created_at,
updatedAt: record.updated_at,
};
}