Code sync, security fixes
This commit is contained in:
@@ -0,0 +1,224 @@
|
||||
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"
|
||||
"github.com/andydixon/relata/internal/ids"
|
||||
)
|
||||
|
||||
// AuditRepo is an append-only record of consequential actions.
|
||||
type AuditRepo struct{ base }
|
||||
|
||||
var _ domain.AuditRepository = (*AuditRepo)(nil)
|
||||
|
||||
// Record appends an audit event. There is deliberately no update or delete.
|
||||
func (r *AuditRepo) Record(ctx context.Context, ev *domain.AuditEvent) error {
|
||||
ctx, cancel := r.ctx(ctx)
|
||||
defer cancel()
|
||||
started := time.Now()
|
||||
|
||||
if ev.ID == "" {
|
||||
ev.ID = ids.New()
|
||||
}
|
||||
if ev.At.IsZero() {
|
||||
ev.At = time.Now().UTC()
|
||||
}
|
||||
_, err := r.coll.InsertOne(ctx, ev)
|
||||
r.observe("record", started, err)
|
||||
return translate(err)
|
||||
}
|
||||
|
||||
// List returns a page of audit events, newest first.
|
||||
func (r *AuditRepo) List(ctx context.Context, workspaceID string, p domain.Page) ([]*domain.AuditEvent, int64, error) {
|
||||
ctx, cancel := r.ctx(ctx)
|
||||
defer cancel()
|
||||
started := time.Now()
|
||||
|
||||
f, err := wsFilter(workspaceID)
|
||||
if err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
p = p.Normalise(50, 200)
|
||||
|
||||
total, err := r.coll.CountDocuments(ctx, f)
|
||||
if err != nil {
|
||||
return nil, 0, translate(err)
|
||||
}
|
||||
cur, err := r.coll.Find(ctx, f, options.Find().
|
||||
SetSort(bson.D{{Key: "at", Value: -1}}).
|
||||
SetSkip(int64(p.Offset)).SetLimit(int64(p.Limit)))
|
||||
r.observe("list", started, err)
|
||||
if err != nil {
|
||||
return nil, 0, translate(err)
|
||||
}
|
||||
var out []*domain.AuditEvent
|
||||
if err := cur.All(ctx, &out); err != nil {
|
||||
return nil, 0, err
|
||||
}
|
||||
return out, total, nil
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// RunRepo stores processing run history.
|
||||
type RunRepo struct{ base }
|
||||
|
||||
var _ domain.RunRepository = (*RunRepo)(nil)
|
||||
|
||||
// Create opens a processing run.
|
||||
func (r *RunRepo) Create(ctx context.Context, run *domain.ProcessingRun) error {
|
||||
ctx, cancel := r.ctx(ctx)
|
||||
defer cancel()
|
||||
started := time.Now()
|
||||
|
||||
if run.ID == "" {
|
||||
run.ID = ids.New()
|
||||
}
|
||||
if run.StartedAt.IsZero() {
|
||||
run.StartedAt = time.Now().UTC()
|
||||
}
|
||||
if run.Outcome == "" {
|
||||
run.Outcome = "running"
|
||||
}
|
||||
_, err := r.coll.InsertOne(ctx, run)
|
||||
r.observe("create", started, err)
|
||||
return translate(err)
|
||||
}
|
||||
|
||||
// Finish closes a processing run with its outcome and statistics.
|
||||
//
|
||||
// Runs are never deleted: a reprocess adds a new run, so the record of what a
|
||||
// previous parser or model produced survives for audit.
|
||||
func (r *RunRepo) Finish(ctx context.Context, id, outcome, errMsg string, stats map[string]int) error {
|
||||
ctx, cancel := r.ctx(ctx)
|
||||
defer cancel()
|
||||
started := time.Now()
|
||||
|
||||
now := time.Now().UTC()
|
||||
_, err := r.coll.UpdateOne(ctx,
|
||||
bson.D{{Key: "_id", Value: id}},
|
||||
bson.D{{Key: "$set", Value: bson.D{
|
||||
{Key: "ended_at", Value: now},
|
||||
{Key: "outcome", Value: outcome},
|
||||
{Key: "error", Value: errMsg},
|
||||
{Key: "stats", Value: stats},
|
||||
}}})
|
||||
r.observe("finish", started, err)
|
||||
return translate(err)
|
||||
}
|
||||
|
||||
// ListByItem returns an item's processing history, newest first.
|
||||
func (r *RunRepo) ListByItem(ctx context.Context, workspaceID, itemID string, limit int) ([]*domain.ProcessingRun, 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})
|
||||
|
||||
if limit <= 0 || limit > 100 {
|
||||
limit = 20
|
||||
}
|
||||
cur, err := r.coll.Find(ctx, f, options.Find().
|
||||
SetSort(bson.D{{Key: "started_at", Value: -1}}).SetLimit(int64(limit)))
|
||||
r.observe("list_by_item", started, err)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
var out []*domain.ProcessingRun
|
||||
if err := cur.All(ctx, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// DeleteByItem removes an item's run history, used by hard deletion.
|
||||
func (r *RunRepo) DeleteByItem(ctx context.Context, workspaceID, itemID string) error {
|
||||
ctx, cancel := r.ctx(ctx)
|
||||
defer cancel()
|
||||
|
||||
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)
|
||||
return translate(err)
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// ModelVersionRepo tracks which models have produced stored data.
|
||||
type ModelVersionRepo struct{ base }
|
||||
|
||||
var _ domain.ModelVersionRepository = (*ModelVersionRepo)(nil)
|
||||
|
||||
// Observe records that a model was used, so a configuration change can be
|
||||
// detected and the affected records identified for reprocessing.
|
||||
func (r *ModelVersionRepo) Observe(ctx context.Context, workspaceID, kind, name string, dims int) error {
|
||||
if name == "" {
|
||||
return nil
|
||||
}
|
||||
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: "kind", Value: kind}, bson.E{Key: "name", Value: name})
|
||||
|
||||
now := time.Now().UTC()
|
||||
update := bson.D{
|
||||
{Key: "$setOnInsert", Value: bson.D{
|
||||
{Key: "_id", Value: ids.New()},
|
||||
{Key: "workspace_id", Value: workspaceID},
|
||||
{Key: "kind", Value: kind},
|
||||
{Key: "name", Value: name},
|
||||
{Key: "first_seen_at", Value: now},
|
||||
}},
|
||||
{Key: "$set", Value: bson.D{
|
||||
{Key: "last_seen_at", Value: now},
|
||||
{Key: "dimensions", Value: dims},
|
||||
}},
|
||||
}
|
||||
_, err = r.coll.UpdateOne(ctx, f, update, options.UpdateOne().SetUpsert(true))
|
||||
if isDup(err) {
|
||||
err = nil
|
||||
}
|
||||
r.observe("observe", started, err)
|
||||
return translate(err)
|
||||
}
|
||||
|
||||
// List returns the models seen in a workspace.
|
||||
func (r *ModelVersionRepo) List(ctx context.Context, workspaceID string) ([]*domain.ModelVersion, error) {
|
||||
ctx, cancel := r.ctx(ctx)
|
||||
defer cancel()
|
||||
started := time.Now()
|
||||
|
||||
f, err := wsFilter(workspaceID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
cur, err := r.coll.Find(ctx, f, options.Find().SetSort(bson.D{
|
||||
{Key: "kind", Value: 1}, {Key: "name", Value: 1},
|
||||
}))
|
||||
r.observe("list", started, err)
|
||||
if err != nil {
|
||||
return nil, translate(err)
|
||||
}
|
||||
var out []*domain.ModelVersion
|
||||
if err := cur.All(ctx, &out); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
Reference in New Issue
Block a user