• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

classconnect-grupo3 / courses-service / 15881755031

25 Jun 2025 04:21PM UTC coverage: 86.577% (-0.1%) from 86.696%
15881755031

push

github

rovifran
fix: 404 when get course by title fails with no courses

5 of 5 new or added lines in 1 file covered. (100.0%)

6 existing lines in 1 file now uncovered.

3883 of 4485 relevant lines covered (86.58%)

0.97 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

86.11
/src/controller/courses_controller.go
1
package controller
2

3
import (
4
        "log/slog"
5
        "net/http"
6
        "slices"
7

8
        "courses-service/src/ai"
9
        "courses-service/src/model"
10
        "courses-service/src/schemas"
11
        "courses-service/src/service"
12

13
        "github.com/gin-gonic/gin"
14
)
15

16
type CourseController struct {
17
        service  service.CourseServiceInterface
18
        aiClient *ai.AiClient
19
}
20

21
func NewCourseController(service service.CourseServiceInterface, aiClient *ai.AiClient) *CourseController {
1✔
22
        return &CourseController{service: service, aiClient: aiClient}
1✔
23
}
1✔
24

25
// @Summary Get all courses
26
// @Description Get all courses available in the database
27
// @Tags courses
28
// @Accept json
29
// @Produce json
30
// @Success 200 {array} model.Course
31
// @Router /courses [get]
32
func (c *CourseController) GetCourses(ctx *gin.Context) {
1✔
33
        slog.Debug("Getting courses")
1✔
34

1✔
35
        courses, err := c.service.GetCourses()
1✔
36
        if err != nil {
2✔
37
                slog.Error("Error getting courses", "error", err)
1✔
38
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
39
                return
1✔
40
        }
1✔
41
        slog.Debug("Courses retrieved", "courses", courses)
1✔
42
        ctx.JSON(http.StatusOK, courses)
1✔
43
}
44

45
// @Summary Course creation
46
// @Description Create a new course
47
// @Tags courses
48
// @Accept json
49
// @Produce json
50
// @Param course body schemas.CreateCourseRequest true "Course to create"
51
// @Success 201 {object} model.Course
52
// @Router /courses [post]
53
func (c *CourseController) CreateCourse(ctx *gin.Context) {
1✔
54
        slog.Debug("Creating course")
1✔
55

1✔
56
        var course schemas.CreateCourseRequest
1✔
57
        if err := ctx.ShouldBindJSON(&course); err != nil {
2✔
58
                slog.Error("Error binding JSON", "error", err)
1✔
59
                ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
1✔
60
                return
1✔
61
        }
1✔
62

63
        createdCourse, err := c.service.CreateCourse(course)
1✔
64
        if err != nil {
2✔
65
                slog.Error("Error creating course", "error", err)
1✔
66
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
67
                return
1✔
68
        }
1✔
69
        slog.Debug("Course created", "course", createdCourse)
1✔
70
        ctx.JSON(http.StatusCreated, createdCourse)
1✔
71
}
72

73
// @Summary Get a course by ID
74
// @Description Get a course by ID
75
// @Tags courses
76
// @Accept json
77
// @Produce json
78
// @Param id path string true "Course ID"
79
// @Success 200 {object} model.Course
80
// @Router /courses/{id} [get]
81
func (c *CourseController) GetCourseById(ctx *gin.Context) {
1✔
82
        slog.Debug("Getting course by ID")
1✔
83

1✔
84
        id := ctx.Param("id")
1✔
85
        course, err := c.service.GetCourseById(id)
1✔
86
        if err != nil {
2✔
87
                slog.Error("Error getting course by ID", "error", err)
1✔
88
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
89
                return
1✔
90
        }
1✔
91
        slog.Debug("Course retrieved", "course", course)
1✔
92
        ctx.JSON(http.StatusOK, course)
1✔
93
}
94

95
// @Summary Delete a course
96
// @Description Delete a course by ID
97
// @Tags courses
98
// @Accept json
99
// @Produce json
100
// @Param id path string true "Course ID"
101
// @Param teacherId query string true "Teacher ID"
102
// @Success 200 {object} schemas.DeleteCourseResponse
103
// @Router /courses/{id} [delete]
104
func (c *CourseController) DeleteCourse(ctx *gin.Context) {
1✔
105
        slog.Debug("Deleting course")
1✔
106
        id := ctx.Param("id")
1✔
107
        if id == "" {
1✔
108
                slog.Error("Course ID is required")
×
109
                ctx.JSON(http.StatusBadRequest, gin.H{"error": "Course ID is required"})
×
110
                return
×
111
        }
×
112

113
        teacherId := ctx.Query("teacherId")
1✔
114
        if teacherId == "" {
2✔
115
                slog.Error("Teacher ID is required")
1✔
116
                ctx.JSON(http.StatusBadRequest, gin.H{"error": "Teacher ID is required"})
1✔
117
                return
1✔
118
        }
1✔
119

120
        err := c.service.DeleteCourse(id, teacherId)
1✔
121
        if err != nil {
2✔
122
                slog.Error("Error deleting course", "error", err)
1✔
123
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
124
                return
1✔
125
        }
1✔
126
        slog.Debug("Course deleted", "id", id)
1✔
127
        ctx.JSON(http.StatusOK, gin.H{"message": "Course deleted successfully"})
1✔
128
}
129

130
// @Summary Get a course by teacher ID
131
// @Description Get a course by teacher ID
132
// @Tags courses
133
// @Accept json
134
// @Produce json
135
// @Param teacherId path string true "Teacher ID"
136
// @Success 200 {array} model.Course
137
// @Router /courses/teacher/{teacherId} [get]
138
func (c *CourseController) GetCourseByTeacherId(ctx *gin.Context) {
1✔
139
        slog.Debug("Getting course by teacher ID")
1✔
140
        teacherId := ctx.Param("teacherId")
1✔
141
        course, err := c.service.GetCourseByTeacherId(teacherId)
1✔
142
        if err != nil {
2✔
143
                slog.Error("Error getting course by teacher ID", "error", err)
1✔
144
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
145
                return
1✔
146
        }
1✔
147
        slog.Debug("Course retrieved", "course", course)
1✔
148
        ctx.JSON(http.StatusOK, course)
1✔
149
}
150

151
// @Summary Get a course by title
152
// @Description Get a course by title
153
// @Tags courses
154
// @Accept json
155
// @Produce json
156
// @Param title path string true "Course title"
157
// @Success 200 {array} model.Course
158
// @Router /courses/title/{title} [get]
159
func (c *CourseController) GetCourseByTitle(ctx *gin.Context) {
1✔
160
        slog.Debug("Getting course by title")
1✔
161
        title := ctx.Param("title")
1✔
162
        course, err := c.service.GetCourseByTitle(title)
1✔
163
        if err != nil {
1✔
UNCOV
164
                slog.Error("Error getting course by title", "error", err)
×
UNCOV
165
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
×
UNCOV
166
                return
×
UNCOV
167
        }
×
168
        if len(course) == 0 {
2✔
169
                slog.Error("Course not found")
1✔
170
                ctx.JSON(http.StatusNotFound, gin.H{"error": "Course not found"})
1✔
171
                return
1✔
172
        }
1✔
UNCOV
173
        slog.Debug("Course retrieved", "course", course)
×
UNCOV
174
        ctx.JSON(http.StatusOK, course)
×
175
}
176

177
// @Summary Update a course
178
// @Description Update a course by ID
179
// @Tags courses
180
// @Accept json
181
// @Produce json
182
// @Param id path string true "Course ID"
183
// @Param course body schemas.UpdateCourseRequest true "Course to update"
184
// @Success 200 {object} model.Course
185
// @Router /courses/{id} [put]
186
func (c *CourseController) UpdateCourse(ctx *gin.Context) {
1✔
187
        slog.Debug("Updating course")
1✔
188
        id := ctx.Param("id")
1✔
189

1✔
190
        var updateCourseRequest schemas.UpdateCourseRequest
1✔
191
        if err := ctx.ShouldBindJSON(&updateCourseRequest); err != nil {
2✔
192
                slog.Error("Error binding JSON", "error", err)
1✔
193
                ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
1✔
194
                return
1✔
195
        }
1✔
196

197
        updatedCourse, err := c.service.UpdateCourse(id, updateCourseRequest)
1✔
198
        if err != nil {
2✔
199
                slog.Error("Error updating course", "error", err)
1✔
200
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
201
                return
1✔
202
        }
1✔
203
        slog.Debug("Course updated", "course", updatedCourse)
1✔
204
        ctx.JSON(http.StatusOK, updatedCourse)
1✔
205
}
206

207
// @Summary Get courses by student ID
208
// @Description Get courses by student ID
209
// @Tags courses
210
// @Accept json
211
// @Produce json
212
// @Param studentId path string true "Student ID"
213
// @Success 200 {array} model.Course
214
// @Router /courses/student/{studentId} [get]
215
func (c *CourseController) GetCoursesByStudentId(ctx *gin.Context) {
1✔
216
        slog.Debug("Getting courses by student ID")
1✔
217
        studentId := ctx.Param("studentId")
1✔
218
        courses, err := c.service.GetCoursesByStudentId(studentId)
1✔
219
        if err != nil {
2✔
220
                slog.Error("Error getting courses by student ID", "error", err)
1✔
221
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
222
                return
1✔
223
        }
1✔
224
        slog.Debug("Courses retrieved", "courses", courses)
1✔
225
        ctx.JSON(http.StatusOK, courses)
1✔
226
}
227

228
// @Summary Get courses by user ID
229
// @Description Get courses by user ID
230
// @Tags courses
231
// @Accept json
232
// @Produce json
233
// @Param userId path string true "User ID"
234
// @Success 200 {array} model.Course
235
// @Router /courses/user/{userId} [get]
236
func (c *CourseController) GetCoursesByUserId(ctx *gin.Context) {
1✔
237
        slog.Debug("Getting courses by user ID")
1✔
238
        userId := ctx.Param("userId")
1✔
239
        courses, err := c.service.GetCoursesByUserId(userId)
1✔
240
        if err != nil {
2✔
241
                slog.Error("Error getting courses by user ID", "error", err)
1✔
242
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
243
                return
1✔
244
        }
1✔
245
        slog.Debug("Courses retrieved", "courses", courses)
1✔
246
        ctx.JSON(http.StatusOK, courses)
1✔
247
}
248

249
// @Summary Add an aux teacher to a course
250
// @Description Add an aux teacher to a course by ID
251
// @Tags courses
252
// @Accept json
253
// @Produce json
254
// @Param id path string true "Course ID"
255
func (c *CourseController) AddAuxTeacherToCourse(ctx *gin.Context) {
1✔
256
        slog.Debug("Adding aux teacher to course")
1✔
257
        id := ctx.Param("id")
1✔
258
        if id == "" {
2✔
259
                slog.Error("Course ID is required")
1✔
260
                ctx.JSON(http.StatusBadRequest, gin.H{"error": "Course ID is required"})
1✔
261
                return
1✔
262
        }
1✔
263

264
        var auxTeacherRequest schemas.AddAuxTeacherToCourseRequest
1✔
265
        if err := ctx.ShouldBindJSON(&auxTeacherRequest); err != nil {
2✔
266
                slog.Error("Error binding JSON", "error", err)
1✔
267
                ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
1✔
268
                return
1✔
269
        }
1✔
270

271
        teacherId := auxTeacherRequest.TeacherID
1✔
272
        auxTeacherId := auxTeacherRequest.AuxTeacherID
1✔
273
        course, err := c.service.AddAuxTeacherToCourse(id, teacherId, auxTeacherId)
1✔
274
        if err != nil {
2✔
275
                slog.Error("Error adding aux teacher to course", "error", err)
1✔
276
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
277
                return
1✔
278
        }
1✔
279
        slog.Debug("Aux teacher added to course", "course", course)
1✔
280
        ctx.JSON(http.StatusOK, course)
1✔
281
}
282

283
// @Summary Remove an aux teacher from a course
284
// @Description Remove an aux teacher from a course by ID
285
// @Tags courses
286
// @Accept json
287
// @Produce json
288
// @Param id path string true "Course ID"
289
// @Param teacherId query string true "Teacher ID"
290
// @Param auxTeacherId query string true "Aux teacher ID"
291
// @Success 200 {object} model.Course
292
// @Router /courses/{id}/aux-teacher/remove [delete]
293
func (c *CourseController) RemoveAuxTeacherFromCourse(ctx *gin.Context) {
1✔
294
        slog.Debug("Removing aux teacher from course")
1✔
295
        id := ctx.Param("id")
1✔
296
        if id == "" {
2✔
297
                slog.Error("Course ID is required")
1✔
298
                ctx.JSON(http.StatusBadRequest, gin.H{"error": "Course ID is required"})
1✔
299
                return
1✔
300
        }
1✔
301

302
        teacherId := ctx.Query("teacherId")
1✔
303
        auxTeacherId := ctx.Query("auxTeacherId")
1✔
304

1✔
305
        if teacherId == "" || auxTeacherId == "" {
2✔
306
                slog.Error("Teacher ID and aux teacher ID are required")
1✔
307
                ctx.JSON(http.StatusBadRequest, gin.H{"error": "Teacher ID and aux teacher ID are required"})
1✔
308
                return
1✔
309
        }
1✔
310

311
        course, err := c.service.RemoveAuxTeacherFromCourse(id, teacherId, auxTeacherId)
1✔
312
        if err != nil {
2✔
313
                slog.Error("Error removing aux teacher from course", "error", err)
1✔
314
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
315
                return
1✔
316
        }
1✔
317
        slog.Debug("Aux teacher removed from course", "course", course)
1✔
318
        ctx.JSON(http.StatusOK, course)
1✔
319
}
320

321
// @Summary Get favourite courses
322
// @Description Get favourite courses by student ID
323
// @Tags courses
324
// @Accept json
325
// @Produce json
326
// @Param studentId path string true "Student ID"
327
// @Success 200 {array} model.Course
328
// @Router /courses/favourite/{studentId} [get]
329
func (c *CourseController) GetFavouriteCourses(ctx *gin.Context) {
1✔
330
        slog.Debug("Getting favourite courses")
1✔
331
        studentId := ctx.Param("studentId")
1✔
332
        if studentId == "" {
2✔
333
                slog.Error("Student ID is required")
1✔
334
                ctx.JSON(http.StatusBadRequest, gin.H{"error": "Student ID is required"})
1✔
335
                return
1✔
336
        }
1✔
337

338
        courses, err := c.service.GetFavouriteCourses(studentId)
1✔
339
        if err != nil {
2✔
340
                slog.Error("Error getting favourite courses", "error", err)
1✔
341
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
342
                return
1✔
343
        }
1✔
344
        slog.Debug("Favourite courses retrieved", "courses", courses)
1✔
345
        ctx.JSON(http.StatusOK, courses)
1✔
346
}
347

348
// @Summary Create course feedback
349
// @Description Create course feedback by course ID
350
// @Tags courses
351
// @Accept json
352
// @Produce json
353
// @Param id path string true "Course ID"
354
// @Param feedback body schemas.CreateCourseFeedbackRequest true "Course feedback"
355
// @Success 200 {object} model.CourseFeedback
356
// @Router /courses/{id}/feedback [post]
357
func (c *CourseController) CreateCourseFeedback(ctx *gin.Context) {
1✔
358
        slog.Debug("Creating course feedback")
1✔
359
        courseId := ctx.Param("id")
1✔
360
        if courseId == "" {
2✔
361
                slog.Error("Course ID is required")
1✔
362
                ctx.JSON(http.StatusBadRequest, gin.H{"error": "Course ID is required"})
1✔
363
                return
1✔
364
        }
1✔
365

366
        var feedback schemas.CreateCourseFeedbackRequest
1✔
367
        if err := ctx.ShouldBindJSON(&feedback); err != nil {
2✔
368
                slog.Error("Error binding create course feedback request", "error", err)
1✔
369
                ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
1✔
370
                return
1✔
371
        }
1✔
372

373
        if !slices.Contains(model.FeedbackTypes, feedback.FeedbackType) {
2✔
374
                slog.Error("Invalid feedback type")
1✔
375
                ctx.JSON(http.StatusBadRequest, gin.H{"error": "Invalid feedback type"})
1✔
376
                return
1✔
377
        }
1✔
378

379
        feedbackModel, err := c.service.CreateCourseFeedback(courseId, feedback)
1✔
380
        if err != nil {
2✔
381
                slog.Error("Error creating course feedback", "error", err)
1✔
382
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
383
                return
1✔
384
        }
1✔
385

386
        slog.Debug("Course feedback created", "feedback", feedbackModel)
1✔
387
        ctx.JSON(http.StatusOK, feedbackModel)
1✔
388
}
389

390
// @Summary Get course feedback
391
// @Description Get course feedback by course ID
392
// @Tags courses
393
// @Accept json
394
// @Produce json
395
// @Param id path string true "Course ID"
396
// @Param getCourseFeedbackRequest body schemas.GetCourseFeedbackRequest true "Get course feedback request"
397
// @Success 200 {array} model.CourseFeedback
398
// @Router /courses/{id}/feedback [put]
399
func (c *CourseController) GetCourseFeedback(ctx *gin.Context) {
1✔
400
        slog.Debug("Getting course feedback")
1✔
401
        courseId := ctx.Param("id")
1✔
402
        if courseId == "" {
2✔
403
                slog.Error("Course ID is required")
1✔
404
                ctx.JSON(http.StatusBadRequest, gin.H{"error": "Course ID is required"})
1✔
405
                return
1✔
406
        }
1✔
407

408
        var getCourseFeedbackRequest schemas.GetCourseFeedbackRequest
1✔
409
        if err := ctx.ShouldBindJSON(&getCourseFeedbackRequest); err != nil {
2✔
410
                slog.Error("Error binding get course feedback request", "error", err)
1✔
411
                ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
1✔
412
                return
1✔
413
        }
1✔
414

415
        feedback, err := c.service.GetCourseFeedback(courseId, getCourseFeedbackRequest)
1✔
416
        if err != nil {
2✔
417
                slog.Error("Error getting course feedback", "error", err)
1✔
418
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
419
                return
1✔
420
        }
1✔
421

422
        slog.Debug("Course feedback retrieved", "feedback", feedback)
1✔
423
        ctx.JSON(http.StatusOK, feedback)
1✔
424
}
425

426
// @Summary Get course feedback summary
427
// @Description Get course feedback summary by course ID
428
// @Tags courses
429
// @Accept json
430
// @Produce json
431
// @Param id path string true "Course ID"
432
// @Success 200 {object} schemas.AiSummaryResponse
433
// @Router /courses/{id}/feedback/summary [get]
434
func (c *CourseController) GetCourseFeedbackSummary(ctx *gin.Context) {
×
435
        slog.Debug("Getting course feedback summary")
×
436
        courseId := ctx.Param("id")
×
437
        if courseId == "" {
×
438
                slog.Error("Course ID is required")
×
439
                ctx.JSON(http.StatusBadRequest, gin.H{"error": "Course ID is required"})
×
440
                return
×
441
        }
×
442

443
        feedbacks, err := c.service.GetCourseFeedback(courseId, schemas.GetCourseFeedbackRequest{})
×
444
        if err != nil {
×
445
                slog.Error("Error getting course feedback", "error", err)
×
446
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
×
447
                return
×
448
        }
×
449

450
        if len(feedbacks) == 0 {
×
451
                slog.Error("No feedbacks found")
×
452
                ctx.JSON(http.StatusNotFound, gin.H{"error": "No feedbacks found"})
×
453
                return
×
454
        }
×
455

456
        summary, err := c.aiClient.SummarizeCourseFeedbacks(feedbacks)
×
457
        if err != nil {
×
458
                slog.Error("Error getting course feedback summary", "error", err)
×
459
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
×
460
                return
×
461
        }
×
462

463
        slog.Debug("Course feedback summary retrieved", "summary", summary)
×
464
        ctx.JSON(http.StatusOK, schemas.AiSummaryResponse{Summary: summary})
×
465
}
466

467
// @Summary Get course members
468
// @Description Get all members of a course (teacher, aux teachers, and students)
469
// @Tags courses
470
// @Accept json
471
// @Produce json
472
// @Param id path string true "Course ID"
473
// @Success 200 {object} schemas.CourseMembersResponse
474
// @Failure 400 {object} schemas.ErrorResponse
475
// @Failure 404 {object} schemas.ErrorResponse
476
// @Failure 500 {object} schemas.ErrorResponse
477
// @Router /courses/{id}/members [get]
478
func (c *CourseController) GetCourseMembers(ctx *gin.Context) {
1✔
479
        slog.Debug("Getting course members")
1✔
480

1✔
481
        courseId := ctx.Param("id")
1✔
482
        if courseId == "" {
1✔
483
                ctx.JSON(http.StatusBadRequest, schemas.ErrorResponse{Error: "Course ID is required"})
×
484
                return
×
485
        }
×
486

487
        members, err := c.service.GetCourseMembers(courseId)
1✔
488
        if err != nil {
2✔
489
                slog.Error("Error getting course members", "error", err)
1✔
490
                ctx.JSON(http.StatusInternalServerError, schemas.ErrorResponse{Error: err.Error()})
1✔
491
                return
1✔
492
        }
1✔
493

494
        slog.Debug("Course members retrieved", "course_id", courseId)
1✔
495
        ctx.JSON(http.StatusOK, members)
1✔
496
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc