feat: admin panel — plans editor + credit costs editor

routes/billing.js: PATCH /api/admin/plans/:id, PATCH /api/admin/credit-costs/:operation
index.js: /api/admin/* → billing routes
DB: AI_IMAGE_* → category=legacy (скрыты из UI)
     engine settings: ENGINE_PUBLIC_URL, APP_PUBLIC_URL, AUTO_DRAFT_DEFAULT_*
This commit is contained in:
Ник (Claude)
2026-06-13 00:02:03 +03:00
parent 2b996820d7
commit ad9f054701
2 changed files with 34 additions and 0 deletions
+1
View File
@@ -105,6 +105,7 @@ app.use('/api/calendar', calendarRoutes);
app.use('/api/metrics', metricsRoutes); app.use('/api/metrics', metricsRoutes);
app.use('/api/usage', usageRoutes); app.use('/api/usage', usageRoutes);
app.use('/api/billing', require('./src/routes/billing')); app.use('/api/billing', require('./src/routes/billing'));
app.use('/api/admin', require('./src/routes/billing')); // /admin/plans, /admin/credit-costs
app.use('/api/channels', require('./src/routes/polls')); app.use('/api/channels', require('./src/routes/polls'));
app.use('/api', inboxRoutes); app.use('/api', inboxRoutes);
app.use('/api', require('./src/routes/drafts')); app.use('/api', require('./src/routes/drafts'));
+33
View File
@@ -120,3 +120,36 @@ router.post('/monthly-reset', async (req, res) => {
res.status(500).json({ error: err.message }); res.status(500).json({ error: err.message });
} }
}); });
// PATCH /api/admin/plans/:id — обновить план (только admin)
router.patch('/admin/plans/:id', async (req, res) => {
const adminId = uid(req);
if (!adminId) return res.status(401).json({ error: 'x-user-id required' });
const { rows: [admin] } = await query('SELECT is_admin FROM users WHERE id=$1', [adminId]);
if (!admin?.is_admin) return res.status(403).json({ error: 'Forbidden' });
const { price_rub, credits_month, channels_max, name } = req.body;
try {
const sets = []; const vals = [];
if (price_rub !== undefined) sets.push(`price_rub=$${vals.push(price_rub)}`);
if (credits_month!== undefined) sets.push(`credits_month=$${vals.push(credits_month)}`);
if (channels_max !== undefined) sets.push(`channels_max=$${vals.push(channels_max)}`);
if (name !== undefined) sets.push(`name=$${vals.push(name)}`);
if (!sets.length) return res.status(400).json({ error: 'nothing to update' });
vals.push(req.params.id);
const { rows: [plan] } = await query(`UPDATE plans SET ${sets.join(',')} WHERE id=$${vals.length} RETURNING *`, vals);
res.json(plan);
} catch (err) { res.status(500).json({ error: err.message }); }
});
// PATCH /api/admin/credit-costs/:operation — обновить стоимость операции
router.patch('/admin/credit-costs/:operation', async (req, res) => {
const adminId = uid(req);
if (!adminId) return res.status(401).json({ error: 'x-user-id required' });
const { rows: [admin] } = await query('SELECT is_admin FROM users WHERE id=$1', [adminId]);
if (!admin?.is_admin) return res.status(403).json({ error: 'Forbidden' });
const { credits } = req.body;
try {
await query('UPDATE credit_costs SET credits=$1 WHERE operation=$2', [credits, req.params.operation]);
res.json({ ok: true, operation: req.params.operation, credits });
} catch (err) { res.status(500).json({ error: err.message }); }
});