mirror of
https://github.com/rgrgogu/new_starr.git
synced 2026-09-27 00:12:54 +08:00
ready to test
Testing Signed-off-by: Kenneth Obsequio <k80308392@gmail.com>
This commit is contained in:
@@ -23,22 +23,40 @@ const { authenticate } = require('../../middleware/auth.middleware');
|
||||
const { requireAdmin } = require('../../middleware/rbac.middleware');
|
||||
const { adminLimiter } = require('../../middleware/rateLimiter.middleware');
|
||||
|
||||
const dashboardRoutes = require('./dashboard.routes');
|
||||
const usersRoutes = require('./users.routes');
|
||||
const groupsRoutes = require('./groups.routes');
|
||||
const assetsRoutes = require('./assets.routes');
|
||||
const coursesRoutes = require('./courses.routes')
|
||||
const taskRoutes = require('./task.routes');
|
||||
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 taskRoutes = require('./task.routes');
|
||||
const tiersRoutes = require('./tiers.routes');
|
||||
const categoriesRoutes = require('./categories.routes');
|
||||
const productsRoutes = require('./products.routes');
|
||||
const advertisementRoutes = require('./advertisements.routes');
|
||||
const notificationRoutes = require('./notifications.routes');
|
||||
const mediaRoutes = require('./media.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('/users', usersRoutes);
|
||||
router.use('/profile', profileRoutes);
|
||||
router.use('/users', usersRoutes);
|
||||
router.use('/groups', groupsRoutes);
|
||||
router.use('/assets', assetsRoutes);
|
||||
router.use('/courses', coursesRoutes);
|
||||
router.use('/task-lists', taskRoutes);
|
||||
router.use('/tiers', tiersRoutes);
|
||||
router.use('/categories', categoriesRoutes);
|
||||
router.use('/products', productsRoutes);
|
||||
router.use('/advertisements', advertisementRoutes);
|
||||
router.use('/notifications', notificationRoutes);
|
||||
router.use('/media', mediaRoutes);
|
||||
|
||||
// ── Activity feed — global ────────────────────────────────────────────────────
|
||||
router.get('/activity', activityCtrl.getActivity);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,22 @@
|
||||
// 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);
|
||||
|
||||
// ─── 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/restore', sensitiveOpsLimiter, controller.restoreAdvertisement);
|
||||
router.delete('/:advertisementId', sensitiveOpsLimiter, controller.archiveAdvertisement);
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,25 +1,25 @@
|
||||
const express = require('express');
|
||||
// routes/admin/assets.routes.js
|
||||
|
||||
const router = require('express').Router();
|
||||
const multer = require('multer');
|
||||
const upload = multer({ storage: multer.memoryStorage() });
|
||||
|
||||
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); // ← must be before /:assetId
|
||||
router.delete('/bulk', sensitiveOpsLimiter, controller.archiveAssets);
|
||||
router.get('/archived', controller.getArchivedAssets);
|
||||
router.get('/field-values', controller.getAssetFieldValues);
|
||||
router.delete('/bulk', sensitiveOpsLimiter, controller.archiveAssets);
|
||||
router.patch('/bulk-restore', controller.restoreAssets);
|
||||
|
||||
// ─── Collection ───────────────────────────────────────────────────────────────
|
||||
router.get('/', controller.getAssets);
|
||||
router.get('/', controller.getAssets);
|
||||
router.post('/', upload.fields([{ name: 'file', maxCount: 1 }, { name: 'thumbnail', maxCount: 1 }]), controller.uploadAsset);
|
||||
|
||||
// ─── Dynamic routes last ──────────────────────────────────────────────────────
|
||||
router.get('/:assetId', controller.getAsset);
|
||||
router.patch('/:assetId', sensitiveOpsLimiter, upload.fields([{ name: 'file', maxCount: 1 }]), controller.updateAsset);
|
||||
router.get('/:assetId', controller.getAsset);
|
||||
router.patch('/:assetId', sensitiveOpsLimiter, upload.fields([{ name: 'file', maxCount: 1 }]), controller.updateAsset);
|
||||
router.patch('/:assetId/restore', sensitiveOpsLimiter, controller.restoreAsset);
|
||||
router.delete('/:assetId', sensitiveOpsLimiter, controller.archiveAsset);
|
||||
router.delete('/:assetId', sensitiveOpsLimiter, controller.archiveAsset);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,12 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const ctrl = require('../../controllers/admin/categories.controller');
|
||||
|
||||
router.get ('/', ctrl.getCategories);
|
||||
router.post ('/', ctrl.createCategory);
|
||||
router.get ('/:id', ctrl.getCategory);
|
||||
router.put ('/:id', ctrl.updateCategory);
|
||||
router.delete('/:id', ctrl.archiveCategory);
|
||||
router.post ('/:id/restore', ctrl.restoreCategory);
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,5 +1,6 @@
|
||||
const router = require("express").Router();
|
||||
const ctrl = require("../../controllers/admin/courses.controller");
|
||||
const ctrl = require("../../controllers/admin/courses.controller");
|
||||
const progressCtrl = require("../../controllers/admin/course_reading_progress.controller");
|
||||
|
||||
const { authenticate } = require("../../middleware/auth.middleware");
|
||||
const { requireAdmin } = require("../../middleware/rbac.middleware");
|
||||
@@ -19,6 +20,9 @@ router.get("/field-values", ctrl.getCourseFieldValues);
|
||||
router.delete("/bulk", ctrl.bulkArchiveCourses);
|
||||
router.get("/archives", ctrl.getArchivedCourses);
|
||||
router.patch("/restore/bulk", ctrl.bulkRestoreCourses);
|
||||
router.get("/flat", ctrl.getCoursesFlat);
|
||||
router.get("/units-flat", ctrl.getUnitsFlat);
|
||||
router.get("/lessons-flat", ctrl.getLessonsFlat);
|
||||
|
||||
// ── then :courseId ────────────────────────────────────────────────────────────
|
||||
router.get("/archives/:courseId", ctrl.getArchivedCourse);
|
||||
@@ -27,6 +31,13 @@ router.get("/:courseId", ctrl.getCourse);
|
||||
router.put("/:courseId", ctrl.updateCourse);
|
||||
router.delete("/:courseId", ctrl.archiveCourse);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// COURSE INSTRUCTORS
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
|
||||
router.get("/:courseId/instructors", ctrl.getInstructors);
|
||||
router.put("/:courseId/instructors", ctrl.syncInstructors);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// PREREQUISITES
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
@@ -45,7 +56,7 @@ router.post("/:courseId/assessment", ctrl.createAssessment);
|
||||
router.get("/:courseId/assessment/archives", ctrl.getArchivedAssessment);
|
||||
|
||||
// ── then :assessmentId ────────────────────────────────────────────────────────
|
||||
router.put("/:courseId/assessment/:assessmentId", ctrl.updateAssessment);
|
||||
router.patch("/:courseId/assessment/:assessmentId", ctrl.updateAssessment);
|
||||
router.delete("/:courseId/assessment/:assessmentId", ctrl.deleteAssessment);
|
||||
router.patch("/:courseId/assessment/:assessmentId/restore", ctrl.restoreAssessment);
|
||||
|
||||
@@ -58,7 +69,7 @@ router.delete("/:courseId/assessment/:assessmentId/questions/bulk", ctrl.bulkArc
|
||||
router.patch("/:courseId/assessment/:assessmentId/questions/restore/bulk", ctrl.bulkRestoreQuestions);
|
||||
router.get("/:courseId/assessment/:assessmentId/questions/archives/:questionId", ctrl.getArchivedQuestion);
|
||||
|
||||
router.put("/:courseId/assessment/:assessmentId/questions/:questionId", ctrl.updateQuestion);
|
||||
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);
|
||||
|
||||
@@ -91,7 +102,7 @@ router.post("/:courseId/units/:unitId/quiz", ctrl.createQuiz);
|
||||
// static before :quizId
|
||||
router.get("/:courseId/units/:unitId/quiz/archives", ctrl.getArchivedQuiz);
|
||||
|
||||
router.put("/:courseId/units/:unitId/quiz/:quizId", ctrl.updateQuiz);
|
||||
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);
|
||||
|
||||
@@ -104,7 +115,7 @@ router.delete("/:courseId/units/:unitId/quiz/:quizId/questions/bulk", ctrl.bulkA
|
||||
router.patch("/:courseId/units/:unitId/quiz/:quizId/questions/restore/bulk", ctrl.bulkRestoreQuestions);
|
||||
router.get("/:courseId/units/:unitId/quiz/:quizId/questions/archives/:questionId", ctrl.getArchivedQuestion);
|
||||
|
||||
router.put("/:courseId/units/:unitId/quiz/:quizId/questions/:questionId", ctrl.updateQuestion);
|
||||
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);
|
||||
|
||||
@@ -134,4 +145,12 @@ router.delete("/:courseId/units/:unitId/lessons/:lessonId", ctrl.archiveLesson);
|
||||
router.get("/:courseId/units/:unitId/lessons/:lessonId/page", ctrl.getLessonPage);
|
||||
router.put("/:courseId/units/:unitId/lessons/:lessonId/page", ctrl.upsertLessonPage);
|
||||
|
||||
// ══════════════════════════════════════════════════════════════════════════════
|
||||
// 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,15 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* 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);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,25 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* 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, markSeen, markAllSeen } = require('../../controllers/admin/notification.controller');
|
||||
|
||||
router.get('/', list);
|
||||
router.get('/unseen', unseenCount);
|
||||
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;
|
||||
@@ -1,5 +1,6 @@
|
||||
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");
|
||||
|
||||
// ─── Task Lists ───────────────────────────────────────────────────────────────
|
||||
@@ -35,4 +36,13 @@ router.patch("/:taskListId/tasks/:taskId", sensitiveOpsLimiter, controller.updat
|
||||
router.delete("/:taskListId/tasks/:taskId", sensitiveOpsLimiter, controller.archiveTask);
|
||||
router.patch("/:taskListId/tasks/:taskId/restore", sensitiveOpsLimiter, 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", sensitiveOpsLimiter, completionController.bulkArchiveCompletions);
|
||||
router.post("/:taskListId/tasks/:taskId/completions/bulk-restore", sensitiveOpsLimiter, completionController.bulkRestoreCompletions);
|
||||
router.get("/:taskListId/tasks/:taskId/completions/:completionId", completionController.getCompletion);
|
||||
router.delete("/:taskListId/tasks/:taskId/completions/:completionId", sensitiveOpsLimiter, completionController.archiveCompletion);
|
||||
router.patch("/:taskListId/tasks/:taskId/completions/:completionId/restore", sensitiveOpsLimiter, completionController.restoreCompletion);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,28 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const ctrl = require('../../controllers/admin/tiers.controller');
|
||||
|
||||
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.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 routes before /:id
|
||||
router.get ('/:id/courses', ctrl.getPlanCourses);
|
||||
router.post ('/:id/courses', ctrl.syncPlanCourses);
|
||||
|
||||
router.get ('/:id', ctrl.getPlan);
|
||||
router.put ('/:id', ctrl.updatePlan);
|
||||
router.delete('/:id', ctrl.archivePlan);
|
||||
router.post ('/:id/restore', ctrl.restorePlan);
|
||||
|
||||
module.exports = router;
|
||||
@@ -1,7 +1,8 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const usersCtrl = require('../../controllers/admin/users.controller');
|
||||
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);
|
||||
@@ -19,4 +20,10 @@ router.post('/:id/restore', sensitiveOpsLimiter, usersCtrl.restoreUser);
|
||||
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;
|
||||
+6
-16
@@ -13,15 +13,14 @@
|
||||
* POST /api/auth/refresh → refresh access token
|
||||
* POST /api/auth/logout → logout (requires authenticate)
|
||||
* POST /api/auth/change-password → change password (requires authenticate)
|
||||
* GET /api/auth/google → initiate Google OAuth
|
||||
* GET /api/auth/google/callback → Google OAuth callback
|
||||
* GET /api/auth/google → initiate Google OIDC (generates state/nonce/PKCE)
|
||||
* GET /api/auth/google/callback → Google OIDC callback (verifies + exchanges code)
|
||||
*
|
||||
* Author: rgrgogu
|
||||
* Date Created: Oct. 6, 2025
|
||||
***********************************************************************************************************************************************************************/
|
||||
const express = require('express');
|
||||
const passport = require('passport');
|
||||
const router = express.Router();
|
||||
const router = express.Router();
|
||||
|
||||
const authCtrl = require('../controllers/auth.controller');
|
||||
const { authenticate } = require('../middleware/auth.middleware');
|
||||
@@ -45,17 +44,8 @@ router.post('/refresh', authCtrl.refreshToken);
|
||||
router.post('/logout', authenticate, authLimiter, authCtrl.logout);
|
||||
router.post('/change-password', authenticate, sensitiveOpsLimiter, ...changePassValidator, validate, authCtrl.changePassword);
|
||||
|
||||
// ── Google OAuth ───────────────────────────────────────────────────────────────
|
||||
router.get('/google',
|
||||
authLimiter,
|
||||
passport.authenticate('google', { scope: ['profile', 'email'], session: false })
|
||||
);
|
||||
router.get('/google/callback',
|
||||
passport.authenticate('google', { session: false, failureRedirect: '/api/auth/google/failed' }),
|
||||
authCtrl.googleCallback
|
||||
);
|
||||
router.get('/google/failed', (req, res) => {
|
||||
res.status(401).json({ status: 'error', message: 'Google authentication failed.' });
|
||||
});
|
||||
// ── Google OIDC ────────────────────────────────────────────────────────────────
|
||||
router.get('/google', authLimiter, authCtrl.googleRedirect);
|
||||
router.get('/google/callback', authCtrl.googleCallback);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,11 @@
|
||||
// routes/client/advertisements.routes.js
|
||||
const router = require('express').Router();
|
||||
const controller = require('../../controllers/client/advertisements.controller');
|
||||
|
||||
// ─── GET /api/client/advertisements/active?type=hero ──────────────────────────
|
||||
router.get('/active', controller.getActiveAdvertisement);
|
||||
|
||||
// ─── POST /api/client/advertisements/:advertisementId/click ───────────────────
|
||||
router.post('/:advertisementId/click', controller.trackClick);
|
||||
|
||||
module.exports = router;
|
||||
@@ -10,25 +10,57 @@
|
||||
* DELETE /api/client/sessions/:id → revoke own session
|
||||
*
|
||||
* Guards: authenticate → requireClient()
|
||||
* Other routes contain here
|
||||
*
|
||||
* Author: rgrgogu
|
||||
* Date Created: Oct. 6, 2025
|
||||
* Modified: Jun. 13, 2026 (by Kenneth Obsequio (@lash0000)
|
||||
***********************************************************************************************************************************************************************/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const profileCtrl = require('../../controllers/client/profile.controller');
|
||||
const { authenticate } = require('../../middleware/auth.middleware');
|
||||
const { authenticate, softAuthenticate } = require('../../middleware/auth.middleware');
|
||||
const { requireClient } = require('../../middleware/rbac.middleware');
|
||||
const { validate } = require('../../middleware/validate.middleware');
|
||||
const { updateProfileValidator } = require('../../validators/profile.validator');
|
||||
const { handleAvatarUpload } = require('../../middleware/avatar_upload.middleware');
|
||||
|
||||
// Apply auth + role to all routes in this file
|
||||
const tiersRoutes = require('./tiers.routes');
|
||||
const coursesRoutes = require('./courses.routes');
|
||||
const coursePurchasesRoutes = require('./course_purchases.routes');
|
||||
const mediaRoutes = require("./media.routes");
|
||||
const groupsRoutes = require("./groups.routes");
|
||||
const advertisementRoutes = require('./advertisements.routes');
|
||||
const notificationRoutes = require('./notifications.routes');
|
||||
const certificateCtrl = require('../../controllers/client/certificate.controller');
|
||||
|
||||
// ── Media routes FIRST — before global auth ───────────────────────────────────
|
||||
// /stream/:token is authenticated by the signed JWT in the URL param, not by
|
||||
// session cookies. Browser media elements don't send auth headers on range requests.
|
||||
router.use('/media', mediaRoutes);
|
||||
|
||||
// ── Soft-auth routes — return null instead of 401 when unauthenticated ────────
|
||||
const { unseenCount } = require('../../controllers/client/notification.controller');
|
||||
router.get('/notifications/unseen', softAuthenticate, unseenCount);
|
||||
|
||||
// ── Apply auth + role to all remaining routes ─────────────────────────────────
|
||||
router.use(authenticate, requireClient());
|
||||
|
||||
router.get('/profile', profileCtrl.getProfile);
|
||||
router.put('/profile', ...updateProfileValidator, validate, profileCtrl.updateProfile);
|
||||
router.get('/sessions', profileCtrl.getSessions);
|
||||
router.delete('/sessions/:id', profileCtrl.revokeSession);
|
||||
router.get('/profile', profileCtrl.getProfile);
|
||||
router.put('/profile', ...updateProfileValidator, validate, profileCtrl.updateProfile);
|
||||
router.post('/profile/avatar', handleAvatarUpload, profileCtrl.uploadAvatar);
|
||||
router.delete('/profile/avatar', profileCtrl.deleteAvatar);
|
||||
router.get('/sessions', profileCtrl.getSessions);
|
||||
router.get('/achievements', profileCtrl.getAchievements);
|
||||
router.delete('/sessions/:id', profileCtrl.revokeSession);
|
||||
|
||||
router.use('/tiers', tiersRoutes);
|
||||
router.use('/courses', coursesRoutes);
|
||||
router.use('/course-purchases', coursePurchasesRoutes);
|
||||
router.use('/groups', groupsRoutes);
|
||||
router.use('/advertisements', advertisementRoutes);
|
||||
router.use('/notifications', notificationRoutes);
|
||||
|
||||
router.get('/certificates/:courseUuid', certificateCtrl.getCertificate);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,10 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const ctrl = require('../../controllers/client/course_purchases.controller');
|
||||
|
||||
router.get ('/', ctrl.getMyPurchases);
|
||||
router.post('/order', ctrl.createCourseOrder);
|
||||
router.post('/capture', ctrl.captureCourseOrder);
|
||||
router.post('/cancel', ctrl.cancelCourseOrder);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,39 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const ctrl = require('../../controllers/client/courses.controller');
|
||||
const progressCtrl = require('../../controllers/client/course_reading_progress.controller');
|
||||
|
||||
// Profile learning progress card — must come before /:courseId
|
||||
router.get('/in-progress', progressCtrl.getMyInProgressCourses);
|
||||
|
||||
// UUID lookups (task requirement blocks — must come before /:courseId)
|
||||
router.get('/uuid/:uuid', ctrl.getCourseByUuid);
|
||||
router.get('/unit/uuid/:uuid/lessons', ctrl.getLessonsByUnitUuid);
|
||||
router.get('/unit/uuid/:uuid', ctrl.getUnitByUuid);
|
||||
router.get('/lesson/uuid/:uuid', ctrl.getLessonByUuid);
|
||||
|
||||
// Courses
|
||||
router.get('/', ctrl.getCourses);
|
||||
router.get('/:courseId', ctrl.getCourse);
|
||||
|
||||
// Unit
|
||||
router.get('/:courseId/units/:unitId', ctrl.getUnit);
|
||||
|
||||
// Lesson
|
||||
router.get('/:courseId/units/:unitId/lessons/:lessonId', ctrl.getLesson);
|
||||
|
||||
// Quiz (no answers)
|
||||
router.get('/:courseId/units/:unitId/quiz', ctrl.getUnitQuiz);
|
||||
|
||||
// Assessment (no answers)
|
||||
router.get('/:courseId/assessment', ctrl.getCourseAssessment);
|
||||
|
||||
router.post('/:courseId/units/:unitId/quiz/:quizId/submit', ctrl.submitUnitQuiz);
|
||||
router.post('/:courseId/assessment/:assessmentId/submit', ctrl.submitCourseAssessment);
|
||||
|
||||
// Reading progress
|
||||
router.get( '/:courseId/progress/summary', progressCtrl.getCourseProgressSummary);
|
||||
router.get( '/:courseId/progress', progressCtrl.getCourseProgress);
|
||||
router.post('/:courseId/units/:unitId/lessons/:lessonId/progress', progressCtrl.upsertLessonProgress);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,97 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: groups.routes.js (client)
|
||||
* Type of Program: Router
|
||||
* Description: Client-facing task routes — groups, task lists, tasks, completions,
|
||||
* file uploads, and progress tracking.
|
||||
*
|
||||
* Route Map:
|
||||
* GET /client/groups → my groups
|
||||
* GET /client/groups/:groupId → single group
|
||||
* GET /client/groups/:groupId/task-lists → task lists (?status=ongoing|done|overdue)
|
||||
* GET /client/groups/:groupId/task-lists/:taskListId → single task list
|
||||
* GET /client/groups/:groupId/task-lists/:taskListId/tasks/:taskId → task + requirements + latest completion
|
||||
* GET /client/groups/:groupId/task-lists/:taskListId/tasks/:taskId/progress → full progress snapshot
|
||||
* GET /client/groups/:groupId/task-lists/:taskListId/tasks/:taskId/completions/latest → latest completion only
|
||||
* GET /client/groups/:groupId/task-lists/:taskListId/tasks/:taskId/completions → full completion history
|
||||
* POST /client/groups/:groupId/task-lists/:taskListId/tasks/:taskId/completions → submit completion
|
||||
* POST /client/groups/:groupId/task-lists/:taskListId/tasks/:taskId/upload → upload file to S3 (returns file metadata)
|
||||
* POST /client/groups/:groupId/task-lists/:taskListId/tasks/:taskId/requirements/:requirementId/visit → UPSERT link visit
|
||||
* POST /client/groups/:groupId/task-lists/:taskListId/tasks/:taskId/requirements/:requirementId/progress → UPSERT lesson progress
|
||||
*
|
||||
* Guards: authenticate + requireClient() applied in client.routes.js
|
||||
*
|
||||
* Author: rgrgogu
|
||||
* Date Created: Jun. 13, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
const router = require('express').Router();
|
||||
const taskController = require('../../controllers/client/task.controller');
|
||||
const uploadController = require('../../controllers/client/task_upload.controller');
|
||||
const progressController = require('../../controllers/client/task_progress.controller');
|
||||
const downloadController = require('../../controllers/client/task_download.controller');
|
||||
const { handleUpload } = require('../../middleware/task_upload.middleware');
|
||||
const { sensitiveOpsLimiter } = require('../../middleware/rateLimiter.middleware');
|
||||
|
||||
// ─── Groups ───────────────────────────────────────────────────────────────────
|
||||
router.get('/', taskController.getMyGroups);
|
||||
router.get('/:groupId', taskController.getMyGroup);
|
||||
|
||||
// ─── Task Lists ───────────────────────────────────────────────────────────────
|
||||
router.get('/:groupId/task-lists', taskController.getGroupTaskLists);
|
||||
router.get('/:groupId/task-lists/:taskListId', taskController.getGroupTaskList);
|
||||
|
||||
// ─── Tasks ────────────────────────────────────────────────────────────────────
|
||||
router.get('/:groupId/task-lists/:taskListId/tasks/:taskId', taskController.getTask);
|
||||
|
||||
// ─── Progress snapshot ────────────────────────────────────────────────────────
|
||||
router.get(
|
||||
'/:groupId/task-lists/:taskListId/tasks/:taskId/progress',
|
||||
progressController.getTaskProgress
|
||||
);
|
||||
|
||||
// ─── Completions ──────────────────────────────────────────────────────────────
|
||||
// NOTE: /latest must be before / to avoid Express treating "latest" as an ID
|
||||
router.get(
|
||||
'/:groupId/task-lists/:taskListId/tasks/:taskId/completions/latest',
|
||||
taskController.getLatestCompletion
|
||||
);
|
||||
router.get(
|
||||
'/:groupId/task-lists/:taskListId/tasks/:taskId/completions',
|
||||
taskController.getMySubmissions
|
||||
);
|
||||
router.post(
|
||||
'/:groupId/task-lists/:taskListId/tasks/:taskId/completions',
|
||||
sensitiveOpsLimiter,
|
||||
taskController.submitTask
|
||||
);
|
||||
router.get(
|
||||
'/:groupId/task-lists/:taskListId/tasks/:taskId/completions/:completionId/files/:fileId/stream',
|
||||
downloadController.streamCompletionFile
|
||||
);
|
||||
router.get(
|
||||
'/:groupId/task-lists/:taskListId/tasks/:taskId/completions/:completionId/files/:fileId/download',
|
||||
downloadController.downloadCompletionFile
|
||||
);
|
||||
|
||||
// ─── File upload → S3 ────────────────────────────────────────────────────────
|
||||
// Client uploads one file at a time, gets back file metadata,
|
||||
// then includes all file references in the completion submit payload.
|
||||
router.post(
|
||||
'/:groupId/task-lists/:taskListId/tasks/:taskId/upload',
|
||||
sensitiveOpsLimiter,
|
||||
handleUpload,
|
||||
uploadController.uploadTaskFile
|
||||
);
|
||||
|
||||
// ─── Requirement-level progress (UPSERT) ─────────────────────────────────────
|
||||
router.post(
|
||||
'/:groupId/task-lists/:taskListId/tasks/:taskId/requirements/:requirementId/visit',
|
||||
sensitiveOpsLimiter,
|
||||
progressController.visitLink
|
||||
);
|
||||
router.post(
|
||||
'/:groupId/task-lists/:taskListId/tasks/:taskId/requirements/:requirementId/progress',
|
||||
sensitiveOpsLimiter,
|
||||
progressController.updateProgress
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,25 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: media.routes.js (client)
|
||||
* Type of Program: Routes
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jun. 12, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
"use strict";
|
||||
|
||||
const router = require("express").Router();
|
||||
const ctrl = require("../../controllers/client/media.controller");
|
||||
const mediaGuard = require("../../middleware/mediaGuard.middleware");
|
||||
const { authenticate } = require("../../middleware/auth.middleware");
|
||||
|
||||
// ── Stream endpoint — NO mediaGuard, NO authenticate ─────────────────────────
|
||||
// The signed JWT in :token IS the only credential needed.
|
||||
// Browser media/document elements do NOT send Origin/Referer on direct requests
|
||||
// so mediaGuard would always block them.
|
||||
// Supports: video, audio, document (PDF), image
|
||||
router.get("/stream/:token", ctrl.streamAsset);
|
||||
|
||||
// ── All other routes — guarded ────────────────────────────────────────────────
|
||||
router.use(mediaGuard);
|
||||
router.post("/token", authenticate, ctrl.issueToken);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,25 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name : notifications.routes.js
|
||||
* Type : Router (Client)
|
||||
* Description : Per-user notification endpoints.
|
||||
*
|
||||
* GET /api/client/notifications — paginated list
|
||||
* GET /api/client/notifications/unseen — unseen count
|
||||
* PATCH /api/client/notifications/seen-all — mark all seen
|
||||
* PATCH /api/client/notifications/:id/seen — mark one seen
|
||||
*
|
||||
* Guards: inherited from client.routes.js (authenticate → requireClient)
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jun. 19, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { list, unseenCount, markSeen, markAllSeen } = require('../../controllers/client/notification.controller');
|
||||
|
||||
router.get('/', list);
|
||||
router.get('/unseen', unseenCount);
|
||||
router.patch('/seen-all', markAllSeen);
|
||||
router.patch('/:id/seen', markSeen);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,23 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const ctrl = require('../../controllers/client/tiers.controller');
|
||||
|
||||
// My tier
|
||||
router.get ('/me', ctrl.getMyTier);
|
||||
router.get ('/me/history', ctrl.getMyTierHistory);
|
||||
|
||||
// Plans
|
||||
router.get ('/plans', ctrl.getPlans);
|
||||
|
||||
// PayPal checkout
|
||||
router.post('/checkout/order', ctrl.createOrder);
|
||||
router.post('/checkout/capture', ctrl.captureOrder);
|
||||
router.post('/checkout/cancel', ctrl.cancelOrder);
|
||||
|
||||
// PayPal refund
|
||||
router.post('/checkout/refund', ctrl.refundOrder);
|
||||
|
||||
// My payments
|
||||
router.get ('/me/payments', ctrl.getMyPayments);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,21 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: health.routes.js
|
||||
* Type of Program: Router
|
||||
* Description: Public health check endpoints — no auth, no originGuard.
|
||||
* Intentionally kept outside all guards so monitoring tools can reach them.
|
||||
*
|
||||
* GET /api/health → dashboard (rich: app info, system info, all service connections)
|
||||
* GET /api/health/ready → readiness (compact: status + checks + memory, for machines)
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jun. 20, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
"use strict";
|
||||
|
||||
const router = require('express').Router();
|
||||
const ctrl = require('../controllers/health.controller');
|
||||
|
||||
router.get('/', ctrl.dashboard);
|
||||
router.get('/ready', ctrl.readiness);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,28 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: media.routes.js (public)
|
||||
* Type of Program: Routes
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jun. 22, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
"use strict";
|
||||
|
||||
const rateLimit = require("express-rate-limit");
|
||||
const router = require("express").Router();
|
||||
const ctrl = require("../../controllers/public/media.controller");
|
||||
|
||||
// 60 token requests per IP per 15 min — generous enough for a page with many assets
|
||||
// but still blocks scrapers from bulk-fetching tokens
|
||||
const publicTokenLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000,
|
||||
max: 60,
|
||||
keyGenerator: (req) => req.ip,
|
||||
standardHeaders: true,
|
||||
legacyHeaders: false,
|
||||
message: { status: "error", message: "Too many requests. Please try again later." },
|
||||
});
|
||||
|
||||
// Supports both GET (query string) and POST (body) for flexibility
|
||||
router.get("/token", publicTokenLimiter, ctrl.issueToken);
|
||||
router.post("/token", publicTokenLimiter, ctrl.issueToken);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,21 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: public.routes.js
|
||||
* Type of Program: Router
|
||||
* Description: Unauthenticated public endpoints. originGuard is still applied
|
||||
* (same as other route groups) so requests must originate from the
|
||||
* configured FRONTEND_URL.
|
||||
*
|
||||
* Route Map:
|
||||
* /api/public/media/token → issue stream token for public S3 assets
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jun. 22, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
"use strict";
|
||||
|
||||
const router = require("express").Router();
|
||||
const mediaRoutes = require("./media.routes");
|
||||
|
||||
router.use("/media", mediaRoutes);
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user