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
+67
View File
@@ -7,10 +7,77 @@ import (
"fmt"
"sort"
"strings"
"time"
_ "github.com/go-sql-driver/mysql"
_ "modernc.org/sqlite"
)
// MySQLConfig targets a shared MySQL database for multi-operator logging
// (multiple OpsLog instances on one logbook, à la Log4OM).
type MySQLConfig struct {
Host string
Port int
User string
Password string
Database string
}
func (c MySQLConfig) dsn() string {
port := c.Port
if port == 0 {
port = 3306
}
// parseTime + UTC so DATETIME columns scan into time.Time; utf8mb4 for full
// Unicode (names, comments…).
return fmt.Sprintf("%s:%s@tcp(%s:%d)/%s?parseTime=true&loc=UTC&charset=utf8mb4",
c.User, c.Password, c.Host, port, c.Database)
}
// validDBIdent guards a database name we splice into DDL (CREATE DATABASE can't
// use a placeholder). Only plain identifiers allowed.
func validDBIdent(s string) bool {
if s == "" {
return false
}
for _, r := range s {
if r != '_' && !(r >= 'a' && r <= 'z') && !(r >= 'A' && r <= 'Z') && !(r >= '0' && r <= '9') {
return false
}
}
return true
}
// PingMySQL verifies a shared-database connection and creates the logbook
// database if it doesn't exist yet. It connects at server level first (no
// database selected) so a not-yet-created DB isn't an error, then runs
// CREATE DATABASE IF NOT EXISTS. Backs the settings "Test connection" button.
func PingMySQL(c MySQLConfig) error {
if strings.TrimSpace(c.Host) == "" {
return fmt.Errorf("host is required")
}
server := c
server.Database = "" // connect to the server, not a specific DB
conn, err := sql.Open("mysql", server.dsn())
if err != nil {
return fmt.Errorf("open mysql: %w", err)
}
defer conn.Close()
conn.SetConnMaxLifetime(5 * time.Second)
if err := conn.Ping(); err != nil {
return fmt.Errorf("connect to %s:%d: %w", c.Host, c.Port, err)
}
if name := strings.TrimSpace(c.Database); name != "" {
if !validDBIdent(name) {
return fmt.Errorf("invalid database name %q (letters, digits, underscore only)", name)
}
if _, err := conn.Exec("CREATE DATABASE IF NOT EXISTS `" + name + "` CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci"); err != nil {
return fmt.Errorf("create database %q: %w", name, err)
}
}
return nil
}
//go:embed migrations/*.sql
var migrationsFS embed.FS