Refactor backend database connection, migration, seed

This commit is contained in:
2025-10-17 12:41:58 +02:00
parent 176032e54b
commit 560693633e
9 changed files with 163 additions and 99 deletions

View File

@@ -2,16 +2,19 @@ package main
import (
"database/sql"
"embed"
"fmt"
"log"
"log/slog"
"os"
"path/filepath"
"strings"
"github.com/pressly/goose/v3"
_ "modernc.org/sqlite"
)
//go:embed migrations/*.sql
var embedMigrations embed.FS
// Create new sqlite database
// If filepath parameter is empty
func NewDatabaseConnection(dataDir string, dbName string) (*sql.DB, error) {
@@ -35,21 +38,47 @@ func NewDatabaseConnection(dataDir string, dbName string) (*sql.DB, error) {
return db, nil
}
// database
//
// Manage database connection to ./app.db
// Handles CRUD
func database(db *sql.DB) *sql.DB {
if db == nil {
slog.Error("No connection to the database!")
func MigrateDatabase(db *sql.DB) {
dialect := "sqlite3"
goose.SetBaseFS(embedMigrations)
if err := goose.SetDialect(dialect); err != nil {
slog.Error("Database dialect error", "dialect", dialect, "error", err)
os.Exit(1)
}
// Seed default menu items if empty
row := db.QueryRow("SELECT COUNT(*) FROM menu_items")
if err := goose.Up(db, "migrations"); err != nil {
slog.Error("Database migration error", "error", err)
os.Exit(1)
}
migratedVersion, err := goose.GetDBVersion(db)
if err != nil {
slog.Error("Datatabase migration version check error", "error", err)
os.Exit(1)
}
slog.Info("Database up to date", "version", migratedVersion)
}
func SeedDatabase(db *sql.DB, superadminPassword string) error {
seededValue := "false"
row := db.QueryRow("SELECT value FROM settings WHERE key='seeded';")
_ = row.Scan(&seededValue)
if seededValue == "true" {
slog.Info("Database already seeded with default data, skipping")
return nil
}
slog.Info("Database empty, seeding with default values")
row = db.QueryRow("SELECT COUNT(*) FROM menu_items")
var count int
_ = row.Scan(&count)
if count == 0 {
log.Println("Seeding default menu_items")
slog.Info("Seeding default menu_items")
defaults := []struct {
category string
name string
@@ -85,88 +114,32 @@ func database(db *sql.DB) *sql.DB {
for _, d := range defaults {
_, err := db.Exec("INSERT INTO menu_items (category, name) VALUES (?, ?)", d.category, d.name)
if err != nil {
log.Println("Error seeding menu item:", err)
return fmt.Errorf("Error seeding menu_items table: %w", err)
}
}
} else {
slog.Info("Table menu_items already seeded, skipping")
}
// Create users table
_, err := db.Exec(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL UNIQUE,
password TEXT NOT NULL,
role INTEGER NOT NULL DEFAULT 2
)
`)
_, err := db.Exec(`INSERT OR REPLACE INTO settings (key, value) VALUES ('seeded', 'true')`)
if err != nil {
log.Fatal(err)
return fmt.Errorf("Error saving setting table: %w", err)
}
// Create settings table
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS settings (
key TEXT PRIMARY KEY,
value TEXT NOT NULL
)
`)
if err != nil {
log.Fatal(err)
}
updateSuperadminPassword(db, superadminPassword)
// Seed default finalize_time if missing
_, err = db.Exec(`INSERT OR IGNORE INTO settings (key, value) VALUES ('finalize_time', '10:30')`)
if err != nil {
log.Fatal(err)
}
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS menu_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
category TEXT NOT NULL,
name TEXT NOT NULL
)
`)
if err != nil {
log.Fatal(err)
}
// Create selections table (latest choice per user)
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS selections (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
main TEXT NOT NULL,
side TEXT NOT NULL,
soup TEXT,
created_at TEXT NOT NULL,
FOREIGN KEY (user_id) REFERENCES users(id)
)
`)
if err != nil {
log.Fatal(err)
}
// Create orders table (all placed orders for history + realtime updates)
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT NOT NULL,
soup TEXT,
main TEXT NOT NULL,
side TEXT NOT NULL,
created_at TEXT NOT NULL
)
`)
if err != nil {
log.Fatal(err)
}
// Add status column if it doesn't exist
_, err = db.Exec(`ALTER TABLE orders ADD COLUMN status TEXT NOT NULL DEFAULT 'active'`)
if err != nil && !strings.Contains(err.Error(), "duplicate column name") {
log.Fatal(err)
}
return db
return nil
}
func updateSuperadminPassword(db *sql.DB, superadminPassword string) {
_, err := db.Exec(`INSERT OR REPLACE INTO users (id, username, password, role)
VALUES (
1,
'superadmin',
?,
COALESCE((SELECT role FROM users WHERE id = 1), '0')
)`, superadminPassword)
if err != nil {
slog.Error("Error setting superadmin password", "error", err)
}
}