Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
83 commits
Select commit Hold shift + click to select a range
4fcd58a
Using concurrently to run both the frontend and backend at the same t…
aroby1 Feb 10, 2026
38d0b8a
Fix: Express 5 wildcard route syntax
aroby1 Feb 10, 2026
608c620
Fix: Use middleware for React routing in Express 5
aroby1 Feb 10, 2026
becb85c
Fix: Remove all localhost references
aroby1 Feb 10, 2026
e5051cf
Fix: Remove all localhost references
aroby1 Feb 10, 2026
1f09430
Fix: Remove all localhost references
aroby1 Feb 10, 2026
592f9fd
Fix: Remove all localhost references
aroby1 Feb 10, 2026
38884dd
Getting the my courses working.
aroby1 Feb 10, 2026
a5de0eb
Getting the my courses working.
aroby1 Feb 10, 2026
c5f062c
Getting the my courses working.
aroby1 Feb 10, 2026
0835f59
Getting the my courses working.
aroby1 Feb 10, 2026
8bac9f9
Getting the my courses working.
aroby1 Feb 10, 2026
9737378
Getting the my courses working.
aroby1 Feb 10, 2026
2745470
Getting the my courses working.
aroby1 Feb 10, 2026
9c695f3
Getting the my courses working.
aroby1 Feb 10, 2026
ae5dbce
Getting the my courses working.
aroby1 Feb 10, 2026
65debf7
Getting the my courses working.
aroby1 Feb 10, 2026
7dfae92
Getting the my courses working.
aroby1 Feb 10, 2026
3b10174
Working on getting individual courses working.
aroby1 Feb 10, 2026
5cf9cc0
Working on getting individual courses working.
aroby1 Feb 10, 2026
129962f
Working on getting individual courses working.
aroby1 Feb 10, 2026
033b89d
Working on getting individual courses working.
aroby1 Feb 11, 2026
e53a179
Working on getting individual courses working.
aroby1 Feb 11, 2026
afeceed
Working on getting individual courses working.
aroby1 Feb 11, 2026
a93a6f3
Working on getting individual courses working.
aroby1 Feb 11, 2026
a16b4bc
Working on getting individual courses working.
aroby1 Feb 11, 2026
fb17e14
Update Create Tutorial page to match Create Course page and standardi…
danielmolinas Feb 11, 2026
03d99f5
Update filtering/sorting and update tutorial card
danielmolinas Feb 11, 2026
73e3e33
Add instructor to tutorials and replace card/search emojis with Feath…
danielmolinas Feb 11, 2026
0294322
Normalize course duration display to consistently format Week/Weeks r…
danielmolinas Feb 11, 2026
26e7692
Make sure thumbnails work for CreateTutorial
danielmolinas Feb 11, 2026
3bdb5f7
Working on new createCourse that is based on example from Alina.
aroby1 Feb 12, 2026
5f0cb85
Working on new createCourse that is based on example from Alina.
aroby1 Feb 12, 2026
9b8fb8f
Working on new createCourse that is based on example from Alina.
aroby1 Feb 12, 2026
b4f452d
Working on new createCourse that is based on example from Alina.
aroby1 Feb 13, 2026
105245a
Merge remote-tracking branch 'origin/react-main' into react-main
danielmolinas Feb 13, 2026
6d5d1d5
Fix small css issue in the Create page.
danielmolinas Feb 13, 2026
2c703f2
Adding Camera Functionality
AidanCaughey1 Feb 16, 2026
561a435
Update CONTRIBUTING.md
AidanCaughey1 Feb 16, 2026
be5ca04
Fixed
AidanCaughey1 Feb 16, 2026
98e499d
Merge branch 'react-main' of https://github.com/aroby1/CS46X-UniFreel…
AidanCaughey1 Feb 16, 2026
a93a7c4
Made it so preview will appear
AidanCaughey1 Feb 16, 2026
a518bbf
removed notation
AidanCaughey1 Feb 16, 2026
aa29fee
Testing Vimeo functionality to embedded videos
baronbaker Feb 16, 2026
51eed0d
Second test
baronbaker Feb 16, 2026
54757ce
More testing
baronbaker Feb 16, 2026
fd76252
Working on Instructor Dashboard.
aroby1 Feb 20, 2026
a4393b1
Working on Instructor Dashboard.
aroby1 Feb 20, 2026
b34a2e7
Working on Instructor Dashboard.
aroby1 Feb 20, 2026
0787853
Update UI for TutorialDetails
danielmolinas Feb 23, 2026
8b2452b
Updating links and wokring on instructor dashboard.
aroby1 Feb 25, 2026
e5c32bf
Have to merge to pull.
aroby1 Feb 25, 2026
fdecf50
Updating links and wokring on instructor dashboard.
aroby1 Feb 26, 2026
847362d
Updating links and wokring on instructor dashboard.
aroby1 Feb 26, 2026
adb4fb5
Working on the edit functionality.
aroby1 Feb 26, 2026
9c0ff74
Working on the edit functionality.
aroby1 Feb 26, 2026
b481c6f
Added debugging messages to find why edit not working.
aroby1 Feb 26, 2026
80e5048
Added debugging messages to find why edit not working.
aroby1 Feb 26, 2026
328a6e6
Working version, going to start merging wish me luck.
aroby1 Feb 26, 2026
645cac4
Implement Zoom seminar join flow
danielmolinas Feb 27, 2026
57de99b
Remove extra link
danielmolinas Feb 27, 2026
0ac3167
Added Camera Functionality to Seminars Page
AidanCaughey1 Feb 27, 2026
26e158e
format seminar duration into hours and minutes
danielmolinas Mar 2, 2026
e86039e
New Test Suite
AidanCaughey1 Mar 3, 2026
9832279
Updated ci suite
AidanCaughey1 Mar 3, 2026
fa069fa
Update ci.yml
AidanCaughey1 Mar 3, 2026
b52de99
Updated seminar Zoom flow to use same-tab join/leave navigation and c…
danielmolinas Mar 4, 2026
926fecd
Update Duration label in CreateSeminar
danielmolinas Mar 4, 2026
ca41d12
Updated Schema for Progress Tracking
AidanCaughey1 Mar 4, 2026
fee8c5b
chore: remove unused local Zoom SDK assets from public folder
danielmolinas Mar 5, 2026
1e1738e
Merge branch 'react-main' into ZoomIntegration
danielmolinas Mar 5, 2026
5fd5d1f
Merge pull request #59 from aroby1/ZoomIntegration
danielmolinas Mar 5, 2026
11efaf4
Learning Tracking
AidanCaughey1 Mar 6, 2026
bb28845
Merge branch 'react-main' of https://github.com/aroby1/CS46X-UniFreel…
AidanCaughey1 Mar 6, 2026
30b81a9
Updated Logic + Schema
AidanCaughey1 Mar 6, 2026
61fe9a5
Fetch Logic
AidanCaughey1 Mar 6, 2026
bbf5bc9
Adding console logs
AidanCaughey1 Mar 6, 2026
3ec2234
Updating the right file
AidanCaughey1 Mar 6, 2026
cd0a082
attempting to fix images
AidanCaughey1 Mar 6, 2026
161362f
Commit with most functionalites working.
aroby1 Mar 6, 2026
e33bbed
Multiple
AidanCaughey1 Mar 6, 2026
7668130
Current implementation that has instructor dashboard and courses impl…
aroby1 Mar 12, 2026
47e5d85
Merge react-main into fully-merged
YukiTheProgrammer Mar 12, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions backend/config/cloudinary.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const cloudinary = require("cloudinary").v2;

cloudinary.config({
cloud_name: process.env.CLOUDINARY_CLOUD_NAME,
api_key: process.env.CLOUDINARY_API_KEY,
api_secret: process.env.CLOUDINARY_API_SECRET,
});

module.exports = cloudinary;
12 changes: 8 additions & 4 deletions backend/config/db.js
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
// config/db.js
const mongoose = require("mongoose");

if (!process.env.MONGO_URI) {
throw new Error("Missing MONGO_URI environment variable");
}

const connectDB = async () => {
if (!process.env.MONGO_URI) {
if (process.env.NODE_ENV === "test") {
return;
}

throw new Error("Missing MONGO_URI environment variable");
}

let retries = 1;
const delay = 1000; // 1s backoff if you want to bump retries later

Expand Down
107 changes: 107 additions & 0 deletions backend/models/AssignmentSubmission.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
const mongoose = require('mongoose');

const GradeSchema = new mongoose.Schema({
points: { type: Number, required: true },
maxPoints: { type: Number, required: true },
comment: { type: String, default: '' }
}, { _id: false });

const AssignmentSubmissionSchema = new mongoose.Schema({
// Student Info
student: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
},
studentName: { type: String, required: true },
studentEmail: { type: String, required: true },

// Course Info
course: {
type: mongoose.Schema.Types.ObjectId,
ref: 'Course',
required: true
},
courseName: { type: String, required: true },

module: {
type: mongoose.Schema.Types.ObjectId,
required: true
},
moduleName: { type: String, required: true },

// Assignment Details
lessonId: { type: String, required: true }, // The generated lesson ID
assignmentTitle: { type: String, required: true },
assignmentType: {
type: String,
enum: ['question-based', 'part-based'],
default: 'part-based'
},

// OLD: Part-based assignment data
assignmentData: {
parts: [{
partNumber: Number,
title: String,
instructions: String
}],
gradingCriteria: [{
name: String,
points: Number
}]
},

// Student Submission
submittedAt: { type: Date, default: Date.now },

// OLD: Part-based answers
partAnswers: {
type: Map,
of: String,
default: {}
},

// NEW: Question-based answers
answers: {
type: Map,
of: mongoose.Schema.Types.Mixed,
default: {}
},

fileUrl: { type: String, default: '' },

// Grading
status: {
type: String,
enum: ['pending', 'graded'],
default: 'pending'
},
grades: {
type: Map,
of: GradeSchema,
default: {}
},
totalScore: { type: Number, default: 0 },
maxScore: { type: Number, required: true },
passed: { type: Boolean, default: false },
passingScore: { type: Number, default: 70 }, // percentage
overallFeedback: { type: String, default: '' },
gradedAt: { type: Date },

// Instructor
instructor: {
type: mongoose.Schema.Types.ObjectId,
ref: 'User',
required: true
}
}, {
timestamps: true
});

// Index for quick queries
AssignmentSubmissionSchema.index({ instructor: 1, status: 1 });
AssignmentSubmissionSchema.index({ student: 1, course: 1 });
AssignmentSubmissionSchema.index({ course: 1, status: 1 });

module.exports = mongoose.model('AssignmentSubmission', AssignmentSubmissionSchema);
182 changes: 167 additions & 15 deletions backend/models/CourseModel.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
const mongoose = require("mongoose");

const InstructorSchema = new mongoose.Schema({
_id: { type: mongoose.Schema.Types.ObjectId, required: true }, // ADD THIS LINE
name: { type: String, required: true },
title: { type: String, default: "" },
bio: { type: String, default: "" },
avatar: { type: String, default: "" }
avatar: { type: String, default: "" },
email: { type: String, default: "" }
}, { _id: false });

const PricingSchema = new mongoose.Schema({
Expand All @@ -22,27 +24,167 @@ const SubscriptionSchema = new mongoose.Schema({
tier: { type: String, default: "" }
}, { _id: false });

// NEW: Learning Materials Schema
const LearningMaterialsSchema = new mongoose.Schema({
readings: [{
title: { type: String },
author: { type: String },
citation: { type: String },
link: { type: String }
}],
podcasts: [{
title: { type: String },
link: { type: String }
}],
videos: [{
title: { type: String },
link: { type: String }
}]
}, { _id: false });

// NEW: Assignment Question Schema with different types
const AssignmentQuestionSchema = new mongoose.Schema({
questionNumber: { type: Number, required: true },
type: {
type: String,
enum: ['multiple-choice', 'written', 'matching', 'pdf-upload', 'true-false'],
required: true
},

// Common fields
question: { type: String, required: true },
points: { type: Number, default: 10 },

// For multiple-choice
options: [{ type: String }],
correctAnswer: { type: Number }, // index of correct option

// For matching
matchPairs: [{
left: { type: String },
right: { type: String }
}],

// For written response
wordLimit: { type: Number },
rubric: { type: String },

// For PDF upload
fileRequirements: { type: String }

}, { _id: false });

// Updated Assignment Schema
const AssignmentSchema = new mongoose.Schema({
title: { type: String, required: true },
purpose: { type: String },
instructions: { type: String },

// NEW: Question-based structure
questions: [AssignmentQuestionSchema],

// DEPRECATED: Old part-based structure (keep for backward compatibility)
parts: [{
partNumber: { type: Number },
title: { type: String },
instructions: { type: String }
}],

gradingCriteria: [{
name: { type: String },
points: { type: Number }
}],

deliverableFormat: { type: String },
totalPoints: { type: Number, default: 100 }

}, { _id: false });

// Lesson Schema (videos, assignments, quizzes within a module)
const LessonSchema = new mongoose.Schema({
type: {
type: String,
enum: ["video", "assignment", "quiz"],
required: true
},
title: { type: String, required: true },
order: { type: Number, required: true },

// For VIDEO lessons
videoUrl: { type: String, default: "" },
duration: { type: String, default: "" },

// For ASSIGNMENT lessons
assignmentType: {
type: String,
enum: ["text", "file", "both"],
default: "text"
},
instructions: { type: String, default: "" },

// For QUIZ lessons
questions: [{
question: { type: String },
questionType: {
type: String,
enum: ["multiple-choice", "short-answer"],
default: "multiple-choice"
},
options: [{ type: String }], // for multiple choice
correctAnswer: { type: String }, // answer text or option index
points: { type: Number, default: 1 }
}],
passingScore: { type: Number, default: 70 }, // percentage

}, { _id: true });

const ModuleSchema = new mongoose.Schema({
title: { type: String, required: true },
description: { type: String, default: "" },

// Content types
order: { type: Number, default: 0 },

// NEW: Academic structure
learningOutcomes: { type: [String], default: [] },
learningMaterials: { type: LearningMaterialsSchema, default: () => ({ readings: [], podcasts: [], videos: [] }) },
assignment: { type: AssignmentSchema, default: null },

// Array of lessons (kept for compatibility)
lessons: { type: [LessonSchema], default: [] },

// DEPRECATED: Old fields kept for backward compatibility
videoUrl: { type: String, default: "" },
articleContent: { type: String, default: "" },
pdfUrl: { type: String, default: "" },

// Quiz placeholder for future
hasQuiz: { type: Boolean, default: false },
quizData: { type: Object, default: null },

learningPoints: { type: [String], default: [] },
duration: { type: String, default: "" },
estimatedMinutes: { type: Number, default: 0 },
thumbnail: { type: String, default: "" },

order: { type: Number, default: 0 }
}, { _id: true });

// Final Test Schema
const FinalTestSchema = new mongoose.Schema({
title: { type: String, default: "Final Test" },
description: { type: String, default: "" },
passingScore: { type: Number, default: 70 },
questions: [{
question: { type: String, required: true },
options: [{ type: String }],
correctAnswer: { type: Number },
points: { type: Number, default: 1 }
}],
timeLimit: { type: Number, default: 0 },
}, { _id: false });

// Course Badge Schema
const BadgeSchema = new mongoose.Schema({
name: { type: String, default: "" },
description: { type: String, default: "" },
imageUrl: { type: String, default: "" },
color: { type: String, default: "#4F46E5" }
}, { _id: false });

const CourseSchema = new mongoose.Schema({
title: { type: String, required: true },
description: { type: String, required: true },
Expand Down Expand Up @@ -75,15 +217,23 @@ const CourseSchema = new mongoose.Schema({

learningPoints: { type: [String], default: [] },
modules: { type: [ModuleSchema], default: [] },


finalTest: {
type: FinalTestSchema,
default: null
},

badge: {
type: BadgeSchema,
default: () => ({})
},
},
{
timestamps: true,
}
);

CourseSchema.virtual("priceAmount").get(function () {
// 1) If pricing.amount is set, use that
if (
this.pricing &&
typeof this.pricing.amount === "number" &&
Expand All @@ -92,25 +242,27 @@ CourseSchema.virtual("priceAmount").get(function () {
return this.pricing.amount;
}

// 2) If the raw document (e.g., created via Atlas) has priceAmount, use it
if (this._doc && typeof this._doc.priceAmount === "number") {
return this._doc.priceAmount;
}

return 0;
});

// Virtual: whether the course counts as free
CourseSchema.virtual("isFree").get(function () {
const price = this.priceAmount;
return this.isLiteVersion || price === 0;
});

// Make sure virtuals show up in JSON sent to frontend
CourseSchema.virtual("totalLessons").get(function () {
return this.modules.reduce((total, module) => {
return total + (module.lessons?.length || 0);
}, 0);
});

CourseSchema.set("toJSON", { virtuals: true });
CourseSchema.set("toObject", { virtuals: true });

// Indexes to support search & filtering (optional but good to add)
CourseSchema.index({
title: "text",
description: "text",
Expand All @@ -121,4 +273,4 @@ CourseSchema.index({ category: 1 });
CourseSchema.index({ "pricing.amount": 1 });
CourseSchema.index({ createdAt: -1 });

module.exports = mongoose.model("Course", CourseSchema);
module.exports = mongoose.model("Course", CourseSchema);
Loading
Loading