package api // The owner shapes of the review routes. They carry student emails, the // model answer, the rubric and grading errors, and are never used by a // student route; the student shapes live in json.go. Built field by field, // so a new column never leaks. import ( "time" "unicode/utf8" "git.golem15.com/golem15/sm-quizzes-plugin/classes" "git.golem15.com/golem15/sm-quizzes-plugin/models" "git.golem15.com/golem15/summercms/modules/wire" ) type reviewQuizShape struct { ID uint `json:"id"` Slug string `json:"slug"` Title string `json:"title"` Mode string `json:"mode"` } type reviewQuestionShape struct { ID uint `json:"id"` Text string `json:"text"` Points int `json:"points"` } type reviewDetailQuestionShape struct { reviewQuestionShape AnswerText *string `json:"answer_text"` Rubric *string `json:"rubric"` MaxLength int `json:"max_length"` } type reviewAttemptShape struct { ID uint `json:"id"` Completed bool `json:"completed"` CompletedAt *wire.Time `json:"completed_at"` } type reviewStudentShape struct { UserID uint `json:"user_id"` Email string `json:"email"` Name *string `json:"name"` Surname *string `json:"surname"` } type reviewerShape struct { UserID *uint `json:"user_id"` AdminID *uint `json:"admin_id"` } // reviewRowShape is one row of GET /api/v1/quiz-reviews. type reviewRowShape struct { AnswerID uint `json:"answer_id"` Status string `json:"status"` Quiz reviewQuizShape `json:"quiz"` Question reviewQuestionShape `json:"question"` Attempt reviewAttemptShape `json:"attempt"` Student reviewStudentShape `json:"student"` Text string `json:"text"` TextTruncated bool `json:"text_truncated"` GraderPoints *float64 `json:"grader_points"` Points float64 `json:"points"` GradedAt *wire.Time `json:"graded_at"` ReviewedAt *wire.Time `json:"reviewed_at"` UpdatedAt wire.Time `json:"updated_at"` } // reviewDetailShape is GET /api/v1/quiz-reviews/{id} and every review // write: the row keys with the whole text, plus the solution and the // grading and review record. type reviewDetailShape struct { AnswerID uint `json:"answer_id"` Status string `json:"status"` Quiz reviewQuizShape `json:"quiz"` Question reviewDetailQuestionShape `json:"question"` Attempt reviewAttemptShape `json:"attempt"` Student reviewStudentShape `json:"student"` Text string `json:"text"` GraderPoints *float64 `json:"grader_points"` Points float64 `json:"points"` GradedAt *wire.Time `json:"graded_at"` ReviewedAt *wire.Time `json:"reviewed_at"` UpdatedAt wire.Time `json:"updated_at"` Grader *string `json:"grader"` GraderFeedback *string `json:"grader_feedback"` GradingError *string `json:"grading_error"` GradingAttempts int `json:"grading_attempts"` IsCorrect bool `json:"is_correct"` ReviewComment *string `json:"review_comment"` ReviewedBy reviewerShape `json:"reviewed_by"` } func wireTimePtr(t *time.Time) *wire.Time { if t == nil { return nil } w := wireTime(*t) return &w } func reviewQuiz(r classes.ReviewRow) reviewQuizShape { return reviewQuizShape{ID: r.QuizID, Slug: r.QuizSlug, Title: r.QuizTitle, Mode: quizMode(&models.Quiz{Mode: r.QuizMode})} } func reviewAttempt(r classes.ReviewRow) reviewAttemptShape { return reviewAttemptShape{ID: r.AttemptID, Completed: r.AttemptCompletedAt != nil, CompletedAt: wireTimePtr(r.AttemptCompletedAt)} } func reviewStudent(r classes.ReviewRow) reviewStudentShape { return reviewStudentShape{UserID: r.UserID, Email: r.Email, Name: r.Name, Surname: r.Surname} } // excerpt keeps the first n runes of s and reports whether it cut. func excerpt(s string, n int) (string, bool) { if utf8.RuneCountInString(s) <= n { return s, false } return string([]rune(s)[:n]), true } // reviewRowJSON is one queue row; the text is cut to the first // classes.ReviewExcerptRunes runes with text_truncated set. func reviewRowJSON(r classes.ReviewRow) reviewRowShape { text, cut := excerpt(r.Text, classes.ReviewExcerptRunes) return reviewRowShape{ AnswerID: r.AnswerID, Status: r.Status, Quiz: reviewQuiz(r), Question: reviewQuestionShape{ID: r.QuestionID, Text: r.QuestionText, Points: r.QuestionPoints}, Attempt: reviewAttempt(r), Student: reviewStudent(r), Text: text, TextTruncated: cut, GraderPoints: r.GraderPoints, Points: r.Points, GradedAt: wireTimePtr(r.GradedAt), ReviewedAt: wireTimePtr(r.ReviewedAt), UpdatedAt: wireTime(r.UpdatedAt), } } // reviewDetailJSON is one answer for review, owner view. func reviewDetailJSON(d classes.ReviewDetail) reviewDetailShape { r := d.ReviewRow return reviewDetailShape{ AnswerID: r.AnswerID, Status: r.Status, Quiz: reviewQuiz(r), Question: reviewDetailQuestionShape{ reviewQuestionShape: reviewQuestionShape{ID: r.QuestionID, Text: r.QuestionText, Points: r.QuestionPoints}, AnswerText: d.AnswerText, Rubric: d.Rubric, MaxLength: d.MaxLength, }, Attempt: reviewAttempt(r), Student: reviewStudent(r), Text: r.Text, GraderPoints: r.GraderPoints, Points: r.Points, GradedAt: wireTimePtr(r.GradedAt), ReviewedAt: wireTimePtr(r.ReviewedAt), UpdatedAt: wireTime(r.UpdatedAt), Grader: d.Grader, GraderFeedback: d.GraderFeedback, GradingError: d.GradingError, GradingAttempts: d.GradingAttempts, IsCorrect: d.IsCorrect, ReviewComment: d.ReviewComment, ReviewedBy: reviewerShape{UserID: d.ReviewedByUserID, AdminID: d.ReviewedByAdminID}, } }