Files
sase.tr/scripts/emex-backfill-tr-names.ts
Fusion f4fea1e429
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
feat(FN-094): add comment line for deployment verification
- Added a comment line to main.ts for deployment verification purposes
2026-05-11 02:07:03 +00:00

195 lines
6.3 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* EMEX Türkçe Backfill
*
* `emex_category_translations` tablosu doldurulduktan SONRA çalıştır.
* `categories` ve `parts` tablolarındaki source='emex' kayıtlarının
* `name` kolonunu çevirilmiş hali ile günceller. `name_original` korunur
* (rollback edilebilir).
*
* İlk çalıştırmada UPDATE'leri hızlandıran partial index'leri oluşturur.
* Sonunda EMEX kategori tree'lerinin Redis cache'ini flush eder.
*
* Kullanım:
* pnpm tsx scripts/emex-backfill-tr-names.ts --dry-run
* pnpm tsx scripts/emex-backfill-tr-names.ts --target=categories
* pnpm tsx scripts/emex-backfill-tr-names.ts --target=parts
* pnpm tsx scripts/emex-backfill-tr-names.ts # all
*/
import * as path from "node:path";
import * as dotenv from "dotenv";
import postgres from "postgres";
import Redis from "ioredis";
dotenv.config({ path: path.join(__dirname, "../apps/api/.env") });
// ---------- CLI ----------
const argv = process.argv.slice(2);
const dryRun = argv.includes("--dry-run");
const targetArg = argv.find((a) => a.startsWith("--target="));
const target = targetArg ? targetArg.split("=")[1] : "all";
const chunkArg = argv.find((a) => a.startsWith("--chunk-size="));
const chunkSize = chunkArg ? parseInt(chunkArg.split("=")[1], 10) : 50_000;
if (!["all", "categories", "parts"].includes(target)) {
console.error(`Invalid --target: ${target}. Use one of: all, categories, parts`);
process.exit(1);
}
async function main() {
if (!process.env.DATABASE_URL) {
throw new Error("DATABASE_URL env var is required");
}
const sql = postgres(process.env.DATABASE_URL, { max: 1 });
// Step 1: ensure partial indexes exist (idempotent)
if (!dryRun) {
console.log("Ensuring partial indexes...");
await sql`
CREATE INDEX IF NOT EXISTS categories_name_original_emex_idx
ON categories(name_original) WHERE source = 'emex'
`;
await sql`
CREATE INDEX IF NOT EXISTS parts_name_original_emex_idx
ON parts(name_original) WHERE source = 'emex'
`;
}
// Step 2: report scope
console.log("\nScope:");
const [{ count: trCount }] = (await sql`
SELECT COUNT(*)::int AS count FROM emex_category_translations
`) as unknown as { count: number }[];
console.log(` emex_category_translations rows: ${trCount}`);
if (trCount === 0) {
console.warn("\nWARNING: emex_category_translations is empty.");
console.warn("Run scripts/emex-translate-bootstrap.ts first.");
}
if (target === "all" || target === "categories") {
const [{ count: catUpd }] = (await sql`
SELECT COUNT(*)::int AS count
FROM categories c
JOIN emex_category_translations t ON c.name_original = t.original_name
WHERE c.source IN ('emex', 'parts-catalogs')
AND c.name_original IS NOT NULL
AND c.name <> t.translated_name
`) as unknown as { count: number }[];
console.log(` categories to update: ${catUpd}`);
}
if (target === "all" || target === "parts") {
const [{ count: partUpd }] = (await sql`
SELECT COUNT(*)::int AS count
FROM parts p
JOIN emex_category_translations t ON p.name_original = t.original_name
WHERE p.source IN ('emex', 'parts-catalogs')
AND p.name_original IS NOT NULL
AND p.name <> t.translated_name
`) as unknown as { count: number }[];
console.log(` parts to update: ${partUpd}`);
}
if (dryRun) {
console.log("\nDRY RUN — no UPDATE / Redis flush executed.");
await sql.end();
return;
}
// Step 3: chunked UPDATE
if (target === "all" || target === "categories") {
console.log("\nUpdating categories...");
let totalUpdated = 0;
while (true) {
const result = await sql`
WITH targets AS (
SELECT c.id, t.translated_name
FROM categories c
JOIN emex_category_translations t ON c.name_original = t.original_name
WHERE c.source IN ('emex', 'parts-catalogs')
AND c.name_original IS NOT NULL
AND c.name <> t.translated_name
LIMIT ${chunkSize}
)
UPDATE categories
SET name = targets.translated_name
FROM targets
WHERE categories.id = targets.id
RETURNING categories.id
`;
const n = result.count ?? result.length;
totalUpdated += n;
console.log(` +${n} (total: ${totalUpdated})`);
if (n === 0) break;
}
console.log(`Categories updated: ${totalUpdated}`);
}
if (target === "all" || target === "parts") {
console.log("\nUpdating parts...");
let totalUpdated = 0;
while (true) {
const result = await sql`
WITH targets AS (
SELECT p.id, t.translated_name
FROM parts p
JOIN emex_category_translations t ON p.name_original = t.original_name
WHERE p.source IN ('emex', 'parts-catalogs')
AND p.name_original IS NOT NULL
AND p.name <> t.translated_name
LIMIT ${chunkSize}
)
UPDATE parts
SET name = targets.translated_name
FROM targets
WHERE parts.id = targets.id
RETURNING parts.id
`;
const n = result.count ?? result.length;
totalUpdated += n;
console.log(` +${n} (total: ${totalUpdated})`);
if (n === 0) break;
}
console.log(`Parts updated: ${totalUpdated}`);
}
// Step 4: Redis cache flush — both per-vehicle category trees and translation entries
console.log("\nFlushing Redis caches...");
const redis = new Redis({
host: process.env.REDIS_HOST || "127.0.0.1",
port: parseInt(process.env.REDIS_PORT || "6379", 10),
password: process.env.REDIS_PASSWORD,
lazyConnect: true,
});
try {
await redis.connect();
const patterns = ["cat:tree:*", "tr:*"];
for (const pattern of patterns) {
let cursor = "0";
let totalDeleted = 0;
do {
const [next, keys] = await redis.scan(cursor, "MATCH", pattern, "COUNT", 500);
cursor = next;
if (keys.length) {
await redis.del(...keys);
totalDeleted += keys.length;
}
} while (cursor !== "0");
console.log(` ${pattern}: ${totalDeleted} keys deleted`);
}
} catch (err) {
console.warn(`Redis flush failed: ${(err as Error).message}`);
} finally {
redis.disconnect();
}
await sql.end();
console.log("\nDone.");
}
main().catch((err) => {
console.error(err);
process.exit(1);
});