refactor: B5 + B7 — pet DDL в migrate.js, requirePermission для admin-роутов

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) <noreply@anthropic.com>
This commit is contained in:
Maxim Dolgolyov
2026-04-16 11:46:42 +03:00
parent 26ba289019
commit 2ae06ba2f1
4 changed files with 25 additions and 22 deletions
-10
View File
@@ -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:'Таинственный лес' },
+10
View File
@@ -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)');
+6 -5
View File
@@ -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;
+9 -7
View File
@@ -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;