This repository has been archived on 2026-04-03. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
core/server/handlers/team_package_settings.go
Jeffrey Smith 70678cb352
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
Feat default surface routing + fix startup hang (#4)
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>
2026-03-26 19:38:02 +00:00

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})
}