This commit is contained in:
@@ -4,6 +4,10 @@ import { z } from 'zod';
|
||||
import type {
|
||||
AnswerCustomWorldSessionQuestionRequest,
|
||||
CreateCustomWorldSessionRequest,
|
||||
CustomWorldGalleryDetailResponse,
|
||||
CustomWorldGalleryResponse,
|
||||
CustomWorldLibraryMutationResponse,
|
||||
CustomWorldLibraryResponse,
|
||||
RuntimeSettings,
|
||||
SavedGameSnapshotInput,
|
||||
} from '../../../packages/shared/src/contracts/runtime.js';
|
||||
@@ -27,6 +31,8 @@ import {
|
||||
prepareEventStreamResponse,
|
||||
sendApiResponse,
|
||||
} from '../http.js';
|
||||
import { requireJwtAuth } from '../middleware/auth.js';
|
||||
import { routeMeta } from '../middleware/routeMeta.js';
|
||||
import {
|
||||
generateCharacterChatSuggestionsFromOrchestrator,
|
||||
generateCharacterChatSummaryFromOrchestrator,
|
||||
@@ -34,8 +40,6 @@ import {
|
||||
streamNpcChatDialogueFromOrchestrator,
|
||||
streamNpcRecruitDialogueFromOrchestrator,
|
||||
} from '../modules/ai/chatOrchestrator.js';
|
||||
import { requireJwtAuth } from '../middleware/auth.js';
|
||||
import { routeMeta } from '../middleware/routeMeta.js';
|
||||
import {
|
||||
hydrateSavedSnapshot,
|
||||
normalizeSavedSnapshotPayload,
|
||||
@@ -104,6 +108,15 @@ function readParam(param: string | string[] | undefined) {
|
||||
return Array.isArray(param) ? param[0]?.trim() || '' : param?.trim() || '';
|
||||
}
|
||||
|
||||
async function resolveAuthDisplayName(context: AppContext, userId: string) {
|
||||
const user = await context.userRepository.findById(userId);
|
||||
if (!user) {
|
||||
throw notFound('user not found');
|
||||
}
|
||||
|
||||
return user.displayName?.trim() || '玩家';
|
||||
}
|
||||
|
||||
export function createRuntimeRoutes(context: AppContext) {
|
||||
const router = Router();
|
||||
const requireAuth = requireJwtAuth(context.config, context.userRepository);
|
||||
@@ -202,11 +215,55 @@ export function createRuntimeRoutes(context: AppContext) {
|
||||
'/runtime/custom-world-library',
|
||||
routeMeta({ operation: 'runtime.customWorldLibrary.list' }),
|
||||
asyncHandler(async (request, response) => {
|
||||
sendApiResponse(response, {
|
||||
profiles: await context.runtimeRepository.listCustomWorldProfiles(
|
||||
request.userId!,
|
||||
),
|
||||
});
|
||||
sendApiResponse(
|
||||
response,
|
||||
{
|
||||
entries: await context.runtimeRepository.listCustomWorldProfiles(
|
||||
request.userId!,
|
||||
),
|
||||
} satisfies CustomWorldLibraryResponse,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/runtime/custom-world-gallery',
|
||||
routeMeta({ operation: 'runtime.customWorldGallery.list' }),
|
||||
asyncHandler(async (_request, response) => {
|
||||
sendApiResponse(
|
||||
response,
|
||||
{
|
||||
entries: await context.runtimeRepository.listPublishedCustomWorldGallery(),
|
||||
} satisfies CustomWorldGalleryResponse,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
router.get(
|
||||
'/runtime/custom-world-gallery/:ownerUserId/:profileId',
|
||||
routeMeta({ operation: 'runtime.customWorldGallery.detail' }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const ownerUserId = readParam(request.params.ownerUserId);
|
||||
const profileId = readParam(request.params.profileId);
|
||||
if (!ownerUserId || !profileId) {
|
||||
throw badRequest('ownerUserId and profileId are required');
|
||||
}
|
||||
|
||||
const entry =
|
||||
await context.runtimeRepository.getPublishedCustomWorldGalleryDetail(
|
||||
ownerUserId,
|
||||
profileId,
|
||||
);
|
||||
if (!entry) {
|
||||
throw notFound('public custom world not found');
|
||||
}
|
||||
|
||||
sendApiResponse(
|
||||
response,
|
||||
{
|
||||
entry,
|
||||
} satisfies CustomWorldGalleryDetailResponse,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -219,13 +276,19 @@ export function createRuntimeRoutes(context: AppContext) {
|
||||
throw badRequest('profileId is required');
|
||||
}
|
||||
const payload = customWorldProfileSchema.parse(request.body);
|
||||
sendApiResponse(response, {
|
||||
profiles: await context.runtimeRepository.upsertCustomWorldProfile(
|
||||
const authorDisplayName = await resolveAuthDisplayName(
|
||||
context,
|
||||
request.userId!,
|
||||
);
|
||||
sendApiResponse(
|
||||
response,
|
||||
await context.runtimeRepository.upsertCustomWorldProfile(
|
||||
request.userId!,
|
||||
profileId,
|
||||
jsonClone(payload.profile),
|
||||
authorDisplayName,
|
||||
),
|
||||
});
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -237,12 +300,75 @@ export function createRuntimeRoutes(context: AppContext) {
|
||||
if (!profileId) {
|
||||
throw badRequest('profileId is required');
|
||||
}
|
||||
sendApiResponse(response, {
|
||||
profiles: await context.runtimeRepository.deleteCustomWorldProfile(
|
||||
sendApiResponse(
|
||||
response,
|
||||
{
|
||||
entries: await context.runtimeRepository.deleteCustomWorldProfile(
|
||||
request.userId!,
|
||||
profileId,
|
||||
),
|
||||
} satisfies CustomWorldLibraryResponse,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/runtime/custom-world-library/:profileId/publish',
|
||||
routeMeta({ operation: 'runtime.customWorldLibrary.publish' }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const profileId = readParam(request.params.profileId);
|
||||
if (!profileId) {
|
||||
throw badRequest('profileId is required');
|
||||
}
|
||||
|
||||
const authorDisplayName = await resolveAuthDisplayName(
|
||||
context,
|
||||
request.userId!,
|
||||
);
|
||||
const mutation =
|
||||
await context.runtimeRepository.publishCustomWorldProfile(
|
||||
request.userId!,
|
||||
profileId,
|
||||
),
|
||||
});
|
||||
authorDisplayName,
|
||||
);
|
||||
if (!mutation) {
|
||||
throw notFound('custom world not found');
|
||||
}
|
||||
|
||||
sendApiResponse(
|
||||
response,
|
||||
mutation satisfies CustomWorldLibraryMutationResponse,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/runtime/custom-world-library/:profileId/unpublish',
|
||||
routeMeta({ operation: 'runtime.customWorldLibrary.unpublish' }),
|
||||
asyncHandler(async (request, response) => {
|
||||
const profileId = readParam(request.params.profileId);
|
||||
if (!profileId) {
|
||||
throw badRequest('profileId is required');
|
||||
}
|
||||
|
||||
const authorDisplayName = await resolveAuthDisplayName(
|
||||
context,
|
||||
request.userId!,
|
||||
);
|
||||
const mutation =
|
||||
await context.runtimeRepository.unpublishCustomWorldProfile(
|
||||
request.userId!,
|
||||
profileId,
|
||||
authorDisplayName,
|
||||
);
|
||||
if (!mutation) {
|
||||
throw notFound('custom world not found');
|
||||
}
|
||||
|
||||
sendApiResponse(
|
||||
response,
|
||||
mutation satisfies CustomWorldLibraryMutationResponse,
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
|
||||
Reference in New Issue
Block a user