refactor: drop the quiz import and the golem15.quizzes dependency

golem15.quizzes now serves its own import at /api/v1/quizzes/import behind
the same shared API token.
This commit is contained in:
Jakub Zych
2026-10-04 23:15:01 +02:00
parent 97000dcd1e
commit 840935d22d
6 changed files with 7 additions and 52 deletions

View File

@@ -12,7 +12,6 @@ import (
"time"
"unicode"
quizzes "git.golem15.com/golem15/sm-quizzes-plugin/classes"
usermodels "git.golem15.com/golem15/sm-user-plugin/models"
"git.golem15.com/golem15/summercms/modules/bouncer"
"git.golem15.com/golem15/summercms/modules/lagoon"
@@ -134,41 +133,6 @@ func (h *Handlers) ApplySeed(w http.ResponseWriter, r *http.Request) {
})
}
// ImportQuiz answers POST /quizzes?replace=: quizzes:import over HTTP. The
// body is the quiz document of golem15.quizzes. An existing slug is a 409
// unless replace is on.
func (h *Handlers) ImportQuiz(w http.ResponseWriter, r *http.Request) {
raw, ok := body(w, r)
if !ok {
return
}
doc, err := quizzes.ParseImport(raw)
if err != nil {
writeInvalid(w, err.Error())
return
}
db, ok := h.db(w, r)
if !ok {
return
}
res, err := quizzes.Import(r.Context(), db, doc, flag(r, "replace"))
if errors.Is(err, quizzes.ErrQuizExists) {
wire.WriteJSON(w, http.StatusConflict, map[string]any{"error": true, "message": "A quiz with the slug " + doc.Slug + " already exists; pass replace=1 to replace its questions."})
return
}
if err != nil {
writeOpaque500(w)
return
}
writeData(w, map[string]any{
"id": res.Quiz.ID,
"slug": res.Quiz.Slug,
"created": res.Created,
"questions": res.Questions,
"answers": res.Answers,
})
}
// managedMaterialJSON is a material as the management API shows it: drafts
// too, so published_at may be null.
type managedMaterialJSON struct {

3
go.mod
View File

@@ -3,7 +3,6 @@ module git.golem15.com/jakub/sm-bm-plugin
go 1.27.0
require (
git.golem15.com/golem15/sm-quizzes-plugin v0.0.0
git.golem15.com/golem15/sm-user-plugin v0.0.0
git.golem15.com/golem15/summercms v0.0.0
github.com/go-gormigrate/gormigrate/v2 v2.1.7
@@ -122,5 +121,3 @@ require (
replace git.golem15.com/golem15/summercms => ../../../../summercms.go
replace git.golem15.com/golem15/sm-user-plugin => ../../golem15/user
replace git.golem15.com/golem15/sm-quizzes-plugin => ../../golem15/quizzes

View File

@@ -54,9 +54,8 @@ type Plugin struct {
func (p *Plugin) ID() string { return "jz.bm" }
// Requires golem15.user: its users table holds the students, and its
// jwt.auth middleware guards the API. golem15.quizzes holds the quizzes the
// management API imports.
func (p *Plugin) Requires() []string { return []string{"golem15.user", "golem15.quizzes"} }
// jwt.auth and user.api_token middleware guard the APIs.
func (p *Plugin) Requires() []string { return []string{"golem15.user"} }
func (p *Plugin) Register(app *backpack.App) error {
p.app = app

View File

@@ -7,7 +7,6 @@ import (
"strings"
"testing"
_ "git.golem15.com/golem15/sm-quizzes-plugin"
_ "git.golem15.com/golem15/sm-user-plugin"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/bonfire"
@@ -48,11 +47,10 @@ func testConfig(t *testing.T) *compass.Config {
return cfg
}
// activate boots the plugin and both declared dependencies the way the
// generated main does.
// activate boots golem15.user and jz.bm the way the generated main does.
func activate(t *testing.T, app *backpack.App) []party.Plugin {
t.Helper()
plugins, err := party.Activate(app, []string{"golem15.user", "golem15.quizzes", "jz.bm"})
plugins, err := party.Activate(app, []string{"golem15.user", "jz.bm"})
if err != nil {
t.Fatalf("Activate: %v", err)
}
@@ -73,7 +71,7 @@ func bmPlugin(t *testing.T, plugins []party.Plugin) *bm.Plugin {
func TestPluginActivates(t *testing.T) {
app := backpack.New(testConfig(t))
p := bmPlugin(t, activate(t, app))
if p.ID() != "jz.bm" || !slices.Equal(p.Requires(), []string{"golem15.user", "golem15.quizzes"}) {
if p.ID() != "jz.bm" || !slices.Equal(p.Requires(), []string{"golem15.user"}) {
t.Errorf("ID %q Requires %v", p.ID(), p.Requires())
}
if n := len(p.Models()); n != 10 {
@@ -125,7 +123,6 @@ func TestRoutes(t *testing.T) {
"POST /api/v1/bm/materials",
"DELETE /api/v1/bm/materials/{id}",
"POST /api/v1/bm/seed",
"POST /api/v1/bm/quizzes",
}
var got []string
sawFeed := false

View File

@@ -91,7 +91,7 @@ func (p *Plugin) Routes(r pact.Router) error {
g.Post("/calendar-feed/rotate", h.RotateCalendarFeed, "throttle:10,1")
g.Delete("/calendar-feed", h.DeleteCalendarFeed)
})
// The management API: what bm:seed and quizzes:import do, plus materials,
// The management API: what bm:seed does, plus materials,
// behind the application-wide user API token. Scope limits delegation;
// user.permission checks the owner's current group permissions.
r.Group(ManagePrefix, surf.Use(bucketManage, "user.api_token"), func(g pact.Router) {
@@ -105,7 +105,6 @@ func (p *Plugin) Routes(r pact.Router) error {
"user.permission:"+controllers.PermCourses,
"user.permission:"+controllers.PermSchedule,
"user.permission:"+controllers.PermMaterials)
g.Post("/quizzes", h.ImportQuiz, "user.scope:write", "user.permission:golem15.quizzes.access_quizzes")
})
// The calendar feed: public, because calendar clients fetch it without
// credentials; the token in the path is the credential. ServeMux cannot

View File

@@ -10,7 +10,6 @@ import (
"testing"
"testing/fstest"
_ "git.golem15.com/golem15/sm-quizzes-plugin"
_ "git.golem15.com/golem15/sm-user-plugin"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/compass"
@@ -220,7 +219,7 @@ func TestStaticThroughRouter(t *testing.T) {
t.Fatal(err)
}
app := backpack.New(cfg)
plugins, err := party.Activate(app, []string{"golem15.user", "golem15.quizzes", "jz.bm"})
plugins, err := party.Activate(app, []string{"golem15.user", "jz.bm"})
if err != nil {
t.Fatal(err)
}