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 `gorm:"default:false" 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"`
32 Name string `gorm:"not null;uniqueIndex"`
33 Level string `gorm:"size:50;not null"`
34 Category string `gorm:"size:50;not null"`
35 Force *string `gorm:"size:50"`
36 Mechanic *string `gorm:"size:50"`
37 Equipment *string `gorm:"size:50"`
38 Instructions *string
39
40 PrimaryMuscles []Muscle `gorm:"many2many:exercise_primary_muscles;constraint:OnDelete:CASCADE"`
41 SecondaryMuscles []Muscle `gorm:"many2many:exercise_secondary_muscles;constraint:OnDelete:CASCADE"`
42
43 CreatedAt time.Time
44 UpdatedAt time.Time
45}
46
47type Muscle struct {
48 ID uint `gorm:"primaryKey"`
49 Name string `gorm:"uniqueIndex;size:50;not null"`
50}
51
52type Set struct {
53 ID uint `gorm:"primaryKey" json:"id"`
54 ExerciseID uint `gorm:"index" json:"exerciseId"`
55 Reps int `json:"reps"`
56 Weight float64 `json:"weight"`
57 Duration int `json:"duration"` // In seconds, for timed exercises
58 OrderIndex int `gorm:"not null" json:"orderIndex"`
59 CreatedAt time.Time `json:"createdAt"`
60 UpdatedAt time.Time `json:"updatedAt"`
61 DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
62
63 Exercise Exercise `json:"-"`
64}
65
66// SuperSet to handle two exercises with single rest time
67type SuperSet struct {
68 ID uint `gorm:"primaryKey" json:"id"`
69 Name string `gorm:"size:100" json:"name"`
70 PrimaryExerciseID uint `gorm:"index" json:"primaryExerciseId"`
71 SecondaryExerciseID uint `gorm:"index" json:"secondaryExerciseId"`
72 RestTime int `gorm:"default:0" json:"restTime"` // In seconds
73 CreatedAt time.Time `json:"createdAt"`
74 UpdatedAt time.Time `json:"updatedAt"`
75 DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
76
77 PrimaryExercise Exercise `json:"primaryExercise"`
78 SecondaryExercise Exercise `json:"secondaryExercise"`
79}
80
81// RoutineItem represents either an Exercise or a SuperSet in a Routine
82type RoutineItem struct {
83 ID uint `gorm:"primaryKey" json:"id"`
84 RoutineID uint `gorm:"index" json:"routineId"`
85 ExerciseID *uint `gorm:"index" json:"exerciseId"`
86 SuperSetID *uint `gorm:"index" json:"superSetId"`
87 RestTime int `gorm:"default:0" json:"restTime"` // In seconds
88 OrderIndex int `gorm:"not null" json:"orderIndex"`
89 CreatedAt time.Time `json:"createdAt"`
90 UpdatedAt time.Time `json:"updatedAt"`
91 DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
92
93 Routine Routine `json:"-"`
94 SuperSet *SuperSet `json:"superSet,omitempty"`
95 Exercise *Exercise `json:"exercise,omitempty"`
96}
97
98type Routine struct {
99 ID uint `gorm:"primaryKey" json:"id"`
100 Name string `gorm:"size:100;not null" json:"name"`
101 Description string `gorm:"size:500" json:"description"`
102 //UserID uint `gorm:"index" json:"userId"`
103 //IsPublic bool `gorm:"default:false" json:"isPublic"`
104 CreatedAt time.Time `json:"createdAt"`
105 UpdatedAt time.Time `json:"updatedAt"`
106 DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
107
108 //User User `json:"-"`
109 RoutineItems []RoutineItem `json:"routineItems,omitempty"`
110}
111
112/*
113type User struct {
114 ID uint `gorm:"primaryKey" json:"id"`
115 Username string `gorm:"size:50;not null;uniqueIndex" json:"username"`
116 Email string `gorm:"size:100;not null;uniqueIndex" json:"email"`
117 Password string `gorm:"size:100;not null" json:"-"`
118 Name string `gorm:"size:100" json:"name"`
119 CreatedAt time.Time `json:"createdAt"`
120 UpdatedAt time.Time `json:"updatedAt"`
121 DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
122
123 Exercises []Exercise `json:"exercises,omitempty"`
124 Routines []Routine `json:"routines,omitempty"`
125 RecordRoutines []RecordRoutine `json:"recordRoutines,omitempty"`
126}
127*/
128
129type RecordRoutine struct {
130 ID uint `gorm:"primaryKey" json:"id"`
131 //UserID uint `gorm:"index" json:"userId"`
132 RoutineID uint `gorm:"index" json:"routineId"`
133 StartedAt time.Time `gorm:"not null" json:"startedAt"`
134 EndedAt *time.Time `json:"endedAt"`
135 CreatedAt time.Time `json:"createdAt"`
136 UpdatedAt time.Time `json:"updatedAt"`
137 DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
138
139 //User User `json:"-"`
140 Routine Routine `json:"routine"`
141 RecordRoutineItems []RecordRoutineItem `json:"recordRoutineItems,omitempty"`
142}
143
144// RecordRoutineItem represents either a RecordExercise or a RecordSuperSet in a completed routine
145type RecordRoutineItem struct {
146 ID uint `gorm:"primaryKey" json:"id"`
147 RecordRoutineID uint `gorm:"index" json:"recordRoutineId"`
148 RecordExerciseID *uint `gorm:"index" json:"recordExerciseId"`
149 RecordSuperSetID *uint `gorm:"index" json:"recordSuperSetId"`
150 ActualRestTime int `json:"actualRestTime"` // In seconds
151 OrderIndex int `gorm:"not null" json:"orderIndex"`
152 CreatedAt time.Time `json:"createdAt"`
153 UpdatedAt time.Time `json:"updatedAt"`
154 DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
155
156 RecordRoutine RecordRoutine `json:"-"`
157 RecordSuperSet *RecordSuperSet `json:"recordSuperSet,omitempty"`
158 RecordExercise *RecordExercise `json:"recordExercise,omitempty"`
159}
160
161// RecordSuperSet records a completed superset
162type RecordSuperSet struct {
163 ID uint `gorm:"primaryKey" json:"id"`
164 RecordRoutineID uint `gorm:"index" json:"recordRoutineId"`
165 SuperSetID uint `gorm:"index" json:"superSetId"`
166 StartedAt time.Time `gorm:"not null" json:"startedAt"`
167 EndedAt time.Time `gorm:"not null" json:"endedAt"`
168 ActualRestTime int `json:"actualRestTime"` // In seconds
169 OrderIndex int `gorm:"not null" json:"orderIndex"`
170 CreatedAt time.Time `json:"createdAt"`
171 UpdatedAt time.Time `json:"updatedAt"`
172 DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
173
174 RecordRoutine RecordRoutine `json:"-"`
175 SuperSet SuperSet `json:"superSet"`
176}
177
178type RecordExercise struct {
179 ID uint `gorm:"primaryKey" json:"id"`
180 RecordRoutineID uint `gorm:"index" json:"recordRoutineId"`
181 RecordRoutine RecordRoutine `json:"-"`
182 ExerciseID uint `gorm:"index" json:"exerciseId"`
183 Exercise Exercise `json:"exercise"`
184 StartedAt time.Time `gorm:"not null" json:"startedAt"`
185 EndedAt time.Time `gorm:"not null" json:"endedAt"`
186 ActualRestTime int `json:"actualRestTime"` // In seconds
187 RecordSets []RecordSet `json:"recordSets,omitempty"`
188 OrderIndex int `gorm:"not null" json:"orderIndex"`
189 RecordSuperSetID *uint `gorm:"index" json:"recordSuperSetId"`
190 RecordSuperSet *RecordSuperSet `json:"-"`
191 CreatedAt time.Time `json:"createdAt"`
192 UpdatedAt time.Time `json:"updatedAt"`
193 DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
194}
195
196type RecordSet struct {
197 ID uint `gorm:"primaryKey" json:"id"`
198 RecordExerciseID uint `gorm:"index" json:"recordExerciseId"`
199 SetID uint `gorm:"index" json:"setId"`
200 ActualReps int `json:"actualReps"`
201 ActualWeight float64 `json:"actualWeight"`
202 ActualDuration int `json:"actualDuration"` // In seconds
203 CompletedAt time.Time `gorm:"not null" json:"completedAt"`
204 OrderIndex int `gorm:"not null" json:"orderIndex"`
205 CreatedAt time.Time `json:"createdAt"`
206 UpdatedAt time.Time `json:"updatedAt"`
207 DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
208
209 RecordExercise RecordExercise `json:"-"`
210 Set Set `json:"set"`
211}
212
213type Localization struct {
214 ID uint `gorm:"primaryKey" json:"id"`
215 LanguageID uint `gorm:"not null;uniqueIndex:idxLangKeyword" json:"languageId"`
216 Keyword string `gorm:"size:255;not null;uniqueIndex:idxLangKeyword" json:"keyword"`
217 Text string `gorm:"size:1000;not null" json:"text"`
218 CreatedAt time.Time `json:"createdAt"`
219 UpdatedAt time.Time `json:"updatedAt"`
220 DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
221}
222
223type Language struct {
224 ID uint `gorm:"primaryKey" json:"id"`
225 Name string `gorm:"size:100;not null;uniqueIndex" json:"name"`
226 Code string `gorm:"size:8;not null;uniqueIndex" json:"code"`
227 Flag string `gorm:"size:50" json:"flag"`
228 CreatedAt time.Time `json:"createdAt"`
229 UpdatedAt time.Time `json:"updatedAt"`
230 DeletedAt gorm.DeletedAt `gorm:"index" json:"deletedAt"`
231}
232
233// InitializeDB creates and initializes the SQLite database with all models
234func InitializeDB() (db *Database, err error) {
235 // Create the data directory if it doesn't exist
236 if _, err = os.Stat(dbDir); os.IsNotExist(err) {
237 err = os.MkdirAll(dbDir, 0755)
238 if err != nil {
239 return
240 }
241 }
242
243 dbPath := filepath.Join(dbDir, dbName)
244
245 // Set up logger for GORM
246 newLogger := logger.New(
247 log.New(os.Stdout, "\r\n", log.LstdFlags),
248 logger.Config{
249 SlowThreshold: time.Second,
250 LogLevel: logger.Info,
251 Colorful: true,
252 },
253 )
254
255 dialector := sqlite.Open(dbPath + "?Pragma=foreignKeys(1)")
256 config := &gorm.Config{Logger: newLogger}
257
258 // Open connection to the database
259 conn, err := gorm.Open(dialector, config)
260 if err != nil {
261 return
262 }
263
264 // Get the underlying SQL database to set connection parameters
265 sqlDB, err := conn.DB()
266 if err != nil {
267 return nil, err
268 }
269
270 // Set connection pool settings
271 sqlDB.SetMaxIdleConns(10)
272 sqlDB.SetMaxOpenConns(100)
273 sqlDB.SetConnMaxLifetime(time.Hour)
274
275 // Auto migrate the models
276 err = conn.AutoMigrate(
277 Muscle{},
278 Exercise{},
279 Set{},
280 SuperSet{},
281 RoutineItem{},
282 Routine{},
283 User{},
284 RecordRoutine{},
285 RecordExercise{},
286 RecordSuperSet{},
287 RecordSet{},
288 Localization{},
289 Language{},
290 )
291 if err != nil {
292 return
293 }
294
295 db = &Database{conn}
296
297 // Ensure initial data is present
298 err = db.CheckInitialData()
299 if err != nil {
300 return nil, err
301 }
302
303 log.Println("Database initialized successfully")
304 return db, nil
305}