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

classconnect-grupo3 / courses-service / 15800674056

21 Jun 2025 11:30PM UTC coverage: 86.671% (-0.5%) from 87.21%
15800674056

push

github

web-flow
Merge pull request #41 from classconnect-grupo3/ai-automatic-corrections

Ai automatic corrections

74 of 109 new or added lines in 3 files covered. (67.89%)

1 existing line in 1 file now uncovered.

3674 of 4239 relevant lines covered (86.67%)

0.97 hits per line

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

77.83
/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
}
20

21
func NewSubmissionController(submissionService service.SubmissionServiceInterface, notificationsQueue queues.NotificationsQueueInterface) *SubmissionController {
1✔
22
        return &SubmissionController{
1✔
23
                submissionService:  submissionService,
1✔
24
                notificationsQueue: notificationsQueue,
1✔
25
        }
1✔
26
}
1✔
27

28
type CreateSubmissionRequest struct {
29
        Answers []model.Answer `json:"answers"`
30
}
31

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

49
        // Get student info from context (assuming middleware sets this)
50
        studentUUID := ctx.GetString("student_uuid")
1✔
51
        studentName := ctx.GetString("student_name")
1✔
52

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

59
        submission.Answers = req.Answers
1✔
60
        submission.UpdatedAt = time.Now()
1✔
61

1✔
62
        if err := c.submissionService.UpdateSubmission(ctx, submission); err != nil {
1✔
63
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
×
64
                return
×
65
        }
×
66

67
        ctx.JSON(http.StatusOK, submission)
1✔
68
}
69

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

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

89
        if submission == nil {
2✔
90
                ctx.JSON(http.StatusNotFound, gin.H{"error": "submission not found"})
1✔
91
                return
1✔
92
        }
1✔
93

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

100
        ctx.JSON(http.StatusOK, submission)
1✔
101
}
102

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

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

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

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

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

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

148
        ctx.JSON(http.StatusOK, submission)
1✔
149
}
150

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

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

179
        if err := c.submissionService.SubmitSubmission(ctx, id); err != nil {
1✔
180
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
×
181
                return
×
182
        }
×
183

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

191
        // Send notification about the corrected submission
192
        c.sendCorrectionNotification(updatedSubmission, assignmentID)
1✔
193

1✔
194
        ctx.JSON(http.StatusOK, updatedSubmission)
1✔
195
}
196

197
// sendCorrectionNotification sends a notification about the corrected submission
198
func (c *SubmissionController) sendCorrectionNotification(submission *model.Submission, assignmentID string) {
1✔
199
        if c.notificationsQueue == nil {
2✔
200
                return // Skip if notifications are not configured
1✔
201
        }
1✔
202

203
        correctionType := "automatic"
1✔
204
        needsManualReview := false
1✔
205

1✔
206
        if submission.NeedsManualReview != nil && *submission.NeedsManualReview {
1✔
NEW
207
                correctionType = "needs_manual_review"
×
NEW
208
                needsManualReview = true
×
NEW
209
        }
×
210

211
        queueMessage := queues.NewSubmissionCorrectedMessage(
1✔
212
                assignmentID,
1✔
213
                submission.ID.Hex(),
1✔
214
                submission.StudentUUID,
1✔
215
                submission.Score,
1✔
216
                submission.Feedback,
1✔
217
                correctionType,
1✔
218
                needsManualReview,
1✔
219
        )
1✔
220

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

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

1✔
239
        submissions, err := c.submissionService.GetSubmissionsByAssignment(ctx, assignmentID)
1✔
240
        if err != nil {
2✔
241
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
242
                return
1✔
243
        }
1✔
244

245
        ctx.JSON(http.StatusOK, submissions)
1✔
246
}
247

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

1✔
259
        // Validate student access
1✔
260
        requestingStudentUUID := ctx.GetString("student_uuid")
1✔
261
        if studentUUID != requestingStudentUUID {
2✔
262
                ctx.JSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
1✔
263
                return
1✔
264
        }
1✔
265

266
        submissions, err := c.submissionService.GetSubmissionsByStudent(ctx, studentUUID)
1✔
267
        if err != nil {
2✔
268
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
1✔
269
                return
1✔
270
        }
1✔
271

272
        ctx.JSON(http.StatusOK, submissions)
1✔
273
}
274

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

1✔
289
        var gradeRequest schemas.GradeSubmissionRequest
1✔
290
        if err := ctx.ShouldBindJSON(&gradeRequest); err != nil {
2✔
291
                ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
1✔
292
                return
1✔
293
        }
1✔
294

295
        // Get teacher info from context
296
        teacherUUID := ctx.GetString("teacher_uuid")
1✔
297

1✔
298
        // Validate teacher permissions for this assignment
1✔
299
        if err := c.submissionService.ValidateTeacherPermissions(ctx, assignmentID, teacherUUID); err != nil {
2✔
300
                ctx.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
1✔
301
                return
1✔
302
        }
1✔
303

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

319
        // Grade the submission
320
        gradedSubmission, err := c.submissionService.GradeSubmission(ctx, id, gradeRequest.Score, gradeRequest.Feedback)
1✔
321
        if err != nil {
1✔
322
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
×
323
                return
×
324
        }
×
325

326
        ctx.JSON(http.StatusOK, gradedSubmission)
1✔
327
}
328

329
// @Summary Generate feedback summary
330
// @Description Generate an AI summary of the feedback for a submission
331
// @Tags submissions
332
// @Accept json
333
// @Produce json
334
// @Param assignmentId path string true "Assignment ID"
335
// @Param id path string true "Submission ID"
336
// @Success 200 {object} schemas.AiSummaryResponse
337
// @Router /assignments/{assignmentId}/submissions/{id}/feedback-summary [get]
338
func (c *SubmissionController) GenerateFeedbackSummary(ctx *gin.Context) {
×
339
        assignmentID := ctx.Param("assignmentId")
×
340
        id := ctx.Param("id")
×
341

×
342
        // Get teacher info from context
×
343
        teacherUUID := ctx.GetString("teacher_uuid")
×
344

×
345
        // Validate teacher permissions for this assignment
×
346
        if err := c.submissionService.ValidateTeacherPermissions(ctx, assignmentID, teacherUUID); err != nil {
×
347
                ctx.JSON(http.StatusForbidden, gin.H{"error": err.Error()})
×
348
                return
×
349
        }
×
350

351
        // Validate submission belongs to the assignment
352
        submission, err := c.submissionService.GetSubmission(ctx, id)
×
353
        if err != nil {
×
354
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
×
355
                return
×
356
        }
×
357
        if submission == nil {
×
358
                ctx.JSON(http.StatusNotFound, gin.H{"error": "submission not found"})
×
359
                return
×
360
        }
×
361
        if submission.AssignmentID != assignmentID {
×
362
                ctx.JSON(http.StatusNotFound, gin.H{"error": "submission not found"})
×
363
                return
×
364
        }
×
365

366
        // Generate feedback summary
367
        summary, err := c.submissionService.GenerateFeedbackSummary(ctx, id)
×
368
        if err != nil {
×
369
                ctx.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()})
×
370
                return
×
371
        }
×
372

373
        ctx.JSON(http.StatusOK, summary)
×
374
}
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