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
14const (
15 dbDir = "data"
16 dbName = "fitness.db"
17)
18
19type Equipment struct {
20 ID uint `gorm:"primaryKey" json:"id"`
21 Name string `gorm:"size:100;not null;uniqueIndex" json:"name"`
22 Description string `gorm:"size:500" json:"description"`
23 CreatedAt time.Time `json:"created_at"`
24 UpdatedAt time.Time `json:"updated_at"`
25 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
26
27 Exercises []Exercise `gorm:"many2many:exercise_equipment" json:"exercises,omitempty"`
28}
29
30type MuscleGroup struct {
31 ID uint `gorm:"primaryKey" json:"id"`
32 Name string `gorm:"size:100;not null;uniqueIndex" json:"name"`
33 CreatedAt time.Time `json:"created_at"`
34 UpdatedAt time.Time `json:"updated_at"`
35 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
36
37 Exercises []Exercise `gorm:"many2many:exercise_muscle_groups" json:"exercises,omitempty"`
38}
39
40type ExerciseMuscleGroup struct {
41 ID uint `gorm:"primaryKey" json:"id"`
42 ExerciseID uint `gorm:"uniqueIndex:idx_exercise_muscle_group" json:"exercise_id"`
43 MuscleGroupID uint `gorm:"uniqueIndex:idx_exercise_muscle_group" json:"muscle_group_id"`
44
45 Exercise Exercise `json:"exercise"`
46 MuscleGroup MuscleGroup `json:"muscle_group"`
47}
48
49type Exercise struct {
50 ID uint `gorm:"primaryKey" json:"id"`
51 Name string `gorm:"size:100;not null" json:"name"`
52 Description string `gorm:"size:500" json:"description"`
53 //UserID uint `gorm:"index" json:"user_id"`
54 //IsPublic bool `gorm:"default:false" json:"is_public"`
55 CreatedAt time.Time `json:"created_at"`
56 UpdatedAt time.Time `json:"updated_at"`
57 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
58
59 //User User `json:"-"`
60 Equipment []Equipment `gorm:"many2many:exercise_equipment" json:"equipment"`
61 MuscleGroups []MuscleGroup `gorm:"many2many:exercise_muscle_groups" json:"muscle_groups"`
62 Sets []Set `json:"sets,omitempty"`
63}
64
65type Set struct {
66 ID uint `gorm:"primaryKey" json:"id"`
67 ExerciseID uint `gorm:"index" json:"exercise_id"`
68 Reps int `json:"reps"`
69 Weight float64 `json:"weight"`
70 Duration int `json:"duration"` // In seconds, for timed exercises
71 OrderIndex int `gorm:"not null" json:"order_index"`
72 CreatedAt time.Time `json:"created_at"`
73 UpdatedAt time.Time `json:"updated_at"`
74 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
75
76 Exercise Exercise `json:"-"`
77}
78
79// SuperSet to handle two exercises with single rest time
80type SuperSet struct {
81 ID uint `gorm:"primaryKey" json:"id"`
82 Name string `gorm:"size:100" json:"name"`
83 PrimaryExerciseID uint `gorm:"index" json:"primary_exercise_id"`
84 SecondaryExerciseID uint `gorm:"index" json:"secondary_exercise_id"`
85 RestTime int `gorm:"default:0" json:"rest_time"` // In seconds
86 CreatedAt time.Time `json:"created_at"`
87 UpdatedAt time.Time `json:"updated_at"`
88 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
89
90 PrimaryExercise Exercise `json:"primary_exercise"`
91 SecondaryExercise Exercise `json:"secondary_exercise"`
92}
93
94// RoutineItem represents either an Exercise or a SuperSet in a Routine
95type RoutineItem struct {
96 ID uint `gorm:"primaryKey" json:"id"`
97 RoutineID uint `gorm:"index" json:"routine_id"`
98 ExerciseID *uint `gorm:"index" json:"exercise_id"`
99 SuperSetID *uint `gorm:"index" json:"super_set_id"`
100 RestTime int `gorm:"default:0" json:"rest_time"` // In seconds
101 OrderIndex int `gorm:"not null" json:"order_index"`
102 CreatedAt time.Time `json:"created_at"`
103 UpdatedAt time.Time `json:"updated_at"`
104 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
105
106 Routine Routine `json:"-"`
107 SuperSet *SuperSet `json:"super_set,omitempty"`
108 Exercise *Exercise `json:"exercise,omitempty"`
109}
110
111type Routine struct {
112 ID uint `gorm:"primaryKey" json:"id"`
113 Name string `gorm:"size:100;not null" json:"name"`
114 Description string `gorm:"size:500" json:"description"`
115 //UserID uint `gorm:"index" json:"user_id"`
116 //IsPublic bool `gorm:"default:false" json:"is_public"`
117 CreatedAt time.Time `json:"created_at"`
118 UpdatedAt time.Time `json:"updated_at"`
119 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
120
121 //User User `json:"-"`
122 RoutineItems []RoutineItem `json:"routine_items,omitempty"`
123}
124
125/*
126type User struct {
127 ID uint `gorm:"primaryKey" json:"id"`
128 Username string `gorm:"size:50;not null;uniqueIndex" json:"username"`
129 Email string `gorm:"size:100;not null;uniqueIndex" json:"email"`
130 Password string `gorm:"size:100;not null" json:"-"`
131 Name string `gorm:"size:100" json:"name"`
132 CreatedAt time.Time `json:"created_at"`
133 UpdatedAt time.Time `json:"updated_at"`
134 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
135
136 Exercises []Exercise `json:"exercises,omitempty"`
137 Routines []Routine `json:"routines,omitempty"`
138 RecordRoutines []RecordRoutine `json:"record_routines,omitempty"`
139}
140*/
141
142type RecordRoutine struct {
143 ID uint `gorm:"primaryKey" json:"id"`
144 //UserID uint `gorm:"index" json:"user_id"`
145 RoutineID uint `gorm:"index" json:"routine_id"`
146 StartedAt time.Time `gorm:"not null" json:"started_at"`
147 EndedAt *time.Time `json:"ended_at"`
148 CreatedAt time.Time `json:"created_at"`
149 UpdatedAt time.Time `json:"updated_at"`
150 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
151
152 //User User `json:"-"`
153 Routine Routine `json:"routine"`
154 RecordRoutineItems []RecordRoutineItem `json:"record_routine_items,omitempty"`
155}
156
157// RecordRoutineItem represents either a RecordExercise or a RecordSuperSet in a completed routine
158type RecordRoutineItem struct {
159 ID uint `gorm:"primaryKey" json:"id"`
160 RecordRoutineID uint `gorm:"index" json:"record_routine_id"`
161 RecordExerciseID *uint `gorm:"index" json:"record_exercise_id"`
162 RecordSuperSetID *uint `gorm:"index" json:"record_super_set_id"`
163 ActualRestTime int `json:"actual_rest_time"` // In seconds
164 OrderIndex int `gorm:"not null" json:"order_index"`
165 CreatedAt time.Time `json:"created_at"`
166 UpdatedAt time.Time `json:"updated_at"`
167 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
168
169 RecordRoutine RecordRoutine `json:"-"`
170 RecordSuperSet *RecordSuperSet `json:"record_super_set,omitempty"`
171 RecordExercise *RecordExercise `json:"record_exercise,omitempty"`
172}
173
174// RecordSuperSet records a completed superset
175type RecordSuperSet struct {
176 ID uint `gorm:"primaryKey" json:"id"`
177 RecordRoutineID uint `gorm:"index" json:"record_routine_id"`
178 SuperSetID uint `gorm:"index" json:"super_set_id"`
179 StartedAt time.Time `gorm:"not null" json:"started_at"`
180 EndedAt time.Time `gorm:"not null" json:"ended_at"`
181 ActualRestTime int `json:"actual_rest_time"` // In seconds
182 OrderIndex int `gorm:"not null" json:"order_index"`
183 CreatedAt time.Time `json:"created_at"`
184 UpdatedAt time.Time `json:"updated_at"`
185 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
186
187 RecordRoutine RecordRoutine `json:"-"`
188 SuperSet SuperSet `json:"super_set"`
189}
190
191type RecordExercise struct {
192 ID uint `gorm:"primaryKey" json:"id"`
193 RecordRoutineID uint `gorm:"index" json:"record_routine_id"`
194 RecordRoutine RecordRoutine `json:"-"`
195 ExerciseID uint `gorm:"index" json:"exercise_id"`
196 Exercise Exercise `json:"exercise"`
197 StartedAt time.Time `gorm:"not null" json:"started_at"`
198 EndedAt time.Time `gorm:"not null" json:"ended_at"`
199 ActualRestTime int `json:"actual_rest_time"` // In seconds
200 RecordSets []RecordSet `json:"record_sets,omitempty"`
201 OrderIndex int `gorm:"not null" json:"order_index"`
202 RecordSuperSetID *uint `gorm:"index" json:"record_super_set_id"`
203 RecordSuperSet *RecordSuperSet `json:"-"`
204 CreatedAt time.Time `json:"created_at"`
205 UpdatedAt time.Time `json:"updated_at"`
206 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
207}
208
209type RecordSet struct {
210 ID uint `gorm:"primaryKey" json:"id"`
211 RecordExerciseID uint `gorm:"index" json:"record_exercise_id"`
212 SetID uint `gorm:"index" json:"set_id"`
213 ActualReps int `json:"actual_reps"`
214 ActualWeight float64 `json:"actual_weight"`
215 ActualDuration int `json:"actual_duration"` // In seconds
216 CompletedAt time.Time `gorm:"not null" json:"completed_at"`
217 OrderIndex int `gorm:"not null" json:"order_index"`
218 CreatedAt time.Time `json:"created_at"`
219 UpdatedAt time.Time `json:"updated_at"`
220 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
221
222 RecordExercise RecordExercise `json:"-"`
223 Set Set `json:"set"`
224}
225
226type Localization struct {
227 ID uint `gorm:"primaryKey" json:"id"`
228 LanguageID uint `gorm:"not null;uniqueIndex:idx_lang_keyword" json:"language_id"`
229 Keyword string `gorm:"size:255;not null;uniqueIndex:idx_lang_keyword" json:"keyword"`
230 Text string `gorm:"size:1000;not null" json:"text"`
231 CreatedAt time.Time `json:"created_at"`
232 UpdatedAt time.Time `json:"updated_at"`
233 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
234}
235
236type Language struct {
237 ID uint `gorm:"primaryKey" json:"id"`
238 Name string `gorm:"size:100;not null;uniqueIndex" json:"name"`
239 Code string `gorm:"size:8;not null;uniqueIndex" json:"code"`
240 Flag string `gorm:"size:50" json:"flag"`
241 CreatedAt time.Time `json:"created_at"`
242 UpdatedAt time.Time `json:"updated_at"`
243 DeletedAt gorm.DeletedAt `gorm:"index" json:"deleted_at"`
244}
245
246// InitializeDB creates and initializes the SQLite database with all models
247func InitializeDB() (db *gorm.DB, err error) {
248 // Create the data directory if it doesn't exist
249 if _, err = os.Stat(dbDir); os.IsNotExist(err) {
250 err = os.MkdirAll(dbDir, 0755)
251 if err != nil {
252 return
253 }
254 }
255
256 dbPath := filepath.Join(dbDir, dbName)
257
258 // Set up logger for GORM
259 newLogger := logger.New(
260 log.New(os.Stdout, "\r\n", log.LstdFlags),
261 logger.Config{
262 SlowThreshold: time.Second,
263 LogLevel: logger.Info,
264 Colorful: true,
265 },
266 )
267
268 dialector := sqlite.Open(dbPath + "?_pragma=foreign_keys(1)")
269 config := &gorm.Config{Logger: newLogger}
270
271 // Open connection to the database
272 db, err = gorm.Open(dialector, config)
273 if err != nil {
274 return
275 }
276
277 // Get the underlying SQL database to set connection parameters
278 sqlDB, err := db.DB()
279 if err != nil {
280 return nil, err
281 }
282
283 // Set connection pool settings
284 sqlDB.SetMaxIdleConns(10)
285 sqlDB.SetMaxOpenConns(100)
286 sqlDB.SetConnMaxLifetime(time.Hour)
287
288 // Auto migrate the models
289 err = db.AutoMigrate(
290 Equipment{},
291 MuscleGroup{},
292 Exercise{},
293 ExerciseMuscleGroup{},
294 Set{},
295 SuperSet{},
296 RoutineItem{},
297 Routine{},
298 //User{},
299 RecordRoutine{},
300 RecordExercise{},
301 RecordSuperSet{},
302 RecordSet{},
303 Localization{},
304 Language{},
305 )
306 if err != nil {
307 return
308 }
309
310 // Ensure initial data is present
311 err = CheckInitialData(db)
312 if err != nil {
313 return nil, err
314 }
315
316 log.Println("Database initialized successfully")
317 return db, nil
318}