feat: add store CRUD methods for submission/extraction/finding/evidence

Create/Get for submission (including the one legitimate status
transition — submission is not append-only, unlike the other three),
Create/GetLatest for extraction and evidence_package, Create for
finding plus ListCurrentFindings which applies the anti-join documented
in the migration (a finding referenced by another row's `supersedes`
is not "current").

Tested against real Postgres, including that the append-only trigger
still rejects UPDATE on evidence_package via this new code path, and
that ListCurrentFindings actually hides a finding once a correction
supersedes it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
noroot
2026-08-27 14:30:08 +02:00
parent 31caa8f66a
commit e0218337e7
5 changed files with 436 additions and 0 deletions

View File

@@ -0,0 +1,66 @@
package store
import (
"context"
"fmt"
"time"
)
// Submission ist ein eingereichter Beitrag.
type Submission struct {
ID string
Platform string
PostType string
Caption string
Status string
CreatedAt time.Time
UpdatedAt time.Time
}
// CreateSubmission legt einen neuen Beitrag an (Status "draft").
func (s *Store) CreateSubmission(ctx context.Context, platform, postType, caption string) (Submission, error) {
var sub Submission
err := s.Pool.QueryRow(ctx, `
INSERT INTO submission (platform, post_type, caption)
VALUES ($1, $2, $3)
RETURNING id, platform, post_type, caption, status, created_at, updated_at
`, platform, postType, caption).Scan(
&sub.ID, &sub.Platform, &sub.PostType, &sub.Caption, &sub.Status, &sub.CreatedAt, &sub.UpdatedAt,
)
if err != nil {
return Submission{}, fmt.Errorf("store: create submission: %w", err)
}
return sub, nil
}
// GetSubmission liest einen Beitrag anhand seiner ID.
func (s *Store) GetSubmission(ctx context.Context, id string) (Submission, error) {
var sub Submission
err := s.Pool.QueryRow(ctx, `
SELECT id, platform, post_type, caption, status, created_at, updated_at
FROM submission WHERE id = $1
`, id).Scan(
&sub.ID, &sub.Platform, &sub.PostType, &sub.Caption, &sub.Status, &sub.CreatedAt, &sub.UpdatedAt,
)
if err != nil {
return Submission{}, fmt.Errorf("store: get submission: %w", err)
}
return sub, nil
}
// SetSubmissionStatus setzt den Status eines Beitrags (submission ist,
// anders als extraction/finding/evidence_package, NICHT append-only —
// der Lebenszyklus draft → checked → published → archived ist eine
// normale Zustandsänderung, kein Beweis-Eintrag).
func (s *Store) SetSubmissionStatus(ctx context.Context, id, status string) error {
tag, err := s.Pool.Exec(ctx, `
UPDATE submission SET status = $2, updated_at = now() WHERE id = $1
`, id, status)
if err != nil {
return fmt.Errorf("store: set submission status: %w", err)
}
if tag.RowsAffected() == 0 {
return fmt.Errorf("store: set submission status: submission %s nicht gefunden", id)
}
return nil
}