feat: upload to external services clublog qrz
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
// Package extsvc uploads logged QSOs to external logbook services
|
||||
// (QRZ.com first; Clublog and LoTW to follow). Each service has its own
|
||||
// credentials and an upload mode chosen per-service: "immediate" pushes as
|
||||
// soon as the QSO is saved, "delayed" waits a random 1–2 minutes (like
|
||||
// Log4OM) so a mistakenly-logged QSO can still be edited or removed before
|
||||
// it leaves.
|
||||
//
|
||||
// The Manager is intentionally fire-and-forget: a failed or skipped upload
|
||||
// just leaves the QSO's per-service upload-status column empty, and the
|
||||
// (future) manual-upload window will let the user retry the backlog.
|
||||
package extsvc
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// errFromResult turns a non-OK result with no transport error into one
|
||||
// (defensive — uploaders normally return an error alongside !OK).
|
||||
func errFromResult(r UploadResult) error {
|
||||
if r.Message != "" {
|
||||
return errors.New(r.Message)
|
||||
}
|
||||
return errors.New("upload rejected")
|
||||
}
|
||||
|
||||
// Service identifies one external logbook.
|
||||
type Service string
|
||||
|
||||
const (
|
||||
ServiceQRZ Service = "qrz" // QRZ.com Logbook
|
||||
ServiceClublog Service = "clublog" // Club Log real-time upload
|
||||
// ServiceLoTW to come.
|
||||
)
|
||||
|
||||
// UploadMode selects when an auto-upload fires after a QSO is saved.
|
||||
type UploadMode string
|
||||
|
||||
const (
|
||||
// ModeImmediate uploads as soon as the QSO is logged.
|
||||
ModeImmediate UploadMode = "immediate"
|
||||
// ModeDelayed waits a random 1–2 minutes before uploading.
|
||||
ModeDelayed UploadMode = "delayed"
|
||||
)
|
||||
|
||||
// ServiceConfig is the per-service user configuration. It's a superset of
|
||||
// the credential shapes the different services need — each service reads
|
||||
// only the fields it uses:
|
||||
//
|
||||
// QRZ.com → APIKey, ForceStationCallsign
|
||||
// Club Log → Email, Password, Callsign, APIKey
|
||||
//
|
||||
// AutoUpload + UploadMode are common to all (timing is per-service, so the
|
||||
// user can run e.g. Club Log immediate and QRZ delayed).
|
||||
type ServiceConfig struct {
|
||||
APIKey string `json:"api_key"`
|
||||
Email string `json:"email"` // Club Log account email
|
||||
Password string `json:"password"` // Club Log account password
|
||||
Callsign string `json:"callsign"` // Club Log logbook (owner) callsign
|
||||
ForceStationCallsign string `json:"force_station_callsign"` // QRZ
|
||||
AutoUpload bool `json:"auto_upload"`
|
||||
UploadMode UploadMode `json:"upload_mode"`
|
||||
}
|
||||
|
||||
// normalised returns the config with whitespace trimmed and a valid upload
|
||||
// mode (defaults to immediate).
|
||||
func (c ServiceConfig) normalised() ServiceConfig {
|
||||
c.APIKey = strings.TrimSpace(c.APIKey)
|
||||
c.Email = strings.TrimSpace(c.Email)
|
||||
c.Callsign = strings.ToUpper(strings.TrimSpace(c.Callsign))
|
||||
c.ForceStationCallsign = strings.ToUpper(strings.TrimSpace(c.ForceStationCallsign))
|
||||
if c.UploadMode != ModeDelayed {
|
||||
c.UploadMode = ModeImmediate
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
// ExternalServices bundles every service's config for the settings UI.
|
||||
// LoTW fields will be added as that service lands.
|
||||
type ExternalServices struct {
|
||||
QRZ ServiceConfig `json:"qrz"`
|
||||
Clublog ServiceConfig `json:"clublog"`
|
||||
}
|
||||
|
||||
// UploadResult is the outcome of a single upload attempt.
|
||||
type UploadResult struct {
|
||||
OK bool // the service accepted (or already had) the QSO
|
||||
LogID string // service-assigned record id, when provided
|
||||
Message string // human-readable detail (reason on failure)
|
||||
}
|
||||
Reference in New Issue
Block a user