domain

package
v1.0.0 Latest Latest
Warning

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

Go to latest
Published: Mar 8, 2026 License: MIT Imports: 3 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

View Source
var (
	// ErrNotFound is returned when a requested resource does not exist or has been soft-deleted.
	ErrNotFound = errors.New("not found")

	// ErrForbidden is returned when the caller lacks permission for a resource.
	ErrForbidden = errors.New("forbidden")

	// ErrConflict is returned when an operation would violate a uniqueness constraint.
	ErrConflict = errors.New("conflict")

	// ErrInvalidInput is returned when request validation fails.
	ErrInvalidInput = errors.New("invalid input")

	// ErrInvalidOTP is returned when an OTP code is wrong, expired, or already used.
	ErrInvalidOTP = errors.New("invalid or expired OTP")

	// ErrOTPRateLimited is returned when too many OTP requests are made.
	ErrOTPRateLimited = errors.New("OTP rate limit exceeded")

	// ErrUnauthorized is returned when no valid token is present.
	ErrUnauthorized = errors.New("unauthorized")

	// ErrTokenExpired is returned when the PASETO token has expired.
	ErrTokenExpired = errors.New("token expired")
)

Sentinel errors — all in internal/domain/errors.go

Functions

This section is empty.

Types

type Account

type Account struct {
	CreatedAt    time.Time   `json:"created_at"`
	UpdatedAt    time.Time   `json:"updated_at"`
	DeletedAt    *time.Time  `json:"deleted_at,omitempty"`
	ID           string      `json:"id"`
	TenantID     string      `json:"tenant_id"`
	UserID       string      `json:"user_id"`
	Name         string      `json:"name"`
	Type         AccountType `json:"type"`
	Currency     string      `json:"currency"`      // ISO 4217 code (e.g. "USD")
	BalanceCents int64       `json:"balance_cents"` // Always in cents; never float
}

Account represents a financial account owned by a user within a household (tenant). All balances are stored in cents (int64) to ensure precision.

type AccountRepository

type AccountRepository interface {
	// Create persists a new account for the specified tenant.
	Create(ctx context.Context, tenantID string, input CreateAccountInput) (*Account, error)

	// GetByID retrieves a specific account by its ID and tenant ID.
	GetByID(ctx context.Context, tenantID, id string) (*Account, error)

	// ListByTenant returns all active accounts for the given tenant.
	ListByTenant(ctx context.Context, tenantID string) ([]Account, error)

	// ListByUser returns all accounts associated with a specific user within a tenant.
	ListByUser(ctx context.Context, tenantID, userID string) ([]Account, error)

	// Update modifies an existing account's metadata.
	Update(ctx context.Context, tenantID, id string, input UpdateAccountInput) (*Account, error)

	// UpdateBalance updates the balance of an account. This should be used strictly
	// through service logic to ensure consistency with transactions.
	UpdateBalance(ctx context.Context, tenantID, id string, newBalanceCents int64) error

	// Delete performs a soft delete on the specified account.
	Delete(ctx context.Context, tenantID, id string) error
}

AccountRepository defines the persistence operations for financial accounts. It ensures that all data is isolated by tenant.

type AccountService

type AccountService interface {
	// Create persists a new account after validating the user belongs to the tenant.
	Create(ctx context.Context, tenantID string, input CreateAccountInput) (*Account, error)

	// GetByID retrieves an account by ID with a tenant ownership guard.
	GetByID(ctx context.Context, tenantID, id string) (*Account, error)

	// ListByTenant returns all active accounts for the given household.
	ListByTenant(ctx context.Context, tenantID string) ([]Account, error)

	// ListByUser returns all accounts associated with a specific user within a tenant.
	ListByUser(ctx context.Context, tenantID, userID string) ([]Account, error)

	// Update modifies an existing account's metadata and records an audit log.
	Update(ctx context.Context, tenantID, id string, input UpdateAccountInput) (*Account, error)

	// Delete performs a soft-delete on the account and records an audit log.
	Delete(ctx context.Context, tenantID, id string) error
}

AccountService defines the business-logic contract for account management.

type AccountType

type AccountType string

AccountType mirrors the database enum for financial account types.

const (
	// AccountTypeChecking representing a standard checking account.
	AccountTypeChecking AccountType = "checking"
	// AccountTypeSavings representing a savings or high-yield account.
	AccountTypeSavings AccountType = "savings"
	// AccountTypeCreditCard representing a credit card account.
	AccountTypeCreditCard AccountType = "credit_card"
	// AccountTypeInvestment representing an investment or brokerage account.
	AccountTypeInvestment AccountType = "investment"
)

type AdminAuditRepository

type AdminAuditRepository interface {
	// ListAll returns audit logs across all tenants with optional filters.
	ListAll(ctx context.Context, params ListAuditLogsParams) ([]AuditLog, error)
}

AdminAuditRepository defines global audit log queries without tenant isolation. This is used for system-wide compliance and debugging.

type AdminService

type AdminService interface {
	// ListAllTenants returns all tenants in the system, with an option to include soft-deleted ones.
	ListAllTenants(ctx context.Context, withDeleted bool) ([]Tenant, error)

	// GetTenantByID retrieves a tenant by its ID without tenant isolation.
	GetTenantByID(ctx context.Context, id string) (*Tenant, error)

	// UpdateTenantPlan changes the subscription plan for a tenant.
	UpdateTenantPlan(ctx context.Context, id string, plan TenantPlan) (*Tenant, error)

	// SuspendTenant performs a soft-delete on the tenant, blocking all user access.
	SuspendTenant(ctx context.Context, id string) error

	// RestoreTenant reverses a soft-delete on the tenant, restoring user access.
	RestoreTenant(ctx context.Context, id string) error

	// HardDeleteTenant permanently deletes a tenant and all associated data after confirmation.
	HardDeleteTenant(ctx context.Context, id, confirmationToken string) error

	// ListAllUsers returns all users across the system without tenant isolation.
	ListAllUsers(ctx context.Context) ([]User, error)

	// GetUserByID retrieves a user by ID without tenant isolation.
	GetUserByID(ctx context.Context, id string) (*User, error)

	// ForceDeleteUser permanently deletes a user record without tenant isolation.
	ForceDeleteUser(ctx context.Context, id string) error

	// ListAuditLogs returns audit logs across all tenants with optional filters, without tenant isolation.
	ListAuditLogs(ctx context.Context, params ListAuditLogsParams) ([]AuditLog, error)
}

AdminService defines the business-logic contract for cross-tenant sysadmin operations.

type AdminTenantRepository

type AdminTenantRepository interface {
	// ListAll returns every tenant in the system, including soft-deleted ones when withDeleted is true.
	ListAll(ctx context.Context, withDeleted bool) ([]Tenant, error)

	// GetByID retrieves a tenant without a tenant_id guard (sysadmin bypass).
	GetByID(ctx context.Context, id string) (*Tenant, error)

	// UpdatePlan changes a tenant's subscription plan.
	UpdatePlan(ctx context.Context, id string, plan TenantPlan) (*Tenant, error)

	// Suspend soft-deletes a tenant, blocking all logins for its users.
	Suspend(ctx context.Context, id string) error

	// Restore reverses a soft-delete on a tenant.
	Restore(ctx context.Context, id string) error

	// HardDelete permanently removes a tenant and all associated data.
	// WARNING: This is irreversible and will delete all household data via cascade.
	// Must only be called after explicit confirmation.
	HardDelete(ctx context.Context, id string) error
}

AdminTenantRepository defines cross-tenant operations for sysadmin use only. These methods bypass the standard tenant_id isolation for system-wide management.

type AdminUserRepository

type AdminUserRepository interface {
	// ListAll returns every user in the system regardless of tenant.
	ListAll(ctx context.Context) ([]User, error)

	// GetByID retrieves a user without a tenant_id guard.
	GetByID(ctx context.Context, id string) (*User, error)

	// ForceDelete hard-deletes a user record.
	// WARNING: This is irreversible and should be used with extreme caution.
	ForceDelete(ctx context.Context, id string) error
}

AdminUserRepository defines cross-tenant user operations for sysadmin use only. These methods allow management of users across the entire system.

type AuditAction

type AuditAction string

AuditAction mirrors the database enum for audit actions.

const (
	// AuditActionCreate representing many create operations.
	AuditActionCreate AuditAction = "create"
	// AuditActionUpdate representing many update operations.
	AuditActionUpdate AuditAction = "update"
	// AuditActionSoftDelete representing soft delete operations.
	AuditActionSoftDelete AuditAction = "soft_delete"
	// AuditActionRestore representing restore operations.
	AuditActionRestore AuditAction = "restore"
	// AuditActionLogin representing login operations.
	AuditActionLogin AuditAction = "login"
	// AuditActionLoginFailed representing login failure operations.
	AuditActionLoginFailed AuditAction = "login_failed"
	// AuditActionOTPRequested representing OTP request operations.
	AuditActionOTPRequested AuditAction = "otp_requested"
	// AuditActionOTPVerified representing OTP verification operations.
	AuditActionOTPVerified AuditAction = "otp_verified"
)

type AuditLog

type AuditLog struct {
	CreatedAt  time.Time   `json:"created_at"`
	ID         string      `json:"id"`
	TenantID   string      `json:"tenant_id"`
	ActorID    string      `json:"actor_id"` // User ULID or "SYSTEM" for automated actions
	EntityType string      `json:"entity_type"`
	EntityID   string      `json:"entity_id"`
	IPAddress  string      `json:"ip_address"`
	UserAgent  string      `json:"user_agent"`
	ActorRole  Role        `json:"actor_role"`
	Action     AuditAction `json:"action"`
	OldValues  []byte      `json:"old_values,omitempty"` // JSON snapshot before change
	NewValues  []byte      `json:"new_values,omitempty"` // JSON snapshot after change
}

AuditLog is an immutable record of a significant system event. It provides a tamper-evident trace of actor actions, IPs, and state changes.

type AuditRepository

type AuditRepository interface {
	// Create appends a new audit log entry.
	Create(ctx context.Context, input CreateAuditLogInput) (*AuditLog, error)

	// ListByTenant returns audit logs for a specific tenant with optional filters.
	ListByTenant(ctx context.Context, tenantID string, params ListAuditLogsParams) ([]AuditLog, error)

	// ListByEntity returns audit logs for a specific entity (e.g. a single transaction).
	ListByEntity(ctx context.Context, tenantID, entityType, entityID string) ([]AuditLog, error)
}

AuditRepository defines persistence operations for audit logs. Audit logs are append-only — no update or delete methods are provided.

type AuthRepository

type AuthRepository interface {
	// CreateOTPRequest persists a new OTP challenge in the database.
	CreateOTPRequest(ctx context.Context, input CreateOTPRequestInput) (*OTPRequest, error)

	// GetActiveOTPRequest retrieves the most recent unused, non-expired OTP for the given email.
	// Returns domain.ErrInvalidOTP if no valid request is found.
	GetActiveOTPRequest(ctx context.Context, email string) (*OTPRequest, error)

	// MarkOTPUsed marks the given OTP request as consumed to prevent reuse.
	MarkOTPUsed(ctx context.Context, id string) error

	// DeleteExpiredOTPRequests removes all expired OTP rows from the database.
	// This is typically called by a periodic cleanup job.
	DeleteExpiredOTPRequests(ctx context.Context) error
}

AuthRepository defines persistence operations for OTP challenges and authentication state. It decouples the auth service from specific database implementations.

type AuthService

type AuthService interface {
	// RequestOTP validates the email, generates an OTP, persists it, and mails
	// the code to the user. Returns ErrNotFound if the user does not exist.
	RequestOTP(ctx context.Context, email string) error

	// VerifyOTP validates the OTP code for the given email. On success it marks
	// the OTP as used, updates the user's last-login timestamp, records an audit
	// log, and returns a fresh PASETO token pair.
	VerifyOTP(ctx context.Context, email, code string) (*TokenPair, error)

	// RefreshToken validates an existing refresh token and returns a new token
	// pair with a refreshed expiry.
	RefreshToken(ctx context.Context, refreshToken string) (*TokenPair, error)
}

AuthService defines the business-logic contract for the OTP auth flow.

type CachedResponse

type CachedResponse struct {
	Body       []byte `json:"body"`
	StatusCode int    `json:"status_code"`
}

CachedResponse represents a stored HTTP response.

type Category

type Category struct {
	CreatedAt time.Time    `json:"created_at"`
	UpdatedAt time.Time    `json:"updated_at"`
	DeletedAt *time.Time   `json:"deleted_at,omitempty"`
	ID        string       `json:"id"`
	TenantID  string       `json:"tenant_id"`
	ParentID  string       `json:"parent_id"` // Empty string means root category
	Name      string       `json:"name"`
	Icon      string       `json:"icon"`  // Optional emoji or icon identifier
	Color     string       `json:"color"` // Optional hex color, e.g. "#FF5733"
	Type      CategoryType `json:"type"`
}

Category is a tenant-scoped label for classifying transactions. It supports one level of parent-child hierarchy.

func (*Category) IsRoot

func (c *Category) IsRoot() bool

IsRoot returns true when the category has no parent.

type CategoryRepository

type CategoryRepository interface {
	// Create persists a new category for the specified tenant.
	Create(ctx context.Context, tenantID string, input CreateCategoryInput) (*Category, error)

	// GetByID retrieves a specific category by its ID and tenant ID.
	GetByID(ctx context.Context, tenantID, id string) (*Category, error)

	// ListByTenant returns all active categories for the given tenant.
	ListByTenant(ctx context.Context, tenantID string) ([]Category, error)

	// ListChildren returns all subcategories for a given parent within a tenant.
	ListChildren(ctx context.Context, tenantID, parentID string) ([]Category, error)

	// Update modifies an existing category's metadata.
	Update(ctx context.Context, tenantID, id string, input UpdateCategoryInput) (*Category, error)

	// Delete performs a soft delete on the specified category.
	Delete(ctx context.Context, tenantID, id string) error
}

CategoryRepository defines persistence operations for categories. It ensures that all data is isolated by tenant.

type CategoryService

type CategoryService interface {
	// Create persists a new category with hierarchy depth validation.
	Create(ctx context.Context, tenantID string, input CreateCategoryInput) (*Category, error)

	// GetByID retrieves a specific category by its ID and tenant ID.
	GetByID(ctx context.Context, tenantID, id string) (*Category, error)

	// ListByTenant returns all categories for the given tenant.
	ListByTenant(ctx context.Context, tenantID string) ([]Category, error)

	// ListChildren returns all subcategories for a given parent within a tenant.
	ListChildren(ctx context.Context, tenantID, parentID string) ([]Category, error)

	// Update modifies an existing category's metadata and writes to the audit trail.
	Update(ctx context.Context, tenantID, id string, input UpdateCategoryInput) (*Category, error)

	// Delete performs a soft-delete and writes to the audit trail.
	Delete(ctx context.Context, tenantID, id string) error
}

CategoryService defines the business-logic contract for category management.

type CategoryType

type CategoryType string

CategoryType mirrors the database enum for transaction categories.

const (
	// CategoryTypeIncome representing income transactions (e.g. salary).
	CategoryTypeIncome CategoryType = "income"
	// CategoryTypeExpense representing expense transactions (e.g. groceries).
	CategoryTypeExpense CategoryType = "expense"
	// CategoryTypeTransfer representing transfers between accounts.
	CategoryTypeTransfer CategoryType = "transfer"
)

type Claims

type Claims struct {
	IssuedAt  time.Time `json:"issued_at"`
	ExpiresAt time.Time `json:"expires_at"`
	UserID    string    `json:"user_id"`
	TenantID  string    `json:"tenant_id"`
	Role      Role      `json:"role"`
}

Claims holds the data encoded in a PASETO token. It contains identification for the user and their tenant, as well as their role and metadata.

type CreateAccountInput

type CreateAccountInput struct {
	UserID       string      `validate:"required"`
	Name         string      `validate:"required,min=1,max=100"`
	Type         AccountType `validate:"required,oneof=checking savings credit_card investment"`
	Currency     string      `validate:"required,len=3"`
	InitialCents int64       `validate:"required"`
}

CreateAccountInput is the value object used for creating a new account.

type CreateAuditLogInput

type CreateAuditLogInput struct {
	TenantID   string      `validate:"required"`
	ActorID    string      `validate:"required"`
	Action     AuditAction `validate:"required"`
	EntityType string      `validate:"required"`
	EntityID   string      `validate:"omitempty"`
	IPAddress  string      `validate:"omitempty"`
	UserAgent  string      `validate:"omitempty"`
	ActorRole  Role        `validate:"required"`
	OldValues  []byte      `json:"-"`
	NewValues  []byte      `json:"-"`
}

CreateAuditLogInput is the value object used for recording a new audit event.

type CreateCategoryInput

type CreateCategoryInput struct {
	ParentID string       `validate:"omitempty"`
	Name     string       `validate:"required,min=1,max=100"`
	Icon     string       `validate:"omitempty,max=10"`
	Color    string       `validate:"omitempty,hexcolor"`
	Type     CategoryType `validate:"required,oneof=income expense transfer"`
}

CreateCategoryInput is the value object used for creating a new category.

type CreateOTPRequestInput

type CreateOTPRequestInput struct {
	ExpiresAt time.Time `validate:"required"`
	Email     string    `validate:"required,email"`
	CodeHash  string    `validate:"required"`
}

CreateOTPRequestInput is the value object used to create a new OTP challenge.

type CreateTenantInput

type CreateTenantInput struct {
	Name string `validate:"required,min=2,max=100"`
}

CreateTenantInput holds the data required to create a new household.

type CreateTransactionInput

type CreateTransactionInput struct {
	OccurredAt       time.Time       `validate:"required"`
	AccountID        string          `validate:"required"`
	CategoryID       string          `validate:"required"`
	UserID           string          `validate:"required"`
	Description      string          `validate:"required,min=1,max=255"`
	MasterPurchaseID string          `validate:"omitempty"`
	Type             TransactionType `validate:"required,oneof=income expense transfer"`
	AmountCents      int64           `validate:"required,gt=0"`
}

CreateTransactionInput is the value object used for creating a new transaction.

type CreateUserInput

type CreateUserInput struct {
	TenantID string `json:"tenant_id" validate:"required"`
	Email    string `json:"email" validate:"required,email"`
	Name     string `json:"name" validate:"required,min=2,max=100"`
	Role     Role   `json:"role" validate:"required"`
}

CreateUserInput holds the data required to enroll a new user.

type IdempotencyStore

type IdempotencyStore interface {
	// Get retrieves a cached response for the given key, if it exists and is not expired.
	Get(ctx context.Context, key string) (*CachedResponse, error)

	// SetLocked attempts to acquire a lock for the given key. It returns true if the lock was acquired,
	SetLocked(ctx context.Context, key string, ttl time.Duration) (bool, error)

	// SetResponse stores the response for the given key with a TTL, and releases any lock.
	SetResponse(ctx context.Context, key string, resp CachedResponse, ttl time.Duration) error
}

IdempotencyStore defines the contract for storing idempotency keys and responses.

type ListAuditLogsParams

type ListAuditLogsParams struct {
	StartDate  *time.Time  `json:"start_date,omitempty"`
	EndDate    *time.Time  `json:"end_date,omitempty"`
	EntityType string      `json:"entity_type,omitempty"`
	EntityID   string      `json:"entity_id,omitempty"`
	ActorID    string      `json:"actor_id,omitempty"`
	Action     AuditAction `json:"action,omitempty"`
	Limit      int32       `json:"limit"`
	Offset     int32       `json:"offset"`
}

ListAuditLogsParams defines the filters and pagination for querying audit trails.

type ListTransactionsParams

type ListTransactionsParams struct {
	StartDate  *time.Time      `json:"start_date,omitempty"`
	EndDate    *time.Time      `json:"end_date,omitempty"`
	AccountID  string          `json:"account_id,omitempty"`
	CategoryID string          `json:"category_id,omitempty"`
	Type       TransactionType `json:"type,omitempty"`
	Limit      int32           `json:"limit"`
	Offset     int32           `json:"offset"`
}

ListTransactionsParams defines the filters and pagination for listing transactions.

type Mailer

type Mailer interface {
	// SendOTP sends a one-time password code to the given email address.
	SendOTP(ctx context.Context, to, code string) error
}

Mailer defines the contract for sending application emails. Implementations: SMTPMailer (production), NoopMailer (tests).

type OTPRequest

type OTPRequest struct {
	ExpiresAt time.Time `json:"expires_at"`
	CreatedAt time.Time `json:"created_at"`
	ID        string    `json:"id"`
	Email     string    `json:"email"`
	CodeHash  string    `json:"-"` // bcrypt hash of the 6-digit code, never serialized
	Used      bool      `json:"used"`
}

OTPRequest represents a pending or consumed OTP challenge. It tracks the email associated with the request, the bcrypt hash of the 6-digit code, whether it has been used, and its expiration time.

type Role

type Role string

Role represents a user's role within a household/tenant.

const (
	// RoleSysadmin has full access to the entire system (global).
	RoleSysadmin Role = "sysadmin"
	// RoleAdmin has full access to a specific tenant.
	RoleAdmin Role = "admin"
	// RoleMember has standard member access to a specific tenant.
	RoleMember Role = "member"
)

func (Role) CanAccess

func (r Role) CanAccess(target Role) bool

CanAccess returns true if the current role is at least as privileged as the target role.

func (Role) Level

func (r Role) Level() int

Level returns a numeric weight for the role to allow comparison.

type Tenant

type Tenant struct {
	CreatedAt time.Time
	UpdatedAt time.Time
	DeletedAt *time.Time
	ID        string
	Name      string
	Plan      TenantPlan
}

Tenant represents a household, the root of the multi-tenancy hierarchy.

type TenantPlan

type TenantPlan string

TenantPlan represents the subscription tier of a household.

const (
	// TenantPlanFree is the default restricted tier.
	TenantPlanFree TenantPlan = "free"
	// TenantPlanBasic is the standard paid tier.
	TenantPlanBasic TenantPlan = "basic"
	// TenantPlanPremium is the highest tier with all features.
	TenantPlanPremium TenantPlan = "premium"
)

type TenantRepository

type TenantRepository interface {
	// Create persists a new tenant.
	Create(ctx context.Context, input CreateTenantInput) (*Tenant, error)

	// GetByID retrieves a tenant by its unique identifier.
	GetByID(ctx context.Context, id string) (*Tenant, error)

	// List returns all active (non-deleted) tenants.
	List(ctx context.Context) ([]Tenant, error)

	// Update modifies an existing tenant's attributes.
	Update(ctx context.Context, id string, input UpdateTenantInput) (*Tenant, error)

	// Delete performs a soft-delete on the tenant.
	Delete(ctx context.Context, id string) error
}

TenantRepository defines the persistence contract for Tenant entities. These operations are typically restricted to the sysadmin role.

type TenantService

type TenantService interface {
	// Create persists a new tenant and records an audit log.
	Create(ctx context.Context, input CreateTenantInput) (*Tenant, error)

	// GetByID retrieves a tenant by its unique identifier.
	GetByID(ctx context.Context, id string) (*Tenant, error)

	// List returns all active (non-deleted) tenants. Restricted to sysadmins.
	List(ctx context.Context) ([]Tenant, error)

	// Update modifies an existing tenant's attributes and records an audit log.
	Update(ctx context.Context, id string, input UpdateTenantInput) (*Tenant, error)

	// Delete performs a soft-delete on the tenant and records an audit log.
	Delete(ctx context.Context, id string) error

	// InviteUser creates a new user within the context of a tenant.
	InviteUser(ctx context.Context, tenantID string, input CreateUserInput) (*User, error)
}

TenantService defines the business-logic contract for tenant management.

type TokenPair

type TokenPair struct {
	ExpiresAt    time.Time `json:"expires_at"`
	AccessToken  string    `json:"access_token"`  //nolint:gosec
	RefreshToken string    `json:"refresh_token"` //nolint:gosec
}

TokenPair holds both tokens returned after successful OTP verification.

type Transaction

type Transaction struct {
	OccurredAt       time.Time       `json:"occurred_at"`
	CreatedAt        time.Time       `json:"created_at"`
	UpdatedAt        time.Time       `json:"updated_at"`
	DeletedAt        *time.Time      `json:"deleted_at,omitempty"`
	ID               string          `json:"id"`
	TenantID         string          `json:"tenant_id"`
	AccountID        string          `json:"account_id"`
	CategoryID       string          `json:"category_id"`
	UserID           string          `json:"user_id"`
	MasterPurchaseID string          `json:"master_purchase_id,omitempty"` // Empty for regular transactions; set in Phase 2
	Description      string          `json:"description"`
	Type             TransactionType `json:"type"`
	AmountCents      int64           `json:"amount_cents"` // Always in cents; positive value; type determines direction
}

Transaction is a single financial event on an account. All amounts are stored in cents (int64) to ensure precision.

type TransactionRepository

type TransactionRepository interface {
	// Create persists a new transaction for the specified tenant.
	Create(ctx context.Context, tenantID string, input CreateTransactionInput) (*Transaction, error)

	// GetByID retrieves a specific transaction by its ID and tenant ID.
	GetByID(ctx context.Context, tenantID, id string) (*Transaction, error)

	// List returns a list of transactions matching the specified filters.
	List(ctx context.Context, tenantID string, params ListTransactionsParams) ([]Transaction, error)

	// Update modifies an existing transaction's metadata.
	Update(ctx context.Context, tenantID, id string, input UpdateTransactionInput) (*Transaction, error)

	// Delete performs a soft delete on the specified transaction.
	Delete(ctx context.Context, tenantID, id string) error
}

TransactionRepository defines persistence operations for transactions. It ensures that all data is isolated by tenant.

type TransactionService

type TransactionService interface {
	// Create persists a transaction and updates the account balance.
	Create(ctx context.Context, tenantID string, input CreateTransactionInput) (*Transaction, error)

	// GetByID retrieves a transaction by ID with tenant isolation.
	GetByID(ctx context.Context, tenantID, id string) (*Transaction, error)

	// List returns transactions matching filters for the household.
	List(ctx context.Context, tenantID string, params ListTransactionsParams) ([]Transaction, error)

	// Update modifies a transaction and adjusts balance if AmountCents changed.
	Update(ctx context.Context, tenantID, id string, input UpdateTransactionInput) (*Transaction, error)

	// Delete reverts the balance impact and soft-deletes the transaction.
	Delete(ctx context.Context, tenantID, id string) error
}

TransactionService defines the business-logic contract for transaction management.

type TransactionType

type TransactionType string

TransactionType mirrors the database enum for transaction types.

const (
	// TransactionTypeIncome representing income transactions (e.g. salary).
	TransactionTypeIncome TransactionType = "income"
	// TransactionTypeExpense representing expense transactions (e.g. groceries).
	TransactionTypeExpense TransactionType = "expense"
	// TransactionTypeTransfer representing transfers between accounts.
	TransactionTypeTransfer TransactionType = "transfer"
)

type UpdateAccountInput

type UpdateAccountInput struct {
	Name     *string `validate:"omitempty,min=1,max=100"`
	Currency *string `validate:"omitempty,len=3"`
}

UpdateAccountInput is the value object used for updating an existing account.

type UpdateCategoryInput

type UpdateCategoryInput struct {
	Name  *string `validate:"omitempty,min=1,max=100"`
	Icon  *string `validate:"omitempty,max=10"`
	Color *string `validate:"omitempty,hexcolor"`
}

UpdateCategoryInput is the value object used for updating an existing category.

type UpdateTenantInput

type UpdateTenantInput struct {
	Name *string     `validate:"omitempty,min=2,max=100"`
	Plan *TenantPlan `validate:"omitempty"`
}

UpdateTenantInput holds the data required to update an existing household.

type UpdateTransactionInput

type UpdateTransactionInput struct {
	OccurredAt  *time.Time `validate:"omitempty"`
	CategoryID  *string    `validate:"omitempty"`
	Description *string    `validate:"omitempty,min=1,max=255"`
	AmountCents *int64     `validate:"omitempty,gt=0"`
}

UpdateTransactionInput is the value object used for updating an existing transaction.

type UpdateUserInput

type UpdateUserInput struct {
	Name *string `json:"name,omitempty" validate:"omitempty,min=2,max=100"`
	Role *Role   `json:"role,omitempty" validate:"omitempty"`
}

UpdateUserInput holds the data that can be updated for a user.

type User

type User struct {
	CreatedAt   time.Time
	UpdatedAt   time.Time
	DeletedAt   *time.Time
	LastLoginAt *time.Time
	ID          string
	TenantID    string
	Email       string
	Name        string
	Role        Role
}

User represents a person belonging to a Tenant (household). All operational field accesses must be isolated by TenantID.

type UserRepository

type UserRepository interface {
	// Create persists a new user within a tenant.
	Create(ctx context.Context, input CreateUserInput) (*User, error)

	// GetByID retrieves a user by ID, scoped to a specific tenant.
	GetByID(ctx context.Context, tenantID, id string) (*User, error)

	// GetByEmail retrieves a user by their unique email across all tenants.
	// Used primarily during the authentication flow.
	GetByEmail(ctx context.Context, email string) (*User, error)

	// ListByTenant returns all active users belonging to a household.
	ListByTenant(ctx context.Context, tenantID string) ([]User, error)

	// Update modifies a user's attributes within their tenant.
	Update(ctx context.Context, tenantID, id string, input UpdateUserInput) (*User, error)

	// UpdateLastLogin updates the login timestamp for a user.
	UpdateLastLogin(ctx context.Context, id string) error

	// Delete performs a soft-delete on a user.
	Delete(ctx context.Context, tenantID, id string) error
}

UserRepository defines the persistence contract for User entities. Every method (except GetByEmail for login) MUST enforce TenantID isolation.

Jump to

Keyboard shortcuts

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