Code sync, security fixes

This commit is contained in:
Andy Dixon
2026-08-11 10:40:10 +01:00
parent 4e00deedf8
commit 53dbc28383
141 changed files with 36194 additions and 0 deletions
+183
View File
@@ -0,0 +1,183 @@
package mongo
import (
"context"
"time"
"go.mongodb.org/mongo-driver/v2/bson"
"go.mongodb.org/mongo-driver/v2/mongo/options"
"github.com/andydixon/relata/internal/domain"
)
// ClaimRepo stores extracted claims.
type ClaimRepo struct{ base }
var _ domain.ClaimRepository = (*ClaimRepo)(nil)
// ReplaceForItem swaps an item's claims for a new set, making claim extraction
// idempotent across reprocessing runs.
func (r *ClaimRepo) ReplaceForItem(ctx context.Context, workspaceID, itemID string, claims []*domain.Claim) error {
ctx, cancel := r.ctx(ctx)
defer cancel()
started := time.Now()
f, err := wsFilter(workspaceID)
if err != nil {
return err
}
f = append(f, bson.E{Key: "item_id", Value: itemID})
if _, err := r.coll.DeleteMany(ctx, f); err != nil {
r.observe("replace_for_item", started, err)
return translate(err)
}
if len(claims) == 0 {
r.observe("replace_for_item", started, nil)
return nil
}
// Deduplicate within the batch: two chunks can yield the same claim, and
// the unique dedupe_key index would otherwise reject the whole insert.
seen := map[string]bool{}
docs := make([]any, 0, len(claims))
for _, c := range claims {
if c.WorkspaceID != workspaceID || c.ItemID != itemID {
return domain.ErrWorkspaceMiss
}
if seen[c.DedupeKey] {
continue
}
seen[c.DedupeKey] = true
docs = append(docs, c)
}
_, err = r.coll.InsertMany(ctx, docs, options.InsertMany().SetOrdered(false))
if isDup(err) {
// A concurrent run inserted an identical claim; that is the intended
// outcome, not a failure.
err = nil
}
r.observe("replace_for_item", started, err)
return translate(err)
}
// ListByItem returns an item's claims.
func (r *ClaimRepo) ListByItem(ctx context.Context, workspaceID, itemID string) ([]*domain.Claim, error) {
ctx, cancel := r.ctx(ctx)
defer cancel()
started := time.Now()
f, err := wsFilter(workspaceID)
if err != nil {
return nil, err
}
f = append(f, bson.E{Key: "item_id", Value: itemID})
cur, err := r.coll.Find(ctx, f, options.Find().SetSort(bson.D{{Key: "created_at", Value: 1}}))
r.observe("list_by_item", started, err)
if err != nil {
return nil, translate(err)
}
var out []*domain.Claim
if err := cur.All(ctx, &out); err != nil {
return nil, err
}
return out, nil
}
// FindBySubjects locates claims about the same coarse subject in other items.
//
// This is how support and contradiction candidates are found without comparing
// every claim to every other claim: the subject key is indexed, so the search
// is a bounded lookup rather than a scan.
func (r *ClaimRepo) FindBySubjects(ctx context.Context, workspaceID string, subjects []string, excludeItem string, limit int) ([]*domain.Claim, error) {
if len(subjects) == 0 {
return nil, nil
}
ctx, cancel := r.ctx(ctx)
defer cancel()
started := time.Now()
f, err := wsFilter(workspaceID)
if err != nil {
return nil, err
}
f = append(f, bson.E{Key: "subject", Value: bson.D{{Key: "$in", Value: stringsToAny(subjects)}}})
if excludeItem != "" {
f = append(f, bson.E{Key: "item_id", Value: bson.D{{Key: "$ne", Value: excludeItem}}})
}
if limit <= 0 || limit > 500 {
limit = 100
}
cur, err := r.coll.Find(ctx, f, options.Find().SetLimit(int64(limit)))
r.observe("find_by_subjects", started, err)
if err != nil {
return nil, translate(err)
}
var out []*domain.Claim
if err := cur.All(ctx, &out); err != nil {
return nil, err
}
return out, nil
}
// GetMany fetches claims by ID within a workspace.
func (r *ClaimRepo) GetMany(ctx context.Context, workspaceID string, claimIDs []string) ([]*domain.Claim, error) {
if len(claimIDs) == 0 {
return nil, nil
}
ctx, cancel := r.ctx(ctx)
defer cancel()
started := time.Now()
f, err := wsFilter(workspaceID)
if err != nil {
return nil, err
}
f = append(f, bson.E{Key: "_id", Value: bson.D{{Key: "$in", Value: stringsToAny(claimIDs)}}})
cur, err := r.coll.Find(ctx, f)
r.observe("get_many", started, err)
if err != nil {
return nil, translate(err)
}
var out []*domain.Claim
if err := cur.All(ctx, &out); err != nil {
return nil, err
}
return out, nil
}
// DeleteByItem removes an item's claims.
func (r *ClaimRepo) DeleteByItem(ctx context.Context, workspaceID, itemID string) error {
ctx, cancel := r.ctx(ctx)
defer cancel()
started := time.Now()
f, err := wsFilter(workspaceID)
if err != nil {
return err
}
f = append(f, bson.E{Key: "item_id", Value: itemID})
_, err = r.coll.DeleteMany(ctx, f)
r.observe("delete_by_item", started, err)
return translate(err)
}
// Count returns the number of claims in a workspace.
func (r *ClaimRepo) Count(ctx context.Context, workspaceID string) (int64, error) {
ctx, cancel := r.ctx(ctx)
defer cancel()
started := time.Now()
f, err := wsFilter(workspaceID)
if err != nil {
return 0, err
}
n, err := r.coll.CountDocuments(ctx, f)
r.observe("count", started, err)
return n, translate(err)
}