all repos — go-lift @ 35d89fd489cd99e63cbf7b49fe5e9088f5b13317

Lightweight workout tracker prototype..

src/database/models.go (view raw)

  1package database
  2
  3import (
  4	"log"
  5	"os"
  6	"path/filepath"
  7	"time"
  8
  9	"github.com/glebarez/sqlite"
 10	"gorm.io/gorm"
 11	"gorm.io/gorm/logger"
 12)
 13
 14type Database struct {
 15	*gorm.DB
 16}
 17
 18type User struct {
 19	ID        uint           `gorm:"primaryKey" json:"id"`
 20	Name      string         `gorm:"size:50" json:"name"`
 21	IsFemale  bool           `json:"isFemale"`
 22	Height    *float64       `json:"height"` // In cm
 23	Weight    *float64       `json:"weight"` // In kg
 24	BirthDate *time.Time     `json:"birthDate"`
 25	CreatedAt time.Time      `json:"createdAt"`
 26	UpdatedAt time.Time      `json:"updatedAt"`
 27	DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
 28}
 29
 30type Exercise struct {
 31	ID           uint    `gorm:"primaryKey;autoIncrement" json:"id"`
 32	Name         string  `gorm:"not null;uniqueIndex" json:"name"`
 33	Level        string  `gorm:"size:50;not null" json:"level"`
 34	Category     string  `gorm:"size:50;not null" json:"category"`
 35	Force        *string `gorm:"size:50" json:"force"`
 36	Mechanic     *string `gorm:"size:50" json:"mechanic"`
 37	Equipment    *string `gorm:"size:50" json:"equipment"`
 38	Instructions *string `json:"instructions"`
 39
 40	PrimaryMuscles   *string `json:"primaryMuscles"`
 41	SecondaryMuscles *string `json:"secondaryMuscles"`
 42
 43	CreatedAt time.Time `json:"createdAt"`
 44	UpdatedAt time.Time `json:"updatedAt"`
 45}
 46
 47// Routine represents a workout routine blueprint
 48type Routine struct {
 49	ID          uint      `gorm:"primaryKey" json:"id"`
 50	Name        string    `gorm:"size:100;not null" json:"name"`
 51	Description string    `gorm:"size:500" json:"description"`
 52	CreatedAt   time.Time `json:"createdAt"`
 53	UpdatedAt   time.Time `json:"updatedAt"`
 54
 55	Items []RoutineItem `json:"items"`
 56}
 57
 58// RoutineItem can be either a single exercise or a superset
 59type RoutineItem struct {
 60	ID         uint      `gorm:"primaryKey" json:"id"`
 61	RoutineID  uint      `gorm:"index;not null" json:"routineId"`
 62	Type       string    `gorm:"size:20;not null" json:"type"` // "exercise" or "superset"
 63	RestTime   int       `gorm:"default:0" json:"restTime"`    // In seconds
 64	OrderIndex int       `gorm:"not null" json:"orderIndex"`
 65	CreatedAt  time.Time `json:"createdAt"`
 66	UpdatedAt  time.Time `json:"updatedAt"`
 67
 68	Routine       Routine        `json:"-"`
 69	ExerciseItems []ExerciseItem `json:"exerciseItems,omitempty"` // For both single exercises and superset items
 70}
 71
 72// ExerciseItem represents an exercise within a routine item (could be standalone or part of superset)
 73type ExerciseItem struct {
 74	ID            uint      `gorm:"primaryKey" json:"id"`
 75	RoutineItemID uint      `gorm:"index;not null" json:"routineItemId"`
 76	ExerciseID    uint      `gorm:"index;not null" json:"exerciseId"`
 77	OrderIndex    int       `gorm:"not null" json:"orderIndex"`
 78	CreatedAt     time.Time `json:"createdAt"`
 79	UpdatedAt     time.Time `json:"updatedAt"`
 80
 81	RoutineItem RoutineItem `json:"-"`
 82	Exercise    Exercise    `json:"exercise"`
 83	Sets        []Set       `json:"sets"`
 84}
 85
 86// Set represents a planned set within an exercise
 87type Set struct {
 88	ID             uint      `gorm:"primaryKey" json:"id"`
 89	ExerciseItemID uint      `gorm:"index;not null" json:"exerciseItemId"`
 90	Reps           int       `json:"reps"`
 91	Weight         float64   `json:"weight"`
 92	Duration       int       `json:"duration"` // In seconds
 93	OrderIndex     int       `gorm:"not null" json:"orderIndex"`
 94	CreatedAt      time.Time `json:"createdAt"`
 95	UpdatedAt      time.Time `json:"updatedAt"`
 96
 97	ExerciseItem ExerciseItem `json:"-"`
 98}
 99
100// ===== RECORD MODELS (for actual workout completion) =====
101
102// RecordRoutine records a completed workout session
103type RecordRoutine struct {
104	ID        uint      `gorm:"primaryKey" json:"id"`
105	RoutineID uint      `gorm:"index;not null" json:"routineId"`
106	Duration  *uint     `json:"duration"` // In seconds
107	CreatedAt time.Time `json:"createdAt"`
108	UpdatedAt time.Time `json:"updatedAt"`
109
110	Routine     Routine      `json:"routine"`
111	RecordItems []RecordItem `json:"recordItems"`
112}
113
114// RecordItem records completion of a routine item (exercise or superset)
115type RecordItem struct {
116	ID              uint      `gorm:"primaryKey" json:"id"`
117	RecordRoutineID uint      `gorm:"index;not null" json:"recordRoutineId"`
118	RoutineItemID   uint      `gorm:"index;not null" json:"routineItemId"`
119	Duration        *uint     `json:"duration"`       // In seconds
120	ActualRestTime  *int      `json:"actualRestTime"` // In seconds
121	OrderIndex      int       `gorm:"not null" json:"orderIndex"`
122	CreatedAt       time.Time `json:"createdAt"`
123	UpdatedAt       time.Time `json:"updatedAt"`
124
125	RecordRoutine       RecordRoutine        `json:"-"`
126	RoutineItem         RoutineItem          `json:"routineItem"`
127	RecordExerciseItems []RecordExerciseItem `json:"recordExerciseItems"`
128}
129
130// RecordExerciseItem records completion of an exercise within a routine item
131type RecordExerciseItem struct {
132	ID             uint      `gorm:"primaryKey" json:"id"`
133	RecordItemID   uint      `gorm:"index;not null" json:"recordItemId"`
134	ExerciseItemID uint      `gorm:"index;not null" json:"exerciseItemId"`
135	OrderIndex     int       `gorm:"not null" json:"orderIndex"`
136	CreatedAt      time.Time `json:"createdAt"`
137	UpdatedAt      time.Time `json:"updatedAt"`
138
139	RecordItem   RecordItem   `json:"-"`
140	ExerciseItem ExerciseItem `json:"exerciseItem"`
141	RecordSets   []RecordSet  `json:"recordSets"`
142}
143
144// RecordSet records completion of an actual set
145type RecordSet struct {
146	ID                   uint      `gorm:"primaryKey" json:"id"`
147	RecordExerciseItemID uint      `gorm:"index;not null" json:"recordExerciseItemId"`
148	SetID                uint      `gorm:"index;not null" json:"setId"`
149	ActualReps           int       `json:"actualReps"`
150	ActualWeight         float64   `json:"actualWeight"`
151	ActualDuration       int       `json:"actualDuration"` // In seconds
152	CompletedAt          time.Time `gorm:"not null" json:"completedAt"`
153	OrderIndex           int       `gorm:"not null" json:"orderIndex"`
154	CreatedAt            time.Time `json:"createdAt"`
155	UpdatedAt            time.Time `json:"updatedAt"`
156
157	RecordExerciseItem RecordExerciseItem `json:"-"`
158	Set                Set                `json:"set"`
159}
160
161// InitializeDB creates and initializes the SQLite database with all models
162func InitializeDB() (db *Database, err error) {
163	// Create the data directory if it doesn't exist
164	if _, err = os.Stat(dbDir); os.IsNotExist(err) {
165		err = os.MkdirAll(dbDir, 0755)
166		if err != nil {
167			return
168		}
169	}
170
171	dbPath := filepath.Join(dbDir, dbName)
172
173	// Set up logger for GORM
174	newLogger := logger.New(
175		log.New(os.Stdout, "\r\n", log.LstdFlags),
176		logger.Config{
177			SlowThreshold: time.Second,
178			LogLevel:      logger.Info,
179			Colorful:      true,
180		},
181	)
182
183	dialector := sqlite.Open(dbPath + "?_pragma=foreign_keys(1)")
184	config := &gorm.Config{Logger: newLogger}
185
186	// Open connection to the database
187	conn, err := gorm.Open(dialector, config)
188	if err != nil {
189		return
190	}
191
192	// Get the underlying SQL database to set connection parameters
193	sqlDB, err := conn.DB()
194	if err != nil {
195		return nil, err
196	}
197
198	// Set connection pool settings
199	sqlDB.SetMaxIdleConns(10)
200	sqlDB.SetMaxOpenConns(100)
201	sqlDB.SetConnMaxLifetime(time.Hour)
202
203	// Auto migrate the models in correct order
204	err = conn.AutoMigrate(
205		&User{},
206		&Exercise{},
207		&Routine{},
208		&RoutineItem{},
209		&ExerciseItem{},
210		&Set{},
211		&RecordRoutine{},
212		&RecordItem{},
213		&RecordExerciseItem{},
214		&RecordSet{},
215	)
216	if err != nil {
217		return
218	}
219
220	db = &Database{conn}
221
222	// Ensure initial data is present
223	err = db.CheckInitialData()
224	if err != nil {
225		return nil, err
226	}
227
228	return db, nil
229}