This commit is contained in:
1 parent
8efd032cc6
commit
df38dccbf1
735 files changed
+128321
-120870
No files matched your search
@@ -7,121 +7,153 @@ import { PERMS } from "@/lib/permissions";
|
||||
import { prisma } from "@/lib/prisma";
|
||||
import { logAudit } from "@/lib/services/audit";
|
||||
import { getFurniAssetDirs } from "@/lib/services/furni-asset-dirs";
|
||||
import { readFurniData, withFurniDataLock, writeFurniData } from "@/lib/services/furni-data";
|
||||
import {
|
||||
readFurniData,
|
||||
withFurniDataLock,
|
||||
writeFurniData,
|
||||
} from "@/lib/services/furni-data";
|
||||
import { rcon } from "@/lib/services/rcon";
|
||||
import {
|
||||
computeDefaultDir,
|
||||
createNitroBundle,
|
||||
extractPartColors,
|
||||
parseNitroBundle,
|
||||
computeDefaultDir,
|
||||
createNitroBundle,
|
||||
extractPartColors,
|
||||
parseNitroBundle,
|
||||
} from "@/lib/services/swf-to-nitro";
|
||||
|
||||
function baseClassname(classname: string): string {
|
||||
return classname.includes("*") ? classname.split("*")[0] : classname;
|
||||
return classname.includes("*") ? classname.split("*")[0] : classname;
|
||||
}
|
||||
|
||||
// ── GET: Read .nitro metadata ──────────────────────────────────────
|
||||
|
||||
export const GET = withAdmin({ permission: PERMS.ASSETS_IMPORT }, async (request: NextRequest) => {
|
||||
const rawClassname = request.nextUrl.searchParams.get("classname");
|
||||
if (!rawClassname) return apiError("Missing classname", 400);
|
||||
export const GET = withAdmin(
|
||||
{ permission: PERMS.ASSETS_IMPORT },
|
||||
async (request: NextRequest) => {
|
||||
const rawClassname = request.nextUrl.searchParams.get("classname");
|
||||
if (!rawClassname) return apiError("Missing classname", 400);
|
||||
|
||||
const base = baseClassname(rawClassname);
|
||||
const { nitroDir } = await getFurniAssetDirs();
|
||||
const nitroPath = path.join(/*turbopackIgnore: true*/ nitroDir, `${base}.nitro`);
|
||||
const base = baseClassname(rawClassname);
|
||||
const { nitroDir } = await getFurniAssetDirs();
|
||||
const nitroPath = path.join(
|
||||
/*turbopackIgnore: true*/ nitroDir,
|
||||
`${base}.nitro`,
|
||||
);
|
||||
|
||||
if (!existsSync(/*turbopackIgnore: true*/ nitroPath)) return apiError("Nitro file not found", 404);
|
||||
if (!existsSync(/*turbopackIgnore: true*/ nitroPath))
|
||||
return apiError("Nitro file not found", 404);
|
||||
|
||||
try {
|
||||
const buffer = await fs.readFile(/*turbopackIgnore: true*/ nitroPath);
|
||||
const { json } = parseNitroBundle(buffer);
|
||||
try {
|
||||
const buffer = await fs.readFile(/*turbopackIgnore: true*/ nitroPath);
|
||||
const { json } = parseNitroBundle(buffer);
|
||||
|
||||
// Fetch interaction flags from DB
|
||||
const items = await prisma.$queryRaw<
|
||||
Array<{
|
||||
allow_sit: string;
|
||||
allow_lay: string;
|
||||
allow_walk: string;
|
||||
}>
|
||||
>`
|
||||
// Fetch interaction flags from DB
|
||||
const items = await prisma.$queryRaw<
|
||||
Array<{
|
||||
allow_sit: string;
|
||||
allow_lay: string;
|
||||
allow_walk: string;
|
||||
}>
|
||||
>`
|
||||
SELECT allow_sit, allow_lay, allow_walk
|
||||
FROM items_base WHERE item_name = ${rawClassname} LIMIT 1
|
||||
`;
|
||||
|
||||
const dbRow = items[0];
|
||||
const flags = {
|
||||
canstandon: dbRow?.allow_walk === "1",
|
||||
cansiton: dbRow?.allow_sit === "1",
|
||||
canlayon: dbRow?.allow_lay === "1",
|
||||
};
|
||||
const dbRow = items[0];
|
||||
const flags = {
|
||||
canstandon: dbRow?.allow_walk === "1",
|
||||
cansiton: dbRow?.allow_sit === "1",
|
||||
canlayon: dbRow?.allow_lay === "1",
|
||||
};
|
||||
|
||||
return apiOk({ metadata: json, flags });
|
||||
} catch (err) {
|
||||
console.error("[nitro-editor] Failed to parse .nitro:", (err as Error).message);
|
||||
return apiError("Failed to parse .nitro file", 500);
|
||||
}
|
||||
});
|
||||
return apiOk({ metadata: json, flags });
|
||||
} catch (err) {
|
||||
console.error(
|
||||
"[nitro-editor] Failed to parse .nitro:",
|
||||
(err as Error).message,
|
||||
);
|
||||
return apiError("Failed to parse .nitro file", 500);
|
||||
}
|
||||
},
|
||||
);
|
||||
|
||||
// ── PUT: Save .nitro metadata ──────────────────────────────────────
|
||||
|
||||
export const PUT = withAdmin({ permission: PERMS.ASSETS_IMPORT }, async (request: NextRequest, ctx) => {
|
||||
const body = await request.json();
|
||||
const { classname: rawClassname, metadata, canstandon, cansiton, canlayon } = body;
|
||||
export const PUT = withAdmin(
|
||||
{ permission: PERMS.ASSETS_IMPORT },
|
||||
async (request: NextRequest, ctx) => {
|
||||
const body = await request.json();
|
||||
const {
|
||||
classname: rawClassname,
|
||||
metadata,
|
||||
canstandon,
|
||||
cansiton,
|
||||
canlayon,
|
||||
} = body;
|
||||
|
||||
if (!rawClassname || !metadata) return apiError("Missing classname or metadata", 400);
|
||||
if (!rawClassname || !metadata)
|
||||
return apiError("Missing classname or metadata", 400);
|
||||
|
||||
const base = baseClassname(rawClassname);
|
||||
const { nitroDir } = await getFurniAssetDirs();
|
||||
const nitroPath = path.join(/*turbopackIgnore: true*/ nitroDir, `${base}.nitro`);
|
||||
const base = baseClassname(rawClassname);
|
||||
const { nitroDir } = await getFurniAssetDirs();
|
||||
const nitroPath = path.join(
|
||||
/*turbopackIgnore: true*/ nitroDir,
|
||||
`${base}.nitro`,
|
||||
);
|
||||
|
||||
if (!existsSync(/*turbopackIgnore: true*/ nitroPath)) return apiError("Nitro file not found", 404);
|
||||
if (!existsSync(/*turbopackIgnore: true*/ nitroPath))
|
||||
return apiError("Nitro file not found", 404);
|
||||
|
||||
// 1. Read existing .nitro to extract PNG
|
||||
const existingBuffer = await fs.readFile(/*turbopackIgnore: true*/ nitroPath);
|
||||
const { png } = parseNitroBundle(existingBuffer);
|
||||
// 1. Read existing .nitro to extract PNG
|
||||
const existingBuffer = await fs.readFile(
|
||||
/*turbopackIgnore: true*/ nitroPath,
|
||||
);
|
||||
const { png } = parseNitroBundle(existingBuffer);
|
||||
|
||||
// 2. Rebuild .nitro with updated JSON + same PNG
|
||||
const newBundle = createNitroBundle(metadata, png, base);
|
||||
await fs.writeFile(nitroPath, newBundle);
|
||||
// 2. Rebuild .nitro with updated JSON + same PNG
|
||||
const newBundle = createNitroBundle(metadata, png, base);
|
||||
await fs.writeFile(nitroPath, newBundle);
|
||||
|
||||
// 3. Extract dimensions from metadata
|
||||
const dims = metadata.logic?.model?.dimensions || { x: 1, y: 1, z: 0 };
|
||||
const directions: number[] = metadata.logic?.model?.directions || [0];
|
||||
const visualizations = metadata.visualizations || [];
|
||||
// 3. Extract dimensions from metadata
|
||||
const dims = metadata.logic?.model?.dimensions || { x: 1, y: 1, z: 0 };
|
||||
const directions: number[] = metadata.logic?.model?.directions || [0];
|
||||
const visualizations = metadata.visualizations || [];
|
||||
|
||||
// 4. Update FurnitureData.json (best-effort)
|
||||
try {
|
||||
await withFurniDataLock(async () => {
|
||||
const furniData = (await readFurniData()) as Record<
|
||||
string,
|
||||
{ furnitype: Array<Record<string, unknown>> }
|
||||
>;
|
||||
// 4. Update FurnitureData.json (best-effort)
|
||||
try {
|
||||
await withFurniDataLock(async () => {
|
||||
const furniData = (await readFurniData()) as Record<
|
||||
string,
|
||||
{ furnitype: Array<Record<string, unknown>> }
|
||||
>;
|
||||
|
||||
for (const section of ["roomitemtypes", "wallitemtypes"] as const) {
|
||||
const types = furniData[section]?.furnitype;
|
||||
if (!Array.isArray(types)) continue;
|
||||
const idx = types.findIndex((e) => e.classname === rawClassname);
|
||||
if (idx !== -1) {
|
||||
types[idx].xdim = dims.x;
|
||||
types[idx].ydim = dims.y;
|
||||
types[idx].defaultdir = computeDefaultDir(directions);
|
||||
types[idx].partcolors = extractPartColors(visualizations);
|
||||
types[idx].canstandon = canstandon ?? false;
|
||||
types[idx].cansiton = cansiton ?? false;
|
||||
types[idx].canlayon = canlayon ?? false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
for (const section of ["roomitemtypes", "wallitemtypes"] as const) {
|
||||
const types = furniData[section]?.furnitype;
|
||||
if (!Array.isArray(types)) continue;
|
||||
const idx = types.findIndex((e) => e.classname === rawClassname);
|
||||
if (idx !== -1) {
|
||||
types[idx].xdim = dims.x;
|
||||
types[idx].ydim = dims.y;
|
||||
types[idx].defaultdir = computeDefaultDir(directions);
|
||||
types[idx].partcolors = extractPartColors(visualizations);
|
||||
types[idx].canstandon = canstandon ?? false;
|
||||
types[idx].cansiton = cansiton ?? false;
|
||||
types[idx].canlayon = canlayon ?? false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
await writeFurniData(furniData as Record<string, unknown>);
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn("[nitro-editor] FurnitureData.json update failed:", (err as Error).message);
|
||||
}
|
||||
await writeFurniData(furniData as Record<string, unknown>);
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
"[nitro-editor] FurnitureData.json update failed:",
|
||||
(err as Error).message,
|
||||
);
|
||||
}
|
||||
|
||||
// 5. Update items_base (best-effort)
|
||||
try {
|
||||
await prisma.$executeRaw`
|
||||
// 5. Update items_base (best-effort)
|
||||
try {
|
||||
await prisma.$executeRaw`
|
||||
UPDATE items_base
|
||||
SET width = ${dims.x}, length = ${dims.y}, stack_height = ${dims.z},
|
||||
allow_sit = ${cansiton ? "1" : "0"},
|
||||
@@ -129,33 +161,46 @@ export const PUT = withAdmin({ permission: PERMS.ASSETS_IMPORT }, async (request
|
||||
allow_walk = ${canstandon ? "1" : "0"}
|
||||
WHERE item_name = ${rawClassname}
|
||||
`;
|
||||
} catch (err) {
|
||||
console.warn("[nitro-editor] items_base update failed:", (err as Error).message);
|
||||
}
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
"[nitro-editor] items_base update failed:",
|
||||
(err as Error).message,
|
||||
);
|
||||
}
|
||||
|
||||
// 6. RCON refresh (best-effort)
|
||||
try {
|
||||
await rcon.updateItems();
|
||||
} catch (err) {
|
||||
console.warn("[nitro-editor] RCON updateItems failed:", (err as Error).message);
|
||||
}
|
||||
// 6. RCON refresh (best-effort)
|
||||
try {
|
||||
await rcon.updateItems();
|
||||
} catch (err) {
|
||||
console.warn(
|
||||
"[nitro-editor] RCON updateItems failed:",
|
||||
(err as Error).message,
|
||||
);
|
||||
}
|
||||
|
||||
// 7. Audit log
|
||||
try {
|
||||
const dbItem = await prisma.itemsBase.findFirst({
|
||||
where: { itemName: rawClassname },
|
||||
select: { id: true },
|
||||
});
|
||||
await logAudit({
|
||||
userId: ctx.session.user.id,
|
||||
action: "furni_edit_nitro",
|
||||
target: "ItemsBase",
|
||||
targetId: dbItem?.id ?? 0,
|
||||
after: { classname: rawClassname, dimensions: dims, canstandon, cansiton, canlayon },
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn("[nitro-editor] Audit log failed:", (err as Error).message);
|
||||
}
|
||||
// 7. Audit log
|
||||
try {
|
||||
const dbItem = await prisma.itemsBase.findFirst({
|
||||
where: { itemName: rawClassname },
|
||||
select: { id: true },
|
||||
});
|
||||
await logAudit({
|
||||
userId: ctx.session.user.id,
|
||||
action: "furni_edit_nitro",
|
||||
target: "ItemsBase",
|
||||
targetId: dbItem?.id ?? 0,
|
||||
after: {
|
||||
classname: rawClassname,
|
||||
dimensions: dims,
|
||||
canstandon,
|
||||
cansiton,
|
||||
canlayon,
|
||||
},
|
||||
});
|
||||
} catch (err) {
|
||||
console.warn("[nitro-editor] Audit log failed:", (err as Error).message);
|
||||
}
|
||||
|
||||
return apiOk({ dimensions: dims });
|
||||
});
|
||||
return apiOk({ dimensions: dims });
|
||||
},
|
||||
);
|
||||
Reference in new issue
Block a user