90 lines
5.2 KiB
JavaScript
90 lines
5.2 KiB
JavaScript
|
|
// แปลง files.external_id ของเอกสาร e-Court จาก fid ดิบ ("ecourt:<fid>") เป็น hash
|
||
|
|
// ("ecourt:" + HMAC-SHA256(pepper, fid)) — รันครั้งเดียวตอน roll การเปลี่ยนของ
|
||
|
|
// lib/ecourt-fid.ts (2026-11-04) + เก็บ title default ที่ฝัง fid ให้เป็นค่ากลางใหม่
|
||
|
|
//
|
||
|
|
// ทำเป็น Node script ไม่ใช่ SQL ใน drizzle/ เพราะ pepper ต้องมาจาก env ของเครื่อง
|
||
|
|
// (ห้าม commit ค่าลับลง migration) · สูตร hash ต้องตรงกับ lib/ecourt-fid.ts เป๊ะ
|
||
|
|
// (HMAC-SHA256 key=pepper บน fid.trim() → hex — script .mjs import TS ไม่ได้จึงมี
|
||
|
|
// สำเนาสูตรตรงนี้ แก้ที่ใดต้องแก้อีกทางคู่กัน)
|
||
|
|
//
|
||
|
|
// idempotent — รันซ้ำได้: แถวที่ tail เป็น hex 64 แล้วถูกข้าม
|
||
|
|
// · idempotent ไม่รวม "ข้ามแถวชน" — ถ้า target hash ที่คำนวณไปชนกับแถวอื่น (มีการ
|
||
|
|
// roll ใหม่ค้างก่อน migration) จะพิมพ์ warning แล้วข้าม ห้าม merge/delete เอง
|
||
|
|
//
|
||
|
|
// รัน: npm run migrate:ecourt-fid (node --env-file=.env ตาม pattern ของ seed-samples)
|
||
|
|
// ⚠️ ระหว่างรันต้องหยุด process ของแอปก่อน — โค้ดเก่าที่ยังเขียน plaintext กลับมา
|
||
|
|
// หลัง migration จะ insert แถวคู่เงียบ ๆ (ดูลำดับ roll ในแผน)
|
||
|
|
import { createHmac } from "node:crypto";
|
||
|
|
import postgres from "postgres";
|
||
|
|
|
||
|
|
const ECOURT_PREFIX = "ecourt:";
|
||
|
|
const ECOURT_DEFAULT_TITLE = "เอกสารจาก e-Court";
|
||
|
|
const HEX64 = /^[0-9a-f]{64}$/;
|
||
|
|
|
||
|
|
const dbUrl = process.env.DATABASE_URL;
|
||
|
|
if (!dbUrl) throw new Error("ต้องตั้ง DATABASE_URL ก่อนรัน (node --env-file=.env)");
|
||
|
|
const pepper = process.env.FID_HASH_PEPPER?.trim();
|
||
|
|
if (!pepper) {
|
||
|
|
throw new Error(
|
||
|
|
"ต้องตั้ง FID_HASH_PEPPER ก่อน (ค่าคงที่ตลอดชีพ — ห้ามเปลี่ยนหลังมีข้อมูล, 32+ ตัวสุ่ม)",
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
const ecourtExternalId = (fid) =>
|
||
|
|
ECOURT_PREFIX + createHmac("sha256", pepper).update(fid.trim()).digest("hex");
|
||
|
|
|
||
|
|
const sql = postgres(dbUrl, { prepare: false, max: 1 });
|
||
|
|
try {
|
||
|
|
const rows = await sql`select id, title, external_id from files where external_id like ${ECOURT_PREFIX + "%"}`;
|
||
|
|
|
||
|
|
// target ทุกแถวก่อนเพื่อตรวจชน (แถวที่ hash แล้ว target = ตัวมันเอง = ไม่ชนใคร)
|
||
|
|
const targets = new Map(); // id → target external_id
|
||
|
|
for (const row of rows) {
|
||
|
|
const fid = row.external_id.slice(ECOURT_PREFIX.length);
|
||
|
|
targets.set(row.id, HEX64.test(fid) ? row.external_id : ecourtExternalId(fid));
|
||
|
|
}
|
||
|
|
const ownerByTarget = new Map();
|
||
|
|
for (const [id, target] of targets) {
|
||
|
|
const prev = ownerByTarget.get(target);
|
||
|
|
if (prev !== undefined && prev !== id) {
|
||
|
|
console.warn(`[fid-hash] ตรวจพบแถวชน: ${prev} และ ${id} hash ไปที่ค่าเดียวกัน — ข้ามทั้งคู่ (ตรวจข้อมูลก่อนรันใหม่)`);
|
||
|
|
targets.delete(prev);
|
||
|
|
targets.delete(id);
|
||
|
|
} else {
|
||
|
|
ownerByTarget.set(target, id);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
let converted = 0;
|
||
|
|
let titlesFixed = 0;
|
||
|
|
let skipped = 0;
|
||
|
|
for (const row of rows) {
|
||
|
|
const target = targets.get(row.id);
|
||
|
|
if (target === undefined) {
|
||
|
|
skipped += 1;
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
if (target === row.external_id) {
|
||
|
|
skipped += 1; // hash แล้ว (idempotent)
|
||
|
|
continue;
|
||
|
|
}
|
||
|
|
const fid = row.external_id.slice(ECOURT_PREFIX.length);
|
||
|
|
// title default เก่าสองรูปแบบที่ฝัง fid ดิบ: "เอกสาร <fid>" (N11) และ fid เปล่า (N8
|
||
|
|
// fallback ของ titleFromExternalUrl ยุคเก่า) — เก็บเป็นค่ากลางใหม่
|
||
|
|
const isDefaultTitle = row.title === `เอกสาร ${fid}` || row.title === fid;
|
||
|
|
const newTitle = isDefaultTitle ? ECOURT_DEFAULT_TITLE : row.title;
|
||
|
|
await sql`update files set external_id = ${target}, title = ${newTitle} where id = ${row.id}`;
|
||
|
|
converted += 1;
|
||
|
|
if (isDefaultTitle) titlesFixed += 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
const leftovers =
|
||
|
|
await sql`select count(*)::int as n from files where external_id like ${ECOURT_PREFIX + "%"} and external_id !~ ${ECOURT_PREFIX + "[0-9a-f]{64}$"}`;
|
||
|
|
console.log(
|
||
|
|
`[fid-hash] เสร็จ: แปลง ${converted} แถว (แก้ title default ${titlesFixed}) · ข้าม ${skipped} แถว (hash แล้ว/ชน) · เหลือ plaintext ${leftovers[0].n} แถว (ต้องเป็น 0)`,
|
||
|
|
);
|
||
|
|
if (leftovers[0].n !== 0) process.exitCode = 1;
|
||
|
|
} finally {
|
||
|
|
await sql.end();
|
||
|
|
}
|