Files
memind/admin-routes.mjs
T
john fde6503bdf
Memind CI / Test, build, and release guards (push) Has been cancelled
feat(mindspace): add SEO/GEO delivery, page template catalog, and admin hooks
Enable optional SEO/GEO injection and discovery routes for confirmed public pages while keeping private pages noindex. Add premium page template skills, portal catalog API, template shop UI, and Baidu push gated by memind_adm config.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-10 08:06:12 +08:00

1322 lines
53 KiB
JavaScript

// admin-routes.mjs
//
// Back-office HTTP route factories for the standalone memind_adm service.
//
// Route logic lives here exactly once. The public server (server.mjs) no longer
// serves these surfaces; admin-server.mjs is the sole consumer. Services are
// injected so the same router can be wired against any container (tests, future
// read-replica pool, etc.).
//
// createAdminApi -> /admin-api/* platform super-admin (role === 'admin')
// createOpsApi -> /api/ops/v1/* plaza operations console (ops_role)
import express from 'express';
import { sendData, sendError } from './api-response.mjs';
import { PLAN_CATALOG, tokensToCallsApprox } from './billing-subscription.mjs';
import { hasOpsRole } from './plaza-ops.mjs';
import { mapPlazaError } from './plaza-posts.mjs';
function plazaRouteError(res, req, error) {
const status = mapPlazaError(error);
const code = error?.code ?? 'internal_error';
const message = error instanceof Error ? error.message : 'Plaza 请求失败';
return sendError(res, req, status, code, message, error?.details);
}
/**
* Platform super-admin API (role === 'admin').
* @param {object} deps
* @param {import('express').RequestHandler} deps.jsonBody JSON body parser middleware.
* @param {(req: import('express').Request) => (string|undefined)} deps.getToken Extract the user session token from a request.
* @param {Promise<unknown>} [deps.ready] Optional bootstrap gate awaited before the first request resolves auth.
* @param {object} deps.userAuth
* @param {object|null} deps.llmProviderService
* @param {object|null} deps.memoryV2ConfigService
* @param {object|null} deps.memoryV2AdminOpsService
* @param {object|null} deps.goalRunAdminOpsService
* @param {object|null} deps.orchestratorConfigService
* @param {object|null} deps.orchestratorObservabilityService
* @param {object|null} deps.skillRuntimeConfigService
* @param {object|null} deps.systemDisclosurePolicyService
* @param {object|null} deps.agentCodeRunPolicyService
* @param {object|null} deps.adminSystemTestService
* @param {object|null} deps.plazaPosts
* @param {object|null} deps.plazaOps
* @param {object|null} deps.wechatAdmin
* @param {object|null} deps.wechatIntentRouterConfigService
*/
export function createAdminApi({
jsonBody,
getToken,
ready,
userAuth,
llmProviderService,
assetGatewayConfigService,
imageMakeAdminConfigService,
memoryV2ConfigService,
memoryV2AdminOpsService,
goalRunAdminOpsService,
orchestratorConfigService,
orchestratorObservabilityService,
mindSearchConfigService,
skillRuntimeConfigService,
systemDisclosurePolicyService,
agentCodeRunPolicyService,
adminSystemTestService,
plazaPosts,
plazaOps,
wechatAdmin,
wechatIntentRouterConfigService,
subscriptionService,
templateCatalogService,
}) {
function requireAdmin(req, res, next) {
if (!req.currentUser || req.currentUser.role !== 'admin') {
res.status(403).json({ message: '需要管理员权限' });
return;
}
next();
}
const adminApi = express.Router();
adminApi.use(jsonBody);
adminApi.use(async (req, res, next) => {
if (ready) await ready;
if (!userAuth) return res.status(503).json({ message: '未启用用户系统' });
const me = await userAuth.getMe(getToken(req));
if (!me) return res.status(401).json({ message: '未登录' });
req.currentUser = me;
next();
});
adminApi.get('/users', requireAdmin, async (req, res) => {
const { page, pageSize, search, role, status } = req.query;
const result = await userAuth.listUsers({ page, pageSize, search, role, status });
res.json(result);
});
adminApi.get('/summary', requireAdmin, async (_req, res) => {
const summary = await userAuth.getAdminSummary();
let llm = null;
if (llmProviderService) {
const keys = await llmProviderService.listKeys();
const global = await llmProviderService.getGlobalSettings();
const selected = keys.find((key) => key.isSelected);
llm = {
keyCount: keys.length,
selectedKeyName: selected?.name ?? null,
globalModel: global?.model ?? null,
};
}
res.json({ summary: { ...summary, llm } });
});
adminApi.get('/asset-gateway/config', requireAdmin, async (_req, res) => {
if (!assetGatewayConfigService?.getConfig) {
return res.status(503).json({ message: '资产能力配置服务未启用' });
}
return res.json(await assetGatewayConfigService.getConfig());
});
adminApi.put('/asset-gateway/config', requireAdmin, async (req, res) => {
if (!assetGatewayConfigService?.updateGlobalConfig) {
return res.status(503).json({ message: '资产能力配置服务未启用' });
}
return res.json(await assetGatewayConfigService.updateGlobalConfig(req.body ?? {}, {
updatedBy: req.currentUser.id,
}));
});
adminApi.put('/asset-gateway/plugins/:pluginId', requireAdmin, async (req, res) => {
if (!assetGatewayConfigService?.updatePluginConfig) {
return res.status(503).json({ message: '资产能力配置服务未启用' });
}
const result = await assetGatewayConfigService.updatePluginConfig(req.params.pluginId, req.body ?? {}, {
updatedBy: req.currentUser.id,
});
if (!result.ok) return res.status(400).json({ message: result.message });
return res.json(result);
});
adminApi.get('/image-make/config', requireAdmin, async (_req, res) => {
if (!imageMakeAdminConfigService?.getAdminConfig) {
return res.status(503).json({ message: 'image_make 配置服务未启用' });
}
return res.json(await imageMakeAdminConfigService.getAdminConfig());
});
const updateImageMakeConfig = async (req, res) => {
if (!imageMakeAdminConfigService?.updateAdminConfig) {
return res.status(503).json({ message: 'image_make 配置服务未启用' });
}
const result = await imageMakeAdminConfigService.updateAdminConfig(req.body ?? {}, {
updatedBy: req.currentUser.id,
});
if (result.ok === false) return res.status(400).json({ message: result.message });
return res.json(result);
};
adminApi.put('/image-make/config', requireAdmin, updateImageMakeConfig);
adminApi.patch('/image-make/config', requireAdmin, updateImageMakeConfig);
adminApi.get('/image-make/runtime', requireAdmin, async (_req, res) => {
if (!imageMakeAdminConfigService?.getRuntimeConfig) {
return res.status(503).json({ message: 'image_make 配置服务未启用' });
}
const runtime = await imageMakeAdminConfigService.getRuntimeConfig();
if (!runtime.ok) return res.status(503).json({ message: runtime.message ?? 'image_make 运行时配置无效' });
const { providers, ...rest } = runtime;
return res.json({
...rest,
providers: {
mock: { enabled: providers.mock.enabled },
aliyun_bailian: providers.aliyun_bailian.enabled
? {
enabled: true,
model: providers.aliyun_bailian.model,
apiBase: providers.aliyun_bailian.apiBase,
apiKeyConfigured: Boolean(providers.aliyun_bailian.apiKey),
}
: { enabled: false },
comfyui: providers.comfyui.enabled
? { enabled: true, ...providers.comfyui }
: { enabled: false },
},
});
});
adminApi.get('/memory-v2/config', requireAdmin, async (_req, res) => {
if (!memoryV2ConfigService?.getAdminConfig) {
return res.status(503).json({ message: 'Memory V2 配置服务未启用' });
}
const result = await memoryV2ConfigService.getAdminConfig();
return res.json(result);
});
const updateMemoryV2Config = async (req, res) => {
if (!memoryV2ConfigService?.updateAdminConfig) {
return res.status(503).json({ message: 'Memory V2 配置服务未启用' });
}
const result = await memoryV2ConfigService.updateAdminConfig(req.body ?? {}, {
updatedBy: req.currentUser.id,
});
return res.json(result);
};
adminApi.put('/memory-v2/config', requireAdmin, updateMemoryV2Config);
adminApi.patch('/memory-v2/config', requireAdmin, updateMemoryV2Config);
adminApi.get('/mindsearch/config', requireAdmin, async (_req, res) => {
if (!mindSearchConfigService?.getAdminConfig) return res.status(503).json({ message: 'MindSearch 配置服务未启用' });
return res.json(await mindSearchConfigService.getAdminConfig());
});
const updateMindSearchConfig = async (req, res) => {
if (!mindSearchConfigService?.updateAdminConfig) return res.status(503).json({ message: 'MindSearch 配置服务未启用' });
return res.json(await mindSearchConfigService.updateAdminConfig(req.body ?? {}, { updatedBy: req.currentUser.id }));
};
adminApi.put('/mindsearch/config', requireAdmin, updateMindSearchConfig);
adminApi.patch('/mindsearch/config', requireAdmin, updateMindSearchConfig);
adminApi.get('/mindsearch/runtime', requireAdmin, async (_req, res) => {
if (!mindSearchConfigService?.getRuntimeState) return res.status(503).json({ message: 'MindSearch 配置服务未启用' });
return res.json(await mindSearchConfigService.getRuntimeState());
});
adminApi.post('/mindsearch/services/:serviceId/test', requireAdmin, async (req, res) => {
if (!mindSearchConfigService?.testService) return res.status(503).json({ message: 'MindSearch 服务测试未启用' });
try {
return res.json(await mindSearchConfigService.testService(req.params.serviceId));
} catch (error) {
if (error?.code === 'SEARCH_SERVICE_NOT_FOUND') return res.status(404).json({ message: error.message });
throw error;
}
});
adminApi.get('/memory-v2/runtime', requireAdmin, async (_req, res) => {
if (!memoryV2ConfigService?.getRuntimeState) {
return res.status(503).json({ message: 'Memory V2 配置服务未启用' });
}
const result = await memoryV2ConfigService.getRuntimeState();
return res.json(result);
});
adminApi.get('/memory-v2/status', requireAdmin, async (_req, res) => {
const portalBase = String(
process.env.SYSTEM_TEST_PORTAL_BASE_URL
?? `http://127.0.0.1:${process.env.H5_PORT ?? 8081}`,
).replace(/\/$/, '');
try {
const upstream = await fetch(`${portalBase}/api/runtime/status`, {
headers: { accept: 'application/json' },
});
const payload = await upstream.json().catch(() => ({}));
if (!upstream.ok) {
return res.status(upstream.status).json({
ok: false,
message: payload?.message ?? '读取 Portal Memory V2 运行时状态失败',
memory: null,
});
}
return res.json({
ok: Boolean(payload?.ok ?? true),
memory: payload?.memory ?? null,
});
} catch (err) {
return res.status(502).json({
ok: false,
message: err instanceof Error ? err.message : '读取 Portal Memory V2 运行时状态失败',
memory: null,
});
}
});
adminApi.get('/memory-v2/metrics', requireAdmin, async (req, res) => {
if (!memoryV2AdminOpsService?.getProductMetrics) {
return res.status(503).json({ message: 'Memory V2 指标服务未启用' });
}
try {
const since = String(req.query?.since ?? '7d').trim() || '7d';
const userId = String(req.query?.userId ?? '').trim() || null;
const metrics = await memoryV2AdminOpsService.getProductMetrics({ since, userId });
const audit = await memoryV2AdminOpsService.getShadowAudit({ since, userId });
const counts = await memoryV2AdminOpsService.countCandidatesByStatus();
return res.json({ metrics, audit, candidateCounts: counts });
} catch (err) {
return res.status(500).json({
message: err instanceof Error ? err.message : '读取 Memory V2 指标失败',
});
}
});
adminApi.get('/memory-v2/candidates', requireAdmin, async (req, res) => {
if (!memoryV2AdminOpsService?.listCandidates) {
return res.status(503).json({ message: 'Memory V2 候选服务未启用' });
}
try {
const status = String(req.query?.status ?? 'candidate').trim() || 'candidate';
const userId = String(req.query?.userId ?? '').trim() || null;
const limit = Number(req.query?.limit ?? 50);
const offset = Number(req.query?.offset ?? 0);
const items = await memoryV2AdminOpsService.listCandidates({ status, userId, limit, offset });
const counts = await memoryV2AdminOpsService.countCandidatesByStatus();
return res.json({ items, status, limit, offset, counts });
} catch (err) {
return res.status(400).json({
message: err instanceof Error ? err.message : '读取候选记忆失败',
});
}
});
adminApi.post('/memory-v2/candidates/auto-review', requireAdmin, async (req, res) => {
if (!memoryV2AdminOpsService?.runAutoReview) {
return res.status(503).json({ message: 'Memory V2 候选服务未启用' });
}
try {
const userId = String(req.body?.userId ?? '').trim() || null;
const limit = Number(req.body?.limit ?? 200);
const result = await memoryV2AdminOpsService.runAutoReview({ userId, limit });
return res.json({ ok: true, ...result });
} catch (err) {
return res.status(500).json({
message: err instanceof Error ? err.message : '自动审核候选记忆失败',
});
}
});
adminApi.post('/memory-v2/candidates/:id/review', requireAdmin, async (req, res) => {
if (!memoryV2AdminOpsService?.reviewCandidate) {
return res.status(503).json({ message: 'Memory V2 候选服务未启用' });
}
const status = String(req.body?.status ?? '').trim();
if (!['accepted', 'rejected'].includes(status)) {
return res.status(400).json({ message: 'status 必须是 accepted 或 rejected' });
}
try {
const result = await memoryV2AdminOpsService.reviewCandidate(req.params.id, status, {
reviewedBy: req.currentUser.id,
});
if (!result.updated) {
return res.status(409).json({ message: '候选不存在或已审核' });
}
return res.json({ ok: true, ...result });
} catch (err) {
return res.status(400).json({
message: err instanceof Error ? err.message : '审核候选记忆失败',
});
}
});
adminApi.get('/goal-runs/runtime', requireAdmin, async (_req, res) => {
if (!goalRunAdminOpsService?.getRuntime) {
return res.status(503).json({ message: 'Goal Run 管理服务未启用' });
}
return res.json(goalRunAdminOpsService.getRuntime());
});
adminApi.get('/goal-runs/summary', requireAdmin, async (_req, res) => {
if (!goalRunAdminOpsService?.countByStatus) {
return res.status(503).json({ message: 'Goal Run 管理服务未启用' });
}
try {
const counts = await goalRunAdminOpsService.countByStatus();
const runtime = goalRunAdminOpsService.getRuntime();
return res.json({ counts, runtime });
} catch (err) {
return res.status(500).json({
message: err instanceof Error ? err.message : '读取 Goal Run 汇总失败',
});
}
});
adminApi.get('/goal-runs', requireAdmin, async (req, res) => {
if (!goalRunAdminOpsService?.listGoals) {
return res.status(503).json({ message: 'Goal Run 管理服务未启用' });
}
try {
const status = String(req.query?.status ?? '').trim() || null;
const userId = String(req.query?.userId ?? req.query?.user_id ?? '').trim() || null;
const limit = Number(req.query?.limit ?? 50);
const offset = Number(req.query?.offset ?? 0);
const items = await goalRunAdminOpsService.listGoals({ status, userId, limit, offset });
return res.json({ items, status, limit, offset });
} catch (err) {
return res.status(400).json({
message: err instanceof Error ? err.message : '读取 Goal Run 列表失败',
});
}
});
adminApi.get('/goal-runs/:goalRunId', requireAdmin, async (req, res) => {
if (!goalRunAdminOpsService?.getGoalDetail) {
return res.status(503).json({ message: 'Goal Run 管理服务未启用' });
}
try {
const goal = await goalRunAdminOpsService.getGoalDetail(req.params.goalRunId);
if (!goal) return res.status(404).json({ message: '目标不存在' });
return res.json({ goal });
} catch (err) {
return res.status(500).json({
message: err instanceof Error ? err.message : '读取 Goal Run 详情失败',
});
}
});
adminApi.post('/goal-runs/:goalRunId/checkpoints/:checkpointId/approve', requireAdmin, async (req, res) => {
if (!goalRunAdminOpsService?.approveCheckpoint) {
return res.status(503).json({ message: 'Goal Run 管理服务未启用' });
}
try {
const goal = await goalRunAdminOpsService.approveCheckpoint({
goalRunId: req.params.goalRunId,
checkpointId: req.params.checkpointId,
feedback: req.body?.feedback ?? null,
reviewedBy: req.currentUser.id,
});
return res.json({ ok: true, goal });
} catch (err) {
const status = err?.code === 'GOAL_RUN_NOT_FOUND' || err?.code === 'GOAL_CHECKPOINT_NOT_FOUND'
? 404
: err?.code === 'GOAL_CHECKPOINT_NOT_APPROVABLE'
? 409
: 500;
return res.status(status).json({
message: err instanceof Error ? err.message : '确认阶段失败',
code: err?.code ?? null,
});
}
});
adminApi.post('/goal-runs/:goalRunId/cancel', requireAdmin, async (req, res) => {
if (!goalRunAdminOpsService?.cancelGoal) {
return res.status(503).json({ message: 'Goal Run 管理服务未启用' });
}
try {
const goal = await goalRunAdminOpsService.cancelGoal({
goalRunId: req.params.goalRunId,
reviewedBy: req.currentUser.id,
});
return res.json({ ok: true, goal });
} catch (err) {
const status = err?.code === 'GOAL_RUN_NOT_FOUND' ? 404 : err?.code === 'GOAL_RUN_NOT_CANCELLABLE' ? 409 : 500;
return res.status(status).json({
message: err instanceof Error ? err.message : '取消目标失败',
code: err?.code ?? null,
});
}
});
adminApi.get('/orchestrator/config', requireAdmin, async (_req, res) => {
if (!orchestratorConfigService?.getAdminConfig) {
return res.status(503).json({ message: 'Orchestrator 配置服务未启用' });
}
return res.json(await orchestratorConfigService.getAdminConfig());
});
const updateOrchestratorConfig = async (req, res) => {
if (!orchestratorConfigService?.updateAdminConfig) {
return res.status(503).json({ message: 'Orchestrator 配置服务未启用' });
}
const result = await orchestratorConfigService.updateAdminConfig(
req.body?.config ?? req.body ?? {},
{ updatedBy: req.currentUser.id },
);
return res.json(result);
};
adminApi.put('/orchestrator/config', requireAdmin, updateOrchestratorConfig);
adminApi.patch('/orchestrator/config', requireAdmin, updateOrchestratorConfig);
adminApi.get('/orchestrator/runtime', requireAdmin, async (_req, res) => {
if (!orchestratorConfigService?.getRuntimeState) {
return res.status(503).json({ message: 'Orchestrator 配置服务未启用' });
}
return res.json(await orchestratorConfigService.getRuntimeState({ probe: true }));
});
adminApi.get('/orchestrator/shadow-runs', requireAdmin, async (req, res) => {
if (!orchestratorObservabilityService?.listShadowRuns) {
return res.status(503).json({ message: 'Orchestrator 观测服务未启用' });
}
return res.json(await orchestratorObservabilityService.listShadowRuns({
hours: req.query.hours,
limit: req.query.limit,
status: req.query.status,
}));
});
adminApi.get('/orchestrator/execution-plans', requireAdmin, async (req, res) => {
if (!orchestratorObservabilityService?.listExecutionPlans) {
return res.status(503).json({ message: 'Orchestrator Dry-run 观测服务未启用' });
}
return res.json(await orchestratorObservabilityService.listExecutionPlans({
hours: req.query.hours,
limit: req.query.limit,
selection: req.query.selection,
}));
});
adminApi.get('/orchestrator/canary-readiness', requireAdmin, async (_req, res) => {
if (!orchestratorObservabilityService?.getCanaryReadiness) {
return res.status(503).json({ message: 'Orchestrator 观测服务未启用' });
}
return res.json(await orchestratorObservabilityService.getCanaryReadiness());
});
adminApi.get('/orchestrator/shadow-runs/:runId', requireAdmin, async (req, res) => {
if (!orchestratorObservabilityService?.getShadowRun) {
return res.status(503).json({ message: 'Orchestrator 观测服务未启用' });
}
const result = await orchestratorObservabilityService.getShadowRun(req.params.runId);
if (!result) return res.status(404).json({ message: 'Shadow run 不存在' });
return res.json(result);
});
adminApi.get('/skill-runtime/config', requireAdmin, async (_req, res) => {
if (!skillRuntimeConfigService?.getAdminConfig) {
return res.status(503).json({ message: 'Skill Runtime 配置服务未启用' });
}
return res.json(await skillRuntimeConfigService.getAdminConfig());
});
const updateSkillRuntimeConfig = async (req, res) => {
if (!skillRuntimeConfigService?.updateAdminConfig) {
return res.status(503).json({ message: 'Skill Runtime 配置服务未启用' });
}
const result = await skillRuntimeConfigService.updateAdminConfig(req.body?.config ?? req.body ?? {}, {
updatedBy: req.currentUser.id,
});
return res.json(result);
};
adminApi.put('/skill-runtime/config', requireAdmin, updateSkillRuntimeConfig);
adminApi.patch('/skill-runtime/config', requireAdmin, updateSkillRuntimeConfig);
adminApi.get('/system-disclosure-policy/config', requireAdmin, async (_req, res) => {
if (!systemDisclosurePolicyService?.getAdminConfig) {
return res.status(503).json({ message: 'System Disclosure Policy 服务未启用' });
}
return res.json(await systemDisclosurePolicyService.getAdminConfig());
});
const updateSystemDisclosurePolicy = async (req, res) => {
if (!systemDisclosurePolicyService?.updateAdminConfig) {
return res.status(503).json({ message: 'System Disclosure Policy 服务未启用' });
}
const result = await systemDisclosurePolicyService.updateAdminConfig(req.body?.config ?? req.body ?? {}, {
updatedBy: req.currentUser.id,
});
return res.json(result);
};
adminApi.put('/system-disclosure-policy/config', requireAdmin, updateSystemDisclosurePolicy);
adminApi.patch('/system-disclosure-policy/config', requireAdmin, updateSystemDisclosurePolicy);
adminApi.get('/system-disclosure-policy/runtime', requireAdmin, async (_req, res) => {
if (!systemDisclosurePolicyService?.getRuntimeState) {
return res.status(503).json({ message: 'System Disclosure Policy 服务未启用' });
}
return res.json(systemDisclosurePolicyService.getRuntimeState());
});
adminApi.get('/skill-runtime/catalog', requireAdmin, async (_req, res) => {
if (!skillRuntimeConfigService?.listCatalogSummary) {
return res.status(503).json({ message: 'Skill Runtime 配置服务未启用' });
}
return res.json({ catalog: await skillRuntimeConfigService.listCatalogSummary() });
});
adminApi.get('/skill-runtime/runtime', requireAdmin, async (_req, res) => {
if (!skillRuntimeConfigService?.getPublicRuntimeConfig) {
return res.status(503).json({ message: 'Skill Runtime 配置服务未启用' });
}
return res.json(await skillRuntimeConfigService.getPublicRuntimeConfig());
});
adminApi.get('/agent-code-run/config', requireAdmin, async (_req, res) => {
if (!agentCodeRunPolicyService?.getAdminConfig) {
return res.status(503).json({ message: 'Agent Code Run 配置服务未启用' });
}
return res.json(await agentCodeRunPolicyService.getAdminConfig());
});
const updateAgentCodeRunConfig = async (req, res) => {
if (!agentCodeRunPolicyService?.updateAdminConfig) {
return res.status(503).json({ message: 'Agent Code Run 配置服务未启用' });
}
try {
const result = await agentCodeRunPolicyService.updateAdminConfig(req.body?.config ?? req.body ?? {}, {
updatedBy: req.currentUser.id,
});
return res.json(result);
} catch (err) {
if (err?.code === 'CODE_RUN_POLICY_ENV_LOCKED') {
return res.status(409).json({ message: err.message });
}
throw err;
}
};
adminApi.put('/agent-code-run/config', requireAdmin, updateAgentCodeRunConfig);
adminApi.patch('/agent-code-run/config', requireAdmin, updateAgentCodeRunConfig);
adminApi.get('/agent-code-run/runtime', requireAdmin, async (_req, res) => {
if (!agentCodeRunPolicyService?.getRuntimeState) {
return res.status(503).json({ message: 'Agent Code Run 配置服务未启用' });
}
return res.json(await agentCodeRunPolicyService.getRuntimeState());
});
adminApi.get('/agent-code-run/runs', requireAdmin, async (req, res) => {
if (!agentCodeRunPolicyService?.listRecentPageDataDevRuns) {
return res.status(503).json({ message: 'Agent Code Run 配置服务未启用' });
}
const limit = Number(req.query?.limit ?? 50);
return res.json(await agentCodeRunPolicyService.listRecentPageDataDevRuns({ limit }));
});
adminApi.post('/system-tests/skill-validation', requireAdmin, async (req, res) => {
if (!adminSystemTestService?.runSkillValidation) {
return res.status(503).json({ message: '系统测试服务未启用' });
}
const username = String(req.body?.username ?? '').trim();
const password = String(req.body?.password ?? '');
const skillName = String(req.body?.skillName ?? 'service-integration-smoke').trim();
if (!username || !password) {
return res.status(400).json({ message: '请输入测试账号和密码' });
}
const result = await adminSystemTestService.runSkillValidation({ username, password, skillName });
return res.json(result);
});
adminApi.post('/users', requireAdmin, async (req, res) => {
const result = await userAuth.createUser(req.body ?? {});
if (!result.ok) return res.status(400).json({ message: result.message });
res.status(201).json({ user: result.user });
});
adminApi.patch('/users/:userId', requireAdmin, async (req, res) => {
const result = await userAuth.updateUser(req.params.userId, req.body ?? {});
if (!result.ok) return res.status(400).json({ message: result.message });
res.json({ user: result.user });
});
adminApi.post('/users/:userId/recharge', requireAdmin, async (req, res) => {
const amountCents = Number(req.body?.amountCents);
const note = typeof req.body?.note === 'string' ? req.body.note : '';
const result = await userAuth.recharge(
req.params.userId,
amountCents,
req.currentUser.id,
note,
);
if (!result.ok) return res.status(400).json({ message: result.message });
res.json({ user: result.user });
});
adminApi.get('/usage', requireAdmin, async (req, res) => {
const userId = typeof req.query.userId === 'string' ? req.query.userId : null;
const { page, pageSize } = req.query;
const result = await userAuth.listUsageRecords({ userId, page, pageSize });
res.json(result);
});
adminApi.get('/capabilities/catalog', requireAdmin, (_req, res) => {
res.json({ catalog: userAuth.capabilityCatalog });
});
adminApi.get('/capabilities/role/:role', requireAdmin, async (req, res) => {
const role = req.params.role === 'admin' ? 'admin' : 'user';
if (role === 'admin') {
return res.json({
role,
capabilities: Object.fromEntries(
userAuth.capabilityCatalog.map((item) => [item.key, true]),
),
unrestricted: true,
});
}
res.json(await userAuth.getRoleCapabilities('user'));
});
adminApi.put('/capabilities/role/:role', requireAdmin, async (req, res) => {
const role = req.params.role === 'admin' ? 'admin' : 'user';
const result = await userAuth.setRoleCapabilities(role, req.body?.capabilities ?? {});
if (!result.ok) return res.status(400).json({ message: result.message });
res.json(result);
});
adminApi.get('/users/:userId/capabilities', requireAdmin, async (req, res) => {
const result = await userAuth.getUserCapabilities(req.params.userId);
if (!result.ok) return res.status(404).json({ message: result.message });
res.json(result);
});
adminApi.put('/users/:userId/capabilities', requireAdmin, async (req, res) => {
const result = await userAuth.setUserCapabilities(
req.params.userId,
req.body?.capabilities ?? {},
);
if (!result.ok) return res.status(400).json({ message: result.message });
res.json(result);
});
adminApi.delete('/users/:userId/capabilities', requireAdmin, async (req, res) => {
const result = await userAuth.clearUserCapabilityOverrides(req.params.userId);
if (!result.ok) return res.status(404).json({ message: result.message });
res.json(result);
});
adminApi.get('/policies/catalog', requireAdmin, (_req, res) => {
res.json({ catalog: userAuth.policyCatalog });
});
adminApi.get('/policies/role/:role', requireAdmin, async (req, res) => {
const role = req.params.role === 'admin' ? 'admin' : 'user';
if (role === 'admin') {
return res.json({ role, policies: {}, unrestricted: true });
}
res.json(await userAuth.getRolePolicies('user'));
});
adminApi.put('/policies/role/:role', requireAdmin, async (req, res) => {
const role = req.params.role === 'admin' ? 'admin' : 'user';
const result = await userAuth.setRolePolicies(role, req.body?.policies ?? {});
if (!result.ok) return res.status(400).json({ message: result.message });
res.json(result);
});
adminApi.get('/users/:userId/policies', requireAdmin, async (req, res) => {
const result = await userAuth.getUserPolicies(req.params.userId);
if (!result.ok) return res.status(404).json({ message: result.message });
res.json(result);
});
adminApi.put('/users/:userId/policies', requireAdmin, async (req, res) => {
const result = await userAuth.setUserPolicies(req.params.userId, req.body?.policies ?? {});
if (!result.ok) return res.status(400).json({ message: result.message });
res.json(result);
});
adminApi.delete('/users/:userId/policies', requireAdmin, async (req, res) => {
const result = await userAuth.clearUserPolicyOverrides(req.params.userId);
if (!result.ok) return res.status(404).json({ message: result.message });
res.json(result);
});
adminApi.get('/skills/catalog', requireAdmin, (_req, res) => {
res.json({ catalog: userAuth.skillCatalog });
});
adminApi.get('/skills/role/:role', requireAdmin, async (req, res) => {
const role = req.params.role === 'admin' ? 'admin' : 'user';
if (role === 'admin') {
return res.json({
role,
skills: Object.fromEntries(userAuth.skillCatalog.map((item) => [item.name, true])),
});
}
res.json(await userAuth.getRoleSkills('user'));
});
adminApi.put('/skills/role/:role', requireAdmin, async (req, res) => {
const role = req.params.role === 'admin' ? 'admin' : 'user';
const result = await userAuth.setRoleSkills(role, req.body?.skills ?? {});
if (!result.ok) return res.status(400).json({ message: result.message });
res.json(result);
});
adminApi.get('/users/:userId/skills', requireAdmin, async (req, res) => {
const result = await userAuth.getUserSkills(req.params.userId);
if (!result.ok) return res.status(404).json({ message: result.message });
res.json(result);
});
adminApi.put('/users/:userId/skills', requireAdmin, async (req, res) => {
const result = await userAuth.setUserSkills(req.params.userId, req.body?.skills ?? {});
if (!result.ok) return res.status(400).json({ message: result.message });
res.json(result);
});
adminApi.delete('/users/:userId/skills', requireAdmin, async (req, res) => {
const result = await userAuth.clearUserSkillOverrides(req.params.userId);
if (!result.ok) return res.status(404).json({ message: result.message });
res.json(result);
});
adminApi.get('/template-catalog', requireAdmin, async (_req, res) => {
if (!templateCatalogService?.adminListCatalog) {
return res.status(503).json({ message: '模板商城未启用' });
}
res.json({ items: await templateCatalogService.adminListCatalog() });
});
adminApi.put('/template-catalog/:skillName', requireAdmin, async (req, res) => {
if (!templateCatalogService?.adminUpsertCatalog) {
return res.status(503).json({ message: '模板商城未启用' });
}
const result = await templateCatalogService.adminUpsertCatalog(req.params.skillName, req.body ?? {});
if (!result.ok) return res.status(400).json({ message: result.message });
res.json(result);
});
adminApi.post('/users/:userId/template-grants/:skillName', requireAdmin, async (req, res) => {
if (!templateCatalogService?.adminGrantTemplate) {
return res.status(503).json({ message: '模板商城未启用' });
}
const result = await templateCatalogService.adminGrantTemplate(req.params.userId, req.params.skillName);
if (!result.ok) return res.status(400).json({ message: result.message });
res.json(result);
});
adminApi.get('/ledger', requireAdmin, async (req, res) => {
const userId = typeof req.query.userId === 'string' ? req.query.userId : null;
const { page, pageSize } = req.query;
const result = await userAuth.listBillingLedger({ userId, page, pageSize });
res.json(result);
});
adminApi.get('/wechat/summary', requireAdmin, async (_req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
res.json(await wechatAdmin.getSummary());
});
adminApi.get('/wechat/bindings', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
res.json(await wechatAdmin.listBindings(req.query));
});
adminApi.get('/wechat/messages', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
res.json(await wechatAdmin.listMessages(req.query));
});
adminApi.get('/wechat/digests', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
res.json(await wechatAdmin.listDigests(req.query));
});
adminApi.get('/wechat/deliveries', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
res.json(await wechatAdmin.listDeliveries(req.query));
});
adminApi.get('/wechat/balance-alerts', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
res.json(await wechatAdmin.listBalanceAlerts(req.query));
});
adminApi.post('/wechat/balance-alerts', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
const result = await wechatAdmin.createBalanceAlert(req.body ?? {});
if (!result.ok) return res.status(400).json({ message: result.message });
res.status(201).json(result);
});
adminApi.post('/wechat/balance-alerts/:id/cancel', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
const result = await wechatAdmin.cancelBalanceAlert(req.params.id);
if (!result.ok) return res.status(404).json({ message: result.message ?? '取消失败' });
res.json(result);
});
adminApi.post('/wechat/balance-alerts/:id/resume', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
const result = await wechatAdmin.resumeBalanceAlert(req.params.id);
if (!result.ok) return res.status(404).json({ message: result.message ?? '恢复失败' });
res.json(result);
});
adminApi.get('/wechat/web-notifications', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
res.json(await wechatAdmin.listWebNotifications(req.query));
});
adminApi.post('/wechat/web-notifications', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
const result = await wechatAdmin.createWebNotification(req.body ?? {});
if (!result.ok) return res.status(400).json({ message: result.message });
res.status(201).json(result);
});
adminApi.post('/wechat/users/:userId/route/clear', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
const result = await wechatAdmin.clearRouteForUser(req.params.userId);
if (!result.ok) return res.status(404).json({ message: result.message ?? '清除失败' });
res.json(result);
});
adminApi.post('/wechat/digests/:id/cancel', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
const result = await wechatAdmin.cancelDigest(req.params.id);
if (!result.ok) return res.status(404).json({ message: '订阅不存在或已暂停' });
res.json(result);
});
adminApi.post('/wechat/digests/:id/resume', requireAdmin, async (req, res) => {
if (!wechatAdmin) return res.status(503).json({ message: '服务号管理未启用' });
const result = await wechatAdmin.resumeDigest(req.params.id);
if (!result.ok) return res.status(404).json({ message: result.message ?? '恢复失败' });
res.json(result);
});
adminApi.get('/wechat/intent-router/config', requireAdmin, async (_req, res) => {
if (!wechatIntentRouterConfigService?.getConfig) {
return res.status(503).json({ message: '微信意图路由配置未启用' });
}
const config = await wechatIntentRouterConfigService.getConfig();
return res.json({ config });
});
adminApi.get('/wechat/intent-router/runtime', requireAdmin, async (_req, res) => {
if (!wechatIntentRouterConfigService?.getRuntimeState) {
return res.status(503).json({ message: '微信意图路由配置未启用' });
}
return res.json(await wechatIntentRouterConfigService.getRuntimeState());
});
const updateWechatIntentRouterConfig = async (req, res) => {
if (!wechatIntentRouterConfigService?.updateConfig) {
return res.status(503).json({ message: '微信意图路由配置未启用' });
}
const config = await wechatIntentRouterConfigService.updateConfig(req.body ?? {}, {
updatedBy: req.currentUser.id,
});
return res.json({ config });
};
adminApi.put('/wechat/intent-router/config', requireAdmin, updateWechatIntentRouterConfig);
adminApi.patch('/wechat/intent-router/config', requireAdmin, updateWechatIntentRouterConfig);
adminApi.get('/llm-providers/catalog', requireAdmin, (_req, res) => {
if (!llmProviderService) return res.status(503).json({ message: '未启用 LLM 配置' });
res.json({ catalog: llmProviderService.catalog });
});
adminApi.get('/llm-providers/keys', requireAdmin, async (_req, res) => {
if (!llmProviderService) return res.status(503).json({ message: '未启用 LLM 配置' });
const keys = await llmProviderService.listKeys();
res.json({ keys });
});
adminApi.post('/llm-providers/keys', requireAdmin, async (req, res) => {
if (!llmProviderService) return res.status(503).json({ message: '未启用 LLM 配置' });
const result = await llmProviderService.createKey(req.body ?? {});
if (!result.ok) return res.status(400).json({ message: result.message });
res.status(201).json({ key: result.key });
});
adminApi.patch('/llm-providers/keys/:keyId', requireAdmin, async (req, res) => {
if (!llmProviderService) return res.status(503).json({ message: '未启用 LLM 配置' });
const result = await llmProviderService.updateKey(req.params.keyId, req.body ?? {});
if (!result.ok) return res.status(400).json({ message: result.message });
res.json({ key: result.key });
});
adminApi.post('/llm-providers/keys/:keyId/select', requireAdmin, async (req, res) => {
if (!llmProviderService) return res.status(503).json({ message: '未启用 LLM 配置' });
const result = await llmProviderService.selectKey(req.params.keyId);
if (!result.ok) return res.status(400).json({ message: result.message });
res.json({ key: result.key });
});
adminApi.delete('/llm-providers/keys/:keyId', requireAdmin, async (req, res) => {
if (!llmProviderService) return res.status(503).json({ message: '未启用 LLM 配置' });
const result = await llmProviderService.deleteKey(req.params.keyId);
if (!result.ok) return res.status(400).json({ message: result.message });
res.json({ ok: true });
});
adminApi.post('/llm-providers/sync', requireAdmin, async (_req, res) => {
if (!llmProviderService) return res.status(503).json({ message: '未启用 LLM 配置' });
try {
const result = await llmProviderService.syncSelectedToGoosed();
res.json(result);
} catch (err) {
res.status(500).json({
message: err instanceof Error ? err.message : '同步失败',
});
}
});
adminApi.get('/llm-providers/global', requireAdmin, async (_req, res) => {
if (!llmProviderService) return res.status(503).json({ message: '未启用 LLM 配置' });
const global = await llmProviderService.getGlobalSettings();
res.json({ global });
});
adminApi.put('/llm-providers/global', requireAdmin, async (req, res) => {
if (!llmProviderService) return res.status(503).json({ message: '未启用 LLM 配置' });
const result = await llmProviderService.setGlobalModel(req.body?.model);
if (!result.ok) return res.status(400).json({ message: result.message });
res.json(result);
});
adminApi.post('/llm-providers/test', requireAdmin, async (req, res) => {
if (!llmProviderService) return res.status(503).json({ message: '未启用 LLM 配置' });
try {
const result = await llmProviderService.testDraft(req.body ?? {});
res.json(result);
} catch (err) {
res.status(500).json({
ok: false,
message: err instanceof Error ? err.message : '联通测试失败',
});
}
});
adminApi.post('/llm-providers/keys/:keyId/test', requireAdmin, async (req, res) => {
if (!llmProviderService) return res.status(503).json({ message: '未启用 LLM 配置' });
try {
const result = await llmProviderService.testKey(req.params.keyId, req.body?.model);
res.json(result);
} catch (err) {
res.status(500).json({
ok: false,
message: err instanceof Error ? err.message : '联通测试失败',
});
}
});
adminApi.get('/plaza/pending', requireAdmin, async (_req, res) => {
if (!plazaPosts) return res.status(503).json({ message: 'Plaza 未启用' });
const posts = await plazaPosts.listPendingPosts();
res.json({ data: { posts } });
});
adminApi.post('/plaza/posts/:id/review', requireAdmin, async (req, res) => {
if (!plazaPosts) return res.status(503).json({ message: 'Plaza 未启用' });
try {
const action = String(req.body?.action ?? 'approve');
const reason = req.body?.reason ?? null;
const result =
plazaOps && req.currentUser?.id
? await plazaOps.reviewPostAsOps(req.currentUser.id, req.params.id, action, { reason })
: await plazaPosts.reviewPost(req.params.id, action, { reason });
res.json({ data: result });
} catch (error) {
const status = mapPlazaError(error);
res.status(status).json({
error: { code: error?.code ?? 'review_failed', message: error.message },
});
}
});
// ── Subscription management ───────────────────────────────────────────────
adminApi.get('/subscriptions', requireAdmin, async (req, res) => {
if (!subscriptionService) return res.status(503).json({ message: '订阅服务未启用' });
const { userId, status, page, pageSize } = req.query;
const result = await subscriptionService.listSubscriptions({ userId, status, page, pageSize });
res.json(result);
});
adminApi.get('/subscriptions/plans', requireAdmin, (_req, res) => {
res.json(
Object.entries(PLAN_CATALOG).map(([planType, def]) => ({
planType,
...def,
approxCalls: tokensToCallsApprox(def.periodTokens),
})),
);
});
adminApi.get('/users/:userId/subscription', requireAdmin, async (req, res) => {
if (!subscriptionService) return res.status(503).json({ message: '订阅服务未启用' });
const sub = await subscriptionService.getActiveSubscription(req.params.userId);
res.json({ subscription: sub });
});
adminApi.post('/users/:userId/subscription', requireAdmin, async (req, res) => {
if (!subscriptionService) return res.status(503).json({ message: '订阅服务未启用' });
const { planType, durationDays, note } = req.body ?? {};
if (!planType) return res.status(400).json({ message: '缺少 planType' });
const result = await subscriptionService.grantSubscription(
req.params.userId,
planType,
durationDays ? Number(durationDays) : undefined,
req.currentUser.id,
note,
);
if (!result.ok) return res.status(400).json({ message: result.message });
res.json(result);
});
adminApi.delete('/users/:userId/subscription', requireAdmin, async (req, res) => {
if (!subscriptionService) return res.status(503).json({ message: '订阅服务未启用' });
const result = await subscriptionService.cancelSubscription(
req.params.userId,
req.currentUser.id,
);
res.json(result);
});
adminApi.post('/subscriptions/expire-stale', requireAdmin, async (_req, res) => {
if (!subscriptionService) return res.status(503).json({ message: '订阅服务未启用' });
const count = await subscriptionService.expireStaleSubscriptions();
res.json({ expiredCount: count });
});
// ── Image generation quota ────────────────────────────────────────────────
adminApi.get('/image-quota/config', requireAdmin, async (_req, res) => {
const planCatalogService = subscriptionService?._planCatalogService;
if (!planCatalogService?.listPlans) {
return res.status(503).json({ message: '套餐目录服务未启用' });
}
const plans = await planCatalogService.listPlans();
res.json({
plans: plans.map((plan) => ({
...plan,
approxCalls: tokensToCallsApprox(plan.periodTokens),
})),
});
});
adminApi.patch('/image-quota/config/:planType', requireAdmin, async (req, res) => {
const planCatalogService = subscriptionService?._planCatalogService;
if (!planCatalogService?.upsertPlan) {
return res.status(503).json({ message: '套餐目录服务未启用' });
}
const periodImages = Number(req.body?.periodImages);
if (!Number.isFinite(periodImages) || periodImages < 0) {
return res.status(400).json({ message: 'periodImages 必须是非负整数;0 表示无限' });
}
const current = await planCatalogService.getPlan(req.params.planType);
if (!current) return res.status(404).json({ message: '套餐不存在' });
const result = await planCatalogService.upsertPlan(req.params.planType, {
...current,
periodImages: Math.floor(periodImages),
});
if (!result.ok) return res.status(400).json({ message: result.message });
res.json(result);
});
adminApi.get('/users/:userId/image-quota', requireAdmin, async (req, res) => {
if (!subscriptionService?.getImageQuota) {
return res.status(503).json({ message: '图片额度服务未启用' });
}
const quotaResult = await subscriptionService.getImageQuota(req.params.userId);
if (!quotaResult.ok) return res.status(404).json({ message: quotaResult.message });
const ledger = subscriptionService.listImageQuotaLedger
? await subscriptionService.listImageQuotaLedger({
userId: req.params.userId,
page: 1,
pageSize: 20,
})
: { entries: [] };
res.json({
subscription: quotaResult.subscription,
quota: quotaResult.quota,
ledger: ledger.entries,
});
});
adminApi.post('/users/:userId/image-quota/grant', requireAdmin, async (req, res) => {
if (!subscriptionService?.grantImageQuota) {
return res.status(503).json({ message: '图片额度服务未启用' });
}
const delta = Number(req.body?.delta);
if (!Number.isFinite(delta) || delta === 0) {
return res.status(400).json({ message: 'delta 必须是非零整数' });
}
const note = String(req.body?.note ?? '').trim();
const result = await subscriptionService.grantImageQuota(
req.params.userId,
Math.floor(delta),
{ operatorId: req.currentUser.id, note },
);
if (!result.ok) return res.status(400).json({ message: result.message });
res.json(result);
});
adminApi.get('/image-quota/ledger', requireAdmin, async (req, res) => {
if (!subscriptionService?.listImageQuotaLedger) {
return res.status(503).json({ message: '图片额度服务未启用' });
}
const result = await subscriptionService.listImageQuotaLedger({
userId: req.query.userId ? String(req.query.userId) : null,
page: req.query.page,
pageSize: req.query.pageSize,
});
res.json(result);
});
return adminApi;
}
/**
* Plaza operations console API (ops_role: reviewer < editor < ops_admin).
* @param {object} deps
* @param {import('express').RequestHandler} deps.jsonBody
* @param {object|null} deps.plazaOps
*/
export function createOpsApi({ jsonBody, plazaOps }) {
function makeRequireOps(minRole = 'reviewer') {
return async (req, res, next) => {
if (!plazaOps) return sendError(res, req, 503, 'plaza_unavailable', 'Plaza 未启用');
if (!req.currentUser) return sendError(res, req, 401, 'unauthorized', '未授权,请重新登录');
const role = await plazaOps.loadOperatorRole(req.currentUser.id);
if (!hasOpsRole(role, minRole)) {
return sendError(res, req, 403, 'OPS_PERMISSION_DENIED', '无运营权限');
}
req.opsRole = role;
return next();
};
}
const opsApi = express.Router();
opsApi.use(jsonBody);
opsApi.use(makeRequireOps('reviewer'));
opsApi.get('/categories', async (req, res) => {
try {
return sendData(res, req, { categories: await plazaOps.listCategories() });
} catch (error) {
return plazaRouteError(res, req, error);
}
});
opsApi.get('/review/queue', async (req, res) => {
try {
const queue = await plazaOps.listReviewQueue({
status: req.query.status ?? 'pending_review',
cursor: req.query.cursor ?? null,
limit: req.query.limit,
keyword: req.query.keyword ?? null,
category: req.query.category ?? null,
});
return sendData(res, req, queue);
} catch (error) {
return plazaRouteError(res, req, error);
}
});
opsApi.post('/review/posts/:id', async (req, res) => {
try {
const result = await plazaOps.reviewPostAsOps(req.currentUser.id, req.params.id, req.body?.action, {
reason: req.body?.reason ?? null,
});
return sendData(res, req, { post: result });
} catch (error) {
return plazaRouteError(res, req, error);
}
});
opsApi.post('/review/batch', makeRequireOps('editor'), async (req, res) => {
try {
const result = await plazaOps.batchReviewPosts(req.currentUser.id, req.body ?? {});
return sendData(res, req, result);
} catch (error) {
return plazaRouteError(res, req, error);
}
});
opsApi.get('/reports', async (req, res) => {
try {
return sendData(res, req, await plazaOps.listReports({ status: req.query.status ?? 'pending' }));
} catch (error) {
return plazaRouteError(res, req, error);
}
});
opsApi.post('/reports/:id/process', async (req, res) => {
try {
const result = await plazaOps.processReport(req.currentUser.id, req.params.id, req.body ?? {});
return sendData(res, req, { report: result });
} catch (error) {
return plazaRouteError(res, req, error);
}
});
opsApi.get('/featured', makeRequireOps('editor'), async (req, res) => {
try {
return sendData(res, req, await plazaOps.listFeatured());
} catch (error) {
return plazaRouteError(res, req, error);
}
});
opsApi.post('/featured', makeRequireOps('editor'), async (req, res) => {
try {
const result = await plazaOps.setFeatured(req.currentUser.id, req.body ?? {});
return sendData(res, req, { featured: result }, 201);
} catch (error) {
return plazaRouteError(res, req, error);
}
});
opsApi.delete('/featured/:id', makeRequireOps('editor'), async (req, res) => {
try {
const result = await plazaOps.removeFeatured(req.currentUser.id, req.params.id);
return sendData(res, req, { featured: result });
} catch (error) {
return plazaRouteError(res, req, error);
}
});
opsApi.get('/analytics/overview', makeRequireOps('ops_admin'), async (req, res) => {
try {
return sendData(res, req, await plazaOps.getAnalyticsOverview());
} catch (error) {
return plazaRouteError(res, req, error);
}
});
opsApi.get('/creators', async (req, res) => {
try {
return sendData(res, req, await plazaOps.listCreators({ keyword: req.query.keyword ?? null }));
} catch (error) {
return plazaRouteError(res, req, error);
}
});
opsApi.patch('/creators/:userId', async (req, res) => {
try {
const result = await plazaOps.updateCreator(req.currentUser.id, req.params.userId, req.body ?? {});
return sendData(res, req, { creator: result });
} catch (error) {
return plazaRouteError(res, req, error);
}
});
return opsApi;
}