Some checks failed
CI/CD / detect-changes (pull_request) Successful in 4s
CI/CD / test-frontend (pull_request) Successful in 4s
CI/CD / test-go-pg (pull_request) Failing after 2m18s
CI/CD / test-sqlite (pull_request) Successful in 2m40s
CI/CD / build-and-deploy (pull_request) Has been skipped
Default surface routing (v0.2.1): - `/` redirects to configurable default surface with fallback chain: configured → first enabled extension surface → /admin - Auto-sets default_surface on first extension surface install - Admin settings UI: Default Surface dropdown - disabledRedirect() and extension surface disabled redirect now go to /admin instead of / to prevent redirect loops Fix Gin route param conflict causing backend startup hang: - Team package settings routes used `:pkgId` while sibling routes used `:id` — Gin's radix tree entered infinite loop on conflicting param names. Unified to `:id`. Docker entrypoint hardening: - Health check timeout 10s → 60s - Stale backend process cleanup on restart - Crash detection (exit early if backend dies) Roadmap direction updates: - v0.3.0: Notes surface (Obsidian-style) replaces Editor surface - v0.4.0: MVP (was v0.5.0) — chat moved to post-MVP - No built-in auto-install — explicit install only Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
123 lines
3.6 KiB
Go
123 lines
3.6 KiB
Go
package handlers
|
|
|
|
// team_package_settings.go — v0.2.0
|
|
//
|
|
// Team-admin endpoints for managing team-scoped package settings.
|
|
// These sit in the settings cascade between global (admin) and user.
|
|
// Only overridable keys (user_overridable != false) can be set at team scope.
|
|
|
|
import (
|
|
"encoding/json"
|
|
"strings"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
|
|
"switchboard-core/store"
|
|
)
|
|
|
|
// TeamPackageSettingsHandler serves team-scoped package settings endpoints.
|
|
type TeamPackageSettingsHandler struct {
|
|
stores store.Stores
|
|
}
|
|
|
|
func NewTeamPackageSettingsHandler(s store.Stores) *TeamPackageSettingsHandler {
|
|
return &TeamPackageSettingsHandler{stores: s}
|
|
}
|
|
|
|
// GetTeamPackageSettings returns the team-scoped settings for a package,
|
|
// along with the overridable schema keys for the UI.
|
|
// GET /api/v1/teams/:teamId/packages/:id/settings
|
|
func (h *TeamPackageSettingsHandler) GetTeamPackageSettings(c *gin.Context) {
|
|
teamID := c.Param("teamId")
|
|
pkgID := c.Param("id")
|
|
|
|
pkg, err := h.stores.Packages.Get(c.Request.Context(), pkgID)
|
|
if err != nil || pkg == nil {
|
|
c.JSON(404, gin.H{"error": "package not found"})
|
|
return
|
|
}
|
|
|
|
// Parse schema and filter to overridable keys only
|
|
schema := store.ParseSettingsSchema(pkg.Manifest)
|
|
var overridableSchema []store.SettingSchema
|
|
for _, s := range schema {
|
|
if s.IsUserOverridable() {
|
|
overridableSchema = append(overridableSchema, s)
|
|
}
|
|
}
|
|
|
|
// Load current team values
|
|
settings, err := h.stores.Packages.GetTeamSettings(c.Request.Context(), pkgID, teamID)
|
|
if err != nil {
|
|
settings = json.RawMessage("{}")
|
|
}
|
|
|
|
c.JSON(200, gin.H{
|
|
"schema": overridableSchema,
|
|
"values": json.RawMessage(settings),
|
|
})
|
|
}
|
|
|
|
// UpdateTeamPackageSettings saves team-scoped overrides for a package.
|
|
// Non-overridable keys are stripped before saving.
|
|
// PUT /api/v1/teams/:teamId/packages/:id/settings
|
|
func (h *TeamPackageSettingsHandler) UpdateTeamPackageSettings(c *gin.Context) {
|
|
teamID := c.Param("teamId")
|
|
pkgID := c.Param("id")
|
|
|
|
pkg, err := h.stores.Packages.Get(c.Request.Context(), pkgID)
|
|
if err != nil || pkg == nil {
|
|
c.JSON(404, gin.H{"error": "package not found"})
|
|
return
|
|
}
|
|
|
|
var body struct {
|
|
Settings json.RawMessage `json:"settings" binding:"required"`
|
|
}
|
|
if err := c.ShouldBindJSON(&body); err != nil {
|
|
c.JSON(400, gin.H{"error": "invalid request body"})
|
|
return
|
|
}
|
|
|
|
// Parse and enforce user_overridable
|
|
schema := store.ParseSettingsSchema(pkg.Manifest)
|
|
var incoming map[string]any
|
|
if err := json.Unmarshal(body.Settings, &incoming); err != nil {
|
|
c.JSON(400, gin.H{"error": "settings must be a JSON object"})
|
|
return
|
|
}
|
|
|
|
filtered, rejected := store.FilterOverridableKeys(incoming, schema)
|
|
if len(filtered) == 0 && len(rejected) > 0 {
|
|
c.JSON(400, gin.H{"error": "these settings cannot be overridden: " + strings.Join(rejected, ", ")})
|
|
return
|
|
}
|
|
|
|
settingsJSON, err := json.Marshal(filtered)
|
|
if err != nil {
|
|
c.JSON(500, gin.H{"error": "failed to marshal settings"})
|
|
return
|
|
}
|
|
|
|
if err := h.stores.Packages.SetTeamSettings(c.Request.Context(), pkgID, teamID, json.RawMessage(settingsJSON)); err != nil {
|
|
c.JSON(500, gin.H{"error": "failed to save team settings"})
|
|
return
|
|
}
|
|
|
|
c.JSON(200, gin.H{"ok": true, "rejected_keys": rejected})
|
|
}
|
|
|
|
// DeleteTeamPackageSettings removes team-scoped overrides for a package.
|
|
// DELETE /api/v1/teams/:teamId/packages/:id/settings
|
|
func (h *TeamPackageSettingsHandler) DeleteTeamPackageSettings(c *gin.Context) {
|
|
teamID := c.Param("teamId")
|
|
pkgID := c.Param("id")
|
|
|
|
if err := h.stores.Packages.DeleteTeamSettings(c.Request.Context(), pkgID, teamID); err != nil {
|
|
c.JSON(500, gin.H{"error": "failed to delete team settings"})
|
|
return
|
|
}
|
|
|
|
c.JSON(200, gin.H{"ok": true})
|
|
}
|