Files
Genarrative/server-node/src/modules/story/storyActionRoutes.ts

75 lines
2.2 KiB
TypeScript

import { Router } from 'express';
import { z } from 'zod';
import type { RuntimeStoryActionRequest } from '../../../../packages/shared/src/contracts/story.js';
import type { AppContext } from '../../context.js';
import { badRequest } from '../../errors.js';
import { asyncHandler, sendApiResponse } from '../../http.js';
import { requireJwtAuth } from '../../middleware/auth.js';
import { routeMeta } from '../../middleware/routeMeta.js';
import {
getRuntimeStoryState,
resolveRuntimeStoryAction,
} from './storyActionService.js';
const actionPayloadSchema = z.record(z.string(), z.unknown());
const runtimeStoryActionSchema = z.object({
sessionId: z.string().trim().min(1),
clientVersion: z.number().int().min(0).optional(),
action: z.object({
type: z.literal('story_choice'),
functionId: z.string().trim().min(1),
targetId: z.string().trim().optional(),
payload: actionPayloadSchema.optional().default({}),
}),
});
export function createStoryActionRoutes(context: AppContext) {
const router = Router();
const requireAuth = requireJwtAuth(context.config, context.userRepository);
router.use(requireAuth);
router.post(
'/actions/resolve',
routeMeta({ operation: 'runtime.story.actions.resolve' }),
asyncHandler(async (request, response) => {
const payload = runtimeStoryActionSchema.parse(
request.body,
) as RuntimeStoryActionRequest;
sendApiResponse(
response,
await resolveRuntimeStoryAction({
runtimeRepository: context.runtimeRepository,
llmClient: context.llmClient,
userId: request.userId!,
request: payload,
}),
);
}),
);
router.get(
'/state/:sessionId',
routeMeta({ operation: 'runtime.story.state.get' }),
asyncHandler(async (request, response) => {
const sessionId = request.params.sessionId?.trim() || '';
if (!sessionId) {
throw badRequest('sessionId is required');
}
sendApiResponse(
response,
await getRuntimeStoryState({
runtimeRepository: context.runtimeRepository,
userId: request.userId!,
sessionId,
}),
);
}),
);
return router;
}