package cabana import ( "context" "errors" "fmt" "time" "git.golem15.com/golem15/summercms/modules/pact" "git.golem15.com/golem15/summercms/modules/party" "git.golem15.com/golem15/summercms/modules/phrasebook" "gorm.io/gorm" ) // CompiledListSchema compiles the admin controllers of plugins without a // database or config, as Activate does, and returns the list schema of the // controller id (with its reorder object). func CompiledListSchema(plugins []party.Plugin, id string) (*ListSchema, error) { items, err := collectControllers(plugins) if err != nil { return nil, err } types, err := compileFieldTypes(plugins) if err != nil { return nil, err } reg, err := compileRegistryWithTypes(items, types) if err != nil { return nil, err } cc, ok := reg.Get(id) if !ok || cc.List == nil { return nil, fmt.Errorf("cabana: no list schema for %s", id) } return cc.List, nil } // AdminAPIRoutes exposes the admin API route inventory (method plus // prefix-relative path) to the external conformance test. func AdminAPIRoutes() []string { out := make([]string, 0, len(phase09Routes)) for _, route := range phase09Routes { if !route.spa { out = append(out, route.key) } } return out } // IssueTestAPIToken mints a personal API token for ownerID with the given // scopes and optional expiry. Used by external Postgres smoke tests before // the HTTP create route exists. func IssueTestAPIToken(db *gorm.DB, ownerID uint, scopes []string, expiresAt *time.Time) (plain string, err error) { plain, _, err = insertAPIToken(db, ownerID, "test", scopes, expiresAt) return plain, err } // ValidateMoneyValue exposes the money field's save check (phrase key and // placeholders of the first failure, "" when accepted) to the external // money field tests. func ValidateMoneyValue(value any, scale int, max string) (string, map[string]string) { return validateMoneyValue(value, scale, max) } // CheckMoneyType exposes the money field's model column gate to the // external money field tests. func CheckMoneyType(model any, field FormField) error { return checkMoneyType(model, field) } // ProjectRow exposes one list row's projection to the external money // column tests. func ProjectRow(row any, controller pact.AdminController, cols []ListColumn) map[string]any { return projectRow(row, controller, cols) } // CheckTaglistValues exposes the taglist save check (every field allowed) // to the external taglist tests: it rewrites body in place and returns the // *ValidationError of the refused values, or nil. func CheckTaglistValues(ctx context.Context, tr *phrasebook.Translator, fields []FormField, body map[string]any, create bool) error { return checkTaglistValues(ctx, tr, fields, body, nil, create) } // HydrateTaglistFields exposes the record GET's taglist hydration to the // external taglist tests. func HydrateTaglistFields(fields []FormField, data map[string]any) { hydrateTaglistFields(fields, data) } // CheckReorderNodes exposes the reorder save's node check to the external // reorder tests, over a reorder that is flat or a tree with maxDepth. func CheckReorderNodes(flat bool, maxDepth int, locked []AdminReorderNode, nodes []AdminReorderNodeInput, flatParent string) error { ro := &compiledReorder{flat: flat, maxDepth: maxDepth} if !flat { ro.parentColumn = "parent_id" } ro.sortColumn = "sort_order" return checkReorderNodes(ro, locked, nodes, flatParent) } // IsReorderStale reports whether err is the reorder save's 409 (the // submitted node set differs from the stored one). func IsReorderStale(err error) bool { var stale reorderStale return errors.As(err, &stale) }