mirror of
https://github.com/tiennm99/bsk.git
synced 2026-09-02 14:19:52 +00:00
- one strict checkJs jsconfig.json replaces tsconfig.json (keeps noUncheckedIndexedAccess); typecheck script now tsc -p jsconfig.json - next.config.ts -> next.config.mjs; vitest/playwright configs -> .js - db scripts run under plain node as .mjs; tsx devDep removed - generated Supabase types move to types/supabase-bsk.d.ts and db:gen-types redirects there - eslint drops eslint-config-next/typescript, adds eslint-plugin-jsdoc; factory-boundary no-restricted-imports globs now target .js
95 lines
3.2 KiB
JavaScript
95 lines
3.2 KiB
JavaScript
#!/usr/bin/env node
|
|
/**
|
|
* Seed Vietnamese geo lookup (bsk.provinces + bsk.wards).
|
|
*
|
|
* Idempotent upsert from a local dataset — safe to re-run. Run once after
|
|
* `npm run db:push`, before using patient registration (the address dropdowns
|
|
* read these tables).
|
|
*
|
|
* Data file: supabase/seed/vn-geo.json (NOT committed — it's large and its
|
|
* source/version is the operator's choice). Expected shape:
|
|
* [ { "code": "01", "name": "Hà Nội",
|
|
* "wards": [ { "code": "00001", "name": "Phường Phúc Xá" }, ... ] }, ... ]
|
|
* Post-2025 Vietnam is a two-level system (province → ward, no districts),
|
|
* which matches this schema. A public source: https://provinces.open-api.vn/.
|
|
*
|
|
* Env required (not read from .env.local automatically — pass explicitly):
|
|
* NEXT_PUBLIC_SUPABASE_URL, SUPABASE_SECRET_KEY
|
|
* Example:
|
|
* NEXT_PUBLIC_SUPABASE_URL=... SUPABASE_SECRET_KEY=... npm run db:seed-geo
|
|
*/
|
|
|
|
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
import { createClient } from "@supabase/supabase-js";
|
|
|
|
/** @typedef {{ code: string, name: string }} Ward */
|
|
/** @typedef {{ code: string, name: string, wards: Ward[] }} Province */
|
|
|
|
/**
|
|
* @param {string} msg
|
|
* @returns {never}
|
|
*/
|
|
function die(msg) {
|
|
process.stderr.write(`\n[seed-geo] ${msg}\n\n`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const url = process.env.NEXT_PUBLIC_SUPABASE_URL;
|
|
const key = process.env.SUPABASE_SECRET_KEY;
|
|
if (!url || !key) {
|
|
die("Missing NEXT_PUBLIC_SUPABASE_URL / SUPABASE_SECRET_KEY in the environment.");
|
|
}
|
|
|
|
const dataPath = resolve(process.cwd(), "supabase/seed/vn-geo.json");
|
|
/** @type {Province[]} */
|
|
let provinces;
|
|
try {
|
|
provinces = JSON.parse(readFileSync(dataPath, "utf8"));
|
|
} catch {
|
|
die(
|
|
`Dataset not found at supabase/seed/vn-geo.json.\n` +
|
|
`Download a Vietnamese province+ward dataset (e.g. from https://provinces.open-api.vn/)\n` +
|
|
`and save it there in the { code, name, wards:[{code,name}] } shape.`,
|
|
);
|
|
}
|
|
if (!Array.isArray(provinces) || provinces.length === 0) {
|
|
die("Dataset is empty or not an array.");
|
|
}
|
|
|
|
const supabase = createClient(url, key, { db: { schema: "bsk" }, auth: { persistSession: false } });
|
|
|
|
/**
|
|
* @template T
|
|
* @param {"provinces" | "wards"} table
|
|
* @param {T[]} rows
|
|
* @param {number} [size]
|
|
* @returns {Promise<void>}
|
|
*/
|
|
async function upsertBatched(table, rows, size = 500) {
|
|
for (let i = 0; i < rows.length; i += size) {
|
|
const batch = rows.slice(i, i + size);
|
|
const { error } = await supabase.from(table).upsert(/** @type {never[]} */ (batch));
|
|
if (error) die(`Upsert into ${table} failed at row ${i}: ${error.message}`);
|
|
process.stdout.write(
|
|
`[seed-geo] ${table}: ${Math.min(i + size, rows.length)}/${rows.length}\n`,
|
|
);
|
|
}
|
|
}
|
|
|
|
async function main() {
|
|
const provinceRows = provinces.map((p) => ({ code: p.code, name: p.name }));
|
|
const wardRows = provinces.flatMap((p) =>
|
|
(p.wards ?? []).map((w) => ({ code: w.code, province_code: p.code, name: w.name })),
|
|
);
|
|
|
|
process.stdout.write(
|
|
`[seed-geo] seeding ${provinceRows.length} provinces, ${wardRows.length} wards…\n`,
|
|
);
|
|
await upsertBatched("provinces", provinceRows);
|
|
await upsertBatched("wards", wardRows);
|
|
process.stdout.write("[seed-geo] done.\n");
|
|
}
|
|
|
|
main().catch((e) => die(String(e)));
|