api

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Jun 14, 2026 License: Apache-2.0 Imports: 15 Imported by: 0

Documentation

Overview

Package api is a typed client for the spoo.me HTTP API.

Index

Constants

View Source
const MaxRangeDays = 90

MaxRangeDays is the widest window the stats endpoint accepts; without explicit dates it defaults to only the LAST 7 DAYS, so clients that want "all recent activity" should request this window explicitly.

Variables

This section is empty.

Functions

func ParseExpiry

func ParseExpiry(raw string, now time.Time) (string, error)

ParseExpiry normalizes user expiry input to RFC 3339, which the backend always accepts. Durations ("30m", "72h") are relative to now; bare epoch seconds are converted; anything else passes through as ISO 8601. Empty input yields an empty string (no expiry change).

Types

type APIError

type APIError struct {
	Status  int    `json:"-"`
	Code    string `json:"code"`
	Message string `json:"error"`
	Detail  string `json:"detail"`
}

APIError mirrors the backend's error envelope {error, code, detail}.

func (*APIError) Error

func (e *APIError) Error() string

type APIKey

type APIKey struct {
	ID          string   `json:"id"`
	Name        string   `json:"name"`
	Description string   `json:"description"`
	Scopes      []string `json:"scopes"`
	CreatedAt   int64    `json:"created_at"`
	ExpiresAt   int64    `json:"expires_at"`
	Revoked     bool     `json:"revoked"`
	TokenPrefix string   `json:"token_prefix"`
	Token       string   `json:"token,omitempty"` // full token, present only on create
}

type AliasCheck

type AliasCheck struct {
	Available bool   `json:"available"`
	Reason    string `json:"reason"`
}

type Client

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

func New

func New(base string, store *auth.Store) *Client

func (*Client) CheckAlias

func (c *Client) CheckAlias(ctx context.Context, alias, domain string) (*AliasCheck, error)

func (*Client) CreateDomain

func (c *Client) CreateDomain(ctx context.Context, fqdn string) (*Domain, error)

func (*Client) CreateKey

func (c *Client) CreateKey(ctx context.Context, req CreateKeyRequest) (*APIKey, error)

CreateKey mints a new API key. Requires a device-flow (JWT) session; the backend refuses key creation authenticated by another API key.

func (*Client) DeleteKey

func (c *Client) DeleteKey(ctx context.Context, id string, revoke bool) error

DeleteKey removes a key. With revoke=true it is soft-revoked (kept in the list, unusable); with revoke=false the record is hard-deleted.

func (*Client) DeleteURL

func (c *Client) DeleteURL(ctx context.Context, id string) error

func (*Client) ExchangeDeviceCode

func (c *Client) ExchangeDeviceCode(ctx context.Context, code string) (*DeviceTokens, error)

ExchangeDeviceCode trades a one-time device-auth code for a JWT pair. The code is the credential — no prior auth is required.

func (*Client) Export

func (c *Client) Export(ctx context.Context, q StatsQuery, format string) (string, []byte, error)

Export downloads stats in the given format (json, csv, xlsx, xml). Returns the server-suggested filename and the file contents. csv arrives as a ZIP archive (one CSV per dimension).

func (*Client) Inspect

func (c *Client) Inspect(ctx context.Context, shortCode string) (*InspectResult, error)

Inspect resolves where a short code points without recording a click: the backend skips click tracking on HEAD requests, and redirects are not followed so the destination never gets hit either.

func (*Client) ListDomains

func (c *Client) ListDomains(ctx context.Context) (*DomainPage, error)

func (*Client) ListKeys

func (c *Client) ListKeys(ctx context.Context) ([]APIKey, error)

func (*Client) ListURLs

func (c *Client) ListURLs(ctx context.Context, opts ListURLsOptions) (*URLPage, error)

func (*Client) Me

func (c *Client) Me(ctx context.Context) (*User, error)

func (*Client) RevokeDomain

func (c *Client) RevokeDomain(ctx context.Context, id string, cascade bool) (*DomainDeleteResult, error)

RevokeDomain stops serving the domain. With cascade, its URLs are deleted too; without, they stay in the database but stop resolving.

func (*Client) Shorten

func (c *Client) Shorten(ctx context.Context, req ShortenRequest) (*ShortURL, error)

func (*Client) Stats

func (c *Client) Stats(ctx context.Context, q StatsQuery) (*StatsResponse, error)

func (*Client) UpdateDomain

func (c *Client) UpdateDomain(ctx context.Context, id string, fields map[string]any) (*Domain, error)

UpdateDomain patches a domain's per-domain routing config. fields holds only the keys to change (root_redirect, not_found_redirect, custom_robots_txt); a nil value clears that field, an omitted key leaves it untouched — the backend distinguishes via model_fields_set.

func (*Client) UpdateURL

func (c *Client) UpdateURL(ctx context.Context, id string, fields map[string]any) (*UpdatedURL, error)

UpdateURL patches the given fields (snake_case keys per the API: long_url, alias, password, max_clicks, expire_after, status, ...).

func (*Client) VerifyDomain

func (c *Client) VerifyDomain(ctx context.Context, id string) (*Domain, error)

type CreateKeyRequest

type CreateKeyRequest struct {
	Name        string   `json:"name"`
	Description string   `json:"description,omitempty"`
	Scopes      []string `json:"scopes"`
	ExpiresAt   string   `json:"expires_at,omitempty"` // ISO 8601 or epoch seconds
}

type DNSRecord

type DNSRecord struct {
	Type  string `json:"type"`
	Name  string `json:"name"`
	Value string `json:"value"`
}

type DeviceTokens

type DeviceTokens struct {
	AccessToken  string `json:"access_token"`
	RefreshToken string `json:"refresh_token"`
	User         User   `json:"user"`
}

type Domain

type Domain struct {
	ID               string      `json:"id"`
	FQDN             string      `json:"fqdn"`
	Status           string      `json:"status"` // PENDING | ACTIVE | REVOKED
	CreatedAt        string      `json:"created_at"`
	VerifiedAt       string      `json:"verified_at"`
	DNSRecords       []DNSRecord `json:"dns_records"`
	RootRedirect     string      `json:"root_redirect"`
	NotFoundRedirect string      `json:"not_found_redirect"`
}

type DomainDeleteResult

type DomainDeleteResult struct {
	ID          string `json:"id"`
	FQDN        string `json:"fqdn"`
	Cascade     bool   `json:"cascade"`
	URLsDeleted int    `json:"urls_deleted"`
}

type DomainPage

type DomainPage struct {
	Items    []Domain `json:"items"`
	Page     int      `json:"page"`
	PageSize int      `json:"pageSize"`
	Total    int      `json:"total"`
	HasNext  bool     `json:"hasNext"`
}

type InspectResult

type InspectResult struct {
	ShortURL    string `json:"short_url"`
	Status      int    `json:"status"`
	Destination string `json:"destination,omitempty"`
}

type ListURLsOptions

type ListURLsOptions struct {
	Page      int
	PageSize  int
	SortBy    string // created_at | last_click | total_clicks
	SortOrder string // ascending | descending
	Search    string
	Status    string // ACTIVE | INACTIVE | BLOCKED | EXPIRED
	Domain    string
}

type MetricPoint

type MetricPoint struct {
	Label string
	Value float64
}

type ShortURL

type ShortURL struct {
	ShortURL  string `json:"short_url"`
	Alias     string `json:"alias"`
	LongURL   string `json:"long_url"`
	CreatedAt int64  `json:"created_at"`
	Status    string `json:"status"`
}

ShortURL mirrors UrlResponse (POST /api/v1/shorten); created_at is Unix seconds in this response.

type ShortenRequest

type ShortenRequest struct {
	LongURL      string `json:"long_url"`
	Alias        string `json:"alias,omitempty"`
	Password     string `json:"password,omitempty"`
	BlockBots    bool   `json:"block_bots,omitempty"`
	MaxClicks    int    `json:"max_clicks,omitempty"`
	ExpireAfter  string `json:"expire_after,omitempty"` // ISO 8601 or epoch seconds
	PrivateStats bool   `json:"private_stats,omitempty"`
	Domain       string `json:"domain,omitempty"`
}

type StatsQuery

type StatsQuery struct {
	ShortCode string
	Scope     string // "all" (authed, optional code) or "anon" (code required)
	StartDate string
	EndDate   string
	GroupBy   []string // time, browser, os, country, city, referrer, short_code
	Timezone  string   // IANA name
	// Filters narrows results server-side; keys are the filterable
	// dimensions (browser, os, country, city, referrer, short_code).
	Filters map[string][]string
}

type StatsResponse

type StatsResponse struct {
	Scope           string                      `json:"scope"`
	ShortCode       string                      `json:"short_code"`
	Summary         StatsSummary                `json:"summary"`
	TimeRange       StatsTimeRange              `json:"time_range"`
	Metrics         map[string][]map[string]any `json:"metrics"`
	ComputedMetrics map[string]float64          `json:"computed_metrics"`
	GeneratedAt     string                      `json:"generated_at"`
}

StatsResponse keeps Metrics loosely typed: keys are dynamic ("clicks_by_browser", "unique_clicks_by_time", ...) and each point carries its dimension label under the dimension's own name.

func (*StatsResponse) Points

func (r *StatsResponse) Points(dimension, metric string) []MetricPoint

Points extracts (label, value) pairs from the loosely typed metrics payload for one dimension/metric pair, e.g. ("browser", "clicks") → the "clicks_by_browser" series with labels from the "browser" key.

type StatsSummary

type StatsSummary struct {
	TotalClicks        int     `json:"total_clicks"`
	UniqueClicks       int     `json:"unique_clicks"`
	FirstClick         string  `json:"first_click"`
	LastClick          string  `json:"last_click"`
	AvgRedirectionTime float64 `json:"avg_redirection_time"`
}

type StatsTimeRange

type StatsTimeRange struct {
	StartDate string `json:"start_date"`
	EndDate   string `json:"end_date"`
}

type URLItem

type URLItem struct {
	ID           string `json:"id"`
	Alias        string `json:"alias"`
	LongURL      string `json:"long_url"`
	CreatedAt    string `json:"created_at"`
	LastClick    string `json:"last_click"`
	TotalClicks  int    `json:"total_clicks"`
	Status       string `json:"status"`
	PasswordSet  bool   `json:"password_set"`
	MaxClicks    *int   `json:"max_clicks"`
	ExpireAfter  *int64 `json:"expire_after"` // Unix seconds, null when unset
	PrivateStats bool   `json:"private_stats"`
	BlockBots    bool   `json:"block_bots"`
	Domain       string `json:"domain"`
}

URLItem is a row from GET /api/v1/urls. The envelope is camelCase (pageSize, hasNext) but items are snake_case; expire_after is a Unix timestamp — see UrlListItem in the backend's schemas/dto/responses/url.py.

type URLPage

type URLPage struct {
	Items    []URLItem `json:"items"`
	Page     int       `json:"page"`
	PageSize int       `json:"pageSize"`
	Total    int       `json:"total"`
	HasNext  bool      `json:"hasNext"`
}

type UpdatedURL

type UpdatedURL struct {
	ID           string `json:"id"`
	Alias        string `json:"alias"`
	LongURL      string `json:"long_url"`
	Status       string `json:"status"`
	PasswordSet  bool   `json:"password_set"`
	MaxClicks    *int   `json:"max_clicks"`
	ExpireAfter  *int64 `json:"expire_after"`
	BlockBots    bool   `json:"block_bots"`
	PrivateStats bool   `json:"private_stats"`
	Domain       string `json:"domain"`
	UpdatedAt    int64  `json:"updated_at"`
}

UpdatedURL mirrors UpdateUrlResponse — unlike the shorten response it carries no short_url, and timestamps are Unix seconds.

type User

type User struct {
	ID            string `json:"id"`
	Email         string `json:"email"`
	EmailVerified bool   `json:"email_verified"`
	Name          string `json:"name"`
	Plan          string `json:"plan"`
}

Jump to

Keyboard shortcuts

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