Help us shape the future of Go! Take the 2026 Go Developer Survey

usage

package
v1.9.1 Latest Latest
Warning

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

Go to latest
Published: Sep 5, 2026 License: Apache-2.0 Imports: 9 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func CurrentDate

func CurrentDate() string

CurrentDate returns today's date as a string in YYYY-MM-DD format.

func RateLimited

func RateLimited(c AccountClass) bool

RateLimited reports whether an account class is subject to the two class-exempt request limiters: the per-IP agent-registration limiter and the per-user poll limiter. (The per-agent SEND limiter is intentionally NOT class-exempt — it is keyed on the agent, so the prober/conformance's fresh-agent-per-run pattern never trips it; there is no shared-bucket exhaustion to relieve.) Only trusted first-party traffic bypasses them: ClassSystem (synthetic monitoring probes) and ClassInternal (internal dogfooding + the conformance suite). The limiters exist to bound real-user and anonymous abuse, and those two classes are neither. This is deliberately NARROWER than PolicyFor's metering exemption: ClassDemo stays limited (it is user-facing, so a demo account is still an abuse surface), and any unknown/empty value fail-closes to limited — a misclassified account is never silently exempted from the limiters. The exemption follows the account (loaded at auth), so it holds regardless of source IP or whether the request arrives behind a proxy.

Types

type AccountClass

type AccountClass string

AccountClass classifies an account for metering/billing/analytics. It is the single source of truth read by the metering gate (see RecordAndCheck), kept orthogonal to the paid plan/tier. Mirrors the CHECK constraint in migrations/037_account_class.sql.

const (
	// ClassStandard is a real customer: metered, billed, counted in analytics.
	// The default for every account.
	ClassStandard AccountClass = "standard"
	// ClassInternal is internal dogfooding.
	ClassInternal AccountClass = "internal"
	// ClassSystem is synthetic-monitoring probe traffic.
	ClassSystem AccountClass = "system"
	// ClassDemo is a demo account.
	ClassDemo AccountClass = "demo"
)

type LiveUsageTracker

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

LiveUsageTracker is the real implementation backed by the billing store.

func NewUsageTracker

func NewUsageTracker(store *Store) *LiveUsageTracker

func (*LiveUsageTracker) RecordAndCheck

func (t *LiveUsageTracker) RecordAndCheck(ctx context.Context, userID, agentID, domain, direction string, units int) (bool, error)

func (*LiveUsageTracker) RecordAndCheckTx added in v1.2.1

func (t *LiveUsageTracker) RecordAndCheckTx(ctx context.Context, tx pgx.Tx, userID, agentID, domain, direction string, units int) (bool, error)

type MeteringPolicy

type MeteringPolicy struct {
	Meter     bool // record usage_events + usage_summaries (and thus count against quota)
	Bill      bool // include in billing
	Analytics bool // include in product analytics
}

MeteringPolicy is the resolved decision for an account class. Resolved once at the metering gate rather than re-derived at each write path.

func PolicyFor

func PolicyFor(c AccountClass) MeteringPolicy

PolicyFor returns the metering policy for an account class. Only standard accounts are metered/billed/analyzed; every non-standard class (internal, system, demo) is excluded on all three axes. An unknown/empty value fail-closed defaults to standard so a misclassified account is never silently exempted from billing.

type NoopUsageTracker

type NoopUsageTracker struct{}

NoopUsageTracker always allows everything. Used when billing is disabled.

func NewNoopUsageTracker

func NewNoopUsageTracker() *NoopUsageTracker

func (*NoopUsageTracker) RecordAndCheck

func (t *NoopUsageTracker) RecordAndCheck(ctx context.Context, userID, agentID, domain, direction string, units int) (bool, error)

func (*NoopUsageTracker) RecordAndCheckTx added in v1.2.1

func (t *NoopUsageTracker) RecordAndCheckTx(context.Context, pgx.Tx, string, string, string, string, int) (bool, error)

type Store

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

func NewStore

func NewStore(pool *pgxpool.Pool) *Store

func (*Store) CountAgentsByUser

func (s *Store) CountAgentsByUser(ctx context.Context, userID string) (int, error)

CountAgentsByUser returns the number of ACTIVE agents owned by the user — active meaning the agent's domain row still exists. The INNER JOIN on domains mirrors identity.Store.ListAgentsByUser EXACTLY (same `agent_identities a JOIN domains d ON a.registered_domain = d.domain WHERE a.user_id` predicate), so this count is guaranteed to equal the length of the /v1/agents list. Without the join an orphaned agent (one whose domain row is gone) would inflate the count above the list length, silently consuming a plan slot and letting usage.agents exceed max_agents while the agent is invisible to the user. Both consumers — the account usage view (usage.Agents) and the max_agents cap in limits.DBEnforcer.CheckAgentCreate — therefore count only agents the user can actually see and manage, so an orphaned agent neither shows up as usage nor blocks creating a new one.

func (*Store) CountDomainsByUser

func (s *Store) CountDomainsByUser(ctx context.Context, userID string) (int, error)

CountDomainsByUser returns the number of domains owned by the user. Used by the limits enforcer to check max_domains caps. Counts every row in domains regardless of verification status; an unverified domain still consumes a slot until the user deletes it.

func (*Store) GetAccountClass

func (s *Store) GetAccountClass(ctx context.Context, userID string) (AccountClass, error)

GetAccountClass returns the account class for a user. A missing user (no row) resolves to ClassStandard so the metering gate fails toward metering — a real customer must never be silently exempted from billing because of a transient lookup miss. The PK lookup on users is cheap; account class is read once per metered message.

func (*Store) GetAccountClassTx added in v1.2.1

func (s *Store) GetAccountClassTx(ctx context.Context, tx pgx.Tx, userID string) (AccountClass, error)

func (*Store) GetStorageBytes

func (s *Store) GetStorageBytes(ctx context.Context, userID string) (int64, error)

GetStorageBytes returns the user's current materialized storage bytes from account_usage. Returns 0 with no error if the user has no row yet — the trigger in migration 016 lazily creates the row on first message insert, so a pre-message user legitimately has 0 storage.

func (*Store) GetUsageSummary

func (s *Store) GetUsageSummary(ctx context.Context, userID, bucketDate string) (*UsageSummary, error)

func (*Store) IncrementUsageSummary

func (s *Store) IncrementUsageSummary(ctx context.Context, userID, bucketDate, direction string, units int) error

func (*Store) IncrementUsageSummaryTx added in v1.2.1

func (s *Store) IncrementUsageSummaryTx(ctx context.Context, tx pgx.Tx, userID, bucketDate, direction string, units int) error

func (*Store) MessagesThisMonth

func (s *Store) MessagesThisMonth(ctx context.Context, userID string) (int, error)

MessagesThisMonth returns the user's OUTBOUND recipient-delivery count for the current UTC calendar month, summed from usage_summaries. Inbound mail is recorded (inbound_count feeds analytics and dashboards) but is free and unmetered — it does not consume the monthly allowance. Returns 0 with no error if the user has no rows yet. The reference is time.Now().UTC() so server clocks crossing midnight UTC roll the counter consistently with the daily bucket_date written by IncrementUsageSummary.

func (*Store) MessagesToday added in v1.8.0

func (s *Store) MessagesToday(ctx context.Context, userID string) (int, error)

MessagesToday returns the user's OUTBOUND recipient-delivery count for the current UTC day, for the optional per-day send cap. Returns 0 with no error if the user has no row for today. Shares CurrentDate()'s UTC bucketing with IncrementUsageSummary so the day rolls consistently.

func (*Store) RecordUsageEvent

func (s *Store) RecordUsageEvent(ctx context.Context, event *UsageEvent) error

func (*Store) RecordUsageEventTx added in v1.2.1

func (s *Store) RecordUsageEventTx(ctx context.Context, tx pgx.Tx, event *UsageEvent) error

type TransactionalUsageTracker added in v1.2.1

type TransactionalUsageTracker interface {
	// RecordAndCheckTx records one usage event of `units` recipient-deliveries
	// inside the caller's transaction. Units < 1 are normalized to 1.
	RecordAndCheckTx(ctx context.Context, tx pgx.Tx, userID, agentID, domain, direction string, units int) (bool, error)
}

type UsageEvent

type UsageEvent struct {
	ID        string `json:"id"`
	UserID    string `json:"user_id"`
	AgentID   string `json:"agent_id"`
	Domain    string `json:"domain"`
	Direction string `json:"direction"`
	EventType string `json:"event_type"`
	// Units is how many recipient-deliveries this event represents: one row
	// per metered message, `units` recipients. Inbound is one unit per row
	// (the SMTP session already fans out per resolved recipient); outbound is
	// the deduplicated to ∪ cc ∪ bcc count. Values < 1 are normalized to 1 on
	// write so a caller bug can never record a free or negative message.
	Units     int       `json:"units"`
	CreatedAt time.Time `json:"created_at"`
}

type UsageSummary

type UsageSummary struct {
	UserID        string `json:"user_id"`
	BucketDate    string `json:"bucket_date"`
	InboundCount  int    `json:"inbound_count"`
	OutboundCount int    `json:"outbound_count"`
	TotalCount    int    `json:"total_count"`
}

type UsageTracker

type UsageTracker interface {
	// RecordAndCheck records a usage event of `units` recipient-deliveries.
	// Always returns true. Units < 1 are normalized to 1.
	RecordAndCheck(ctx context.Context, userID, agentID, domain, direction string, units int) (allowed bool, err error)
}

UsageTracker records usage events. Always allows the action (no quota enforcement).

Jump to

Keyboard shortcuts

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