feat(FN-094): add comment line for deployment verification
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:
Fusion
2026-05-11 02:07:03 +00:00
parent c72f063a25
commit f4fea1e429
274 changed files with 20712 additions and 6305 deletions

View 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);
});