api

package
v0.1.44 Latest Latest
Warning

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

Go to latest
Published: Aug 31, 2026 License: MIT Imports: 60 Imported by: 0

Documentation

Overview

Package api builds Shrike's HTTP API.

The API, authentication, and image routes share one Huma registry and one OpenAPI document on the main eve-kill.com origin. API operations live below /api, images below /images, and browser OAuth below /auth.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func Site

func Site(opts Options) http.Handler

Site constructs the main-origin handler.

func WS

func WS(Options) http.Handler

WS is a lightweight placeholder used by ingress-focused tests. Production supplies the real same-origin /ws handler from internal/websocket.

Types

type Announcement

type Announcement struct {
	ID        int64     `json:"id"`
	Tier      int16     `json:"tier" enum:"1,2,3"`
	Title     string    `json:"title"`
	BodyMD    string    `json:"body_md"`
	BodyHTML  string    `json:"body_html"`
	Color     string    `json:"color" enum:"info,warning,danger,success"`
	Icon      *string   `json:"icon"`
	LinkURL   *string   `json:"link_url"`
	LinkLabel *string   `json:"link_label"`
	StartsAt  time.Time `json:"starts_at"`
	ExpiresAt time.Time `json:"expires_at"`
	CreatedAt time.Time `json:"created_at"`
}

Announcement is the stable public shape shared by editorial announcements stored in Postgres and ephemeral ticker announcements stored in Valkey.

Database-only bookkeeping such as created_by, updated_at, and archived_at is intentionally not part of the public contract. Ephemeral announcements never had those fields, and the frontend only consumes the presentation fields.

type AnnouncementDismissalResponse

type AnnouncementDismissalResponse struct {
	OK bool `json:"ok"`
}

type AnnouncementsResponse

type AnnouncementsResponse struct {
	Announcements []Announcement `json:"announcements" nullable:"false"`
}

AnnouncementsResponse is the canonical frontend and public API payload. Defining it here, rather than importing a database row in Nuxt, makes the generated Huma contract authoritative for both persisted and live tickers.

type AuthOptions

type AuthOptions struct {
	ClientID     string
	ClientSecret string
	StateSecret  string
	CallbackURL  string
	UserAgent    string
	Production   bool
	HTTPClient   *http.Client
	// contains filtered or unexported fields
}

AuthOptions configures EVE SSO and the browser session cookie.

StateSecret signs browser OAuth state and may be rotated independently from EVE's client secret. An empty value falls back to ClientSecret for transition compatibility. HTTPClient is injected for deterministic OAuth tests; nil uses a bounded production client.

type Database

type Database interface {
	Ping(context.Context) error
	Query(context.Context, string, ...any) (pgx.Rows, error)
	QueryRow(context.Context, string, ...any) pgx.Row
}

Database is the read-only subset of pgxpool.Pool used by API handlers. Keeping it as an interface makes the HTTP contract testable without a live Postgres server; *pgxpool.Pool satisfies it directly in production.

type DismissedAnnouncementIDsResponse

type DismissedAnnouncementIDsResponse struct {
	DismissedIDs []int64 `json:"dismissedIds" nullable:"false"`
}

type DomainAssetStorage

type DomainAssetStorage interface {
	Put(context.Context, string, []byte, string) error
	Get(context.Context, string) ([]byte, error)
	Delete(context.Context, string) error
}

DomainAssetStorage is the object-store boundary used by custom-domain images. The API owns key generation and validates every key read from the database before calling this interface; an implementation should treat a nil body and nil error from Get as "not found".

Keeping this boundary small lets production use its S3-compatible B2 bucket while tests use an in-memory store without coupling the API to an SDK.

type FeedManager

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

FeedManager fans Redis notifications out to SSE clients. Poll/catch-up reads remain in Postgres, which is the durable sequence source.

func NewFeedManager

func NewFeedManager(db Database, client *redis.Client) *FeedManager

func (*FeedManager) Start

func (m *FeedManager) Start(ctx context.Context)

type GraphDatabase

type GraphDatabase interface {
	Read(context.Context, string, map[string]any) ([]map[string]any, error)
}

type MutationDatabase

type MutationDatabase interface {
	Database
	Exec(context.Context, string, ...any) (pgconn.CommandTag, error)
	Begin(context.Context) (pgx.Tx, error)
}

MutationDatabase is the database contract for API-owned state.

Read-only handlers deliberately depend on the smaller Database interface. The production *pgxpool.Pool satisfies both, while focused tests do not need mutation methods unless the operation they exercise actually writes.

type Options

type Options struct {
	Version string
	Commit  string
	DB      Database
	// Primary is used for mutations and reads that require read-after-write
	// consistency. PrimaryPool is the same connection exposed concretely for
	// River, whose client requires *pgxpool.Pool.
	Primary     MutationDatabase
	PrimaryPool *pgxpool.Pool
	Graph       GraphDatabase
	Feed        *FeedManager
	Cache       *redis.Client
	// ResponseCacheBytes bounds the process-local L1. Cache remains the shared
	// Valkey client used as L2 and by non-response coordination features.
	ResponseCacheBytes int64

	Auth         AuthOptions
	DomainAssets DomainAssetStorage
	Images       *images.Service
	RequestGuard *RequestGuard
	OpenAIAPIKey string
	KlipyAPIKey  string
	// contains filtered or unexported fields
}

Options carries what the surfaces need from the process.

Deliberately not the whole *config.Config: a surface that wants a database pool should be handed a pool, so the dependency shows up in the signature rather than being fished out of a bag at request time.

type Principal

type Principal struct {
	CharacterID            int32
	CharacterName          string
	IsAdmin                bool
	CorporationID          *int32
	CorporationName        *string
	AllianceID             *int32
	AllianceName           *string
	LastSeenNotificationID int64
}

Principal is the small request-time identity. It deliberately excludes user_config and account-detail fields: authorization should be one indexed lookup, not a settings-page query paid by every authenticated request.

type RequestGuard

type RequestGuard struct{}

RequestGuard enforces the public HTTP etiquette documented by the API.

It deliberately wraps /api only. Edge-layer infrastructure owns traffic shaping and rate limiting; the origin only requires clients to identify themselves so operators can contact a misbehaving integration.

func NewRequestGuard

func NewRequestGuard() *RequestGuard

NewRequestGuard returns the production API request policy.

func (*RequestGuard) Wrap

func (g *RequestGuard) Wrap(next http.Handler) http.Handler

Wrap applies the guard without changing handlers for the other same-origin namespaces.

type ResponseCache

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

ResponseCache is the API response cache.

L1 is a byte-bounded LRU in this process. L2 is the shared Valkey. A read checks L1, promotes an L2 hit into L1 for the L2 entry's remaining lifetime, and otherwise lets the handler load the source. A source response is written through to both tiers.

func NewResponseCache

func NewResponseCache(client *redis.Client, maxBytes int64) *ResponseCache

NewResponseCache builds an L1 cache over a shared Valkey L2.

maxBytes is a hard payload ceiling for this process. Zero disables L1 but leaves L2 enabled. A nil client disables L2 but still permits a local cache, which is useful during a temporary Valkey outage and in focused tests.

func (*ResponseCache) DeleteMatching

func (c *ResponseCache) DeleteMatching(ctx context.Context, pattern string)

DeleteMatching invalidates both tiers. The pattern uses the Redis glob syntax needed by the current callers: '*' and '?' wildcards.

func (*ResponseCache) Load

func (c *ResponseCache) Load(
	ctx context.Context,
	key string,
) (cachedResponse, bool)

func (*ResponseCache) LoadOnce added in v0.1.7

func (c *ResponseCache) LoadOnce(
	ctx context.Context,
	key string,
	load func() (any, error),
) (any, error, bool)

LoadOnce collapses concurrent cache misses for the same key in this process. The shared Valkey cache handles reuse across replicas; this guard prevents a burst inside one replica from running the same expensive database query for every waiting request before the first response reaches Valkey.

func (*ResponseCache) LoadState added in v0.1.9

func (c *ResponseCache) LoadState(
	ctx context.Context,
	key string,
) (cachedResponse, bool, bool)

LoadState returns fresh and stale entries while their retention TTL lasts.

func (*ResponseCache) Refresh added in v0.1.9

func (c *ResponseCache) Refresh(key string, load func() (any, error))

Refresh starts one detached load for a key in this process. Callers use it after serving a stale response, so the client request never owns the refresh.

func (*ResponseCache) Store

func (c *ResponseCache) Store(
	ctx context.Context,
	key string,
	entry cachedResponse,
	ttl time.Duration,
)

func (*ResponseCache) StoreStale added in v0.1.9

func (c *ResponseCache) StoreStale(
	ctx context.Context,
	key string,
	entry cachedResponse,
	freshTTL time.Duration,
	staleTTL time.Duration,
)

StoreStale retains an entry after its fresh TTL so a request can serve it while a detached refresh replaces it.

type Service

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

Service is one API registry exposed through the main-site transport.

func New

func New(opts Options) *Service

New builds the shared API once.

func (*Service) OpenAPI

func (s *Service) OpenAPI() *huma.OpenAPI

OpenAPI returns the generated contract owned by this service.

The document is complete immediately after New returns: route registration never invokes a handler or touches its database dependencies, which lets the CLI emit the frontend contract without starting the server.

func (*Service) Site

func (s *Service) Site() http.Handler

Site serves /api, /auth, /images, /health, and /ready on the frontend origin.

type SiteConfigurationResponse

type SiteConfigurationResponse struct {
	Domain       *SiteDomainConfiguration `json:"domain"`
	IsDomainHost bool                     `json:"isDomainHost"`
}

SiteConfigurationResponse is the host-aware bootstrap consumed by Nuxt. Keeping it as a real Huma response makes the generated TypeScript contract authoritative for both SSR and browser rendering.

type SiteDomainConfiguration

type SiteDomainConfiguration struct {
	ID                int32                  `json:"id"`
	Subdomain         string                 `json:"subdomain"`
	CustomHostname    *string                `json:"customHostname"`
	UserID            int32                  `json:"userId"`
	Entities          []SiteDomainEntity     `json:"entities" nullable:"false"`
	EntityIDs         SiteDomainEntityIDs    `json:"entityIds"`
	Theme             SiteDomainTheme        `json:"theme"`
	NavbarLinks       []SiteDomainNavbarLink `json:"navbarLinks" nullable:"false"`
	Widgets           SiteDomainWidgets      `json:"widgets"`
	SiteName          *string                `json:"siteName"`
	SiteDescription   *string                `json:"siteDescription"`
	CampaignPolicy    int16                  `json:"campaignPolicy" enum:"0,1"`
	CampaignIDs       []string               `json:"campaignIds" nullable:"false"`
	PublicCampaignIDs []string               `json:"publicCampaignIds" nullable:"false"`
	Backgrounds       []string               `json:"backgrounds" nullable:"false"`
}

type SiteDomainEntity

type SiteDomainEntity struct {
	Type string `json:"type" enum:"character,corporation,alliance"`
	ID   int32  `json:"id"`
	Name string `json:"name"`
}

type SiteDomainEntityIDs

type SiteDomainEntityIDs struct {
	CharacterIDs   []int32 `json:"characterIds" nullable:"false"`
	CorporationIDs []int32 `json:"corporationIds" nullable:"false"`
	AllianceIDs    []int32 `json:"allianceIds" nullable:"false"`
}

type SiteDomainNavbarGroup

type SiteDomainNavbarGroup struct {
	Label *string                `json:"label,omitempty"`
	Items []SiteDomainNavbarItem `json:"items" nullable:"false"`
}

type SiteDomainNavbarItem

type SiteDomainNavbarItem struct {
	Label    string  `json:"label"`
	Href     string  `json:"href"`
	External *bool   `json:"external,omitempty"`
	Icon     *string `json:"icon,omitempty"`
}
type SiteDomainNavbarLink struct {
	Label    string                  `json:"label"`
	Href     string                  `json:"href"`
	External *bool                   `json:"external,omitempty"`
	Icon     *string                 `json:"icon,omitempty"`
	Children []SiteDomainNavbarGroup `json:"children,omitempty" nullable:"false"`
}

type SiteDomainTheme

type SiteDomainTheme struct {
	PrimaryColor            *string           `json:"primaryColor,omitempty"`
	AccentColor             *string           `json:"accentColor,omitempty"`
	BackgroundColor         *string           `json:"bgColor,omitempty"`
	TextColor               *string           `json:"textColor,omitempty"`
	BannerURL               *string           `json:"bannerUrl,omitempty"`
	LogoURL                 *string           `json:"logoUrl,omitempty"`
	ShowLogoInBanner        *bool             `json:"showLogoInBanner,omitempty"`
	ShowNameInBanner        *bool             `json:"showNameInBanner,omitempty"`
	ShowDescriptionInBanner *bool             `json:"showDescriptionInBanner,omitempty"`
	TransparentBanner       *bool             `json:"transparentBanner,omitempty"`
	ContentOpacity          *float64          `json:"contentOpacity,omitempty"`
	DefaultThemePreset      *string           `json:"defaultThemePreset,omitempty"`
	DefaultThemeOverrides   map[string]string `json:"defaultThemeOverrides,omitempty"`
}

type SiteDomainWidget

type SiteDomainWidget struct {
	Type         string  `` /* 145-byte string literal not displayed */
	Enabled      bool    `json:"enabled"`
	Content      *string `json:"content,omitempty"`
	KilllistType *string `json:"killlistType,omitempty"`
}

type SiteDomainWidgets

type SiteDomainWidgets struct {
	Top         []SiteDomainWidget `json:"top" nullable:"false"`
	Left        []SiteDomainWidget `json:"left" nullable:"false"`
	Right       []SiteDomainWidget `json:"right" nullable:"false"`
	ColumnRatio *string            `json:"columnRatio,omitempty"`
}

Source Files

Jump to

Keyboard shortcuts

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