Refactor backend database connection, migration, seed
This commit is contained in:
149
backend/db.go
149
backend/db.go
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user