feat: list and show quizzes over MCP for their owner
- 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
This commit is contained in:
97
classes/manage.go
Normal file
97
classes/manage.go
Normal file
@@ -0,0 +1,97 @@
|
||||
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
|
||||
}
|
||||
Reference in New Issue
Block a user