chore: 初始化仓库

中华文明全图鉴——文物全图系统(PC Web 地图 + NestJS API + 管理后台)。
含三大 IP(文物南迁北归 / 国宝海外回归 / 博物馆手艺人)、AI 文物对话、
文物地图与详情、以及 demo-video-kit 演示视频生成工具。
This commit is contained in:
selfrelease
2026-06-13 20:55:44 +08:00
commit 2d847e154f
161 changed files with 22629 additions and 0 deletions
+3
View File
@@ -0,0 +1,3 @@
export { runMigrations } from "./migrate";
export { runSeeds } from "./seed";
export { getPool } from "./pool";
+8
View File
@@ -0,0 +1,8 @@
import { runMigrations } from "./migrate";
runMigrations()
.then(() => process.exit(0))
.catch((err) => {
console.error("[migration] 失败:", err);
process.exit(1);
});
+40
View File
@@ -0,0 +1,40 @@
import * as fs from "fs";
import * as path from "path";
import { getPool } from "./pool";
export async function runMigrations(): Promise<void> {
const pool = getPool();
const migrationsDir = path.resolve(__dirname, "../migrations");
const files = fs
.readdirSync(migrationsDir)
.filter((f) => f.endsWith(".sql"))
.sort();
await pool.query(`
CREATE TABLE IF NOT EXISTS _migrations (
id SERIAL PRIMARY KEY,
filename VARCHAR(255) UNIQUE NOT NULL,
applied_at TIMESTAMPTZ DEFAULT NOW()
)
`);
for (const file of files) {
const row = await pool.query(
"SELECT id FROM _migrations WHERE filename = $1",
[file]
);
if (row.rows.length > 0) {
console.log(`[migration] skip ${file} (already applied)`);
continue;
}
const sql = fs.readFileSync(path.join(migrationsDir, file), "utf-8");
console.log(`[migration] apply ${file}`);
await pool.query(sql);
await pool.query("INSERT INTO _migrations (filename) VALUES ($1)", [file]);
console.log(`[migration] done ${file}`);
}
console.log("[migration] all migrations complete");
await pool.end();
}
+16
View File
@@ -0,0 +1,16 @@
import { Pool } from "pg";
import * as dotenv from "dotenv";
import * as path from "path";
dotenv.config({ path: path.resolve(__dirname, "../../../.env") });
let pool: Pool | null = null;
export function getPool(): Pool {
if (!pool) {
const url = process.env["DATABASE_URL"];
if (!url) throw new Error("DATABASE_URL 未设置");
pool = new Pool({ connectionString: url });
}
return pool;
}
+8
View File
@@ -0,0 +1,8 @@
import { runSeeds } from "./seed";
runSeeds()
.then(() => process.exit(0))
.catch((err) => {
console.error("[seed] 失败:", err);
process.exit(1);
});
+23
View File
@@ -0,0 +1,23 @@
import * as fs from "fs";
import * as path from "path";
import { getPool } from "./pool";
export async function runSeeds(): Promise<void> {
const pool = getPool();
const seedsDir = path.resolve(__dirname, "../seeds");
const files = fs
.readdirSync(seedsDir)
.filter((f) => f.endsWith(".sql"))
.sort();
for (const file of files) {
const sql = fs.readFileSync(path.join(seedsDir, file), "utf-8");
console.log(`[seed] apply ${file}`);
await pool.query(sql);
console.log(`[seed] done ${file}`);
}
console.log("[seed] all seeds complete");
await pool.end();
}