Initial commit

This commit is contained in:
admin
2026-05-26 22:46:00 +02:00
commit 7e2c2ff89c
256 changed files with 51523 additions and 0 deletions
+195
View File
@@ -0,0 +1,195 @@
import "server-only";
import path from "node:path";
import { rawDb } from "@/lib/db/client";
import { clearAppSettingsCache } from "@/lib/db/appSettings";
const DB_PATH = path.join(process.cwd(), "data", "library.db");
const WIPE_ORDER = [
"actress_categories_map",
"collection_images",
"image_tags",
"image_genres",
"image_actresses",
"tags",
"tag_categories",
"genres",
"actresses",
"actress_categories",
"collections",
"series",
"labels",
"studios",
"images",
"app_settings",
];
const INSERT_ORDER = [
"studios",
"labels",
"series",
"actresses",
"genres",
"tag_categories",
"tags",
"actress_categories",
"images",
"collections",
"image_actresses",
"image_genres",
"image_tags",
"collection_images",
"actress_categories_map",
"app_settings",
];
function escIdent(s: string): string {
return `"${s.replace(/"/g, '""')}"`;
}
function tableColumns(schema: "main" | "restore", table: string): string[] {
try {
const rows = rawDb.prepare(`PRAGMA ${schema}.table_info(${escIdent(table)})`).all() as Array<{ name: string }>;
return rows.map((r) => r.name);
} catch {
return [];
}
}
export type ImportDbResult = {
ok: boolean;
counts: Record<string, number>;
errors: Array<{ table: string; message: string }>;
snapshotPath: string | null;
error?: string;
};
export async function importDatabaseTables(
tables: Record<string, unknown[]>,
): Promise<ImportDbResult> {
const counts: Record<string, number> = {};
const errors: Array<{ table: string; message: string }> = [];
let snapshotPath: string | null = null;
try {
const ts = new Date().toISOString().replace(/[:.]/g, "-");
snapshotPath = `${DB_PATH}.${ts}.bak`;
await rawDb.backup(snapshotPath);
} catch (e) {
return {
ok: false,
counts,
errors,
snapshotPath: null,
error: `Failed to snapshot DB before import: ${(e as Error).message}`,
};
}
rawDb.pragma("foreign_keys = OFF");
try {
const tx = rawDb.transaction(() => {
for (const t of WIPE_ORDER) {
try {
rawDb.prepare(`DELETE FROM ${escIdent(t)}`).run();
rawDb.prepare(`DELETE FROM sqlite_sequence WHERE name = ?`).run(t);
} catch (e) {
const msg = (e as Error).message ?? "";
if (!/no such table/i.test(msg)) {
throw new Error(`Wipe failed on ${t}: ${msg}`);
}
}
}
for (const t of INSERT_ORDER) {
const rows = tables[t];
if (!Array.isArray(rows) || rows.length === 0) {
counts[t] = 0;
continue;
}
const sample = rows[0] as Record<string, unknown>;
const cols = Object.keys(sample);
if (cols.length === 0) {
counts[t] = 0;
continue;
}
const colList = cols.map(escIdent).join(",");
const placeholders = cols.map(() => "?").join(",");
const stmt = rawDb.prepare(
`INSERT INTO ${escIdent(t)} (${colList}) VALUES (${placeholders})`,
);
let inserted = 0;
for (let rowIndex = 0; rowIndex < rows.length; rowIndex++) {
const r = rows[rowIndex];
if (!r || typeof r !== "object") continue;
const row = r as Record<string, unknown>;
const values = cols.map((c) => {
const v = row[c];
if (v === undefined) return null;
if (typeof v === "boolean") return v ? 1 : 0;
return v as null | string | number | bigint | Buffer;
});
try {
stmt.run(...values);
inserted++;
} catch (e) {
const message = `Insert failed on ${t} row ${rowIndex + 1}: ${(e as Error).message}`;
errors.push({ table: t, message });
throw new Error(message);
}
}
counts[t] = inserted;
}
});
tx();
} catch (e) {
rawDb.pragma("foreign_keys = ON");
return {
ok: false,
counts,
errors,
snapshotPath,
error: (e as Error).message,
};
}
rawDb.pragma("foreign_keys = ON");
clearAppSettingsCache();
return { ok: true, counts, errors, snapshotPath };
}
export async function restoreDatabaseSnapshot(snapshotPath: string): Promise<void> {
rawDb.pragma("foreign_keys = OFF");
try {
rawDb.prepare("ATTACH DATABASE ? AS restore").run(snapshotPath);
const tx = rawDb.transaction(() => {
for (const t of WIPE_ORDER) {
try {
rawDb.prepare(`DELETE FROM ${escIdent(t)}`).run();
rawDb.prepare(`DELETE FROM sqlite_sequence WHERE name = ?`).run(t);
} catch (e) {
const msg = (e as Error).message ?? "";
if (!/no such table/i.test(msg)) throw e;
}
}
for (const t of INSERT_ORDER) {
const mainCols = tableColumns("main", t);
const restoreCols = new Set(tableColumns("restore", t));
const cols = mainCols.filter((c) => restoreCols.has(c));
if (cols.length === 0) continue;
const colList = cols.map(escIdent).join(",");
rawDb.prepare(`
INSERT INTO ${escIdent(t)} (${colList})
SELECT ${colList} FROM restore.${escIdent(t)}
`).run();
}
});
tx();
} finally {
try {
rawDb.prepare("DETACH DATABASE restore").run();
} catch {}
rawDb.pragma("foreign_keys = ON");
clearAppSettingsCache();
}
}