package handler import ( "net/http" "strings" "github.com/gin-gonic/gin" "github.com/sundynix/sundynix-gateway/internal/store" ) // SpacesList: GET /api/v1/me/spaces —— 当前活跃租户内我所属的工作区(供切换器)+ 当前活跃空间 id。 func (h *Handler) SpacesList(c *gin.Context) { rows, err := h.db.MySpaces(c.Request.Context(), tenantID(c), userID(c)) if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"spaces": rows, "active": spaceID(c)}) } // SwitchSpace: POST /api/v1/me/space {space_id} —— 切换当前活跃工作区(须为其成员)。 func (h *Handler) SwitchSpace(c *gin.Context) { var b struct { SpaceID string `json:"space_id"` } if err := c.ShouldBindJSON(&b); err != nil || b.SpaceID == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "space_id 必填"}) return } if err := h.db.SetActiveSpace(c.Request.Context(), userID(c), b.SpaceID); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "ok", "active": b.SpaceID}) } // SpaceCurrent: GET /api/v1/spaces/current —— 当前活跃工作区上下文 + 我在其中的角色。 func (h *Handler) SpaceCurrent(c *gin.Context) { sid := spaceID(c) if sid == "" { c.JSON(http.StatusOK, gin.H{"space": nil}) return } s, _ := h.db.GetSpace(c.Request.Context(), sid) if s == nil { c.JSON(http.StatusOK, gin.H{"space": nil}) return } c.JSON(http.StatusOK, gin.H{ "space": gin.H{"id": s.ID, "name": s.Name, "kind": s.Kind, "creator": s.Creator, "archived": s.Archived}, "role": h.db.SpaceMemberRole(c.Request.Context(), sid, userID(c)), }) } // SpaceCreate: POST /api/v1/spaces {name, kind} —— 在活跃租户建一个工作区(建者成为 owner)。 // 已由 RequireTenantRole(member) 门控:租户只读 viewer 进不来。 func (h *Handler) SpaceCreate(c *gin.Context) { var b struct { Name string `json:"name"` Kind string `json:"kind"` } if err := c.ShouldBindJSON(&b); err != nil || strings.TrimSpace(b.Name) == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "name 必填"}) return } kind := b.Kind if kind != store.SpaceKindProject && kind != store.SpaceKindTenant { kind = store.SpaceKindProject // 只允许经此建 project/tenant;personal 由系统建 } s, err := h.db.CreateSpace(c.Request.Context(), tenantID(c), b.Name, kind, userID(c)) if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"id": s.ID, "name": s.Name, "kind": s.Kind}) } // spaceAdminOr403 校验当前用户在指定空间是 admin/owner,否则写 403 并返回 false。 func (h *Handler) spaceAdminOr403(c *gin.Context, sid string) bool { if store.RoleRank(h.db.SpaceMemberRole(c.Request.Context(), sid, userID(c))) < store.RoleRank(store.RoleAdmin) { c.JSON(http.StatusForbidden, gin.H{"error": "需要空间管理员权限"}) return false } return true } // SpaceMembers: GET /api/v1/spaces/:id/members —— 空间成员列表(须为该空间成员)。 func (h *Handler) SpaceMembers(c *gin.Context) { sid := c.Param("id") if h.db.SpaceMemberRole(c.Request.Context(), sid, userID(c)) == "" { c.JSON(http.StatusForbidden, gin.H{"error": "你不是该空间的成员"}) return } rows, err := h.db.ListSpaceMembers(c.Request.Context(), sid) if err != nil { c.JSON(http.StatusBadGateway, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"members": rows}) } // SpaceAddMember: POST /api/v1/spaces/:id/members {email, role} —— 拉租户成员进空间(须空间 admin)。 func (h *Handler) SpaceAddMember(c *gin.Context) { sid := c.Param("id") if !h.spaceAdminOr403(c, sid) { return } var b struct { Email string `json:"email"` Role string `json:"role"` } if err := c.ShouldBindJSON(&b); err != nil || strings.TrimSpace(b.Email) == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "email 必填"}) return } if b.Role == "" { b.Role = store.RoleMember } m, err := h.db.AddSpaceMemberByEmail(c.Request.Context(), sid, b.Email, b.Role) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"member": m}) } // SpaceSetMemberRole: PUT /api/v1/spaces/:id/members/:uid {role} —— 改空间成员角色(须空间 admin)。 func (h *Handler) SpaceSetMemberRole(c *gin.Context) { sid := c.Param("id") if !h.spaceAdminOr403(c, sid) { return } var b struct { Role string `json:"role"` } if err := c.ShouldBindJSON(&b); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := h.db.SetSpaceMemberRole(c.Request.Context(), sid, c.Param("uid"), b.Role); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "ok"}) } // SpaceRemoveMember: DELETE /api/v1/spaces/:id/members/:uid —— 移除空间成员(须空间 admin)。 func (h *Handler) SpaceRemoveMember(c *gin.Context) { sid := c.Param("id") if !h.spaceAdminOr403(c, sid) { return } if err := h.db.RemoveSpaceMember(c.Request.Context(), sid, c.Param("uid")); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "ok"}) } // SpaceArchive: POST /api/v1/spaces/:id/archive —— 归档空间(须空间 admin;个人空间不可)。 func (h *Handler) SpaceArchive(c *gin.Context) { sid := c.Param("id") if !h.spaceAdminOr403(c, sid) { return } if err := h.db.ArchiveSpace(c.Request.Context(), sid); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"status": "ok"}) }