package handler import ( "net/http" "github.com/gin-gonic/gin" "flowstack/internal/database" "flowstack/internal/model" ) // ============================================================ // 流水线权限组(默认手动多选流水线;高级模式用 * 通配符匹配名称)—— 仅管理员 // ============================================================ type roleBody struct { Name string `json:"name"` ScopeMode string `json:"scopeMode"` // manual 手动多选 | pattern 名称匹配 Pattern string `json:"pattern"` // scopeMode=pattern 时生效,支持 * 通配符 PipelineIDs []uint `json:"pipelineIds"` Level string `json:"level"` Members []string `json:"members"` } func validateRoleBody(b *roleBody) string { if b.Name == "" { return "名称不能为空" } if b.ScopeMode == "" { b.ScopeMode = "manual" } if b.ScopeMode != "manual" && b.ScopeMode != "pattern" { return "授权范围类型不合法" } if b.ScopeMode == "pattern" && b.Pattern == "" { return "名称匹配需要填写匹配规则,如 cex-*" } if b.ScopeMode == "manual" && len(b.PipelineIDs) == 0 { return "请至少选择一条流水线" } if LevelRank(b.Level) == 0 { return "请选择权限等级" } return "" } // ListPipelineRoles GET /api/pipeline-roles —— 附匹配流水线数 func ListPipelineRoles(c *gin.Context) { var roles []model.PipelineRole database.DB.Order("id").Find(&roles) var pipelines []model.Pipeline database.DB.Select("id", "name", "jenkins_job_name").Find(&pipelines) type roleWithCount struct { model.PipelineRole MatchedPipelines int `json:"matchedPipelines"` } out := make([]roleWithCount, 0, len(roles)) for i := range roles { role := &roles[i] n := 0 for j := range pipelines { if roleMatchPipeline(role, &pipelines[j]) { n++ } } out = append(out, roleWithCount{PipelineRole: *role, MatchedPipelines: n}) } c.JSON(http.StatusOK, out) } // CreatePipelineRole POST /api/pipeline-roles func CreatePipelineRole(c *gin.Context) { var body roleBody if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if msg := validateRoleBody(&body); msg != "" { c.JSON(http.StatusBadRequest, gin.H{"error": msg}) return } role := model.PipelineRole{ Name: body.Name, ScopeMode: body.ScopeMode, Pattern: body.Pattern, PipelineIDs: body.PipelineIDs, Level: body.Level, Members: body.Members, } if role.PipelineIDs == nil { role.PipelineIDs = []uint{} } if role.Members == nil { role.Members = []string{} } if err := database.DB.Create(&role).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": "创建失败(名称可能已存在): " + err.Error()}) return } c.JSON(http.StatusOK, role) } // UpdatePipelineRole PUT /api/pipeline-roles/:id func UpdatePipelineRole(c *gin.Context) { var role model.PipelineRole if err := database.DB.First(&role, c.Param("id")).Error; err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "权限组不存在"}) return } var body roleBody if err := c.ShouldBindJSON(&body); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if msg := validateRoleBody(&body); msg != "" { c.JSON(http.StatusBadRequest, gin.H{"error": msg}) return } role.Name, role.ScopeMode, role.Pattern = body.Name, body.ScopeMode, body.Pattern role.PipelineIDs, role.Level, role.Members = body.PipelineIDs, body.Level, body.Members if role.PipelineIDs == nil { role.PipelineIDs = []uint{} } if role.Members == nil { role.Members = []string{} } if err := database.DB.Save(&role).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, role) } // DeletePipelineRole DELETE /api/pipeline-roles/:id func DeletePipelineRole(c *gin.Context) { database.DB.Delete(&model.PipelineRole{}, c.Param("id")) c.JSON(http.StatusOK, gin.H{"success": true}) } // ============================================================ // 单流水线成员(云效式直接授权)—— owner 或 admin // ============================================================ // ListPipelineMembers GET /api/pipelines/:id/members —— 直接成员 + 命中的权限组(只读参考) func ListPipelineMembers(c *gin.Context) { p, _ := c.Get("pipeline") pipeline := p.(*model.Pipeline) var members []model.PipelineMember database.DB.Where("pipeline_id = ?", pipeline.ID).Order("id").Find(&members) var roles []model.PipelineRole database.DB.Find(&roles) type matchedRole struct { Name string `json:"name"` Level string `json:"level"` Pattern string `json:"pattern"` Mode string `json:"mode"` Members []string `json:"members"` } matched := make([]matchedRole, 0) for i := range roles { role := &roles[i] if roleMatchPipeline(role, pipeline) { matched = append(matched, matchedRole{Name: role.Name, Level: role.Level, Pattern: role.Pattern, Mode: role.ScopeMode, Members: role.Members}) } } c.JSON(http.StatusOK, gin.H{"members": members, "matchedRoles": matched}) } // AddPipelineMember POST /api/pipelines/:id/members —— 已存在则更新等级 func AddPipelineMember(c *gin.Context) { p, _ := c.Get("pipeline") pipeline := p.(*model.Pipeline) var body struct { Username string `json:"username"` Level string `json:"level"` } if err := c.ShouldBindJSON(&body); err != nil || body.Username == "" { c.JSON(http.StatusBadRequest, gin.H{"error": "请选择用户"}) return } if LevelRank(body.Level) == 0 { c.JSON(http.StatusBadRequest, gin.H{"error": "请选择权限等级"}) return } var user model.User if err := database.DB.Where("username = ?", body.Username).First(&user).Error; err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "用户不存在(LDAP 用户请先导入或登录一次)"}) return } var member model.PipelineMember if err := database.DB.Where("pipeline_id = ? AND username = ?", pipeline.ID, body.Username).First(&member).Error; err == nil { member.Level = body.Level database.DB.Save(&member) c.JSON(http.StatusOK, member) return } member = model.PipelineMember{PipelineID: pipeline.ID, Username: body.Username, Level: body.Level} if err := database.DB.Create(&member).Error; err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, member) } // DeletePipelineMember DELETE /api/pipelines/:id/members/:mid func DeletePipelineMember(c *gin.Context) { p, _ := c.Get("pipeline") pipeline := p.(*model.Pipeline) database.DB.Where("pipeline_id = ? AND id = ?", pipeline.ID, c.Param("mid")).Delete(&model.PipelineMember{}) c.JSON(http.StatusOK, gin.H{"success": true}) }