package handler import ( "net/http" "strconv" "strings" "cyberstrike-ai/internal/database" "github.com/gin-gonic/gin" "go.uber.org/zap" ) // ProjectHandler 项目管理处理器。 type ProjectHandler struct { db *database.DB logger *zap.Logger } // NewProjectHandler 创建项目管理处理器。 func NewProjectHandler(db *database.DB, logger *zap.Logger) *ProjectHandler { return &ProjectHandler{db: db, logger: logger} } type createProjectRequest struct { Name string `json:"name" binding:"required"` Description string `json:"description"` ScopeJSON string `json:"scope_json"` Status string `json:"status"` } type updateProjectRequest struct { Name string `json:"name"` Description string `json:"description"` ScopeJSON string `json:"scope_json"` Status string `json:"status"` Pinned *bool `json:"pinned"` } // CreateProject POST /api/projects func (h *ProjectHandler) CreateProject(c *gin.Context) { var req createProjectRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } p := &database.Project{ Name: strings.TrimSpace(req.Name), Description: req.Description, ScopeJSON: req.ScopeJSON, Status: strings.TrimSpace(req.Status), } created, err := h.db.CreateProject(p) if err != nil { h.logger.Error("创建项目失败", zap.Error(err)) c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, created) } // ListProjects GET /api/projects func (h *ProjectHandler) ListProjects(c *gin.Context) { status := c.Query("status") limit, _ := strconv.Atoi(c.DefaultQuery("limit", "200")) offset, _ := strconv.Atoi(c.Query("offset")) list, err := h.db.ListProjects(status, limit, offset) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if list == nil { list = []*database.Project{} } c.JSON(http.StatusOK, list) } // GetProject GET /api/projects/:id func (h *ProjectHandler) GetProject(c *gin.Context) { p, err := h.db.GetProject(c.Param("id")) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "项目不存在"}) return } c.JSON(http.StatusOK, p) } // UpdateProject PUT /api/projects/:id func (h *ProjectHandler) UpdateProject(c *gin.Context) { id := c.Param("id") p, err := h.db.GetProject(id) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": "项目不存在"}) return } var req updateProjectRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if s := strings.TrimSpace(req.Name); s != "" { p.Name = s } if req.Description != "" || c.Request.ContentLength > 0 { p.Description = req.Description } if req.ScopeJSON != "" || c.GetHeader("Content-Type") != "" { p.ScopeJSON = req.ScopeJSON } if s := strings.TrimSpace(req.Status); s != "" { p.Status = s } if req.Pinned != nil { p.Pinned = *req.Pinned } if err := h.db.UpdateProject(p); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, p) } // DeleteProject DELETE /api/projects/:id func (h *ProjectHandler) DeleteProject(c *gin.Context) { if err := h.db.DeleteProject(c.Param("id")); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true}) } type upsertFactRequest struct { FactKey string `json:"fact_key" binding:"required"` Category string `json:"category"` Summary string `json:"summary" binding:"required"` Body string `json:"body"` Confidence string `json:"confidence"` Pinned bool `json:"pinned"` RelatedVulnerabilityID string `json:"related_vulnerability_id"` } // ListFacts GET /api/projects/:id/facts (fact_key 查询参数可获取单条详情) func (h *ProjectHandler) ListFacts(c *gin.Context) { projectID := c.Param("id") if key := strings.TrimSpace(c.Query("fact_key")); key != "" { f, err := h.db.GetProjectFactByKey(projectID, key) if err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, f) return } limit, _ := strconv.Atoi(c.DefaultQuery("limit", "100")) offset, _ := strconv.Atoi(c.Query("offset")) filter := database.ProjectFactListFilter{ Category: c.Query("category"), Confidence: c.Query("confidence"), Search: c.Query("search"), } list, err := h.db.ListProjectFacts(projectID, filter, limit, offset) if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } if list == nil { list = []*database.ProjectFact{} } c.JSON(http.StatusOK, list) } // CreateFact POST /api/projects/:id/facts func (h *ProjectHandler) CreateFact(c *gin.Context) { var req upsertFactRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } f := &database.ProjectFact{ ProjectID: c.Param("id"), FactKey: req.FactKey, Category: req.Category, Summary: req.Summary, Body: req.Body, Confidence: req.Confidence, Pinned: req.Pinned, RelatedVulnerabilityID: req.RelatedVulnerabilityID, } created, err := h.db.UpsertProjectFact(f) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, created) } // UpdateFact PUT /api/projects/:id/facts/:factId func (h *ProjectHandler) UpdateFact(c *gin.Context) { existing, err := h.db.GetProjectFact(c.Param("factId")) if err != nil || existing.ProjectID != c.Param("id") { c.JSON(http.StatusNotFound, gin.H{"error": "事实不存在"}) return } var req upsertFactRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if k := strings.TrimSpace(req.FactKey); k != "" { existing.FactKey = k } if req.Category != "" { existing.Category = req.Category } if req.Summary != "" { existing.Summary = req.Summary } existing.Body = req.Body if req.Confidence != "" { existing.Confidence = req.Confidence } existing.Pinned = req.Pinned existing.RelatedVulnerabilityID = req.RelatedVulnerabilityID updated, err := h.db.UpsertProjectFact(existing) if err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, updated) } // DeleteFact DELETE /api/projects/:id/facts/:factId func (h *ProjectHandler) DeleteFact(c *gin.Context) { existing, err := h.db.GetProjectFact(c.Param("factId")) if err != nil || existing.ProjectID != c.Param("id") { c.JSON(http.StatusNotFound, gin.H{"error": "事实不存在"}) return } if err := h.db.DeleteProjectFact(existing.ID); err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true}) } type deprecateFactRequest struct { FactKey string `json:"fact_key" binding:"required"` } // DeprecateFact POST /api/projects/:id/facts/deprecate func (h *ProjectHandler) DeprecateFact(c *gin.Context) { var req deprecateFactRequest if err := c.ShouldBindJSON(&req); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()}) return } if err := h.db.DeprecateProjectFact(c.Param("id"), req.FactKey); err != nil { c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) return } c.JSON(http.StatusOK, gin.H{"success": true}) }