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,20 @@
|
||||
// routes/client/advertisements.routes.js
|
||||
const router = require('express').Router();
|
||||
const controller = require('../../controllers/client/advertisements.controller');
|
||||
|
||||
// ─── GET /api/client/advertisements/active?placement=dashboard.hero ───────────
|
||||
router.get('/active', controller.getActiveAdvertisement);
|
||||
|
||||
// ─── GET /api/client/advertisements/active-batch?placements=a,b,c ─────────────
|
||||
router.get('/active-batch', controller.getActiveAdvertisements);
|
||||
|
||||
// ─── GET /api/client/advertisements/active-list?placement=dashboard.hero ──────
|
||||
router.get('/active-list', controller.getActiveAdvertisementList);
|
||||
|
||||
// ─── GET /api/client/advertisements/uuid/:uuid ─────────────────────────────────
|
||||
router.get('/uuid/:uuid', controller.getAdvertisementByUuid);
|
||||
|
||||
// ─── POST /api/client/advertisements/:advertisementId/click ───────────────────
|
||||
router.post('/:advertisementId/click', controller.trackClick);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,72 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: client.routes.js
|
||||
* Type of Program: Router
|
||||
* Description: Protected routes accessible by any authenticated user (client, staff, admin).
|
||||
*
|
||||
* Route Map:
|
||||
* GET /api/client/profile → view own profile
|
||||
* PUT /api/client/profile → update own profile
|
||||
* DELETE /api/client/profile → delete own account (soft-delete)
|
||||
* GET /api/client/sessions → view own active sessions
|
||||
* 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 profileCtrl = require('../../controllers/client/profile.controller');
|
||||
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');
|
||||
|
||||
const tiersRoutes = require('./tiers.routes');
|
||||
const coursesRoutes = require('./courses.routes');
|
||||
const unitsRoutes = require('./units.routes');
|
||||
const lessonsRoutes = require('./lessons.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.delete('/profile', profileCtrl.deleteAccount);
|
||||
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('/units', unitsRoutes); // standalone Unit consumption (junction revamp)
|
||||
router.use('/lessons', lessonsRoutes); // standalone Lesson consumption (junction revamp)
|
||||
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,55 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const ctrl = require('../../controllers/client/courses.controller');
|
||||
const progressCtrl = require('../../controllers/client/course_reading_progress.controller');
|
||||
|
||||
// Course categories for filter chips — must come before /:courseId
|
||||
router.get('/categories', ctrl.getCategories);
|
||||
|
||||
// Profile learning progress card — must come before /:courseId
|
||||
router.get('/in-progress', progressCtrl.getMyInProgressCourses);
|
||||
|
||||
// Live view of every completed lesson/unit/course — must come before /:courseId
|
||||
router.get('/completed', progressCtrl.getMyCompletedContent);
|
||||
|
||||
// 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/task-context', progressCtrl.getUnitTaskContext);
|
||||
router.get('/unit/uuid/:uuid', ctrl.getUnitByUuid);
|
||||
router.get('/lesson/uuid/:uuid/task-context', progressCtrl.getLessonTaskContext);
|
||||
router.get('/lesson/uuid/:uuid', ctrl.getLessonByUuid);
|
||||
|
||||
// Courses
|
||||
router.get('/', ctrl.getCourses);
|
||||
router.get('/:courseId/checkout-info', ctrl.getCourseCheckoutInfo);
|
||||
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
|
||||
router.get( '/:courseId/assessment', ctrl.getCourseAssessment);
|
||||
router.post('/:courseId/assessment/:assessmentId/start', ctrl.startCourseAssessment);
|
||||
router.get( '/:courseId/assessment/:assessmentId/session', ctrl.getAssessmentSession);
|
||||
router.patch('/:courseId/assessment/:assessmentId/draft', ctrl.saveDraft);
|
||||
|
||||
router.patch('/:courseId/units/:unitId/quiz/:quizId/draft', ctrl.saveQuizDraft);
|
||||
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.get( '/:courseId/task-context', progressCtrl.getCourseTaskContext);
|
||||
router.post('/:courseId/units/:unitId/lessons/:lessonId/progress', progressCtrl.upsertLessonProgress);
|
||||
router.post('/:courseId/units/:unitId/lessons/:lessonId/watch-progress', progressCtrl.upsertWatchProgress);
|
||||
router.post('/:courseId/units/:unitId/lessons/:lessonId/mark-complete', progressCtrl.markLessonComplete);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,102 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* 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|completed|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.delete(
|
||||
'/:groupId/task-lists/:taskListId/tasks/:taskId/requirements/:requirementId/visit',
|
||||
sensitiveOpsLimiter,
|
||||
progressController.unvisitLink
|
||||
);
|
||||
router.post(
|
||||
'/:groupId/task-lists/:taskListId/tasks/:taskId/requirements/:requirementId/progress',
|
||||
sensitiveOpsLimiter,
|
||||
progressController.updateProgress
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,24 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: lessons.routes.js (client)
|
||||
* Type of Program: Router
|
||||
* Description: Standalone Lesson consumption — each Lesson runs independently.
|
||||
*
|
||||
* GET /client/lessons → learner-facing lesson library
|
||||
* GET /client/lessons/:uuid → lesson with full block content
|
||||
* POST /client/lessons/:uuid/progress → standalone reading progress
|
||||
* (course NULL; body.unit_uuid optional)
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jul. 7, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const ctrl = require('../../controllers/client/units.controller');
|
||||
|
||||
router.get('/', ctrl.getLessons);
|
||||
router.get('/:uuid', ctrl.getLessonByUuid);
|
||||
router.post('/:uuid/progress', ctrl.upsertStandaloneLessonProgress);
|
||||
router.post('/:uuid/watch-progress', ctrl.upsertStandaloneWatchProgress);
|
||||
router.post('/:uuid/mark-complete', ctrl.markStandaloneLessonComplete);
|
||||
|
||||
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,28 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* 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
|
||||
* DELETE /api/client/notifications/clear-all — delete all notifications
|
||||
*
|
||||
* 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, stickyAnnouncement, markSeen, markAllSeen, clearAll } = require('../../controllers/client/notification.controller');
|
||||
|
||||
router.get('/', list);
|
||||
router.get('/unseen', unseenCount);
|
||||
router.get('/sticky', stickyAnnouncement);
|
||||
router.patch('/seen-all', markAllSeen);
|
||||
router.patch('/:id/seen', markSeen);
|
||||
router.delete('/clear-all', clearAll);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,30 @@
|
||||
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);
|
||||
|
||||
// Promo code validation
|
||||
router.post('/promos/validate', ctrl.validatePromo);
|
||||
|
||||
// Checkout
|
||||
router.post('/checkout/order', ctrl.createOrder);
|
||||
router.post('/checkout/capture', ctrl.captureOrder);
|
||||
router.post('/checkout/cancel', ctrl.cancelOrder);
|
||||
router.post('/checkout/refund', ctrl.refundOrder);
|
||||
|
||||
// My payments
|
||||
router.get ('/me/payments', ctrl.getMyPayments);
|
||||
|
||||
// Tier categories (public — for course badge display)
|
||||
router.get ('/categories', ctrl.getCategories);
|
||||
|
||||
// System badges (public read for profile display)
|
||||
router.get ('/system-badges', ctrl.getSystemBadges);
|
||||
|
||||
module.exports = router;
|
||||
@@ -0,0 +1,26 @@
|
||||
/***********************************************************************************************************************************************************************
|
||||
* File Name: units.routes.js (client)
|
||||
* Type of Program: Router
|
||||
* Description: Standalone Unit consumption — Units run independently of Courses.
|
||||
*
|
||||
* GET /client/units → learner-facing unit library
|
||||
* GET /client/units/:uuid → unit metadata
|
||||
* GET /client/units/:uuid/lessons → unit + ALL lesson data
|
||||
* GET /client/units/:uuid/quiz → quiz (answers stripped)
|
||||
* POST /client/units/:uuid/quiz/:quizId/submit → graded attempt (no course context)
|
||||
*
|
||||
* Author: Kenneth Obsequio (@lash0000)
|
||||
* Date Created: Jul. 7, 2026
|
||||
***********************************************************************************************************************************************************************/
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const ctrl = require('../../controllers/client/units.controller');
|
||||
|
||||
router.get('/', ctrl.getUnits);
|
||||
router.get('/:uuid/lessons', ctrl.getLessonsByUnitUuid);
|
||||
router.get('/:uuid/quiz', ctrl.getUnitQuiz);
|
||||
router.patch('/:uuid/quiz/:quizId/draft', ctrl.saveUnitQuizDraft);
|
||||
router.post('/:uuid/quiz/:quizId/submit', ctrl.submitUnitQuiz);
|
||||
router.get('/:uuid', ctrl.getUnitByUuid);
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user