feat: per-profile separate SQLite logbook file (config never swapped)

Design flaw: the SQLite backend conflated the app/config database (opslog.db —
settings + profiles) with the QSO logbook. connectLogbook returned a.db for any
non-MySQL profile, so the ONLY way to get a separate SQLite logbook was the
whole-app "change database location" pointer — which swapped config + profiles
too, wiping the operator's setup (reported: creating Jerem.db lost all configs).

profile.ProfileDB gains a Path field: a non-empty path routes that profile's
logbook to its own SQLite file (db.Open creates + migrates it on first use),
while opslog.db keeps settings/profiles. connectLogbook opens the file; empty
path = shared db (unchanged default, backward compatible). MySQLSettings carries
sqlite_path; Get/Save wire it through.

UI: the Database panel gains a third backend option "SQLite — separate file"
with a file picker, and now clearly labels the shared application database
(settings + profiles) vs this profile's logbook, so the two are never confused.
This commit is contained in:
2026-07-24 17:12:31 +02:00
parent bf4fba484a
commit 638ffcb326
6 changed files with 80 additions and 11 deletions
+19
View File
@@ -1563,6 +1563,9 @@ type MySQLSettings struct {
User string `json:"user"`
Password string `json:"password"`
Database string `json:"database"`
// SqlitePath, when set (and Enabled=false), routes this profile's logbook to
// its OWN SQLite file instead of the shared app database. Empty = shared.
SqlitePath string `json:"sqlite_path,omitempty"`
}
// DBBackendStatus reports which backend OpsLog actually opened at startup so
@@ -1620,6 +1623,17 @@ func (a *App) connectLogbook(cfg profile.ProfileDB) (*sql.DB, string, error) {
}
return c, "mysql", nil
}
// A per-profile SQLite logbook FILE (separate from the app/config database).
// db.Open creates it and runs the schema migrations if it doesn't exist yet,
// so pointing a profile at a fresh name just works. Empty path = the shared
// app database (a.db) is the logbook, as before.
if p := strings.TrimSpace(cfg.Path); p != "" {
c, err := db.Open(p)
if err != nil {
return nil, "", fmt.Errorf("open logbook %s: %w", p, err)
}
return c, "sqlite", nil
}
return a.db, "sqlite", nil
}
@@ -1684,6 +1698,7 @@ func (a *App) GetMySQLSettings() (MySQLSettings, error) {
d := p.DB
out.Enabled = d.Backend == "mysql"
out.Host, out.User, out.Password, out.Database = d.Host, d.User, d.Password, d.Database
out.SqlitePath = d.Path
if d.Port > 0 {
out.Port = d.Port
}
@@ -1708,6 +1723,10 @@ func (a *App) SaveMySQLSettings(s MySQLSettings) error {
cfg.Port = s.Port
cfg.User = strings.TrimSpace(s.User)
cfg.Password = s.Password
} else if sp := strings.TrimSpace(s.SqlitePath); sp != "" {
// Separate per-profile SQLite logbook file (config stays in opslog.db).
cfg.Backend = "sqlite"
cfg.Path = sp
}
if err := a.profiles.SetDB(a.ctx, p.ID, cfg); err != nil {
return err