Migration 0002 replaces finding.message with title/fix/sources (a Postgres text[]). A finding needs to render into the dossier the way it looked at the moment it was raised — referencing the current rules/*.yaml by rule_id+version isn't safe once that file is edited for a later version, since old wording isn't kept around as a separate live file. Added as a new migration rather than editing 0001, since that's already applied on the test server. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
75 lines
2.8 KiB
Go
75 lines
2.8 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"time"
|
|
)
|
|
|
|
// Finding ist das Ergebnis einer Regel für einen Beitrag. Append-only:
|
|
// siehe Migration. ExtractionID ist optional (nil wenn ein Finding nicht
|
|
// direkt aus einer Extraktion, sondern z. B. manuell erzeugt wurde).
|
|
// Title/Fix/Sources sind zum Zeitpunkt der Regelauswertung fixiert
|
|
// gespeichert (nicht nur rule_id/rule_version referenziert), weil ein
|
|
// späteres Update der Regel-YAML eine ältere Version sonst nicht mehr
|
|
// nachträglich auflösen könnte — der Wortlaut zum Zeitpunkt des
|
|
// Findings ist der Beweis, kein Verweis darauf.
|
|
type Finding struct {
|
|
ID string
|
|
SubmissionID string
|
|
ExtractionID *string
|
|
RuleID string
|
|
RuleVersion int
|
|
Severity string
|
|
Title string
|
|
Fix string
|
|
Sources []string
|
|
CreatedAt time.Time
|
|
}
|
|
|
|
// CreateFinding speichert ein Finding.
|
|
func (s *Store) CreateFinding(ctx context.Context, submissionID string, extractionID *string, ruleID string, ruleVersion int, severity, title, fix string, sources []string) (Finding, error) {
|
|
var f Finding
|
|
err := s.Pool.QueryRow(ctx, `
|
|
INSERT INTO finding (submission_id, extraction_id, rule_id, rule_version, severity, title, fix, sources)
|
|
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
RETURNING id, submission_id, extraction_id, rule_id, rule_version, severity, title, fix, sources, created_at
|
|
`, submissionID, extractionID, ruleID, ruleVersion, severity, title, fix, sources).Scan(
|
|
&f.ID, &f.SubmissionID, &f.ExtractionID, &f.RuleID, &f.RuleVersion, &f.Severity, &f.Title, &f.Fix, &f.Sources, &f.CreatedAt,
|
|
)
|
|
if err != nil {
|
|
return Finding{}, fmt.Errorf("store: create finding: %w", err)
|
|
}
|
|
return f, nil
|
|
}
|
|
|
|
// ListCurrentFindings liefert die aktuell gültigen Findings eines
|
|
// Beitrags — Zeilen, die von keiner anderen Zeile per supersedes
|
|
// ersetzt wurden (siehe Migrationskommentar zu finding.supersedes).
|
|
func (s *Store) ListCurrentFindings(ctx context.Context, submissionID string) ([]Finding, error) {
|
|
rows, err := s.Pool.Query(ctx, `
|
|
SELECT f.id, f.submission_id, f.extraction_id, f.rule_id, f.rule_version, f.severity, f.title, f.fix, f.sources, f.created_at
|
|
FROM finding f
|
|
WHERE f.submission_id = $1
|
|
AND NOT EXISTS (SELECT 1 FROM finding f2 WHERE f2.supersedes = f.id)
|
|
ORDER BY f.created_at
|
|
`, submissionID)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("store: list current findings: %w", err)
|
|
}
|
|
defer rows.Close()
|
|
|
|
var findings []Finding
|
|
for rows.Next() {
|
|
var f Finding
|
|
if err := rows.Scan(&f.ID, &f.SubmissionID, &f.ExtractionID, &f.RuleID, &f.RuleVersion, &f.Severity, &f.Title, &f.Fix, &f.Sources, &f.CreatedAt); err != nil {
|
|
return nil, fmt.Errorf("store: scan finding: %w", err)
|
|
}
|
|
findings = append(findings, f)
|
|
}
|
|
if err := rows.Err(); err != nil {
|
|
return nil, fmt.Errorf("store: list current findings: %w", err)
|
|
}
|
|
return findings, nil
|
|
}
|