Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
94 changes: 91 additions & 3 deletions backend/internal/handlers/courses.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func Course(c *fiber.Ctx) error {
Preload("Modules").
Preload("Modules.Content", func(db *gorm.DB) *gorm.DB {
return db.Order("id ASC")
}).
}).
Preload("Tags").
Where("id = ?", courseID).
First(&course).Error; err != nil {
Expand Down Expand Up @@ -196,6 +196,30 @@ func CreateCourse(c *fiber.Ctx) error {
})
}

// Returns a true if user is enrolled in course
func IsEnrolled(c *fiber.Ctx) error {
course_id := c.Params("course_id")
user_id := c.Params("user_id")

var count int64

if err := database.DB.Table("enrollment").Where("account_id = ? AND course_id = ?", user_id, course_id).Count(&count).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Error retrieving courses"})
}

if count > 0 {
return c.JSON(fiber.Map{
"message": "User is enrolled in this course",
"isEnrolled": true,
})
} else {
return c.JSON(fiber.Map{
"message": "User is not enrolled in this course",
"isEnrolled": false,
})
}
}

// Enroll user into course
func Enroll(c *fiber.Ctx) error {
user_id := c.Params("user_id")
Expand Down Expand Up @@ -223,6 +247,70 @@ func Enroll(c *fiber.Ctx) error {
})
}

// Uneroll user into course
func Unenroll(c *fiber.Ctx) error {
user_id := c.Params("user_id")
course_id := c.Params("course_id")

var user entity.Account
// Check if the user exists
if err := database.DB.Where("id = ?", user_id).First(&user).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}

var course entity.Course
// Check if the course exists
if err := database.DB.Where("id = ?", course_id).First(&course).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Course not found"})
}

// Check if the user is enrolled in the course
if err := database.DB.Model(&user).Association("Courses").Find(&course); err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User is not enrolled in this course"})
}

// Unenroll user into course
if err := database.DB.Model(&user).Association("Courses").Delete(&course); err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Error unenrolling into course"})
}

return c.JSON(fiber.Map{
"message": fmt.Sprintf("Successfully unenrolled user id %d in course %s", user.ID, course.Title),
})
}

func CheckEnrollmentStatus(c *fiber.Ctx) error {
user_id := c.Params("user_id")
course_id := c.Params("course_id")

var user entity.Account
// Check if the user exists
if err := database.DB.Where("id = ?", user_id).First(&user).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
}

var course entity.Course
// Check if the course exists
if err := database.DB.Where("id = ?", course_id).First(&course).Error; err != nil {
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Course not found"})
}

// Check if the user is enrolled in the course
isEnrolled := false
if err := database.DB.Table("enrollment").
Where("account_id = ? AND course_id = ?", user_id, course_id).
Select("count(*) > 0").
Scan(&isEnrolled).Error; err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Error checking enrollment status"})
}

// Return the enrollment status
return c.JSON(fiber.Map{
"message": "Enrollment status checked successfully",
"isEnrolled": isEnrolled,
})
}

// Create a module inside a course
func CreateModule(c *fiber.Ctx) error {
creator_id, err := strconv.Atoi(c.Params("creator_id"))
Expand Down Expand Up @@ -372,7 +460,7 @@ func EditContent(c *fiber.Ctx) error {
if err := os.Remove("./content" + path); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()})
}
} else if !os.IsNotExist(err){
} else if !os.IsNotExist(err) {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()})
}

Expand Down Expand Up @@ -480,7 +568,7 @@ func EditThumbnail(c *fiber.Ctx) error {
if err := os.Remove(fmt.Sprintf("./content/%d/thumbnail.png", course_id)); err != nil {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()})
}
} else if !os.IsNotExist(err){
} else if !os.IsNotExist(err) {
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()})
}

Expand Down
2 changes: 2 additions & 0 deletions backend/internal/router/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -91,5 +91,7 @@ func SetupRoutes(app *fiber.App) {
app.Post("/delete-file/:creator_id/:content_id", handlers.DeleteFile)
app.Post("/edit-content/:creator_id/:content_id", handlers.EditContent)
app.Post("/edit-thumbnail/:creator_id/:course_id", handlers.EditThumbnail)
app.Get("/is-enrolled/:user_id/:course_id", handlers.IsEnrolled)
app.Post("/enroll/:user_id/:course_id", handlers.Enroll)
app.Delete("/unenroll/:user_id/:course_id", handlers.Unenroll)
}
85 changes: 84 additions & 1 deletion frontend/src/components/pages/Course.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ export function Course() {
const [userInfo, setUserInfo] = useState(null);
const [error, setError] = useState(null);
const [file, setFile] = useState(null);
const [isEnrolled, setIsEnrolled] = useState(null);
const [newContentName, setNewContentName] = useState({
title: "",
});
Expand Down Expand Up @@ -118,6 +119,67 @@ export function Course() {
}
};

const handleEnroll = async () => {
const userId = Cookies.get('userId');

if (!userId) {
setError('User ID not found');
return;
}

try {
const response = await fetch(`http://localhost:4000/Enroll/${userId}/${courseID}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
if (response.ok) {
Notiflix.Notify.success("Succesfully enrolled in the course!");
setTimeout(() => {
window.location.reload();
}, 500);
} else {
Notiflix.Notify.failure(data.message || "Enrollment failed");
}
} catch (error) {
Notiflix.Notify.failure("Error occurred during enrollment");
}

};

const handleUnenroll = async () => {
const userId = Cookies.get('userId');

if (!userId) {
setError('User ID not found');
return;
}

try {
const response = await fetch(`http://localhost:4000/Unenroll/${userId}/${courseID}`, {
method: "DELETE",
headers: {
"Content-Type": "application/json",
},
});
const data = await response.json();
if (response.ok) {
Notiflix.Notify.success("Succesfully unenrolled in the course!");
setTimeout(() => {
window.location.reload();
}, 500);
} else {
Notiflix.Notify.failure(data.message || "Unenrollment failed");
}
} catch (error) {
Notiflix.Notify.failure("Error occurred during Unenrollment");
}

};


useEffect(() => {
const userId = Cookies.get('userId');

Expand Down Expand Up @@ -150,12 +212,25 @@ export function Course() {
.catch((error) => setError(error.message));
}

async function fetchIsEnrolled() {
await fetch(`http://localhost:4000/is-enrolled/${userId}/${courseID}`)
.then((response) => {
if (!response.ok) {
throw new Error('Network response was not ok');
}
return response.json();
})
.then((data) => setIsEnrolled(data.isEnrolled))
.catch((error) => setError(error.message));
}

fetchCourse();
fetchUser();
fetchIsEnrolled();
}, [courseID]);

if (error) return <p>Error: {error}</p>;
if (!courseInfo || !userInfo) return <p>Loading...</p>;
if (!courseInfo || !userInfo || isEnrolled === null) return <p>Loading...</p>;

var moduleList = [];
//Push all modules into moduleList
Expand Down Expand Up @@ -248,6 +323,14 @@ export function Course() {
<div className="flex flex-1 justify-end flex-wrap gap-5 ml-20">
{editThumbnail}
{createButton}
{userInfo.role === "student" && (
<button
onClick={isEnrolled ? handleUnenroll : handleEnroll}
className={`p-2 rounded shadow text-white font-semibold ${isEnrolled ? 'bg-red-500 hover:bg-red-700' : 'bg-green-500 hover:bg-green-700'}`}
>
{isEnrolled ? 'Unenroll from Course' : 'Enroll in Course'}
</button>
)}
</div>
</div>
<div className="grid grid-cols-1 gap-6">
Expand Down
Loading