package failures import ( "context" "database/sql" "reanimator/internal/domain" ) type FailureRepository struct { db *sql.DB } func NewFailureRepository(db *sql.DB) *FailureRepository { return &FailureRepository{db: db} } func (r *FailureRepository) BeginTx(ctx context.Context) (*sql.Tx, error) { return r.db.BeginTx(ctx, nil) } type execer interface { ExecContext(ctx context.Context, query string, args ...any) (sql.Result, error) QueryRowContext(ctx context.Context, query string, args ...any) *sql.Row } func execerFor(db *sql.DB, tx *sql.Tx) execer { if tx != nil { return tx } return db } func (r *FailureRepository) Upsert(ctx context.Context, tx *sql.Tx, event domain.FailureEvent) (int64, error) { execer := execerFor(r.db, tx) _, err := execer.ExecContext(ctx, `INSERT INTO failure_events (source, external_id, component_id, asset_id, failure_type, failure_time, details, confidence) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ON DUPLICATE KEY UPDATE component_id = VALUES(component_id), asset_id = VALUES(asset_id), failure_type = VALUES(failure_type), failure_time = VALUES(failure_time), details = VALUES(details), confidence = VALUES(confidence)`, event.Source, event.ExternalID, event.ComponentID, event.AssetID, event.FailureType, event.FailureTime, event.Details, event.Confidence, ) if err != nil { return 0, err } var id int64 row := execer.QueryRowContext(ctx, `SELECT id FROM failure_events WHERE source = ? AND external_id = ?`, event.Source, event.ExternalID, ) if err := row.Scan(&id); err != nil { return 0, err } return id, nil } func (r *FailureRepository) ListAll(ctx context.Context, limit int) ([]domain.FailureEvent, error) { if limit <= 0 { limit = 200 } rows, err := r.db.QueryContext(ctx, `SELECT id, source, external_id, component_id, asset_id, failure_type, failure_time, details, confidence, created_at FROM failure_events ORDER BY failure_time DESC, id DESC LIMIT ?`, limit, ) if err != nil { return nil, err } defer rows.Close() items := make([]domain.FailureEvent, 0) for rows.Next() { var event domain.FailureEvent var assetID sql.NullInt64 var details sql.NullString var confidence sql.NullFloat64 if err := rows.Scan(&event.ID, &event.Source, &event.ExternalID, &event.ComponentID, &assetID, &event.FailureType, &event.FailureTime, &details, &confidence, &event.CreatedAt); err != nil { return nil, err } if assetID.Valid { value := assetID.Int64 event.AssetID = &value } if details.Valid { value := details.String event.Details = &value } if confidence.Valid { value := confidence.Float64 event.Confidence = &value } items = append(items, event) } if err := rows.Err(); err != nil { return nil, err } return items, nil }