ma7engine: ядро, шаблон панели, скаффолдер и доктор
Первый коммит существующего состояния. Ядро кладётся под git ради того, чтобы половины разъезжались в продуктовые репозитории через git subtree, а не копированием: subtree делает настоящий merge, хранит историю и умеет отдавать правки обратно. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019LeBkZLiQrvdECf5nfgiMz
This commit is contained in:
Generated
+1629
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "ma7engine-example-backend",
|
||||
"version": "0.1.0",
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"start": "tsx server.ts",
|
||||
"dev": "tsx watch server.ts",
|
||||
"typecheck": "tsc --noEmit"
|
||||
},
|
||||
"dependencies": {
|
||||
"express": "^4.19.0",
|
||||
"jsonwebtoken": "^9.0.0",
|
||||
"tsx": "^4.7.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/express": "^4.17.0",
|
||||
"@types/jsonwebtoken": "^9.0.0",
|
||||
"@types/node": "^26.3.0",
|
||||
"typescript": "^5.4.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
// Шаблон бэкенда панели — с этого начинается новый. Копируется вместе с
|
||||
// ../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}`));
|
||||
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ES2020",
|
||||
"lib": ["ES2020"],
|
||||
"module": "CommonJS",
|
||||
"moduleResolution": "node",
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noEmit": true,
|
||||
"esModuleInterop": true,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"include": ["server.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user