courseworx/backend/routes/assignments.js

384 lines
No EOL
11 KiB
JavaScript

const express = require('express');
const { body, validationResult } = require('express-validator');
const { Assignment, Course, User } = require('../models');
const { auth, requireTrainer } = require('../middleware/auth');
const router = express.Router();
// @route POST /api/assignments
// @desc Create new assignment (Trainer or Super Admin)
// @access Private
router.post('/', [
auth,
requireTrainer,
body('title').isLength({ min: 3, max: 200 }),
body('description').notEmpty(),
body('courseId').isUUID(),
body('maxScore').isInt({ min: 1, max: 1000 }),
body('weight').isFloat({ min: 0, max: 100 }),
body('type').isIn(['homework', 'quiz', 'project', 'exam', 'presentation']),
body('dueDate').optional().isISO8601()
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const {
title,
description,
instructions,
courseId,
dueDate,
maxScore,
weight,
type,
isRequired,
allowLateSubmission,
latePenalty,
attachments,
rubric
} = req.body;
// Check if course exists and user has permission
const course = await Course.findByPk(courseId);
if (!course) {
return res.status(404).json({ error: 'Course not found.' });
}
if (req.user.role !== 'super_admin' && course.trainerId !== req.user.id) {
return res.status(403).json({ error: 'Not authorized to create assignments for this course.' });
}
const assignment = await Assignment.create({
title,
description,
instructions,
courseId,
trainerId: req.user.id,
dueDate,
maxScore,
weight,
type,
isRequired: isRequired !== undefined ? isRequired : true,
allowLateSubmission: allowLateSubmission !== undefined ? allowLateSubmission : false,
latePenalty,
attachments: attachments || [],
rubric: rubric || null,
isPublished: req.user.role === 'super_admin' // Auto-publish for super admin
});
const assignmentWithDetails = await Assignment.findByPk(assignment.id, {
include: [
{
model: Course,
as: 'course',
attributes: ['id', 'title']
},
{
model: User,
as: 'trainer',
attributes: ['id', 'firstName', 'lastName']
}
]
});
res.status(201).json({
message: 'Assignment created successfully.',
assignment: assignmentWithDetails
});
} catch (error) {
console.error('Create assignment error:', error);
res.status(500).json({ error: 'Server error.' });
}
});
// @route GET /api/assignments/course/:courseId
// @desc Get assignments for a specific course
// @access Private
router.get('/course/:courseId', auth, async (req, res) => {
try {
const { courseId } = req.params;
const { type, isPublished = true, page = 1, limit = 10 } = req.query;
const offset = (page - 1) * limit;
// Check if user can access this course
const course = await Course.findByPk(courseId);
if (!course) {
return res.status(404).json({ error: 'Course not found.' });
}
const canAccess = req.user.role === 'super_admin' ||
course.trainerId === req.user.id ||
await course.hasEnrollment(req.user.id);
if (!canAccess) {
return res.status(403).json({ error: 'Not authorized to view assignments for this course.' });
}
const whereClause = { courseId };
if (type) whereClause.type = type;
if (isPublished !== undefined) whereClause.isPublished = isPublished === 'true';
const { count, rows: assignments } = await Assignment.findAndCountAll({
where: whereClause,
include: [
{
model: User,
as: 'trainer',
attributes: ['id', 'firstName', 'lastName', 'avatar']
}
],
order: [['createdAt', 'DESC']],
limit: parseInt(limit),
offset: parseInt(offset)
});
res.json({
assignments,
pagination: {
currentPage: parseInt(page),
totalPages: Math.ceil(count / limit),
totalItems: count,
itemsPerPage: parseInt(limit)
}
});
} catch (error) {
console.error('Get assignments error:', error);
res.status(500).json({ error: 'Server error.' });
}
});
// @route GET /api/assignments/:id
// @desc Get assignment by ID
// @access Private
router.get('/:id', auth, async (req, res) => {
try {
const assignment = await Assignment.findByPk(req.params.id, {
include: [
{
model: Course,
as: 'course',
include: [
{
model: User,
as: 'trainer',
attributes: ['id', 'firstName', 'lastName', 'avatar']
}
]
},
{
model: User,
as: 'trainer',
attributes: ['id', 'firstName', 'lastName', 'avatar']
}
]
});
if (!assignment) {
return res.status(404).json({ error: 'Assignment not found.' });
}
// Check if user can access this assignment
const canAccess = req.user.role === 'super_admin' ||
assignment.trainerId === req.user.id ||
await assignment.course.hasEnrollment(req.user.id);
if (!canAccess) {
return res.status(403).json({ error: 'Not authorized to view this assignment.' });
}
res.json({ assignment });
} catch (error) {
console.error('Get assignment error:', error);
res.status(500).json({ error: 'Server error.' });
}
});
// @route PUT /api/assignments/:id
// @desc Update assignment (Owner or Super Admin)
// @access Private
router.put('/:id', [
auth,
body('title').optional().isLength({ min: 3, max: 200 }),
body('description').optional().notEmpty(),
body('maxScore').optional().isInt({ min: 1, max: 1000 }),
body('weight').optional().isFloat({ min: 0, max: 100 }),
body('type').optional().isIn(['homework', 'quiz', 'project', 'exam', 'presentation']),
body('dueDate').optional().isISO8601()
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const assignment = await Assignment.findByPk(req.params.id);
if (!assignment) {
return res.status(404).json({ error: 'Assignment not found.' });
}
// Check permissions
if (req.user.role !== 'super_admin' && assignment.trainerId !== req.user.id) {
return res.status(403).json({ error: 'Not authorized to update this assignment.' });
}
const updateData = req.body;
await assignment.update(updateData);
const updatedAssignment = await Assignment.findByPk(assignment.id, {
include: [
{
model: Course,
as: 'course',
attributes: ['id', 'title']
},
{
model: User,
as: 'trainer',
attributes: ['id', 'firstName', 'lastName']
}
]
});
res.json({
message: 'Assignment updated successfully.',
assignment: updatedAssignment
});
} catch (error) {
console.error('Update assignment error:', error);
res.status(500).json({ error: 'Server error.' });
}
});
// @route DELETE /api/assignments/:id
// @desc Delete assignment (Owner or Super Admin)
// @access Private
router.delete('/:id', auth, async (req, res) => {
try {
const assignment = await Assignment.findByPk(req.params.id);
if (!assignment) {
return res.status(404).json({ error: 'Assignment not found.' });
}
// Check permissions
if (req.user.role !== 'super_admin' && assignment.trainerId !== req.user.id) {
return res.status(403).json({ error: 'Not authorized to delete this assignment.' });
}
await assignment.destroy();
res.json({ message: 'Assignment deleted successfully.' });
} catch (error) {
console.error('Delete assignment error:', error);
res.status(500).json({ error: 'Server error.' });
}
});
// @route PUT /api/assignments/:id/publish
// @desc Publish/unpublish assignment (Owner or Super Admin)
// @access Private
router.put('/:id/publish', [
auth,
body('isPublished').isBoolean()
], async (req, res) => {
try {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
const assignment = await Assignment.findByPk(req.params.id);
if (!assignment) {
return res.status(404).json({ error: 'Assignment not found.' });
}
// Check permissions
if (req.user.role !== 'super_admin' && assignment.trainerId !== req.user.id) {
return res.status(403).json({ error: 'Not authorized to update this assignment.' });
}
await assignment.update({ isPublished: req.body.isPublished });
res.json({
message: `Assignment ${req.body.isPublished ? 'published' : 'unpublished'} successfully.`,
assignment: {
id: assignment.id,
title: assignment.title,
isPublished: assignment.isPublished
}
});
} catch (error) {
console.error('Publish assignment error:', error);
res.status(500).json({ error: 'Server error.' });
}
});
// @route GET /api/assignments/my
// @desc Get user's assignments (as trainee)
// @access Private
router.get('/my', auth, async (req, res) => {
try {
const { status, page = 1, limit = 10 } = req.query;
const offset = (page - 1) * limit;
// Get courses where user is enrolled
const enrollments = await require('../models').Enrollment.findAll({
where: { userId: req.user.id, status: 'active' },
include: [
{
model: Course,
as: 'course',
include: [
{
model: Assignment,
as: 'assignments',
where: { isPublished: true },
required: false
}
]
}
]
});
const courseIds = enrollments.map(enrollment => enrollment.courseId);
const whereClause = { courseId: courseIds };
if (status) whereClause.status = status;
const { count, rows: assignments } = await Assignment.findAndCountAll({
where: whereClause,
include: [
{
model: Course,
as: 'course',
attributes: ['id', 'title', 'thumbnail']
},
{
model: User,
as: 'trainer',
attributes: ['id', 'firstName', 'lastName', 'avatar']
}
],
order: [['dueDate', 'ASC']],
limit: parseInt(limit),
offset: parseInt(offset)
});
res.json({
assignments,
pagination: {
currentPage: parseInt(page),
totalPages: Math.ceil(count / limit),
totalItems: count,
itemsPerPage: parseInt(limit)
}
});
} catch (error) {
console.error('Get my assignments error:', error);
res.status(500).json({ error: 'Server error.' });
}
});
module.exports = router;