Skip to content

Commit 098a782

Browse files
authored
Merge pull request #27 from sandbox-science/enrollment
Enrollment
2 parents 724f072 + 210043e commit 098a782

File tree

3 files changed

+177
-4
lines changed

3 files changed

+177
-4
lines changed

backend/internal/handlers/courses.go

Lines changed: 91 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,7 +61,7 @@ func Course(c *fiber.Ctx) error {
6161
Preload("Modules").
6262
Preload("Modules.Content", func(db *gorm.DB) *gorm.DB {
6363
return db.Order("id ASC")
64-
}).
64+
}).
6565
Preload("Tags").
6666
Where("id = ?", courseID).
6767
First(&course).Error; err != nil {
@@ -196,6 +196,30 @@ func CreateCourse(c *fiber.Ctx) error {
196196
})
197197
}
198198

199+
// Returns a true if user is enrolled in course
200+
func IsEnrolled(c *fiber.Ctx) error {
201+
course_id := c.Params("course_id")
202+
user_id := c.Params("user_id")
203+
204+
var count int64
205+
206+
if err := database.DB.Table("enrollment").Where("account_id = ? AND course_id = ?", user_id, course_id).Count(&count).Error; err != nil {
207+
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Error retrieving courses"})
208+
}
209+
210+
if count > 0 {
211+
return c.JSON(fiber.Map{
212+
"message": "User is enrolled in this course",
213+
"isEnrolled": true,
214+
})
215+
} else {
216+
return c.JSON(fiber.Map{
217+
"message": "User is not enrolled in this course",
218+
"isEnrolled": false,
219+
})
220+
}
221+
}
222+
199223
// Enroll user into course
200224
func Enroll(c *fiber.Ctx) error {
201225
user_id := c.Params("user_id")
@@ -223,6 +247,70 @@ func Enroll(c *fiber.Ctx) error {
223247
})
224248
}
225249

250+
// Uneroll user into course
251+
func Unenroll(c *fiber.Ctx) error {
252+
user_id := c.Params("user_id")
253+
course_id := c.Params("course_id")
254+
255+
var user entity.Account
256+
// Check if the user exists
257+
if err := database.DB.Where("id = ?", user_id).First(&user).Error; err != nil {
258+
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
259+
}
260+
261+
var course entity.Course
262+
// Check if the course exists
263+
if err := database.DB.Where("id = ?", course_id).First(&course).Error; err != nil {
264+
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Course not found"})
265+
}
266+
267+
// Check if the user is enrolled in the course
268+
if err := database.DB.Model(&user).Association("Courses").Find(&course); err != nil {
269+
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User is not enrolled in this course"})
270+
}
271+
272+
// Unenroll user into course
273+
if err := database.DB.Model(&user).Association("Courses").Delete(&course); err != nil {
274+
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Error unenrolling into course"})
275+
}
276+
277+
return c.JSON(fiber.Map{
278+
"message": fmt.Sprintf("Successfully unenrolled user id %d in course %s", user.ID, course.Title),
279+
})
280+
}
281+
282+
func CheckEnrollmentStatus(c *fiber.Ctx) error {
283+
user_id := c.Params("user_id")
284+
course_id := c.Params("course_id")
285+
286+
var user entity.Account
287+
// Check if the user exists
288+
if err := database.DB.Where("id = ?", user_id).First(&user).Error; err != nil {
289+
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "User not found"})
290+
}
291+
292+
var course entity.Course
293+
// Check if the course exists
294+
if err := database.DB.Where("id = ?", course_id).First(&course).Error; err != nil {
295+
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Course not found"})
296+
}
297+
298+
// Check if the user is enrolled in the course
299+
isEnrolled := false
300+
if err := database.DB.Table("enrollment").
301+
Where("account_id = ? AND course_id = ?", user_id, course_id).
302+
Select("count(*) > 0").
303+
Scan(&isEnrolled).Error; err != nil {
304+
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Error checking enrollment status"})
305+
}
306+
307+
// Return the enrollment status
308+
return c.JSON(fiber.Map{
309+
"message": "Enrollment status checked successfully",
310+
"isEnrolled": isEnrolled,
311+
})
312+
}
313+
226314
// Create a module inside a course
227315
func CreateModule(c *fiber.Ctx) error {
228316
creator_id, err := strconv.Atoi(c.Params("creator_id"))
@@ -372,7 +460,7 @@ func EditContent(c *fiber.Ctx) error {
372460
if err := os.Remove("./content" + path); err != nil {
373461
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()})
374462
}
375-
} else if !os.IsNotExist(err){
463+
} else if !os.IsNotExist(err) {
376464
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()})
377465
}
378466

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

backend/internal/router/route.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -91,5 +91,7 @@ func SetupRoutes(app *fiber.App) {
9191
app.Post("/delete-file/:creator_id/:content_id", handlers.DeleteFile)
9292
app.Post("/edit-content/:creator_id/:content_id", handlers.EditContent)
9393
app.Post("/edit-thumbnail/:creator_id/:course_id", handlers.EditThumbnail)
94+
app.Get("/is-enrolled/:user_id/:course_id", handlers.IsEnrolled)
9495
app.Post("/enroll/:user_id/:course_id", handlers.Enroll)
96+
app.Delete("/unenroll/:user_id/:course_id", handlers.Unenroll)
9597
}

frontend/src/components/pages/Course.js

Lines changed: 84 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ export function Course() {
1010
const [userInfo, setUserInfo] = useState(null);
1111
const [error, setError] = useState(null);
1212
const [file, setFile] = useState(null);
13+
const [isEnrolled, setIsEnrolled] = useState(null);
1314
const [newContentName, setNewContentName] = useState({
1415
title: "",
1516
});
@@ -118,6 +119,67 @@ export function Course() {
118119
}
119120
};
120121

122+
const handleEnroll = async () => {
123+
const userId = Cookies.get('userId');
124+
125+
if (!userId) {
126+
setError('User ID not found');
127+
return;
128+
}
129+
130+
try {
131+
const response = await fetch(`http://localhost:4000/Enroll/${userId}/${courseID}`, {
132+
method: "POST",
133+
headers: {
134+
"Content-Type": "application/json",
135+
},
136+
});
137+
const data = await response.json();
138+
if (response.ok) {
139+
Notiflix.Notify.success("Succesfully enrolled in the course!");
140+
setTimeout(() => {
141+
window.location.reload();
142+
}, 500);
143+
} else {
144+
Notiflix.Notify.failure(data.message || "Enrollment failed");
145+
}
146+
} catch (error) {
147+
Notiflix.Notify.failure("Error occurred during enrollment");
148+
}
149+
150+
};
151+
152+
const handleUnenroll = async () => {
153+
const userId = Cookies.get('userId');
154+
155+
if (!userId) {
156+
setError('User ID not found');
157+
return;
158+
}
159+
160+
try {
161+
const response = await fetch(`http://localhost:4000/Unenroll/${userId}/${courseID}`, {
162+
method: "DELETE",
163+
headers: {
164+
"Content-Type": "application/json",
165+
},
166+
});
167+
const data = await response.json();
168+
if (response.ok) {
169+
Notiflix.Notify.success("Succesfully unenrolled in the course!");
170+
setTimeout(() => {
171+
window.location.reload();
172+
}, 500);
173+
} else {
174+
Notiflix.Notify.failure(data.message || "Unenrollment failed");
175+
}
176+
} catch (error) {
177+
Notiflix.Notify.failure("Error occurred during Unenrollment");
178+
}
179+
180+
};
181+
182+
121183
useEffect(() => {
122184
const userId = Cookies.get('userId');
123185

@@ -150,12 +212,25 @@ export function Course() {
150212
.catch((error) => setError(error.message));
151213
}
152214

215+
async function fetchIsEnrolled() {
216+
await fetch(`http://localhost:4000/is-enrolled/${userId}/${courseID}`)
217+
.then((response) => {
218+
if (!response.ok) {
219+
throw new Error('Network response was not ok');
220+
}
221+
return response.json();
222+
})
223+
.then((data) => setIsEnrolled(data.isEnrolled))
224+
.catch((error) => setError(error.message));
225+
}
226+
153227
fetchCourse();
154228
fetchUser();
229+
fetchIsEnrolled();
155230
}, [courseID]);
156231

157232
if (error) return <p>Error: {error}</p>;
158-
if (!courseInfo || !userInfo) return <p>Loading...</p>;
233+
if (!courseInfo || !userInfo || isEnrolled === null) return <p>Loading...</p>;
159234

160235
var moduleList = [];
161236
//Push all modules into moduleList
@@ -248,6 +323,14 @@ export function Course() {
248323
<div className="flex flex-1 justify-end flex-wrap gap-5 ml-20">
249324
{editThumbnail}
250325
{createButton}
326+
{userInfo.role === "student" && (
327+
<button
328+
onClick={isEnrolled ? handleUnenroll : handleEnroll}
329+
className={`p-2 rounded shadow text-white font-semibold ${isEnrolled ? 'bg-red-500 hover:bg-red-700' : 'bg-green-500 hover:bg-green-700'}`}
330+
>
331+
{isEnrolled ? 'Unenroll from Course' : 'Enroll in Course'}
332+
</button>
333+
)}
251334
</div>
252335
</div>
253336
<div className="grid grid-cols-1 gap-6">

0 commit comments

Comments
 (0)