storage

package
v0.1.12 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 4, 2026 License: MIT Imports: 10 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var ErrBudgetExceeded = fmt.Errorf("budget exceeded")

ErrBudgetExceeded indicates the budget limit has been reached

View Source
var ErrStateConflict = fmt.Errorf("state conflict: status was modified by another request")

ErrStateConflict is returned when a compare-and-update fails because the current status does not match the expected status (concurrent modification).

Functions

func NewDB

func NewDB(cfg Config) (*gorm.DB, error)

NewDB creates a new database connection with auto-migration

func NewDBWithLogger

func NewDBWithLogger(cfg Config, logLevel logger.LogLevel) (*gorm.DB, error)

NewDBWithLogger creates a new database connection with custom logger

Types

type APIKeyFilter

type APIKeyFilter struct {
	EnabledOnly bool
	Offset      int
	Limit       int
}

APIKeyFilter for querying API keys

type APIKeyRepository

type APIKeyRepository interface {
	Create(ctx context.Context, key *types.APIKey) error
	Get(ctx context.Context, id string) (*types.APIKey, error)
	Update(ctx context.Context, key *types.APIKey) error
	Delete(ctx context.Context, id string) error
	List(ctx context.Context, filter APIKeyFilter) ([]*types.APIKey, error)
	UpdateLastUsed(ctx context.Context, id string) error
}

APIKeyRepository defines the interface for API key persistence

type AuditFilter

type AuditFilter struct {
	RequestID *types.SignRequestID
	APIKeyID  *string
	EventType *types.AuditEventType
	ChainType *types.ChainType
	StartTime *time.Time
	EndTime   *time.Time
	// Cursor-based pagination (preferred over Offset)
	// Cursor is the timestamp of the last item from previous page
	Cursor *time.Time
	// CursorID is the ID of the last item (for tie-breaking when timestamps are equal)
	CursorID *types.AuditID
	Limit    int
}

AuditFilter for querying audit records

type AuditRepository

type AuditRepository interface {
	Log(ctx context.Context, record *types.AuditRecord) error
	Query(ctx context.Context, filter AuditFilter) ([]*types.AuditRecord, error)
	Count(ctx context.Context, filter AuditFilter) (int, error)
	GetByRequestID(ctx context.Context, requestID types.SignRequestID) ([]*types.AuditRecord, error)
}

AuditRepository defines the interface for audit log persistence

type BudgetRepository

type BudgetRepository interface {
	Create(ctx context.Context, budget *types.RuleBudget) error
	GetByRuleID(ctx context.Context, ruleID types.RuleID, unit string) (*types.RuleBudget, error)
	Delete(ctx context.Context, id string) error
	DeleteByRuleID(ctx context.Context, ruleID types.RuleID) error
	// AtomicSpend atomically increments spent amount and tx count.
	// Returns ErrBudgetExceeded if the spend would exceed limits.
	// Uses SQL-level conditional UPDATE to prevent race conditions.
	AtomicSpend(ctx context.Context, ruleID types.RuleID, unit string, amount string) error
	// ResetBudget resets spent/txCount/alertSent for a new period.
	// Uses conditional WHERE to ensure idempotent reset (only resets if in old period).
	ResetBudget(ctx context.Context, ruleID types.RuleID, unit string, currentPeriodStart time.Time) error
	ListByRuleID(ctx context.Context, ruleID types.RuleID) ([]*types.RuleBudget, error)
	ListByRuleIDs(ctx context.Context, ruleIDs []types.RuleID) ([]*types.RuleBudget, error)
	// MarkAlertSent sets alert_sent=true for the given rule+unit budget.
	// This prevents duplicate alert notifications within the same period.
	MarkAlertSent(ctx context.Context, ruleID types.RuleID, unit string) error
}

BudgetRepository defines the interface for budget persistence

type Config

type Config struct {
	DSN string `yaml:"dsn"`
}

Config holds database configuration

type GormAPIKeyRepository

type GormAPIKeyRepository struct {
	// contains filtered or unexported fields
}

GormAPIKeyRepository implements APIKeyRepository using GORM

func NewGormAPIKeyRepository

func NewGormAPIKeyRepository(db *gorm.DB) (*GormAPIKeyRepository, error)

NewGormAPIKeyRepository creates a new GORM-based API key repository

func (*GormAPIKeyRepository) Create

func (r *GormAPIKeyRepository) Create(ctx context.Context, key *types.APIKey) error

Create creates a new API key

func (*GormAPIKeyRepository) Delete

func (r *GormAPIKeyRepository) Delete(ctx context.Context, id string) error

Delete deletes an API key by ID

func (*GormAPIKeyRepository) Get

Get retrieves an API key by ID

func (*GormAPIKeyRepository) List

func (r *GormAPIKeyRepository) List(ctx context.Context, filter APIKeyFilter) ([]*types.APIKey, error)

List returns API keys matching the filter

func (*GormAPIKeyRepository) Update

func (r *GormAPIKeyRepository) Update(ctx context.Context, key *types.APIKey) error

Update updates an existing API key

func (*GormAPIKeyRepository) UpdateLastUsed

func (r *GormAPIKeyRepository) UpdateLastUsed(ctx context.Context, id string) error

UpdateLastUsed updates the last used timestamp for an API key

type GormAuditRepository

type GormAuditRepository struct {
	// contains filtered or unexported fields
}

GormAuditRepository implements AuditRepository using GORM

func NewGormAuditRepository

func NewGormAuditRepository(db *gorm.DB) (*GormAuditRepository, error)

NewGormAuditRepository creates a new GORM-based audit repository

func (*GormAuditRepository) Count

func (r *GormAuditRepository) Count(ctx context.Context, filter AuditFilter) (int, error)

Count returns the total count of audit records matching the filter (ignoring Offset/Limit)

func (*GormAuditRepository) GetByRequestID

func (r *GormAuditRepository) GetByRequestID(ctx context.Context, requestID types.SignRequestID) ([]*types.AuditRecord, error)

GetByRequestID returns all audit records for a specific request

func (*GormAuditRepository) Log

Log creates a new audit record

func (*GormAuditRepository) Query

Query returns audit records matching the filter using cursor-based pagination

type GormBudgetRepository

type GormBudgetRepository struct {
	// contains filtered or unexported fields
}

GormBudgetRepository implements BudgetRepository using GORM

func NewGormBudgetRepository

func NewGormBudgetRepository(db *gorm.DB) (*GormBudgetRepository, error)

NewGormBudgetRepository creates a new GORM-based budget repository

func (*GormBudgetRepository) AtomicSpend

func (r *GormBudgetRepository) AtomicSpend(ctx context.Context, ruleID types.RuleID, unit string, amount string) error

AtomicSpend atomically increments the spent amount and tx count for a budget. Uses conditional WHERE clause to prevent exceeding limits in concurrent scenarios. Returns ErrBudgetExceeded if the spend would exceed max_total or max_tx_count.

func (*GormBudgetRepository) Create

func (r *GormBudgetRepository) Create(ctx context.Context, budget *types.RuleBudget) error

Create creates a new budget record

func (*GormBudgetRepository) Delete

func (r *GormBudgetRepository) Delete(ctx context.Context, id string) error

Delete deletes a budget by ID

func (*GormBudgetRepository) DeleteByRuleID

func (r *GormBudgetRepository) DeleteByRuleID(ctx context.Context, ruleID types.RuleID) error

DeleteByRuleID deletes all budgets for a rule

func (*GormBudgetRepository) GetByRuleID

func (r *GormBudgetRepository) GetByRuleID(ctx context.Context, ruleID types.RuleID, unit string) (*types.RuleBudget, error)

GetByRuleID retrieves a budget by rule ID and unit

func (*GormBudgetRepository) ListByRuleID

func (r *GormBudgetRepository) ListByRuleID(ctx context.Context, ruleID types.RuleID) ([]*types.RuleBudget, error)

ListByRuleID returns all budgets for a specific rule

func (*GormBudgetRepository) ListByRuleIDs

func (r *GormBudgetRepository) ListByRuleIDs(ctx context.Context, ruleIDs []types.RuleID) ([]*types.RuleBudget, error)

ListByRuleIDs returns all budgets for the given rule IDs

func (*GormBudgetRepository) MarkAlertSent added in v0.1.0

func (r *GormBudgetRepository) MarkAlertSent(ctx context.Context, ruleID types.RuleID, unit string) error

MarkAlertSent sets alert_sent=true for the given rule+unit budget.

func (*GormBudgetRepository) ResetBudget

func (r *GormBudgetRepository) ResetBudget(ctx context.Context, ruleID types.RuleID, unit string, currentPeriodStart time.Time) error

ResetBudget resets the budget for a new period. Uses conditional WHERE on updated_at to ensure idempotent reset. Only resets if the budget was last updated before the current period start.

type GormRequestRepository

type GormRequestRepository struct {
	// contains filtered or unexported fields
}

GormRequestRepository implements RequestRepository using GORM

func NewGormRequestRepository

func NewGormRequestRepository(db *gorm.DB) (*GormRequestRepository, error)

NewGormRequestRepository creates a new GORM-based request repository

func (*GormRequestRepository) CompareAndUpdate

func (r *GormRequestRepository) CompareAndUpdate(ctx context.Context, req *types.SignRequest, expectedStatus types.SignRequestStatus) error

CompareAndUpdate atomically updates a request only if its current status matches expectedStatus. Uses a SQL WHERE clause to ensure atomicity at the database level, preventing TOCTOU race conditions. Returns ErrStateConflict if the status has been modified concurrently.

func (*GormRequestRepository) Count

func (r *GormRequestRepository) Count(ctx context.Context, filter RequestFilter) (int, error)

Count returns the total count of requests matching the filter (ignoring Offset/Limit)

func (*GormRequestRepository) Create

Create creates a new sign request

func (*GormRequestRepository) Get

Get retrieves a sign request by ID

func (*GormRequestRepository) List

List returns sign requests matching the filter using cursor-based pagination

func (*GormRequestRepository) Update

Update updates an existing sign request

func (*GormRequestRepository) UpdateStatus

UpdateStatus updates the status of a sign request

type GormRuleRepository

type GormRuleRepository struct {
	// contains filtered or unexported fields
}

GormRuleRepository implements RuleRepository using GORM

func NewGormRuleRepository

func NewGormRuleRepository(db *gorm.DB) (*GormRuleRepository, error)

NewGormRuleRepository creates a new GORM-based rule repository

func (*GormRuleRepository) Count

func (r *GormRuleRepository) Count(ctx context.Context, filter RuleFilter) (int, error)

Count returns the total count of rules matching the filter (ignoring Offset/Limit)

func (*GormRuleRepository) Create

func (r *GormRuleRepository) Create(ctx context.Context, rule *types.Rule) error

Create creates a new rule

func (*GormRuleRepository) Delete

func (r *GormRuleRepository) Delete(ctx context.Context, id types.RuleID) error

Delete deletes a rule by ID

func (*GormRuleRepository) Get

Get retrieves a rule by ID

func (*GormRuleRepository) IncrementMatchCount

func (r *GormRuleRepository) IncrementMatchCount(ctx context.Context, id types.RuleID) error

IncrementMatchCount increments the match count for a rule

func (*GormRuleRepository) List

func (r *GormRuleRepository) List(ctx context.Context, filter RuleFilter) ([]*types.Rule, error)

List returns rules matching the filter

func (*GormRuleRepository) ListByChainType

func (r *GormRuleRepository) ListByChainType(ctx context.Context, chainType types.ChainType) ([]*types.Rule, error)

ListByChainType returns all enabled rules for a specific chain type

func (*GormRuleRepository) RunInTransaction added in v0.1.0

func (r *GormRuleRepository) RunInTransaction(ctx context.Context, fn func(txRepo RuleRepository) error) error

RunInTransaction runs fn inside a database transaction. A new GormRuleRepository backed by the transaction is passed to fn.

func (*GormRuleRepository) Update

func (r *GormRuleRepository) Update(ctx context.Context, rule *types.Rule) error

Update updates an existing rule

type GormTemplateRepository

type GormTemplateRepository struct {
	// contains filtered or unexported fields
}

GormTemplateRepository implements TemplateRepository using GORM

func NewGormTemplateRepository

func NewGormTemplateRepository(db *gorm.DB) (*GormTemplateRepository, error)

NewGormTemplateRepository creates a new GORM-based template repository

func (*GormTemplateRepository) Count

func (r *GormTemplateRepository) Count(ctx context.Context, filter TemplateFilter) (int, error)

Count returns the total count of templates matching the filter

func (*GormTemplateRepository) Create

Create creates a new template

func (*GormTemplateRepository) Delete

func (r *GormTemplateRepository) Delete(ctx context.Context, id string) error

Delete deletes a template by ID

func (*GormTemplateRepository) Get

Get retrieves a template by ID

func (*GormTemplateRepository) GetByName

func (r *GormTemplateRepository) GetByName(ctx context.Context, name string) (*types.RuleTemplate, error)

GetByName retrieves a template by name

func (*GormTemplateRepository) List

List returns templates matching the filter

func (*GormTemplateRepository) Update

Update updates an existing template

type InMemoryNonceStore

type InMemoryNonceStore struct {
	// contains filtered or unexported fields
}

InMemoryNonceStore implements NonceStore with in-memory storage. Suitable for single-instance deployments. For multi-instance deployments, use Redis-based implementation.

func NewInMemoryNonceStore

func NewInMemoryNonceStore(cleanupInterval time.Duration) (*InMemoryNonceStore, error)

NewInMemoryNonceStore creates a new in-memory nonce store. cleanupInterval specifies how often to run cleanup of expired nonces.

func (*InMemoryNonceStore) CheckAndStore

func (s *InMemoryNonceStore) CheckAndStore(ctx context.Context, apiKeyID, nonce string, ttl time.Duration) (bool, error)

CheckAndStore checks if a nonce exists and stores it if not. Returns true if the nonce was stored (new), false if it already exists (replay).

func (*InMemoryNonceStore) Close

func (s *InMemoryNonceStore) Close() error

Close stops the cleanup goroutine

type MemoryRuleRepository added in v0.1.0

type MemoryRuleRepository struct {
	// contains filtered or unexported fields
}

MemoryRuleRepository is an in-memory RuleRepository for validation and tests. All methods are safe for concurrent use.

func NewMemoryRuleRepository added in v0.1.0

func NewMemoryRuleRepository() *MemoryRuleRepository

NewMemoryRuleRepository creates an empty in-memory rule repository.

func (*MemoryRuleRepository) Count added in v0.1.0

func (r *MemoryRuleRepository) Count(ctx context.Context, filter RuleFilter) (int, error)

Count returns the number of rules matching the filter.

func (*MemoryRuleRepository) Create added in v0.1.0

func (r *MemoryRuleRepository) Create(ctx context.Context, rule *types.Rule) error

Create stores a new rule. Fails if ID already exists.

func (*MemoryRuleRepository) Delete added in v0.1.0

Delete removes a rule by ID.

func (*MemoryRuleRepository) Get added in v0.1.0

Get returns a rule by ID.

func (*MemoryRuleRepository) IncrementMatchCount added in v0.1.0

func (r *MemoryRuleRepository) IncrementMatchCount(ctx context.Context, id types.RuleID) error

IncrementMatchCount is a no-op for in-memory repo.

func (*MemoryRuleRepository) List added in v0.1.0

func (r *MemoryRuleRepository) List(ctx context.Context, filter RuleFilter) ([]*types.Rule, error)

List returns rules matching the filter. Scope fields (ChainType, ChainID, APIKeyID, SignerAddress) use same semantics as GORM: rule field nil = applies to all; include when rule field is nil OR equals filter.

func (*MemoryRuleRepository) ListByChainType added in v0.1.0

func (r *MemoryRuleRepository) ListByChainType(ctx context.Context, chainType types.ChainType) ([]*types.Rule, error)

ListByChainType returns enabled rules for the given chain type.

func (*MemoryRuleRepository) RunInTransaction added in v0.1.0

func (r *MemoryRuleRepository) RunInTransaction(_ context.Context, fn func(txRepo RuleRepository) error) error

RunInTransaction runs fn directly (in-memory repo is single-process, already atomic).

func (*MemoryRuleRepository) Update added in v0.1.0

func (r *MemoryRuleRepository) Update(ctx context.Context, rule *types.Rule) error

Update replaces an existing rule.

type NonceStore

type NonceStore interface {
	// CheckAndStore checks if a nonce exists and stores it if not.
	// Returns true if the nonce was stored (new), false if it already exists (replay).
	CheckAndStore(ctx context.Context, apiKeyID, nonce string, ttl time.Duration) (bool, error)
}

NonceStore provides storage for request nonces to prevent replay attacks. Nonces are stored with TTL and automatically cleaned up.

type RequestFilter

type RequestFilter struct {
	APIKeyID      *string
	SignerAddress *string
	ChainType     *types.ChainType
	ChainID       *string
	Status        []types.SignRequestStatus
	// Cursor-based pagination (preferred over Offset)
	// Cursor is the created_at timestamp of the last item from previous page
	Cursor *time.Time
	// CursorID is the ID of the last item (for tie-breaking when timestamps are equal)
	CursorID *types.SignRequestID
	Limit    int
}

RequestFilter for querying requests

type RequestRepository

type RequestRepository interface {
	Create(ctx context.Context, req *types.SignRequest) error
	Get(ctx context.Context, id types.SignRequestID) (*types.SignRequest, error)
	Update(ctx context.Context, req *types.SignRequest) error
	// CompareAndUpdate atomically updates a request only if its current status
	// matches expectedStatus. Returns ErrStateConflict if the status has changed.
	CompareAndUpdate(ctx context.Context, req *types.SignRequest, expectedStatus types.SignRequestStatus) error
	List(ctx context.Context, filter RequestFilter) ([]*types.SignRequest, error)
	Count(ctx context.Context, filter RequestFilter) (int, error)
	UpdateStatus(ctx context.Context, id types.SignRequestID, status types.SignRequestStatus) error
}

RequestRepository defines the interface for sign request persistence

type RuleFilter

type RuleFilter struct {
	ChainType     *types.ChainType
	ChainID       *string
	APIKeyID      *string
	SignerAddress *string
	Type          *types.RuleType
	Source        *types.RuleSource
	EnabledOnly   bool
	Offset        int
	Limit         int
}

RuleFilter for querying rules

type RuleRepository

type RuleRepository interface {
	Create(ctx context.Context, rule *types.Rule) error
	Get(ctx context.Context, id types.RuleID) (*types.Rule, error)
	Update(ctx context.Context, rule *types.Rule) error
	Delete(ctx context.Context, id types.RuleID) error
	List(ctx context.Context, filter RuleFilter) ([]*types.Rule, error)
	Count(ctx context.Context, filter RuleFilter) (int, error)
	ListByChainType(ctx context.Context, chainType types.ChainType) ([]*types.Rule, error)
	IncrementMatchCount(ctx context.Context, id types.RuleID) error
}

RuleRepository defines the interface for rule persistence

type TemplateFilter

type TemplateFilter struct {
	Type        *types.RuleType
	Source      *types.RuleSource
	EnabledOnly bool
	Offset      int
	Limit       int
}

TemplateFilter for querying templates

type TemplateRepository

type TemplateRepository interface {
	Create(ctx context.Context, tmpl *types.RuleTemplate) error
	Get(ctx context.Context, id string) (*types.RuleTemplate, error)
	GetByName(ctx context.Context, name string) (*types.RuleTemplate, error)
	Update(ctx context.Context, tmpl *types.RuleTemplate) error
	Delete(ctx context.Context, id string) error
	List(ctx context.Context, filter TemplateFilter) ([]*types.RuleTemplate, error)
	Count(ctx context.Context, filter TemplateFilter) (int, error)
}

TemplateRepository defines the interface for template persistence

type Transactional added in v0.1.0

type Transactional interface {
	RunInTransaction(ctx context.Context, fn func(txRepo RuleRepository) error) error
}

Transactional is implemented by repositories that support atomic operations. This is optional — callers should type-assert before use.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL