Split OnlineUsersWidget from StatusCard, decouple ad delete button, sync badge import to ExternalTexts+WebsiteBadges, add Import section hub with cancelable SSE jobs and upload SQL option. Co-authored-by: Cursor <[email protected]>
106 lines
2.3 KiB
TypeScript
106 lines
2.3 KiB
TypeScript
import { promises as fs } from "node:fs";
|
|
import path from "node:path";
|
|
import { db, WebsiteBadges } from "@/lib/db";
|
|
|
|
const EXTERNAL_TEXTS_PATH = path.join(
|
|
process.cwd(),
|
|
"public/nitro-assets/gamedata/ExternalTexts.json",
|
|
);
|
|
|
|
export type ImportBadgeResult =
|
|
| { ok: true }
|
|
| { ok: false; error: string; status: number };
|
|
|
|
/** Write badge name/desc into ExternalTexts.json (409 if already present). */
|
|
export async function writeBadgeToExternalTexts(
|
|
code: string,
|
|
name: string,
|
|
description: string,
|
|
): Promise<ImportBadgeResult> {
|
|
try {
|
|
const raw = await fs.readFile(EXTERNAL_TEXTS_PATH, "utf-8");
|
|
const texts = JSON.parse(raw) as Record<string, string>;
|
|
|
|
const nameKey = `badge_name_${code}`;
|
|
const descKey = `badge_desc_${code}`;
|
|
|
|
if (texts[nameKey] !== undefined) {
|
|
return {
|
|
ok: false,
|
|
error: "Badge already exists in ExternalTexts.json",
|
|
status: 409,
|
|
};
|
|
}
|
|
|
|
texts[nameKey] = name;
|
|
texts[descKey] = description || "";
|
|
|
|
await fs.writeFile(
|
|
EXTERNAL_TEXTS_PATH,
|
|
JSON.stringify(texts, null, 4),
|
|
"utf-8",
|
|
);
|
|
return { ok: true };
|
|
} catch (err) {
|
|
console.warn(
|
|
"[import-badges] Failed to update ExternalTexts.json:",
|
|
(err as Error).message,
|
|
);
|
|
return {
|
|
ok: false,
|
|
error: "Failed to update ExternalTexts.json",
|
|
status: 500,
|
|
};
|
|
}
|
|
}
|
|
|
|
/** Upsert WebsiteBadges row so CMS badge tables stay in sync. */
|
|
export async function upsertWebsiteBadge(
|
|
code: string,
|
|
name: string,
|
|
description: string,
|
|
): Promise<void> {
|
|
const now = new Date();
|
|
await db
|
|
.insert(WebsiteBadges)
|
|
.values({
|
|
badgeKey: code,
|
|
badgeName: name,
|
|
badgeDescription: description,
|
|
createdAt: now,
|
|
updatedAt: now,
|
|
})
|
|
.onDuplicateKeyUpdate({
|
|
set: {
|
|
badgeName: name,
|
|
badgeDescription: description,
|
|
updatedAt: now,
|
|
},
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Single import path: ExternalTexts.json + WebsiteBadges.
|
|
* Callers should gate with ASSETS_IMPORT.
|
|
*/
|
|
export async function importBadgeSynced(input: {
|
|
code: string;
|
|
name: string;
|
|
description: string;
|
|
}): Promise<ImportBadgeResult> {
|
|
const texts = await writeBadgeToExternalTexts(
|
|
input.code,
|
|
input.name,
|
|
input.description,
|
|
);
|
|
if (!texts.ok) return texts;
|
|
|
|
try {
|
|
await upsertWebsiteBadge(input.code, input.name, input.description);
|
|
} catch {
|
|
return { ok: false, error: "Failed to import badge", status: 500 };
|
|
}
|
|
|
|
return { ok: true };
|
|
}
|