Files
rouggy 7ace2cc602 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>
2026-05-26 00:16:45 +02:00

83 lines
2.5 KiB
Go

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
}