Skip to content

Commit a36ccc7

Browse files
authored
Merge pull request #18 from sandbox-science/all-content
[Feature] Display all content
2 parents 57f61e0 + d903edd commit a36ccc7

File tree

4 files changed

+164
-53
lines changed

4 files changed

+164
-53
lines changed

backend/internal/handlers/courses.go

Lines changed: 59 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -48,18 +48,45 @@ func Courses(c *fiber.Ctx) error {
4848

4949
// Course function retrieves an indiviudal course from its id
5050
func Course(c *fiber.Ctx) error {
51-
course_id := c.Params("course_id")
51+
courseID := c.Params("course_id")
5252

53+
var courses []entity.Course
5354
var course entity.Course
54-
// Check if the course exists
55-
if err := database.DB.Preload("Students").Preload("Modules").Preload("Modules.Content").Preload("Tags").Where("id = ?", course_id).First(&course).Error; err != nil {
56-
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Course not found"})
57-
}
5855

59-
return c.JSON(fiber.Map{
60-
"message": "Course successfully retrieved",
61-
"course": course,
62-
})
56+
if courseID != "" {
57+
// Check if the specific course exists
58+
if err := database.DB.
59+
Preload("Students").
60+
Preload("Modules").
61+
Preload("Modules.Content").
62+
Preload("Tags").
63+
Where("id = ?", courseID).
64+
First(&course).Error; err != nil {
65+
return c.Status(fiber.StatusNotFound).JSON(fiber.Map{"error": "Course not found"})
66+
}
67+
68+
// Return the specific course
69+
return c.JSON(fiber.Map{
70+
"message": "Course successfully retrieved",
71+
"course": course,
72+
})
73+
} else {
74+
// Load all courses
75+
if err := database.DB.
76+
Preload("Students").
77+
Preload("Modules").
78+
Preload("Modules.Content").
79+
Preload("Tags").
80+
Find(&courses).Error; err != nil {
81+
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"error": "Could not load courses"})
82+
}
83+
84+
// Return all courses
85+
return c.JSON(fiber.Map{
86+
"message": "All courses successfully retrieved",
87+
"courses": courses,
88+
})
89+
}
6390
}
6491

6592
// Modules function retrieves the modules of a course
@@ -265,7 +292,7 @@ func CreateContent(c *fiber.Ctx) error {
265292
if module.Course.CreatorID != creator_id {
266293
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "User is not the course creator"})
267294
}
268-
295+
269296
// Create content database entry
270297
content := entity.Content{
271298
Title: title,
@@ -306,56 +333,56 @@ func EditContent(c *fiber.Ctx) error {
306333
if module.Course.CreatorID != creator_id {
307334
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "User is not the course creator"})
308335
}
309-
336+
310337
title := c.FormValue("title")
311338
if title != "" {
312-
if err := database.DB.Model(&content).Update("title", title).Error; err != nil{
339+
if err := database.DB.Model(&content).Update("title", title).Error; err != nil {
313340
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()})
314341
}
315342
}
316343

317344
body := c.FormValue("body")
318345
if body != "" {
319-
if err := database.DB.Model(&content).Update("body", body).Error; err != nil{
346+
if err := database.DB.Model(&content).Update("body", body).Error; err != nil {
320347
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()})
321348
}
322349
}
323-
324-
file, err := c.FormFile("file");
350+
351+
file, err := c.FormFile("file")
325352
if err != nil {
326-
if err.Error() != "there is no uploaded file associated with the given key"{
353+
if err.Error() != "there is no uploaded file associated with the given key" {
327354
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "Invalid file"})
328355
}
329356
}
330357

331358
if file != nil {
332-
var fileExtension = file.Filename[strings.LastIndex(file.Filename, "."):]
359+
var fileExtension = file.Filename[strings.LastIndex(file.Filename, "."):]
333360

334-
if err := os.MkdirAll(fmt.Sprintf("./content/%d/", module.Course.ID), 0777); err != nil{
361+
if err := os.MkdirAll(fmt.Sprintf("./content/%d/", module.Course.ID), 0777); err != nil {
335362
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()})
336363
}
337-
338-
path := fmt.Sprintf("/%d/%s", module.Course.ID, strconv.FormatUint(uint64(content.ID), 10) + fileExtension)
364+
365+
path := fmt.Sprintf("/%d/%s", module.Course.ID, strconv.FormatUint(uint64(content.ID), 10)+fileExtension)
339366

340367
//Remove previous attachment if there is one
341-
if _, err := os.Stat("./content"+path); os.IsExist(err){
342-
if err := os.Remove("./content"+path); err != nil{
368+
if _, err := os.Stat("./content" + path); os.IsExist(err) {
369+
if err := os.Remove("./content" + path); err != nil {
343370
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()})
344371
}
345372
}
346-
347-
if err := c.SaveFile(file, "./content"+path); err != nil{
373+
374+
if err := c.SaveFile(file, "./content"+path); err != nil {
348375
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()})
349376
}
350-
377+
351378
// Add the path
352-
if err := database.DB.Model(&content).Update("path", path).Error; err != nil{
379+
if err := database.DB.Model(&content).Update("path", path).Error; err != nil {
353380
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()})
354381
}
355382

356383
// Add the type
357384
filetype := file.Header.Get("Content-Type")
358-
if err := database.DB.Model(&content).Update("type", filetype).Error; err != nil{
385+
if err := database.DB.Model(&content).Update("type", filetype).Error; err != nil {
359386
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()})
360387
}
361388
}
@@ -388,19 +415,19 @@ func DeleteFile(c *fiber.Ctx) error {
388415
if module.Course.CreatorID != creator_id {
389416
return c.Status(fiber.StatusBadRequest).JSON(fiber.Map{"error": "User is not the course creator"})
390417
}
391-
418+
392419
//Delete file
393-
if _, err := os.Stat("./content"+content.Path); os.IsExist(err){
394-
if err := os.Remove("./content"+content.Path); err != nil{
420+
if _, err := os.Stat("./content" + content.Path); os.IsExist(err) {
421+
if err := os.Remove("./content" + content.Path); err != nil {
395422
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()})
396423
}
397424
}
398425

399426
//Update database entry
400-
if err := database.DB.Model(&content).Update("path", "").Error; err != nil{
427+
if err := database.DB.Model(&content).Update("path", "").Error; err != nil {
401428
return c.Status(fiber.StatusInternalServerError).JSON(fiber.Map{"message": err.Error()})
402429
}
403-
430+
404431
return c.JSON(fiber.Map{
405432
"message": "Successfully deleted file",
406433
})

backend/internal/router/route.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ func SetupRoutes(app *fiber.App) {
8181
// Routes for courses
8282
app.Get("/courses/:user_id", handlers.Courses)
8383
app.Get("/course/:course_id", handlers.Course)
84+
app.Get("/course/", handlers.Course)
8485
app.Get("/modules/:course_id", handlers.Modules)
8586
app.Get("/content/:content_id", handlers.Content)
8687
app.Get("/all-content/:module_id", handlers.AllContent)

frontend/src/components/pages/CourseDashboard.js

Lines changed: 55 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,10 @@ export function CourseDashboard() {
1111
title: "",
1212
description: "",
1313
});
14-
const [searchQuery, setSearchQuery] = useState("");
15-
const [error, setError] = useState(null);
14+
const [searchQuery, setSearchQuery] = useState("");
15+
const [error, setError] = useState(null);
16+
const [courses, setCourses] = useState([]);
17+
const [loading, setLoading] = useState(true);
1618

1719
const handleChange = (e) => {
1820
const { name, value } = e.target;
@@ -87,6 +89,21 @@ export function CourseDashboard() {
8789
.catch((error) => setError(error.message));
8890
}
8991

92+
async function fetchAllCourses() {
93+
try {
94+
const response = await fetch('http://localhost:4000/course/');
95+
if (!response.ok) {
96+
throw new Error('Network response was not ok');
97+
}
98+
const data = await response.json();
99+
setCourses(data.courses || []);
100+
setLoading(false);
101+
} catch (error) {
102+
setLoading(false);
103+
}
104+
}
105+
fetchAllCourses();
106+
90107
fetchCourses();
91108
fetchUser();
92109
}, []);
@@ -117,11 +134,11 @@ export function CourseDashboard() {
117134
);
118135
}
119136

120-
const filteredCourses = courseInfo.filter((course) =>
137+
const filteredCourses = courses.filter((course) =>
121138
course.title.toLowerCase().includes(searchQuery.toLowerCase())
122139
);
123140

124-
const courseList = filteredCourses.map((course) => (
141+
const myCourses = courseInfo.map((course) => (
125142
<a href={`/courses/${course.ID}`} key={course.ID}>
126143
<div className="bg-gray-100 p-4 rounded shadow hover:bg-gray-300">
127144
<h3 className="text-xl font-semibold truncate overflow-hidden">{course.title}</h3>
@@ -130,6 +147,15 @@ export function CourseDashboard() {
130147
</a>
131148
));
132149

150+
const allCourses = filteredCourses.map((course) => (
151+
<div key={course.ID} className="course-box">
152+
<a href={`/courses/${course.ID}`} key={course.ID}>
153+
<h3><b>{course.title}</b></h3>
154+
<p>{course.description}</p>
155+
</a>
156+
</div>
157+
))
158+
133159
return (
134160
<div className="p-6">
135161
<div className="mb-5">
@@ -141,8 +167,31 @@ export function CourseDashboard() {
141167
<SearchBar onChange={handleSearchChange} />
142168
</div>
143169
</div>
144-
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
145-
{courseList}
170+
171+
{/* My Courses Section */}
172+
<div className="mt-10">
173+
<h2 className="text-xl font-semibold mb-4">My Courses</h2>
174+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
175+
{myCourses}
176+
</div>
177+
</div>
178+
179+
{/* All Courses Section */}
180+
<div className="mt-10">
181+
<h2 className="text-xl font-semibold mb-4">All Courses</h2>
182+
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-6">
183+
<div className="course-list">
184+
{loading ? (
185+
<p>Loading courses...</p>
186+
) : filteredCourses.length > 0 ? (
187+
allCourses
188+
) : (
189+
<div className="course-box">
190+
<p>Nothing to show, yet</p>
191+
</div>
192+
)}
193+
</div>
194+
</div>
146195
</div>
147196
</div>
148197
);
Lines changed: 49 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,27 @@
1-
import React from 'react';
1+
import React, { useEffect, useState } from 'react';
22
import './homepagestyle.css';
33

44
export function Home() {
5+
const [courses, setCourses] = useState([]);
6+
const [loading, setLoading] = useState(true);
7+
8+
useEffect(() => {
9+
async function fetchCourses() {
10+
try {
11+
const response = await fetch('http://localhost:4000/course/');
12+
if (!response.ok) {
13+
throw new Error('Network response was not ok');
14+
}
15+
const data = await response.json();
16+
setCourses(data.courses || []);
17+
setLoading(false);
18+
} catch (error) {
19+
setLoading(false);
20+
}
21+
}
22+
fetchCourses();
23+
}, []);
24+
525
return (
626
<div className="p-6">
727
<section className="about">
@@ -15,34 +35,48 @@ export function Home() {
1535

1636
<section className="features">
1737
<div className="feature-box">
18-
<a href='https://github.com/sandbox-science/online-learning-platform/blob/main/README.md' target="_blankc" rel="noreferrer">
19-
<h3><b>MISSION</b></h3>
20-
<p>Learn more about our mission and vision.</p>
38+
<a href="/courses">
39+
<h3><b>COURSES</b></h3>
40+
<p>Browse through a variety of courses tailored to your interests.</p>
2141
</a>
2242
</div>
2343
<div className="feature-box">
24-
<a href='#top' target="_blankc" rel="noreferrer">
25-
<h3><b>COURSES</b></h3>
26-
<p>Browse through a variety of courses tailored to your interests.</p>
44+
<a href="https://github.com/sandbox-science/online-learning-platform/blob/main/README.md" target="_blank" rel="noreferrer">
45+
<h3><b>MISSION</b></h3>
46+
<p>Learn more about our mission and vision.</p>
2747
</a>
2848
</div>
2949
<div className="feature-box">
30-
<a href='#top' target="_blankc" rel="noreferrer">
50+
<a href="#top" target="_blank" rel="noreferrer">
3151
<h3><b>CONTACT</b></h3>
3252
<p>Reach out to us for support and inquiries.</p>
3353
</a>
3454
</div>
3555
</section>
3656

37-
<section className="popular-courses">
38-
<h2>Popular Courses</h2>
57+
<section className="popular-courses" id="course-list">
58+
{/* <h2>Popular Courses</h2> */} {/* Uncomment this when popular courses algo is implemented */}
59+
<h2>Our Courses</h2>
3960
<div className="course-list">
40-
<div className="course-box">
41-
<h3><b>Placeholder</b></h3>
42-
<p>Nothing to show, yet</p>
43-
</div>
61+
{loading ? (
62+
<p>Loading courses...</p>
63+
) : courses.length > 0 ? (
64+
courses.map((course) => (
65+
<div key={course.ID} className="course-box">
66+
<a href={`/courses/${course.ID}`} key={course.ID}>
67+
<h3><b>{course.title}</b></h3>
68+
</a>
69+
<p>{course.description}</p>
70+
</div>
71+
))
72+
) : (
73+
<div className="course-box">
74+
<h3><b>Placeholder</b></h3>
75+
<p>Nothing to show, yet</p>
76+
</div>
77+
)}
4478
</div>
4579
</section>
4680
</div>
4781
);
48-
}
82+
}

0 commit comments

Comments
 (0)