package handlers // persona_groups.go — Persona group (roster template) CRUD (v0.23.2) // // v0.29.0: Raw SQL replaced with PersonaGroupStore methods. import ( "net/http" "strings" "github.com/gin-gonic/gin" "chat-switchboard/models" "chat-switchboard/store" ) type PersonaGroupHandler struct { stores store.Stores } func NewPersonaGroupHandler(s store.Stores) *PersonaGroupHandler { return &PersonaGroupHandler{stores: s} } // ── List ──────────────────────────────────────── func (h *PersonaGroupHandler) List(c *gin.Context) { userID := getUserID(c) groups, err := h.stores.PersonaGroups.List(c.Request.Context(), userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to list groups"}) return } for i := range groups { members, _ := h.stores.PersonaGroups.ListMembers(c.Request.Context(), groups[i].ID) groups[i].Members = members } c.JSON(http.StatusOK, gin.H{"data": groups}) } // ── Get ───────────────────────────────────────── func (h *PersonaGroupHandler) Get(c *gin.Context) { userID := getUserID(c) id := c.Param("id") g, err := h.stores.PersonaGroups.Get(c.Request.Context(), id, userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to get group"}) return } if g == nil { c.JSON(http.StatusNotFound, gin.H{"error": "group not found"}) return } members, _ := h.stores.PersonaGroups.ListMembers(c.Request.Context(), g.ID) if members == nil { members = []models.PersonaGroupMember{} } g.Members = members c.JSON(http.StatusOK, g) } // ── Create ────────────────────────────────────── func (h *PersonaGroupHandler) Create(c *gin.Context) { userID := getUserID(c) var req struct { Name string `json:"name" binding:"required,min=1,max=100"` Description string `json:"description"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } g := &models.PersonaGroup{ Name: strings.TrimSpace(req.Name), Description: req.Description, OwnerID: userID, Scope: "personal", } if err := h.stores.PersonaGroups.Create(c.Request.Context(), g); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to create group"}) return } g.Members = []models.PersonaGroupMember{} c.JSON(http.StatusCreated, g) } // ── Update ────────────────────────────────────── func (h *PersonaGroupHandler) Update(c *gin.Context) { userID := getUserID(c) id := c.Param("id") var req struct { Name *string `json:"name"` Description *string `json:"description"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Verify ownership ownerID, err := h.stores.PersonaGroups.GetOwnerID(c.Request.Context(), id) if err != nil || ownerID == "" { c.JSON(http.StatusNotFound, gin.H{"error": "group not found"}) return } if ownerID != userID { c.JSON(http.StatusForbidden, gin.H{"error": "not your group"}) return } fields := map[string]interface{}{} if req.Name != nil { fields["name"] = strings.TrimSpace(*req.Name) } if req.Description != nil { fields["description"] = *req.Description } if len(fields) > 0 { _ = h.stores.PersonaGroups.Update(c.Request.Context(), id, fields) } c.JSON(http.StatusOK, gin.H{"ok": true}) } // ── Delete ────────────────────────────────────── func (h *PersonaGroupHandler) Delete(c *gin.Context) { userID := getUserID(c) id := c.Param("id") n, err := h.stores.PersonaGroups.Delete(c.Request.Context(), id, userID) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "delete failed"}) return } if n == 0 { c.JSON(http.StatusNotFound, gin.H{"error": "group not found"}) return } c.JSON(http.StatusOK, gin.H{"ok": true}) } // ── Add Member ────────────────────────────────── func (h *PersonaGroupHandler) AddMember(c *gin.Context) { userID := getUserID(c) groupID := c.Param("id") var req struct { PersonaID string `json:"persona_id" binding:"required"` IsLeader bool `json:"is_leader"` } if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } // Verify ownership ownerID, err := h.stores.PersonaGroups.GetOwnerID(c.Request.Context(), groupID) if err != nil || ownerID != userID { c.JSON(http.StatusForbidden, gin.H{"error": "not your group"}) return } if err := h.stores.PersonaGroups.AddMember(c.Request.Context(), groupID, req.PersonaID, req.IsLeader); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "failed to add member"}) return } c.JSON(http.StatusCreated, gin.H{"ok": true}) } // ── Remove Member ─────────────────────────────── func (h *PersonaGroupHandler) RemoveMember(c *gin.Context) { userID := getUserID(c) groupID := c.Param("id") memberID := c.Param("memberId") ownerID, err := h.stores.PersonaGroups.GetOwnerID(c.Request.Context(), groupID) if err != nil || ownerID != userID { c.JSON(http.StatusForbidden, gin.H{"error": "not your group"}) return } _ = h.stores.PersonaGroups.RemoveMember(c.Request.Context(), memberID, groupID) c.JSON(http.StatusOK, gin.H{"ok": true}) }