fix: Upload to HRDLog

This commit is contained in:
2026-06-18 22:58:00 +02:00
parent 4d074de27e
commit 183db7ac2b
4 changed files with 161 additions and 144 deletions
+3 -56
View File
@@ -6,7 +6,6 @@ import (
"io"
"net/http"
"net/url"
"strconv"
"strings"
"time"
)
@@ -117,61 +116,9 @@ func UploadHRDLog(ctx context.Context, client *http.Client, callsign, code, adif
return UploadResult{OK: false, Message: reason}, fmt.Errorf("hrdlog: upload failed: %s", reason)
}
// UploadHRDLogADIF pushes a WHOLE ADIF document (header + many records) to
// HRDLog.net in one NewEntry request — HRDLog parses every <eor> record and
// replies "<insert>N" with the number actually inserted (duplicates aren't
// counted but aren't errors). Use this for bulk uploads instead of calling
// UploadHRDLog once per QSO.
func UploadHRDLogADIF(ctx context.Context, client *http.Client, callsign, code, adifDoc string) (UploadResult, error) {
callsign = strings.ToUpper(strings.TrimSpace(callsign))
code = strings.TrimSpace(code)
if callsign == "" {
return UploadResult{}, fmt.Errorf("hrdlog: station callsign not set")
}
if code == "" {
return UploadResult{}, fmt.Errorf("hrdlog: upload code not set")
}
if strings.TrimSpace(adifDoc) == "" {
return UploadResult{}, fmt.Errorf("hrdlog: empty adif")
}
body, err := hrdlogPost(ctx, client, callsign, code, adifDoc)
if err != nil {
return UploadResult{OK: false, Message: body}, err
}
if reason := authErrHRDLog(body); reason != "" {
return UploadResult{OK: false, Message: reason}, fmt.Errorf("hrdlog: %s", reason)
}
if n, ok := parseHRDLogInsert(body); ok {
return UploadResult{OK: true, Message: fmt.Sprintf("%d added", n)}, nil
}
if strings.Contains(strings.ToLower(body), "<error>") {
return UploadResult{OK: false, Message: body}, fmt.Errorf("hrdlog: %s", body)
}
return UploadResult{OK: true, Message: "uploaded"}, nil
}
// parseHRDLogInsert reads N from "<insert>N" (or "<insert>N</insert>").
func parseHRDLogInsert(body string) (int, bool) {
b := strings.ToLower(body)
i := strings.Index(b, "<insert>")
if i < 0 {
return 0, false
}
rest := b[i+len("<insert>"):]
j := 0
for j < len(rest) && rest[j] >= '0' && rest[j] <= '9' {
j++
}
if j == 0 {
return 0, false
}
n, err := strconv.Atoi(rest[:j])
if err != nil {
return 0, false
}
return n, true
}
// NOTE: HRDLog's NewEntry.aspx inserts ONLY the first record of a multi-record
// ADIFData, so there is no batch upload — callers must POST one record per
// request (see UploadHRDLog). The bulk uploader in app.go does exactly that.
// TestHRDLog validates the configured HRDLog credentials with a REAL request:
// it posts an empty ADIF so nothing is inserted, then checks for HRDLog's auth
+23
View File
@@ -625,6 +625,29 @@ func (r *Repo) MarkLoTWUploaded(ctx context.Context, id int64, date string) erro
return nil
}
// MarkUploadedBatch sets <statusCol>='Y' and <dateCol>=date on EVERY id in one
// UPDATE — used by bulk upload (Club Log / HRDLog) so a 25k-QSO run isn't one
// round-trip per QSO on a remote MySQL. statusCol/dateCol come from a fixed
// whitelist (not user input), so the column interpolation is safe.
func (r *Repo) MarkUploadedBatch(ctx context.Context, statusCol, dateCol, date string, ids []int64) error {
if len(ids) == 0 {
return nil
}
ph := strings.TrimSuffix(strings.Repeat("?,", len(ids)), ",")
args := make([]any, 0, len(ids)+2)
args = append(args, date, db.NowISO())
for _, id := range ids {
args = append(args, id)
}
_, err := r.db.ExecContext(ctx,
`UPDATE qso SET `+statusCol+` = 'Y', `+dateCol+` = ?, updated_at = ? WHERE id IN (`+ph+`)`,
args...)
if err != nil {
return fmt.Errorf("mark uploaded batch (%d): %w", len(ids), err)
}
return nil
}
// MarkEQSLSent stamps EQSL_QSL_SENT=Y and the sent date after a successful
// eQSL e-mail. date is an ADIF YYYYMMDD string.
func (r *Repo) MarkEQSLSent(ctx context.Context, id int64, date string) error {