repository

package
v1.6.2 Latest Latest
Warning

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

Go to latest
Published: Sep 29, 2026 License: AGPL-3.0 Imports: 10 Imported by: 0

Documentation

Overview

Package repository is the data-access layer. It holds the GORM-backed repositories for the core models — users, accounts, and transactions — behind interfaces that expose persistence operations and nothing else. Business rules live in the service packages above; this layer only reads and writes rows.

Repositories bundles the three repositories, and NewRepositories wires them from a single *gorm.DB. Each constructor (and NewRepositories itself) rejects a nil database with errors.ErrNilDB. Every repository owns its own sentinel errors — ErrUserNotFound, ErrAccountNotFound, ErrTransactionNotFound, and the matching ErrFailedTo* values — and translates raw GORM errors into them, so callers match on package errors and never import GORM's.

Reads that return lists take limit and offset for pagination; the service layer is responsible for turning page numbers into those.

Soft deletes

Deletion is reversible and implemented by hand through a deleted_at column, not GORM's automatic soft delete. Delete stamps deleted_at with the current time, Restore clears it (an Unscoped update, since the row is otherwise hidden), and every read filters on deleted_at IS NULL. A deleted row therefore stays in the table but disappears from normal queries until it is restored.

Transactions

Write methods come in a plain form and a WithTx form. The WithTx variant accepts a *gorm.DB supplied by the caller, so a write can join a larger unit of work — for example creating a user and their first account atomically. The account index handed to a new account is derived as one past the highest existing index across all accounts, with index 0 reserved for the admin.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrAccountNotFound           = errors.New("account not found")
	ErrFailedToCreateAccount     = errors.New("failed to create account")
	ErrFailedToGetAccount        = errors.New("failed to get account")
	ErrFailedToGetAccountsByUser = errors.New("failed to get accounts by user")
	ErrFailedToGetNextIndex      = errors.New("failed to get next account index")
	ErrFailedToUpdateAccount     = errors.New("failed to update account")
	ErrFailedToDeleteAccount     = errors.New("failed to delete account")
	ErrFailedToRestoreAccount    = errors.New("failed to restore account")
)

Common errors for AccountRepository

View Source
var (
	ErrAirtelNotFound       = errors.New("airtel transaction not found")
	ErrFailedToRecordAirtel = errors.New("failed to record airtel observation")
	ErrFailedToPollAirtel   = errors.New("failed to fetch due airtel observations")
)

Errors for AirtelTransactionRepository.

View Source
var (
	ErrCounterpartyNotFound        = errors.New("counterparty not found")
	ErrFailedToCreateCounterparty  = errors.New("failed to create counterparty")
	ErrFailedToGetCounterparty     = errors.New("failed to get counterparty")
	ErrFailedToGetCounterparties   = errors.New("failed to get counterparties")
	ErrFailedToCountCounterparties = errors.New("failed to count counterparties")
	ErrFailedToUpdateCounterparty  = errors.New("failed to update counterparty")

	ErrCounterpartyAddressNotFound        = errors.New("counterparty address not found")
	ErrFailedToAddCounterpartyAddress     = errors.New("failed to add counterparty address")
	ErrFailedToGetCounterpartyAddress     = errors.New("failed to get counterparty address")
	ErrFailedToGetCounterpartyAddresses   = errors.New("failed to get counterparty addresses")
	ErrFailedToCountCounterpartyAddresses = errors.New("failed to count counterparty addresses")
	ErrFailedToUpdateCounterpartyAddress  = errors.New("failed to update counterparty address")

	ErrFailedToRecordScreening = errors.New("failed to record screening")
)

Common errors for CounterpartyRepository.

View Source
var (
	ErrMpesaConflict  = errors.New("mpesa transaction already recorded")
	ErrFailedToRecord = errors.New("failed to record mpesa observation")
	ErrMpesaNotFound  = errors.New("mpesa transaction not found")
	ErrFailedToPoll   = errors.New("failed to fetch due mpesa observations")
)

Errors for MpesaTransactionRepository.

View Source
var (
	ErrTransactionNotFound             = errors.New("transaction not found")
	ErrFailedToCreateTransaction       = errors.New("failed to create transaction")
	ErrFailedToCreateBatchTransactions = errors.New("failed to create batch transactions")
	ErrFailedToGetTransaction          = errors.New("failed to get transaction")
	ErrFailedToGetTransactionByHash    = errors.New("failed to get transaction by stellar hash")
	ErrFailedToGetTransactionsByLoanID = errors.New("failed to get transactions by loan ID")
	ErrFailedToGetTransactionsByUserID = errors.New("failed to get transactions by user ID")
	ErrFailedToGetTransactionsByStatus = errors.New("failed to get transactions by status")
	ErrFailedToUpdateTransaction       = errors.New("failed to update transaction")
	ErrFailedToListTransactions        = errors.New("failed to list transactions")
	ErrFailedToCountTransactions       = errors.New("failed to count transactions")
)

Common errors for TransactionRepository

View Source
var (
	ErrUserNotFound           = errors.New("user not found")
	ErrFailedToCreateUser     = errors.New("failed to create user")
	ErrFailedToGetUser        = errors.New("failed to get user")
	ErrFailedToGetUsers       = errors.New("failed to get users")
	ErrFailedToGetUsersByKYC  = errors.New("failed to get users by KYC status")
	ErrFailedToGetUsersByRole = errors.New("failed to get users by role")
	ErrFailedToCountUsers     = errors.New("failed to count users")
	ErrFailedToCountAdmins    = errors.New("failed to count admins")
	ErrFailedToUpdateUser     = errors.New("failed to update user")
	ErrFailedToDeleteUser     = errors.New("failed to delete user")
	ErrFailedToRestoreUser    = errors.New("failed to restore user")
)

Common errors for UserRepository

View Source
var ErrMpesaBalanceQueryNotFound = errors.New("mpesa balance query not found")

ErrMpesaBalanceQueryNotFound means no pending query matches the OriginatorConversationID a balance result arrived with.

View Source
var ErrMpesaValidationNotFound = errors.New("mpesa number validation not found")

ErrMpesaValidationNotFound means no cached verdict exists for that hash.

Functions

func IsTransactionUpdatableColumn added in v1.0.0

func IsTransactionUpdatableColumn(col string) bool

IsTransactionUpdatableColumn reports whether col may be written via UpdateFields. Exposed for test-guarding the service-layer partial-update mapping against drift.

Types

type AccountRepository

type AccountRepository interface {
	// Create operations
	Create(ctx context.Context, account *models.Account) error
	CreateWithTx(ctx context.Context, tx *gorm.DB, account *models.Account) error

	// Read operations
	GetByID(ctx context.Context, id string) (*models.Account, error)
	GetByUserID(ctx context.Context, userID string) (*models.Account, error)
	GetByPublicKey(ctx context.Context, publicKey string) (*models.Account, error)
	GetNextAccountIndex(ctx context.Context, userID string) (int, error)
	GetNextAccountIndexWithTx(ctx context.Context, tx *gorm.DB) (int, error)
	// EnsureAccountIndexFloor advances account_index_seq so the next handed-out
	// index is >= floor. No-op when the sequence is already at or past floor.
	EnsureAccountIndexFloor(ctx context.Context, floor int64) error
	// MaxAccountIndex is the highest derivation index ever recorded, counting
	// soft-deleted rows. Returns -1 when the table holds none.
	MaxAccountIndex(ctx context.Context) (int64, error)
	// EnsureAccountIndexIntegrity floors the sequence above both the recorded
	// high-water mark and the operator-supplied base, returning the index the
	// next allocation will hand out.
	EnsureAccountIndexIntegrity(ctx context.Context, base int64) (int64, error)

	// Update operations
	Update(ctx context.Context, account *models.Account) error
	UpdateChainStatus(ctx context.Context, id string, chainStatus string) error
	Restore(ctx context.Context, id string) error

	// Delete operations
	Delete(ctx context.Context, id string) error
}

AccountRepository defines the interface for account data access

func NewAccountRepository

func NewAccountRepository(db *gorm.DB) (AccountRepository, error)

NewAccountRepository creates a new AccountRepository

type AirtelSummaryCursorRepository added in v1.6.0

type AirtelSummaryCursorRepository interface {
	// Get returns the exclusive start of the next window to sweep.
	Get(ctx context.Context) (time.Time, error)
	// Advance moves the cursor forward to at.
	Advance(ctx context.Context, at time.Time) error
}

AirtelSummaryCursorRepository tracks how far the Transactions Summary reconciliation sweep has walked. One row (id=1).

func NewAirtelSummaryCursorRepository added in v1.6.0

func NewAirtelSummaryCursorRepository(db *gorm.DB) (AirtelSummaryCursorRepository, error)

NewAirtelSummaryCursorRepository builds the repository.

type AirtelTransactionRepository added in v1.6.0

type AirtelTransactionRepository interface {
	// RecordCallback upserts an observation from a callback. A second
	// callback for the same transaction updates the existing row rather than
	// conflicting, which is what makes an intermediate-then-final pair one
	// payment instead of two.
	//
	// A row already confirmed by an enquiry is left untouched: the enquiry is
	// the authority, and a late callback must not walk a settled row back.
	RecordCallback(ctx context.Context, tx *models.AirtelTransaction) error

	// GetByPartnerID fetches one observation by the id we generated.
	GetByPartnerID(ctx context.Context, partnerTxnID string) (*models.AirtelTransaction, error)

	// GetByAirtelMoneyID fetches one observation by Airtel's receipt. Only
	// settled rows have one.
	GetByAirtelMoneyID(ctx context.Context, airtelMoneyID string) (*models.AirtelTransaction, error)

	// DuePoll returns unconfirmed observations whose NextPollAt has come due,
	// oldest first so a backlog drains in arrival order.
	DuePoll(ctx context.Context, limit int) ([]*models.AirtelTransaction, error)

	// Confirm marks an observation verified by an independent check and
	// records the receipt that check disclosed. Passing an empty
	// airtelMoneyID leaves the column alone rather than clearing it: a
	// confirmation of a failed transaction has no receipt to record, and
	// nulling one that a callback already supplied would lose the only key a
	// refund accepts.
	Confirm(ctx context.Context, partnerTxnID string, via models.AirtelTransactionConfirmVia, statusCode, airtelMoneyID, loanID string) error

	// UpdatePoll writes the next poll moment and counts the attempt.
	UpdatePoll(ctx context.Context, partnerTxnID string, at time.Time) error

	// StopPoll clears the poll moment, taking the observation out of the due
	// set for good.
	StopPoll(ctx context.Context, partnerTxnID string) error

	// UpsertFromSummary records a settled transaction the reconciliation
	// sweep found, or fills in what it learned about one already staged.
	// This is how a payment whose callback was lost is still credited.
	UpsertFromSummary(ctx context.Context, tx *models.AirtelTransaction) error

	// ListUnappliedConfirmed returns confirmed, loan-attributed observations
	// not yet converted toward a loan's repayment progress, oldest first.
	ListUnappliedConfirmed(ctx context.Context, limit int) ([]*models.AirtelTransaction, error)

	// SetAppliedStroops records the converted figure for one observation.
	SetAppliedStroops(ctx context.Context, id string, stroops int64) error

	// SumAppliedStroopsByLoan totals every observation already converted for
	// a loan.
	SumAppliedStroopsByLoan(ctx context.Context, loanID string) (int64, error)
}

AirtelTransactionRepository persists inbound Airtel Money observations.

It differs from MpesaTransactionRepository in one structural way, and the difference runs through the whole interface: an Airtel transaction can report more than once. Airtel's callback carries intermediate or final status, so the write path is an upsert keyed by our own transaction id rather than an insert that treats a second notification as a conflict to swallow.

func NewAirtelTransactionRepository added in v1.6.0

func NewAirtelTransactionRepository(db *gorm.DB) (AirtelTransactionRepository, error)

NewAirtelTransactionRepository creates a new AirtelTransactionRepository.

type CounterpartyRepository added in v1.5.0

type CounterpartyRepository interface {
	Create(ctx context.Context, cp *models.Counterparty) error
	GetByID(ctx context.Context, id string) (*models.Counterparty, error) // preloads Addresses
	List(ctx context.Context, kybStatus string, limit, offset int) ([]*models.Counterparty, error)
	Count(ctx context.Context, kybStatus string) (int64, error)
	ApproveKYB(ctx context.Context, id, actor string) error
	RejectKYB(ctx context.Context, id, actor string) error

	AddAddress(ctx context.Context, addr *models.CounterpartyAddress) error
	GetAddressByID(ctx context.Context, id string) (*models.CounterpartyAddress, error) // preloads Counterparty + LatestScreening
	ListAddressesByCounterparty(ctx context.Context, counterpartyID string) ([]*models.CounterpartyAddress, error)
	ListAddressesByStatus(ctx context.Context, statuses []string, limit, offset int) ([]*models.CounterpartyAddress, error)
	CountAddressesByStatus(ctx context.Context, statuses []string) (int64, error)
	ApproveAddress(ctx context.Context, id, actor, reason string) error
	RejectAddress(ctx context.Context, id, actor, reason string) error
	RevokeAddress(ctx context.Context, id, actor, reason string) error

	// ListScreeningsByAddress returns an address's full screening history,
	// newest first — the audit artefact the screening detail screen renders
	// from raw_payload, per the source design doc §14.
	ListScreeningsByAddress(ctx context.Context, addressID string) ([]*models.AddressScreening, error)

	// RecordScreening inserts the append-only screening row and updates the
	// parent address's last_screening_id/screened_at/expires_at in one
	// transaction. newAddressStatus and newOnchainState are each optional —
	// nil leaves that column untouched (a screening whose verdict didn't
	// resolve to anything new, e.g. still pending, must not silently
	// downgrade an address that was already approved).
	RecordScreening(ctx context.Context, screening *models.AddressScreening, newAddressStatus *models.CounterpartyAddressStatus, newOnchainState *models.CounterpartyAddressOnchainState, expiresAt *time.Time) error

	// ListAddressesNeedingOnchainAllow returns approved addresses whose
	// onchain_state is still "pending" — the on-chain writer's queue for
	// allow_depositor. See the source design doc §14 on why this write
	// stays a separate worker rather than something the admin web process
	// signs itself.
	ListAddressesNeedingOnchainAllow(ctx context.Context, limit int) ([]*models.CounterpartyAddress, error)

	// ListAddressesNeedingOnchainRevoke returns addresses with a recorded
	// revocation intent (RevokedAt set) whose onchain_state has not yet
	// been confirmed as revoked — the on-chain writer's queue for
	// disallow_depositor.
	ListAddressesNeedingOnchainRevoke(ctx context.Context, limit int) ([]*models.CounterpartyAddress, error)

	// SetOnchainState records what the on-chain writer actually observed
	// after submitting a transaction — the "observed state" half of the
	// intent/observed split described in RevokeAddress's doc comment.
	SetOnchainState(ctx context.Context, id string, state models.CounterpartyAddressOnchainState) error

	// MarkExpiredAddresses flips every approved address whose expires_at
	// has passed to AddressStatusExpired, and returns how many it touched.
	// One bulk statement rather than a per-row round trip, since this runs
	// on every rescreening-sweep tick. Per the source design doc §10:
	// "expired must actually be reachable — a status that can only ever be
	// set by a sweep that nobody scheduled is a status that never fires."
	MarkExpiredAddresses(ctx context.Context) (int64, error)
}

CounterpartyRepository spans all three compliance tables (counterparties, counterparty_addresses, address_screenings) — one bounded context, mirroring how UserRepository spans everything user-related in one file. Actor parameters throughout are the admin's Stellar public key as text (see the source design doc §14 and counterparty.go's KYBApprovedBy doc comment).

func NewCounterpartyRepository added in v1.5.0

func NewCounterpartyRepository(db *gorm.DB) (CounterpartyRepository, error)

NewCounterpartyRepository builds the repository.

type MpesaBalanceRepository added in v1.4.1

type MpesaBalanceRepository interface {
	// RecordQuery notes that a balance query for shortcode was sent, keyed on
	// the ack's OriginatorConversationID.
	RecordQuery(ctx context.Context, originatorConversationID string, shortcode uint) error
	// ResolveQuery returns the shortcode a landing result's
	// OriginatorConversationID was queried for, or ErrMpesaBalanceQueryNotFound.
	ResolveQuery(ctx context.Context, originatorConversationID string) (uint, error)
	// RecordBalance persists one parsed account balance snapshot.
	RecordBalance(ctx context.Context, shortcode uint, accountName, currency string, availableKES int64, observedAt time.Time) error
}

MpesaBalanceRepository correlates Account Balance requests with their asynchronous results and persists the parsed figures.

func NewMpesaBalanceRepository added in v1.4.1

func NewMpesaBalanceRepository(db *gorm.DB) (MpesaBalanceRepository, error)

NewMpesaBalanceRepository builds the repository.

type MpesaNumberValidationRepository added in v1.4.1

type MpesaNumberValidationRepository interface {
	// Get returns the cached verdict for a hash, or ErrMpesaValidationNotFound.
	Get(ctx context.Context, identityHash string) (*models.MpesaNumberValidation, error)
	// Upsert records a fresh verdict, replacing whatever was cached for the
	// same identity hash.
	Upsert(ctx context.Context, v *models.MpesaNumberValidation) error
}

MpesaNumberValidationRepository caches Mobile Number Validation verdicts so a payer's identity is checked at the paid rate at most once per cache entry rather than once per payment.

func NewMpesaNumberValidationRepository added in v1.4.1

func NewMpesaNumberValidationRepository(db *gorm.DB) (MpesaNumberValidationRepository, error)

NewMpesaNumberValidationRepository builds the repository.

type MpesaPullCursorRepository added in v1.4.1

type MpesaPullCursorRepository interface {
	// Get returns the cursor's current position — the exclusive start of the
	// next window to sweep.
	Get(ctx context.Context) (time.Time, error)
	// Advance moves the cursor forward to at.
	Advance(ctx context.Context, at time.Time) error
}

MpesaPullCursorRepository tracks how far the Pull reconciliation sweep has walked. One row (id=1); Get and Advance both operate on it.

func NewMpesaPullCursorRepository added in v1.4.1

func NewMpesaPullCursorRepository(db *gorm.DB) (MpesaPullCursorRepository, error)

NewMpesaPullCursorRepository builds the repository.

type MpesaTransactionRepository added in v1.4.1

type MpesaTransactionRepository interface {
	// Record inserts a new observation. A duplicate TransID returns
	// ErrMpesaConflict, which is the idempotency check the controller relies
	// on.
	Record(ctx context.Context, tx *models.MpesaTransaction) error

	// GetByTransID fetches one observation by receipt, e.g. to confirm a
	// callback against.
	GetByTransID(ctx context.Context, transID string) (*models.MpesaTransaction, error)

	// GetByCheckoutID fetches one observation by its STK checkout request ID,
	// e.g. to tie a query-confirmed payment back to its callback row.
	GetByCheckoutID(ctx context.Context, checkoutID string) (*models.MpesaTransaction, error)

	// DuePoll returns unconfirmed observations whose NextPollAt has come due,
	// newest last so a stuck poll does not starve newer ones.
	DuePoll(ctx context.Context, limit int) ([]*models.MpesaTransaction, error)

	// Confirm marks an observation verified and stamps how, which is what
	// moves it out of the poller's due set.
	Confirm(ctx context.Context, transID string, via models.MpesaTransactionConfirmVia, loanID string) error

	// UpdatePoll writes the next poll moment — the cadence lives in a column,
	// not in in-memory timers, per the mgpoller reasoning.
	UpdatePoll(ctx context.Context, transID string, at time.Time) error

	// StopPoll clears the poll moment, taking the observation out of the due
	// set for good: the terminal-failure path, where re-asking Daraja can never
	// change the answer.
	StopPoll(ctx context.Context, transID string) error

	// GetLoanIDByReference resolves a loan reference to a loan ID on the shared
	// platform DB. Raw SQL rather than the credit module's repository because
	// importing the lending module would invert the layering — the same
	// reasoning mgpoller documents for its loan status constants. Returns ""
	// when the reference resolves to nothing.
	GetLoanIDByReference(ctx context.Context, reference string) (string, error)

	// SetReversalState writes the reversal lifecycle state.
	SetReversalState(ctx context.Context, transID string, state models.MpesaTransactionReversal) error

	// UpdateFields sets the mutable fields a Pull reconciler fills in —
	// unmasked MSISDN, loan attribution, confirmed status.
	UpdateFields(ctx context.Context, tx *models.MpesaTransaction) error

	// UpsertFromPull records tx if Pull is the first thing to see it — the
	// callback was lost — or updates the mutable fields on the existing row
	// when it was already recorded from a callback. This is the entry point
	// the Pull reconciler uses instead of choosing between Record and
	// UpdateFields itself.
	UpsertFromPull(ctx context.Context, tx *models.MpesaTransaction) error

	// ListUnappliedConfirmed returns confirmed, loan-attributed observations
	// not yet converted toward a loan's repayment progress — the paybill
	// sweep's queue. Order is oldest first so a backlog drains in payment
	// order rather than newest-first.
	ListUnappliedConfirmed(ctx context.Context, limit int) ([]*models.MpesaTransaction, error)

	// SetAppliedStroops records the converted figure for one observation.
	// Idempotent to call twice with the same value; the sweep never calls it
	// a second time for a row that already has one, since ListUnappliedConfirmed
	// would no longer return it.
	SetAppliedStroops(ctx context.Context, id string, stroops int64) error

	// SumAppliedStroopsByLoan totals every observation already converted for
	// a loan — the authoritative "how much has this loan received toward its
	// payoff so far" figure. Safe to call repeatedly; it is a pure aggregate,
	// never a running total that could drift from the rows it is summing.
	SumAppliedStroopsByLoan(ctx context.Context, loanID string) (int64, error)
}

MpesaTransactionRepository persists inbound M-Pesa observations.

It exists to make confirm-before-credit enforceable, so Create is careful to give only the "recorded" state and Confirm marks the verified one.

func NewMpesaTransactionRepository added in v1.4.1

func NewMpesaTransactionRepository(db *gorm.DB) (MpesaTransactionRepository, error)

NewMpesaTransactionRepository creates a new MpesaTransactionRepository.

type Repositories

type Repositories struct {
	// Core repositories
	User                UserRepository
	Account             AccountRepository
	Transaction         TransactionRepository
	Mpesa               MpesaTransactionRepository
	MpesaValidation     MpesaNumberValidationRepository
	MpesaPullCursor     MpesaPullCursorRepository
	MpesaBalance        MpesaBalanceRepository
	Airtel              AirtelTransactionRepository
	AirtelSummaryCursor AirtelSummaryCursorRepository
	VaultWatchCursor    VaultWatchCursorRepository
	Counterparty        CounterpartyRepository
}

func NewRepositories

func NewRepositories(db *gorm.DB) (*Repositories, error)

type TransactionRepository

type TransactionRepository interface {
	// Create operations
	Create(ctx context.Context, tx *models.Transaction) error
	BatchCreate(ctx context.Context, txs []*models.Transaction) error

	// Read operations
	GetByID(ctx context.Context, id string) (*models.Transaction, error)
	GetByStellarHash(ctx context.Context, txHash string) (*models.Transaction, error)
	ListByExternalID(ctx context.Context, externalID string) ([]*models.Transaction, error)
	GetByLoanIDAndType(ctx context.Context, loanID, txType string) (*models.Transaction, error)
	GetByLoanID(ctx context.Context, loanID string, limit, offset int) ([]*models.Transaction, error)
	GetByUserID(ctx context.Context, userID string, limit, offset int) ([]*models.Transaction, error)
	GetByStatus(ctx context.Context, status string, limit, offset int) ([]*models.Transaction, error)

	// List returns transactions newest first, filtered by status and/or type
	// when either is given. Empty filters return everything.
	List(ctx context.Context, status, txType string, limit, offset int) ([]*models.Transaction, error)

	// Count returns the number of transactions matching the same filters List applies.
	Count(ctx context.Context, status, txType string) (int64, error)

	// Update operations
	Update(ctx context.Context, tx *models.Transaction) error

	// UpdateFields writes only the supplied columns, validated against the same
	// allow-list Update uses. Preferred for partial changes to avoid rewriting
	// the text/jsonb columns and all indexes on every touch.
	UpdateFields(ctx context.Context, id string, fields map[string]any) error
}

TransactionRepository defines the interface for transaction data access

func NewTransactionRepository

func NewTransactionRepository(db *gorm.DB) (TransactionRepository, error)

NewTransactionRepository creates a new instance of TransactionRepository

type UserRepository

type UserRepository interface {
	// Create operations
	Create(ctx context.Context, user *models.User) error
	CreateWithTx(ctx context.Context, tx *gorm.DB, user *models.User) error

	// Read operations
	GetByID(ctx context.Context, id string) (*models.User, error)
	GetByMobileNumber(ctx context.Context, mobileNumber string) (*models.User, error)
	GetByNationalID(ctx context.Context, nationalID string) (*models.User, error)
	GetByKYCStatus(ctx context.Context, kycStatus string, limit, offset int) ([]*models.User, error)
	GetByRole(ctx context.Context, role string, limit, offset int) ([]*models.User, error)
	List(ctx context.Context, limit, offset int) ([]*models.User, error)

	// ListFiltered returns users newest first, filtered by status and/or KYC
	// status when either is given. Empty filters return every non-deleted user.
	ListFiltered(ctx context.Context, status, kycStatus string, limit, offset int) ([]*models.User, error)

	// Count operations
	Count(ctx context.Context) (int64, error)

	// CountFiltered returns the number of users matching ListFiltered's filters.
	CountFiltered(ctx context.Context, status, kycStatus string) (int64, error)
	CountByKYCStatus(ctx context.Context, kycStatus string) (int64, error)
	CountByRole(ctx context.Context, role string) (int64, error)
	CountAdmins(ctx context.Context) (int, error)

	// Update operations
	Update(ctx context.Context, user *models.User) error

	// UpdateMobileNumber rebinds a user to a new MSISDN. Deliberately separate
	// from Update (which does not touch mobile_number) because this changes the
	// account's identity anchor — it is only used by new-SIM account recovery,
	// after the caller has verified ownership. The unique index on
	// mobile_number is the final guard against binding a number twice.
	UpdateMobileNumber(ctx context.Context, userID, mobileNumber string) error

	Restore(ctx context.Context, id string) error

	// Delete operations
	Delete(ctx context.Context, id string) error
}

UserRepository defines the interface for user data access

func NewUserRepository

func NewUserRepository(db *gorm.DB) (UserRepository, error)

NewUserRepository creates a new instance of UserRepository

type VaultWatchCursorRepository added in v1.5.0

type VaultWatchCursorRepository interface {
	// Get returns the last ledger sequence the watcher has scanned through.
	Get(ctx context.Context) (uint32, error)
	// Advance moves the cursor forward to ledger.
	Advance(ctx context.Context, ledger uint32) error
}

VaultWatchCursorRepository tracks how far the compliance watcher has scanned the vault's on-chain events. One row (id=1); Get and Advance both operate on it.

func NewVaultWatchCursorRepository added in v1.5.0

func NewVaultWatchCursorRepository(db *gorm.DB) (VaultWatchCursorRepository, error)

NewVaultWatchCursorRepository builds the repository.

Jump to

Keyboard shortcuts

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