mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
chore: relocate backend into apps/api ahead of monorepo merge
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
const router = require('express').Router();
|
||||
const ctrl = require('../../controllers/admin/achievements.controller');
|
||||
|
||||
// Auth + requireAdmin applied by admin.routes.js
|
||||
|
||||
router.get ('/', ctrl.getAchievements);
|
||||
router.post ('/', ctrl.createAchievement);
|
||||
router.get ('/:id', ctrl.getAchievement);
|
||||
router.put ('/:id', ctrl.updateAchievement);
|
||||
router.delete('/:id', ctrl.deleteAchievement);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,79 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: admin.routes.js
|
||||
* Type of Program: Router
|
||||
* Description: Protected routes accessible by admin only — full control.
|
||||
*
|
||||
* Route Map:
|
||||
* Dashboard : GET /api/admin/dashboard
|
||||
* Users : /api/admin/users/*
|
||||
* Groups : /api/admin/groups/*
|
||||
* Assets : /api/admin/assets/*
|
||||
*
|
||||
* Guards: authenticate → requireAdmin() → adminLimiter
|
||||
*
|
||||
* Author: rgrgogu and Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Oct. 6, 2025
|
||||
* Date Modified: May 5, 2026
|
||||
* Latest: May 19, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const { authenticate } = require('../../middleware/auth.middleware');
|
||||
const { requireAdmin } = require('../../middleware/rbac.middleware');
|
||||
const { adminLimiter } = require('../../middleware/rateLimiter.middleware');
|
||||
|
||||
const dashboardRoutes = require('./dashboard.routes');
|
||||
const profileRoutes = require('./profile.routes');
|
||||
const usersRoutes = require('./users.routes');
|
||||
const groupsRoutes = require('./groups.routes');
|
||||
const assetsRoutes = require('./assets.routes');
|
||||
const coursesRoutes = require('./courses.routes');
|
||||
const unitsRoutes = require('./units.routes');
|
||||
const lessonsRoutes = require('./lessons.routes');
|
||||
const taskRoutes = require('./task.routes');
|
||||
const tiersRoutes = require('./tiers.routes');
|
||||
const tierPoliciesRoutes = require('./tier_policies.routes');
|
||||
const tierCategoriesRoutes = require('./tier_categories.routes');
|
||||
const categoriesRoutes = require('./categories.routes');
|
||||
const productsRoutes = require('./products.routes');
|
||||
const advertisementRoutes = require('./advertisements.routes');
|
||||
const notificationRoutes = require('./notifications.routes');
|
||||
const notificationBroadcastRoutes = require('./notificationBroadcasts.routes');
|
||||
const notificationSettingsRoutes = require('./notificationSettings.routes');
|
||||
const mediaRoutes = require('./media.routes');
|
||||
const achievementsRoutes = require('./achievements.routes');
|
||||
const activityCtrl = require('../../controllers/admin/user_activity.controller');
|
||||
|
||||
// ── Guards — applied to ALL admin routes ──────────────────────────────────────
|
||||
router.use(authenticate, requireAdmin(), adminLimiter);
|
||||
|
||||
// ── Mount ─────────────────────────────────────────────────────────────────────
|
||||
router.use('/dashboard', dashboardRoutes);
|
||||
router.use('/profile', profileRoutes);
|
||||
router.use('/users', usersRoutes);
|
||||
router.use('/groups', groupsRoutes);
|
||||
router.use('/assets', assetsRoutes);
|
||||
router.use('/courses', coursesRoutes);
|
||||
router.use('/units', unitsRoutes); // standalone Unit library (junction revamp)
|
||||
router.use('/lessons', lessonsRoutes); // standalone Lesson library (junction revamp)
|
||||
router.use('/task-lists', taskRoutes);
|
||||
router.use('/tiers/categories', tierCategoriesRoutes);
|
||||
router.use('/tiers', tiersRoutes);
|
||||
router.use('/tier-policies', tierPoliciesRoutes);
|
||||
router.use('/categories', categoriesRoutes);
|
||||
router.use('/products', productsRoutes);
|
||||
router.use('/advertisements', advertisementRoutes);
|
||||
router.use('/notifications', notificationRoutes);
|
||||
router.use('/notification-broadcasts', notificationBroadcastRoutes);
|
||||
router.use('/notification-settings', notificationSettingsRoutes);
|
||||
// Announcements (alias routes for notification broadcasts/settings)
|
||||
router.use('/announcements', notificationBroadcastRoutes);
|
||||
router.use('/announcement-settings', notificationSettingsRoutes);
|
||||
router.use('/media', mediaRoutes);
|
||||
router.use('/achievements', achievementsRoutes);
|
||||
|
||||
// ── Activity feed — global ────────────────────────────────────────────────────
|
||||
router.get('/activity', activityCtrl.getActivity);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,25 @@
|
||||
// routes/admin/advertisements.routes.js
|
||||
const router = require('express').Router();
|
||||
const controller = require('../../controllers/admin/advertisements.controller');
|
||||
const { sensitiveOpsLimiter } = require('../../middleware/rateLimiter.middleware');
|
||||
|
||||
// ─── Static routes first ──────────────────────────────────────────────────────
|
||||
router.get('/archived', controller.getArchivedAdvertisements);
|
||||
router.get('/field-values', controller.getAdvertisementFieldValues);
|
||||
router.delete('/bulk', sensitiveOpsLimiter, controller.archiveAdvertisements);
|
||||
router.patch('/bulk-restore', controller.restoreAdvertisements);
|
||||
router.delete('/bulk/permanent', sensitiveOpsLimiter, controller.permanentlyDeleteAdvertisements);
|
||||
|
||||
// ─── Collection ───────────────────────────────────────────────────────────────
|
||||
router.get('/', controller.getAdvertisements);
|
||||
router.post('/', controller.createAdvertisement);
|
||||
|
||||
// ─── Dynamic routes last ──────────────────────────────────────────────────────
|
||||
router.get('/:advertisementId', controller.getAdvertisement);
|
||||
router.patch('/:advertisementId', sensitiveOpsLimiter, controller.updateAdvertisement);
|
||||
router.patch('/:advertisementId/reorder', sensitiveOpsLimiter, controller.reorderAdvertisement);
|
||||
router.patch('/:advertisementId/restore', sensitiveOpsLimiter, controller.restoreAdvertisement);
|
||||
router.delete('/:advertisementId', sensitiveOpsLimiter, controller.archiveAdvertisement);
|
||||
router.delete('/:advertisementId/permanent', sensitiveOpsLimiter, controller.permanentlyDeleteAdvertisement);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,36 @@
|
||||
// routes/admin/assets.routes.js
|
||||
|
||||
const router = require('express').Router();
|
||||
const controller = require('../../controllers/admin/assets.controller');
|
||||
const { sensitiveOpsLimiter } = require('../../middleware/rateLimiter.middleware');
|
||||
|
||||
// ─── Static routes first ──────────────────────────────────────────────────────
|
||||
router.get('/archived', controller.getArchivedAssets);
|
||||
router.get('/field-values', controller.getAssetFieldValues);
|
||||
router.delete('/bulk', sensitiveOpsLimiter, controller.archiveAssets);
|
||||
router.delete('/bulk/permanent', sensitiveOpsLimiter, controller.permanentlyDeleteAssets);
|
||||
router.patch('/bulk-restore', controller.restoreAssets);
|
||||
|
||||
// ─── Collection ───────────────────────────────────────────────────────────────
|
||||
router.get('/', controller.getAssets);
|
||||
// Presigned-upload flow: the browser PUTs file bytes straight to storage, this
|
||||
// backend never buffers them (see assets.controller.js). presign mints the
|
||||
// upload URL(s); the POST / body that follows is plain JSON (no multer here).
|
||||
router.post('/presign', controller.presignAssetUpload);
|
||||
// Above presign's single-PUT ceiling only (see s3.service.js's
|
||||
// MULTIPART_THRESHOLD) — the browser PUTs every part directly, then calls
|
||||
// complete/abort here. Plain JSON, no file bytes.
|
||||
router.post('/complete-multipart', controller.completeMultipartAssetUpload);
|
||||
router.post('/abort-multipart', controller.abortMultipartAssetUpload);
|
||||
router.post('/', controller.uploadAsset);
|
||||
|
||||
// ─── Dynamic routes last ──────────────────────────────────────────────────────
|
||||
router.get('/:assetId', controller.getAsset);
|
||||
// Replace flow also goes through presign + direct PUT (browser -> storage) —
|
||||
// this body is plain JSON (storage_key etc.), same as POST /. No multer here.
|
||||
router.patch('/:assetId', sensitiveOpsLimiter, controller.updateAsset);
|
||||
router.patch('/:assetId/restore', sensitiveOpsLimiter, controller.restoreAsset);
|
||||
router.delete('/:assetId', sensitiveOpsLimiter, controller.archiveAsset);
|
||||
router.delete('/:assetId/permanent', sensitiveOpsLimiter, controller.permanentlyDeleteAsset);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,17 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const ctrl = require('../../controllers/admin/categories.controller');
|
||||
|
||||
router.get ('/', ctrl.getCategories);
|
||||
router.post ('/', ctrl.createCategory);
|
||||
router.delete('/bulk', ctrl.bulkArchiveCategories);
|
||||
router.post ('/bulk-restore', ctrl.bulkRestoreCategories);
|
||||
router.delete('/bulk/permanent', ctrl.bulkPermanentlyDeleteCategories);
|
||||
router.get ('/:id/permanent-delete-impact', ctrl.getCategoryPermanentDeleteImpact);
|
||||
router.get ('/:id', ctrl.getCategory);
|
||||
router.put ('/:id', ctrl.updateCategory);
|
||||
router.delete('/:id', ctrl.archiveCategory);
|
||||
router.post ('/:id/restore', ctrl.restoreCategory);
|
||||
router.delete('/:id/permanent', ctrl.permanentlyDeleteCategory);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,203 @@
|
||||
const router = require("express").Router();
|
||||
const ctrl = require("../../controllers/admin/courses.controller");
|
||||
const progressCtrl = require("../../controllers/admin/course_reading_progress.controller");
|
||||
const requirementsCtrl = require("../../controllers/admin/completion_requirements.controller");
|
||||
|
||||
// Guards are applied by admin.routes.js (authenticate → requireAdmin → adminLimiter)
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// COURSES
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
router.get("/", ctrl.getCourses);
|
||||
router.post("/", ctrl.createCourse);
|
||||
router.post("/full", ctrl.createCourseFull); // one-shot Add Course wizard submit (course + roadmap + rewards)
|
||||
|
||||
// ── static segments first ────────────────────────────────────────────────────
|
||||
router.get("/field-values", ctrl.getCourseFieldValues);
|
||||
router.delete("/bulk", ctrl.bulkArchiveCourses);
|
||||
router.delete("/bulk/permanent", ctrl.bulkPermanentlyDeleteCourses);
|
||||
router.get("/archives", ctrl.getArchivedCourses);
|
||||
router.patch("/restore/bulk", ctrl.bulkRestoreCourses);
|
||||
router.get("/flat", ctrl.getCoursesFlat);
|
||||
router.get("/by-subscription", ctrl.getCoursesBySubscription);
|
||||
router.get("/units-flat", ctrl.getUnitsFlat);
|
||||
router.get("/lessons-flat", ctrl.getLessonsFlat);
|
||||
router.get("/uuid/:uuid/structure-counts", ctrl.getCourseStructureCounts);
|
||||
router.put("/order", ctrl.reorderCourses); // persist top-level course catalog ordering { course_ids }
|
||||
|
||||
// ── then :courseId ────────────────────────────────────────────────────────────
|
||||
router.get("/archives/:courseId", ctrl.getArchivedCourse);
|
||||
router.patch("/:courseId/restore", ctrl.restoreCourse);
|
||||
router.get("/:courseId/archive-impact", ctrl.getCourseArchiveImpact);
|
||||
router.get("/:courseId/permanent-delete-impact", ctrl.getCoursePermanentDeleteImpact);
|
||||
router.get("/:courseId", ctrl.getCourse);
|
||||
router.put("/:courseId", ctrl.updateCourse);
|
||||
router.delete("/:courseId", ctrl.archiveCourse);
|
||||
router.delete("/:courseId/permanent", ctrl.permanentlyDeleteCourse);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// COURSE INSTRUCTORS
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
router.get("/:courseId/instructors", ctrl.getInstructors);
|
||||
router.put("/:courseId/instructors", ctrl.syncInstructors);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// COURSE ACHIEVEMENTS
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
router.get("/:courseId/achievements", ctrl.getCourseAchievements);
|
||||
router.put("/:courseId/achievements", ctrl.syncCourseAchievements);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// PREREQUISITES
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
router.get("/:courseId/prerequisites", ctrl.getPrerequisites);
|
||||
router.put("/:courseId/prerequisites", ctrl.syncPrerequisites);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// COMPLETION REQUIREMENTS
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
router.get("/:courseId/requirements", requirementsCtrl.getCourseRequirements);
|
||||
router.put("/:courseId/requirements", requirementsCtrl.syncCourseRequirements);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// COURSE ASSESSMENT
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
router.get("/:courseId/assessment", ctrl.getAssessment);
|
||||
router.post("/:courseId/assessment", ctrl.createAssessment);
|
||||
|
||||
// ── static segments first ────────────────────────────────────────────────────
|
||||
router.get("/:courseId/assessment/archives", ctrl.getArchivedAssessment);
|
||||
|
||||
// ── then :assessmentId ────────────────────────────────────────────────────────
|
||||
router.patch("/:courseId/assessment/:assessmentId", ctrl.updateAssessment);
|
||||
router.delete("/:courseId/assessment/:assessmentId", ctrl.deleteAssessment);
|
||||
router.patch("/:courseId/assessment/:assessmentId/restore", ctrl.restoreAssessment);
|
||||
|
||||
// ── Assessment Questions ──────────────────────────────────────────────────────
|
||||
router.get("/:courseId/assessment/:assessmentId/questions", ctrl.getQuestions);
|
||||
router.post("/:courseId/assessment/:assessmentId/questions", ctrl.createQuestion);
|
||||
|
||||
// static before :questionId
|
||||
router.delete("/:courseId/assessment/:assessmentId/questions/bulk", ctrl.bulkArchiveQuestions);
|
||||
router.patch("/:courseId/assessment/:assessmentId/questions/restore/bulk", ctrl.bulkRestoreQuestions);
|
||||
router.put("/:courseId/assessment/:assessmentId/questions/bulk-sync", ctrl.bulkSyncQuestions);
|
||||
router.get("/:courseId/assessment/:assessmentId/questions/archives/:questionId", ctrl.getArchivedQuestion);
|
||||
|
||||
router.patch("/:courseId/assessment/:assessmentId/questions/:questionId", ctrl.updateQuestion);
|
||||
router.delete("/:courseId/assessment/:assessmentId/questions/:questionId", ctrl.deleteQuestion);
|
||||
router.patch("/:courseId/assessment/:assessmentId/questions/:questionId/restore", ctrl.restoreQuestion);
|
||||
|
||||
// ── Assessment Completions & Sessions ─────────────────────────────────────────
|
||||
router.get("/:courseId/assessment/:assessmentId/completions", ctrl.getAssessmentCompletions);
|
||||
router.get("/:courseId/assessment/:assessmentId/sessions", ctrl.getAssessmentSessions);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// UNITS
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
router.get("/:courseId/field-values", ctrl.getUnitFieldValues);
|
||||
router.get("/:courseId/units", ctrl.getUnits);
|
||||
router.post("/:courseId/units", ctrl.createUnit); // create new unit + attach, or attach existing via body { unit_id }
|
||||
|
||||
// static before :unitId
|
||||
router.post("/:courseId/units/attach", ctrl.attachUnits); // bulk-attach existing library units { unit_ids }
|
||||
router.put("/:courseId/units/order", ctrl.reorderUnits); // persist course-level unit ordering { unit_ids }
|
||||
router.delete("/:courseId/units/bulk", ctrl.bulkArchiveUnits);
|
||||
router.delete("/:courseId/units/bulk/permanent", ctrl.bulkPermanentlyDeleteUnits);
|
||||
router.get("/:courseId/units/archives", ctrl.getArchivedUnits);
|
||||
router.patch("/:courseId/units/restore/bulk", ctrl.bulkRestoreUnits);
|
||||
|
||||
router.get("/:courseId/units/archives/:unitId", ctrl.getArchivedUnit);
|
||||
router.patch("/:courseId/units/:unitId/restore", ctrl.restoreUnit);
|
||||
router.get("/:courseId/units/:unitId/archive-impact", ctrl.getUnitArchiveImpact);
|
||||
router.get("/:courseId/units/:unitId/permanent-delete-impact", ctrl.getUnitPermanentDeleteImpact);
|
||||
router.get("/:courseId/units/:unitId", ctrl.getUnit);
|
||||
router.put("/:courseId/units/:unitId", ctrl.updateUnit);
|
||||
router.delete("/:courseId/units/:unitId", ctrl.archiveUnit);
|
||||
router.delete("/:courseId/units/:unitId/permanent", ctrl.permanentlyDeleteUnit);
|
||||
|
||||
router.get("/:courseId/units/:unitId/requirements", requirementsCtrl.getUnitRequirements);
|
||||
router.put("/:courseId/units/:unitId/requirements", requirementsCtrl.syncUnitRequirements);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// UNIT QUIZ
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
router.get("/:courseId/units/:unitId/quiz", ctrl.getQuiz);
|
||||
router.post("/:courseId/units/:unitId/quiz", ctrl.createQuiz);
|
||||
|
||||
// static before :quizId
|
||||
router.get("/:courseId/units/:unitId/quiz/archives", ctrl.getArchivedQuiz);
|
||||
|
||||
router.patch("/:courseId/units/:unitId/quiz/:quizId", ctrl.updateQuiz);
|
||||
router.delete("/:courseId/units/:unitId/quiz/:quizId", ctrl.deleteQuiz);
|
||||
router.patch("/:courseId/units/:unitId/quiz/:quizId/restore", ctrl.restoreQuiz);
|
||||
|
||||
// ── Quiz Questions ────────────────────────────────────────────────────────────
|
||||
router.get("/:courseId/units/:unitId/quiz/:quizId/questions", ctrl.getQuestions);
|
||||
router.post("/:courseId/units/:unitId/quiz/:quizId/questions", ctrl.createQuestion);
|
||||
|
||||
// static before :questionId
|
||||
router.delete("/:courseId/units/:unitId/quiz/:quizId/questions/bulk", ctrl.bulkArchiveQuestions);
|
||||
router.patch("/:courseId/units/:unitId/quiz/:quizId/questions/restore/bulk", ctrl.bulkRestoreQuestions);
|
||||
router.put("/:courseId/units/:unitId/quiz/:quizId/questions/bulk-sync", ctrl.bulkSyncQuestions);
|
||||
router.get("/:courseId/units/:unitId/quiz/:quizId/questions/archives/:questionId", ctrl.getArchivedQuestion);
|
||||
|
||||
router.patch("/:courseId/units/:unitId/quiz/:quizId/questions/:questionId", ctrl.updateQuestion);
|
||||
router.delete("/:courseId/units/:unitId/quiz/:quizId/questions/:questionId", ctrl.deleteQuestion);
|
||||
router.patch("/:courseId/units/:unitId/quiz/:quizId/questions/:questionId/restore", ctrl.restoreQuestion);
|
||||
|
||||
// ── Quiz Completions ──────────────────────────────────────────────────────────
|
||||
router.get("/:courseId/units/:unitId/quiz/:quizId/completions", ctrl.getQuizCompletions);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// LESSONS
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
router.get("/:courseId/units/:unitId/field-values", ctrl.getLessonFieldValues);
|
||||
router.get("/:courseId/units/:unitId/lessons", ctrl.getLessons);
|
||||
router.post("/:courseId/units/:unitId/lessons", ctrl.createLesson); // create new lesson + attach, or attach existing via body { lesson_id }
|
||||
|
||||
// static before :lessonId
|
||||
router.put("/:courseId/units/:unitId/lessons/order", ctrl.reorderLessons); // persist unit-level lesson ordering { lesson_ids }
|
||||
router.delete("/:courseId/units/:unitId/lessons/bulk", ctrl.bulkArchiveLessons);
|
||||
router.delete("/:courseId/units/:unitId/lessons/bulk/permanent", ctrl.bulkPermanentlyDeleteLessons);
|
||||
router.get("/:courseId/units/:unitId/lessons/archives", ctrl.getArchivedLessons);
|
||||
router.patch("/:courseId/units/:unitId/lessons/restore/bulk", ctrl.bulkRestoreLessons);
|
||||
|
||||
router.get("/:courseId/units/:unitId/lessons/archives/:lessonId", ctrl.getArchivedLesson);
|
||||
router.patch("/:courseId/units/:unitId/lessons/:lessonId/restore", ctrl.restoreLesson);
|
||||
router.get("/:courseId/units/:unitId/lessons/:lessonId", ctrl.getLesson);
|
||||
router.put("/:courseId/units/:unitId/lessons/:lessonId", ctrl.updateLesson);
|
||||
router.delete("/:courseId/units/:unitId/lessons/:lessonId", ctrl.archiveLesson);
|
||||
router.delete("/:courseId/units/:unitId/lessons/:lessonId/permanent", ctrl.permanentlyDeleteLesson);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// LESSON PAGE
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
router.get("/:courseId/units/:unitId/lessons/:lessonId/page", ctrl.getLessonPage);
|
||||
router.put("/:courseId/units/:unitId/lessons/:lessonId/page", ctrl.upsertLessonPage);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// LESSON COMPLETION REQUIREMENTS
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
router.get("/:courseId/units/:unitId/lessons/:lessonId/requirements", requirementsCtrl.getLessonRequirements);
|
||||
router.put("/:courseId/units/:unitId/lessons/:lessonId/requirements", requirementsCtrl.syncLessonRequirements);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// COURSE READING PROGRESS
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
// static segment before :userId to avoid param collision
|
||||
router.get("/:courseId/reading-progress/users/:userId", progressCtrl.getUserReadingProgress);
|
||||
router.get("/:courseId/reading-progress", progressCtrl.getCourseReadingProgress);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,9 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const { getUsersDashboard, getGroupsDashboard } = require('../../controllers/admin/dashboard.controller');
|
||||
|
||||
router.get('/users', getUsersDashboard);
|
||||
router.get('/groups', getGroupsDashboard);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,25 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const groupsCtrl = require('../../controllers/admin/user_groups.controller');
|
||||
const { sensitiveOpsLimiter } = require('../../middleware/rateLimiter.middleware');
|
||||
|
||||
// ── Groups ────────────────────────────────────────────────────────────────────
|
||||
router.get('/field-values', groupsCtrl.getGroupFieldValues);
|
||||
router.post('/bulk/restore', sensitiveOpsLimiter, groupsCtrl.bulkRestoreGroups);
|
||||
router.delete('/bulk', sensitiveOpsLimiter, groupsCtrl.bulkDeactivateGroups);
|
||||
router.delete('/bulk/permanent', sensitiveOpsLimiter, groupsCtrl.bulkPermanentlyDeleteGroups);
|
||||
router.get('/archived', groupsCtrl.getArchivedGroups);
|
||||
router.get('/', groupsCtrl.getGroups);
|
||||
router.get('/:gid', groupsCtrl.getGroup);
|
||||
router.post('/', sensitiveOpsLimiter, groupsCtrl.createGroup);
|
||||
router.put('/:gid', sensitiveOpsLimiter, groupsCtrl.updateGroup);
|
||||
router.patch('/:gid/deactivate', sensitiveOpsLimiter, groupsCtrl.deactivateGroup);
|
||||
router.patch('/:gid/restore', sensitiveOpsLimiter, groupsCtrl.restoreGroup);
|
||||
router.delete('/:gid/permanent', sensitiveOpsLimiter, groupsCtrl.permanentlyDeleteGroup);
|
||||
router.get('/:gid/users', groupsCtrl.getUsersInGroup);
|
||||
router.get('/:gid/users/add', groupsCtrl.getUsersNotInGroup);
|
||||
router.post('/:gid/users', sensitiveOpsLimiter, groupsCtrl.addUserToGroup);
|
||||
router.delete('/:gid/users', sensitiveOpsLimiter, groupsCtrl.removeUserFromGroup);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,45 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: lessons.routes.js (admin)
|
||||
* Type of Program: Router
|
||||
* Description: Standalone Lesson library — Lessons run independently of Units.
|
||||
* Unit membership (attach/detach/reorder) lives under
|
||||
* /admin/units/:unitId/lessons.
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jul. 7, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
const router = require("express").Router();
|
||||
const ctrl = require("../../controllers/admin/lessons.controller");
|
||||
const requirementsCtrl = require("../../controllers/admin/completion_requirements.controller");
|
||||
|
||||
// Guards are applied by admin.routes.js (authenticate → requireAdmin → adminLimiter)
|
||||
|
||||
// ── static segments first ─────────────────────────────────────────────────────
|
||||
router.get("/", ctrl.getLessons);
|
||||
router.post("/", ctrl.createLesson);
|
||||
router.get("/flat", ctrl.getLessonsFlat);
|
||||
router.get("/by-subscription", ctrl.getLessonsBySubscription);
|
||||
router.get("/field-values", ctrl.getLessonFieldValues);
|
||||
router.delete("/bulk", ctrl.bulkArchiveLessons);
|
||||
router.delete("/bulk/permanent", ctrl.bulkPermanentlyDeleteLessons);
|
||||
router.get("/archives", ctrl.getArchivedLessons);
|
||||
router.patch("/restore/bulk", ctrl.bulkRestoreLessons);
|
||||
router.get("/archives/:lessonId", ctrl.getArchivedLesson);
|
||||
|
||||
// ── then :lessonId ────────────────────────────────────────────────────────────
|
||||
router.patch("/:lessonId/restore", ctrl.restoreLesson);
|
||||
router.get("/:lessonId/permanent-delete-impact", ctrl.getLessonPermanentDeleteImpact);
|
||||
router.get("/:lessonId", ctrl.getLesson);
|
||||
router.put("/:lessonId", ctrl.updateLesson);
|
||||
router.delete("/:lessonId", ctrl.archiveLesson);
|
||||
router.delete("/:lessonId/permanent", ctrl.permanentlyDeleteLesson);
|
||||
|
||||
// ── Lesson page (block content) ───────────────────────────────────────────────
|
||||
router.get("/:lessonId/page", ctrl.getLessonPage);
|
||||
router.put("/:lessonId/page", ctrl.upsertLessonPage);
|
||||
|
||||
// ── Completion requirements (shared with the nested courses.routes.js mount) ──
|
||||
router.get("/:lessonId/requirements", requirementsCtrl.getLessonRequirements);
|
||||
router.put("/:lessonId/requirements", requirementsCtrl.syncLessonRequirements);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,16 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: media.routes.js (admin)
|
||||
* Type of Program: Routes
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jun. 22, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
"use strict";
|
||||
|
||||
const router = require("express").Router();
|
||||
const ctrl = require("../../controllers/admin/media.controller");
|
||||
|
||||
// Auth + requireAdmin are already applied by admin.routes.js before reaching here.
|
||||
router.post("/token", ctrl.issueToken);
|
||||
router.post("/tokens", ctrl.issueTokensBatch);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,24 @@
|
||||
// routes/admin/notificationBroadcasts.routes.js
|
||||
const router = require('express').Router();
|
||||
const controller = require('../../controllers/admin/notificationBroadcasts.controller');
|
||||
const { sensitiveOpsLimiter } = require('../../middleware/rateLimiter.middleware');
|
||||
|
||||
// ─── Static routes first ──────────────────────────────────────────────────────
|
||||
router.get('/archived', controller.getArchivedBroadcasts);
|
||||
router.delete('/bulk', sensitiveOpsLimiter, controller.archiveBroadcasts);
|
||||
router.patch('/bulk-restore', controller.restoreBroadcasts);
|
||||
router.delete('/bulk/permanent', sensitiveOpsLimiter, controller.permanentlyDeleteBroadcasts);
|
||||
|
||||
// ─── Collection ───────────────────────────────────────────────────────────────
|
||||
router.get('/', controller.getBroadcasts);
|
||||
router.post('/', controller.createBroadcast);
|
||||
|
||||
// ─── Dynamic routes last ──────────────────────────────────────────────────────
|
||||
router.get('/:broadcastId', controller.getBroadcast);
|
||||
router.patch('/:broadcastId', sensitiveOpsLimiter, controller.updateBroadcast);
|
||||
router.patch('/:broadcastId/send', sensitiveOpsLimiter, controller.sendBroadcast);
|
||||
router.patch('/:broadcastId/restore', sensitiveOpsLimiter, controller.restoreBroadcast);
|
||||
router.delete('/:broadcastId', sensitiveOpsLimiter, controller.archiveBroadcast);
|
||||
router.delete('/:broadcastId/permanent', sensitiveOpsLimiter, controller.permanentlyDeleteBroadcast);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,8 @@
|
||||
// routes/admin/notificationSettings.routes.js
|
||||
const router = require('express').Router();
|
||||
const controller = require('../../controllers/admin/notificationSettings.controller');
|
||||
|
||||
router.get('/', controller.getSettings);
|
||||
router.patch('/:jobName', controller.updateSetting);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,26 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name : notifications.routes.js
|
||||
* Type : Router (Admin)
|
||||
* Description : Admin notification endpoints.
|
||||
*
|
||||
* GET /api/admin/notifications — paginated list
|
||||
* GET /api/admin/notifications/unseen — unseen count
|
||||
* PATCH /api/admin/notifications/seen-all — mark all seen
|
||||
* PATCH /api/admin/notifications/:id/seen — mark one seen
|
||||
*
|
||||
* Guards: inherited from admin.routes.js (authenticate → requireAdmin → adminLimiter)
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jun. 19, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { list, unseenCount, stickyAnnouncement, markSeen, markAllSeen } = require('../../controllers/admin/notification.controller');
|
||||
|
||||
router.get('/', list);
|
||||
router.get('/unseen', unseenCount);
|
||||
router.get('/sticky', stickyAnnouncement);
|
||||
router.patch('/seen-all', markAllSeen);
|
||||
router.patch('/:id/seen', markSeen);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,14 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const ctrl = require('../../controllers/admin/products.controller');
|
||||
|
||||
// Product per course
|
||||
router.get ('/courses/:courseId/product', ctrl.getCourseProduct);
|
||||
router.put ('/courses/:courseId/product', ctrl.upsertCourseProduct);
|
||||
router.delete('/courses/:courseId/product', ctrl.removeCourseProduct);
|
||||
|
||||
// Categories per course
|
||||
router.get ('/courses/:courseId/categories', ctrl.getCourseCategories);
|
||||
router.post ('/courses/:courseId/categories', ctrl.syncCourseCategories);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,14 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const ctrl = require('../../controllers/admin/profile.controller');
|
||||
const { validate } = require('../../middleware/validate.middleware');
|
||||
const { updateProfileValidator } = require('../../validators/profile.validator');
|
||||
const { handleAvatarUpload } = require('../../middleware/avatar_upload.middleware');
|
||||
|
||||
router.get('/', ctrl.getProfile);
|
||||
router.put('/', ...updateProfileValidator, validate, ctrl.updateProfile);
|
||||
router.post('/avatar', handleAvatarUpload, ctrl.uploadAvatar);
|
||||
router.delete('/avatar', ctrl.deleteAvatar);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,65 @@
|
||||
const router = require("express").Router();
|
||||
const controller = require("../../controllers/admin/task.controller");
|
||||
const completionController = require("../../controllers/admin/task_completion.controller");
|
||||
const { sensitiveOpsLimiter } = require("../../middleware/rateLimiter.middleware");
|
||||
|
||||
// NOTE on rate limiting: every route below already sits behind adminLimiter
|
||||
// (200 req/15min per admin user — applied blanket-wide in admin.routes.js).
|
||||
// sensitiveOpsLimiter (10 req/hour, ALSO keyed per admin user and shared
|
||||
// across every route it's attached to) is reserved for genuinely irreversible
|
||||
// actions — permanent delete — so routine CRUD doesn't compete with those for
|
||||
// the same tight budget.
|
||||
|
||||
// ─── Task Lists ───────────────────────────────────────────────────────────────
|
||||
router.get("/", controller.getTaskLists);
|
||||
router.get("/archived", controller.getArchivedTaskLists);
|
||||
router.get("/field-values", controller.getTaskListFieldValues);
|
||||
router.post("/", controller.createTaskList);
|
||||
router.post("/bulk-archive", controller.bulkArchiveTaskLists);
|
||||
router.post("/bulk-restore", controller.bulkRestoreTaskLists);
|
||||
router.post("/bulk-delete", sensitiveOpsLimiter, controller.bulkPermanentlyDeleteTaskLists);
|
||||
|
||||
// ─── Task List (single) ───────────────────────────────────────────────────────
|
||||
router.get("/:taskListId", controller.getTaskList);
|
||||
router.get("/:taskListId/permanent-delete-impact", controller.getTaskListPermanentDeleteImpact);
|
||||
router.patch("/:taskListId", controller.updateTaskList);
|
||||
router.delete("/:taskListId", controller.archiveTaskList);
|
||||
router.delete("/:taskListId/permanent", sensitiveOpsLimiter, controller.permanentlyDeleteTaskList);
|
||||
router.patch("/:taskListId/restore", controller.restoreTaskList);
|
||||
|
||||
// ─── Task List Groups ─────────────────────────────────────────────────────────
|
||||
router.get("/:taskListId/groups", controller.getTaskListGroups);
|
||||
router.put("/:taskListId/groups", controller.syncGroups);
|
||||
router.post("/:taskListId/groups/assign", controller.assignGroups);
|
||||
router.post("/:taskListId/groups/unassign", controller.unassignGroups);
|
||||
|
||||
// ─── Tasks (nested under task-list) ──────────────────────────────────────────
|
||||
router.get("/:taskListId/tasks", controller.getTasks);
|
||||
router.get("/:taskListId/tasks/archived", controller.getArchivedTasks);
|
||||
router.get("/:taskListId/tasks/field-values", controller.getTaskFieldValues);
|
||||
router.get("/:taskListId/tasks/requirement-completion-check", controller.checkRequirementCompletion);
|
||||
router.post("/:taskListId/tasks", controller.createTask);
|
||||
router.post("/:taskListId/tasks/bulk", controller.createTasksBulk);
|
||||
router.post("/:taskListId/tasks/bulk-archive", controller.bulkArchiveTasks);
|
||||
router.post("/:taskListId/tasks/bulk-restore", controller.bulkRestoreTasks);
|
||||
router.post("/:taskListId/tasks/bulk-delete", sensitiveOpsLimiter, controller.bulkPermanentlyDeleteTasks);
|
||||
router.patch("/:taskListId/tasks/order", controller.reorderTasks);
|
||||
|
||||
// ─── Tasks (single) ───────────────────────────────────────────────────────────
|
||||
router.get("/:taskListId/tasks/:taskId", controller.getTask);
|
||||
router.patch("/:taskListId/tasks/:taskId", controller.updateTask);
|
||||
router.delete("/:taskListId/tasks/:taskId", controller.archiveTask);
|
||||
router.delete("/:taskListId/tasks/:taskId/permanent", sensitiveOpsLimiter, controller.permanentlyDeleteTask);
|
||||
router.patch("/:taskListId/tasks/:taskId/restore", controller.restoreTask);
|
||||
|
||||
// ─── Completions (nested under task) ─────────────────────────────────────────
|
||||
router.get("/:taskListId/tasks/:taskId/completions", completionController.getCompletions);
|
||||
router.get("/:taskListId/tasks/:taskId/completions/user/:userId", completionController.getCompletionsByUser);
|
||||
router.post("/:taskListId/tasks/:taskId/completions/bulk-archive", completionController.bulkArchiveCompletions);
|
||||
router.post("/:taskListId/tasks/:taskId/completions/bulk-restore", completionController.bulkRestoreCompletions);
|
||||
router.get("/:taskListId/tasks/:taskId/completions/:completionId", completionController.getCompletion);
|
||||
router.patch("/:taskListId/tasks/:taskId/completions/:completionId/review", completionController.reviewSubmission);
|
||||
router.delete("/:taskListId/tasks/:taskId/completions/:completionId", completionController.archiveCompletion);
|
||||
router.patch("/:taskListId/tasks/:taskId/completions/:completionId/restore", completionController.restoreCompletion);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,14 @@
|
||||
'use strict';
|
||||
|
||||
const router = require('express').Router();
|
||||
const ctrl = require('../../controllers/admin/tier_categories.controller');
|
||||
|
||||
// Auth + requireAdmin applied by admin.routes.js
|
||||
|
||||
router.get ('/', ctrl.getCategories);
|
||||
router.post ('/', ctrl.createCategory);
|
||||
router.get ('/:id', ctrl.getCategory);
|
||||
router.put ('/:id', ctrl.updateCategory);
|
||||
router.delete('/:id', ctrl.deleteCategory);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,19 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const ctrl = require('../../controllers/admin/tier_policies.controller');
|
||||
|
||||
// ─── Payment Policies (promo codes + refund window) ───────────────────────────
|
||||
// GET /admin/tiers/plans/:planId/payment-policy
|
||||
// PUT /admin/tiers/plans/:planId/payment-policy — JSON: { promo_rules, refund_policy, allowed_providers }
|
||||
router.get('/plans/:planId/payment-policy', ctrl.getPaymentPolicy);
|
||||
router.put('/plans/:planId/payment-policy', ctrl.upsertPaymentPolicy);
|
||||
|
||||
// ─── System Badges ────────────────────────────────────────────────────────────
|
||||
// GET /admin/tiers/system-badges
|
||||
// GET /admin/tiers/system-badges/:key
|
||||
// PUT /admin/tiers/system-badges/:key — JSON: { asset_id, label, active_from, active_until, … }
|
||||
router.get('/system-badges', ctrl.getSystemBadges);
|
||||
router.get('/system-badges/:key', ctrl.getSystemBadge);
|
||||
router.put('/system-badges/:key', ctrl.upsertSystemBadge);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,37 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const ctrl = require('../../controllers/admin/tiers.controller');
|
||||
|
||||
router.get ('/currencies', ctrl.getCurrencies);
|
||||
router.get ('/', ctrl.getPlans);
|
||||
router.post ('/', ctrl.createPlan);
|
||||
router.get ('/field-values', ctrl.getPlanFieldValues);
|
||||
router.post ('/bulk/archive', ctrl.bulkArchivePlans);
|
||||
router.post ('/bulk/restore', ctrl.bulkRestorePlans);
|
||||
router.post ('/bulk/permanent-delete', ctrl.bulkPermanentlyDeletePlans);
|
||||
|
||||
router.get ('/payments/field-values', ctrl.getPaymentFieldValues);
|
||||
router.get ('/payments', ctrl.getPayments);
|
||||
router.get ('/payments/:id', ctrl.getPayment);
|
||||
|
||||
router.get ('/users/:id/tiers', ctrl.getUserTiers);
|
||||
router.post ('/users/tiers/grant', ctrl.grantTier);
|
||||
router.patch ('/users/tiers/:tid/revoke', ctrl.revokeTier);
|
||||
|
||||
// ← course/unit/lesson + impact routes before /:id
|
||||
router.get ('/:id/impact', ctrl.getPlanImpact);
|
||||
router.get ('/:id/permanent-delete-impact', ctrl.getPlanPermanentDeleteImpact);
|
||||
router.get ('/:id/courses', ctrl.getPlanCourses);
|
||||
router.post ('/:id/courses', ctrl.syncPlanCourses);
|
||||
router.get ('/:id/units', ctrl.getPlanUnits);
|
||||
router.post ('/:id/units', ctrl.syncPlanUnits);
|
||||
router.get ('/:id/lessons', ctrl.getPlanLessons);
|
||||
router.post ('/:id/lessons', ctrl.syncPlanLessons);
|
||||
|
||||
router.get ('/:id', ctrl.getPlan);
|
||||
router.put ('/:id', ctrl.updatePlan);
|
||||
router.delete('/:id', ctrl.archivePlan);
|
||||
router.post ('/:id/restore', ctrl.restorePlan);
|
||||
router.delete('/:id/permanent', ctrl.permanentlyDeletePlan);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,71 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: units.routes.js (admin)
|
||||
* Type of Program: Router
|
||||
* Description: Standalone Unit library — Units run independently of Courses.
|
||||
* Course membership (attach/detach/reorder) stays under
|
||||
* /admin/courses/:courseId/units.
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jul. 7, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
const router = require("express").Router();
|
||||
const ctrl = require("../../controllers/admin/units.controller");
|
||||
const questionCtrl = require("../../controllers/admin/courses.controller"); // shared quiz-question handlers (keyed by quizId)
|
||||
const requirementsCtrl = require("../../controllers/admin/completion_requirements.controller");
|
||||
|
||||
// Guards are applied by admin.routes.js (authenticate → requireAdmin → adminLimiter)
|
||||
|
||||
// ── static segments first ─────────────────────────────────────────────────────
|
||||
router.get("/", ctrl.getUnits);
|
||||
router.post("/", ctrl.createUnit);
|
||||
router.post("/full", ctrl.createUnitFull);
|
||||
router.get("/flat", ctrl.getUnitsFlat);
|
||||
router.get("/by-subscription", ctrl.getUnitsBySubscription);
|
||||
router.get("/field-values", ctrl.getUnitFieldValues);
|
||||
router.delete("/bulk", ctrl.bulkArchiveUnits);
|
||||
router.delete("/bulk/permanent", ctrl.bulkPermanentlyDeleteUnits);
|
||||
router.get("/archives", ctrl.getArchivedUnits);
|
||||
router.patch("/restore/bulk", ctrl.bulkRestoreUnits);
|
||||
router.get("/archives/:unitId", ctrl.getArchivedUnit);
|
||||
|
||||
// ── then :unitId ──────────────────────────────────────────────────────────────
|
||||
router.patch("/:unitId/restore", ctrl.restoreUnit);
|
||||
router.get("/:unitId/archive-impact", ctrl.getUnitArchiveImpact);
|
||||
router.get("/:unitId/permanent-delete-impact", ctrl.getUnitPermanentDeleteImpact);
|
||||
router.get("/:unitId", ctrl.getUnit);
|
||||
router.put("/:unitId", ctrl.updateUnit);
|
||||
router.delete("/:unitId", ctrl.archiveUnit);
|
||||
router.delete("/:unitId/permanent", ctrl.permanentlyDeleteUnit);
|
||||
|
||||
// ── Completion requirements (shared with the nested courses.routes.js mount) ──
|
||||
router.get("/:unitId/requirements", requirementsCtrl.getUnitRequirements);
|
||||
router.put("/:unitId/requirements", requirementsCtrl.syncUnitRequirements);
|
||||
|
||||
// ── Lesson membership (attach / detach / reorder) ─────────────────────────────
|
||||
router.post("/:unitId/lessons", ctrl.attachLessons);
|
||||
router.put("/:unitId/lessons/order", ctrl.reorderLessons);
|
||||
router.delete("/:unitId/lessons/:lessonId", ctrl.detachLesson);
|
||||
|
||||
// ── Unit quiz (travels with the Unit) ─────────────────────────────────────────
|
||||
router.get("/:unitId/quiz", ctrl.getQuiz);
|
||||
router.post("/:unitId/quiz", ctrl.createQuiz);
|
||||
router.get("/:unitId/quiz/archives", ctrl.getArchivedQuiz);
|
||||
router.patch("/:unitId/quiz/:quizId", ctrl.updateQuiz);
|
||||
router.delete("/:unitId/quiz/:quizId", ctrl.deleteQuiz);
|
||||
router.patch("/:unitId/quiz/:quizId/restore", ctrl.restoreQuiz);
|
||||
|
||||
// ── Quiz questions (shared handlers — keyed by quizId only) ───────────────────
|
||||
router.get("/:unitId/quiz/:quizId/questions", questionCtrl.getQuestions);
|
||||
router.post("/:unitId/quiz/:quizId/questions", questionCtrl.createQuestion);
|
||||
router.delete("/:unitId/quiz/:quizId/questions/bulk", questionCtrl.bulkArchiveQuestions);
|
||||
router.patch("/:unitId/quiz/:quizId/questions/restore/bulk", questionCtrl.bulkRestoreQuestions);
|
||||
router.put("/:unitId/quiz/:quizId/questions/bulk-sync", questionCtrl.bulkSyncQuestions);
|
||||
router.get("/:unitId/quiz/:quizId/questions/archives/:questionId", questionCtrl.getArchivedQuestion);
|
||||
router.patch("/:unitId/quiz/:quizId/questions/:questionId", questionCtrl.updateQuestion);
|
||||
router.delete("/:unitId/quiz/:quizId/questions/:questionId", questionCtrl.deleteQuestion);
|
||||
router.patch("/:unitId/quiz/:quizId/questions/:questionId/restore", questionCtrl.restoreQuestion);
|
||||
|
||||
// ── Quiz completions ──────────────────────────────────────────────────────────
|
||||
router.get("/:unitId/quiz/:quizId/completions", questionCtrl.getQuizCompletions);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,40 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const usersCtrl = require('../../controllers/admin/users.controller');
|
||||
const activityCtrl = require('../../controllers/admin/user_activity.controller');
|
||||
const { sensitiveOpsLimiter } = require('../../middleware/rateLimiter.middleware');
|
||||
|
||||
router.post('/bulk/restore', sensitiveOpsLimiter, usersCtrl.bulkRestoreUsers);
|
||||
router.delete('/bulk', sensitiveOpsLimiter, usersCtrl.bulkDeactivateUsers);
|
||||
router.delete('/bulk/permanent', sensitiveOpsLimiter, usersCtrl.bulkPermanentlyDeleteUsers);
|
||||
router.get('/field-values', usersCtrl.getUserFieldValues);
|
||||
router.get('/archived', usersCtrl.getArchivedUsers);
|
||||
router.post('/staff', sensitiveOpsLimiter, usersCtrl.addStaffUser);
|
||||
router.get('/', usersCtrl.getUsers);
|
||||
router.get('/:id', usersCtrl.getUser);
|
||||
router.put('/:id', sensitiveOpsLimiter, usersCtrl.updateUser);
|
||||
router.delete('/:id', sensitiveOpsLimiter, usersCtrl.deactivateUser);
|
||||
router.delete('/:id/permanent', sensitiveOpsLimiter, usersCtrl.permanentlyDeleteUser);
|
||||
router.post('/:id/restore', sensitiveOpsLimiter, usersCtrl.restoreUser);
|
||||
router.post('/:id/make-admin', sensitiveOpsLimiter, usersCtrl.makeAdmin);
|
||||
router.post('/:id/demote-admin', sensitiveOpsLimiter, usersCtrl.demoteAdmin);
|
||||
|
||||
// ── Bans ──────────────────────────────────────────────────────────────────────
|
||||
router.post('/bulk/ban', sensitiveOpsLimiter, usersCtrl.bulkBanUsers);
|
||||
router.post('/bulk/unban', sensitiveOpsLimiter, usersCtrl.bulkUnbanUsers);
|
||||
router.get('/:id/bans', usersCtrl.getUserBans);
|
||||
router.post('/:id/ban', sensitiveOpsLimiter, usersCtrl.banUser);
|
||||
router.post('/:id/unban', sensitiveOpsLimiter, usersCtrl.unbanUser);
|
||||
|
||||
// ── Sessions ──────────────────────────────────────────────────────────────────
|
||||
router.get('/:id/sessions', usersCtrl.getUserSessions);
|
||||
router.delete('/:id/sessions/:sid', sensitiveOpsLimiter, usersCtrl.terminateSession);
|
||||
|
||||
// ── Achievements ──────────────────────────────────────────────────────────────
|
||||
router.get('/:id/achievements', usersCtrl.getUserAchievements);
|
||||
|
||||
// ── Activity ──────────────────────────────────────────────────────────────────
|
||||
router.get('/:id/activity', activityCtrl.getUserActivity);
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user