feat(FN-094): add comment line for deployment verification
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
Some checks failed
Sync dev → Gitea / Mirror dev to Gitea (push) Has been cancelled
- Added a comment line to main.ts for deployment verification purposes
This commit is contained in:
194
scripts/emex-backfill-tr-names.ts
Normal file
194
scripts/emex-backfill-tr-names.ts
Normal file
@@ -0,0 +1,194 @@
|
||||
/**
|
||||
* 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);
|
||||
});
|
||||
Reference in New Issue
Block a user