Files
training-tracker/backend/internal/storage/db.go
2026-01-19 19:49:59 +01:00

110 lines
2.6 KiB
Go

package storage
import (
"context"
"database/sql"
"fmt"
"os"
_ "github.com/lib/pq"
)
type DB struct {
*sql.DB
}
func NewDB(ctx context.Context) (*DB, error) {
host := getEnv("DB_HOST", "localhost")
port := getEnv("DB_PORT", "5432")
user := getEnv("DB_USER", "postgres")
password := getEnv("DB_PASSWORD", "postgres")
dbname := getEnv("DB_NAME", "training_tracker")
connStr := fmt.Sprintf(
"host=%s port=%s user=%s password=%s dbname=%s sslmode=disable",
host, port, user, password, dbname,
)
db, err := openDB(connStr)
if err != nil {
return nil, err
}
if err := db.PingContext(ctx); err != nil {
return nil, fmt.Errorf("failed to ping database: %w", err)
}
return &DB{db}, nil
}
func openDB(connStr string) (*sql.DB, error) {
db, err := sql.Open("postgres", connStr)
if err != nil {
return nil, fmt.Errorf("failed to open database: %w", err)
}
return db, nil
}
func (db *DB) Migrate(ctx context.Context) error {
migrations := []string{
`CREATE TABLE IF NOT EXISTS exercises (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
type VARCHAR(50) NOT NULL,
muscle_group VARCHAR(100),
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE TABLE IF NOT EXISTS training_plans (
id SERIAL PRIMARY KEY,
name VARCHAR(255) NOT NULL,
description TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE TABLE IF NOT EXISTS plan_exercises (
id SERIAL PRIMARY KEY,
plan_id INTEGER REFERENCES training_plans(id) ON DELETE CASCADE,
exercise_id INTEGER REFERENCES exercises(id) ON DELETE CASCADE,
sets INTEGER,
reps INTEGER,
duration INTEGER,
sort_order INTEGER DEFAULT 0
)`,
`CREATE TABLE IF NOT EXISTS sessions (
id SERIAL PRIMARY KEY,
plan_id INTEGER REFERENCES training_plans(id) ON DELETE SET NULL,
date TIMESTAMP NOT NULL,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`,
`CREATE TABLE IF NOT EXISTS session_entries (
id SERIAL PRIMARY KEY,
session_id INTEGER REFERENCES sessions(id) ON DELETE CASCADE,
exercise_id INTEGER REFERENCES exercises(id) ON DELETE CASCADE,
weight DECIMAL(10, 2),
reps INTEGER,
sets_completed INTEGER,
duration INTEGER,
distance DECIMAL(10, 2),
heart_rate INTEGER,
notes TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)`,
}
for _, migration := range migrations {
if _, err := db.ExecContext(ctx, migration); err != nil {
return fmt.Errorf("migration failed: %w", err)
}
}
return nil
}
func getEnv(key, defaultValue string) string {
if value := os.Getenv(key); value != "" {
return value
}
return defaultValue
}