mirror of
https://github.com/Ed1s0nZ/CyberStrikeAI.git
synced 2026-09-26 19:21:52 +02:00
Add files via upload
This commit is contained in:
@@ -1084,8 +1084,6 @@ func setupRoutes(
|
|||||||
protected.PUT("/projects/:id", projectHandler.UpdateProject)
|
protected.PUT("/projects/:id", projectHandler.UpdateProject)
|
||||||
protected.DELETE("/projects/:id", projectHandler.DeleteProject)
|
protected.DELETE("/projects/:id", projectHandler.DeleteProject)
|
||||||
protected.GET("/projects/:id/facts", projectHandler.ListFacts)
|
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.POST("/projects/:id/facts", projectHandler.CreateFact)
|
||||||
protected.PUT("/projects/:id/facts/:factId", projectHandler.UpdateFact)
|
protected.PUT("/projects/:id/facts/:factId", projectHandler.UpdateFact)
|
||||||
protected.DELETE("/projects/:id/facts/:factId", projectHandler.DeleteFact)
|
protected.DELETE("/projects/:id/facts/:factId", projectHandler.DeleteFact)
|
||||||
|
|||||||
@@ -334,7 +334,6 @@ func (db *DB) initTables() error {
|
|||||||
source_conversation_id TEXT,
|
source_conversation_id TEXT,
|
||||||
source_message_id TEXT,
|
source_message_id TEXT,
|
||||||
pinned INTEGER NOT NULL DEFAULT 0,
|
pinned INTEGER NOT NULL DEFAULT 0,
|
||||||
supersedes_fact_id TEXT,
|
|
||||||
related_vulnerability_id TEXT,
|
related_vulnerability_id TEXT,
|
||||||
created_at DATETIME NOT NULL,
|
created_at DATETIME NOT NULL,
|
||||||
updated_at DATETIME NOT NULL,
|
updated_at DATETIME NOT NULL,
|
||||||
@@ -342,25 +341,6 @@ func (db *DB) initTables() error {
|
|||||||
UNIQUE(project_id, fact_key)
|
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 := `
|
createVulnerabilitiesTable := `
|
||||||
CREATE TABLE IF NOT EXISTS vulnerabilities (
|
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_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_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_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_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_vulnerabilities_project_id ON vulnerabilities(project_id);
|
||||||
CREATE INDEX IF NOT EXISTS idx_batch_tasks_queue_id ON batch_tasks(queue_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)
|
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 {
|
if _, err := db.Exec(createVulnerabilitiesTable); err != nil {
|
||||||
return fmt.Errorf("创建vulnerabilities表失败: %w", err)
|
return fmt.Errorf("创建vulnerabilities表失败: %w", err)
|
||||||
}
|
}
|
||||||
@@ -754,8 +729,8 @@ func (db *DB) initTables() error {
|
|||||||
if err := db.migrateProjectsTable(); err != nil {
|
if err := db.migrateProjectsTable(); err != nil {
|
||||||
db.logger.Warn("迁移projects相关表失败", zap.Error(err))
|
db.logger.Warn("迁移projects相关表失败", zap.Error(err))
|
||||||
}
|
}
|
||||||
if err := db.migrateProjectFactVersionsTable(); err != nil {
|
if err := db.dropProjectFactVersionsTable(); err != nil {
|
||||||
db.logger.Warn("迁移project_fact_versions表失败", zap.Error(err))
|
db.logger.Warn("清理project_fact_versions表失败", zap.Error(err))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := db.migrateWebshellConnectionsTable(); err != nil {
|
if err := db.migrateWebshellConnectionsTable(); err != nil {
|
||||||
@@ -1153,32 +1128,10 @@ func (db *DB) migrateProjectsTable() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// migrateProjectFactVersionsTable 为已有库创建事实版本表。
|
// dropProjectFactVersionsTable 移除已废弃的事实版本归档表。
|
||||||
func (db *DB) migrateProjectFactVersionsTable() error {
|
func (db *DB) dropProjectFactVersionsTable() error {
|
||||||
ddl := `
|
_, err := db.Exec(`DROP TABLE IF EXISTS project_fact_versions`)
|
||||||
CREATE TABLE IF NOT EXISTS project_fact_versions (
|
return err
|
||||||
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
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// migrateVulnerabilitiesTable 迁移 vulnerabilities 表,补充标签字段
|
// migrateVulnerabilitiesTable 迁移 vulnerabilities 表,补充标签字段
|
||||||
|
|||||||
@@ -51,7 +51,6 @@ type ProjectFact struct {
|
|||||||
SourceConversationID string `json:"source_conversation_id,omitempty"`
|
SourceConversationID string `json:"source_conversation_id,omitempty"`
|
||||||
SourceMessageID string `json:"source_message_id,omitempty"`
|
SourceMessageID string `json:"source_message_id,omitempty"`
|
||||||
Pinned bool `json:"pinned"`
|
Pinned bool `json:"pinned"`
|
||||||
SupersedesFactID string `json:"supersedes_fact_id,omitempty"`
|
|
||||||
RelatedVulnerabilityID string `json:"related_vulnerability_id,omitempty"`
|
RelatedVulnerabilityID string `json:"related_vulnerability_id,omitempty"`
|
||||||
CreatedAt time.Time `json:"created_at"`
|
CreatedAt time.Time `json:"created_at"`
|
||||||
UpdatedAt time.Time `json:"updated_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) {
|
func (db *DB) ListProjectFactsForIndex(projectID string, includeDeprecated bool) ([]*ProjectFact, error) {
|
||||||
query := `SELECT id, project_id, fact_key, category, summary, COALESCE(body,''), confidence,
|
query := `SELECT id, project_id, fact_key, category, summary, COALESCE(body,''), confidence,
|
||||||
COALESCE(source_conversation_id,''), COALESCE(source_message_id,''), pinned,
|
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 = ?`
|
FROM project_facts WHERE project_id = ?`
|
||||||
args := []interface{}{projectID}
|
args := []interface{}{projectID}
|
||||||
if !includeDeprecated {
|
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,
|
query := `SELECT id, project_id, fact_key, category, summary, COALESCE(body,''), confidence,
|
||||||
COALESCE(source_conversation_id,''), COALESCE(source_message_id,''), pinned,
|
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 = ?`
|
FROM project_facts WHERE project_id = ?`
|
||||||
args := []interface{}{projectID}
|
args := []interface{}{projectID}
|
||||||
if c := strings.TrimSpace(filter.Category); c != "" {
|
if c := strings.TrimSpace(filter.Category); c != "" {
|
||||||
@@ -301,7 +300,7 @@ func (db *DB) GetProjectFactByKey(projectID, factKey string) (*ProjectFact, erro
|
|||||||
row := db.QueryRow(
|
row := db.QueryRow(
|
||||||
`SELECT id, project_id, fact_key, category, summary, COALESCE(body,''), confidence,
|
`SELECT id, project_id, fact_key, category, summary, COALESCE(body,''), confidence,
|
||||||
COALESCE(source_conversation_id,''), COALESCE(source_message_id,''), pinned,
|
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 = ?`,
|
FROM project_facts WHERE project_id = ? AND fact_key = ?`,
|
||||||
projectID, factKey,
|
projectID, factKey,
|
||||||
)
|
)
|
||||||
@@ -313,7 +312,7 @@ func (db *DB) GetProjectFact(id string) (*ProjectFact, error) {
|
|||||||
row := db.QueryRow(
|
row := db.QueryRow(
|
||||||
`SELECT id, project_id, fact_key, category, summary, COALESCE(body,''), confidence,
|
`SELECT id, project_id, fact_key, category, summary, COALESCE(body,''), confidence,
|
||||||
COALESCE(source_conversation_id,''), COALESCE(source_message_id,''), pinned,
|
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,
|
FROM project_facts WHERE id = ?`, id,
|
||||||
)
|
)
|
||||||
return scanProjectFactRow(row)
|
return scanProjectFactRow(row)
|
||||||
@@ -352,24 +351,15 @@ func (db *DB) UpsertProjectFact(f *ProjectFact) (*ProjectFact, error) {
|
|||||||
if strings.TrimSpace(f.Confidence) == "" {
|
if strings.TrimSpace(f.Confidence) == "" {
|
||||||
f.Confidence = existing.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(
|
_, err = db.Exec(
|
||||||
`UPDATE project_facts SET category = ?, summary = ?, body = ?, confidence = ?,
|
`UPDATE project_facts SET category = ?, summary = ?, body = ?, confidence = ?,
|
||||||
source_conversation_id = COALESCE(?, source_conversation_id),
|
source_conversation_id = COALESCE(?, source_conversation_id),
|
||||||
source_message_id = COALESCE(?, source_message_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 = ?`,
|
WHERE id = ?`,
|
||||||
f.Category, f.Summary, f.Body, f.Confidence,
|
f.Category, f.Summary, f.Body, f.Confidence,
|
||||||
nullIfEmpty(f.SourceConversationID), nullIfEmpty(f.SourceMessageID), boolToInt(f.Pinned),
|
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 {
|
if err != nil {
|
||||||
return nil, fmt.Errorf("更新事实失败: %w", err)
|
return nil, fmt.Errorf("更新事实失败: %w", err)
|
||||||
@@ -385,12 +375,12 @@ func (db *DB) UpsertProjectFact(f *ProjectFact) (*ProjectFact, error) {
|
|||||||
_, err = db.Exec(
|
_, err = db.Exec(
|
||||||
`INSERT INTO project_facts (
|
`INSERT INTO project_facts (
|
||||||
id, project_id, fact_key, category, summary, body, confidence,
|
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
|
created_at, updated_at
|
||||||
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
||||||
f.ID, f.ProjectID, f.FactKey, f.Category, f.Summary, f.Body, f.Confidence,
|
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.SourceConversationID), nullIfEmpty(f.SourceMessageID), boolToInt(f.Pinned),
|
||||||
nullIfEmpty(f.SupersedesFactID), nullIfEmpty(f.RelatedVulnerabilityID),
|
nullIfEmpty(f.RelatedVulnerabilityID),
|
||||||
f.CreatedAt, f.UpdatedAt,
|
f.CreatedAt, f.UpdatedAt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -465,7 +455,7 @@ func scanProjectFactRow(row *sql.Row) (*ProjectFact, error) {
|
|||||||
err := row.Scan(
|
err := row.Scan(
|
||||||
&f.ID, &f.ProjectID, &f.FactKey, &f.Category, &f.Summary, &f.Body, &f.Confidence,
|
&f.ID, &f.ProjectID, &f.FactKey, &f.Category, &f.Summary, &f.Body, &f.Confidence,
|
||||||
&f.SourceConversationID, &f.SourceMessageID, &pinned,
|
&f.SourceConversationID, &f.SourceMessageID, &pinned,
|
||||||
&f.SupersedesFactID, &f.RelatedVulnerabilityID, &createdAt, &updatedAt,
|
&f.RelatedVulnerabilityID, &createdAt, &updatedAt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
if err == sql.ErrNoRows {
|
if err == sql.ErrNoRows {
|
||||||
@@ -486,7 +476,7 @@ func scanProjectFactFromRows(rows *sql.Rows) (*ProjectFact, error) {
|
|||||||
err := rows.Scan(
|
err := rows.Scan(
|
||||||
&f.ID, &f.ProjectID, &f.FactKey, &f.Category, &f.Summary, &f.Body, &f.Confidence,
|
&f.ID, &f.ProjectID, &f.FactKey, &f.Category, &f.Summary, &f.Body, &f.Confidence,
|
||||||
&f.SourceConversationID, &f.SourceMessageID, &pinned,
|
&f.SourceConversationID, &f.SourceMessageID, &pinned,
|
||||||
&f.SupersedesFactID, &f.RelatedVulnerabilityID, &createdAt, &updatedAt,
|
&f.RelatedVulnerabilityID, &createdAt, &updatedAt,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|||||||
@@ -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) {
|
func TestMergeFactBodyOnUpdate(t *testing.T) {
|
||||||
if got := mergeFactBodyOnUpdate("", "keep"); got != "keep" {
|
if got := mergeFactBodyOnUpdate("", "keep"); got != "keep" {
|
||||||
t.Fatalf("empty incoming: got %q", got)
|
t.Fatalf("empty incoming: got %q", got)
|
||||||
|
|||||||
@@ -278,44 +278,6 @@ func (h *ProjectHandler) ListFacts(c *gin.Context) {
|
|||||||
c.JSON(http.StatusOK, list)
|
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
|
// CreateFact POST /api/projects/:id/facts
|
||||||
func (h *ProjectHandler) CreateFact(c *gin.Context) {
|
func (h *ProjectHandler) CreateFact(c *gin.Context) {
|
||||||
var req upsertFactRequest
|
var req upsertFactRequest
|
||||||
|
|||||||
Reference in New Issue
Block a user