From ad9f05470169079d7e4bb04b2a6d68c825d37414 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9D=D0=B8=D0=BA=20=28Claude=29?= Date: Sat, 13 Jun 2026 00:02:03 +0300 Subject: [PATCH] =?UTF-8?q?feat:=20admin=20panel=20=E2=80=94=20plans=20edi?= =?UTF-8?q?tor=20+=20credit=20costs=20editor?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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_* --- index.js | 1 + src/routes/billing.js | 33 +++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/index.js b/index.js index 481a88d..715aab0 100644 --- a/index.js +++ b/index.js @@ -105,6 +105,7 @@ app.use('/api/calendar', calendarRoutes); app.use('/api/metrics', metricsRoutes); app.use('/api/usage', usageRoutes); 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', inboxRoutes); app.use('/api', require('./src/routes/drafts')); diff --git a/src/routes/billing.js b/src/routes/billing.js index 6263490..389a404 100644 --- a/src/routes/billing.js +++ b/src/routes/billing.js @@ -120,3 +120,36 @@ router.post('/monthly-reset', async (req, res) => { 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 }); } +});