fix(hamlog): stop offering an upload that cannot succeed
HAMLOG.online no longer issues API keys, and its upload API takes nothing else. An operator without a key cannot obtain one, so the auto-upload switch, the on-close sweep and the 'Send to' entry were all arming something that could only fail — silently, once per QSO. Closed at the source rather than hidden in the UI: the upload returns a sentinel that says why, the manager stops routing to it and says so once a session, and the manual path refuses with the same words. The settings page states it plainly instead of showing a switch that does nothing. Nothing else goes. Their confirmations arrive as an ADIF FILE and never needed a key, so that import stays; the sent/received state already in operators' logs stays readable, filterable and bulk-editable; and the upload itself is kept whole as uploadHamlogLive, still covered by its request-shape tests, against the day keys come back.
This commit is contained in:
@@ -4,6 +4,7 @@ import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
@@ -104,7 +105,26 @@ func UploadHamlog(ctx context.Context, client *http.Client, cfg ServiceConfig, a
|
||||
return uploadHamlogTo(ctx, client, hamlogAPIEndpoint, cfg, adifRecord)
|
||||
}
|
||||
|
||||
// ErrHamlogClosed is why nothing is sent to HAMLOG.online any more.
|
||||
//
|
||||
// The site stopped issuing API keys, and the upload API takes nothing else. An
|
||||
// operator without a key cannot obtain one, and one WITH an old key is the
|
||||
// exception this cannot be built around — so the door is closed here rather
|
||||
// than left ajar for a request that can only fail.
|
||||
//
|
||||
// The code stays: their confirmations still arrive as an ADIF FILE (QSL Manager
|
||||
// → HAMLOG.online → Import confirmations), which never needed a key, and the
|
||||
// sent/received state already in operators' logs stays readable, filterable and
|
||||
// bulk-editable.
|
||||
var ErrHamlogClosed = errors.New("hamlog: HAMLOG.online no longer issues API keys, so uploading is not possible — their confirmations can still be imported from a file")
|
||||
|
||||
func uploadHamlogTo(ctx context.Context, client *http.Client, endpoint string, cfg ServiceConfig, adifRecord string) (UploadResult, error) {
|
||||
return UploadResult{}, ErrHamlogClosed
|
||||
}
|
||||
|
||||
// uploadHamlogLive is the upload as it was, kept whole against the day keys
|
||||
// come back. Nothing calls it.
|
||||
func uploadHamlogLive(ctx context.Context, client *http.Client, endpoint string, cfg ServiceConfig, adifRecord string) (UploadResult, error) {
|
||||
key := strings.TrimSpace(cfg.APIKey)
|
||||
if key == "" {
|
||||
return UploadResult{}, fmt.Errorf("hamlog: API key not set — get one at %s", hamlogKeyPage)
|
||||
|
||||
@@ -3,6 +3,7 @@ package extsvc
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
@@ -28,7 +29,7 @@ func TestUploadHamlogRequestShape(t *testing.T) {
|
||||
}))
|
||||
defer srv.Close()
|
||||
|
||||
res, err := uploadHamlogTo(context.Background(), nil, srv.URL, ServiceConfig{APIKey: "KEY123"}, "<call:5>F4BPO <eor>")
|
||||
res, err := uploadHamlogLive(context.Background(), nil, srv.URL, ServiceConfig{APIKey: "KEY123"}, "<call:5>F4BPO <eor>")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
@@ -64,7 +65,7 @@ func TestHamlogFailureIsNotSuccess(t *testing.T) {
|
||||
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
_, _ = w.Write([]byte(tc.body))
|
||||
}))
|
||||
res, err := uploadHamlogTo(context.Background(), nil, srv.URL, ServiceConfig{APIKey: "K"}, "<eor>")
|
||||
res, err := uploadHamlogLive(context.Background(), nil, srv.URL, ServiceConfig{APIKey: "K"}, "<eor>")
|
||||
srv.Close()
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %v", tc.body, err)
|
||||
@@ -80,8 +81,21 @@ func TestHamlogFailureIsNotSuccess(t *testing.T) {
|
||||
|
||||
// Nothing leaves without a key, and the message says where to get one.
|
||||
func TestUploadHamlogNeedsAKey(t *testing.T) {
|
||||
_, err := UploadHamlog(context.Background(), nil, ServiceConfig{}, "<eor>")
|
||||
_, err := uploadHamlogLive(context.Background(), nil, "http://example.invalid", ServiceConfig{}, "<eor>")
|
||||
if err == nil || !strings.Contains(err.Error(), hamlogKeyPage) {
|
||||
t.Fatalf("err = %v, want it to point at %s", err, hamlogKeyPage)
|
||||
}
|
||||
}
|
||||
|
||||
// The door is shut: HAMLOG.online stopped issuing API keys, so an upload that
|
||||
// could only fail is refused before it is attempted. The request-shape tests
|
||||
// above still cover the code kept against the day keys come back.
|
||||
func TestUploadHamlogIsClosed(t *testing.T) {
|
||||
res, err := UploadHamlog(context.Background(), nil, ServiceConfig{APIKey: "KEY123"}, "<eor>")
|
||||
if !errors.Is(err, ErrHamlogClosed) {
|
||||
t.Fatalf("err = %v, want ErrHamlogClosed", err)
|
||||
}
|
||||
if res.OK {
|
||||
t.Error("a refused upload reported success")
|
||||
}
|
||||
}
|
||||
|
||||
+13
-15
@@ -84,9 +84,10 @@ type Deps struct {
|
||||
type Manager struct {
|
||||
deps Deps
|
||||
|
||||
mu sync.Mutex
|
||||
cfg ExternalServices
|
||||
rnd *rand.Rand
|
||||
mu sync.Mutex
|
||||
cfg ExternalServices
|
||||
rnd *rand.Rand
|
||||
hamlogClosedOnce sync.Once
|
||||
}
|
||||
|
||||
// maxUploadAttempts bounds retries of a transient upload failure.
|
||||
@@ -230,13 +231,13 @@ func (m *Manager) OnQSOLogged(id int64) {
|
||||
m.route(ServiceCloudlog, id, c)
|
||||
}
|
||||
}
|
||||
// HAMLOG.online — one API key and nothing else to get wrong.
|
||||
if h := cfg.Hamlog; h.AutoUpload {
|
||||
if h.APIKey == "" {
|
||||
m.logf("extsvc: hamlog auto-upload is ON but no API key is set (QSO %d not sent)", id)
|
||||
} else {
|
||||
m.route(ServiceHamlog, id, h)
|
||||
}
|
||||
// HAMLOG.online is closed to uploads — see ErrHamlogClosed. Said once per
|
||||
// session rather than per QSO, because an operator who left the switch on
|
||||
// deserves to know why nothing leaves, and does not deserve it every minute.
|
||||
if cfg.Hamlog.AutoUpload {
|
||||
m.hamlogClosedOnce.Do(func() {
|
||||
m.logf("extsvc: %v", ErrHamlogClosed)
|
||||
})
|
||||
}
|
||||
// HamQTH — the callbook credentials double as the logbook login.
|
||||
if h := cfg.HamQTH; h.AutoUpload {
|
||||
@@ -296,9 +297,7 @@ func (m *Manager) onCloseServices() []Service {
|
||||
if c := cfg.Cloudlog; c.AutoUpload && c.UploadMode == ModeOnClose && c.URL != "" && c.APIKey != "" && c.StationID != "" {
|
||||
out = append(out, ServiceCloudlog)
|
||||
}
|
||||
if h := cfg.Hamlog; h.AutoUpload && h.UploadMode == ModeOnClose && h.APIKey != "" {
|
||||
out = append(out, ServiceHamlog)
|
||||
}
|
||||
|
||||
if h := cfg.HamQTH; h.AutoUpload && h.UploadMode == ModeOnClose && h.Username != "" && h.Password != "" {
|
||||
out = append(out, ServiceHamQTH)
|
||||
}
|
||||
@@ -348,8 +347,7 @@ func (m *Manager) FlushOnClose() int {
|
||||
uploaded += m.flushOneByOne(svc, ids, cfg.HRDLog)
|
||||
case ServiceCloudlog:
|
||||
uploaded += m.flushOneByOne(svc, ids, cfg.Cloudlog)
|
||||
case ServiceHamlog:
|
||||
uploaded += m.flushOneByOne(svc, ids, cfg.Hamlog)
|
||||
|
||||
case ServiceHamQTH:
|
||||
uploaded += m.flushOneByOne(svc, ids, cfg.HamQTH)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user