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 ¶
- func Site(opts Options) http.Handler
- func WS(Options) http.Handler
- type Announcement
- type AnnouncementDismissalResponse
- type AnnouncementsResponse
- type AuthOptions
- type Database
- type DismissedAnnouncementIDsResponse
- type DomainAssetStorage
- type FeedManager
- type GraphDatabase
- type MutationDatabase
- type Options
- type Principal
- type RequestGuard
- type ResponseCache
- func (c *ResponseCache) DeleteMatching(ctx context.Context, pattern string)
- func (c *ResponseCache) Load(ctx context.Context, key string) (cachedResponse, bool)
- func (c *ResponseCache) LoadOnce(ctx context.Context, key string, load func() (any, error)) (any, error, bool)
- func (c *ResponseCache) LoadState(ctx context.Context, key string) (cachedResponse, bool, bool)
- func (c *ResponseCache) Refresh(key string, load func() (any, error))
- func (c *ResponseCache) Store(ctx context.Context, key string, entry cachedResponse, ttl time.Duration)
- func (c *ResponseCache) StoreStale(ctx context.Context, key string, entry cachedResponse, freshTTL time.Duration, ...)
- type Service
- type SiteConfigurationResponse
- type SiteDomainConfiguration
- type SiteDomainEntity
- type SiteDomainEntityIDs
- type SiteDomainNavbarGroup
- type SiteDomainNavbarItem
- type SiteDomainNavbarLink
- type SiteDomainTheme
- type SiteDomainWidget
- type SiteDomainWidgets
Constants ¶
This section is empty.
Variables ¶
This section is empty.
Functions ¶
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 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.
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
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) 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.
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"`
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 SiteDomainEntityIDs ¶
type SiteDomainNavbarGroup ¶
type SiteDomainNavbarGroup struct {
}
type SiteDomainNavbarItem ¶
type SiteDomainNavbarItem struct {
}
type SiteDomainNavbarLink ¶
type SiteDomainNavbarLink struct {
}
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 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
¶
- account.go
- account_announcements.go
- account_handlers.go
- account_routes.go
- account_store.go
- account_values.go
- admin.go
- admin_esi.go
- admin_esi_logs.go
- admin_overview.go
- admin_users.go
- advanced_killlist.go
- analyze.go
- announcements_admin.go
- api.go
- auth.go
- auth_routes.go
- auth_store.go
- backgrounds.go
- batch_stats.go
- battle_analysis.go
- battle_generator.go
- battles.go
- blog.go
- cache.go
- campaign_access.go
- campaign_killmails.go
- campaign_mutations.go
- campaigns.go
- character_intel.go
- character_intel_batch.go
- coalitions.go
- comments.go
- comments_render.go
- comments_store.go
- conflict_battles.go
- conflicts.go
- domain_killboard.go
- domains.go
- domains_assets.go
- domains_site.go
- domains_store.go
- domains_theme.go
- entities.go
- entity_killmails.go
- entity_pages.go
- entity_pages_intel.go
- entity_pages_lists.go
- entity_pages_stats.go
- entity_stats.go
- entity_top.go
- faction_war_dashboard.go
- feed.go
- fitting_catalog.go
- fitting_roles.go
- fitting_search.go
- fitting_ship.go
- fitting_trends.go
- fittings.go
- global_stats.go
- global_stats_palettes.go
- global_stats_realtime.go
- graph_api.go
- history.go
- killboard.go
- killmail_details.go
- killmail_search.go
- killmail_submission.go
- killmails.go
- legacy.go
- legacy_archive.go
- legacy_body.go
- legacy_scalars.go
- location.go
- map.go
- market.go
- matchup.go
- moderation.go
- openapi_body_types.go
- openapi_descriptions.go
- openapi_query.go
- openapi_query_operations.go
- openapi_tags.go
- openapi_taxonomy.go
- options.go
- request_guard.go
- resolve.go
- response_cache.go
- response_schemas.go
- response_schemas_application.go
- response_schemas_campaign.go
- response_schemas_content.go
- route_cache.go
- routes.go
- scans.go
- schema.go
- sde.go
- search.go
- ships.go
- sitemap.go
- stats_rankings.go
- universe.go
- universe_killlists.go
- wallet.go
- wallet_accounting.go
- wallet_authorization.go
- wars.go