mirror of
https://github.com/tiennm99/bsk.git
synced 2026-08-09 00:24:19 +00:00
CI has been red on every push: the workflow runs `pnpm format:check`, which I never included in local validation (I only gated typecheck/lint/build), so 54 files were committed unformatted. - ran `pnpm format` — all changes are Prettier line-reflow, no logic touched (verified: `git diff -w` flags exactly the same 54 files CI reported) - added .gitattributes (`* text=auto eol=lf` + binary rules for fonts/images): with core.autocrlf and no attributes file, `format:check` counted 141 files locally vs 54 in CI, which is what let this slip through All six CI gates now pass locally in workflow order: secret-leak, format:check, test (97), lint, typecheck, build.
87 lines
3.0 KiB
TypeScript
87 lines
3.0 KiB
TypeScript
#!/usr/bin/env tsx
|
|
/**
|
|
* Seed Vietnamese geo lookup (bsk.provinces + bsk.wards).
|
|
*
|
|
* Idempotent upsert from a local dataset — safe to re-run. Run once after
|
|
* `pnpm 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=... pnpm db:seed-geo
|
|
*/
|
|
|
|
import { readFileSync } from "node:fs";
|
|
import { resolve } from "node:path";
|
|
import { createClient } from "@supabase/supabase-js";
|
|
|
|
type Ward = { code: string; name: string };
|
|
type Province = { code: string; name: string; wards: Ward[] };
|
|
|
|
function die(msg: string): never {
|
|
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");
|
|
let provinces: Province[];
|
|
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 } });
|
|
|
|
async function upsertBatched<T>(
|
|
table: "provinces" | "wards",
|
|
rows: T[],
|
|
size = 500,
|
|
): Promise<void> {
|
|
for (let i = 0; i < rows.length; i += size) {
|
|
const batch = rows.slice(i, i + size);
|
|
const { error } = await supabase.from(table).upsert(batch as never[]);
|
|
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)));
|