This commit is contained in:
2026-06-13 21:56:38 +02:00
parent 81e505e040
commit 08162fa126
9 changed files with 257 additions and 15 deletions
+87
View File
@@ -1020,6 +1020,84 @@ func (a *App) GetDatabaseSettings() DatabaseSettings {
return DatabaseSettings{Path: a.dbPath, DefaultPath: def, IsCustom: a.dbPath != def}
}
// MySQLSettings is the shared-database (multi-operator) connection config. When
// enabled, OpsLog logs to a central MySQL server so several operators see each
// other's QSOs live (à la Log4OM). SQLite stays the default.
type MySQLSettings struct {
Enabled bool `json:"enabled"`
Host string `json:"host"`
Port int `json:"port"`
User string `json:"user"`
Password string `json:"password"`
Database string `json:"database"`
}
const (
keyMySQLEnabled = "mysql.enabled"
keyMySQLHost = "mysql.host"
keyMySQLPort = "mysql.port"
keyMySQLUser = "mysql.user"
keyMySQLPassword = "mysql.password"
keyMySQLDatabase = "mysql.database"
)
// GetMySQLSettings returns the stored shared-database config (defaults applied).
func (a *App) GetMySQLSettings() (MySQLSettings, error) {
out := MySQLSettings{Port: 3306}
if a.settings == nil {
return out, nil
}
m, err := a.settings.GetMany(a.ctx, keyMySQLEnabled, keyMySQLHost, keyMySQLPort, keyMySQLUser, keyMySQLPassword, keyMySQLDatabase)
if err != nil {
return out, err
}
out.Enabled = m[keyMySQLEnabled] == "1"
out.Host = m[keyMySQLHost]
if p, _ := strconv.Atoi(m[keyMySQLPort]); p > 0 {
out.Port = p
}
out.User = m[keyMySQLUser]
out.Password = m[keyMySQLPassword]
out.Database = m[keyMySQLDatabase]
return out, nil
}
// SaveMySQLSettings persists the shared-database config. (Switching the active
// backend takes effect on restart — wired in a later phase.)
func (a *App) SaveMySQLSettings(s MySQLSettings) error {
if a.settings == nil {
return fmt.Errorf("db not initialized")
}
if s.Port <= 0 {
s.Port = 3306
}
enabled := "0"
if s.Enabled {
enabled = "1"
}
for k, v := range map[string]string{
keyMySQLEnabled: enabled,
keyMySQLHost: strings.TrimSpace(s.Host),
keyMySQLPort: strconv.Itoa(s.Port),
keyMySQLUser: strings.TrimSpace(s.User),
keyMySQLPassword: s.Password,
keyMySQLDatabase: strings.TrimSpace(s.Database),
} {
if err := a.settings.Set(a.ctx, k, v); err != nil {
return err
}
}
return nil
}
// TestMySQLConnection pings the shared MySQL database with the given settings
// (no migrations) so the user can validate connectivity from the UI.
func (a *App) TestMySQLConnection(s MySQLSettings) error {
return db.PingMySQL(db.MySQLConfig{
Host: s.Host, Port: s.Port, User: s.User, Password: s.Password, Database: s.Database,
})
}
// PickOpenDatabase opens a file dialog to choose an existing .db file.
func (a *App) PickOpenDatabase() (string, error) {
if a.ctx == nil {
@@ -2819,6 +2897,15 @@ func (a *App) WorkedBefore(callsign string, dxccHint int) (qso.WorkedBefore, err
if a.qso == nil {
return qso.WorkedBefore{}, fmt.Errorf("db not initialized")
}
// When the frontend lookup didn't carry a DXCC number (a QRZ cache hit may
// have the country name but no number), resolve it from the callsign via
// cty.dat + Clublog exceptions — the same source QSOs are logged with — so
// the entity matrix populates even for a call we've never worked directly.
if dxccHint == 0 && a.dxcc != nil {
if m, ok := a.dxcc.Lookup(callsign); ok && m.Entity != nil {
dxccHint = dxcc.EntityDXCC(m.Entity.Name)
}
}
return a.qso.WorkedBefore(a.ctx, callsign, dxccHint)
}