1
This commit is contained in:
141
server-node/src/modules/editor/editorRoutes.ts
Normal file
141
server-node/src/modules/editor/editorRoutes.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
import { mkdir, readdir, readFile, writeFile } from 'node:fs/promises';
|
||||
import path from 'node:path';
|
||||
|
||||
import { Router } from 'express';
|
||||
|
||||
import type { AppConfig } from '../../config.js';
|
||||
import { badRequest, notFound } from '../../errors.js';
|
||||
import { asyncHandler } from '../../http.js';
|
||||
|
||||
const EDITOR_JSON_RESOURCE_FILES = {
|
||||
'item-overrides': 'src/data/itemOverrides.json',
|
||||
'npc-visual-overrides': 'src/data/npcVisualOverrides.json',
|
||||
'npc-layout-config': 'src/data/npcLayoutConfig.json',
|
||||
'character-overrides': 'src/data/characterOverrides.json',
|
||||
'monster-overrides': 'src/data/monsterOverrides.json',
|
||||
'scene-overrides': 'src/data/sceneOverrides.json',
|
||||
'scene-npc-overrides': 'src/data/sceneNpcOverrides.json',
|
||||
'state-function-overrides': 'src/data/stateFunctionOverrides.json',
|
||||
} as const;
|
||||
|
||||
type EditorJsonResourceId = keyof typeof EDITOR_JSON_RESOURCE_FILES;
|
||||
|
||||
function isEditorJsonPayload(value: unknown): value is Record<string, unknown> {
|
||||
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
||||
}
|
||||
|
||||
function resolveEditorJsonFile(
|
||||
config: AppConfig,
|
||||
resourceId: string,
|
||||
) {
|
||||
const relativePath =
|
||||
EDITOR_JSON_RESOURCE_FILES[
|
||||
resourceId as EditorJsonResourceId
|
||||
];
|
||||
if (!relativePath) {
|
||||
throw notFound('未知的编辑器资源。');
|
||||
}
|
||||
|
||||
return path.resolve(config.projectRoot, relativePath);
|
||||
}
|
||||
|
||||
async function readEditorJsonFile(filePath: string) {
|
||||
try {
|
||||
const content = await readFile(filePath, 'utf8');
|
||||
return JSON.parse(content) as Record<string, unknown>;
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') {
|
||||
return {};
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
async function collectPngAssetPaths(
|
||||
rootDir: string,
|
||||
relativeDir = 'Icons',
|
||||
): Promise<string[]> {
|
||||
const entries = await readdir(rootDir, { withFileTypes: true });
|
||||
const collected: string[] = [];
|
||||
|
||||
for (const entry of entries) {
|
||||
const absolutePath = path.join(rootDir, entry.name);
|
||||
const relativePath = `${relativeDir}/${entry.name}`.replace(/\\/g, '/');
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
collected.push(
|
||||
...(await collectPngAssetPaths(absolutePath, relativePath)),
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry.isFile() && entry.name.toLowerCase().endsWith('.png')) {
|
||||
collected.push(relativePath);
|
||||
}
|
||||
}
|
||||
|
||||
return collected.sort((left, right) => left.localeCompare(right));
|
||||
}
|
||||
|
||||
export function createEditorRoutes(config: AppConfig) {
|
||||
const router = Router();
|
||||
|
||||
router.use((request, response, next) => {
|
||||
if (
|
||||
request.path !== '/api/editor' &&
|
||||
!request.path.startsWith('/api/editor/')
|
||||
) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!config.editorApiEnabled) {
|
||||
response.status(403).json({
|
||||
error: {
|
||||
message: '编辑器接口当前未启用。',
|
||||
},
|
||||
});
|
||||
return;
|
||||
}
|
||||
next();
|
||||
});
|
||||
|
||||
router.get(
|
||||
'/api/editor/catalog/items',
|
||||
asyncHandler(async (_request, response) => {
|
||||
response.json({
|
||||
assetPaths: await collectPngAssetPaths(
|
||||
path.resolve(config.projectRoot, 'public/Icons'),
|
||||
),
|
||||
});
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/api/editor/json/:resourceId',
|
||||
asyncHandler(async (request, response) => {
|
||||
const filePath = resolveEditorJsonFile(config, request.params.resourceId);
|
||||
response.json(await readEditorJsonFile(filePath));
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/api/editor/json/:resourceId',
|
||||
asyncHandler(async (request, response) => {
|
||||
if (!isEditorJsonPayload(request.body)) {
|
||||
throw badRequest('编辑器保存请求必须是 JSON 对象。');
|
||||
}
|
||||
|
||||
const filePath = resolveEditorJsonFile(config, request.params.resourceId);
|
||||
await mkdir(path.dirname(filePath), { recursive: true });
|
||||
await writeFile(
|
||||
filePath,
|
||||
JSON.stringify(request.body, null, 2) + '\n',
|
||||
'utf8',
|
||||
);
|
||||
response.json({ ok: true });
|
||||
}),
|
||||
);
|
||||
|
||||
return router;
|
||||
}
|
||||
Reference in New Issue
Block a user