7ace2cc602
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>
83 lines
2.5 KiB
Go
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
|
|
}
|