From 09e858619e8ae1eec69c724fa76a9a802a65ab3b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=85=AC=E6=98=8E?= <83812544+Ed1s0nZ@users.noreply.github.com> Date: Wed, 10 Jun 2026 11:17:29 +0800 Subject: [PATCH] Add files via upload --- internal/app/app.go | 2 - internal/database/database.go | 59 ++----------------- internal/database/project.go | 32 ++++------ internal/database/project_fact_upsert_test.go | 48 --------------- internal/handler/project.go | 38 ------------ 5 files changed, 17 insertions(+), 162 deletions(-) diff --git a/internal/app/app.go b/internal/app/app.go index 808233a5..536d59fd 100644 --- a/internal/app/app.go +++ b/internal/app/app.go @@ -1084,8 +1084,6 @@ func setupRoutes( protected.PUT("/projects/:id", projectHandler.UpdateProject) protected.DELETE("/projects/:id", projectHandler.DeleteProject) protected.GET("/projects/:id/facts", projectHandler.ListFacts) - protected.GET("/projects/:id/facts/:factId/previous-version", projectHandler.GetFactPreviousVersion) - protected.GET("/projects/:id/facts/:factId/versions", projectHandler.ListFactVersions) protected.POST("/projects/:id/facts", projectHandler.CreateFact) protected.PUT("/projects/:id/facts/:factId", projectHandler.UpdateFact) protected.DELETE("/projects/:id/facts/:factId", projectHandler.DeleteFact) diff --git a/internal/database/database.go b/internal/database/database.go index 06dc35f3..cc5bfd54 100644 --- a/internal/database/database.go +++ b/internal/database/database.go @@ -334,7 +334,6 @@ func (db *DB) initTables() error { source_conversation_id TEXT, source_message_id TEXT, pinned INTEGER NOT NULL DEFAULT 0, - supersedes_fact_id TEXT, related_vulnerability_id TEXT, created_at DATETIME NOT NULL, updated_at DATETIME NOT NULL, @@ -342,25 +341,6 @@ func (db *DB) initTables() error { UNIQUE(project_id, fact_key) );` - createProjectFactVersionsTable := ` - CREATE TABLE IF NOT EXISTS project_fact_versions ( - id TEXT PRIMARY KEY, - fact_id TEXT NOT NULL, - project_id TEXT NOT NULL, - fact_key TEXT NOT NULL, - category TEXT NOT NULL DEFAULT 'note', - summary TEXT NOT NULL DEFAULT '', - body TEXT, - confidence TEXT NOT NULL DEFAULT 'tentative', - source_conversation_id TEXT, - source_message_id TEXT, - pinned INTEGER NOT NULL DEFAULT 0, - related_vulnerability_id TEXT, - archived_at DATETIME NOT NULL, - FOREIGN KEY (fact_id) REFERENCES project_facts(id) ON DELETE CASCADE, - FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE - );` - // 创建漏洞表 createVulnerabilitiesTable := ` CREATE TABLE IF NOT EXISTS vulnerabilities ( @@ -598,7 +578,6 @@ func (db *DB) initTables() error { CREATE INDEX IF NOT EXISTS idx_project_facts_project_id ON project_facts(project_id); CREATE INDEX IF NOT EXISTS idx_project_facts_confidence ON project_facts(confidence); CREATE INDEX IF NOT EXISTS idx_project_facts_related_vuln ON project_facts(related_vulnerability_id); - CREATE INDEX IF NOT EXISTS idx_project_fact_versions_fact_id ON project_fact_versions(fact_id); CREATE INDEX IF NOT EXISTS idx_conversations_project_id ON conversations(project_id); CREATE INDEX IF NOT EXISTS idx_vulnerabilities_project_id ON vulnerabilities(project_id); CREATE INDEX IF NOT EXISTS idx_batch_tasks_queue_id ON batch_tasks(queue_id); @@ -680,10 +659,6 @@ func (db *DB) initTables() error { return fmt.Errorf("创建project_facts表失败: %w", err) } - if _, err := db.Exec(createProjectFactVersionsTable); err != nil { - return fmt.Errorf("创建project_fact_versions表失败: %w", err) - } - if _, err := db.Exec(createVulnerabilitiesTable); err != nil { return fmt.Errorf("创建vulnerabilities表失败: %w", err) } @@ -754,8 +729,8 @@ func (db *DB) initTables() error { if err := db.migrateProjectsTable(); err != nil { db.logger.Warn("迁移projects相关表失败", zap.Error(err)) } - if err := db.migrateProjectFactVersionsTable(); err != nil { - db.logger.Warn("迁移project_fact_versions表失败", zap.Error(err)) + if err := db.dropProjectFactVersionsTable(); err != nil { + db.logger.Warn("清理project_fact_versions表失败", zap.Error(err)) } if err := db.migrateWebshellConnectionsTable(); err != nil { @@ -1153,32 +1128,10 @@ func (db *DB) migrateProjectsTable() error { return nil } -// migrateProjectFactVersionsTable 为已有库创建事实版本表。 -func (db *DB) migrateProjectFactVersionsTable() error { - ddl := ` - CREATE TABLE IF NOT EXISTS project_fact_versions ( - id TEXT PRIMARY KEY, - fact_id TEXT NOT NULL, - project_id TEXT NOT NULL, - fact_key TEXT NOT NULL, - category TEXT NOT NULL DEFAULT 'note', - summary TEXT NOT NULL DEFAULT '', - body TEXT, - confidence TEXT NOT NULL DEFAULT 'tentative', - source_conversation_id TEXT, - source_message_id TEXT, - pinned INTEGER NOT NULL DEFAULT 0, - related_vulnerability_id TEXT, - archived_at DATETIME NOT NULL, - FOREIGN KEY (fact_id) REFERENCES project_facts(id) ON DELETE CASCADE, - FOREIGN KEY (project_id) REFERENCES projects(id) ON DELETE CASCADE - );` - if _, err := db.Exec(ddl); err != nil { - return err - } - _, _ = db.Exec(`CREATE INDEX IF NOT EXISTS idx_project_fact_versions_fact_id ON project_fact_versions(fact_id)`) - _, _ = db.Exec(`CREATE INDEX IF NOT EXISTS idx_project_facts_related_vuln ON project_facts(related_vulnerability_id)`) - return nil +// dropProjectFactVersionsTable 移除已废弃的事实版本归档表。 +func (db *DB) dropProjectFactVersionsTable() error { + _, err := db.Exec(`DROP TABLE IF EXISTS project_fact_versions`) + return err } // migrateVulnerabilitiesTable 迁移 vulnerabilities 表,补充标签字段 diff --git a/internal/database/project.go b/internal/database/project.go index 4a675de6..448958d4 100644 --- a/internal/database/project.go +++ b/internal/database/project.go @@ -51,7 +51,6 @@ type ProjectFact struct { SourceConversationID string `json:"source_conversation_id,omitempty"` SourceMessageID string `json:"source_message_id,omitempty"` Pinned bool `json:"pinned"` - SupersedesFactID string `json:"supersedes_fact_id,omitempty"` RelatedVulnerabilityID string `json:"related_vulnerability_id,omitempty"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` @@ -240,7 +239,7 @@ func (db *DB) SetConversationProjectID(conversationID, projectID string) error { func (db *DB) ListProjectFactsForIndex(projectID string, includeDeprecated bool) ([]*ProjectFact, error) { query := `SELECT id, project_id, fact_key, category, summary, COALESCE(body,''), confidence, COALESCE(source_conversation_id,''), COALESCE(source_message_id,''), pinned, - COALESCE(supersedes_fact_id,''), COALESCE(related_vulnerability_id,''), created_at, updated_at + COALESCE(related_vulnerability_id,''), created_at, updated_at FROM project_facts WHERE project_id = ?` args := []interface{}{projectID} if !includeDeprecated { @@ -262,7 +261,7 @@ func (db *DB) ListProjectFacts(projectID string, filter ProjectFactListFilter, l } query := `SELECT id, project_id, fact_key, category, summary, COALESCE(body,''), confidence, COALESCE(source_conversation_id,''), COALESCE(source_message_id,''), pinned, - COALESCE(supersedes_fact_id,''), COALESCE(related_vulnerability_id,''), created_at, updated_at + COALESCE(related_vulnerability_id,''), created_at, updated_at FROM project_facts WHERE project_id = ?` args := []interface{}{projectID} if c := strings.TrimSpace(filter.Category); c != "" { @@ -301,7 +300,7 @@ func (db *DB) GetProjectFactByKey(projectID, factKey string) (*ProjectFact, erro row := db.QueryRow( `SELECT id, project_id, fact_key, category, summary, COALESCE(body,''), confidence, COALESCE(source_conversation_id,''), COALESCE(source_message_id,''), pinned, - COALESCE(supersedes_fact_id,''), COALESCE(related_vulnerability_id,''), created_at, updated_at + COALESCE(related_vulnerability_id,''), created_at, updated_at FROM project_facts WHERE project_id = ? AND fact_key = ?`, projectID, factKey, ) @@ -313,7 +312,7 @@ func (db *DB) GetProjectFact(id string) (*ProjectFact, error) { row := db.QueryRow( `SELECT id, project_id, fact_key, category, summary, COALESCE(body,''), confidence, COALESCE(source_conversation_id,''), COALESCE(source_message_id,''), pinned, - COALESCE(supersedes_fact_id,''), COALESCE(related_vulnerability_id,''), created_at, updated_at + COALESCE(related_vulnerability_id,''), created_at, updated_at FROM project_facts WHERE id = ?`, id, ) return scanProjectFactRow(row) @@ -352,24 +351,15 @@ func (db *DB) UpsertProjectFact(f *ProjectFact) (*ProjectFact, error) { if strings.TrimSpace(f.Confidence) == "" { f.Confidence = existing.Confidence } - if projectFactContentChanged(existing, f) { - versionID, verr := db.InsertProjectFactVersion(existing) - if verr != nil { - return nil, verr - } - f.SupersedesFactID = versionID - } else if f.SupersedesFactID == "" { - f.SupersedesFactID = existing.SupersedesFactID - } _, err = db.Exec( `UPDATE project_facts SET category = ?, summary = ?, body = ?, confidence = ?, source_conversation_id = COALESCE(?, source_conversation_id), source_message_id = COALESCE(?, source_message_id), - pinned = ?, supersedes_fact_id = ?, related_vulnerability_id = ?, updated_at = ? + pinned = ?, related_vulnerability_id = ?, updated_at = ? WHERE id = ?`, f.Category, f.Summary, f.Body, f.Confidence, nullIfEmpty(f.SourceConversationID), nullIfEmpty(f.SourceMessageID), boolToInt(f.Pinned), - nullIfEmpty(f.SupersedesFactID), nullIfEmpty(f.RelatedVulnerabilityID), f.UpdatedAt, f.ID, + nullIfEmpty(f.RelatedVulnerabilityID), f.UpdatedAt, f.ID, ) if err != nil { return nil, fmt.Errorf("更新事实失败: %w", err) @@ -385,12 +375,12 @@ func (db *DB) UpsertProjectFact(f *ProjectFact) (*ProjectFact, error) { _, err = db.Exec( `INSERT INTO project_facts ( id, project_id, fact_key, category, summary, body, confidence, - source_conversation_id, source_message_id, pinned, supersedes_fact_id, related_vulnerability_id, + source_conversation_id, source_message_id, pinned, related_vulnerability_id, created_at, updated_at - ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, + ) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, f.ID, f.ProjectID, f.FactKey, f.Category, f.Summary, f.Body, f.Confidence, nullIfEmpty(f.SourceConversationID), nullIfEmpty(f.SourceMessageID), boolToInt(f.Pinned), - nullIfEmpty(f.SupersedesFactID), nullIfEmpty(f.RelatedVulnerabilityID), + nullIfEmpty(f.RelatedVulnerabilityID), f.CreatedAt, f.UpdatedAt, ) if err != nil { @@ -465,7 +455,7 @@ func scanProjectFactRow(row *sql.Row) (*ProjectFact, error) { err := row.Scan( &f.ID, &f.ProjectID, &f.FactKey, &f.Category, &f.Summary, &f.Body, &f.Confidence, &f.SourceConversationID, &f.SourceMessageID, &pinned, - &f.SupersedesFactID, &f.RelatedVulnerabilityID, &createdAt, &updatedAt, + &f.RelatedVulnerabilityID, &createdAt, &updatedAt, ) if err != nil { if err == sql.ErrNoRows { @@ -486,7 +476,7 @@ func scanProjectFactFromRows(rows *sql.Rows) (*ProjectFact, error) { err := rows.Scan( &f.ID, &f.ProjectID, &f.FactKey, &f.Category, &f.Summary, &f.Body, &f.Confidence, &f.SourceConversationID, &f.SourceMessageID, &pinned, - &f.SupersedesFactID, &f.RelatedVulnerabilityID, &createdAt, &updatedAt, + &f.RelatedVulnerabilityID, &createdAt, &updatedAt, ) if err != nil { return nil, err diff --git a/internal/database/project_fact_upsert_test.go b/internal/database/project_fact_upsert_test.go index e5ea08f6..c843d508 100644 --- a/internal/database/project_fact_upsert_test.go +++ b/internal/database/project_fact_upsert_test.go @@ -135,54 +135,6 @@ func TestRestoreProjectFact(t *testing.T) { } } -func TestUpsertProjectFact_createsVersionOnContentChange(t *testing.T) { - dbPath := filepath.Join(t.TempDir(), "facts.db") - db, err := NewDB(dbPath, zap.NewNop()) - if err != nil { - t.Fatal(err) - } - defer db.Close() - - proj, err := db.CreateProject(&Project{Name: "version-test"}) - if err != nil { - t.Fatal(err) - } - - created, err := db.UpsertProjectFact(&ProjectFact{ - ProjectID: proj.ID, - FactKey: "finding/xss", - Category: "finding", - Summary: "v1", - Body: "body v1", - }) - if err != nil { - t.Fatal(err) - } - if created.SupersedesFactID != "" { - t.Fatalf("expected no supersedes on create, got %q", created.SupersedesFactID) - } - - updated, err := db.UpsertProjectFact(&ProjectFact{ - ProjectID: proj.ID, - FactKey: "finding/xss", - Summary: "v2", - Body: "body v2", - }) - if err != nil { - t.Fatal(err) - } - if updated.SupersedesFactID == "" { - t.Fatal("expected supersedes_fact_id after content change") - } - prev, err := db.GetProjectFactVersion(updated.SupersedesFactID) - if err != nil { - t.Fatal(err) - } - if prev.Summary != "v1" || prev.Body != "body v1" { - t.Fatalf("previous version mismatch: summary=%q body=%q", prev.Summary, prev.Body) - } -} - func TestMergeFactBodyOnUpdate(t *testing.T) { if got := mergeFactBodyOnUpdate("", "keep"); got != "keep" { t.Fatalf("empty incoming: got %q", got) diff --git a/internal/handler/project.go b/internal/handler/project.go index 588d7619..eeeb82e8 100644 --- a/internal/handler/project.go +++ b/internal/handler/project.go @@ -278,44 +278,6 @@ func (h *ProjectHandler) ListFacts(c *gin.Context) { c.JSON(http.StatusOK, list) } -// GetFactPreviousVersion GET /api/projects/:id/facts/:factId/previous-version -func (h *ProjectHandler) GetFactPreviousVersion(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 strings.TrimSpace(existing.SupersedesFactID) == "" { - c.JSON(http.StatusNotFound, gin.H{"error": "无上一版本"}) - return - } - v, err := h.db.GetProjectFactVersion(existing.SupersedesFactID) - if err != nil { - c.JSON(http.StatusNotFound, gin.H{"error": err.Error()}) - return - } - c.JSON(http.StatusOK, v) -} - -// ListFactVersions GET /api/projects/:id/facts/:factId/versions -func (h *ProjectHandler) ListFactVersions(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 - } - limit, _ := strconv.Atoi(c.DefaultQuery("limit", "20")) - list, err := h.db.ListProjectFactVersions(existing.ID, limit) - if err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) - return - } - if list == nil { - list = []*database.ProjectFactVersion{} - } - c.JSON(http.StatusOK, list) -} - // CreateFact POST /api/projects/:id/facts func (h *ProjectHandler) CreateFact(c *gin.Context) { var req upsertFactRequest