215 lines
6.4 KiB
TypeScript
215 lines
6.4 KiB
TypeScript
import { Router } from 'express';
|
|
import { z } from 'zod';
|
|
|
|
import type {
|
|
PlatformBrowseHistoryBatchSyncRequest,
|
|
PlatformBrowseHistoryResponse,
|
|
PlatformBrowseHistoryWriteEntry,
|
|
ProfileDashboardSummary,
|
|
ProfilePlayStatsResponse,
|
|
ProfileWalletLedgerResponse,
|
|
RuntimeSettings,
|
|
} from '../../../../packages/shared/src/contracts/runtime.js';
|
|
import { PLATFORM_THEMES } from '../../../../packages/shared/src/contracts/runtime.js';
|
|
import type { AppContext } from '../../context.js';
|
|
import { asyncHandler, sendApiResponse } from '../../http.js';
|
|
import { requireJwtAuth } from '../../middleware/auth.js';
|
|
import { routeMeta } from '../../middleware/routeMeta.js';
|
|
|
|
const platformBrowseHistoryEntrySchema = z.object({
|
|
ownerUserId: z.string().trim().min(1),
|
|
profileId: z.string().trim().min(1),
|
|
worldName: z.string().trim().min(1),
|
|
subtitle: z.string().trim().optional().default(''),
|
|
summaryText: z.string().trim().optional().default(''),
|
|
coverImageSrc: z.string().trim().nullable().optional().default(null),
|
|
themeMode: z.string().trim().optional().default('mythic'),
|
|
authorDisplayName: z.string().trim().optional().default('玩家'),
|
|
visitedAt: z.string().trim().optional().default(''),
|
|
});
|
|
|
|
const platformBrowseHistoryBatchSchema = z.object({
|
|
entries: z.array(platformBrowseHistoryEntrySchema).max(100),
|
|
});
|
|
|
|
const settingsSchema = z.object({
|
|
musicVolume: z.number().min(0).max(1),
|
|
platformTheme: z.enum(PLATFORM_THEMES),
|
|
});
|
|
|
|
export const RPG_PROFILE_ROUTE_BASE_PATH = '/api/runtime/profile';
|
|
export const RPG_PROFILE_LEGACY_ROUTE_BASE_PATH = '/api/profile';
|
|
|
|
function routeCompatPaths(path: string) {
|
|
return [path, path.replace('runtime/', '')] as const;
|
|
}
|
|
|
|
export function createRpgProfileRoutes(context: AppContext) {
|
|
const router = Router();
|
|
const requireAuth = requireJwtAuth(context.config, context.userRepository);
|
|
|
|
routeCompatPaths('/api/runtime/profile/dashboard').forEach((path, index) => {
|
|
router.get(
|
|
path.replace('/api/', '/'),
|
|
requireAuth,
|
|
routeMeta({
|
|
operation:
|
|
index === 0
|
|
? 'profile.dashboard.get'
|
|
: 'profile.dashboard.get.compat',
|
|
}),
|
|
asyncHandler(async (request, response) => {
|
|
sendApiResponse<ProfileDashboardSummary>(
|
|
response,
|
|
await context.rpgProfileDashboardRepository.getProfileDashboard(
|
|
request.userId!,
|
|
),
|
|
);
|
|
}),
|
|
);
|
|
});
|
|
|
|
routeCompatPaths('/api/runtime/profile/wallet-ledger').forEach((path, index) => {
|
|
router.get(
|
|
path.replace('/api/', '/'),
|
|
requireAuth,
|
|
routeMeta({
|
|
operation:
|
|
index === 0
|
|
? 'profile.walletLedger.list'
|
|
: 'profile.walletLedger.list.compat',
|
|
}),
|
|
asyncHandler(async (request, response) => {
|
|
sendApiResponse<ProfileWalletLedgerResponse>(response, {
|
|
entries:
|
|
await context.rpgProfileDashboardRepository.listProfileWalletLedger(
|
|
request.userId!,
|
|
),
|
|
});
|
|
}),
|
|
);
|
|
});
|
|
|
|
routeCompatPaths('/api/runtime/profile/play-stats').forEach((path, index) => {
|
|
router.get(
|
|
path.replace('/api/', '/'),
|
|
requireAuth,
|
|
routeMeta({
|
|
operation:
|
|
index === 0
|
|
? 'profile.playStats.get'
|
|
: 'profile.playStats.get.compat',
|
|
}),
|
|
asyncHandler(async (request, response) => {
|
|
sendApiResponse<ProfilePlayStatsResponse>(
|
|
response,
|
|
await context.rpgProfileDashboardRepository.getProfilePlayStats(
|
|
request.userId!,
|
|
),
|
|
);
|
|
}),
|
|
);
|
|
});
|
|
|
|
routeCompatPaths('/api/runtime/profile/browse-history').forEach((path, index) => {
|
|
router.get(
|
|
path.replace('/api/', '/'),
|
|
requireAuth,
|
|
routeMeta({
|
|
operation:
|
|
index === 0
|
|
? 'profile.browseHistory.list'
|
|
: 'profile.browseHistory.list.compat',
|
|
}),
|
|
asyncHandler(async (request, response) => {
|
|
sendApiResponse<PlatformBrowseHistoryResponse>(response, {
|
|
entries: await context.rpgBrowseHistoryRepository.listPlatformBrowseHistory(
|
|
request.userId!,
|
|
),
|
|
});
|
|
}),
|
|
);
|
|
|
|
router.post(
|
|
path.replace('/api/', '/'),
|
|
requireAuth,
|
|
routeMeta({
|
|
operation:
|
|
index === 0
|
|
? 'profile.browseHistory.upsert'
|
|
: 'profile.browseHistory.upsert.compat',
|
|
}),
|
|
asyncHandler(async (request, response) => {
|
|
const rawBody =
|
|
request.body && typeof request.body === 'object' ? request.body : {};
|
|
const payload = (
|
|
'entries' in rawBody
|
|
? platformBrowseHistoryBatchSchema.parse(rawBody)
|
|
: platformBrowseHistoryEntrySchema.parse(rawBody)
|
|
) as
|
|
| PlatformBrowseHistoryBatchSyncRequest
|
|
| PlatformBrowseHistoryWriteEntry;
|
|
|
|
const entries = 'entries' in payload ? payload.entries : [payload];
|
|
|
|
sendApiResponse<PlatformBrowseHistoryResponse>(response, {
|
|
entries:
|
|
await context.rpgBrowseHistoryRepository.upsertPlatformBrowseHistoryEntries(
|
|
request.userId!,
|
|
entries,
|
|
),
|
|
});
|
|
}),
|
|
);
|
|
|
|
router.delete(
|
|
path.replace('/api/', '/'),
|
|
requireAuth,
|
|
routeMeta({
|
|
operation:
|
|
index === 0
|
|
? 'profile.browseHistory.clear'
|
|
: 'profile.browseHistory.clear.compat',
|
|
}),
|
|
asyncHandler(async (request, response) => {
|
|
await context.rpgBrowseHistoryRepository.clearPlatformBrowseHistory(
|
|
request.userId!,
|
|
);
|
|
sendApiResponse<PlatformBrowseHistoryResponse>(response, {
|
|
entries: [],
|
|
});
|
|
}),
|
|
);
|
|
});
|
|
|
|
router.get(
|
|
'/api/runtime/settings'.replace('/api/', '/'),
|
|
requireAuth,
|
|
routeMeta({ operation: 'runtime.settings.get' }),
|
|
asyncHandler(async (request, response) => {
|
|
sendApiResponse(
|
|
response,
|
|
await context.rpgProfileDashboardRepository.getSettings(request.userId!),
|
|
);
|
|
}),
|
|
);
|
|
|
|
router.put(
|
|
'/api/runtime/settings'.replace('/api/', '/'),
|
|
requireAuth,
|
|
routeMeta({ operation: 'runtime.settings.put' }),
|
|
asyncHandler(async (request, response) => {
|
|
const payload = settingsSchema.parse(request.body) as RuntimeSettings;
|
|
sendApiResponse(
|
|
response,
|
|
await context.rpgProfileDashboardRepository.putSettings(
|
|
request.userId!,
|
|
payload,
|
|
),
|
|
);
|
|
}),
|
|
);
|
|
|
|
return router;
|
|
}
|