fix: SQL maker creates wrong categories and badge import fails when ExternalTexts.json missing
CI / check (push) Successful in 26s
CI / release (push) Skipped
CI / deploy (push) Successful in 1m5s

- writeSqlMigration now classifies furni via classifyFurni and generates
  correct category sub-pages (imp_<catKey>) instead of hardcoded imp_other
- writeSqlMigration generates idempotent INSERT...SELECT WHERE NOT EXISTS
  for parent and category catalog_pages, with correct numeric page_id
- writeBadgeToExternalTexts creates ExternalTexts.json (and parent dirs)
  when missing instead of failing with ENOENT
This commit is contained in:
openhands committed 2026-08-05 15:25:07 +02:00
1 parent 1851653afb
commit 4d4cbd2211
3 files changed
+60 -15

No files matched your search

+28 -10
View File
@@ -17,24 +17,42 @@ export async function writeBadgeToExternalTexts(
name: string,
description: string,
): Promise<ImportBadgeResult> {
const nameKey = `badge_name_${code}`;
const descKey = `badge_desc_${code}`;
let texts: Record<string, string>;
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) {
texts = JSON.parse(raw) as Record<string, string>;
} catch (err) {
// File not present yet (fresh deployment) — start with an empty object.
if ((err as NodeJS.ErrnoException).code !== "ENOENT") {
console.warn(
"[import-badges] Failed to update ExternalTexts.json:",
(err as Error).message,
);
return {
ok: false,
error: "Badge already exists in ExternalTexts.json",
status: 409,
error: "Failed to update ExternalTexts.json",
status: 500,
};
}
texts = {};
}
texts[nameKey] = name;
texts[descKey] = description || "";
if (texts[nameKey] !== undefined) {
return {
ok: false,
error: "Badge already exists in ExternalTexts.json",
status: 409,
};
}
texts[nameKey] = name;
texts[descKey] = description || "";
try {
await fs.mkdir(path.dirname(EXTERNAL_TEXTS_PATH), { recursive: true });
await fs.writeFile(
EXTERNAL_TEXTS_PATH,
JSON.stringify(texts, null, 4),
+4
View File
@@ -62,6 +62,8 @@ vi.mock("@/lib/services/furni-import", () => ({
async (insertFn: (nextId: number) => Promise<unknown>) => insertFn(701),
),
autoPriceFurni: vi.fn(() => ({ credits: 3, points: 0, pointsType: 0 })),
CATEGORY_PAGE: {},
classifyFurni: vi.fn(() => "other"),
ensureDirectories: vi.fn(async () => {
const dirs = [
assetTargets.swfDir,
@@ -76,6 +78,8 @@ vi.mock("@/lib/services/furni-import", () => ({
await Promise.all(dirs.map((dir) => fs.mkdir(dir, { recursive: true })));
}),
getOrCreateCategoryPage: getOrCreateCategoryPageMock,
IMPORTED_PAGE_CAPTION: "Imported Furniture",
IMPORTED_PAGE_CAPTION_SAVE: "imported_furni",
}));
vi.mock("@/lib/services/swf/nitro-builder", () => ({
+28 -5
View File
@@ -7,8 +7,12 @@ import { appendFurniEntry, buildFurniEntry } from "@/lib/services/furni-data";
import {
allocateCatalogItemId,
autoPriceFurni,
CATEGORY_PAGE,
classifyFurni,
ensureDirectories,
getOrCreateCategoryPage,
IMPORTED_PAGE_CAPTION,
IMPORTED_PAGE_CAPTION_SAVE,
} from "@/lib/services/furni-import";
import { parseNitroBundle } from "@/lib/services/swf/nitro-builder";
@@ -153,6 +157,7 @@ async function writeSqlMigration(params: {
interactionType: string;
customparams: string;
price: { credits: number; points: number; pointsType: number };
categoryKey: string;
}): Promise<string> {
const num = await findNextMigrationNumber();
const padded = String(num).padStart(4, "0");
@@ -163,11 +168,29 @@ async function writeSqlMigration(params: {
fileName,
);
const catKey = params.categoryKey;
const captionSave = `imp_${catKey}`;
const catInfo = CATEGORY_PAGE[catKey] || CATEGORY_PAGE.other;
const lines: string[] = [
`-- Migration: ${fileName}`,
`-- Description: Import furniture "${params.classname}"`,
`-- Generated: ${new Date().toISOString()}`,
"",
`-- Ensure the "Imported Furniture" parent page exists`,
`INSERT INTO catalog_pages (caption_save, caption, page_layout, parent_id, min_rank, order_num, icon_image, enabled, visible, includes, page_headline, page_teaser, page_special, page_text1, page_text2, page_text_details, page_text_teaser)`,
`SELECT ${escapeSql(IMPORTED_PAGE_CAPTION_SAVE)}, ${escapeSql(IMPORTED_PAGE_CAPTION)}, 'default_3x3', -1, '1', 9999, 263, '1', '1', '', '', '', '', '', '', '', ''`,
`WHERE NOT EXISTS (SELECT 1 FROM catalog_pages WHERE caption_save = ${escapeSql(IMPORTED_PAGE_CAPTION_SAVE)});`,
"",
`-- Ensure the category sub-page exists (${catKey})`,
`INSERT INTO catalog_pages (caption_save, caption, page_layout, parent_id, min_rank, order_num, icon_image, enabled, visible, includes, page_headline, page_teaser, page_special, page_text1, page_text2, page_text_details, page_text_teaser)`,
`SELECT ${escapeSql(captionSave)}, ${escapeSql(catInfo.label)}, 'default_3x3', p.id, '1', ${catInfo.order}, ${catInfo.icon}, '1', '1', '', '', '', '', '', '', '', ''`,
`FROM catalog_pages p`,
`WHERE p.caption_save = ${escapeSql(IMPORTED_PAGE_CAPTION_SAVE)}`,
` AND NOT EXISTS (SELECT 1 FROM catalog_pages c WHERE c.caption_save = ${escapeSql(captionSave)} AND c.parent_id = p.id);`,
"",
`SET @next_id = (SELECT COALESCE(MAX(id), 0) + 1 FROM items_base);`,
"",
`INSERT INTO items_base`,
` (id, sprite_id, public_name, item_name, type, width, length, stack_height,`,
` allow_stack, allow_sit, allow_lay, allow_walk, interaction_type, customparams)`,
@@ -177,13 +200,12 @@ async function writeSqlMigration(params: {
` ${params.allowStack}, ${params.cansiton ? "1" : "0"}, ${params.canlayon ? "1" : "0"}, ${params.canstandon ? "1" : "0"},`,
` ${escapeSql(params.interactionType)}, ${escapeSql(params.customparams)});`,
"",
`SET @next_id = LAST_INSERT_ID();`,
"",
`INSERT INTO catalog_items`,
` (id, page_id, item_ids, catalog_name, cost_credits, cost_points, points_type, amount, order_number, offer_id, extradata)`,
`VALUES`,
` (NULL, ${escapeSql("imp_other")}, @next_id, ${escapeSql(params.classname)},`,
` ${params.price.credits}, ${params.price.points}, ${params.price.pointsType}, 1, 1, '-1', '');`,
`SELECT (SELECT COALESCE(MAX(id), 0) + 1 FROM catalog_items), p.id, @next_id, ${escapeSql(params.classname)},`,
` ${params.price.credits}, ${params.price.points}, ${params.price.pointsType}, 1, 1, '-1', ''`,
`FROM catalog_pages p`,
`WHERE p.caption_save = ${escapeSql(captionSave)};`,
"",
];
@@ -379,6 +401,7 @@ export async function uploadSingleFurni(params: {
interactionType: userInteractionType,
customparams,
price,
categoryKey: classifyFurni(classname, itemType),
});
warnings.push(`SQL migration written: ${sqlFile}`);
} catch (err) {