- Debian-based api image (bookworm-slim), docker/debian mirrors, prisma binaryTargets for openssl 3.0 - nginx: admin SPA under /admin, TLS via acme.sh (ZeroSSL) + auto-renewal cron, http->https redirect - prisma: add origin_goods.delisted migration, sync missing schema (good_image/tag_font_color/good_tags), fix users.createdAt Timestamptz - api: CORS wildcard reflection, helmet CORP cross-origin, price backfill in persistProductDetail, categoryIcon ancestor fallback, mediaByColor per-color gallery in public goods detail - admin: /admin base path (vite + router) - import-data.mjs: udt_name casting, serial sequence advance fix
107 lines
3.5 KiB
JavaScript
107 lines
3.5 KiB
JavaScript
/**
|
|
* Import a dump produced by export-data.mjs into the current database.
|
|
* Tables are truncated first (order-independent via session_replication_role)
|
|
* and columns are cast back to their real types using information_schema.
|
|
*
|
|
* Usage (inside the api container):
|
|
* node scripts/import-data.mjs <dump.json>
|
|
*/
|
|
import { PrismaClient, Prisma } from '@prisma/client';
|
|
import { readFileSync } from 'node:fs';
|
|
|
|
const prisma = new PrismaClient();
|
|
|
|
function toLiteral(value, udtName) {
|
|
if (value === null) return Prisma.sql`NULL`;
|
|
const target = Prisma.raw(`"${udtName}"`);
|
|
switch (udtName) {
|
|
case 'int2':
|
|
case 'int4':
|
|
case 'int8':
|
|
return Prisma.sql`${BigInt(value)}::${target}`;
|
|
case 'float4':
|
|
case 'float8':
|
|
case 'numeric':
|
|
return Prisma.sql`${Number(value)}::${target}`;
|
|
case 'bool':
|
|
return Prisma.sql`${!!value}::${target}`;
|
|
case 'timestamptz':
|
|
case 'timestamp':
|
|
return Prisma.sql`${new Date(value).toISOString()}::${target}`;
|
|
case 'date':
|
|
return Prisma.sql`${String(value)}::${target}`;
|
|
case 'jsonb':
|
|
case 'json':
|
|
return Prisma.sql`${typeof value === 'string' ? value : JSON.stringify(value)}::${target}`;
|
|
case 'bytea':
|
|
return Prisma.sql`${Buffer.from(value, 'base64')}::bytea`;
|
|
default:
|
|
// text, varchar, enums and anything else: pass as text and cast
|
|
return Prisma.sql`${String(value)}::${target}`;
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
const file = process.argv[2];
|
|
if (!file) {
|
|
console.error('Usage: node scripts/import-data.mjs <dump.json>');
|
|
process.exit(1);
|
|
}
|
|
const dump = JSON.parse(readFileSync(file, 'utf8'));
|
|
|
|
// Suspend FK checks during bulk load (postgres superuser not required for
|
|
// session_replication_role in the compose postgres where app user owns db).
|
|
await prisma.$executeRawUnsafe(`SET session_replication_role = replica`);
|
|
|
|
const summary = {};
|
|
for (const [table, rows] of Object.entries(dump.tables)) {
|
|
if (rows.length === 0) {
|
|
summary[table] = 0;
|
|
continue;
|
|
}
|
|
await prisma.$executeRawUnsafe(`TRUNCATE TABLE "${table}" CASCADE`);
|
|
|
|
const colTypes = {};
|
|
const info = await prisma.$queryRawUnsafe(
|
|
`SELECT column_name, udt_name FROM information_schema.columns WHERE table_name = '${table}'`,
|
|
);
|
|
for (const c of info) colTypes[c.column_name] = c.udt_name;
|
|
|
|
const columns = Object.keys(rows[0]);
|
|
const colList = Prisma.raw(columns.map((c) => `"${c}"`).join(', '));
|
|
const CHUNK = 200;
|
|
for (let i = 0; i < rows.length; i += CHUNK) {
|
|
const tuples = rows.slice(i, i + CHUNK).map(
|
|
(r) =>
|
|
Prisma.sql`(${Prisma.join(
|
|
columns.map((c) => toLiteral(r[c], colTypes[c])),
|
|
)})`,
|
|
);
|
|
await prisma.$executeRaw(
|
|
Prisma.sql`INSERT INTO ${Prisma.raw(`"${table}"`)} (${colList}) VALUES ${Prisma.join(tuples)}`,
|
|
);
|
|
}
|
|
// Keep sequences ahead of imported serial ids
|
|
// Keep serial sequences ahead of imported ids
|
|
const idCol = columns.find(
|
|
(c) => c === 'id' || c === `${table.replace(/s$/, '')}_id`,
|
|
);
|
|
if (idCol) {
|
|
await prisma.$executeRawUnsafe(
|
|
`SELECT setval(pg_get_serial_sequence('"${table}"', '${idCol}'), COALESCE((SELECT MAX("${idCol}") FROM "${table}"), 1))`,
|
|
);
|
|
}
|
|
summary[table] = rows.length;
|
|
}
|
|
|
|
await prisma.$executeRawUnsafe(`SET session_replication_role = DEFAULT`);
|
|
console.log('Imported:', JSON.stringify(summary, null, 2));
|
|
}
|
|
|
|
main()
|
|
.catch((e) => {
|
|
console.error(e);
|
|
process.exit(1);
|
|
})
|
|
.finally(() => prisma.$disconnect());
|