package quizzes import ( "git.golem15.com/golem15/sm-quizzes-plugin/controllers/api" "git.golem15.com/golem15/summercms/modules/pact" "git.golem15.com/golem15/summercms/modules/surf" ) var _ pact.HasRoutes = (*Plugin)(nil) // ManagePrefix is the path prefix of the management API. const ManagePrefix = "/api/v1/quizzes" // ReviewPrefix is the path prefix of the owner review routes (the MCP // review tools). It is a separate prefix on purpose: under ManagePrefix a // literal segment such as /reviews would shadow the quiz whose slug it is. const ReviewPrefix = "/api/v1/quiz-reviews" // permQuizzes is the permission that lets a user manage quizzes, in the // admin and through the management API. const permQuizzes = "golem15.quizzes.access_quizzes" // bucketManage limits the owner manage routes (the MCP quiz tools). It runs // after user.api_token, so it keys by user. const bucketManage = "golem15.quizzes.manage" // Routes mounts the quiz API under /_quizzes/api/v1 behind golem15.user's // jwt.auth middleware, and the management API under /api/v1/quizzes behind // its application-wide API token: the import route, and the owner manage // routes the MCP quiz tools bind, which also need the access_quizzes // permission and run under the per-user golem15.quizzes.manage throttle. // The owner review routes under /api/v1/quiz-reviews (the MCP review // tools) run behind the same middleware and permission (D-13). func (p *Plugin) Routes(r pact.Router) error { h := api.New(p.app) r.Group("/_quizzes/api/v1", surf.Use("jwt.auth"), func(g pact.Router) { g.Get("/quizzes", h.ListQuizzes) g.Get("/quizzes/{slug}", h.ShowQuiz) g.Where("slug", `[a-z0-9-]+`) g.Post("/quizzes/{slug}/attempts", h.StartAttempt) g.Where("slug", `[a-z0-9-]+`) g.Get("/attempts", h.ListAttempts) g.Get("/attempts/{id}", h.ShowAttempt) g.Where("id", `[0-9]+`) g.Post("/attempts/{id}/answers", h.Answer) g.Where("id", `[0-9]+`) g.Post("/attempts/{id}/finish", h.Finish) g.Where("id", `[0-9]+`) }) // quizzes:import over HTTP. The token's scope limits delegation; // user.permission checks the owner's current group permissions. r.Group(ManagePrefix, surf.Use("throttle:60,1", "user.api_token"), func(g pact.Router) { g.Post("/import", h.ImportQuiz, "user.scope:write", "user.permission:"+permQuizzes) }) // The owner manage routes (MCP). user.api_token runs first so the // bucket keys by user; every route needs the permission, so a student // whose groups lack it never lists or reaches these tools. perm := "user.permission:" + permQuizzes r.Group(ManagePrefix, surf.Use("user.api_token", "throttle:"+bucketManage), func(g pact.Router) { g.Get("", h.ManageListQuizzes, "user.scope:read", perm) g.Get("/{slug}", h.ManageShowQuiz, "user.scope:read", perm) g.Where("slug", `[a-z0-9-]+`) g.Post("", h.ManageCreateQuiz, "user.scope:write", perm) g.Patch("/{slug}", h.ManageUpdateQuiz, "user.scope:write", perm) g.Where("slug", `[a-z0-9-]+`) g.Delete("/{slug}", h.ManageDeleteQuiz, "user.scope:write", perm) g.Where("slug", `[a-z0-9-]+`) g.Post("/{slug}/publish", h.ManagePublish, "user.scope:write", perm) g.Where("slug", `[a-z0-9-]+`) g.Post("/{slug}/unpublish", h.ManageUnpublish, "user.scope:write", perm) g.Where("slug", `[a-z0-9-]+`) g.Get("/{slug}/stats", h.ManageQuizStats, "user.scope:read", perm) g.Where("slug", `[a-z0-9-]+`) g.Post("/{slug}/questions", h.ManageCreateQuestion, "user.scope:write", perm) g.Where("slug", `[a-z0-9-]+`) g.Patch("/{slug}/questions/{id}", h.ManageUpdateQuestion, "user.scope:write", perm) g.Where("slug", `[a-z0-9-]+`) g.Where("id", "[0-9]+") g.Delete("/{slug}/questions/{id}", h.ManageDeleteQuestion, "user.scope:write", perm) g.Where("slug", `[a-z0-9-]+`) g.Where("id", "[0-9]+") g.Put("/{slug}/questions/order", h.ManageReorderQuestions, "user.scope:write", perm) g.Where("slug", `[a-z0-9-]+`) }) // The owner review routes (MCP review tools): the queue of open // answers and the owner's decisions on them. r.Group(ReviewPrefix, surf.Use("user.api_token", "throttle:"+bucketManage), func(g pact.Router) { g.Get("", h.ReviewList, "user.scope:read", perm) g.Get("/{id}", h.ReviewShow, "user.scope:read", perm) g.Where("id", "[0-9]+") g.Post("/{id}/confirm", h.ReviewConfirm, "user.scope:write", perm) g.Where("id", "[0-9]+") g.Post("/{id}/score", h.ReviewSetScore, "user.scope:write", perm) g.Where("id", "[0-9]+") g.Post("/confirm", h.ReviewConfirmBulk, "user.scope:write", perm) g.Post("/regrade", h.ReviewRegrade, "user.scope:write", perm) }) return nil }