package handlers // profile_bootstrap.go — Single-call boot payload for the SDK. // // GET /api/v1/profile/bootstrap // // Collapses what previously required 3-4 sequential requests // (profile, permissions, teams/mine, settings) into one call. // The SDK calls this at startup and on token refresh. // // v0.37.15 import ( "net/http" "sort" "github.com/gin-gonic/gin" "switchboard-core/auth" "switchboard-core/store" ) // ProfileBootstrapHandler serves the combined boot payload. type ProfileBootstrapHandler struct { stores store.Stores } func NewProfileBootstrapHandler(s store.Stores) *ProfileBootstrapHandler { return &ProfileBootstrapHandler{stores: s} } // GetBootstrap returns everything the shell needs at startup. // GET /api/v1/profile/bootstrap func (h *ProfileBootstrapHandler) GetBootstrap(c *gin.Context) { userID := getUserID(c) ctx := c.Request.Context() // ── User profile ──────────────────────── user, err := h.stores.Users.GetByID(ctx, userID) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "user not found"}) return } userPayload := gin.H{ "id": user.ID, "username": user.Username, "display_name": user.DisplayName, "email": user.Email, } if user.AvatarURL != "" { userPayload["avatar"] = user.AvatarURL } // ── Permissions ───────────────────────── perms, err := auth.ResolvePermissions(ctx, h.stores, userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to resolve permissions"}) return } permList := make([]string, 0, len(perms)) for p := range perms { permList = append(permList, p) } sort.Strings(permList) // ── Groups ────────────────────────────── groupIDs, _ := h.stores.Groups.GetUserGroupIDs(ctx, userID) if groupIDs == nil { groupIDs = []string{} } // ── Teams ─────────────────────────────── teams, _ := h.stores.Teams.ListForUser(ctx, userID) teamData := make([]gin.H, 0, len(teams)) for _, t := range teams { teamData = append(teamData, gin.H{ "id": t.ID, "name": t.Name, "my_role": t.MyRole, }) } // ── Policies ──────────────────────────── policies := make(map[string]bool) if ps := h.stores.Policies; ps != nil { policies["allow_user_byok"], _ = ps.GetBool(ctx, "allow_user_byok") policies["allow_user_personas"], _ = ps.GetBool(ctx, "allow_user_personas") } // ── Settings ──────────────────────────── settings := make(map[string]interface{}) if user.Settings != nil { settings = user.Settings } // ── Response ──────────────────────────── c.JSON(http.StatusOK, gin.H{ "user": userPayload, "permissions": permList, "groups": groupIDs, "teams": teamData, "policies": policies, "settings": settings, }) }