Initial codebase: Go + Wails amateur radio logbook
Backend (Go 1.25 / Wails v2): - QSO storage on SQLite (modernc) with embedded migrations (0001..0005) - Streaming ADIF import (batch insert) + WorkedBefore per callsign and DXCC - Callsign lookup with QRZ.com + HamQTH providers (primary/failsafe routing) and SQLite-backed TTL cache - DXCC resolver from cty.dat (auto-download, longest-prefix-match) - Multi-profile operator identities (home/portable/SOTA/contest) — every QSO stamps MY_* from the active profile - CAT control via OmniRig COM on a single OS-locked goroutine, with bidirectional sync (freq/mode/band/split/VFOs) and Rig1/Rig2 hot-swap - Settings store (key/value), CAT debug log at %APPDATA%/HamLog/cat.log Frontend (React 18 + TypeScript + Tailwind v4 + shadcn-style): - Single-row entry strip with CAT-aware band/mode/freq, RST, Start/End UTC, per-field locks (band/mode/freq/start/end) for backdated QSOs - Topbar: live freq (MHz.kHz.Hz dotted), live UTC, band/mode/SPLIT badges, CAT pill with rig selector and clickable Azimuth pill (rotor TODO) - Settings tree: Profiles (Log4OM-style manager), Station Information (edits the active profile), unified Callsign Lookup with Test buttons, Bands/Modes lists, CAT - Worked-before matrix (band × mode × class) with new-DXCC highlighting - ADIF import from menu + Maintenance > Refresh cty.dat Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
package profile
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
)
|
||||
|
||||
// SettingsReader is the minimal slice of the settings store we need for
|
||||
// migrating legacy single-station settings into the first profile.
|
||||
type SettingsReader interface {
|
||||
GetMany(ctx context.Context, keys ...string) (map[string]string, error)
|
||||
}
|
||||
|
||||
// EnsureDefault makes sure at least one profile exists and one is active.
|
||||
//
|
||||
// First-run path: if the table is empty, build a "Default" profile from
|
||||
// the legacy `station.*` settings keys and mark it active — so the user's
|
||||
// existing config carries over invisibly. Returns the active profile.
|
||||
//
|
||||
// The legacy key names are passed in from the caller (app.go) so this
|
||||
// package doesn't need to import or duplicate them.
|
||||
func EnsureDefault(ctx context.Context, db *sql.DB, settings SettingsReader, legacyKeys LegacyStationKeys) (Profile, error) {
|
||||
repo := NewRepo(db)
|
||||
var count int
|
||||
if err := db.QueryRowContext(ctx, `SELECT COUNT(*) FROM station_profiles`).Scan(&count); err != nil {
|
||||
return Profile{}, err
|
||||
}
|
||||
if count == 0 {
|
||||
seed, err := buildSeedFromSettings(ctx, settings, legacyKeys)
|
||||
if err != nil {
|
||||
return Profile{}, err
|
||||
}
|
||||
seed.IsActive = true
|
||||
if err := repo.Save(ctx, &seed); err != nil {
|
||||
return Profile{}, err
|
||||
}
|
||||
return seed, nil
|
||||
}
|
||||
// Profiles exist but none active (manual DB edit, deleted active row
|
||||
// outside the app, …) — promote the first one.
|
||||
if active, err := repo.Active(ctx); err == nil {
|
||||
return active, nil
|
||||
}
|
||||
var firstID int64
|
||||
if err := db.QueryRowContext(ctx,
|
||||
`SELECT id FROM station_profiles ORDER BY sort_order ASC, id ASC LIMIT 1`).Scan(&firstID); err != nil {
|
||||
return Profile{}, err
|
||||
}
|
||||
if err := repo.SetActive(ctx, firstID); err != nil {
|
||||
return Profile{}, err
|
||||
}
|
||||
return repo.Get(ctx, firstID)
|
||||
}
|
||||
|
||||
// LegacyStationKeys names the pre-profiles settings keys used to seed
|
||||
// the first profile. Kept as a struct so adding/renaming a key doesn't
|
||||
// silently fall through to an empty default.
|
||||
type LegacyStationKeys struct {
|
||||
Callsign string
|
||||
Operator string
|
||||
MyGrid string
|
||||
Country string
|
||||
SOTA string
|
||||
POTA string
|
||||
}
|
||||
|
||||
func buildSeedFromSettings(ctx context.Context, settings SettingsReader, keys LegacyStationKeys) (Profile, error) {
|
||||
m, err := settings.GetMany(ctx,
|
||||
keys.Callsign, keys.Operator, keys.MyGrid, keys.Country, keys.SOTA, keys.POTA)
|
||||
if err != nil {
|
||||
return Profile{}, err
|
||||
}
|
||||
return Profile{
|
||||
Name: "Default",
|
||||
Callsign: m[keys.Callsign],
|
||||
Operator: m[keys.Operator],
|
||||
MyGrid: m[keys.MyGrid],
|
||||
MyCountry: m[keys.Country],
|
||||
MySOTARef: m[keys.SOTA],
|
||||
MyPOTARef: m[keys.POTA],
|
||||
}, nil
|
||||
}
|
||||
Reference in New Issue
Block a user