dbgen

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jul 31, 2026 License: MIT Imports: 5 Imported by: 0

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AnalyticsSalt

type AnalyticsSalt struct {
	ValidOn   time.Time
	Salt      []byte
	CreatedAt time.Time
	PurgeAt   time.Time
}

type ApiKey

type ApiKey struct {
	ID             uuid.UUID
	UserID         uuid.UUID
	OrganizationID uuid.UUID
	WorkspaceID    *uuid.UUID
	Name           string
	Prefix         string
	KeyHash        []byte
	Scopes         []string
	LastUsedAt     *time.Time
	ExpiresAt      *time.Time
	RevokedAt      *time.Time
	CreatedAt      time.Time
}

type ArchiveLinkParams

type ArchiveLinkParams struct {
	ID          uuid.UUID
	WorkspaceID uuid.UUID
}

type AttachTagParams

type AttachTagParams struct {
	LinkID      uuid.UUID
	TagID       uuid.UUID
	WorkspaceID uuid.UUID
}

type AuditLog

type AuditLog struct {
	ID             uuid.UUID
	OccurredAt     time.Time
	OrganizationID *uuid.UUID
	WorkspaceID    *uuid.UUID
	ActorUserID    *uuid.UUID
	ActorLabel     string
	ActorApiKeyID  *uuid.UUID
	Action         string
	TargetType     *string
	TargetID       *uuid.UUID
	Metadata       []byte
	IpPrefix       *string
}

type AutomationRule

type AutomationRule struct {
	ID            uuid.UUID
	WorkspaceID   uuid.UUID
	Name          string
	Trigger       string
	TriggerConfig []byte
	Actions       []byte
	Enabled       bool
	LastFiredAt   *time.Time
	CreatedAt     time.Time
	UpdatedAt     time.Time
}

type Campaign

type Campaign struct {
	ID          uuid.UUID
	WorkspaceID uuid.UUID
	Name        string
	Slug        string
	Description string
	Settings    []byte
	StartsAt    *time.Time
	EndsAt      *time.Time
	CreatedAt   time.Time
	UpdatedAt   time.Time
	DeletedAt   *time.Time
}

type ClickEvent

type ClickEvent struct {
	ID           uuid.UUID
	LinkID       uuid.UUID
	WorkspaceID  uuid.UUID
	OccurredAt   time.Time
	VisitorHash  []byte
	IsFirstVisit bool
	Country      *string
	Region       *string
	City         *string
	Device       *string
	Browser      *string
	Os           *string
	Language     *string
	ReferrerHost *string
	IsBot        bool
	LatencyUs    *int32
}

type CountLinksParams

type CountLinksParams struct {
	WorkspaceID uuid.UUID
	Status      *string
	Search      *string
	TagIds      []uuid.UUID
}

type CreateAPIKeyParams

type CreateAPIKeyParams struct {
	ID             uuid.UUID
	UserID         uuid.UUID
	OrganizationID uuid.UUID
	WorkspaceID    *uuid.UUID
	Name           string
	Prefix         string
	KeyHash        []byte
	Scopes         []string
	ExpiresAt      *time.Time
}

type CreateDestinationParams

type CreateDestinationParams struct {
	ID          uuid.UUID
	LinkID      uuid.UUID
	WorkspaceID uuid.UUID
	Url         string
	UrlHost     string
}

type CreateLinkParams

type CreateLinkParams struct {
	ID           uuid.UUID
	WorkspaceID  uuid.UUID
	DomainID     uuid.UUID
	Alias        string
	PrimaryUrl   string
	Title        string
	Description  string
	Status       string
	ExpiresAt    *time.Time
	CreatedBy    *uuid.UUID
	ForwardQuery bool
}

type CreateMembershipParams

type CreateMembershipParams struct {
	ID             uuid.UUID
	UserID         uuid.UUID
	OrganizationID uuid.UUID
	RoleID         uuid.UUID
	WorkspaceID    *uuid.UUID
}

type CreateOrganizationParams

type CreateOrganizationParams struct {
	ID         uuid.UUID
	Name       string
	Slug       string
	IsPersonal bool
}

type CreateSaltParams

type CreateSaltParams struct {
	ValidOn time.Time
	Salt    []byte
	PurgeAt time.Time
}

type CreateSessionParams

type CreateSessionParams struct {
	ID        uuid.UUID
	UserID    uuid.UUID
	TokenHash []byte
	IpPrefix  *string
	UserAgent *string
	ExpiresAt time.Time
}

type CreateTagParams

type CreateTagParams struct {
	ID          uuid.UUID
	WorkspaceID uuid.UUID
	Name        string
	Color       string
}

type CreateUserParams

type CreateUserParams struct {
	ID              uuid.UUID
	Email           string
	Name            string
	PasswordHash    *string
	Status          string
	EmailVerifiedAt *time.Time
}

type CreateWorkspaceParams

type CreateWorkspaceParams struct {
	ID             uuid.UUID
	OrganizationID uuid.UUID
	Name           string
	Slug           string
}

type DBTX

type DBTX interface {
	Exec(context.Context, string, ...interface{}) (pgconn.CommandTag, error)
	Query(context.Context, string, ...interface{}) (pgx.Rows, error)
	QueryRow(context.Context, string, ...interface{}) pgx.Row
}

type DeleteTagParams

type DeleteTagParams struct {
	ID          uuid.UUID
	WorkspaceID uuid.UUID
}

type Destination

type Destination struct {
	ID          uuid.UUID
	LinkID      uuid.UUID
	WorkspaceID uuid.UUID
	Url         string
	UrlHost     string
	Label       string
	Weight      int32
	Position    int32
	CreatedAt   time.Time
	UpdatedAt   time.Time
	DeletedAt   *time.Time
}

type Domain

type Domain struct {
	ID              uuid.UUID
	OrganizationID  *uuid.UUID
	Hostname        string
	IsDefault       bool
	VerifiedAt      *time.Time
	SslStatus       string
	CreatedAt       time.Time
	UpdatedAt       time.Time
	DeletedAt       *time.Time
	RootRedirectUrl *string
}

type Folder

type Folder struct {
	ID          uuid.UUID
	WorkspaceID uuid.UUID
	ParentID    *uuid.UUID
	Name        string
	CreatedAt   time.Time
	UpdatedAt   time.Time
	DeletedAt   *time.Time
}

type GetAPIKeyByPrefixRow

type GetAPIKeyByPrefixRow struct {
	ID             uuid.UUID
	UserID         uuid.UUID
	OrganizationID uuid.UUID
	WorkspaceID    *uuid.UUID
	KeyHash        []byte
	Scopes         []string
	ExpiresAt      *time.Time
	RevokedAt      *time.Time
	Email          string
	UserName       string
	Status         string
}

type GetDefaultDomainSettingsRow

type GetDefaultDomainSettingsRow struct {
	ID              uuid.UUID
	Hostname        string
	RootRedirectUrl *string
}

type GetLinkByAliasParams

type GetLinkByAliasParams struct {
	DomainID uuid.UUID
	Alias    string
}

type GetLinkDimensionsParams

type GetLinkDimensionsParams struct {
	LinkID    uuid.UUID
	Dimension string
	FromDay   time.Time
	ToDay     time.Time
	RowLimit  int32
}

type GetLinkDimensionsRow

type GetLinkDimensionsRow struct {
	Value          string
	Clicks         int64
	UniqueVisitors int64
}

type GetLinkParams

type GetLinkParams struct {
	ID          uuid.UUID
	WorkspaceID uuid.UUID
}

type GetLinkStatsParams

type GetLinkStatsParams struct {
	LinkID  uuid.UUID
	FromDay time.Time
	ToDay   time.Time
}

type GetLinkStatsRow

type GetLinkStatsRow struct {
	Day            time.Time
	Clicks         int64
	UniqueVisitors int64
	BotClicks      int64
}

type GetLinkTagsRow

type GetLinkTagsRow struct {
	ID    uuid.UUID
	Name  string
	Color string
}

type GetRecentClicksParams

type GetRecentClicksParams struct {
	LinkID   uuid.UUID
	RowLimit int32
}

type GetRecentClicksRow

type GetRecentClicksRow struct {
	OccurredAt   time.Time
	Device       *string
	Browser      *string
	Os           *string
	Country      *string
	ReferrerHost *string
	IsBot        bool
}

type GetSessionByTokenHashRow

type GetSessionByTokenHashRow struct {
	ID           uuid.UUID
	UserID       uuid.UUID
	CreatedAt    time.Time
	LastSeenAt   time.Time
	ExpiresAt    time.Time
	Email        string
	Name         string
	Status       string
	PasswordHash *string
}

type GetTagByNameParams

type GetTagByNameParams struct {
	WorkspaceID uuid.UUID
	Lower       string
}

type GetUserPermissionsParams

type GetUserPermissionsParams struct {
	UserID uuid.UUID
	ID     uuid.UUID
}

type GetUserRoleInWorkspaceParams

type GetUserRoleInWorkspaceParams struct {
	UserID uuid.UUID
	ID     uuid.UUID
}

type GetUserRoleInWorkspaceRow

type GetUserRoleInWorkspaceRow struct {
	Slug string
	Name string
	Rank int32
}

type GetWorkspaceDefaultDomainRow

type GetWorkspaceDefaultDomainRow struct {
	ID       uuid.UUID
	Hostname string
}

type GetWorkspaceStatsParams

type GetWorkspaceStatsParams struct {
	WorkspaceID uuid.UUID
	FromDay     time.Time
	ToDay       time.Time
}

type GetWorkspaceStatsRow

type GetWorkspaceStatsRow struct {
	Day            time.Time
	Clicks         int64
	UniqueVisitors int64
	BotClicks      int64
	ActiveLinks    int64
}

type GetWorkspaceTotalsParams

type GetWorkspaceTotalsParams struct {
	WorkspaceID uuid.UUID
	FromDay     time.Time
	ToDay       time.Time
}

type GetWorkspaceTotalsRow

type GetWorkspaceTotalsRow struct {
	Clicks         int64
	UniqueVisitors int64
	BotClicks      int64
}

type IsAliasTakenParams

type IsAliasTakenParams struct {
	DomainID uuid.UUID
	Alias    string
}

type JobState

type JobState struct {
	Job       string
	LastRunAt *time.Time
	Watermark *time.Time
	LastError *string
	UpdatedAt time.Time
}
type Link struct {
	ID                   uuid.UUID
	WorkspaceID          uuid.UUID
	DomainID             uuid.UUID
	FolderID             *uuid.UUID
	Alias                string
	PrimaryUrl           string
	PrimaryDestinationID *uuid.UUID
	Title                string
	Description          string
	Status               string
	ExpiresAt            *time.Time
	PasswordHash         *string
	MaxClicks            *int64
	OneTime              bool
	ForwardQuery         bool
	ClickCount           int64
	LastClickAt          *time.Time
	CreatedBy            *uuid.UUID
	CreatedAt            time.Time
	UpdatedAt            time.Time
	ArchivedAt           *time.Time
	DeletedAt            *time.Time
	PurgeAfter           *time.Time
	SearchVector         interface{}
	CampaignID           *uuid.UUID
}

type LinkClickDaily

type LinkClickDaily struct {
	LinkID         uuid.UUID
	WorkspaceID    uuid.UUID
	Day            time.Time
	Clicks         int64
	UniqueVisitors int64
	BotClicks      int64
	FinalizedAt    *time.Time
}

type LinkDimensionDaily

type LinkDimensionDaily struct {
	LinkID         uuid.UUID
	WorkspaceID    uuid.UUID
	Day            time.Time
	Dimension      string
	Value          string
	Clicks         int64
	UniqueVisitors int64
}

type LinkTag

type LinkTag struct {
	LinkID      uuid.UUID
	TagID       uuid.UUID
	WorkspaceID uuid.UUID
}

type ListAPIKeysForUserParams

type ListAPIKeysForUserParams struct {
	UserID         uuid.UUID
	OrganizationID uuid.UUID
}

type ListAPIKeysForUserRow

type ListAPIKeysForUserRow struct {
	ID         uuid.UUID
	Name       string
	Prefix     string
	Scopes     []string
	LastUsedAt *time.Time
	ExpiresAt  *time.Time
	RevokedAt  *time.Time
	CreatedAt  time.Time
}

type ListLinksParams

type ListLinksParams struct {
	WorkspaceID   uuid.UUID
	Status        *string
	Search        *string
	TagIds        []uuid.UUID
	CursorID      *uuid.UUID
	Sort          string
	CursorCreated *time.Time
	CursorClicks  *int64
	PageLimit     int32
}

type ListLinksRow

type ListLinksRow struct {
	ID                   uuid.UUID
	WorkspaceID          uuid.UUID
	DomainID             uuid.UUID
	FolderID             *uuid.UUID
	Alias                string
	PrimaryUrl           string
	PrimaryDestinationID *uuid.UUID
	Title                string
	Description          string
	Status               string
	ExpiresAt            *time.Time
	PasswordHash         *string
	MaxClicks            *int64
	OneTime              bool
	ForwardQuery         bool
	ClickCount           int64
	LastClickAt          *time.Time
	CreatedBy            *uuid.UUID
	CreatedAt            time.Time
	UpdatedAt            time.Time
	ArchivedAt           *time.Time
	DeletedAt            *time.Time
	PurgeAfter           *time.Time
	SearchVector         interface{}
	CampaignID           *uuid.UUID
	TagNames             []string
	TagIds               []string
}

type ListTagsRow

type ListTagsRow struct {
	ID          uuid.UUID
	WorkspaceID uuid.UUID
	Name        string
	Color       string
	CreatedAt   time.Time
	LinkCount   int64
}

type ListUserSessionsRow

type ListUserSessionsRow struct {
	ID         uuid.UUID
	IpPrefix   *string
	UserAgent  *string
	CreatedAt  time.Time
	LastSeenAt time.Time
	ExpiresAt  time.Time
}

type ListUsersRow

type ListUsersRow struct {
	ID          uuid.UUID
	Email       string
	Name        string
	Status      string
	LastLoginAt *time.Time
	CreatedAt   time.Time
}

type Membership

type Membership struct {
	ID             uuid.UUID
	UserID         uuid.UUID
	OrganizationID uuid.UUID
	RoleID         uuid.UUID
	WorkspaceID    *uuid.UUID
	CreatedAt      time.Time
	UpdatedAt      time.Time
}

type Notification

type Notification struct {
	ID          uuid.UUID
	UserID      uuid.UUID
	WorkspaceID *uuid.UUID
	Kind        string
	Title       string
	Body        string
	Data        []byte
	ReadAt      *time.Time
	CreatedAt   time.Time
}

type Organization

type Organization struct {
	ID         uuid.UUID
	Name       string
	Slug       string
	DataRegion string
	IsPersonal bool
	CreatedAt  time.Time
	UpdatedAt  time.Time
	DeletedAt  *time.Time
}

type Permission

type Permission struct {
	ID          uuid.UUID
	Slug        string
	Description string
}

type PurgeExpiredLinksRow

type PurgeExpiredLinksRow struct {
	Alias    string
	Reserved bool
}

type QrCode

type QrCode struct {
	ID          uuid.UUID
	LinkID      uuid.UUID
	WorkspaceID uuid.UUID
	Style       []byte
	ScanCount   int64
	CreatedAt   time.Time
	UpdatedAt   time.Time
}

type Querier

type Querier interface {
	ArchiveLink(ctx context.Context, arg ArchiveLinkParams) (Link, error)
	AttachTag(ctx context.Context, arg AttachTagParams) error
	CountClickEvents(ctx context.Context, workspaceID uuid.UUID) (int64, error)
	// Only issued when the caller explicitly asks for a total, because counting
	// costs a scan the common page load should not pay for.
	CountLinks(ctx context.Context, arg CountLinksParams) (int64, error)
	// Users, sessions and tenancy provisioning.
	// Drives the first-run setup flow: /setup exists only while this is zero.
	CountUsers(ctx context.Context) (int64, error)
	// API keys and the permission vocabulary their scopes are drawn from.
	CreateAPIKey(ctx context.Context, arg CreateAPIKeyParams) (ApiKey, error)
	CreateDestination(ctx context.Context, arg CreateDestinationParams) (Destination, error)
	// Links, destinations and tags.
	CreateLink(ctx context.Context, arg CreateLinkParams) (Link, error)
	CreateMembership(ctx context.Context, arg CreateMembershipParams) (Membership, error)
	CreateOrganization(ctx context.Context, arg CreateOrganizationParams) (Organization, error)
	// ON CONFLICT DO NOTHING returns no row when another replica inserted first,
	// which the caller detects and re-reads. Two replicas using different salts
	// for the same day would split every visitor in two.
	CreateSalt(ctx context.Context, arg CreateSaltParams) ([]byte, error)
	CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error)
	// --- tags -------------------------------------------------------------------
	CreateTag(ctx context.Context, arg CreateTagParams) (Tag, error)
	CreateUser(ctx context.Context, arg CreateUserParams) (User, error)
	CreateWorkspace(ctx context.Context, arg CreateWorkspaceParams) (Workspace, error)
	// Reaper. Revoked rows are kept briefly so "sign out everywhere" is visible in
	// the session list before it disappears.
	DeleteExpiredSessions(ctx context.Context) (int64, error)
	// Reaper. Kept long enough to be visible in the key list after revocation, and
	// long enough for the audit question above to be answerable.
	DeleteRevokedAPIKeys(ctx context.Context) (int64, error)
	DeleteTag(ctx context.Context, arg DeleteTagParams) (int64, error)
	DetachAllTags(ctx context.Context, linkID uuid.UUID) error
	// The verification lookup, on the unique prefix index, joined with the user so
	// authentication is one round trip. Revoked and expired keys are returned
	// rather than filtered out: the caller distinguishes them so the response can
	// say which it was, and a deleted user's key resolves to no row at all.
	GetAPIKeyByPrefix(ctx context.Context, prefix string) (GetAPIKeyByPrefixRow, error)
	// The instance's link domain and where its root points. Phase 1 has exactly one
	// default domain; Phase 2 gives a workspace its own and this gains a filter.
	GetDefaultDomainSettings(ctx context.Context) (GetDefaultDomainSettingsRow, error)
	// The workspace a user lands in with no explicit selection. Ordered so the
	// result is deterministic rather than whatever the planner returns first.
	GetDefaultWorkspaceForUser(ctx context.Context, userID uuid.UUID) (Workspace, error)
	// --- job bookkeeping ---------------------------------------------------------
	// The point a job is known to have completed through. Rollups recompute rather
	// than accumulate, so this is not a correctness dependency for a run that
	// happens on schedule — it exists for the run that does not. Without it,
	// RunRecent covered a fixed yesterday-and-today window, and any downtime that
	// spanned a UTC day left that day with no rollup and nothing to notice it: the
	// raw events were still there, but nothing ever aggregated them again.
	GetJobWatermark(ctx context.Context, job string) (*time.Time, error)
	// Workspace-scoped by design. Passing the workspace here rather than checking
	// it after the fetch makes cross-tenant reads impossible to write by accident:
	// the wrong workspace returns no rows rather than a row the caller must
	// remember to reject.
	GetLink(ctx context.Context, arg GetLinkParams) (Link, error)
	GetLinkByAlias(ctx context.Context, arg GetLinkByAliasParams) (Link, error)
	GetLinkDimensions(ctx context.Context, arg GetLinkDimensionsParams) ([]GetLinkDimensionsRow, error)
	// Reads the rollup, never the raw events. This is what keeps analytics under
	// the 2s target as click_events grows into the tens of millions.
	GetLinkStats(ctx context.Context, arg GetLinkStatsParams) ([]GetLinkStatsRow, error)
	GetLinkTags(ctx context.Context, linkID uuid.UUID) ([]GetLinkTagsRow, error)
	// The live-activity feed. Bounded and index-backed on (link_id, occurred_at).
	GetRecentClicks(ctx context.Context, arg GetRecentClicksParams) ([]GetRecentClicksRow, error)
	GetRoleBySlug(ctx context.Context, slug string) (Role, error)
	// Analytics: salts, rollups and reads.
	GetSalt(ctx context.Context, validOn time.Time) ([]byte, error)
	// Joined with the user so validating a session is one round trip on a path
	// that runs for every authenticated request. Filters revoked and deleted here
	// rather than in Go, so a revoked session cannot be resurrected by a caller
	// that forgets to check.
	GetSessionByTokenHash(ctx context.Context, tokenHash []byte) (GetSessionByTokenHashRow, error)
	GetTagByName(ctx context.Context, arg GetTagByNameParams) (Tag, error)
	// Comparison is on the generated email_lower column, so callers cannot
	// accidentally do a case-sensitive lookup and create a duplicate account.
	GetUserByEmail(ctx context.Context, email string) (User, error)
	GetUserByID(ctx context.Context, id uuid.UUID) (User, error)
	// The RBAC evaluator's source of truth. Returns every permission a user holds
	// in a workspace, via their organization membership and its role.
	//
	// A NULL memberships.workspace_id means the membership covers every workspace
	// in the organization, which is what Phase 1 always creates.
	GetUserPermissions(ctx context.Context, arg GetUserPermissionsParams) ([]string, error)
	GetUserRoleInWorkspace(ctx context.Context, arg GetUserRoleInWorkspaceParams) (GetUserRoleInWorkspaceRow, error)
	GetWorkspaceDefaultDomain(ctx context.Context) (GetWorkspaceDefaultDomainRow, error)
	GetWorkspaceStats(ctx context.Context, arg GetWorkspaceStatsParams) ([]GetWorkspaceStatsRow, error)
	// Summing daily uniques over-counts anyone visiting on more than one day.
	// Reported as "unique visitors per day, summed" in the UI rather than
	// presented as a distinct-person count, because the exact figure cannot be
	// recovered once the salts are purged. That is the intended trade.
	GetWorkspaceTotals(ctx context.Context, arg GetWorkspaceTotalsParams) (GetWorkspaceTotalsRow, error)
	// Consulted by BOTH create paths — generated aliases before insert, and
	// user-supplied aliases as validation — and by alias changes.
	//
	// No deleted_at filter on the links branch, deliberately: a soft-deleted row
	// holds its alias for the whole trash window, so a link deleted by accident can
	// be restored under its own name. The partial unique index cannot enforce that
	// (it ignores trashed rows), so this check is the enforcement and the index
	// remains the guarantee against live-row races only.
	IsAliasTaken(ctx context.Context, arg IsAliasTakenParams) (bool, error)
	// Revoked keys are included. "Which keys existed and when were they revoked"
	// is the question asked after an incident, so they are listed until the reaper
	// removes them.
	ListAPIKeysForUser(ctx context.Context, arg ListAPIKeysForUserParams) ([]ListAPIKeysForUserRow, error)
	// Keyset pagination over (created_at, id).
	//
	// The cursor is a composite so ordering is total: created_at alone is not
	// unique, and a tie at the page boundary would drop or duplicate rows.
	// Comparing the pair with row-value syntax lets the composite index serve it
	// directly.
	//
	// Sorting is a CASE rather than three separate queries because sqlc has no
	// dynamic SQL. If plan stability becomes a problem this splits into
	// ListLinksNewest/Oldest/Clicks; measure before doing that.
	// The two tag aggregates are paired positionally by the caller, so they must
	// agree on their order — and on the table they read. Aggregating names from a
	// join and ids from link_tags alone, each sorted by its own column, produced
	// arrays in different orders whenever a link's tags sorted differently by name
	// than by id, and every tag came back carrying another tag's name. One
	// subquery, one ORDER BY, both columns.
	ListLinks(ctx context.Context, arg ListLinksParams) ([]ListLinksRow, error)
	// The scope vocabulary. Scopes are validated against the permissions table
	// rather than a list in Go, so RBAC and API keys cannot drift apart.
	ListPermissionSlugs(ctx context.Context) ([]string, error)
	// Counts l.id, not lt.link_id. The join onto links is what excludes trashed
	// links, but counting the link_tags column ignored it: a LEFT JOIN keeps the
	// link_tags row when its link is soft-deleted, so the count included trashed
	// links for the whole 30-day window and the tag list disagreed with the link
	// list it filters.
	ListTags(ctx context.Context, workspaceID uuid.UUID) ([]ListTagsRow, error)
	ListUserSessions(ctx context.Context, userID uuid.UUID) ([]ListUserSessionsRow, error)
	ListUsers(ctx context.Context) ([]ListUsersRow, error)
	// Serializes the setup flow's count-then-create.
	//
	// Both setup surfaces read CountUsers and then, in a separate transaction,
	// register the first user. Nothing held the gap, and the gap is wide: the
	// argon2 hash runs for ~100ms before the transaction even begins. On a fresh
	// closed instance, setup is unauthenticated and only login-rate-limited, so an
	// attacker polling it could have their CountUsers land in the window while the
	// real operator was hashing, and both would be created as "the first user" —
	// each with their own organization, on an instance the operator believes only
	// they can reach.
	//
	// Transaction-scoped, so it releases on commit or rollback with nothing to
	// clean up. The key is the ASCII bytes "lcsetup\0" as a literal, NOT a hash of
	// anything; to inspect it from psql use the value directly:
	//
	//     SELECT pg_advisory_xact_lock(7810213058373316608);
	LockFirstUserSetup(ctx context.Context) error
	// The end of the trash window: hard-delete links whose purge_after has passed.
	//
	// One statement, so the reservation and the deletion cannot be separated by a
	// crash: an alias that ever received traffic is written to reserved_aliases in
	// the same command that removes its row, and ON CONFLICT makes a retried run
	// converge rather than fail. Aliases that never received a click are released —
	// deliberately, per the reserved_aliases rationale: nothing in the wild points
	// at them, so permanent reservation would only bleed the namespace.
	//
	// SKIP LOCKED so the purge can never block, or be blocked by, a concurrent
	// restore-by-hand of the same row; a skipped row is caught on the next run.
	// Destinations and link_tags follow by ON DELETE CASCADE. click_events rows
	// carry no FK (partitioned) and are dropped by analytics retention instead.
	PurgeExpiredLinks(ctx context.Context, batchSize int32) ([]PurgeExpiredLinksRow, error)
	// The de-identification step. Once the salt is gone the day's hashes cannot be
	// linked back to an address.
	PurgeExpiredSalts(ctx context.Context) (int64, error)
	// Returns the new count so the caller can apply the lockout policy without a
	// second round trip and without a read-modify-write race between two
	// concurrent attempts.
	//
	// An elapsed lockout starts the count over. Incrementing unconditionally meant
	// the counter only ever went down on a successful sign-in or a password change,
	// so once an account had been locked it sat at the threshold forever: the user
	// waited out the window, got one attempt, and a single wrong guess re-locked
	// them for the full duration. The lockout became permanent for anyone who could
	// not remember their password on the first try — which is the population it
	// applies to.
	RecordFailedLogin(ctx context.Context, arg RecordFailedLoginParams) (RecordFailedLoginRow, error)
	// Keeps the watermark where it was: a failed run has not covered its window,
	// and advancing past it would turn one bad run into permanent gaps.
	RecordJobFailure(ctx context.Context, arg RecordJobFailureParams) error
	RecordSuccessfulLogin(ctx context.Context, id uuid.UUID) error
	// Called before purging a link that has clicks. The alias is in the wild — on
	// printed material and in other people's bookmarks — so handing it to a new
	// destination would be a redirect hijack.
	ReserveAlias(ctx context.Context, arg ReserveAliasParams) error
	// The redirect hot path.
	//
	// Everything here runs under a 20ms budget on the dedicated redirect pool.
	// Keep the query set small, index-covered, and free of joins that are not
	// strictly required.
	// Single-row lookup on links_domain_alias_key.
	//
	// primary_url is read from the denormalized column rather than joined from
	// destinations: the join would double the row fetches on the hottest query in
	// the system to retrieve a value a trigger already keeps in step.
	//
	// Status and expiry are returned rather than filtered, so the handler can
	// distinguish 404 (unknown or archived) from 410 (expired) and can cache a
	// negative result. Filtering here would make every non-serving state look
	// identical.
	ResolveAliasForRedirect(ctx context.Context, arg ResolveAliasForRedirectParams) (ResolveAliasForRedirectRow, error)
	// Read once at boot and cached. The default domain is matched on the flag
	// rather than on a hostname string, so it never has to agree with
	// LINKCTRL_BASE_URL.
	ResolveDefaultDomain(ctx context.Context) (ResolveDefaultDomainRow, error)
	// PHASE 2: custom domains. Present now because the cache key is already
	// host-scoped, so enabling it later needs no key change.
	ResolveDomainByHostname(ctx context.Context, lower string) (ResolveDomainByHostnameRow, error)
	RestoreLink(ctx context.Context, arg RestoreLinkParams) (Link, error)
	// Idempotent: revoking an already-revoked key keeps the original timestamp and
	// still reports one row, so a repeated call is a success rather than a 404
	// while a genuinely unknown id is still distinguishable.
	RevokeAPIKey(ctx context.Context, arg RevokeAPIKeyParams) (int64, error)
	// Used on password change. Anyone who had the old password must be logged out,
	// which is the entire point of changing it.
	// keep_session is optional: pass NULL to revoke everything, or the current
	// session's id to leave the browser the user is changing their password in
	// still signed in.
	RevokeAllUserSessions(ctx context.Context, arg RevokeAllUserSessionsParams) error
	RevokeSession(ctx context.Context, id uuid.UUID) error
	// Every dimension in one pass over click_events.
	//
	// This was six UNION ALL branches, one per dimension, reading the same rows six
	// times. Measured on the load-test dataset (5.7M events, ~830k inside the
	// recomputed window), that shape sorted 6.2M rows through an external merge that
	// spilled 471 MB of temp files, every 60 seconds. Reading once and expanding each
	// row with LATERAL VALUES lets the sort use the index's link_id ordering, so it
	// runs incrementally in memory instead — peak 152 kB per group, no temp files.
	//
	// Wall clock is unchanged (~20s either way), and that is the finding rather than a
	// disappointment: the time is in the 553k upserts a whole-day recompute implies,
	// not in reading the events. See docs/slo.md. This version is kept because
	// eliminating half a gigabyte of temp I/O per run is worth having on any host
	// smaller than the one it was measured on; it is not a fix for the job's cost.
	//
	// The output is identical: same grouping keys, same aggregates, same conflict
	// resolution. TestDimensionRollupMatchesAPerDimensionAggregate checks that
	// against a per-dimension aggregate written the other way round.
	RollupDimensionDaily(ctx context.Context, arg RollupDimensionDailyParams) error
	// Recompute per-link daily totals for a window.
	//
	// Idempotent by construction: it recomputes a whole day from the raw events
	// and upserts, so running it twice, or after a crash mid-run, converges to the
	// same numbers. An incremental "add what is new" design would double-count on
	// any retry.
	RollupLinkDaily(ctx context.Context, arg RollupLinkDailyParams) error
	RollupWorkspaceDaily(ctx context.Context, arg RollupWorkspaceDailyParams) error
	// NULL clears it, which restores the 404 the root answered before anyone set
	// anything.
	SetDefaultDomainRootRedirect(ctx context.Context, rootRedirectUrl *string) (SetDefaultDomainRootRedirectRow, error)
	SetJobWatermark(ctx context.Context, arg SetJobWatermarkParams) error
	SetPrimaryDestination(ctx context.Context, arg SetPrimaryDestinationParams) error
	// Soft delete with a purge deadline rather than an immediate DELETE. Restoring
	// a link someone deleted by accident is a common request, and the alias stays
	// reserved while the row exists.
	SoftDeleteLink(ctx context.Context, arg SoftDeleteLinkParams) (SoftDeleteLinkRow, error)
	// Batch write of last_used_at, from the coalescing tracker rather than from the
	// request path: authenticating a key must not cost a synchronous write.
	//
	// GREATEST guards against a late batch moving the timestamp backwards, which
	// two processes flushing out of order would otherwise do.
	TouchAPIKeys(ctx context.Context, arg TouchAPIKeysParams) error
	// Idle expiry is measured from last_seen_at. Updated at most once a minute by
	// the caller, because writing on every request would turn a read-mostly path
	// into a write on the hottest authenticated query.
	TouchSession(ctx context.Context, id uuid.UUID) error
	// The trigger on destinations mirrors this into links.primary_url, so the hot
	// path never joins.
	UpdateDestinationURL(ctx context.Context, arg UpdateDestinationURLParams) error
	// COALESCE with sqlc.narg gives partial update: a NULL argument leaves the
	// column alone, so PATCH semantics need no dynamic SQL.
	UpdateLink(ctx context.Context, arg UpdateLinkParams) (Link, error)
	UpdateUserPassword(ctx context.Context, arg UpdateUserPasswordParams) error
}

type Queries

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

func New

func New(db DBTX) *Queries
func (q *Queries) ArchiveLink(ctx context.Context, arg ArchiveLinkParams) (Link, error)

func (*Queries) AttachTag

func (q *Queries) AttachTag(ctx context.Context, arg AttachTagParams) error

func (*Queries) CountClickEvents

func (q *Queries) CountClickEvents(ctx context.Context, workspaceID uuid.UUID) (int64, error)
func (q *Queries) CountLinks(ctx context.Context, arg CountLinksParams) (int64, error)

Only issued when the caller explicitly asks for a total, because counting costs a scan the common page load should not pay for.

func (*Queries) CountUsers

func (q *Queries) CountUsers(ctx context.Context) (int64, error)

Users, sessions and tenancy provisioning. Drives the first-run setup flow: /setup exists only while this is zero.

func (*Queries) CreateAPIKey

func (q *Queries) CreateAPIKey(ctx context.Context, arg CreateAPIKeyParams) (ApiKey, error)

API keys and the permission vocabulary their scopes are drawn from.

func (*Queries) CreateDestination

func (q *Queries) CreateDestination(ctx context.Context, arg CreateDestinationParams) (Destination, error)
func (q *Queries) CreateLink(ctx context.Context, arg CreateLinkParams) (Link, error)

Links, destinations and tags.

func (*Queries) CreateMembership

func (q *Queries) CreateMembership(ctx context.Context, arg CreateMembershipParams) (Membership, error)

func (*Queries) CreateOrganization

func (q *Queries) CreateOrganization(ctx context.Context, arg CreateOrganizationParams) (Organization, error)

func (*Queries) CreateSalt

func (q *Queries) CreateSalt(ctx context.Context, arg CreateSaltParams) ([]byte, error)

ON CONFLICT DO NOTHING returns no row when another replica inserted first, which the caller detects and re-reads. Two replicas using different salts for the same day would split every visitor in two.

func (*Queries) CreateSession

func (q *Queries) CreateSession(ctx context.Context, arg CreateSessionParams) (Session, error)

func (*Queries) CreateTag

func (q *Queries) CreateTag(ctx context.Context, arg CreateTagParams) (Tag, error)

--- tags -------------------------------------------------------------------

func (*Queries) CreateUser

func (q *Queries) CreateUser(ctx context.Context, arg CreateUserParams) (User, error)

func (*Queries) CreateWorkspace

func (q *Queries) CreateWorkspace(ctx context.Context, arg CreateWorkspaceParams) (Workspace, error)

func (*Queries) DeleteExpiredSessions

func (q *Queries) DeleteExpiredSessions(ctx context.Context) (int64, error)

Reaper. Revoked rows are kept briefly so "sign out everywhere" is visible in the session list before it disappears.

func (*Queries) DeleteRevokedAPIKeys

func (q *Queries) DeleteRevokedAPIKeys(ctx context.Context) (int64, error)

Reaper. Kept long enough to be visible in the key list after revocation, and long enough for the audit question above to be answerable.

func (*Queries) DeleteTag

func (q *Queries) DeleteTag(ctx context.Context, arg DeleteTagParams) (int64, error)

func (*Queries) DetachAllTags

func (q *Queries) DetachAllTags(ctx context.Context, linkID uuid.UUID) error

func (*Queries) GetAPIKeyByPrefix

func (q *Queries) GetAPIKeyByPrefix(ctx context.Context, prefix string) (GetAPIKeyByPrefixRow, error)

The verification lookup, on the unique prefix index, joined with the user so authentication is one round trip. Revoked and expired keys are returned rather than filtered out: the caller distinguishes them so the response can say which it was, and a deleted user's key resolves to no row at all.

func (*Queries) GetDefaultDomainSettings

func (q *Queries) GetDefaultDomainSettings(ctx context.Context) (GetDefaultDomainSettingsRow, error)

The instance's link domain and where its root points. Phase 1 has exactly one default domain; Phase 2 gives a workspace its own and this gains a filter.

func (*Queries) GetDefaultWorkspaceForUser

func (q *Queries) GetDefaultWorkspaceForUser(ctx context.Context, userID uuid.UUID) (Workspace, error)

The workspace a user lands in with no explicit selection. Ordered so the result is deterministic rather than whatever the planner returns first.

func (*Queries) GetJobWatermark

func (q *Queries) GetJobWatermark(ctx context.Context, job string) (*time.Time, error)

--- job bookkeeping --------------------------------------------------------- The point a job is known to have completed through. Rollups recompute rather than accumulate, so this is not a correctness dependency for a run that happens on schedule — it exists for the run that does not. Without it, RunRecent covered a fixed yesterday-and-today window, and any downtime that spanned a UTC day left that day with no rollup and nothing to notice it: the raw events were still there, but nothing ever aggregated them again.

func (q *Queries) GetLink(ctx context.Context, arg GetLinkParams) (Link, error)

Workspace-scoped by design. Passing the workspace here rather than checking it after the fetch makes cross-tenant reads impossible to write by accident: the wrong workspace returns no rows rather than a row the caller must remember to reject.

func (*Queries) GetLinkByAlias

func (q *Queries) GetLinkByAlias(ctx context.Context, arg GetLinkByAliasParams) (Link, error)

func (*Queries) GetLinkDimensions

func (q *Queries) GetLinkDimensions(ctx context.Context, arg GetLinkDimensionsParams) ([]GetLinkDimensionsRow, error)

func (*Queries) GetLinkStats

func (q *Queries) GetLinkStats(ctx context.Context, arg GetLinkStatsParams) ([]GetLinkStatsRow, error)

Reads the rollup, never the raw events. This is what keeps analytics under the 2s target as click_events grows into the tens of millions.

func (*Queries) GetLinkTags

func (q *Queries) GetLinkTags(ctx context.Context, linkID uuid.UUID) ([]GetLinkTagsRow, error)

func (*Queries) GetRecentClicks

func (q *Queries) GetRecentClicks(ctx context.Context, arg GetRecentClicksParams) ([]GetRecentClicksRow, error)

The live-activity feed. Bounded and index-backed on (link_id, occurred_at).

func (*Queries) GetRoleBySlug

func (q *Queries) GetRoleBySlug(ctx context.Context, slug string) (Role, error)

func (*Queries) GetSalt

func (q *Queries) GetSalt(ctx context.Context, validOn time.Time) ([]byte, error)

Analytics: salts, rollups and reads.

func (*Queries) GetSessionByTokenHash

func (q *Queries) GetSessionByTokenHash(ctx context.Context, tokenHash []byte) (GetSessionByTokenHashRow, error)

Joined with the user so validating a session is one round trip on a path that runs for every authenticated request. Filters revoked and deleted here rather than in Go, so a revoked session cannot be resurrected by a caller that forgets to check.

func (*Queries) GetTagByName

func (q *Queries) GetTagByName(ctx context.Context, arg GetTagByNameParams) (Tag, error)

func (*Queries) GetUserByEmail

func (q *Queries) GetUserByEmail(ctx context.Context, email string) (User, error)

Comparison is on the generated email_lower column, so callers cannot accidentally do a case-sensitive lookup and create a duplicate account.

func (*Queries) GetUserByID

func (q *Queries) GetUserByID(ctx context.Context, id uuid.UUID) (User, error)

func (*Queries) GetUserPermissions

func (q *Queries) GetUserPermissions(ctx context.Context, arg GetUserPermissionsParams) ([]string, error)

The RBAC evaluator's source of truth. Returns every permission a user holds in a workspace, via their organization membership and its role.

A NULL memberships.workspace_id means the membership covers every workspace in the organization, which is what Phase 1 always creates.

func (*Queries) GetUserRoleInWorkspace

func (q *Queries) GetUserRoleInWorkspace(ctx context.Context, arg GetUserRoleInWorkspaceParams) (GetUserRoleInWorkspaceRow, error)

func (*Queries) GetWorkspaceDefaultDomain

func (q *Queries) GetWorkspaceDefaultDomain(ctx context.Context) (GetWorkspaceDefaultDomainRow, error)

func (*Queries) GetWorkspaceStats

func (q *Queries) GetWorkspaceStats(ctx context.Context, arg GetWorkspaceStatsParams) ([]GetWorkspaceStatsRow, error)

func (*Queries) GetWorkspaceTotals

func (q *Queries) GetWorkspaceTotals(ctx context.Context, arg GetWorkspaceTotalsParams) (GetWorkspaceTotalsRow, error)

Summing daily uniques over-counts anyone visiting on more than one day. Reported as "unique visitors per day, summed" in the UI rather than presented as a distinct-person count, because the exact figure cannot be recovered once the salts are purged. That is the intended trade.

func (*Queries) IsAliasTaken

func (q *Queries) IsAliasTaken(ctx context.Context, arg IsAliasTakenParams) (bool, error)

Consulted by BOTH create paths — generated aliases before insert, and user-supplied aliases as validation — and by alias changes.

No deleted_at filter on the links branch, deliberately: a soft-deleted row holds its alias for the whole trash window, so a link deleted by accident can be restored under its own name. The partial unique index cannot enforce that (it ignores trashed rows), so this check is the enforcement and the index remains the guarantee against live-row races only.

func (*Queries) ListAPIKeysForUser

func (q *Queries) ListAPIKeysForUser(ctx context.Context, arg ListAPIKeysForUserParams) ([]ListAPIKeysForUserRow, error)

Revoked keys are included. "Which keys existed and when were they revoked" is the question asked after an incident, so they are listed until the reaper removes them.

func (q *Queries) ListLinks(ctx context.Context, arg ListLinksParams) ([]ListLinksRow, error)

Keyset pagination over (created_at, id).

The cursor is a composite so ordering is total: created_at alone is not unique, and a tie at the page boundary would drop or duplicate rows. Comparing the pair with row-value syntax lets the composite index serve it directly.

Sorting is a CASE rather than three separate queries because sqlc has no dynamic SQL. If plan stability becomes a problem this splits into ListLinksNewest/Oldest/Clicks; measure before doing that. The two tag aggregates are paired positionally by the caller, so they must agree on their order — and on the table they read. Aggregating names from a join and ids from link_tags alone, each sorted by its own column, produced arrays in different orders whenever a link's tags sorted differently by name than by id, and every tag came back carrying another tag's name. One subquery, one ORDER BY, both columns.

func (*Queries) ListPermissionSlugs

func (q *Queries) ListPermissionSlugs(ctx context.Context) ([]string, error)

The scope vocabulary. Scopes are validated against the permissions table rather than a list in Go, so RBAC and API keys cannot drift apart.

func (*Queries) ListTags

func (q *Queries) ListTags(ctx context.Context, workspaceID uuid.UUID) ([]ListTagsRow, error)

Counts l.id, not lt.link_id. The join onto links is what excludes trashed links, but counting the link_tags column ignored it: a LEFT JOIN keeps the link_tags row when its link is soft-deleted, so the count included trashed links for the whole 30-day window and the tag list disagreed with the link list it filters.

func (*Queries) ListUserSessions

func (q *Queries) ListUserSessions(ctx context.Context, userID uuid.UUID) ([]ListUserSessionsRow, error)

func (*Queries) ListUsers

func (q *Queries) ListUsers(ctx context.Context) ([]ListUsersRow, error)

func (*Queries) LockFirstUserSetup

func (q *Queries) LockFirstUserSetup(ctx context.Context) error

Serializes the setup flow's count-then-create.

Both setup surfaces read CountUsers and then, in a separate transaction, register the first user. Nothing held the gap, and the gap is wide: the argon2 hash runs for ~100ms before the transaction even begins. On a fresh closed instance, setup is unauthenticated and only login-rate-limited, so an attacker polling it could have their CountUsers land in the window while the real operator was hashing, and both would be created as "the first user" — each with their own organization, on an instance the operator believes only they can reach.

Transaction-scoped, so it releases on commit or rollback with nothing to clean up. The key is the ASCII bytes "lcsetup\0" as a literal, NOT a hash of anything; to inspect it from psql use the value directly:

SELECT pg_advisory_xact_lock(7810213058373316608);
func (q *Queries) PurgeExpiredLinks(ctx context.Context, batchSize int32) ([]PurgeExpiredLinksRow, error)

The end of the trash window: hard-delete links whose purge_after has passed.

One statement, so the reservation and the deletion cannot be separated by a crash: an alias that ever received traffic is written to reserved_aliases in the same command that removes its row, and ON CONFLICT makes a retried run converge rather than fail. Aliases that never received a click are released — deliberately, per the reserved_aliases rationale: nothing in the wild points at them, so permanent reservation would only bleed the namespace.

SKIP LOCKED so the purge can never block, or be blocked by, a concurrent restore-by-hand of the same row; a skipped row is caught on the next run. Destinations and link_tags follow by ON DELETE CASCADE. click_events rows carry no FK (partitioned) and are dropped by analytics retention instead.

func (*Queries) PurgeExpiredSalts

func (q *Queries) PurgeExpiredSalts(ctx context.Context) (int64, error)

The de-identification step. Once the salt is gone the day's hashes cannot be linked back to an address.

func (*Queries) RecordFailedLogin

func (q *Queries) RecordFailedLogin(ctx context.Context, arg RecordFailedLoginParams) (RecordFailedLoginRow, error)

Returns the new count so the caller can apply the lockout policy without a second round trip and without a read-modify-write race between two concurrent attempts.

An elapsed lockout starts the count over. Incrementing unconditionally meant the counter only ever went down on a successful sign-in or a password change, so once an account had been locked it sat at the threshold forever: the user waited out the window, got one attempt, and a single wrong guess re-locked them for the full duration. The lockout became permanent for anyone who could not remember their password on the first try — which is the population it applies to.

func (*Queries) RecordJobFailure

func (q *Queries) RecordJobFailure(ctx context.Context, arg RecordJobFailureParams) error

Keeps the watermark where it was: a failed run has not covered its window, and advancing past it would turn one bad run into permanent gaps.

func (*Queries) RecordSuccessfulLogin

func (q *Queries) RecordSuccessfulLogin(ctx context.Context, id uuid.UUID) error

func (*Queries) ReserveAlias

func (q *Queries) ReserveAlias(ctx context.Context, arg ReserveAliasParams) error

Called before purging a link that has clicks. The alias is in the wild — on printed material and in other people's bookmarks — so handing it to a new destination would be a redirect hijack.

func (*Queries) ResolveAliasForRedirect

func (q *Queries) ResolveAliasForRedirect(ctx context.Context, arg ResolveAliasForRedirectParams) (ResolveAliasForRedirectRow, error)

The redirect hot path.

Everything here runs under a 20ms budget on the dedicated redirect pool. Keep the query set small, index-covered, and free of joins that are not strictly required. Single-row lookup on links_domain_alias_key.

primary_url is read from the denormalized column rather than joined from destinations: the join would double the row fetches on the hottest query in the system to retrieve a value a trigger already keeps in step.

Status and expiry are returned rather than filtered, so the handler can distinguish 404 (unknown or archived) from 410 (expired) and can cache a negative result. Filtering here would make every non-serving state look identical.

func (*Queries) ResolveDefaultDomain

func (q *Queries) ResolveDefaultDomain(ctx context.Context) (ResolveDefaultDomainRow, error)

Read once at boot and cached. The default domain is matched on the flag rather than on a hostname string, so it never has to agree with LINKCTRL_BASE_URL.

func (*Queries) ResolveDomainByHostname

func (q *Queries) ResolveDomainByHostname(ctx context.Context, lower string) (ResolveDomainByHostnameRow, error)

PHASE 2: custom domains. Present now because the cache key is already host-scoped, so enabling it later needs no key change.

func (q *Queries) RestoreLink(ctx context.Context, arg RestoreLinkParams) (Link, error)

func (*Queries) RevokeAPIKey

func (q *Queries) RevokeAPIKey(ctx context.Context, arg RevokeAPIKeyParams) (int64, error)

Idempotent: revoking an already-revoked key keeps the original timestamp and still reports one row, so a repeated call is a success rather than a 404 while a genuinely unknown id is still distinguishable.

func (*Queries) RevokeAllUserSessions

func (q *Queries) RevokeAllUserSessions(ctx context.Context, arg RevokeAllUserSessionsParams) error

Used on password change. Anyone who had the old password must be logged out, which is the entire point of changing it. keep_session is optional: pass NULL to revoke everything, or the current session's id to leave the browser the user is changing their password in still signed in.

func (*Queries) RevokeSession

func (q *Queries) RevokeSession(ctx context.Context, id uuid.UUID) error

func (*Queries) RollupDimensionDaily

func (q *Queries) RollupDimensionDaily(ctx context.Context, arg RollupDimensionDailyParams) error

Every dimension in one pass over click_events.

This was six UNION ALL branches, one per dimension, reading the same rows six times. Measured on the load-test dataset (5.7M events, ~830k inside the recomputed window), that shape sorted 6.2M rows through an external merge that spilled 471 MB of temp files, every 60 seconds. Reading once and expanding each row with LATERAL VALUES lets the sort use the index's link_id ordering, so it runs incrementally in memory instead — peak 152 kB per group, no temp files.

Wall clock is unchanged (~20s either way), and that is the finding rather than a disappointment: the time is in the 553k upserts a whole-day recompute implies, not in reading the events. See docs/slo.md. This version is kept because eliminating half a gigabyte of temp I/O per run is worth having on any host smaller than the one it was measured on; it is not a fix for the job's cost.

The output is identical: same grouping keys, same aggregates, same conflict resolution. TestDimensionRollupMatchesAPerDimensionAggregate checks that against a per-dimension aggregate written the other way round.

func (*Queries) RollupLinkDaily

func (q *Queries) RollupLinkDaily(ctx context.Context, arg RollupLinkDailyParams) error

Recompute per-link daily totals for a window.

Idempotent by construction: it recomputes a whole day from the raw events and upserts, so running it twice, or after a crash mid-run, converges to the same numbers. An incremental "add what is new" design would double-count on any retry.

func (*Queries) RollupWorkspaceDaily

func (q *Queries) RollupWorkspaceDaily(ctx context.Context, arg RollupWorkspaceDailyParams) error

func (*Queries) SetDefaultDomainRootRedirect

func (q *Queries) SetDefaultDomainRootRedirect(ctx context.Context, rootRedirectUrl *string) (SetDefaultDomainRootRedirectRow, error)

NULL clears it, which restores the 404 the root answered before anyone set anything.

func (*Queries) SetJobWatermark

func (q *Queries) SetJobWatermark(ctx context.Context, arg SetJobWatermarkParams) error

func (*Queries) SetPrimaryDestination

func (q *Queries) SetPrimaryDestination(ctx context.Context, arg SetPrimaryDestinationParams) error
func (q *Queries) SoftDeleteLink(ctx context.Context, arg SoftDeleteLinkParams) (SoftDeleteLinkRow, error)

Soft delete with a purge deadline rather than an immediate DELETE. Restoring a link someone deleted by accident is a common request, and the alias stays reserved while the row exists.

func (*Queries) TouchAPIKeys

func (q *Queries) TouchAPIKeys(ctx context.Context, arg TouchAPIKeysParams) error

Batch write of last_used_at, from the coalescing tracker rather than from the request path: authenticating a key must not cost a synchronous write.

GREATEST guards against a late batch moving the timestamp backwards, which two processes flushing out of order would otherwise do.

func (*Queries) TouchSession

func (q *Queries) TouchSession(ctx context.Context, id uuid.UUID) error

Idle expiry is measured from last_seen_at. Updated at most once a minute by the caller, because writing on every request would turn a read-mostly path into a write on the hottest authenticated query.

func (*Queries) UpdateDestinationURL

func (q *Queries) UpdateDestinationURL(ctx context.Context, arg UpdateDestinationURLParams) error

The trigger on destinations mirrors this into links.primary_url, so the hot path never joins.

func (q *Queries) UpdateLink(ctx context.Context, arg UpdateLinkParams) (Link, error)

COALESCE with sqlc.narg gives partial update: a NULL argument leaves the column alone, so PATCH semantics need no dynamic SQL.

func (*Queries) UpdateUserPassword

func (q *Queries) UpdateUserPassword(ctx context.Context, arg UpdateUserPasswordParams) error

func (*Queries) WithTx

func (q *Queries) WithTx(tx pgx.Tx) *Queries

type RecordFailedLoginParams

type RecordFailedLoginParams struct {
	ID             uuid.UUID
	Threshold      int32
	LockoutSeconds int32
}

type RecordFailedLoginRow

type RecordFailedLoginRow struct {
	FailedLoginCount int32
	LockedUntil      *time.Time
}

type RecordJobFailureParams

type RecordJobFailureParams struct {
	Job       string
	LastError *string
}

type ReserveAliasParams

type ReserveAliasParams struct {
	DomainID uuid.UUID
	Alias    string
	Reason   string
}

type ReservedAlias

type ReservedAlias struct {
	DomainID   uuid.UUID
	Alias      string
	Reason     string
	ReservedAt time.Time
}

type ResolveAliasForRedirectParams

type ResolveAliasForRedirectParams struct {
	DomainID uuid.UUID
	Alias    string
}

type ResolveAliasForRedirectRow

type ResolveAliasForRedirectRow struct {
	ID           uuid.UUID
	WorkspaceID  uuid.UUID
	DomainID     uuid.UUID
	Alias        string
	PrimaryUrl   string
	Status       string
	ExpiresAt    *time.Time
	PasswordHash *string
	MaxClicks    *int64
	OneTime      bool
	ForwardQuery bool
}

type ResolveDefaultDomainRow

type ResolveDefaultDomainRow struct {
	ID       uuid.UUID
	Hostname string
}

type ResolveDomainByHostnameRow

type ResolveDomainByHostnameRow struct {
	ID             uuid.UUID
	OrganizationID *uuid.UUID
	Hostname       string
}

type RestoreLinkParams

type RestoreLinkParams struct {
	ID          uuid.UUID
	WorkspaceID uuid.UUID
}

type RevokeAPIKeyParams

type RevokeAPIKeyParams struct {
	ID     uuid.UUID
	UserID uuid.UUID
}

type RevokeAllUserSessionsParams

type RevokeAllUserSessionsParams struct {
	UserID      uuid.UUID
	KeepSession *uuid.UUID
}

type Role

type Role struct {
	ID             uuid.UUID
	OrganizationID *uuid.UUID
	Slug           string
	Name           string
	Description    string
	IsBuiltin      bool
	Rank           int32
	CreatedAt      time.Time
}

type RolePermission

type RolePermission struct {
	RoleID       uuid.UUID
	PermissionID uuid.UUID
}

type RollupDimensionDailyParams

type RollupDimensionDailyParams struct {
	WindowStart time.Time
	WindowEnd   time.Time
}

type RollupLinkDailyParams

type RollupLinkDailyParams struct {
	WindowStart time.Time
	WindowEnd   time.Time
}

type RollupWorkspaceDailyParams

type RollupWorkspaceDailyParams struct {
	WindowStart time.Time
	WindowEnd   time.Time
}

type RoutingRule

type RoutingRule struct {
	ID            uuid.UUID
	LinkID        uuid.UUID
	WorkspaceID   uuid.UUID
	DestinationID *uuid.UUID
	Priority      int32
	Conditions    []byte
	Kind          string
	Enabled       bool
	CreatedAt     time.Time
	UpdatedAt     time.Time
}

type Session

type Session struct {
	ID         uuid.UUID
	UserID     uuid.UUID
	TokenHash  []byte
	IpPrefix   *string
	UserAgent  *string
	CreatedAt  time.Time
	LastSeenAt time.Time
	ExpiresAt  time.Time
	RevokedAt  *time.Time
}

type SetDefaultDomainRootRedirectRow

type SetDefaultDomainRootRedirectRow struct {
	ID              uuid.UUID
	Hostname        string
	RootRedirectUrl *string
}

type SetJobWatermarkParams

type SetJobWatermarkParams struct {
	Job       string
	Watermark *time.Time
}

type SetPrimaryDestinationParams

type SetPrimaryDestinationParams struct {
	ID                   uuid.UUID
	PrimaryDestinationID *uuid.UUID
}

type SoftDeleteLinkParams

type SoftDeleteLinkParams struct {
	RetentionDays int32
	ID            uuid.UUID
	WorkspaceID   uuid.UUID
}

type SoftDeleteLinkRow

type SoftDeleteLinkRow struct {
	ID         uuid.UUID
	Alias      string
	DomainID   uuid.UUID
	ClickCount int64
}

type Tag

type Tag struct {
	ID          uuid.UUID
	WorkspaceID uuid.UUID
	Name        string
	Color       string
	CreatedAt   time.Time
}

type TouchAPIKeysParams

type TouchAPIKeysParams struct {
	Ids    []uuid.UUID
	UsedAt []time.Time
}

type UpdateDestinationURLParams

type UpdateDestinationURLParams struct {
	LinkID      uuid.UUID
	WorkspaceID uuid.UUID
	Url         string
	UrlHost     string
}

type UpdateLinkParams

type UpdateLinkParams struct {
	Title        *string
	Description  *string
	ClearExpiry  bool
	ExpiresAt    *time.Time
	Alias        *string
	ForwardQuery *bool
	ID           uuid.UUID
	WorkspaceID  uuid.UUID
}

type UpdateUserPasswordParams

type UpdateUserPasswordParams struct {
	ID           uuid.UUID
	PasswordHash *string
}

type User

type User struct {
	ID               uuid.UUID
	Email            string
	EmailLower       *string
	EmailVerifiedAt  *time.Time
	Name             string
	PasswordHash     *string
	Status           string
	FailedLoginCount int32
	LockedUntil      *time.Time
	MfaSecret        *string
	MfaEnabledAt     *time.Time
	AnonymizedAt     *time.Time
	LastLoginAt      *time.Time
	CreatedAt        time.Time
	UpdatedAt        time.Time
	DeletedAt        *time.Time
}

type Visitor

type Visitor struct {
	VisitorHash []byte
	LinkID      uuid.UUID
	WorkspaceID uuid.UUID
	SeenOn      time.Time
	FirstSeenAt time.Time
	OccurredAt  time.Time
}

type Webhook

type Webhook struct {
	ID           uuid.UUID
	WorkspaceID  uuid.UUID
	Url          string
	Secret       []byte
	Subscription []byte
	Enabled      bool
	CreatedAt    time.Time
	UpdatedAt    time.Time
}

type WebhookDelivery

type WebhookDelivery struct {
	ID            uuid.UUID
	WebhookID     uuid.UUID
	Event         string
	Payload       []byte
	Status        string
	Attempts      int32
	ResponseCode  *int32
	NextAttemptAt *time.Time
	CreatedAt     time.Time
	CompletedAt   *time.Time
}

type Workspace

type Workspace struct {
	ID                     uuid.UUID
	OrganizationID         uuid.UUID
	Name                   string
	Slug                   string
	AnalyticsRetentionDays *int32
	CreatedAt              time.Time
	UpdatedAt              time.Time
	DeletedAt              *time.Time
}

type WorkspaceClickDaily

type WorkspaceClickDaily struct {
	WorkspaceID    uuid.UUID
	Day            time.Time
	Clicks         int64
	UniqueVisitors int64
	BotClicks      int64
	ActiveLinks    int64
	FinalizedAt    *time.Time
}

Jump to

Keyboard shortcuts

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