Documentation
¶
Index ¶
- Constants
- func AuthMiddleware(store model.DeviceStore, logger *slog.Logger) func(http.Handler) http.Handler
- func ContextWithDeviceID(ctx context.Context, deviceID string) context.Context
- func DeviceIDFromContext(ctx context.Context) string
- func IdempotencyMiddleware(cfg IdempotencyConfig) func(http.Handler) http.Handler
- func OpenAPISpec() []byte
- func RequestIDFromContext(ctx context.Context) string
- func WriteError(w http.ResponseWriter, status int, code, message string)
- type Config
- type ErrorBody
- type ErrorResponse
- type IdempotencyConfig
- type OwnerInfo
- type Server
Constants ¶
const ( ErrorCodeBadRequest = "bad_request" ErrorCodeForbidden = "forbidden" ErrorCodeNotFound = "not_found" ErrorCodeMethodNotAllowed = "method_not_allowed" ErrorCodeConflict = "conflict" ErrorCodeUnprocessable = "unprocessable_entity" ErrorCodeRateLimited = "rate_limited" ErrorCodeInternal = "internal" ErrorCodeTimeout = "timeout" ErrorCodeAliasInvalid = "alias_invalid" ErrorCodeAliasTaken = "alias_taken" ErrorCodeAliasConflictsWithName = "alias_conflicts_with_name" ErrorCodeAliasLimitReached = "alias_limit_reached" ErrorCodeAliasForbiddenForInbox = "alias_forbidden_for_inbox" ErrorCodeInvalidAudio = "invalid_audio" ErrorCodeInvalidMetadata = "invalid_metadata" ErrorCodeDurationTooLong = "duration_too_long" ErrorCodeProjectNotFound = "project_not_found" ErrorCodeAudioTooLarge = "audio_too_large" ErrorCodeUnsupportedMedia = "unsupported_media_type" ErrorCodeTranscriberDisabled = "transcriber_disabled" )
API error codes. String values are stable and sent to the client — they must not be changed without updating the OpenAPI specification.
const IdempotencyHeader = "Idempotency-Key"
IdempotencyHeader is the HTTP header name the client uses to request cached replay of a repeated request.
const Version = "0.2.0"
Version is the API version returned by GET /v1/me. Bumped manually on breaking changes to the OpenAPI contract.
Variables ¶
This section is empty.
Functions ¶
func AuthMiddleware ¶
AuthMiddleware validates the Authorization: Bearer <token> header, compares SHA256(token) against the DeviceStore, and stores device_id in the request context. Returns 401 with a uniform JSON body for unauthenticated requests. Updating last_seen_at is best-effort: errors are logged but the request proceeds.
func ContextWithDeviceID ¶
ContextWithDeviceID returns a context with device_id set. Useful in tests that do not want to set up the full AuthMiddleware.
func DeviceIDFromContext ¶
DeviceIDFromContext returns the device ID stored by AuthMiddleware. Returns an empty string if the middleware is not applied or the request is not authorised.
func IdempotencyMiddleware ¶
func IdempotencyMiddleware(cfg IdempotencyConfig) func(http.Handler) http.Handler
IdempotencyMiddleware caches responses for mutating HTTP requests keyed on `device_id + Idempotency-Key`. A repeated request with the same key gets the previously stored status, headers, and body — the real handler is not called. Transparent for GET/HEAD/OPTIONS and requests without the header. Responses with 5xx status are not cached: the client must be able to retry after a transient error is resolved.
func OpenAPISpec ¶
func OpenAPISpec() []byte
OpenAPISpec returns the embedded OpenAPI specification as bytes. Exported for tests to verify YAML validity and coverage of all registered routes.
func RequestIDFromContext ¶
RequestIDFromContext returns the request-id stored by requestIDMiddleware. Returns an empty string if the middleware is not applied.
func WriteError ¶
func WriteError(w http.ResponseWriter, status int, code, message string)
WriteError serialises an error into the response.
Types ¶
type Config ¶
type Config struct {
ListenAddr string
RequestTimeout time.Duration
Logger *slog.Logger
DB *sql.DB
// Devices is the device store for AuthMiddleware. Required to mount protected
// /v1/* routes; if nil only /healthz, /readyz, and 404 are served.
Devices model.DeviceStore
// Projects is the use-case for /v1/projects. Optional; nil — routes not mounted.
Projects model.ProjectService
// Tasks is the use-case for /v1/tasks. Optional; nil — routes not mounted.
Tasks model.TaskService
// Chat is the use-case for /v1/chat. Optional; nil — routes not mounted.
Chat model.ChatService
// History is the history store for /v1/chat/history. Optional; nil — returns empty history.
History model.History
// ChatTimeout is the agent processing timeout for /v1/chat (0 means no timeout).
ChatTimeout time.Duration
// Owner holds owner data published at /v1/me.
Owner OwnerInfo
// IdempotencyTTL/IdempotencyMaxEntries are the idempotency cache parameters;
// zero uses IdempotencyMiddleware defaults.
IdempotencyTTL time.Duration
IdempotencyMaxEntries int
// Events is the domain event store for SSE replay and /v1/sync/snapshot.
// If nil, /v1/events and /v1/sync/snapshot routes are not registered.
Events model.EventStore
// Broker is the in-memory SSE broker. If nil, /v1/events is not registered.
Broker model.Broker
// SSEHeartbeatInterval is the SSE keepalive comment interval. 0 → 15 seconds.
SSEHeartbeatInterval time.Duration
// PairingService is the use-case for /v1/pair/* and /pair/confirm/*. If nil — routes not mounted.
PairingService model.PairingService
// PairingRateLimit is the POST /v1/pair/request rate limit per IP per hour. 0 → default 5.
PairingRateLimit int
// PairingSecure: if true, the CSRF cookie is set as __Host-csrf with Secure:true (HTTPS);
// if false — as csrf without Secure (HTTP, dev environment).
PairingSecure bool
// Relay is the push relay client for syncing registrations on patchMe and revoke.
// Defaults to NilRelayClient (push disabled) when nil.
Relay push.RelayClient
// DateTime holds date/time configuration served at GET /v1/datetime/config.
DateTime config.DateTimeConfig
// VoiceQueue is the voice job queue for POST /v1/chat/voice. If nil, the route is not mounted.
VoiceQueue model.VoiceQueue
// UploadsDir is the base directory for client audio file storage.
UploadsDir string
// VoiceMaxBytes is the max audio upload size in bytes (default 10 MiB).
VoiceMaxBytes int64
// VoiceDuration is the max allowed audio duration (default: no limit).
VoiceDuration time.Duration
// Transcriber is the speech-to-text backend. Used by Task 9b to return 503 when nil.
Transcriber model.Transcriber
// TaskStore is the task/project store used by POST /v1/chat/voice to validate projectId.
TaskStore model.TaskStore
// VoiceRateLimit is the per-device rate limit for POST /v1/chat/voice, per minute. 0 → default 10.
VoiceRateLimit int
// BuildVersion is the binary version string injected via ldflags. Falls back to
// the package-level Version constant when empty.
BuildVersion string
}
Config holds HTTP server parameters. DB is required for /readyz; Logger for middleware. ListenAddr is required for Run but not for Handler().
type ErrorResponse ¶
type ErrorResponse struct {
Error ErrorBody `json:"error"`
}
ErrorResponse wraps the error body. The format `{"error": {"code": "...", "message": "..."}}` is fixed in the OpenAPI specification.
type IdempotencyConfig ¶
type IdempotencyConfig struct {
// TTL is how long to keep a cached response. Default: 1 hour.
TTL time.Duration
// MaxEntries is the LRU size limit. Oldest entries are evicted when
// exceeded. Default: 1024.
MaxEntries int
// Now is the time extension point for tests that need to "shift" time.
// Default: time.Now.
Now func() time.Time
}
IdempotencyConfig controls the middleware parameters. Any zero field receives a safe default.