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

classconnect-grupo3 / courses-service / 15914402693

26 Jun 2025 11:12PM UTC coverage: 81.843% (-5.9%) from 87.722%
15914402693

Pull #49

github

web-flow
Merge pull request #48 from classconnect-grupo3/notification-messages-and-testing

Notification messages and testing
Pull Request #49: Final merge (thank you for the ride)

1542 of 2153 new or added lines in 24 files covered. (71.62%)

16 existing lines in 5 files now uncovered.

4548 of 5557 relevant lines covered (81.84%)

0.9 hits per line

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

78.3
/src/controller/submission_controller.go
1
package controller
2

3
import (
4
        "fmt"
5
        "net/http"
6
        "time"
7

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

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

16
type SubmissionController struct {
17
        submissionService  service.SubmissionServiceInterface
18
        notificationsQueue queues.NotificationsQueueInterface
19
        activityService    service.TeacherActivityServiceInterface
20
        assignmentService  service.AssignmentServiceInterface
21
}
22

23
func NewSubmissionController(submissionService service.SubmissionServiceInterface, notificationsQueue queues.NotificationsQueueInterface, activityService service.TeacherActivityServiceInterface, assignmentService service.AssignmentServiceInterface) *SubmissionController {
1✔
24
        return &SubmissionController{
1✔
25
                submissionService:  submissionService,
1✔
26
                notificationsQueue: notificationsQueue,
1✔
27
                activityService:    activityService,
1✔
28
                assignmentService:  assignmentService,
1✔
29
        }
1✔
30
}
1✔
31

32
type CreateSubmissionRequest struct {
33
        Answers []model.Answer `json:"answers"`
34
}
35

36
// @Summary Create a submission
37
// @Description Create a submission
38
// @Tags submissions
39
// @Accept json
40
// @Produce json
41
// @Param assignmentId path string true "Assignment ID"
42
// @Param submission body CreateSubmissionRequest true "Submission to create"
43
// @Success 201 {object} model.Submission
44
// @Router /assignments/{assignmentId}/submissions [post]
45
func (c *SubmissionController) CreateSubmission(ctx *gin.Context) {
1✔
46
        assignmentID := ctx.Param("assignmentId")
1✔
47
        var req CreateSubmissionRequest
1✔
48
        if err := ctx.ShouldBindJSON(&req); err != nil {
2✔
49
                ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
1✔
50
                return
1✔
51
        }
1✔
52

53
        // Get student info from context (assuming middleware sets this)
54
        studentUUID := ctx.GetString("student_uuid")
1✔
55
        studentName := ctx.GetString("student_name")
1✔
56

1✔
57
        submission, err := c.submissionService.GetOrCreateSubmission(ctx, assignmentID, studentUUID, studentName)
1✔
58
        if err != nil {
2✔
59
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
60
                return
1✔
61
        }
1✔
62

63
        submission.Answers = req.Answers
1✔
64
        submission.UpdatedAt = time.Now()
1✔
65

1✔
66
        if err := c.submissionService.UpdateSubmission(ctx, submission); err != nil {
1✔
67
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
×
68
                return
×
69
        }
×
70

71
        ctx.JSON(http.StatusOK, submission)
1✔
72
}
73

74
// @Summary Get a submission by ID
75
// @Description Get a submission by ID
76
// @Tags submissions
77
// @Accept json
78
// @Produce json
79
// @Param assignmentId path string true "Assignment ID"
80
// @Param id path string true "Submission ID"
81
// @Success 200 {object} model.Submission
82
// @Router /assignments/{assignmentId}/submissions/{id} [get]
83
func (c *SubmissionController) GetSubmission(ctx *gin.Context) {
1✔
84
        assignmentID := ctx.Param("assignmentId")
1✔
85
        id := ctx.Param("id")
1✔
86

1✔
87
        submission, err := c.submissionService.GetSubmission(ctx, id)
1✔
88
        if err != nil {
2✔
89
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
90
                return
1✔
91
        }
1✔
92

93
        if submission == nil {
2✔
94
                ctx.JSON(http.StatusNotFound, gin.H{"error": "submission not found"})
1✔
95
                return
1✔
96
        }
1✔
97

98
        // Validate submission belongs to the assignment
99
        if submission.AssignmentID != assignmentID {
2✔
100
                ctx.JSON(http.StatusNotFound, gin.H{"error": "submission not found"})
1✔
101
                return
1✔
102
        }
1✔
103

104
        ctx.JSON(http.StatusOK, submission)
1✔
105
}
106

107
// @Summary Update a submission
108
// @Description Update a submission by ID
109
// @Tags submissions
110
// @Accept json
111
// @Produce json
112
// @Param assignmentId path string true "Assignment ID"
113
// @Param id path string true "Submission ID"
114
// @Param submission body model.Submission true "Submission to update"
115
// @Success 200 {object} model.Submission
116
// @Router /assignments/{assignmentId}/submissions/{id} [put]
117
func (c *SubmissionController) UpdateSubmission(ctx *gin.Context) {
1✔
118
        assignmentID := ctx.Param("assignmentId")
1✔
119
        id := ctx.Param("id")
1✔
120

1✔
121
        var submission model.Submission
1✔
122
        if err := ctx.ShouldBindJSON(&submission); err != nil {
2✔
123
                ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
1✔
124
                return
1✔
125
        }
1✔
126

127
        // Validate submission ID matches URL
128
        if submission.ID.Hex() != id {
2✔
129
                fmt.Printf("submission ID mismatch: %s != %s\n", submission.ID.Hex(), id)
1✔
130
                ctx.JSON(http.StatusBadRequest, gin.H{"error": "submission ID mismatch"})
1✔
131
                return
1✔
132
        }
1✔
133

134
        // Validate submission belongs to the assignment
135
        if submission.AssignmentID != assignmentID {
2✔
136
                ctx.JSON(http.StatusBadRequest, gin.H{"error": "assignment ID mismatch"})
1✔
137
                return
1✔
138
        }
1✔
139

140
        // Validate student ownership
141
        studentUUID := ctx.GetString("student_uuid")
1✔
142
        if submission.StudentUUID != studentUUID {
2✔
143
                ctx.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
1✔
144
                return
1✔
145
        }
1✔
146

147
        if err := c.submissionService.UpdateSubmission(ctx, &submission); err != nil {
1✔
148
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
×
149
                return
×
150
        }
×
151

152
        ctx.JSON(http.StatusOK, submission)
1✔
153
}
154

155
// @Summary Submit a submission
156
// @Description Submit a submission by ID
157
// @Tags submissions
158
// @Accept json
159
// @Produce json
160
// @Param assignmentId path string true "Assignment ID"
161
// @Param id path string true "Submission ID"
162
// @Success 200 {object} model.Submission
163
// @Router /assignments/{assignmentId}/submissions/{id}/submit [post]
164
func (c *SubmissionController) SubmitSubmission(ctx *gin.Context) {
1✔
165
        assignmentID := ctx.Param("assignmentId")
1✔
166
        id := ctx.Param("id")
1✔
167

1✔
168
        // Validate submission belongs to the assignment
1✔
169
        submission, err := c.submissionService.GetSubmission(ctx, id)
1✔
170
        if err != nil {
2✔
171
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
172
                return
1✔
173
        }
1✔
174
        if submission == nil {
2✔
175
                ctx.JSON(http.StatusNotFound, gin.H{"error": "submission not found"})
1✔
176
                return
1✔
177
        }
1✔
178
        if submission.AssignmentID != assignmentID {
2✔
179
                ctx.JSON(http.StatusNotFound, gin.H{"error": "submission not found"})
1✔
180
                return
1✔
181
        }
1✔
182

183
        if err := c.submissionService.SubmitSubmission(ctx, id); err != nil {
1✔
184
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
×
185
                return
×
186
        }
×
187

188
        // Get the updated submission after auto-correction
189
        updatedSubmission, err := c.submissionService.GetSubmission(ctx, id)
1✔
190
        if err != nil {
1✔
191
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
×
192
                return
×
193
        }
×
194

195
        // Send notification about the corrected submission
196
        c.sendCorrectionNotification(updatedSubmission, assignmentID)
1✔
197

1✔
198
        ctx.JSON(http.StatusOK, updatedSubmission)
1✔
199
}
200

201
// sendCorrectionNotification sends a notification about the corrected submission
202
func (c *SubmissionController) sendCorrectionNotification(submission *model.Submission, assignmentID string) {
1✔
203
        if c.notificationsQueue == nil {
1✔
NEW
204
                return // Skip if notifications are not configured
×
NEW
205
        }
×
206

207
        correctionType := "automatic"
1✔
208
        needsManualReview := false
1✔
209

1✔
210
        if submission.NeedsManualReview != nil && *submission.NeedsManualReview {
1✔
NEW
211
                correctionType = "needs_manual_review"
×
NEW
212
                needsManualReview = true
×
NEW
213
        }
×
214

215
        queueMessage := queues.NewSubmissionCorrectedMessage(
1✔
216
                assignmentID,
1✔
217
                submission.ID.Hex(),
1✔
218
                submission.StudentUUID,
1✔
219
                submission.Score,
1✔
220
                submission.Feedback,
1✔
221
                submission.AIScore,
1✔
222
                submission.AIFeedback,
1✔
223
                correctionType,
1✔
224
                needsManualReview,
1✔
225
        )
1✔
226

1✔
227
        // if err := c.notificationsQueue.Publish(queueMessage); err != nil {
1✔
228
        //         // Log the error but don't fail the response
1✔
229
        //         fmt.Printf("Error publishing correction notification: %v\n", err)
1✔
230
        // } TODO: Uncomment this when the notifications queue is implemented
1✔
231
        fmt.Println("queueMessage: ", queueMessage)
1✔
232
}
233

234
// @Summary Get submissions by assignment ID
235
// @Description Get submissions by assignment ID
236
// @Tags submissions
237
// @Accept json
238
// @Produce json
239
// @Param assignmentId path string true "Assignment ID"
240
// @Success 200 {array} model.Submission
241
// @Router /assignments/{assignmentId}/submissions [get]
242
func (c *SubmissionController) GetSubmissionsByAssignment(ctx *gin.Context) {
1✔
243
        assignmentID := ctx.Param("assignmentId")
1✔
244

1✔
245
        submissions, err := c.submissionService.GetSubmissionsByAssignment(ctx, assignmentID)
1✔
246
        if err != nil {
2✔
247
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
248
                return
1✔
249
        }
1✔
250

251
        ctx.JSON(http.StatusOK, submissions)
1✔
252
}
253

254
// @Summary Get submissions by student ID
255
// @Description Get submissions by student ID
256
// @Tags submissions
257
// @Accept json
258
// @Produce json
259
// @Param studentUUID path string true "Student ID"
260
// @Success 200 {array} model.Submission
261
// @Router /students/{studentUUID}/submissions [get]
262
func (c *SubmissionController) GetSubmissionsByStudent(ctx *gin.Context) {
1✔
263
        studentUUID := ctx.Param("studentUUID")
1✔
264

1✔
265
        // Validate student access
1✔
266
        requestingStudentUUID := ctx.GetString("student_uuid")
1✔
267
        if studentUUID != requestingStudentUUID {
2✔
268
                ctx.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
1✔
269
                return
1✔
270
        }
1✔
271

272
        submissions, err := c.submissionService.GetSubmissionsByStudent(ctx, studentUUID)
1✔
273
        if err != nil {
2✔
274
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
275
                return
1✔
276
        }
1✔
277

278
        ctx.JSON(http.StatusOK, submissions)
1✔
279
}
280

281
// @Summary Grade a submission
282
// @Description Grade a submission by ID (for teachers)
283
// @Tags submissions
284
// @Accept json
285
// @Produce json
286
// @Param assignmentId path string true "Assignment ID"
287
// @Param id path string true "Submission ID"
288
// @Param gradeRequest body schemas.GradeSubmissionRequest true "Grade request"
289
// @Success 200 {object} model.Submission
290
// @Router /assignments/{assignmentId}/submissions/{id}/grade [put]
291
func (c *SubmissionController) GradeSubmission(ctx *gin.Context) {
1✔
292
        assignmentID := ctx.Param("assignmentId")
1✔
293
        id := ctx.Param("id")
1✔
294

1✔
295
        var gradeRequest schemas.GradeSubmissionRequest
1✔
296
        if err := ctx.ShouldBindJSON(&gradeRequest); err != nil {
2✔
297
                ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
1✔
298
                return
1✔
299
        }
1✔
300

301
        // Get teacher info from context
302
        teacherUUID := ctx.GetString("teacher_uuid")
1✔
303

1✔
304
        // Validate teacher permissions for this assignment
1✔
305
        if err := c.submissionService.ValidateTeacherPermissions(ctx, assignmentID, teacherUUID); err != nil {
2✔
306
                ctx.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
1✔
307
                return
1✔
308
        }
1✔
309

310
        // Validate submission belongs to the assignment
311
        submission, err := c.submissionService.GetSubmission(ctx, id)
1✔
312
        if err != nil {
2✔
313
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
314
                return
1✔
315
        }
1✔
316
        if submission == nil {
2✔
317
                ctx.JSON(http.StatusNotFound, gin.H{"error": "submission not found"})
1✔
318
                return
1✔
319
        }
1✔
320
        if submission.AssignmentID != assignmentID {
2✔
321
                ctx.JSON(http.StatusNotFound, gin.H{"error": "submission not found"})
1✔
322
                return
1✔
323
        }
1✔
324

325
        // Grade the submission
326
        gradedSubmission, err := c.submissionService.GradeSubmission(ctx, id, gradeRequest.Score, gradeRequest.Feedback)
1✔
327
        if err != nil {
1✔
328
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
×
329
                return
×
330
        }
×
331

332
        // Log activity if teacher is auxiliary - we need to get the assignment to find the course
333
        if c.activityService != nil && c.assignmentService != nil {
2✔
334
                assignment, err := c.assignmentService.GetAssignmentById(assignmentID)
1✔
335
                if err == nil && assignment != nil {
2✔
336
                        c.activityService.LogActivityIfAuxTeacher(
1✔
337
                                assignment.CourseID,
1✔
338
                                teacherUUID,
1✔
339
                                "GRADE_SUBMISSION",
1✔
340
                                fmt.Sprintf("Graded submission for student %s", gradedSubmission.StudentName),
1✔
341
                        )
1✔
342
                }
1✔
343
        }
344

345
        ctx.JSON(http.StatusOK, gradedSubmission)
1✔
346
}
347

348
// @Summary Generate feedback summary
349
// @Description Generate an AI summary of the feedback for a submission
350
// @Tags submissions
351
// @Accept json
352
// @Produce json
353
// @Param assignmentId path string true "Assignment ID"
354
// @Param id path string true "Submission ID"
355
// @Success 200 {object} schemas.AiSummaryResponse
356
// @Router /assignments/{assignmentId}/submissions/{id}/feedback-summary [get]
NEW
357
func (c *SubmissionController) GenerateFeedbackSummary(ctx *gin.Context) {
×
NEW
358
        assignmentID := ctx.Param("assignmentId")
×
NEW
359
        id := ctx.Param("id")
×
NEW
360

×
NEW
361
        // Get teacher info from context
×
NEW
362
        teacherUUID := ctx.GetString("teacher_uuid")
×
NEW
363

×
NEW
364
        // Validate teacher permissions for this assignment
×
NEW
365
        if err := c.submissionService.ValidateTeacherPermissions(ctx, assignmentID, teacherUUID); err != nil {
×
NEW
366
                ctx.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
×
NEW
367
                return
×
NEW
368
        }
×
369

370
        // Validate submission belongs to the assignment
NEW
371
        submission, err := c.submissionService.GetSubmission(ctx, id)
×
NEW
372
        if err != nil {
×
NEW
373
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
×
NEW
374
                return
×
NEW
375
        }
×
NEW
376
        if submission == nil {
×
NEW
377
                ctx.JSON(http.StatusNotFound, gin.H{"error": "submission not found"})
×
NEW
378
                return
×
NEW
379
        }
×
NEW
380
        if submission.AssignmentID != assignmentID {
×
NEW
381
                ctx.JSON(http.StatusNotFound, gin.H{"error": "submission not found"})
×
NEW
382
                return
×
NEW
383
        }
×
384

385
        // Generate feedback summary
NEW
386
        summary, err := c.submissionService.GenerateFeedbackSummary(ctx, id)
×
NEW
387
        if err != nil {
×
NEW
388
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
×
NEW
389
                return
×
NEW
390
        }
×
391

NEW
392
        ctx.JSON(http.StatusOK, summary)
×
393
}
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