1
This commit is contained in:
@@ -2,14 +2,52 @@ import express from 'express';
|
||||
import pinoHttp from 'pino-http';
|
||||
|
||||
import type { AppContext } from './context.js';
|
||||
import { buildApiLogContext, withRouteMeta } from './http.js';
|
||||
import { errorHandler } from './middleware/errorHandler.js';
|
||||
import { requestIdMiddleware } from './middleware/requestId.js';
|
||||
import { responseEnvelopeMiddleware } from './middleware/responseEnvelope.js';
|
||||
import { createCharacterAssetRoutes } from './modules/assets/characterAssetRoutes.js';
|
||||
import { createQwenSpriteRoutes } from './modules/assets/qwenSpriteRoutes.js';
|
||||
import { createEditorRoutes } from './modules/editor/editorRoutes.js';
|
||||
import { createStoryActionRoutes } from './modules/story/storyActionRoutes.js';
|
||||
import { createAuthRoutes } from './routes/authRoutes.js';
|
||||
import { createRuntimeRoutes } from './routes/runtimeRoutes.js';
|
||||
import { notFound } from './errors.js';
|
||||
|
||||
function matchesRoutePrefix(
|
||||
request: express.Request,
|
||||
prefixes: readonly string[],
|
||||
) {
|
||||
const requestPath = request.path || request.originalUrl || request.url || '/';
|
||||
|
||||
return prefixes.some((prefix) => {
|
||||
const normalizedPrefix = prefix.endsWith('/') ? prefix.slice(0, -1) : prefix;
|
||||
return (
|
||||
requestPath === normalizedPrefix ||
|
||||
requestPath.startsWith(`${normalizedPrefix}/`)
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
function scopeToPrefixes(
|
||||
prefixes: readonly string[],
|
||||
handler: express.RequestHandler,
|
||||
): express.RequestHandler {
|
||||
return (request, response, next) => {
|
||||
if (!matchesRoutePrefix(request, prefixes)) {
|
||||
next();
|
||||
return;
|
||||
}
|
||||
|
||||
handler(request, response, next);
|
||||
};
|
||||
}
|
||||
|
||||
export function createApp(context: AppContext) {
|
||||
const app = express();
|
||||
const createHttpLogger = pinoHttp as unknown as (options: Record<string, unknown>) => express.RequestHandler;
|
||||
const createHttpLogger = pinoHttp as unknown as (
|
||||
options: Record<string, unknown>,
|
||||
) => express.RequestHandler;
|
||||
|
||||
app.disable('x-powered-by');
|
||||
|
||||
@@ -17,18 +55,14 @@ export function createApp(context: AppContext) {
|
||||
app.use(
|
||||
createHttpLogger({
|
||||
logger: context.logger,
|
||||
genReqId: (request) => request.requestId,
|
||||
customProps: (request: express.Request) => ({
|
||||
request_id: request.requestId,
|
||||
user_id: request.userId ?? null,
|
||||
}),
|
||||
genReqId: (request: express.Request) => request.requestId,
|
||||
customSuccessObject: (
|
||||
request: express.Request,
|
||||
response: express.Response,
|
||||
baseObject: Record<string, unknown> & { responseTime?: number },
|
||||
) => ({
|
||||
...baseObject,
|
||||
request_id: request.requestId,
|
||||
...buildApiLogContext(request, response),
|
||||
user_id: request.userId ?? null,
|
||||
method: request.method,
|
||||
path: request.url,
|
||||
@@ -42,7 +76,7 @@ export function createApp(context: AppContext) {
|
||||
baseObject: Record<string, unknown> & { responseTime?: number },
|
||||
) => ({
|
||||
...baseObject,
|
||||
request_id: request.requestId,
|
||||
...buildApiLogContext(request, response),
|
||||
user_id: request.userId ?? null,
|
||||
method: request.method,
|
||||
path: request.url,
|
||||
@@ -53,17 +87,67 @@ export function createApp(context: AppContext) {
|
||||
}),
|
||||
);
|
||||
app.use(express.json({ limit: '10mb' }));
|
||||
app.use(responseEnvelopeMiddleware);
|
||||
|
||||
app.get('/healthz', (_request, response) => {
|
||||
response.json({
|
||||
ok: true,
|
||||
service: 'genarrative-node-server',
|
||||
});
|
||||
app.get(
|
||||
'/healthz',
|
||||
withRouteMeta({ operation: 'health.check' }),
|
||||
(_request, response) => {
|
||||
response.json({
|
||||
ok: true,
|
||||
service: 'genarrative-node-server',
|
||||
});
|
||||
},
|
||||
);
|
||||
|
||||
app.use(
|
||||
scopeToPrefixes(
|
||||
['/api/editor'],
|
||||
withRouteMeta({ routeVersion: '2026-04-08', operation: 'editor.api' }),
|
||||
),
|
||||
);
|
||||
app.use(scopeToPrefixes(['/api/editor'], createEditorRoutes(context.config)));
|
||||
app.use(
|
||||
scopeToPrefixes(
|
||||
['/api/assets'],
|
||||
withRouteMeta({ routeVersion: '2026-04-08', operation: 'assets.api' }),
|
||||
),
|
||||
);
|
||||
app.use(
|
||||
scopeToPrefixes(['/api/assets'], createCharacterAssetRoutes(context.config)),
|
||||
);
|
||||
app.use(
|
||||
scopeToPrefixes(
|
||||
['/api/assets/qwen-sprite'],
|
||||
withRouteMeta({ routeVersion: '2026-04-08', operation: 'assets.qwen' }),
|
||||
),
|
||||
);
|
||||
app.use(
|
||||
scopeToPrefixes(
|
||||
['/api/assets/qwen-sprite'],
|
||||
createQwenSpriteRoutes(context.config),
|
||||
),
|
||||
);
|
||||
app.use(
|
||||
'/api/auth',
|
||||
withRouteMeta({ routeVersion: '2026-04-08' }),
|
||||
createAuthRoutes(context),
|
||||
);
|
||||
app.use(
|
||||
'/api/runtime/story',
|
||||
withRouteMeta({ routeVersion: '2026-04-08' }),
|
||||
createStoryActionRoutes(context),
|
||||
);
|
||||
app.use(
|
||||
'/api',
|
||||
withRouteMeta({ routeVersion: '2026-04-08' }),
|
||||
createRuntimeRoutes(context),
|
||||
);
|
||||
|
||||
app.use((request, _response, next) => {
|
||||
next(notFound(`接口不存在:${request.method} ${request.originalUrl}`));
|
||||
});
|
||||
|
||||
app.use('/api/auth', createAuthRoutes(context));
|
||||
app.use('/api', createRuntimeRoutes(context));
|
||||
|
||||
app.use(errorHandler);
|
||||
return app;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user