// Шаблон бэкенда панели — с этого начинается новый. Копируется вместе с // ../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}`));