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:
42
plugin.go
42
plugin.go
@@ -4,14 +4,19 @@ import (
|
||||
"context"
|
||||
"embed"
|
||||
"io/fs"
|
||||
"net/http"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"git.golem15.com/golem15/sm-quizzes-plugin/console"
|
||||
"git.golem15.com/golem15/sm-quizzes-plugin/models"
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
"git.golem15.com/golem15/summercms/modules/bonfire"
|
||||
"git.golem15.com/golem15/summercms/modules/bouncer"
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon"
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
"git.golem15.com/golem15/summercms/modules/party"
|
||||
"git.golem15.com/golem15/summercms/modules/surf"
|
||||
"github.com/go-gormigrate/gormigrate/v2"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
@@ -28,6 +33,9 @@ var (
|
||||
_ pact.AdminAssets = (*Plugin)(nil)
|
||||
_ pact.HasPermissions = (*Plugin)(nil)
|
||||
_ pact.HasNavigation = (*Plugin)(nil)
|
||||
|
||||
_ pact.HasFrontendPermissions = (*Plugin)(nil)
|
||||
_ surf.BucketProvider = (*Plugin)(nil)
|
||||
)
|
||||
|
||||
//go:embed config
|
||||
@@ -115,11 +123,43 @@ func (p *Plugin) AdminFS() fs.FS { return adminFS }
|
||||
// Permissions declares the admin permissions.
|
||||
func (p *Plugin) Permissions() []pact.Permission {
|
||||
return []pact.Permission{
|
||||
{Code: "golem15.quizzes.access_quizzes", Tab: "golem15.quizzes::lang.plugin.name", Label: "golem15.quizzes::lang.permissions.access_quizzes"},
|
||||
{Code: permQuizzes, Tab: "golem15.quizzes::lang.plugin.name", Label: "golem15.quizzes::lang.permissions.access_quizzes"},
|
||||
{Code: "golem15.quizzes.access_attempts", Tab: "golem15.quizzes::lang.plugin.name", Label: "golem15.quizzes::lang.permissions.access_attempts"},
|
||||
}
|
||||
}
|
||||
|
||||
// FrontendPermissions declares the code the owner manage routes check (the
|
||||
// MCP quiz tools), so golem15.user's User Groups editor can grant it.
|
||||
// Declaring it grants nothing by itself.
|
||||
func (p *Plugin) FrontendPermissions() []pact.FrontendPermission {
|
||||
return []pact.FrontendPermission{{
|
||||
Code: permQuizzes,
|
||||
Label: "golem15.quizzes::lang.permissions.access_quizzes",
|
||||
Tab: "golem15.quizzes::lang.plugin.name",
|
||||
}}
|
||||
}
|
||||
|
||||
// Buckets declares the per-user rate limit of the owner manage routes:
|
||||
// building a quiz question by question is chatty, so 120 calls a minute.
|
||||
// A request without a user (never after user.api_token) keys by client IP.
|
||||
func (p *Plugin) Buckets() map[string]surf.Bucket {
|
||||
trusted := surf.TrustedProxies(nil)
|
||||
if p != nil && p.app != nil {
|
||||
trusted = surf.TrustedProxies(p.app.Config)
|
||||
}
|
||||
byUser := func(prefix string) func(*http.Request) string {
|
||||
return func(r *http.Request) string {
|
||||
if u, ok := bouncer.User(r.Context()); ok && u.ID != 0 {
|
||||
return prefix + "|u:" + strconv.FormatUint(uint64(u.ID), 10)
|
||||
}
|
||||
return prefix + "|ip:" + surf.ClientIP(r, trusted)
|
||||
}
|
||||
}
|
||||
return map[string]surf.Bucket{
|
||||
bucketManage: {Max: 120, Decay: time.Minute, Key: byUser(bucketManage)},
|
||||
}
|
||||
}
|
||||
|
||||
// Navigation declares the "Quizzes" admin menu with its side menu.
|
||||
func (p *Plugin) Navigation() []pact.NavigationItem {
|
||||
return []pact.NavigationItem{{
|
||||
|
||||
Reference in New Issue
Block a user