Added proper db migrations
This commit is contained in:
parent
54497370ce
commit
d381528db1
|
|
@ -1,44 +0,0 @@
|
|||
package app
|
||||
|
||||
import "database/sql"
|
||||
|
||||
func initializeDb(databaseUrl string) *sql.DB {
|
||||
db, err := sql.Open("sqlite3", databaseUrl)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS sensor_values (
|
||||
timestamp INTEGER NOT NULL,
|
||||
sensor TEXT NOT NULL,
|
||||
value REAL NOT NULL
|
||||
);`)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS sensor_config (
|
||||
sensor TEXT NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value REAL NOT NULL,
|
||||
PRIMARY KEY (sensor, key)
|
||||
);`)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS sessions (
|
||||
id TEXT NOT NULL,
|
||||
expires_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
);`)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return db
|
||||
}
|
||||
|
|
@ -2,6 +2,7 @@ package app
|
|||
|
||||
import (
|
||||
"basic-sensor-receiver/config"
|
||||
"basic-sensor-receiver/database"
|
||||
"basic-sensor-receiver/services"
|
||||
"database/sql"
|
||||
)
|
||||
|
|
@ -15,7 +16,14 @@ type Server struct {
|
|||
func InitializeServer() *Server {
|
||||
server := Server{}
|
||||
server.Config = config.LoadConfig()
|
||||
server.DB = initializeDb(server.Config.DatabaseUrl)
|
||||
|
||||
db, err := database.Initialize(server.Config.DatabaseUrl)
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
server.DB = db
|
||||
|
||||
ctx := services.Context{DB: server.DB, Config: server.Config}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,52 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Initialize(databaseUrl string) (*sql.DB, error) {
|
||||
db, err := sql.Open("sqlite3", databaseUrl)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open database connection: %w", err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(`CREATE TABLE IF NOT EXISTS migrations (
|
||||
id TEXT NOT NULL,
|
||||
run_at INTEGER NOT NULL
|
||||
);`)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to initialize migrations table: %w", err)
|
||||
}
|
||||
|
||||
pendingMigrations, err := getPendingMigrations(db)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to get pending migrations: %w", err)
|
||||
}
|
||||
|
||||
for _, migrationId := range pendingMigrations {
|
||||
migrationSource, err := getMigrationContent(migrationId)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = db.Exec(migrationSource)
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to run migration %s: %w", migrationId, err)
|
||||
}
|
||||
|
||||
_, err = db.Exec("INSERT INTO migrations (id, run_at) VALUES (?, ?)", migrationId, time.Now().Unix())
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to store run migration: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
return db, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
package database
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"embed"
|
||||
"fmt"
|
||||
"io/fs"
|
||||
"sort"
|
||||
"strings"
|
||||
)
|
||||
|
||||
//go:embed migrations/*.sql
|
||||
var migrationStorage embed.FS
|
||||
|
||||
func getAppliedMigrationsSet(db *sql.DB) (map[string]bool, error) {
|
||||
set := make(map[string]bool)
|
||||
|
||||
rows, err := db.Query("SELECT id FROM migrations")
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
defer rows.Close()
|
||||
|
||||
for rows.Next() {
|
||||
var id string
|
||||
|
||||
err := rows.Scan(&id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
set[id] = true
|
||||
}
|
||||
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return set, nil
|
||||
}
|
||||
|
||||
func getMigrations() ([]string, error) {
|
||||
var items []string
|
||||
|
||||
entries, err := migrationStorage.ReadDir("migrations")
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, entry := range entries {
|
||||
if !entry.IsDir() && strings.HasSuffix(entry.Name(), ".sql") {
|
||||
items = append(items, strings.TrimSuffix(entry.Name(), ".sql"))
|
||||
}
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
sort.Strings(items)
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
func getMigrationContent(id string) (string, error) {
|
||||
content, err := fs.ReadFile(migrationStorage, "migrations/"+id+".sql")
|
||||
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
return string(content), nil
|
||||
}
|
||||
|
||||
func getPendingMigrations(db *sql.DB) ([]string, error) {
|
||||
var migrationsToRun []string
|
||||
|
||||
migrationSet, err := getAppliedMigrationsSet(db)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
storedMigrations, err := getMigrations()
|
||||
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to load stored migrations: %w", err)
|
||||
}
|
||||
|
||||
for _, storedMigration := range storedMigrations {
|
||||
if !migrationSet[storedMigration] {
|
||||
migrationsToRun = append(migrationsToRun, storedMigration)
|
||||
}
|
||||
}
|
||||
|
||||
return migrationsToRun, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
CREATE TABLE IF NOT EXISTS sensor_values (
|
||||
timestamp INTEGER NOT NULL,
|
||||
sensor TEXT NOT NULL,
|
||||
value REAL NOT NULL
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sensor_config (
|
||||
sensor TEXT NOT NULL,
|
||||
key TEXT NOT NULL,
|
||||
value REAL NOT NULL,
|
||||
PRIMARY KEY (sensor, key)
|
||||
);
|
||||
|
||||
CREATE TABLE IF NOT EXISTS sessions (
|
||||
id TEXT NOT NULL,
|
||||
expires_at INTEGER NOT NULL,
|
||||
PRIMARY KEY (id)
|
||||
);
|
||||
Loading…
Reference in New Issue