fix: skill 路由统一挂在 /admin-api/skills,兼容 vite proxy
前台 vite 将 /api → portal(8081),/admin-api → memindadm(8085)。 将 user 路由从 /api/skills 移至 /admin-api/skills,公共 GET 不需要 auth,invoke 需 user auth,admin 操作需 admin auth。 Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
+25
-16
@@ -792,31 +792,40 @@ export function createAdminApp(services) {
|
||||
res.json(result);
|
||||
});
|
||||
|
||||
app.use('/admin-api', adminApi);
|
||||
|
||||
// ── Skill routes ──────────────────────────────────────────────────────────
|
||||
// ── Skill routes (/admin-api/skills/*) ───────────────────────────────────
|
||||
// Mounted BEFORE adminApi so public GET endpoints bypass the global auth.
|
||||
// Admin-only and user-auth endpoints use their own per-route middleware.
|
||||
if (services.skillService) {
|
||||
const { adminRouter: skillAdminRouter, userRouter: skillUserRouter } = createSkillRoutes(
|
||||
services.skillService,
|
||||
{
|
||||
requireAdmin,
|
||||
requireAuth: asyncHandler(async (req, res, next) => {
|
||||
const { router: skillRouter } = createSkillRoutes(services.skillService, {
|
||||
requireAdmin: [
|
||||
asyncHandler(async (req, res, next) => {
|
||||
await cookieReady;
|
||||
await ready;
|
||||
const me = await userAuth.getMe(userToken(req));
|
||||
if (!me) return res.status(401).json({ error: 'unauthorized' });
|
||||
req.user = me;
|
||||
req.currentUser = me;
|
||||
next();
|
||||
}),
|
||||
},
|
||||
);
|
||||
adminApi.use('/skills', skillAdminRouter);
|
||||
const skillPublicRouter = express.Router();
|
||||
wrapRouterAsync(skillPublicRouter);
|
||||
skillPublicRouter.use('/skills', skillUserRouter);
|
||||
app.use('/api', skillPublicRouter);
|
||||
requireAdmin,
|
||||
],
|
||||
requireAuth: asyncHandler(async (req, res, next) => {
|
||||
await cookieReady;
|
||||
await ready;
|
||||
const me = await userAuth.getMe(userToken(req));
|
||||
if (!me) return res.status(401).json({ error: 'unauthorized' });
|
||||
req.user = me;
|
||||
next();
|
||||
}),
|
||||
});
|
||||
const skillApiRouter = express.Router();
|
||||
wrapRouterAsync(skillApiRouter);
|
||||
skillApiRouter.use(jsonBody);
|
||||
skillApiRouter.use('/skills', skillRouter);
|
||||
app.use('/admin-api', skillApiRouter);
|
||||
}
|
||||
|
||||
app.use('/admin-api', adminApi);
|
||||
|
||||
if (services.createOpsApi) {
|
||||
const opsApi = express.Router();
|
||||
wrapRouterAsync(opsApi);
|
||||
|
||||
+60
-54
@@ -1,86 +1,61 @@
|
||||
/**
|
||||
* Skill API routes mounted at /admin-api/skills (admin)
|
||||
* and /api/skills (user-facing)
|
||||
* Skill API routes — all mounted under /admin-api/skills on memindadm (port 8085).
|
||||
*
|
||||
* Public (no auth):
|
||||
* GET /admin-api/skills/catalog — list active skills
|
||||
* GET /admin-api/skills/designs — list design brands
|
||||
* GET /admin-api/skills/designs/:brand — DESIGN.md content
|
||||
*
|
||||
* User (requires auth):
|
||||
* POST /admin-api/skills/:slug/invoke — invoke a skill
|
||||
*
|
||||
* Admin only:
|
||||
* PATCH /admin-api/skills/:slug/enabled — toggle on/off
|
||||
* POST /admin-api/skills/:slug/flags — set flag override
|
||||
*
|
||||
* Why /admin-api? The vite dev proxy routes /admin-api → memindadm (8085),
|
||||
* while /api → portal server (8081). Mounting here avoids touching the portal.
|
||||
*/
|
||||
|
||||
import { Router } from 'express';
|
||||
import { listDesigns, loadDesignMd, designExists } from './design-catalog.mjs';
|
||||
|
||||
export function createSkillRoutes(skillService, { requireAdmin, requireAuth } = {}) {
|
||||
const adminRouter = Router();
|
||||
const userRouter = Router();
|
||||
const router = Router();
|
||||
|
||||
// ── Admin routes (/admin-api/skills) ──────────────────────────────────────
|
||||
// ── Public endpoints ───────────────────────────────────────────────────────
|
||||
|
||||
// List all skills including disabled
|
||||
adminRouter.get('/', requireAdmin, async (req, res) => {
|
||||
const skills = await skillService.listSkills({ includeDisabled: true });
|
||||
res.json({ skills });
|
||||
});
|
||||
|
||||
// Toggle skill on/off
|
||||
adminRouter.patch('/:slug/enabled', requireAdmin, async (req, res) => {
|
||||
const { enabled, note } = req.body ?? {};
|
||||
if (typeof enabled !== 'boolean') {
|
||||
return res.status(400).json({ error: 'enabled (boolean) required' });
|
||||
}
|
||||
await skillService.setSkillEnabled(req.params.slug, enabled, note ?? '');
|
||||
res.json({ ok: true, slug: req.params.slug, enabled });
|
||||
});
|
||||
|
||||
// Set a flag override (user / plan / caller_type scope)
|
||||
adminRouter.post('/:slug/flags', requireAdmin, async (req, res) => {
|
||||
const { scope_type, scope_id, enabled, note } = req.body ?? {};
|
||||
if (!scope_type || typeof enabled !== 'boolean') {
|
||||
return res.status(400).json({ error: 'scope_type and enabled required' });
|
||||
}
|
||||
await skillService.setSkillFlag(req.params.slug, {
|
||||
scopeType: scope_type,
|
||||
scopeId: scope_id,
|
||||
enabled,
|
||||
note,
|
||||
});
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
// ── User-facing routes (/api/skills) ──────────────────────────────────────
|
||||
|
||||
// List active skills
|
||||
userRouter.get('/', async (req, res) => {
|
||||
router.get('/catalog', async (_req, res) => {
|
||||
const skills = await skillService.listSkills();
|
||||
res.json({ skills });
|
||||
});
|
||||
|
||||
// List available design styles (awesome-design-md catalog)
|
||||
userRouter.get('/designs', async (req, res) => {
|
||||
const designs = listDesigns();
|
||||
res.json({ designs });
|
||||
router.get('/designs', (_req, res) => {
|
||||
res.json({ designs: listDesigns() });
|
||||
});
|
||||
|
||||
// Get a single DESIGN.md content
|
||||
userRouter.get('/designs/:brand', async (req, res) => {
|
||||
router.get('/designs/:brand', (req, res) => {
|
||||
const { brand } = req.params;
|
||||
if (!designExists(brand)) return res.status(404).json({ error: 'design not found' });
|
||||
res.json({ brand, content: loadDesignMd(brand) });
|
||||
});
|
||||
|
||||
// Invoke a skill (one-shot, non-streaming)
|
||||
userRouter.post('/:slug/invoke', requireAuth, async (req, res) => {
|
||||
const { slug } = req.params;
|
||||
const input = req.body ?? {};
|
||||
// ── User endpoints (requires auth) ────────────────────────────────────────
|
||||
|
||||
router.post('/:slug/invoke', requireAuth, async (req, res) => {
|
||||
const { slug } = req.params;
|
||||
const input = { ...(req.body ?? {}) };
|
||||
|
||||
// For design-gen skill: inject DESIGN.md if caller passed design= param
|
||||
if (input.design && !input.design_md_content) {
|
||||
const md = loadDesignMd(input.design);
|
||||
if (!md) return res.status(400).json({ error: `Unknown design style: ${input.design}` });
|
||||
input.design_md_content = md;
|
||||
}
|
||||
|
||||
const callerType = req.headers['x-caller-type'] ?? 'frontend_user';
|
||||
const context = {
|
||||
userId: req.user?.id,
|
||||
planName: req.user?.plan,
|
||||
callerType,
|
||||
callerType: req.headers['x-caller-type'] ?? 'frontend_user',
|
||||
};
|
||||
|
||||
try {
|
||||
@@ -103,5 +78,36 @@ export function createSkillRoutes(skillService, { requireAdmin, requireAuth } =
|
||||
}
|
||||
});
|
||||
|
||||
return { adminRouter, userRouter };
|
||||
// ── Admin-only endpoints ───────────────────────────────────────────────────
|
||||
|
||||
// List all skills including disabled
|
||||
router.get('/', requireAdmin, async (_req, res) => {
|
||||
const skills = await skillService.listSkills({ includeDisabled: true });
|
||||
res.json({ skills });
|
||||
});
|
||||
|
||||
router.patch('/:slug/enabled', requireAdmin, async (req, res) => {
|
||||
const { enabled, note } = req.body ?? {};
|
||||
if (typeof enabled !== 'boolean') {
|
||||
return res.status(400).json({ error: 'enabled (boolean) required' });
|
||||
}
|
||||
await skillService.setSkillEnabled(req.params.slug, enabled, note ?? '');
|
||||
res.json({ ok: true, slug: req.params.slug, enabled });
|
||||
});
|
||||
|
||||
router.post('/:slug/flags', requireAdmin, async (req, res) => {
|
||||
const { scope_type, scope_id, enabled, note } = req.body ?? {};
|
||||
if (!scope_type || typeof enabled !== 'boolean') {
|
||||
return res.status(400).json({ error: 'scope_type and enabled required' });
|
||||
}
|
||||
await skillService.setSkillFlag(req.params.slug, {
|
||||
scopeType: scope_type,
|
||||
scopeId: scope_id,
|
||||
enabled,
|
||||
note,
|
||||
});
|
||||
res.json({ ok: true });
|
||||
});
|
||||
|
||||
return { router };
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user