domain

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: 3 Imported by: 0

Documentation

Overview

Package domain holds the types the services exchange.

These are deliberately separate from the sqlc-generated structs. Generated types describe table shape, change whenever a column does, and carry driver concerns; domain types describe what the product means and are what handlers and templates see. The mapping between them lives in the service layer, so a column rename does not ripple into the API.

Index

Constants

This section is empty.

Variables

View Source
var (
	ErrNotFound     = errors.New("not found")
	ErrConflict     = errors.New("conflict")
	ErrForbidden    = errors.New("forbidden")
	ErrUnauthorized = errors.New("unauthorized")
	ErrValidation   = errors.New("validation failed")
	// ErrNotImplemented marks Phase 2 fields that exist in the schema and are
	// rejected with a clear message rather than silently ignored. Silently
	// accepting a field that does nothing is worse than refusing it.
	ErrNotImplemented = errors.New("not implemented in this version")
)

Sentinel errors. Handlers map these to status codes in exactly one place, so a service can signal "not found" without knowing about HTTP.

Functions

This section is empty.

Types

type FieldError

type FieldError struct {
	Field   string `json:"field"`
	Code    string `json:"code"`
	Message string `json:"message"`
}

FieldError is a per-field validation failure, so a form can highlight the offending input rather than showing one opaque message.

type Link struct {
	ID          uuid.UUID  `json:"id"`
	WorkspaceID uuid.UUID  `json:"workspace_id"`
	Alias       string     `json:"alias"`
	ShortURL    string     `json:"short_url"`
	URL         string     `json:"url"`
	Title       string     `json:"title,omitempty"`
	Description string     `json:"description,omitempty"`
	Status      LinkStatus `json:"status"`
	Tags        []Tag      `json:"tags"`

	// ForwardQuery merges the incoming query string into the destination on
	// redirect. Off by default: destinations were configured deliberately, and
	// most callers do not expect ?utm_source to reach them.
	ForwardQuery bool `json:"forward_query"`

	ExpiresAt *time.Time `json:"expires_at,omitempty"`

	// Approximate: updated in batches with the click events, so it lags by up
	// to one flush interval and can lose a batch on an unclean shutdown.
	// Nothing that must be exact may read it.
	ClickCount  int64      `json:"click_count"`
	LastClickAt *time.Time `json:"last_click_at,omitempty"`

	CreatedAt  time.Time  `json:"created_at"`
	UpdatedAt  time.Time  `json:"updated_at"`
	ArchivedAt *time.Time `json:"archived_at,omitempty"`
}

Link is a short link as the product understands it.

type LinkFilter

type LinkFilter struct {
	WorkspaceID  uuid.UUID
	Search       string
	TagIDs       []uuid.UUID
	Status       LinkStatus
	Sort         LinkSort
	Cursor       string
	Limit        int32
	IncludeTotal bool
}

LinkFilter describes a link query.

type LinkSort

type LinkSort string

LinkSort is the ordering for a link list.

const (
	SortNewest     LinkSort = "newest"
	SortOldest     LinkSort = "oldest"
	SortMostClicks LinkSort = "clicks"
)

type LinkStatus

type LinkStatus string

LinkStatus is the lifecycle state of a link.

const (
	StatusActive   LinkStatus = "active"
	StatusArchived LinkStatus = "archived"
	StatusExpired  LinkStatus = "expired"
	StatusDisabled LinkStatus = "disabled"
)

func EffectiveStatus

func EffectiveStatus(stored LinkStatus, expiresAt *time.Time, now time.Time) LinkStatus

EffectiveStatus is the status a link presents to the outside world.

Expiry is a timestamp, never a stored status. Nothing writes 'expired' to the column, because a written status is stale from the moment the expiry passes until whatever job notices — and that window is exactly when somebody is looking at the link asking why it stopped working.

The redirect path has always derived it this way, which is how an expired link came to answer 410 while every management surface still called it active. The rule matches Snapshot.Decide, including that expiry outranks an archived status: if the two disagreed, this would be the same bug in a smaller form.

type Page

type Page[T any] struct {
	Items      []T    `json:"items"`
	NextCursor string `json:"next_cursor,omitempty"`
	HasMore    bool   `json:"has_more"`
	Total      *int64 `json:"total,omitempty"`
}

Page is a keyset-paginated result.

Cursor rather than offset: offset pagination re-scans skipped rows and, more importantly, silently duplicates or drops entries when rows are inserted while a user is paging. Total is optional because counting costs a scan the common page load should not pay for.

type Tag

type Tag struct {
	ID        uuid.UUID `json:"id"`
	Name      string    `json:"name"`
	Color     string    `json:"color,omitempty"`
	LinkCount int64     `json:"link_count,omitempty"`
}

Tag groups links within a workspace.

type ValidationErrors

type ValidationErrors []FieldError

ValidationErrors is a collection of field errors. Like config validation, every problem is reported at once rather than one per round trip.

func (ValidationErrors) Error

func (v ValidationErrors) Error() string

func (ValidationErrors) Is

func (v ValidationErrors) Is(target error) bool

func (ValidationErrors) Or

func (v ValidationErrors) Or(err error) error

Jump to

Keyboard shortcuts

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