From 2ae06ba2f110bb980bfef70901bb3a93ff1ed1a2 Mon Sep 17 00:00:00 2001 From: Maxim Dolgolyov Date: Thu, 16 Apr 2026 11:46:42 +0300 Subject: [PATCH] =?UTF-8?q?refactor:=20B5=20+=20B7=20=E2=80=94=20pet=20DDL?= =?UTF-8?q?=20=D0=B2=20migrate.js,=20requirePermission=20=D0=B4=D0=BB?= =?UTF-8?q?=D1=8F=20admin-=D1=80=D0=BE=D1=83=D1=82=D0=BE=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit B5: убрать inline ALTER TABLE из petController.js → перенести в migrate.js (pet_name, pet_color, pet_last_petted, pet_petting_streak, pet_last_star, pet_bg, pet_bg_owned, pet_last_fed) B7: gamification/shop admin endpoints: - /admin/award, /admin/stats, /admin/user/:id → requirePermission('gamification.manage') - /shop/admin/items(GET), /admin/award-coins, /admin/stats → requirePermission('shop.manage') - Деструктивные операции (reset, create/update/delete items) — requireRole('admin') Оба пермишна существуют в PERM_DEFAULTS (default false для teacher, admin всегда проходит) Co-Authored-By: Claude Opus 4.6 (1M context) --- backend/src/controllers/petController.js | 10 ---------- backend/src/db/migrate.js | 10 ++++++++++ backend/src/routes/gamification.js | 11 ++++++----- backend/src/routes/shop.js | 16 +++++++++------- 4 files changed, 25 insertions(+), 22 deletions(-) diff --git a/backend/src/controllers/petController.js b/backend/src/controllers/petController.js index fd807b2..13ce618 100644 --- a/backend/src/controllers/petController.js +++ b/backend/src/controllers/petController.js @@ -2,16 +2,6 @@ const db = require('../db/db'); const { awardCoins } = require('./gamificationController'); -// Incremental migrations -try { db.exec("ALTER TABLE users ADD COLUMN pet_name TEXT"); } catch {} -try { db.exec("ALTER TABLE users ADD COLUMN pet_color TEXT DEFAULT 'purple'"); } catch {} -try { db.exec("ALTER TABLE users ADD COLUMN pet_last_petted TEXT"); } catch {} -try { db.exec("ALTER TABLE users ADD COLUMN pet_petting_streak INT DEFAULT 0"); } catch {} -try { db.exec("ALTER TABLE users ADD COLUMN pet_last_star TEXT"); } catch {} -try { db.exec("ALTER TABLE users ADD COLUMN pet_bg TEXT DEFAULT 'default'"); } catch {} -try { db.exec("ALTER TABLE users ADD COLUMN pet_bg_owned TEXT DEFAULT '[]'"); } catch {} -try { db.exec("ALTER TABLE users ADD COLUMN pet_last_fed TEXT"); } catch {} - const BG_SHOP = [ { id:'space', name:'Космос', price:50, desc:'Звёздный простор' }, { id:'forest', name:'Лес', price:50, desc:'Таинственный лес' }, diff --git a/backend/src/db/migrate.js b/backend/src/db/migrate.js index 01dce61..9ad506a 100644 --- a/backend/src/db/migrate.js +++ b/backend/src/db/migrate.js @@ -2937,4 +2937,14 @@ db.exec(` updated_at TEXT NOT NULL DEFAULT (datetime('now')) ) `); + +// Pet columns on users table (ALTER TABLE is idempotent via try/catch) +try { db.exec("ALTER TABLE users ADD COLUMN pet_name TEXT"); } catch {} +try { db.exec("ALTER TABLE users ADD COLUMN pet_color TEXT DEFAULT 'purple'"); } catch {} +try { db.exec("ALTER TABLE users ADD COLUMN pet_last_petted TEXT"); } catch {} +try { db.exec("ALTER TABLE users ADD COLUMN pet_petting_streak INT DEFAULT 0"); } catch {} +try { db.exec("ALTER TABLE users ADD COLUMN pet_last_star TEXT"); } catch {} +try { db.exec("ALTER TABLE users ADD COLUMN pet_bg TEXT DEFAULT 'default'"); } catch {} +try { db.exec("ALTER TABLE users ADD COLUMN pet_bg_owned TEXT DEFAULT '[]'"); } catch {} +try { db.exec("ALTER TABLE users ADD COLUMN pet_last_fed TEXT"); } catch {} db.exec('CREATE INDEX IF NOT EXISTS idx_geo_subs_student ON geometry_submissions(student_id)'); diff --git a/backend/src/routes/gamification.js b/backend/src/routes/gamification.js index 2ee33f8..6b207ac 100644 --- a/backend/src/routes/gamification.js +++ b/backend/src/routes/gamification.js @@ -31,10 +31,11 @@ router.post('/lab-activity', requirePermission('simulations.access'), labLimiter res.json({ ok: true }); }); -/* Admin routes */ -router.post('/admin/award', requireRole('admin', 'teacher'), adminAward); -router.post('/admin/reset', requireRole('admin'), adminReset); -router.get('/admin/stats', requireRole('admin', 'teacher'), adminGamStats); -router.get('/admin/user/:id', requireRole('admin', 'teacher'), adminGetUser); +/* Admin routes — award/stats/user require gamification.manage permission + (admin always passes; teachers need explicit grant from permissions UI) */ +router.post('/admin/award', requirePermission('gamification.manage'), adminAward); +router.post('/admin/reset', requireRole('admin'), adminReset); +router.get('/admin/stats', requirePermission('gamification.manage'), adminGamStats); +router.get('/admin/user/:id', requirePermission('gamification.manage'), adminGetUser); module.exports = router; diff --git a/backend/src/routes/shop.js b/backend/src/routes/shop.js index 7cf54cd..c5c8154 100644 --- a/backend/src/routes/shop.js +++ b/backend/src/routes/shop.js @@ -28,12 +28,14 @@ router.get('/coins', getCoins); router.get('/my-active', getMyActive); router.post('/activate', validate(activateSchema), activateItem); -/* Admin routes */ -router.get('/admin/items', requireRole('admin', 'teacher'), adminGetItems); -router.post('/admin/items', requireRole('admin'), validate(adminItemSchema), adminCreateItem); -router.put('/admin/items/:id', requireRole('admin'), adminUpdateItem); -router.delete('/admin/items/:id',requireRole('admin'), adminDeleteItem); -router.post('/admin/award-coins',requireRole('admin', 'teacher'), validate(awardCoinsSchema), adminAwardCoins); -router.get('/admin/stats', requireRole('admin', 'teacher'), adminShopStats); +/* Admin routes — read/award/stats require shop.manage permission + (admin always passes; teachers need explicit grant from permissions UI) + Create/update/delete items remain admin-only (shop catalogue changes) */ +router.get('/admin/items', requirePermission('shop.manage'), adminGetItems); +router.post('/admin/items', requireRole('admin'), validate(adminItemSchema), adminCreateItem); +router.put('/admin/items/:id', requireRole('admin'), adminUpdateItem); +router.delete('/admin/items/:id', requireRole('admin'), adminDeleteItem); +router.post('/admin/award-coins', requirePermission('shop.manage'), validate(awardCoinsSchema), adminAwardCoins); +router.get('/admin/stats', requirePermission('shop.manage'), adminShopStats); module.exports = router;