server

package
v1.0.1-0...-5a0dde2 Latest Latest
Warning

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

Go to latest
Published: Sep 20, 2026 License: GPL-3.0 Imports: 23 Imported by: 0

Documentation

Overview

Package server is the SM Cloud HTTP API (ADR 0040 / docs/v2-design/sm-cloud-p1.md, step S2): the thin transport over internal/cloud/store that the daemon's smcloud forwarder (S3), reconcile routine (S4), and restore command (S5) talk to.

Routes

PUT  /v1/qsos                     batch upsert-by-UUID; deleted_at set = tombstone
PUT  /v1/evidence                 §5 evidence-sync batch: per-row outcomes (evidencewire);
                                  retention-slice supersession = tombstone-then-delete, and
                                  tombstones gate every kind's upsert (re-offer → tombstoned)
GET  /v1/logbooks                 the tenant's logbooks (id + name)
GET  /v1/logbooks/{id}/reconcile  {count, hash} over the live rows (reconcile.Summary)
GET  /v1/logbooks/{id}/manifest   the (uuid, modified_at, deleted) diff list
GET  /v1/export                   full-fidelity dump of everything the tenant owns
GET  /v1/health                   liveness + DB ping (unauthenticated)
GET  /v1/version                  build version (unauthenticated)

Wire contract

The QSO payload on the wire is canonical types.Qso JSON, projected by the daemon to remove the daemon-local fields the cloud never reads (id, logbook_id, dedupe_key, csid, country_details.id, contact_history[].id — see smcloud.projectCloudQso). The server stores the projected bytes VERBATIM (json.RawMessage end to end): it unmarshals only to validate shape and extract the UUID, never re-marshals — so restore returns exactly what backup sent, byte for byte (UUID, HH:MM:SS seconds, additional_data intact). modified_at/revision/deleted_at ride an envelope beside the payload because they are storage-row facts (trigger-stamped locally), not ADIF fields. revision (ADR 0050) is the version marker the upsert guard orders on — revision first, modified_at breaking ties — because local modified_at is second-precision and cannot order same-second edits; an absent revision decodes as 0 (legacy client) and gets pure-timestamp semantics via the tie fallback.

PUT bodies must be a SINGLE JSON document (trailing content is a 400), and every uploaded UUID must be a valid UUIDv7 — the store's uuid column would take any RFC 4122 value, but restore (qsoservice.Restore) admits only v7, and an accepted backup must be restorable. Validation runs before the EnsureLogbook side effect, so a rejected batch provisions nothing. /v1/export reads logbooks + records from ONE repeatable-read snapshot (store.ExportSnapshot), so a push landing mid-export can't produce QSOs whose logbook is missing from the same dump.

Auth

Bearer token → tenant, constant-time compared. P1 provisions a single (token, tenant) pair at boot (trust-on-provisioning, ADR 0040 § Identity); the map shape means multi-tenant is data, not a rearchitecture. Everything except /v1/health and /v1/version requires auth; a per-logbook read on a logbook the tenant doesn't own is a 404 (not 403 — existence is not leaked).

Boundary

Imports types + the cloud store/reconcile packages + internal/utils (UUID validation) + stdlib (log/slog for output — the service runs under systemd/journald, stderr is the sink). Nothing daemon-specific: no internal/api, no bridge, no storage — the package-boundary rule from cmd/smd/doc.go holds by import graph.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ExportQso

type ExportQso struct {
	UUID       string          `json:"uuid"`
	LogbookID  int64           `json:"logbook_id"`
	ModifiedAt time.Time       `json:"modified_at"`
	Revision   int64           `json:"revision,omitempty"`
	DeletedAt  *time.Time      `json:"deleted_at,omitempty"`
	Qso        json.RawMessage `json:"qso"`
}

ExportQso is one row of the export dump: the verbatim stored payload plus the storage-row facts restore needs (logbook routing, recency, revision continuity, tombstone).

type PutQsosRequest

type PutQsosRequest struct {
	Logbook string      `json:"logbook"`
	Qsos    []QsoUpload `json:"qsos"`
}

PutQsosRequest is the PUT /v1/qsos body. Logbook is the target logbook's NAME — the server ensures it exists under the authenticated tenant (idempotent), so the client never has to pre-provision or learn ids to push.

type PutQsosResponse

type PutQsosResponse struct {
	Received int `json:"received"`
	Applied  int `json:"applied"`
}

PutQsosResponse reports the batch outcome. Applied < Received means the stale-push guard rejected some rows (sync telemetry, ADR 0040).

type QsoUpload

type QsoUpload struct {
	ModifiedAt time.Time       `json:"modified_at"`
	Revision   int64           `json:"revision,omitempty"`
	DeletedAt  *time.Time      `json:"deleted_at,omitempty"`
	Qso        json.RawMessage `json:"qso"`
}

QsoUpload is one QSO on the PUT /v1/qsos wire: canonical types.Qso JSON as sent — projected by the daemon to drop daemon-local fields, stored verbatim — plus the storage-row facts that are not ADIF fields. DeletedAt set = tombstone (soft delete; the cloud is retentive). Revision is the row's monotonic edit counter (ADR 0050); absent decodes as 0 = legacy client, which the store guard handles via its modified_at fallback.

type ReconcileResponse

type ReconcileResponse struct {
	LogbookID int64  `json:"logbook_id"`
	Count     int    `json:"count"`
	Hash      string `json:"hash"`
}

ReconcileResponse is the per-logbook drift summary: the live-row count and the reconcile.Summary hash the daemon compares its local computation to.

type Server

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

Server is the SM Cloud HTTP API over the Postgres store. Construct with New, mount via Handler.

func New

func New(st *store.Store, db *sql.DB, log *slog.Logger, tokens map[string]int64, version string, maxConcurrent int) *Server

New builds a Server. tokens maps bearer tokens to tenant ids (P1: one entry, provisioned at boot). db is used only for the /v1/health ping. maxConcurrent caps in-flight requests (<= 0 → defaultMaxConcurrent; see limit.go).

func (*Server) Handler

func (s *Server) Handler() http.Handler

Handler returns the routed HTTP handler.

Jump to

Keyboard shortcuts

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