Files
maenekoandClaude Opus 5 18bd05bcbb ma7engine: ядро, шаблон панели, скаффолдер и доктор
Первый коммит существующего состояния. Ядро кладётся под git ради того,
чтобы половины разъезжались в продуктовые репозитории через git subtree,
а не копированием: subtree делает настоящий merge, хранит историю и умеет
отдавать правки обратно.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019LeBkZLiQrvdECf5nfgiMz
2026-08-26 20:39:37 +03:00

64 lines
3.4 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Шаблон бэкенда панели — с этого начинается новый. Копируется вместе с
// ../frontend; ядро в свою панель приезжает через `npm run update`.
//
// Всё, что здесь есть: конфиг и свои маршруты. Логин, сессия, права,
// рейт-лимит, раздача SPA и порядок их монтирования — в createPanelServer.
//
// Запуск:
// ADMIN_PASS=... ADMIN_JWT_SECRET=... npm start
//
// Панель-прокси, у которой своих учёток нет (авторизация живёт в основном
// продукте), createPanelServer не использует и собирает express сама из кусков
// ядра — см. раздел про панель-прокси в README.
import path from 'node:path';
import type { Request, Response } from 'express';
// Ядро — вендоренная копия в ./core: её кладёт `npm run update`, запущенный во
// фронте (он же predev/prebuild). Поэтому express и jsonwebtoken резолвятся из
// backend/node_modules, как в любой панели.
import { createPanelServer } from './core';
const PORT = Number(process.env.PORT) || 8080;
const app = createPanelServer({
auth: {
user: process.env.ADMIN_USER ?? 'admin',
password: process.env.ADMIN_PASS,
passwordHash: process.env.ADMIN_PASS_HASH,
secret: process.env.ADMIN_JWT_SECRET ?? '',
ttl: '12h',
revocable: true,
// claims не задан — вошедший получает permissions: ['*'], то есть
// видит все вкладки. Появятся учётки с разным доступом — размечать
// здесь.
},
// Раскладка как в панелях: <панель>/backend рядом с <панель>/frontend.
staticDir: process.env.STATIC_DIR ?? path.join(__dirname, '..', 'frontend', 'dist'),
mountRoutes(app, { auth, forwardError }) {
const { requireAuth, requirePermission, getSession } = auth;
// Открыт любому залогиненному — у вкладки в metadata.json нет permission.
app.get('/api/demo/whoami', requireAuth, (req: Request, res: Response) => {
res.json(getSession(req));
});
// Закрыт правом. Гейт на фронте (permission во вкладке) — это только
// UX; настоящая граница здесь.
app.get('/api/demo/secrets', requireAuth, requirePermission('secrets.view'),
(_req: Request, res: Response) => {
res.json({ items: ['первый', 'второй'] });
});
// Поход в вышестоящий API: его статус и текст пробрасываются клиенту,
// а не превращаются в 500.
app.get('/api/demo/upstream', requireAuth, async (_req: Request, res: Response) => {
try {
throw { response: { status: 404, data: { message: 'Не найдено наверху' } } };
} catch (e) { forwardError(res, e); }
});
},
});
app.listen(PORT, () => console.log(`demo-panel: listening on :${PORT}`));