118 lines
4.2 KiB
JavaScript
118 lines
4.2 KiB
JavaScript
// Populates the database from data-pipeline/seed/manifest.json.
|
|
// Run after `npm run build:seed-models` and after backend Prisma migrations
|
|
// have been applied (this script uses plain SQL, not Prisma, so the
|
|
// data-pipeline stays independent of the backend's toolchain).
|
|
import { readFileSync } from "node:fs";
|
|
import path from "node:path";
|
|
import { fileURLToPath } from "node:url";
|
|
import pg from "pg";
|
|
import { SYSTEMS as systems } from "../seed/systems.mjs";
|
|
|
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
const manifestPath = path.resolve(__dirname, "../seed/manifest.json");
|
|
const { structures } = JSON.parse(readFileSync(manifestPath, "utf-8"));
|
|
|
|
const connectionString = process.env.DATABASE_URL;
|
|
if (!connectionString) {
|
|
console.error("DATABASE_URL não definida.");
|
|
process.exit(1);
|
|
}
|
|
|
|
const client = new pg.Client({ connectionString });
|
|
await client.connect();
|
|
|
|
// The "api" service applies Prisma migrations on startup; if this pipeline
|
|
// runs first (e.g. right after `docker compose up`), wait for the table.
|
|
async function waitForTable(name, maxAttempts = 30) {
|
|
for (let attempt = 1; attempt <= maxAttempts; attempt++) {
|
|
const { rows } = await client.query(
|
|
`SELECT to_regclass($1) AS exists`,
|
|
[name],
|
|
);
|
|
if (rows[0]?.exists) return;
|
|
console.log(`Aguardando migrations do backend criarem "${name}" (${attempt}/${maxAttempts})...`);
|
|
await new Promise((r) => setTimeout(r, 2000));
|
|
}
|
|
throw new Error(`Tabela "${name}" não apareceu — rode "docker compose up -d api" antes do pipeline.`);
|
|
}
|
|
|
|
await waitForTable("structures");
|
|
|
|
function cuid(prefix) {
|
|
return `${prefix}_${Math.random().toString(36).slice(2, 10)}${Date.now().toString(36)}`;
|
|
}
|
|
|
|
try {
|
|
await client.query("BEGIN");
|
|
|
|
const systemIds = new Map();
|
|
for (const s of systems) {
|
|
const id = cuid("sys");
|
|
systemIds.set(s.slug, id);
|
|
await client.query(
|
|
`INSERT INTO anatomical_systems (id, slug, "namePt", "nameEn", "colorHex", "order")
|
|
VALUES ($1,$2,$3,$4,$5,$6)
|
|
ON CONFLICT (slug) DO UPDATE SET "namePt"=$3, "nameEn"=$4, "colorHex"=$5, "order"=$6
|
|
RETURNING id`,
|
|
[id, s.slug, s.namePt, s.nameEn, s.colorHex, s.order],
|
|
);
|
|
}
|
|
// Re-read ids (in case of ON CONFLICT they may differ from freshly generated ones).
|
|
const { rows: existingSystems } = await client.query(
|
|
`SELECT id, slug FROM anatomical_systems`,
|
|
);
|
|
for (const row of existingSystems) systemIds.set(row.slug, row.id);
|
|
|
|
for (const st of structures) {
|
|
const systemId = systemIds.get(st.system);
|
|
if (!systemId) throw new Error(`Sistema desconhecido: ${st.system}`);
|
|
await client.query(
|
|
`INSERT INTO structures (id, "fmaId", slug, "namePt", "nameEn", synonyms, description, "regionPath", "modelFile", "meshName", "systemId", "updatedAt")
|
|
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11, now())
|
|
ON CONFLICT (slug) DO UPDATE SET
|
|
"namePt"=$4, "nameEn"=$5, synonyms=$6, description=$7, "regionPath"=$8,
|
|
"modelFile"=$9, "meshName"=$10, "systemId"=$11, "updatedAt"=now()`,
|
|
[
|
|
cuid("str"),
|
|
null,
|
|
st.slug,
|
|
st.namePt,
|
|
st.nameEn,
|
|
st.synonyms ?? [],
|
|
st.description ?? null,
|
|
st.regionPath ?? [],
|
|
st.modelFile,
|
|
st.meshName,
|
|
systemId,
|
|
],
|
|
);
|
|
}
|
|
|
|
// O manifesto de cada execução é sempre completo (cobre todos os
|
|
// sistemas) — qualquer coisa no banco cujo slug não está mais nele é
|
|
// obsoleta (ex: itens excluídos por um filtro/curadoria novos) e deve
|
|
// ser removida, senão elas só se acumulam a cada execução do pipeline.
|
|
const currentSlugs = structures.map((s) => s.slug);
|
|
await client.query(
|
|
`DELETE FROM quiz_cards WHERE "structureId" IN (
|
|
SELECT id FROM structures WHERE NOT (slug = ANY($1::text[]))
|
|
)`,
|
|
[currentSlugs],
|
|
);
|
|
const { rowCount: removed } = await client.query(
|
|
`DELETE FROM structures WHERE NOT (slug = ANY($1::text[]))`,
|
|
[currentSlugs],
|
|
);
|
|
|
|
await client.query("COMMIT");
|
|
console.log(
|
|
`Seed concluído: ${systems.length} sistemas, ${structures.length} estruturas (${removed} obsoletas removidas).`,
|
|
);
|
|
} catch (err) {
|
|
await client.query("ROLLBACK");
|
|
console.error(err);
|
|
process.exit(1);
|
|
} finally {
|
|
await client.end();
|
|
}
|