Documentation
¶
Overview ¶
Package dto: actions resource shapes.
The Actions REST API uses one wire struct per resource for both reads and writes — the operator-facing form binds against the same shape it gets back from GET. ID and derived fields (LastInvokedAt/LastInvokedBy) are populated only on reads and ignored on writes.
Package dto defines the request and response shapes accepted and returned by pkg/webapi. DTOs decouple the HTTP contract from configstore storage models so the DB schema can evolve without changing the API surface, and vice versa.
Each mutable resource exposes:
- A request struct used for both POST (create) and PUT (update). It implements Validator.
- ToModel / ToUpdate that map into a configstore.* value.
- A response struct + FromModel conversion that masks any internal fields (timestamps, denormalized state).
Read-only resources continue to return storage models directly; add DTOs only if the shape needs to diverge.
Package dto wire shapes for the outbound Actions feature.
Index ¶
- Constants
- func DeriveMessageStatus(m configstore.Message) string
- func NormalizeCallsign(in string) (string, error)
- func SmartBeaconConfigToModel(r SmartBeaconConfigRequest) configstore.SmartBeaconConfig
- func ValidateAddressee(to string) error
- func ValidateChannelRef(ctx context.Context, lookup ChannelLookup, fieldName string, channelID uint32) error
- func ValidateMessageText(text string) error
- type AX25SessionProfile
- type AX25SessionProfilePin
- type AX25TerminalConfig
- type AX25TerminalConfigPatch
- type AX25TerminalMacro
- type AX25TranscriptDetail
- type AX25TranscriptEntry
- type AX25TranscriptSession
- type AcceptInviteRequest
- type AcceptInviteResponse
- type Action
- type ActionInvocation
- type ActionListenerAddressee
- type AgwRequest
- type AgwResponse
- type ArgSpec
- type AudioDeviceDeleteConflict
- type AudioDeviceDeleteResponse
- type AudioDeviceLevelsResponse
- type AudioDeviceRequest
- type AudioDeviceResponse
- type AudioDeviceSetGainRequest
- type BeaconRequest
- type BeaconResponse
- type BeaconSendResponse
- type Catalog
- type CatalogCountry
- type CatalogProvince
- type CatalogState
- type ChannelBacking
- type ChannelKissTncEntry
- type ChannelLookup
- type ChannelModemBacking
- type ChannelRequest
- type ChannelResponse
- type ConversationSummary
- type DigipeaterConfigRequest
- type DigipeaterConfigResponse
- type DigipeaterRuleRequest
- type DigipeaterRuleResponse
- type DownloadStatus
- type GPSRequest
- type GPSResponse
- type HealthResponse
- type IGateConfigRequest
- type IGateConfigResponse
- type IGateRfFilterRequest
- type IGateRfFilterResponse
- type KissRequest
- type KissResponse
- type MapsConfigRequest
- type MapsConfigResponse
- type MessageChange
- type MessageListResponse
- type MessagePreferencesRequest
- type MessagePreferencesResponse
- type MessageResponse
- type MessagesConfig
- type OTPCredential
- type OTPCredentialCreated
- type ParticipantResponse
- type ParticipantsEnvelope
- type PositionLogRequest
- type PositionLogResponse
- type PttRequest
- type PttResponse
- type RegisterRequest
- type RegisterResponse
- type ReleaseNoteDTO
- type ReleaseNotesResponse
- type RemoteActionMacro
- type RemoteActionMacroReorderRequest
- type RemoteActionMacroRequest
- type RemoteOTPCode
- type RemoteOTPCredential
- type RemoteOTPCredentialRequest
- type SendMessageRequest
- type SmartBeaconConfigRequest
- type SmartBeaconConfigResponse
- type StationAutocomplete
- type StationConfigRequest
- type StationConfigResponse
- type TacticalCallsignRequest
- type TacticalCallsignResponse
- type TestFireRequest
- type TestFireResponse
- type TestRigctldRequest
- type TestRigctldResponse
- type TestToneResponse
- type ThemeConfigRequest
- type ThemeConfigResponse
- type TxCapability
- type TxTimingRequest
- type TxTimingResponse
- type UnitsConfigRequest
- type UnitsConfigResponse
- type UpdatesConfigRequest
- type UpdatesConfigResponse
- type UpdatesStatusResponse
- type Validator
Constants ¶
const ( DefaultAgwListenAddr = "0.0.0.0:8000" DefaultAgwCallsigns = "N0CALL" )
First-run defaults seeded into the response DTO when the source model field is the Go zero value. These mirror the gorm column defaults on configstore.AgwConfig so GET /api/agw on a fresh install returns a populated, UI-ready config. See IGate defaults for the "zero means unset" caveat.
const ( TxReasonNoInputDevice = "no input device configured" TxReasonNoOutputDevice = "no output device configured" )
TX-capability reason strings. Exported so tests and the validator callers can assert against them without re-stringing the literal.
const ( ChannelBackingSummaryModem = "modem" ChannelBackingSummaryKissTnc = "kiss-tnc" ChannelBackingSummaryUnbound = "unbound" )
Backing summary values.
const ( ChannelBackingHealthLive = "live" ChannelBackingHealthDown = "down" ChannelBackingHealthUnbound = "unbound" )
Backing health values.
const ( DefaultIGateServer = "rotate.aprs2.net" DefaultIGatePort = 14580 DefaultIGateMaxMsgHops = 2 DefaultIGateSoftwareName = "graywolf" DefaultIGateSoftwareVersion = "0.1" )
First-run defaults seeded into the response DTO when the source model field is the Go zero value. These mirror the gorm column defaults on configstore.IGateConfig so GET /api/igate/config on a fresh install (empty store) returns a populated, UI-ready config. Users who explicitly save these fields as zero will see them overwritten on the next GET; this is acceptable for singleton config where "no row yet" and "saved as zero" are not meaningfully distinguishable anyway.
rf_channel and tx_channel are deliberately NOT defaulted here. Both are soft-FKs onto configstore.Channel.ID and there is no guarantee any specific ID exists (channels can be created/deleted, leaving non-contiguous IDs). Defaulting to "1" caused a write-time validation failure when the operator saved an unrelated change: the response echoed rf_channel=1, the UI round-tripped it back, and the handler's non-idempotent ValidateChannelRef path 400'd with "rf_channel: channel 1 does not exist". Passing 0 (the documented "unset" sentinel) lets the UI's defaultCh fallback pick the lowest live channel for tx_channel and lets idempotent-skip absorb rf_channel.
const ( MessageStatusQueued = "queued" // outbound, not yet submitted MessageStatusTxSubmitted = "tx_submitted" // outbound, submitted but not yet TxHook-confirmed MessageStatusSentRF = "sent_rf" // outbound DM, RF sent, awaiting ack MessageStatusSentIS = "sent_is" // outbound DM, IS sent, awaiting ack MessageStatusAwaitingAck = "awaiting_ack" // outbound DM, sent but not yet acked MessageStatusAcked = "acked" // outbound DM, acked MessageStatusRejected = "rejected" // outbound DM, REJ received MessageStatusTimeout = "timeout" // outbound DM, retry budget exhausted MessageStatusBroadcast = "sent" // tactical outbound broadcast — terminal MessageStatusFailed = "failed" // terminal failure (non-retryable) MessageStatusReceived = "received" // inbound )
Status wire values. Derived from the underlying configstore.Message columns — see MessageResponse.Status below for the derivation table.
const MaxMessageText = 67
MaxMessageText is the APRS101 per-message body cap applied to addressee-line direct messages (":ADDRESSEE9:text{id}"). Bulletins, status beacons, positions, and weather frames have their own length conventions and are not affected by this constant. REST uses this value as early-reject feedback so the web UI sees a 400 instead of a silent truncation; the authoritative gate lives on the sender path (pkg/messages.Sender) and consults MessagePreferences for whether an operator-set override relaxes it up to MaxMessageTextUnsafe.
const MaxMessageTextUnsafe = 200
MaxMessageTextUnsafe is the hard upper ceiling when an operator opts in to long messages via MessagePreferences.MaxMessageTextOverride. 200 bytes leaves safe headroom under the AX.25 info-field limit (~256 bytes) for the addressee framing (":ADDRESSEE9:") and the msgid tail ("{NNN"). Applies to addressee-line direct messages only; bulletins/status/position frames are unaffected.
Variables ¶
This section is empty.
Functions ¶
func DeriveMessageStatus ¶
func DeriveMessageStatus(m configstore.Message) string
DeriveMessageStatus maps the row's persisted column tuple to the wire-visible status enum. The mapping is deterministic — same inputs always produce the same output — and is the single source of truth for the Svelte UI's status pill.
Direction = "in" → "received" Direction = "out" && ThreadKind = "tactical":
AckState == "broadcast" → "sent" (per plan: tactical terminal maps to "sent") SentAt == nil && Attempts == 0 → "queued" SentAt == nil && Attempts > 0 → "tx_submitted" default → "sent"
Direction = "out" && ThreadKind = "dm":
AckState == "acked" → "acked" AckState == "rejected" && FailureReason != "" → "timeout" or "failed" AckState == "rejected" → "rejected" SentAt == nil && Attempts == 0 → "queued" SentAt == nil && Attempts > 0 → "tx_submitted" SentAt != nil && Source == "is" → "sent_is" (if not yet acked) SentAt != nil → "sent_rf"
func NormalizeCallsign ¶
NormalizeCallsign uppercases, strips -SSID, and validates the format. Returns the cleaned callsign or an error matching the server's "must include at least one digit" rule. Used both by the client-side pre-flight (in JS, mirrored) and the backend handler.
func SmartBeaconConfigToModel ¶
func SmartBeaconConfigToModel(r SmartBeaconConfigRequest) configstore.SmartBeaconConfig
SmartBeaconConfigToModel maps a validated request into a storage model. Caller is responsible for stamping ID on update via the upsert path (configstore adopts the existing singleton id when cfg.ID == 0).
func ValidateAddressee ¶
ValidateAddressee returns nil iff to is a syntactically valid APRS addressee. Does NOT check against the tactical set or the loopback guard — handlers do those checks after this one.
func ValidateChannelRef ¶
func ValidateChannelRef(ctx context.Context, lookup ChannelLookup, fieldName string, channelID uint32) error
ValidateChannelRef rejects a write whose channelID points at a non-existent channel. The zero value is treated as "none" — several soft-FK columns (IGateConfig.TxChannel, a TxTiming row's channel after a cascade nulls it, etc.) use 0 as the sentinel for "unset" and must pass through this helper unchanged. A non-zero value that fails to resolve returns a human-legible error intended to land verbatim in a 400 response body.
Callers wire this into the request lifecycle AFTER the DTO's own Validate() method has passed — keeping Validate() pure (no I/O) and letting the handler thread the store into the cross-table check. This mirrors the pattern Phase 3 established for the mutual-exclusivity rule, which lives at the configstore layer and runs once per create/update regardless of which DTO triggered it.
Note: this helper does not hold a DB lock across the lookup and the subsequent store write, so a racing delete between the check and the write could still land an orphan. The store-layer ChannelReferrers + post-delete reload notify path catches that class of race at the next reload; DTO validation is a friendly-error gate, not a hard invariant.
func ValidateMessageText ¶
ValidateMessageText rejects empty or patently over-long bodies. The upper bound here is MaxMessageTextUnsafe (the hard AX.25 headroom ceiling), not the default 67-char cap — the authoritative per-operator cap lives on pkg/messages.Sender and consults MessagePreferences, so a long-mode user with override=200 can compose up to that without the DTO rejecting them first. In default mode the sender-path gate still rejects anything over 67 chars; the DTO's role is just to short-circuit blatantly oversized bodies before they hit the sender.
Types ¶
type AX25SessionProfile ¶ added in v0.12.4
type AX25SessionProfile struct {
ID uint32 `json:"id"`
Name string `json:"name"`
LocalCall string `json:"local_call"`
LocalSSID uint8 `json:"local_ssid"`
DestCall string `json:"dest_call"`
DestSSID uint8 `json:"dest_ssid"`
ViaPath string `json:"via_path"`
Mod128 bool `json:"mod128"`
Paclen uint32 `json:"paclen"`
Maxframe uint32 `json:"maxframe"`
T1MS uint32 `json:"t1_ms"`
T2MS uint32 `json:"t2_ms"`
T3MS uint32 `json:"t3_ms"`
N2 uint32 `json:"n2"`
ChannelID *uint32 `json:"channel_id,omitempty"`
Pinned bool `json:"pinned"`
LastUsed *time.Time `json:"last_used,omitempty"`
}
AX25SessionProfile is the on-wire shape of GET / POST / PUT /api/ax25/profiles. Pinned + LastUsed are read-only on POST/PUT; promote with POST /api/ax25/profiles/{id}/pin and update LastUsed via the WebSocket bridge's CONNECTED hook.
type AX25SessionProfilePin ¶ added in v0.12.4
type AX25SessionProfilePin struct {
Pinned bool `json:"pinned"`
}
AX25SessionProfilePin is the on-wire body for POST /api/ax25/profiles/{id}/pin.
type AX25TerminalConfig ¶ added in v0.12.4
type AX25TerminalConfig struct {
ScrollbackRows uint32 `json:"scrollback_rows"`
CursorBlink bool `json:"cursor_blink"`
DefaultModulo uint32 `json:"default_modulo"`
DefaultPaclen uint32 `json:"default_paclen"`
Macros []AX25TerminalMacro `json:"macros"`
RawTailFilter string `json:"raw_tail_filter"`
}
AX25TerminalConfig is the GET response shape for /api/ax25/terminal-config. Macros is exposed as a typed array; the store persists it as a JSON-text column.
type AX25TerminalConfigPatch ¶ added in v0.12.4
type AX25TerminalConfigPatch struct {
ScrollbackRows *uint32 `json:"scrollback_rows,omitempty"`
CursorBlink *bool `json:"cursor_blink,omitempty"`
DefaultModulo *uint32 `json:"default_modulo,omitempty"`
DefaultPaclen *uint32 `json:"default_paclen,omitempty"`
Macros []AX25TerminalMacro `json:"macros,omitempty"`
RawTailFilter *string `json:"raw_tail_filter,omitempty"`
}
AX25TerminalConfigPatch is the PUT body for /api/ax25/terminal-config. Every field is a pointer so the handler can distinguish "field absent from request" (preserve existing column) from "field present with zero value" (validation error). The Macros slice uses nil-vs-empty to mean the same: nil = absent, []{} = explicit clear.
type AX25TerminalMacro ¶ added in v0.12.4
AX25TerminalMacro is one toolbar macro stored under MacrosJSON. Label is the human-visible button text; Payload is base64-encoded raw bytes the operator wants the macro to send (so the macro can carry terminal control codes, not just printable text).
type AX25TranscriptDetail ¶ added in v0.12.4
type AX25TranscriptDetail struct {
Session AX25TranscriptSession `json:"session"`
Entries []AX25TranscriptEntry `json:"entries"`
}
AX25TranscriptDetail bundles a session row with its full entry list, served by GET /api/ax25/transcripts/{id}.
type AX25TranscriptEntry ¶ added in v0.12.4
type AX25TranscriptEntry struct {
ID uint64 `json:"id"`
TS time.Time `json:"ts"`
Direction string `json:"direction"` // rx|tx
Kind string `json:"kind"` // data|event
Payload []byte `json:"payload"`
}
AX25TranscriptEntry is one persisted line in a transcript.
type AX25TranscriptSession ¶ added in v0.12.4
type AX25TranscriptSession struct {
ID uint32 `json:"id"`
ChannelID uint32 `json:"channel_id"`
PeerCall string `json:"peer_call"`
PeerSSID uint8 `json:"peer_ssid"`
ViaPath string `json:"via_path"`
StartedAt time.Time `json:"started_at"`
EndedAt *time.Time `json:"ended_at,omitempty"`
EndReason string `json:"end_reason"`
ByteCount uint64 `json:"byte_count"`
FrameCount uint64 `json:"frame_count"`
}
AX25TranscriptSession is the on-wire shape for the transcripts list. Body bytes are NOT included here -- fetch via /api/ax25/transcripts/{id}.
type AcceptInviteRequest ¶
type AcceptInviteRequest struct {
// Callsign is the tactical label to subscribe to. Required. Must
// match the APRS tactical syntax (1-9 of [A-Z0-9-]) after uppercase
// normalization.
Callsign string `json:"callsign" binding:"required"`
// SourceMessageID, when non-zero, identifies the inbound invite
// message that triggered the accept. Used only for audit — the
// handler sets InviteAcceptedAt on that row if it resolves to a
// valid invite for the same tactical. Zero = accept without audit.
SourceMessageID uint `json:"source_message_id"`
}
AcceptInviteRequest is the body accepted by the accept-invite endpoint (Phase 2 wires POST /api/tacticals/subscribe or similar). Acceptance is tactical-keyed, not message-keyed: the message ID is optional and exists only to let the handler stamp InviteAcceptedAt on the originating row for audit.
type AcceptInviteResponse ¶
type AcceptInviteResponse struct {
// Tactical is the post-accept state of the subscription. Always
// populated with Enabled=true (accept is the "turn it on" verb).
Tactical TacticalCallsignResponse `json:"tactical"`
// AlreadyMember is true when the operator was already subscribed
// and enabled before this request. Lets the UI suppress the
// "Joined TAC" toast and emit "Already a member" instead.
AlreadyMember bool `json:"already_member"`
}
AcceptInviteResponse is the body returned by a successful accept. Never returns 409 — "already a member" is a normal success with AlreadyMember=true so the client can render a distinct toast without error-handling ceremony.
type Action ¶ added in v0.13.0
type Action struct {
ID uint `json:"id"`
Name string `json:"name"`
Description string `json:"description"`
Type string `json:"type"` // "command" | "webhook"
CommandPath string `json:"command_path,omitempty"`
WorkingDir string `json:"working_dir,omitempty"`
WebhookMethod string `json:"webhook_method,omitempty"`
WebhookURL string `json:"webhook_url,omitempty"`
WebhookHeaders map[string]string `json:"webhook_headers,omitempty"`
WebhookBodyTemplate string `json:"webhook_body_template,omitempty"`
TimeoutSec int `json:"timeout_sec"`
OTPRequired bool `json:"otp_required"`
OTPCredentialID *uint `json:"otp_credential_id,omitempty"`
SenderAllowlist string `json:"sender_allowlist"`
ArgSchema []ArgSpec `json:"arg_schema"`
ArgMode string `json:"arg_mode"` // "kv" (default) | "freeform"
RateLimitSec int `json:"rate_limit_sec"`
QueueDepth int `json:"queue_depth"`
MaxReplyLines int `json:"max_reply_lines"`
Enabled bool `json:"enabled"`
LastInvokedAt *string `json:"last_invoked_at,omitempty"`
LastInvokedBy string `json:"last_invoked_by,omitempty"`
}
Action is the wire shape for an Action definition.
type ActionInvocation ¶ added in v0.13.0
type ActionInvocation struct {
ID uint `json:"id"`
ActionID *uint `json:"action_id,omitempty"`
ActionName string `json:"action_name"`
SenderCall string `json:"sender_call"`
Source string `json:"source"`
OTPCredID *uint `json:"otp_credential_id,omitempty"`
OTPVerified bool `json:"otp_verified"`
Args map[string]string `json:"args"`
Status string `json:"status"`
StatusDetail string `json:"status_detail,omitempty"`
ExitCode *int `json:"exit_code,omitempty"`
HTTPStatus *int `json:"http_status,omitempty"`
OutputCapture string `json:"output_capture,omitempty"`
ReplyText string `json:"reply_text"`
ReplyLineCount int `json:"reply_line_count"`
Truncated bool `json:"truncated"`
CreatedAt string `json:"created_at"`
}
ActionInvocation is one audit row.
type ActionListenerAddressee ¶ added in v0.13.0
type ActionListenerAddressee struct {
ID uint `json:"id"`
Addressee string `json:"addressee"`
CreatedAt string `json:"created_at"`
}
ActionListenerAddressee is one extra APRS addressee that triggers the Actions classifier (independent of the station call and tactical aliases).
type AgwRequest ¶
type AgwRequest struct {
ListenAddr string `json:"listen_addr"`
Callsigns string `json:"callsigns"`
Enabled bool `json:"enabled"`
}
AgwRequest is the body accepted by PUT /api/agw (singleton).
func (AgwRequest) ToModel ¶
func (r AgwRequest) ToModel() configstore.AgwConfig
func (AgwRequest) Validate ¶
func (r AgwRequest) Validate() error
type AgwResponse ¶
type AgwResponse struct {
ID uint32 `json:"id"`
AgwRequest
}
AgwResponse is the body returned by GET/PUT for the singleton.
func AgwFromModel ¶
func AgwFromModel(m configstore.AgwConfig) AgwResponse
type ArgSpec ¶ added in v0.13.0
type ArgSpec struct {
Key string `json:"key"`
Regex string `json:"regex,omitempty"`
MaxLen int `json:"max_len,omitempty"`
Required bool `json:"required,omitempty"`
}
ArgSpec is one entry in an Action's arg_schema.
type AudioDeviceDeleteConflict ¶
type AudioDeviceDeleteConflict struct {
Error string `json:"error"`
Channels []ChannelResponse `json:"channels"`
}
AudioDeviceDeleteConflict is the body returned by DELETE /api/audio-devices/{id} with a 409 when the device is referenced by one or more channels and the caller did not request cascade deletion. The channels slice lists the referencing channels so the UI can surface them in the confirm dialog.
Wire shape matches the pre-typed `map[string]any{"error": ..., "channels": ...}` literal previously emitted by the handler — byte-identical when the slice fields are populated the same way.
type AudioDeviceDeleteResponse ¶
type AudioDeviceDeleteResponse struct {
Deleted []ChannelResponse `json:"deleted"`
}
AudioDeviceDeleteResponse is the body returned by DELETE /api/audio-devices/{id} on success. Deleted lists the channels that were removed alongside the device when cascade was requested; empty when the device had no referencing channels.
type AudioDeviceLevelsResponse ¶
type AudioDeviceLevelsResponse map[uint32]*modembridge.DeviceLevel
AudioDeviceLevelsResponse is the body returned by GET /api/audio-devices/levels — a map from device id to the latest cached peak/rms/clipping measurement. Swag cannot render a keyed map[uint32]*T directly in a Swagger 2.0 definition, so the response is documented as {object} and the TypeScript client represents it as Record<string, modembridge.DeviceLevel> (JSON object keys are always strings on the wire).
type AudioDeviceRequest ¶
type AudioDeviceRequest struct {
Name string `json:"name"`
Direction string `json:"direction"`
SourceType string `json:"source_type"`
DevicePath string `json:"device_path"`
SampleRate uint32 `json:"sample_rate"`
Channels uint32 `json:"channels"`
Format string `json:"format"`
GainDB float32 `json:"gain_db"`
}
AudioDeviceRequest is the body accepted by POST /api/audio-devices and PUT /api/audio-devices/{id}.
func (AudioDeviceRequest) ToModel ¶
func (r AudioDeviceRequest) ToModel() configstore.AudioDevice
func (AudioDeviceRequest) ToUpdate ¶
func (r AudioDeviceRequest) ToUpdate(id uint32) configstore.AudioDevice
func (AudioDeviceRequest) Validate ¶
func (r AudioDeviceRequest) Validate() error
Validate ensures required fields are set and gain is in range.
type AudioDeviceResponse ¶
type AudioDeviceResponse struct {
ID uint32 `json:"id"`
AudioDeviceRequest
}
AudioDeviceResponse is the body returned by GET/POST/PUT for a device.
func AudioDeviceFromModel ¶
func AudioDeviceFromModel(m configstore.AudioDevice) AudioDeviceResponse
func AudioDevicesFromModels ¶
func AudioDevicesFromModels(ms []configstore.AudioDevice) []AudioDeviceResponse
type AudioDeviceSetGainRequest ¶
type AudioDeviceSetGainRequest struct {
GainDB float32 `json:"gain_db"`
}
AudioDeviceSetGainRequest is the body for PUT /api/audio-devices/{id}/gain.
func (AudioDeviceSetGainRequest) Validate ¶
func (r AudioDeviceSetGainRequest) Validate() error
Validate enforces the same gain window as AudioDeviceRequest so the live-update path can't install a value the create/update path would reject.
type BeaconRequest ¶
type BeaconRequest struct {
Type string `json:"type"`
Channel uint32 `json:"channel"`
Callsign *string `json:"callsign"`
Destination string `json:"destination"`
Path string `json:"path"`
UseGps bool `json:"use_gps"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
AltFt float64 `json:"alt_ft"`
Ambiguity uint32 `json:"ambiguity"`
SymbolTable string `json:"symbol_table"`
Symbol string `json:"symbol"`
Overlay string `json:"overlay"`
Compress bool `json:"compress"`
Messaging bool `json:"messaging"`
Comment string `json:"comment"`
CommentCmd string `json:"comment_cmd"`
CustomInfo string `json:"custom_info"`
ObjectName string `json:"object_name"`
Power uint32 `json:"power"`
Height uint32 `json:"height"`
Gain uint32 `json:"gain"`
Dir uint32 `json:"dir"`
Freq string `json:"freq"`
Tone string `json:"tone"`
FreqOffset string `json:"freq_offset"`
DelaySeconds uint32 `json:"delay_seconds"`
EverySeconds uint32 `json:"interval"`
SlotSeconds int32 `json:"slot_seconds"`
SmartBeacon bool `json:"smart_beacon"`
SbFastSpeed uint32 `json:"sb_fast_speed"`
SbSlowSpeed uint32 `json:"sb_slow_speed"`
SbFastRate uint32 `json:"sb_fast_rate"`
SbSlowRate uint32 `json:"sb_slow_rate"`
SbTurnAngle uint32 `json:"sb_turn_angle"`
SbTurnSlope uint32 `json:"sb_turn_slope"`
SbMinTurnTime uint32 `json:"sb_min_turn_time"`
SendToAPRSIS bool `json:"send_to_aprs_is"`
Enabled bool `json:"enabled"`
}
BeaconRequest is the body accepted by POST /api/beacons and PUT /api/beacons/{id}.
Callsign is a per-beacon callsign override (see centralized station-callsign plan, D2/D3). The request DTO uses *string so the three meaningful states are expressible independently:
- nil → field omitted; on PUT, leave the stored value unchanged. On POST, treated the same as "".
- "" → inherit from StationConfig at transmit time.
- non-empty → explicit override (e.g. a vanity or tactical call).
The response DTO carries Callsign as plain string — an empty value in the response means "inherits from station callsign".
func (BeaconRequest) ApplyToUpdate ¶
func (r BeaconRequest) ApplyToUpdate(id uint32, existing configstore.Beacon) configstore.Beacon
ApplyToUpdate merges the request onto an existing stored beacon, honouring pointer-nil = "leave unchanged" on the Callsign override field. All other fields are overwritten with the request value (replace-style PUT).
func (BeaconRequest) ToModel ¶
func (r BeaconRequest) ToModel() configstore.Beacon
func (BeaconRequest) ToUpdate ¶
func (r BeaconRequest) ToUpdate(id uint32) configstore.Beacon
func (BeaconRequest) Validate ¶
func (r BeaconRequest) Validate() error
Validate rejects configurations that would cause the scheduler to skip transmission at send time. Position/igate beacons must either source coordinates from the GPS cache or carry non-zero fixed coordinates. The Callsign override field is no longer validated here — empty / nil mean "inherit from StationConfig", which is now the canonical source of truth.
type BeaconResponse ¶
type BeaconResponse struct {
ID uint32 `json:"id"`
Type string `json:"type"`
Channel uint32 `json:"channel"`
Callsign string `json:"callsign"`
Destination string `json:"destination"`
Path string `json:"path"`
UseGps bool `json:"use_gps"`
Latitude float64 `json:"latitude"`
Longitude float64 `json:"longitude"`
AltFt float64 `json:"alt_ft"`
Ambiguity uint32 `json:"ambiguity"`
SymbolTable string `json:"symbol_table"`
Symbol string `json:"symbol"`
Overlay string `json:"overlay"`
Compress bool `json:"compress"`
Messaging bool `json:"messaging"`
Comment string `json:"comment"`
CommentCmd string `json:"comment_cmd"`
CustomInfo string `json:"custom_info"`
ObjectName string `json:"object_name"`
Power uint32 `json:"power"`
Height uint32 `json:"height"`
Gain uint32 `json:"gain"`
Dir uint32 `json:"dir"`
Freq string `json:"freq"`
Tone string `json:"tone"`
FreqOffset string `json:"freq_offset"`
DelaySeconds uint32 `json:"delay_seconds"`
EverySeconds uint32 `json:"interval"`
SlotSeconds int32 `json:"slot_seconds"`
SmartBeacon bool `json:"smart_beacon"`
SbFastSpeed uint32 `json:"sb_fast_speed"`
SbSlowSpeed uint32 `json:"sb_slow_speed"`
SbFastRate uint32 `json:"sb_fast_rate"`
SbSlowRate uint32 `json:"sb_slow_rate"`
SbTurnAngle uint32 `json:"sb_turn_angle"`
SbTurnSlope uint32 `json:"sb_turn_slope"`
SbMinTurnTime uint32 `json:"sb_min_turn_time"`
SendToAPRSIS bool `json:"send_to_aprs_is"`
Enabled bool `json:"enabled"`
}
BeaconResponse is the body returned by GET/POST/PUT for a beacon. Callsign is the stored value — empty means "inherit from station callsign" at transmit time.
func BeaconFromModel ¶
func BeaconFromModel(m configstore.Beacon) BeaconResponse
func BeaconsFromModels ¶
func BeaconsFromModels(ms []configstore.Beacon) []BeaconResponse
type BeaconSendResponse ¶
type BeaconSendResponse struct {
Status string `json:"status"` // "sent"
}
BeaconSendResponse is the body returned by POST /api/beacons/{id}/send when a one-shot transmission has been handed to the beacon scheduler.
type Catalog ¶ added in v0.12.1
type Catalog struct {
SchemaVersion int `json:"schemaVersion"`
GeneratedAt string `json:"generatedAt"`
Countries []CatalogCountry `json:"countries"`
Provinces []CatalogProvince `json:"provinces"`
States []CatalogState `json:"states"`
}
Catalog is the response shape for GET /api/maps/catalog. It mirrors the Worker's /manifest.json output 1:1 so the UI can reuse the arrays without translation. SchemaVersion is pinned to 1; bumps require a coordinated UI update.
type CatalogCountry ¶ added in v0.12.1
type CatalogProvince ¶ added in v0.12.1
type CatalogState ¶ added in v0.12.1
type ChannelBacking ¶
type ChannelBacking struct {
Modem ChannelModemBacking `json:"modem"`
KissTnc []ChannelKissTncEntry `json:"kiss_tnc"`
Summary string `json:"summary"`
Health string `json:"health"`
Tx TxCapability `json:"tx"`
}
ChannelBacking describes the runtime backing — modem and/or KISS-TNC interfaces — attached to a channel. Computed at request time from store + kissMgr.Status() + modembridge.SessionStatus().
Summary is one of "modem", "kiss-tnc", or "unbound". Dual-backend is forbidden at the config layer (D3) so this is always a single value.
Health is one of "live" (≥1 backend instance is up), "down" (backends exist but all are down), or "unbound" (no backend configured).
Tx is the channel's TX-capability signal used by the beacon / iGate / digipeater picker predicate and by the server-side referrer validator. It is derived from the same underlying fields as Summary + Health but answers a different question: "can a frame submitted on this channel actually be transmitted?" See TxCapability's contract for the single-branch decision rule and the Reason invariant.
type ChannelKissTncEntry ¶
type ChannelKissTncEntry struct {
InterfaceID uint32 `json:"interface_id"`
InterfaceName string `json:"interface_name"`
AllowTxFromGovernor bool `json:"allow_tx_from_governor"`
State string `json:"state"`
LastError string `json:"last_error,omitempty"`
RetryAtUnixMs int64 `json:"retry_at_unix_ms,omitempty"`
}
ChannelKissTncEntry is one TNC-mode KISS interface attached to the channel. AllowTxFromGovernor reflects KissInterface.AllowTxFromGovernor — Phase 3's opt-in flag gating governor-originated TX fan-out to this interface.
State / LastError / RetryAtUnixMs are best-effort today: Phase 1 only supports server-listen interfaces, which expose a state of "listening" (or "stopped" when not running) with no error or retry timestamp. Phase 4 fills these in for tcp-client interfaces. Fields are pre-declared now so the JSON contract doesn't shift between phases.
type ChannelLookup ¶
ChannelLookup is the narrow read surface DTO validators need to reject writes that reference a non-existent channel. Implemented by *configstore.Store via its ChannelExists method. Kept as an interface so tests can stub it without pulling the full configstore into scope, and so the DTO package doesn't need to import configstore just to typecheck the signature (DTOs already depend on configstore for model types; the interface is a convenience, not an isolation layer).
type ChannelModemBacking ¶
type ChannelModemBacking struct {
Active bool `json:"active"`
Reason string `json:"reason,omitempty"`
}
ChannelModemBacking reports whether an audio modem currently serves this channel. Active is true when the channel has a bound input audio device and the modem subprocess is running. Reason is populated when Active is false; empty otherwise.
type ChannelRequest ¶
type ChannelRequest struct {
Name string `json:"name"`
Mode string `json:"mode"`
InputDeviceID *uint32 `json:"input_device_id"`
InputChannel uint32 `json:"input_channel"`
OutputDeviceID uint32 `json:"output_device_id"`
OutputChannel uint32 `json:"output_channel"`
ModemType string `json:"modem_type"`
BitRate uint32 `json:"bit_rate"`
MarkFreq uint32 `json:"mark_freq"`
SpaceFreq uint32 `json:"space_freq"`
Profile string `json:"profile"`
NumSlicers uint32 `json:"num_slicers"`
FixBits string `json:"fix_bits"`
FX25Encode bool `json:"fx25_encode"`
IL2PEncode bool `json:"il2p_encode"`
NumDecoders uint32 `json:"num_decoders"`
DecoderOffset int32 `json:"decoder_offset"`
}
ChannelRequest is the body accepted by POST /api/channels and PUT /api/channels/{id}.
InputDeviceID is a pointer (*uint32) to model the KISS-only channel case introduced in Phase 2: a null value means the channel is not audio-backed and will be serviced exclusively by a KISS-TNC interface. When null, ModemType / BitRate / etc. are accepted but unused by the modem subprocess (see modembridge/session.go pushConfiguration). When non-null, the device must exist and have direction=input; configstore enforces that at write time.
func (ChannelRequest) ToModel ¶
func (r ChannelRequest) ToModel() configstore.Channel
ToModel maps a create request to a storage model.
func (ChannelRequest) ToUpdate ¶
func (r ChannelRequest) ToUpdate(id uint32) configstore.Channel
ToUpdate maps an update request to a storage model, preserving id.
func (ChannelRequest) Validate ¶
func (r ChannelRequest) Validate() error
Validate ensures required fields are set. Deep validation (device existence, channel range) still runs inside configstore.
InputDeviceID follows the Phase 2 nullable contract:
- nil → KISS-only channel; OutputDeviceID must be 0 (no TX audio without RX audio).
- non-nil → modem-backed channel; device existence + direction is validated by configstore.validateChannel.
type ChannelResponse ¶
type ChannelResponse struct {
ID uint32 `json:"id"`
Backing *ChannelBacking `json:"backing,omitempty"`
ChannelRequest
}
ChannelResponse is the body returned by GET/POST/PUT for a channel.
Backing is a computed, read-only object that tells the UI where a frame submitted on this channel will actually go (see design decision D7 in .context/2026-04-20-kiss-tcp-client-and-channel-backing.md). Empty in POST/PUT round-trips because the store model carries no backing state of its own; populated only by list/get handlers that have access to the running modembridge + kiss manager. Omitted from JSON when zero so create/update response bodies don't carry stale "unbound" placeholders.
func ChannelFromModel ¶
func ChannelFromModel(m configstore.Channel) ChannelResponse
ChannelFromModel converts a storage model into a response DTO. The Backing field is left nil — list/get handlers populate it after the base mapping using the live kiss manager and modem bridge status.
InputDeviceID is copied as-is (both sides are *uint32). A nil pointer round-trips as JSON null, which the TS client surfaces as `input_device_id: null` — the segmented type picker on the Channels page treats that as "KISS-TNC only".
func ChannelsFromModels ¶
func ChannelsFromModels(ms []configstore.Channel) []ChannelResponse
ChannelsFromModels maps a slice for list responses.
type ConversationSummary ¶
type ConversationSummary struct {
ThreadKind string `json:"thread_kind"`
ThreadKey string `json:"thread_key"`
Alias string `json:"alias,omitempty"`
LastAt time.Time `json:"last_at"`
LastSnippet string `json:"last_snippet"`
LastSenderCall string `json:"last_sender_call"`
UnreadCount int `json:"unread_count"`
TotalCount int `json:"total_count"`
ParticipantCount int `json:"participant_count,omitempty"`
Archived bool `json:"archived"`
}
ConversationSummary is the wire format for one thread in the master pane's rollup. Tactical threads populate ParticipantCount; DM rows omit it.
func ConversationSummaryFromModel ¶
func ConversationSummaryFromModel(s messages.ConversationSummary, alias string) ConversationSummary
ConversationSummaryFromModel renders one store summary into its DTO. alias is looked up by the caller from the tactical map.
type DigipeaterConfigRequest ¶
type DigipeaterConfigRequest struct {
Enabled bool `json:"enabled"`
DedupeWindowSeconds uint32 `json:"dedupe_window_seconds"`
MyCall *string `json:"my_call"`
}
DigipeaterConfigRequest is the body accepted by PUT /api/digipeater.
MyCall is a per-station callsign override (see centralized station-callsign plan, D2/D3). The request DTO uses *string so the three meaningful states are expressible independently:
- nil → field omitted; leave the stored value unchanged.
- "" → inherit from StationConfig at transmit time.
- non-empty → explicit override (e.g. "MTNTOP-1").
The response DTO carries MyCall as plain string — an empty value in the response means "inherits from station callsign". The override is stored verbatim (no case normalization here; the operator's casing is the source of truth for what they typed).
func (DigipeaterConfigRequest) ApplyToModel ¶
func (r DigipeaterConfigRequest) ApplyToModel(existing configstore.DigipeaterConfig) configstore.DigipeaterConfig
ApplyToModel merges the request fields onto an existing stored DigipeaterConfig. Fields whose request representation is a pointer only overwrite the target when the pointer is non-nil, preserving "field omitted = leave unchanged" semantics on this PUT endpoint. Other fields are written unconditionally (consistent with the replace-style PUT pattern used elsewhere in webapi).
func (DigipeaterConfigRequest) Validate ¶
func (r DigipeaterConfigRequest) Validate() error
type DigipeaterConfigResponse ¶
type DigipeaterConfigResponse struct {
ID uint32 `json:"id"`
Enabled bool `json:"enabled"`
DedupeWindowSeconds uint32 `json:"dedupe_window_seconds"`
MyCall string `json:"my_call"`
}
func DigipeaterConfigFromModel ¶
func DigipeaterConfigFromModel(m configstore.DigipeaterConfig) DigipeaterConfigResponse
type DigipeaterRuleRequest ¶
type DigipeaterRuleRequest struct {
FromChannel uint32 `json:"from_channel"`
ToChannel uint32 `json:"to_channel"`
Alias string `json:"alias"`
AliasType string `json:"alias_type"`
MaxHops uint32 `json:"max_hops"`
Action string `json:"action"`
Priority uint32 `json:"priority"`
Enabled bool `json:"enabled"`
}
DigipeaterRuleRequest is the body accepted by POST /api/digipeater/rules and PUT /api/digipeater/rules/{id}.
func (DigipeaterRuleRequest) ToModel ¶
func (r DigipeaterRuleRequest) ToModel() configstore.DigipeaterRule
func (DigipeaterRuleRequest) ToUpdate ¶
func (r DigipeaterRuleRequest) ToUpdate(id uint32) configstore.DigipeaterRule
func (DigipeaterRuleRequest) Validate ¶
func (r DigipeaterRuleRequest) Validate() error
type DigipeaterRuleResponse ¶
type DigipeaterRuleResponse struct {
ID uint32 `json:"id"`
DigipeaterRuleRequest
}
func DigipeaterRuleFromModel ¶
func DigipeaterRuleFromModel(m configstore.DigipeaterRule) DigipeaterRuleResponse
func DigipeaterRulesFromModels ¶
func DigipeaterRulesFromModels(ms []configstore.DigipeaterRule) []DigipeaterRuleResponse
type DownloadStatus ¶
type DownloadStatus struct {
Slug string `json:"slug"`
State string `json:"state"`
BytesTotal int64 `json:"bytes_total"`
BytesDownloaded int64 `json:"bytes_downloaded"`
DownloadedAt time.Time `json:"downloaded_at"`
ErrorMessage string `json:"error_message,omitempty"`
}
DownloadStatus is the response shape for /api/maps/downloads endpoints. State is one of: "absent" | "pending" | "downloading" | "complete" | "error".
DownloadedAt has no `omitempty` — Go's encoding/json silently treats `omitempty` on a struct value as a no-op (only the empty interface, nil pointer, etc. trigger omission), so the field always serializes. A zero-value timestamp on the wire signals "not complete yet"; clients must use State, not the timestamp, to decide whether the download finished.
type GPSRequest ¶
type GPSRequest struct {
SourceType string `json:"source"`
Device string `json:"serial_port"`
BaudRate uint32 `json:"baud_rate"`
GpsdHost string `json:"gpsd_host"`
GpsdPort uint32 `json:"gpsd_port"`
}
GPSRequest is the body accepted by PUT /api/gps (singleton). Enabled is derived from SourceType on the handler side so the UI doesn't need a separate toggle.
func (GPSRequest) ToModel ¶
func (r GPSRequest) ToModel() configstore.GPSConfig
func (GPSRequest) Validate ¶
func (r GPSRequest) Validate() error
type GPSResponse ¶
type GPSResponse struct {
ID uint32 `json:"id"`
GPSRequest
Enabled bool `json:"enabled"`
}
GPSResponse is the body returned by GET/PUT for the singleton.
func GPSFromModel ¶
func GPSFromModel(m configstore.GPSConfig) GPSResponse
type HealthResponse ¶
type HealthResponse struct {
StartedAt string `json:"started_at"` // process start time, RFC3339
Status string `json:"status"` // "ok"
Time string `json:"time"` // current UTC time, RFC3339
}
HealthResponse is the body returned by GET /api/health. Used by orchestration (systemd, docker healthcheck) and the web UI header.
Field order matches the alphabetical key order produced by the prior map[string]any encoding so the emitted JSON is byte-identical.
type IGateConfigRequest ¶
type IGateConfigRequest struct {
Enabled bool `json:"enabled"`
Server string `json:"server"`
Port uint32 `json:"port"`
ServerFilter string `json:"server_filter"`
SimulationMode bool `json:"simulation_mode"`
GateRfToIs bool `json:"gate_rf_to_is"`
GateIsToRf bool `json:"gate_is_to_rf"`
RfChannel uint32 `json:"rf_channel"`
MaxMsgHops uint32 `json:"max_msg_hops"`
SoftwareName string `json:"software_name"`
SoftwareVersion string `json:"software_version"`
TxChannel uint32 `json:"tx_channel"`
}
IGateConfigRequest is the body accepted by PUT /api/igate/config.
Per the centralized station-callsign plan (D3, D4), the iGate login callsign is StationConfig.Callsign and the APRS-IS passcode is a computed implementation detail. Neither is part of this DTO; the corresponding DB columns are retained for downgrade-safety only.
func (IGateConfigRequest) ToModel ¶
func (r IGateConfigRequest) ToModel() configstore.IGateConfig
func (IGateConfigRequest) Validate ¶
func (r IGateConfigRequest) Validate() error
Validate enforces syntax rules on fields the handler can't re-check cheaply. Today that means rejecting `|` in ServerFilter: APRS-IS filter expressions are space-separated OR'd clauses (see https://www.aprs-is.net/javAPRSFilter.aspx) — a pipe is not a valid separator. Some T2 servers silently drop the whole filter when they see one, which turns into "the iGate is receiving every packet on the network" without any on-box symptom. Reject at save time so the UI can surface the error before we ever log in.
type IGateConfigResponse ¶
type IGateConfigResponse struct {
ID uint32 `json:"id"`
IGateConfigRequest
}
func IGateConfigFromModel ¶
func IGateConfigFromModel(m configstore.IGateConfig) IGateConfigResponse
type IGateRfFilterRequest ¶
type IGateRfFilterRequest struct {
Channel uint32 `json:"channel"`
Type string `json:"type"`
Pattern string `json:"pattern"`
Action string `json:"action"`
Priority uint32 `json:"priority"`
Enabled bool `json:"enabled"`
}
IGateRfFilterRequest is the body accepted by POST /api/igate/filters and PUT /api/igate/filters/{id}.
func (IGateRfFilterRequest) ToModel ¶
func (r IGateRfFilterRequest) ToModel() configstore.IGateRfFilter
func (IGateRfFilterRequest) ToUpdate ¶
func (r IGateRfFilterRequest) ToUpdate(id uint32) configstore.IGateRfFilter
func (IGateRfFilterRequest) Validate ¶
func (r IGateRfFilterRequest) Validate() error
type IGateRfFilterResponse ¶
type IGateRfFilterResponse struct {
ID uint32 `json:"id"`
IGateRfFilterRequest
}
func IGateRfFilterFromModel ¶
func IGateRfFilterFromModel(m configstore.IGateRfFilter) IGateRfFilterResponse
func IGateRfFiltersFromModels ¶
func IGateRfFiltersFromModels(ms []configstore.IGateRfFilter) []IGateRfFilterResponse
type KissRequest ¶
type KissRequest struct {
Type string `json:"type"`
TcpPort int `json:"tcp_port"`
SerialDevice string `json:"serial_device"`
BaudRate uint32 `json:"baud_rate"`
Channel uint32 `json:"channel"`
Mode string `json:"mode"`
TncIngressRateHz uint32 `json:"tnc_ingress_rate_hz"`
TncIngressBurst uint32 `json:"tnc_ingress_burst"`
// AllowTxFromGovernor opts this TNC-mode interface in to receive
// frames from the TX governor (beacon / digipeater / iGate /
// KISS / AGW submissions). Only meaningful when Mode == "tnc";
// the validator rejects true with any other mode. Default false
// on migrated rows so existing TNC servers do not silently start
// transmitting; Phase 4 sets the DTO default to true for newly
// created tcp-client rows.
AllowTxFromGovernor bool `json:"allow_tx_from_governor"`
// Tcp-client fields (Phase 4): RemoteHost:RemotePort is the dial
// target; ReconnectInitMs / ReconnectMaxMs size the supervisor's
// exponential-backoff reconnect schedule. Unused / zero for
// Type != "tcp-client".
RemoteHost string `json:"remote_host"`
RemotePort uint16 `json:"remote_port"`
ReconnectInitMs uint32 `json:"reconnect_init_ms"`
ReconnectMaxMs uint32 `json:"reconnect_max_ms"`
}
KissRequest is the body accepted by POST /api/kiss and PUT /api/kiss/{id}. The frontend uses tcp_port (int) rather than listen_addr (host:port string); the store converts between them.
Mode defaults to "modem" when the client omits the field. The two TncIngress* fields default to the KissInterface struct tags (50/100) via the store-layer normalizer when sent as zero; the handler still rejects obviously-wrong non-zero values up front so the error lands at the API boundary instead of the SQLite boundary.
func (KissRequest) ToModel ¶
func (r KissRequest) ToModel() configstore.KissInterface
func (KissRequest) ToUpdate ¶
func (r KissRequest) ToUpdate(id uint32) configstore.KissInterface
func (KissRequest) Validate ¶
func (r KissRequest) Validate() error
type KissResponse ¶
type KissResponse struct {
ID uint32 `json:"id"`
Type string `json:"type"`
TcpPort int `json:"tcp_port"`
SerialDevice string `json:"serial_device"`
BaudRate uint32 `json:"baud_rate"`
Channel uint32 `json:"channel"`
Mode string `json:"mode"`
TncIngressRateHz uint32 `json:"tnc_ingress_rate_hz"`
TncIngressBurst uint32 `json:"tnc_ingress_burst"`
AllowTxFromGovernor bool `json:"allow_tx_from_governor"`
NeedsReconfig bool `json:"needs_reconfig"`
// Tcp-client fields (Phase 4). Zero-valued for non-tcp-client rows.
RemoteHost string `json:"remote_host"`
RemotePort uint16 `json:"remote_port"`
ReconnectInitMs uint32 `json:"reconnect_init_ms"`
ReconnectMaxMs uint32 `json:"reconnect_max_ms"`
// Per-interface runtime status (Phase 4). Surfaced verbatim from
// kiss.Manager.Status(); zero-valued when the row is not running
// or when the manager has nothing to report. Omitted from the
// wire when the interface is not tcp-client (State == "" +
// omitempty).
State string `json:"state,omitempty"`
LastError string `json:"last_error,omitempty"`
RetryAtUnixMs int64 `json:"retry_at_unix_ms,omitempty"`
PeerAddr string `json:"peer_addr,omitempty"`
ConnectedSince int64 `json:"connected_since,omitempty"`
ReconnectCount uint64 `json:"reconnect_count,omitempty"`
BackoffSeconds uint32 `json:"backoff_seconds,omitempty"`
}
KissResponse is the body returned by GET/POST/PUT for a KISS interface. Keeps the current shape exactly: tcp_port is derived from listen_addr, and bogus/unparseable ports surface as 0.
AllowTxFromGovernor round-trips KissInterface.AllowTxFromGovernor — the Phase 3 opt-in flag that gates per-interface governor TX. The field is always present but meaningful only when Mode == "tnc". NeedsReconfig mirrors KissInterface.NeedsReconfig so the Kiss page can surface a "reconfigure me" banner on rows whose Channel was nulled by a Phase 5 cascade delete.
func KissFromModel ¶
func KissFromModel(m configstore.KissInterface) KissResponse
func KissesFromModels ¶
func KissesFromModels(ms []configstore.KissInterface) []KissResponse
type MapsConfigRequest ¶
type MapsConfigRequest struct {
Source string `json:"source"`
}
MapsConfigRequest is the body for PUT /api/preferences/maps. Only Source is updatable from the client; Callsign and Token are managed by the /register sub-endpoint to keep the registration ceremony out of generic preference writes.
func (MapsConfigRequest) Validate ¶
func (r MapsConfigRequest) Validate() error
type MapsConfigResponse ¶
type MapsConfigResponse struct {
Source string `json:"source"`
Callsign string `json:"callsign,omitempty"`
Registered bool `json:"registered"`
RegisteredAt time.Time `json:"registered_at"`
Token string `json:"token,omitempty"`
}
MapsConfigResponse is what GET /api/preferences/maps and the PUT echo back. Token is omitted unless ?include_token=1 is set on the GET — see the handler. Registered is true iff a token is present.
RegisteredAt always serializes; when not registered it will be the Go zero time. The Registered bool is the authoritative source of truth for "is this populated" — don't infer from the timestamp.
type MessageChange ¶
type MessageChange struct {
ID uint64 `json:"id"`
Kind string `json:"kind"`
Message *MessageResponse `json:"message,omitempty"`
}
MessageChange is one entry in a MessageListResponse or an SSE frame. Kind is "created" | "updated" | "deleted"; Message is nil for deletions.
type MessageListResponse ¶
type MessageListResponse struct {
Cursor string `json:"cursor"`
Changes []MessageChange `json:"changes"`
}
MessageListResponse is the envelope returned by GET /api/messages. The future SSE endpoint reuses the MessageChange shape inside its data frames so clients share a single reconciliation codepath.
type MessagePreferencesRequest ¶
type MessagePreferencesRequest struct {
FallbackPolicy string `json:"fallback_policy"`
DefaultPath string `json:"default_path"`
RetryMaxAttempts uint32 `json:"retry_max_attempts"`
RetentionDays uint32 `json:"retention_days"`
// MaxMessageTextOverride raises the default 67-char addressee-line
// direct-message cap. 0 (or field absent) means "use the default";
// any positive value must fall in [MaxMessageText+1, MaxMessageTextUnsafe]
// (68..200). Applies to addressee-line DMs only — bulletins, status
// beacons, and position/weather frames are unaffected. The server
// rejects out-of-range values with 400 rather than silently clamping
// so operators see a clear error.
MaxMessageTextOverride uint32 `json:"max_message_text_override,omitempty"`
}
MessagePreferencesRequest is the body accepted by PUT /api/messages/preferences.
func (MessagePreferencesRequest) ToModel ¶
func (r MessagePreferencesRequest) ToModel() configstore.MessagePreferences
ToModel maps the request to the persisted configstore row.
func (MessagePreferencesRequest) Validate ¶
func (r MessagePreferencesRequest) Validate() error
Validate clamps FallbackPolicy to the canonical enum and enforces retry_max_attempts > 0 (zero is surprising — treat as invalid so operators see a clear error instead of the defaults swallow silently).
type MessagePreferencesResponse ¶
type MessagePreferencesResponse struct {
FallbackPolicy string `json:"fallback_policy"`
DefaultPath string `json:"default_path"`
RetryMaxAttempts uint32 `json:"retry_max_attempts"`
RetentionDays uint32 `json:"retention_days"`
// MaxMessageTextOverride mirrors the request field on read. 0
// means "default enforce 67" — older servers that have never been
// upgraded return 0 here, which is also what a fresh singleton with
// no override set returns. Positive values fall in
// (MaxMessageText, MaxMessageTextUnsafe].
MaxMessageTextOverride uint32 `json:"max_message_text_override"`
}
MessagePreferencesResponse is the body returned by GET/PUT preferences.
func MessagePreferencesFromModel ¶
func MessagePreferencesFromModel(m configstore.MessagePreferences) MessagePreferencesResponse
MessagePreferencesFromModel renders one row. Applies policy normalization so GETs against an uninitialised row return a sensible default instead of an empty string.
type MessageResponse ¶
type MessageResponse struct {
ID uint64 `json:"id"`
Direction string `json:"direction"` // "in" | "out"
Status string `json:"status"` // derived — see DeriveMessageStatus
OurCall string `json:"our_call"`
PeerCall string `json:"peer_call"`
FromCall string `json:"from_call"`
ToCall string `json:"to_call"`
Text string `json:"text"`
MsgID string `json:"msg_id,omitempty"`
CreatedAt time.Time `json:"created_at"`
ReceivedAt *time.Time `json:"received_at,omitempty"`
SentAt *time.Time `json:"sent_at,omitempty"`
AckedAt *time.Time `json:"acked_at,omitempty"`
Source string `json:"source,omitempty"` // "rf" | "is"
Channel *uint32 `json:"channel,omitempty"`
Path string `json:"path,omitempty"`
Via string `json:"via,omitempty"`
Unread bool `json:"unread"`
Attempts uint32 `json:"attempts"`
NextRetryAt *time.Time `json:"next_retry_at,omitempty"`
FailureReason string `json:"failure_reason,omitempty"`
IsAck bool `json:"is_ack,omitempty"`
IsBulletin bool `json:"is_bulletin,omitempty"`
ThreadKind string `json:"thread_kind"`
ThreadKey string `json:"thread_key"`
ReceivedByCall string `json:"received_by_call,omitempty"`
// Kind is the body classification. Always populated — "text" for
// normal messages, "invite" for tactical invitations. Never omitted
// so clients can use a simple equality check without worrying about
// a legacy empty string.
Kind string `json:"kind"`
// InviteTactical is the tactical callsign referenced by an invite.
// Empty (and omitted) on non-invite rows.
InviteTactical string `json:"invite_tactical,omitempty"`
// InviteAcceptedAt is audit-only: set when the local operator
// accepted this invite. The UI must NOT use this to decide "joined"
// state — that comes from the live TacticalSet cache. Kept so
// operators can see when/if an invite was acted on.
InviteAcceptedAt *time.Time `json:"invite_accepted_at,omitempty"`
// Extended is true when the transmitted body exceeded the default
// MaxMessageText (67). The UI renders an "extended" badge on these
// rows so operators can correlate if recipients report missing or
// truncated messages. Derived from len(Text) > MaxMessageText; no
// dedicated column.
Extended bool `json:"extended,omitempty"`
}
MessageResponse is the full wire shape for one message row. The Status field is derived server-side; clients don't infer it from the underlying columns.
func MessageFromModel ¶
func MessageFromModel(m configstore.Message) MessageResponse
MessageFromModel renders one row into its DTO. Channel is surfaced as a *uint32 so "unset" (0) serializes as omitted rather than as a semantic "channel 0" that would confuse clients.
func MessagesFromModels ¶
func MessagesFromModels(ms []configstore.Message) []MessageResponse
MessagesFromModels renders a slice of rows.
type MessagesConfig ¶ added in v0.12.4
type MessagesConfig struct {
TxChannel uint32 `json:"tx_channel"` // 0 = auto-resolve
}
MessagesConfig is the on-wire shape of GET/PUT /api/messages/config.
type OTPCredential ¶ added in v0.13.0
type OTPCredential struct {
ID uint `json:"id"`
Name string `json:"name"`
Issuer string `json:"issuer"`
Account string `json:"account"`
Algorithm string `json:"algorithm"`
Digits int `json:"digits"`
Period int `json:"period"`
CreatedAt string `json:"created_at"`
LastUsedAt *string `json:"last_used_at,omitempty"`
UsedBy []string `json:"used_by,omitempty"` // Action names that reference this cred
}
OTPCredential is the safe wire shape for a TOTP credential. SecretB32 is intentionally absent — see OTPCredentialCreated for the one-shot reveal on POST.
type OTPCredentialCreated ¶ added in v0.13.0
type OTPCredentialCreated struct {
OTPCredential
SecretB32 string `json:"secret_b32"`
OtpAuthURI string `json:"otpauth_uri"`
}
OTPCredentialCreated is the response body for POST /api/otp-credentials. SecretB32 and OtpAuthURI are returned only on this response and never read back.
type ParticipantResponse ¶
type ParticipantResponse struct {
Callsign string `json:"callsign"`
LastActive time.Time `json:"last_active"`
MessageCount int `json:"message_count"`
}
ParticipantResponse is one distinct sender on a tactical thread.
type ParticipantsEnvelope ¶
type ParticipantsEnvelope struct {
Participants []ParticipantResponse `json:"participants"`
EffectiveWithinDays int `json:"effective_within_days"`
}
ParticipantsEnvelope wraps ParticipantResponse with the effective window (in days) after retention clamp, so the UI can caption the chip row honestly even when a 7d request was clamped to 3d.
type PositionLogRequest ¶
type PositionLogRequest struct {
Enabled bool `json:"enabled"`
}
PositionLogRequest is the body accepted by PUT /api/position-log. The database path is controlled by the -history-db flag, not the API.
type PositionLogResponse ¶
PositionLogResponse is returned by GET/PUT for the singleton. DBPath is informational (read-only from the client's perspective).
type PttRequest ¶
type PttRequest struct {
ChannelID uint32 `json:"channel_id"`
Method string `json:"method"`
DevicePath string `json:"device_path"`
// GpioPin is the CM108 HID GPIO pin number (1-indexed, default 3). Not used
// by the `gpio` method, which references `gpio_line` instead to avoid
// indexing ambiguity between CM108 pin numbers and gpiochip line offsets.
GpioPin uint32 `json:"gpio_pin"`
// GpioLine is the gpiochip v2 line offset (0-indexed) used by the `gpio`
// method. Ignored for every other method.
GpioLine uint32 `json:"gpio_line"`
Invert bool `json:"invert"`
SlotTimeMs uint32 `json:"slot_time_ms"`
Persist uint32 `json:"persist"`
DwaitMs uint32 `json:"dwait_ms"`
}
PttRequest is the body accepted by POST /api/ptt and PUT /api/ptt/{channel}. The store upserts by channel_id.
func (PttRequest) ToModel ¶
func (r PttRequest) ToModel() configstore.PttConfig
func (PttRequest) ToUpdate ¶
func (r PttRequest) ToUpdate(channelID uint32) configstore.PttConfig
ToUpdate maps an update request to a storage model, pinning the channel id from the URL instead of the body so path-wins semantics match the current handler.
func (PttRequest) Validate ¶
func (r PttRequest) Validate() error
type PttResponse ¶
type PttResponse struct {
ID uint32 `json:"id"`
PttRequest
}
PttResponse is the body returned by GET/POST/PUT for a PTT config.
func PttFromModel ¶
func PttFromModel(m configstore.PttConfig) PttResponse
func PttsFromModels ¶
func PttsFromModels(ms []configstore.PttConfig) []PttResponse
type RegisterRequest ¶
type RegisterRequest struct {
Callsign string `json:"callsign"`
}
RegisterRequest is the body for POST /api/preferences/maps/register.
type RegisterResponse ¶
type RegisterResponse = MapsConfigResponse
RegisterResponse mirrors MapsConfigResponse — after a successful registration, the endpoint returns the same shape the GET would return next, including the freshly issued token (always, just this once, so the UI can offer the operator an export-token-to-file flow before it goes back to being suppressed).
type ReleaseNoteDTO ¶
type ReleaseNoteDTO struct {
SchemaVersion int `json:"schema_version"`
Version string `json:"version"`
Date string `json:"date"` // ISO YYYY-MM-DD
Style string `json:"style"` // "info" | "cta"
Title string `json:"title"`
Body string `json:"body"` // pre-sanitized HTML
}
ReleaseNoteDTO is a single release-note entry. Body carries server-sanitized, server-rendered HTML — the frontend binds it via {@html ...} untouched.
type ReleaseNotesResponse ¶
type ReleaseNotesResponse struct {
SchemaVersion int `json:"schema_version"`
Current string `json:"current"`
// LastSeen is the authenticated caller's last acknowledged release
// version at the moment the request was served. Empty on the
// /api/release-notes endpoint (caller-agnostic) and on /unseen for
// a user who has never acked. The frontend uses this to render a
// "Since your last visit · vA → vB" subtitle in the news popup.
LastSeen string `json:"last_seen,omitempty"`
Notes []ReleaseNoteDTO `json:"notes"`
}
ReleaseNotesResponse is the envelope returned by GET /api/release-notes and GET /api/release-notes/unseen.
SchemaVersion represents the response-envelope schema. Clients that know about envelope version N can safely ignore notes whose per-note schema_version exceeds their own MAX_SCHEMA (forward-compat; see plan D9).
type RemoteActionMacro ¶ added in v0.13.0
type RemoteActionMacro struct {
ID uint `json:"id"`
TargetCall string `json:"target_call"`
Label string `json:"label"`
ActionName string `json:"action_name"`
ArgsString string `json:"args_string"`
RemoteOTPCredentialID *uint `json:"remote_otp_credential_id,omitempty"`
Position int `json:"position"`
CreatedAt string `json:"created_at"`
UpdatedAt string `json:"updated_at"`
}
RemoteActionMacro is the wire shape of one saved macro.
type RemoteActionMacroReorderRequest ¶ added in v0.13.0
type RemoteActionMacroReorderRequest struct {
TargetCall string `json:"target_call"`
IDs []uint `json:"ids"`
}
RemoteActionMacroReorderRequest is the body of POST /api/remote-actions/macros/reorder. The IDs list defines the new order: index 0 -> position 0, etc. Macros for the target that are not in the list are left unchanged.
type RemoteActionMacroRequest ¶ added in v0.13.0
type RemoteActionMacroRequest struct {
TargetCall string `json:"target_call"`
Label string `json:"label"`
ActionName string `json:"action_name"`
ArgsString string `json:"args_string"`
RemoteOTPCredentialID *uint `json:"remote_otp_credential_id,omitempty"`
Position int `json:"position"`
}
RemoteActionMacroRequest is the create / update body. TargetCall is uppercased on the server; clients may send any case.
Update (PUT) semantics:
- Label, ActionName: gated — empty string leaves the field unchanged.
- ArgsString, RemoteOTPCredentialID: always overwrite. Empty string clears args; nil unbinds the credential. Clients must send the full update body.
- Position: ignored on PUT. The /macros/reorder endpoint is the sole owner of macro ordering.
type RemoteOTPCode ¶ added in v0.13.0
RemoteOTPCode is the response from POST /api/remote-actions/otp/{credId}. ExpiresAt is RFC3339 UTC and marks the inclusive upper edge of the step that produced this code. The drawer uses (ExpiresAt - now) as the countdown source.
type RemoteOTPCredential ¶ added in v0.13.0
type RemoteOTPCredential struct {
ID uint `json:"id"`
Name string `json:"name"`
Algorithm string `json:"algorithm"`
Digits int `json:"digits"`
Period int `json:"period"`
CreatedAt string `json:"created_at"`
LastUsedAt *string `json:"last_used_at,omitempty"`
UsedBy []string `json:"used_by"`
}
RemoteOTPCredential is the safe wire shape for a remote-station TOTP secret. SecretB32 is intentionally absent — it is only echoed back at create time via RemoteOTPCredentialCreated, and never retrievable thereafter.
UsedBy is the list of distinct uppercased target callsigns whose macros reference this credential. The credential cannot be deleted while non-empty (HTTP 409); the UI surfaces "Unbind from N macro(s) first" using its length.
type RemoteOTPCredentialRequest ¶ added in v0.13.0
type RemoteOTPCredentialRequest struct {
Name string `json:"name"`
SecretB32 string `json:"secret_b32"`
Algorithm string `json:"algorithm,omitempty"`
Digits int `json:"digits,omitempty"`
Period int `json:"period,omitempty"`
}
RemoteOTPCredentialRequest is the create / update body. Algorithm, Digits, Period are optional on create; the server fills sha1/6/30.
type SendMessageRequest ¶
type SendMessageRequest struct {
// To is the addressee: a station callsign for a DM or a tactical
// label for a group broadcast. Uppercase-normalized server-side.
To string `json:"to"`
// Text is the message body (<= 67 APRS chars after validation).
// Ignored when Kind == "invite" — the server builds the wire body
// from InviteTactical.
Text string `json:"text"`
// PreferIS, when true, routes the outbound via APRS-IS regardless
// of the current fallback policy.
PreferIS bool `json:"prefer_is,omitempty"`
// Path overrides the default RF path from preferences. Empty =
// use MessagePreferences.DefaultPath.
Path string `json:"path,omitempty"`
// Channel overrides the configured TX channel. Nil = use default.
Channel *uint32 `json:"channel,omitempty"`
// ClientID is an opaque client-side correlation token. Echoed back
// unchanged in the response so the optimistic UI can reconcile its
// local row with the persisted ID.
ClientID string `json:"client_id,omitempty"`
// Kind classifies the outbound row. Empty or "text" is a normal
// DM/tactical message; "invite" makes the sender build a
// `!GW1 INVITE <InviteTactical>` body and stamp the row with
// Kind=invite + InviteTactical. The sender (Phase 2) is
// responsible for honoring this; the DTO just carries it.
Kind string `json:"kind,omitempty"`
// InviteTactical is the tactical callsign referenced by an invite.
// Must be set when Kind == "invite"; ignored otherwise.
InviteTactical string `json:"invite_tactical,omitempty"`
}
SendMessageRequest is the body accepted by POST /api/messages.
func (SendMessageRequest) Validate ¶
func (r SendMessageRequest) Validate() error
Validate enforces the minimal invariants every compose request must satisfy. Loopback / tactical-vs-DM classification is handler-local because it needs the OurCall context.
type SmartBeaconConfigRequest ¶
type SmartBeaconConfigRequest struct {
// Enabled is true when SmartBeacon curve computation is active.
// When false, every beacon with smart_beacon=true falls back to
// its fixed interval.
Enabled bool `json:"enabled"`
// FastSpeedKt is the knots threshold at or above which beacons
// transmit at FastRateSec. The "moving fast" end of the curve.
// Must be greater than SlowSpeedKt.
FastSpeedKt uint32 `json:"fast_speed"`
// FastRateSec is the beacon interval in seconds at or above
// FastSpeedKt. Must be shorter than SlowRateSec.
FastRateSec uint32 `json:"fast_rate"`
// SlowSpeedKt is the knots threshold at or below which beacons
// transmit at SlowRateSec. Must be greater than zero to prevent a
// degenerate middle-branch division by zero inside
// beacon.SmartBeaconConfig.Interval().
SlowSpeedKt uint32 `json:"slow_speed"`
// SlowRateSec is the beacon interval in seconds at or below
// SlowSpeedKt. Must be longer than FastRateSec.
SlowRateSec uint32 `json:"slow_rate"`
// MinTurnDeg is the fixed-component turn angle threshold, in
// degrees, used in the corner-pegging formula. Must be in
// [1, 179].
MinTurnDeg uint32 `json:"min_turn_angle"`
// TurnSlope is the speed-dependent component (degrees·knots) of
// the corner-pegging turn threshold. Higher speed → lower
// effective threshold → corner pegs fire sooner. Must be greater
// than zero.
TurnSlope uint32 `json:"turn_slope"`
// MinTurnSec is the minimum interval in seconds between
// turn-triggered beacons. Must be greater than zero.
MinTurnSec uint32 `json:"min_turn_time"`
}
SmartBeaconConfigRequest is the body accepted by PUT /api/smart-beacon.
The wire shape is snake_case and matches the UI mock byte-for-byte. Speeds are in knots, rates are in seconds, the turn angle is in degrees, and the turn slope is in degrees·knots. These field names are the source of truth for the on-the-wire contract; renaming any of them is a breaking change for every consumer of the generated TypeScript client.
func (SmartBeaconConfigRequest) Validate ¶
func (r SmartBeaconConfigRequest) Validate() error
Validate enforces the SmartBeacon parameter constraints documented in the HamHUD/direwolf references. Errors use human-readable field names that mirror the wire tags so 400 responses point the UI at the offending field.
type SmartBeaconConfigResponse ¶
type SmartBeaconConfigResponse struct {
// Enabled is true when SmartBeacon curve computation is active.
Enabled bool `json:"enabled"`
// FastSpeedKt is the knots threshold at or above which beacons
// transmit at FastRateSec.
FastSpeedKt uint32 `json:"fast_speed"`
// FastRateSec is the beacon interval in seconds at or above
// FastSpeedKt.
FastRateSec uint32 `json:"fast_rate"`
// SlowSpeedKt is the knots threshold at or below which beacons
// transmit at SlowRateSec.
SlowSpeedKt uint32 `json:"slow_speed"`
// SlowRateSec is the beacon interval in seconds at or below
// SlowSpeedKt.
SlowRateSec uint32 `json:"slow_rate"`
// MinTurnDeg is the fixed-component turn angle threshold in
// degrees.
MinTurnDeg uint32 `json:"min_turn_angle"`
// TurnSlope is the speed-dependent component (degrees·knots) of
// the corner-pegging turn threshold.
TurnSlope uint32 `json:"turn_slope"`
// MinTurnSec is the minimum interval in seconds between
// turn-triggered beacons.
MinTurnSec uint32 `json:"min_turn_time"`
}
SmartBeaconConfigResponse is the body returned by GET/PUT /api/smart-beacon. Shape matches SmartBeaconConfigRequest — the singleton has no id or timestamps exposed on the wire.
func SmartBeaconConfigDefaults ¶
func SmartBeaconConfigDefaults() SmartBeaconConfigResponse
SmartBeaconConfigDefaults returns the response DTO populated from beacon.DefaultSmartBeacon(), which is the single source of truth for SmartBeacon parameter defaults. GET /api/smart-beacon uses this on a fresh install where no singleton row has been written yet.
Unit conversions applied to cross the package boundary:
- FastSpeed / SlowSpeed (float64 knots) → uint32 knots (rounded).
- FastRate / SlowRate / TurnTime (time.Duration) → uint32 seconds.
- TurnAngle / TurnSlope (float64) → uint32 (rounded).
func SmartBeaconConfigFromModel ¶
func SmartBeaconConfigFromModel(m configstore.SmartBeaconConfig) SmartBeaconConfigResponse
SmartBeaconConfigFromModel converts the persisted singleton into the response DTO. Straight field copy — model and DTO share the same units.
type StationAutocomplete ¶
type StationAutocomplete struct {
Callsign string `json:"callsign"`
LastHeard string `json:"last_heard,omitempty"` // RFC3339, empty for bots / missing
Source string `json:"source"` // "bot" | "cache" | "history" | "cache+history"
Description string `json:"description,omitempty"`
}
StationAutocomplete is one suggestion in GET /api/stations/autocomplete. Description is only set for "bot" sources; the station cache and history sources leave it empty.
type StationConfigRequest ¶
type StationConfigRequest struct {
Callsign string `json:"callsign"`
}
StationConfigRequest is the body accepted by PUT /api/station/config. An empty Callsign is permitted and triggers the clear-with-auto-disable flow defined in the centralized station-callsign plan (D7 rule 2): iGate and Digipeater Enabled are flipped to false atomically when they were previously true.
func (StationConfigRequest) Validate ¶
func (r StationConfigRequest) Validate() error
Validate is a no-op. Any non-empty callsign that fails shape validation is normalized (trim + uppercase) at the store boundary via configstore.UpsertStationConfig; completely invalid strings (internal whitespace etc.) are persisted verbatim and filtered at the resolve site. This mirrors other singleton request DTOs.
type StationConfigResponse ¶
type StationConfigResponse struct {
Callsign string `json:"callsign"`
Disabled []string `json:"disabled,omitempty"`
}
StationConfigResponse is the body returned by both GET and PUT on /api/station/config. Disabled is populated only on the PUT path when the clear-with-auto-disable rule fired; on GET it is omitted from the JSON envelope (omitempty).
Disabled values are the canonical feature names "igate" and "digipeater" (lowercase, exactly those strings) — clients can match on them to surface a "these features were disabled because you cleared the station callsign" notice.
type TacticalCallsignRequest ¶
type TacticalCallsignRequest struct {
Callsign string `json:"callsign"`
Alias string `json:"alias,omitempty"`
Enabled bool `json:"enabled"`
}
TacticalCallsignRequest is the body accepted by POST + PUT on /api/messages/tactical.
func (TacticalCallsignRequest) ToModel ¶
func (r TacticalCallsignRequest) ToModel() configstore.TacticalCallsign
ToModel builds a configstore row from the request. Callsign is uppercased by the model's BeforeSave hook; we upper here too so validation and collision checks use the canonical form.
func (TacticalCallsignRequest) Validate ¶
func (r TacticalCallsignRequest) Validate() error
Validate enforces addressee syntax and non-empty callsign. The handler additionally rejects well-known bot labels after this.
type TacticalCallsignResponse ¶
type TacticalCallsignResponse struct {
ID uint32 `json:"id"`
Callsign string `json:"callsign"`
Alias string `json:"alias,omitempty"`
Enabled bool `json:"enabled"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
}
TacticalCallsignResponse is the body returned by GET/POST/PUT.
func TacticalCallsignFromModel ¶
func TacticalCallsignFromModel(m configstore.TacticalCallsign) TacticalCallsignResponse
TacticalCallsignFromModel renders one row.
type TestFireRequest ¶ added in v0.13.0
type TestFireRequest struct {
Args map[string]string `json:"args,omitempty"`
Text *string `json:"text,omitempty"`
}
TestFireRequest is the body accepted by POST /api/actions/{id}/test-fire.
Args is used for kv-mode actions; Text is used for freeform-mode actions. The handler rejects requests that mix shapes against the Action's mode.
type TestFireResponse ¶ added in v0.13.0
type TestFireResponse struct {
Status string `json:"status"`
StatusDetail string `json:"status_detail,omitempty"`
OutputCapture string `json:"output_capture,omitempty"`
ReplyText string `json:"reply_text"`
ReplyLines []string `json:"reply_lines"`
ReplyLineCount int `json:"reply_line_count"`
Truncated bool `json:"truncated"`
ExitCode *int `json:"exit_code,omitempty"`
HTTPStatus *int `json:"http_status,omitempty"`
InvocationID uint `json:"invocation_id"`
}
TestFireResponse is the body returned by POST /api/actions/{id}/test-fire.
Truncated mirrors the value the audit row would have stored for a real on-air invocation, so the UI can warn the operator that their reply got chopped to fit the 67-char APRS message cap.
type TestRigctldRequest ¶
TestRigctldRequest is the body accepted by POST /api/ptt/test-rigctld. The handler opens a short-lived TCP connection to the given rigctld endpoint and sends a non-disruptive `t` (get_ptt) probe.
type TestRigctldResponse ¶
type TestRigctldResponse struct {
OK bool `json:"ok"`
Message string `json:"message"`
LatencyMs int64 `json:"latency_ms"`
}
TestRigctldResponse reports the outcome of a rigctld probe. OK is the single source of truth — clients must not infer success from HTTP status. Message is a human-readable diagnostic; LatencyMs is populated only on success and measures wall-clock from dial start to RPRT 0.
type TestToneResponse ¶
type TestToneResponse struct {
Status string `json:"status"`
}
TestToneResponse is the body returned by POST /api/audio-devices/{id}/test-tone on success. Preserves the pre-typed `{"status":"ok"}` wire shape.
type ThemeConfigRequest ¶
type ThemeConfigRequest struct {
ID string `json:"id"`
}
ThemeConfigRequest is the body accepted by PUT /api/preferences/theme. ID must match the kebab-case/lowercase pattern validated by configstore.IsValidTheme. The server does not enforce that the id matches a shipped theme — that's the frontend's responsibility, and keeping it so lets contributors add themes without touching Go.
func (ThemeConfigRequest) Validate ¶
func (r ThemeConfigRequest) Validate() error
type ThemeConfigResponse ¶
type ThemeConfigResponse struct {
ID string `json:"id"`
}
ThemeConfigResponse is the body returned by GET and PUT on /api/preferences/theme.
type TxCapability ¶
TxCapability reports whether a channel can currently transmit.
Capable is true when the channel has at least one usable TX path:
- ≥ 1 TNC-mode KissInterface attached (the KISS path short-circuits first, so a KISS-only channel with InputDeviceID == nil still reports Capable=true, not "no input device configured"), OR
- a modem backing with both InputDeviceID != nil AND OutputDeviceID != 0 (the zero-sentinel on OutputDeviceID marks RX-only modem configs; those cannot TX even though Backing.Summary is "modem").
Reason is a short human-legible explanation of why Capable is false (e.g. "no input device configured", "no output device configured").
Contract: Reason is the empty string if and only if Capable == true. Callers may rely on this invariant — don't overload Reason with a hint when Capable is true, and don't leave Reason empty when Capable is false. The reason strings are stable wire values (consumed by the UI picker's disabled-option secondary text and the server-side 400 body on referrer writes), so treat them as API surface.
type TxTimingRequest ¶
type TxTimingRequest struct {
Channel uint32 `json:"channel"`
TxDelayMs uint32 `json:"tx_delay_ms"`
TxTailMs uint32 `json:"tx_tail_ms"`
SlotMs uint32 `json:"slot_ms"`
Persist uint32 `json:"persist"`
FullDup bool `json:"full_dup"`
Rate1Min uint32 `json:"rate_1min"`
Rate5Min uint32 `json:"rate_5min"`
}
TxTimingRequest is the body accepted by POST /api/tx-timing and PUT /api/tx-timing/{channel}.
func (TxTimingRequest) ToModel ¶
func (r TxTimingRequest) ToModel() configstore.TxTiming
func (TxTimingRequest) ToUpdate ¶
func (r TxTimingRequest) ToUpdate(channel uint32) configstore.TxTiming
ToUpdate maps an update request to a storage model, pinning the channel from the URL instead of the body.
func (TxTimingRequest) Validate ¶
func (r TxTimingRequest) Validate() error
type TxTimingResponse ¶
type TxTimingResponse struct {
ID uint32 `json:"id"`
TxTimingRequest
}
func TxTimingFromModel ¶
func TxTimingFromModel(m configstore.TxTiming) TxTimingResponse
func TxTimingsFromModels ¶
func TxTimingsFromModels(ms []configstore.TxTiming) []TxTimingResponse
type UnitsConfigRequest ¶
type UnitsConfigRequest struct {
System string `json:"system"`
}
UnitsConfigRequest is the body accepted by PUT /api/preferences/units. System must be "imperial" or "metric".
func (UnitsConfigRequest) Validate ¶
func (r UnitsConfigRequest) Validate() error
Validate rejects anything other than the two recognized systems so a bad payload doesn't reach the store (which would reject it anyway) and the client gets a clean 400 instead of a 500.
type UnitsConfigResponse ¶
type UnitsConfigResponse struct {
System string `json:"system"`
}
UnitsConfigResponse is the body returned by GET and PUT on /api/preferences/units.
type UpdatesConfigRequest ¶
type UpdatesConfigRequest struct {
Enabled bool `json:"enabled"`
}
UpdatesConfigRequest is the body accepted by PUT /api/updates/config. Enabled controls whether the daily GitHub update check runs at all. Disabling stops the ticker and causes GET /api/updates/status to report status="disabled".
func (UpdatesConfigRequest) Validate ¶
func (r UpdatesConfigRequest) Validate() error
Validate is a no-op. A single bool field has no input shape that can fail validation; the method exists for symmetry with the other singleton-config request DTOs that implement dto.Validator.
type UpdatesConfigResponse ¶
type UpdatesConfigResponse struct {
Enabled bool `json:"enabled"`
}
UpdatesConfigResponse is the body returned by GET and PUT on /api/updates/config. Mirrors UpdatesConfigRequest — the stored state is a single bool.
type UpdatesStatusResponse ¶
type UpdatesStatusResponse struct {
Status string `json:"status"`
Current string `json:"current"`
Latest string `json:"latest,omitempty"`
URL string `json:"url,omitempty"`
CheckedAt string `json:"checked_at,omitempty"` // RFC3339, omitted if zero
}
UpdatesStatusResponse is the body returned by GET /api/updates/status. Status is a server-computed enum with exactly four values: "disabled", "pending", "current", "available" (D6). Latest / URL / CheckedAt are omitted from the JSON envelope when empty so a "disabled" or "pending" response collapses to a minimal shape.
Source Files
¶
- actions.go
- agw.go
- audio_device.go
- ax25_profiles.go
- ax25_terminal_config.go
- ax25_transcripts.go
- beacon.go
- catalog.go
- channel.go
- channel_refs.go
- digipeater.go
- downloads.go
- dto.go
- gps.go
- health.go
- igate.go
- kiss.go
- maps.go
- messages.go
- messages_config.go
- position_log.go
- ptt.go
- release_notes.go
- remote_actions.go
- smart_beacon.go
- station.go
- theme.go
- tx_timing.go
- units.go
- updates.go