- quizzes_list_quizzes and quizzes_show_quiz bound to GET /api/v1/quizzes
and /api/v1/quizzes/{slug} behind user.api_token, the per-user
golem15.quizzes.manage throttle, a scope and the access_quizzes permission
- the permission is grantable through pact.HasFrontendPermissions
- owner JSON shapes with solutions in manage_json.go, never used by a
student route
98 lines
3.1 KiB
Go
98 lines
3.1 KiB
Go
package classes
|
|
|
|
// Owner-side management behind the manage routes under /api/v1/quizzes,
|
|
// which the MCP quiz tools bind. There is no owner column: a user whose
|
|
// groups grant golem15.quizzes.access_quizzes manages every quiz, as in the
|
|
// admin (A7). Quizzes are addressed by slug.
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
|
|
"git.golem15.com/golem15/sm-quizzes-plugin/models"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// ManagedQuizRow is one quiz of the owner list with its question count and
|
|
// the number of completed attempts (of every user).
|
|
type ManagedQuizRow struct {
|
|
Quiz models.Quiz
|
|
QuestionCount int
|
|
AttemptsCount int
|
|
}
|
|
|
|
// ListManagedQuizzes returns every quiz, published or not, optionally only
|
|
// those whose context equals quizContext, ordered by context (quizzes
|
|
// without one last), title and id.
|
|
func ListManagedQuizzes(ctx context.Context, db *gorm.DB, quizContext string) ([]ManagedQuizRow, error) {
|
|
db = db.WithContext(ctx)
|
|
q := db.Model(&models.Quiz{})
|
|
if quizContext != "" {
|
|
q = q.Where("context = ?", quizContext)
|
|
}
|
|
var quizzes []models.Quiz
|
|
if err := q.Order("context ASC NULLS LAST, title ASC, id ASC").Find(&quizzes).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
out := make([]ManagedQuizRow, 0, len(quizzes))
|
|
if len(quizzes) == 0 {
|
|
return out, nil
|
|
}
|
|
ids := make([]uint, len(quizzes))
|
|
for i, quiz := range quizzes {
|
|
ids[i] = quiz.ID
|
|
}
|
|
type countRow struct {
|
|
QuizID uint
|
|
N int
|
|
}
|
|
var questions, attempts []countRow
|
|
if err := db.Model(&models.Question{}).Select("quiz_id, COUNT(*) AS n").
|
|
Where("quiz_id IN ?", ids).Group("quiz_id").Scan(&questions).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
if err := db.Model(&models.Attempt{}).Select("quiz_id, COUNT(*) AS n").
|
|
Where("quiz_id IN ? AND completed_at IS NOT NULL", ids).Group("quiz_id").Scan(&attempts).Error; err != nil {
|
|
return nil, err
|
|
}
|
|
questionsBy := map[uint]int{}
|
|
for _, c := range questions {
|
|
questionsBy[c.QuizID] = c.N
|
|
}
|
|
attemptsBy := map[uint]int{}
|
|
for _, c := range attempts {
|
|
attemptsBy[c.QuizID] = c.N
|
|
}
|
|
for _, quiz := range quizzes {
|
|
out = append(out, ManagedQuizRow{Quiz: quiz, QuestionCount: questionsBy[quiz.ID], AttemptsCount: attemptsBy[quiz.ID]})
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// ManagedQuiz loads a quiz, published or not, with every question in sort
|
|
// order and every answer of each, solutions included. A missing slug is
|
|
// ErrNotFound.
|
|
func ManagedQuiz(ctx context.Context, db *gorm.DB, slug string) (*models.Quiz, error) {
|
|
var quiz models.Quiz
|
|
err := db.WithContext(ctx).
|
|
Preload("Questions", func(tx *gorm.DB) *gorm.DB { return tx.Order("sort_order ASC, id ASC") }).
|
|
Preload("Questions.Answers", func(tx *gorm.DB) *gorm.DB { return tx.Order("sort_order ASC, id ASC") }).
|
|
Where("slug = ?", slug).Take(&quiz).Error
|
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
|
return nil, ErrNotFound
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &quiz, nil
|
|
}
|
|
|
|
// CompletedAttemptCount counts the completed attempts of every user on a
|
|
// quiz.
|
|
func CompletedAttemptCount(ctx context.Context, db *gorm.DB, quizID uint) (int, error) {
|
|
var n int64
|
|
err := db.WithContext(ctx).Model(&models.Attempt{}).
|
|
Where("quiz_id = ? AND completed_at IS NOT NULL", quizID).Count(&n).Error
|
|
return int(n), err
|
|
}
|