blob: 121ba52b75fe5b92b20559458388742686909249 [file] [log] [blame]
Sabina Leaverc5fd2772022-01-29 17:00:23 -08001package db
2
3import (
Philipp Schrader30005e42022-03-06 13:53:58 -08004 "errors"
Sabina Leaverc5fd2772022-01-29 17:00:23 -08005 "fmt"
6
Philipp Schradereecb8962022-06-01 21:02:42 -07007 "gorm.io/driver/postgres"
8 "gorm.io/gorm"
9 "gorm.io/gorm/clause"
10 "gorm.io/gorm/logger"
Sabina Leaverc5fd2772022-01-29 17:00:23 -080011)
12
13type Database struct {
Philipp Schradereecb8962022-06-01 21:02:42 -070014 *gorm.DB
Sabina Leaverc5fd2772022-01-29 17:00:23 -080015}
16
17type Match struct {
Philipp Schradereecb8962022-06-01 21:02:42 -070018 // TODO(phil): Rework this be be one team per row.
19 // Makes queries much simpler.
20 MatchNumber int32 `gorm:"primaryKey"`
21 SetNumber int32 `gorm:"primaryKey"`
22 CompLevel string `gorm:"primaryKey"`
Philipp Schradercbf5c6a2022-02-27 23:25:19 -080023 R1, R2, R3, B1, B2, B3 int32
Sabina Leaverc5fd2772022-01-29 17:00:23 -080024}
25
Milo Lina72e2002022-04-06 20:31:13 -070026type Shift struct {
Philipp Schradereecb8962022-06-01 21:02:42 -070027 MatchNumber int32 `gorm:"primaryKey"`
Milo Lina72e2002022-04-06 20:31:13 -070028 R1scouter, R2scouter, R3scouter, B1scouter, B2scouter, B3scouter string
29}
30
Sabina Leaverc5fd2772022-01-29 17:00:23 -080031type Stats struct {
Philipp Schradereecb8962022-06-01 21:02:42 -070032 TeamNumber int32 `gorm:"primaryKey"`
33 MatchNumber int32 `gorm:"primaryKey"`
34 SetNumber int32 `gorm:"primaryKey"`
35 CompLevel string `gorm:"primaryKey"`
36 StartingQuadrant int32
37 // This field is for the balls picked up during auto. Use this field
38 // when using this library. Ignore the AutoBallPickedUpX fields below.
39 AutoBallPickedUp [5]bool `gorm:"-:all"`
40 // These fields are internal implementation details. Do not use these.
41 // TODO(phil): Figure out how to use the JSON gorm serializer instead
42 // of manually serializing/deserializing these.
43 AutoBallPickedUp1 bool
44 AutoBallPickedUp2 bool
45 AutoBallPickedUp3 bool
46 AutoBallPickedUp4 bool
47 AutoBallPickedUp5 bool
Philipp Schraderfee07e12022-03-17 22:19:47 -070048 // TODO(phil): Re-order auto and teleop fields so auto comes first.
Philipp Schraderfa45d742022-03-18 19:29:05 -070049 ShotsMissed, UpperGoalShots, LowerGoalShots int32
50 ShotsMissedAuto, UpperGoalAuto, LowerGoalAuto int32
51 PlayedDefense, DefenseReceivedScore int32
Philipp Schrader36df73a2022-03-17 23:27:24 -070052 // Climbing level:
53 // 0 -> "NoAttempt"
54 // 1 -> "Failed"
55 // 2 -> "FailedWithPlentyOfTime"
56 // 3 -> "Low"
57 // 4 -> "Medium"
58 // 5 -> "High"
Philipp Schrader85f6e5b2022-04-16 14:20:06 -070059 // 6 -> "Traversal"
Philipp Schrader36df73a2022-03-17 23:27:24 -070060 Climbing int32
Philipp Schraderfa45d742022-03-18 19:29:05 -070061 // Some non-numerical data that the scout felt worth noting.
62 Comment string
Philipp Schraderfae8a7e2022-03-13 22:51:54 -070063 // The username of the person who collected these statistics.
64 // "unknown" if submitted without logging in.
65 // Empty if the stats have not yet been collected.
66 CollectedBy string
Sabina Leaverc5fd2772022-01-29 17:00:23 -080067}
68
Sabina Leaver759090b2023-01-14 20:42:56 -080069type Stats2023 struct {
70 TeamNumber string `gorm:"primaryKey"`
71 MatchNumber int32 `gorm:"primaryKey"`
72 SetNumber int32 `gorm:"primaryKey"`
73 CompLevel string `gorm:"primaryKey"`
74 StartingQuadrant int32
75 LowCubesAuto, MiddleCubesAuto, HighCubesAuto, CubesDroppedAuto int32
76 LowConesAuto, MiddleConesAuto, HighConesAuto, ConesDroppedAuto int32
77 LowCubes, MiddleCubes, HighCubes, CubesDropped int32
78 LowCones, MiddleCones, HighCones, ConesDropped int32
79 AvgCycle int32
80 // The username of the person who collected these statistics.
81 // "unknown" if submitted without logging in.
82 // Empty if the stats have not yet been collected.
83 CollectedBy string
84}
85
86type Action struct {
87 TeamNumber string `gorm:"primaryKey"`
88 MatchNumber int32 `gorm:"primaryKey"`
89 SetNumber int32 `gorm:"primaryKey"`
90 CompLevel string `gorm:"primaryKey"`
91 CompletedAction []byte
92 // This contains a serialized scouting.webserver.requests.ActionType flatbuffer.
93 TimeStamp int32 `gorm:"primaryKey"`
94 CollectedBy string
95}
96
Alex Perry871eab92022-03-12 17:43:52 -080097type NotesData struct {
Filip Kujawaf947cb42022-11-21 10:00:30 -080098 ID uint `gorm:"primaryKey"`
99 TeamNumber int32
100 Notes string
101 GoodDriving bool
102 BadDriving bool
103 SketchyClimb bool
104 SolidClimb bool
105 GoodDefense bool
106 BadDefense bool
Alex Perry871eab92022-03-12 17:43:52 -0800107}
108
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700109type Ranking struct {
Philipp Schradereecb8962022-06-01 21:02:42 -0700110 TeamNumber int `gorm:"primaryKey"`
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700111 Losses, Wins, Ties int32
112 Rank, Dq int32
113}
114
Filip Kujawa210a03b2022-11-24 14:41:11 -0800115type DriverRankingData struct {
116 // Each entry in the table is a single scout's ranking.
117 // Multiple scouts can submit a driver ranking for the same
118 // teams in the same match.
119 // The teams being ranked are stored in Rank1, Rank2, Rank3,
120 // Rank1 being the best driving and Rank3 being the worst driving.
121
122 ID uint `gorm:"primaryKey"`
123 MatchNumber int32
124 Rank1 int32
125 Rank2 int32
126 Rank3 int32
127}
128
Philipp Schrader7365d322022-03-06 16:40:08 -0800129// Opens a database at the specified port on localhost. We currently don't
130// support connecting to databases on other hosts.
131func NewDatabase(user string, password string, port int) (*Database, error) {
Philipp Schrader83fc2722022-03-10 21:59:20 -0800132 var err error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800133 database := new(Database)
Philipp Schrader83fc2722022-03-10 21:59:20 -0800134
Philipp Schradereecb8962022-06-01 21:02:42 -0700135 dsn := fmt.Sprintf("host=localhost user=%s password=%s dbname=postgres port=%d sslmode=disable", user, password, port)
136 database.DB, err = gorm.Open(postgres.Open(dsn), &gorm.Config{
137 Logger: logger.Default.LogMode(logger.Silent),
138 })
Philipp Schrader7365d322022-03-06 16:40:08 -0800139 if err != nil {
Philipp Schradereecb8962022-06-01 21:02:42 -0700140 database.Delete()
Philipp Schrader7365d322022-03-06 16:40:08 -0800141 return nil, errors.New(fmt.Sprint("Failed to connect to postgres: ", err))
142 }
Philipp Schrader36df73a2022-03-17 23:27:24 -0700143
Sabina Leaver759090b2023-01-14 20:42:56 -0800144 err = database.AutoMigrate(&Match{}, &Shift{}, &Stats{}, &Stats2023{}, &Action{}, &NotesData{}, &Ranking{}, &DriverRankingData{})
Philipp Schrader83fc2722022-03-10 21:59:20 -0800145 if err != nil {
Philipp Schradereecb8962022-06-01 21:02:42 -0700146 database.Delete()
147 return nil, errors.New(fmt.Sprint("Failed to create/migrate tables: ", err))
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700148 }
149
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800150 return database, nil
151}
152
153func (database *Database) Delete() error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700154 sql, err := database.DB.DB()
Philipp Schrader83fc2722022-03-10 21:59:20 -0800155 if err != nil {
Philipp Schradereecb8962022-06-01 21:02:42 -0700156 return err
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800157 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700158 return sql.Close()
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800159}
160
Philipp Schradereecb8962022-06-01 21:02:42 -0700161func (database *Database) SetDebugLogLevel() {
162 database.DB.Logger = database.DB.Logger.LogMode(logger.Info)
163}
Philipp Schradercd12c952022-04-08 18:58:49 -0700164
Philipp Schradereecb8962022-06-01 21:02:42 -0700165func (database *Database) AddToMatch(m Match) error {
166 result := database.Clauses(clause.OnConflict{
167 UpdateAll: true,
168 }).Create(&m)
169 return result.Error
Philipp Schradercd12c952022-04-08 18:58:49 -0700170}
171
Milo Lina72e2002022-04-06 20:31:13 -0700172func (database *Database) AddToShift(sh Shift) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700173 result := database.Clauses(clause.OnConflict{
174 UpdateAll: true,
175 }).Create(&sh)
176 return result.Error
Milo Lina72e2002022-04-06 20:31:13 -0700177}
178
Sabina Leaver759090b2023-01-14 20:42:56 -0800179func (database *Database) AddAction(a Action) error {
180 result := database.Clauses(clause.OnConflict{
181 UpdateAll: true,
182 }).Create(&a)
183 return result.Error
184}
185
Philipp Schradercd12c952022-04-08 18:58:49 -0700186func (database *Database) AddToStats(s Stats) error {
187 matches, err := database.QueryMatches(s.TeamNumber)
188 if err != nil {
189 return err
190 }
191 foundMatch := false
192 for _, match := range matches {
193 if match.MatchNumber == s.MatchNumber {
194 foundMatch = true
195 break
196 }
197 }
198 if !foundMatch {
199 return errors.New(fmt.Sprint(
200 "Failed to find team ", s.TeamNumber,
201 " in match ", s.MatchNumber, " in the schedule."))
202 }
203
Philipp Schradereecb8962022-06-01 21:02:42 -0700204 // Unpack the auto balls array.
205 s.AutoBallPickedUp1 = s.AutoBallPickedUp[0]
206 s.AutoBallPickedUp2 = s.AutoBallPickedUp[1]
207 s.AutoBallPickedUp3 = s.AutoBallPickedUp[2]
208 s.AutoBallPickedUp4 = s.AutoBallPickedUp[3]
209 s.AutoBallPickedUp5 = s.AutoBallPickedUp[4]
210 result := database.Create(&s)
211 return result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800212}
213
Sabina Leaver759090b2023-01-14 20:42:56 -0800214func (database *Database) AddToStats2023(s Stats2023) error {
215 matches, err := database.QueryMatchesString(s.TeamNumber)
216 if err != nil {
217 return err
218 }
219 foundMatch := false
220 for _, match := range matches {
221 if match.MatchNumber == s.MatchNumber {
222 foundMatch = true
223 break
224 }
225 }
226 if !foundMatch {
227 return errors.New(fmt.Sprint(
228 "Failed to find team ", s.TeamNumber,
229 " in match ", s.MatchNumber, " in the schedule."))
230 }
231
232 result := database.Create(&s)
233 return result.Error
234}
235
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700236func (database *Database) AddOrUpdateRankings(r Ranking) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700237 result := database.Clauses(clause.OnConflict{
238 UpdateAll: true,
239 }).Create(&r)
240 return result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700241}
242
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800243func (database *Database) ReturnMatches() ([]Match, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700244 var matches []Match
245 result := database.Find(&matches)
246 return matches, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800247}
248
Filip Kujawaf882e022022-12-14 13:14:08 -0800249func (database *Database) ReturnAllNotes() ([]NotesData, error) {
250 var notes []NotesData
251 result := database.Find(&notes)
252 return notes, result.Error
253}
254
255func (database *Database) ReturnAllDriverRankings() ([]DriverRankingData, error) {
256 var rankings []DriverRankingData
257 result := database.Find(&rankings)
258 return rankings, result.Error
259}
260
Milo Lina72e2002022-04-06 20:31:13 -0700261func (database *Database) ReturnAllShifts() ([]Shift, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700262 var shifts []Shift
263 result := database.Find(&shifts)
264 return shifts, result.Error
265}
Milo Lina72e2002022-04-06 20:31:13 -0700266
Sabina Leaver759090b2023-01-14 20:42:56 -0800267func (database *Database) ReturnActions() ([]Action, error) {
268 var actions []Action
269 result := database.Find(&actions)
270 return actions, result.Error
271}
272
Philipp Schradereecb8962022-06-01 21:02:42 -0700273// Packs the stats. This really just consists of taking the individual auto
274// ball booleans and turning them into an array. The individual booleans are
275// cleared so that they don't affect struct comparisons.
276func packStats(stats *Stats) {
277 stats.AutoBallPickedUp = [5]bool{
278 stats.AutoBallPickedUp1,
279 stats.AutoBallPickedUp2,
280 stats.AutoBallPickedUp3,
281 stats.AutoBallPickedUp4,
282 stats.AutoBallPickedUp5,
Milo Lina72e2002022-04-06 20:31:13 -0700283 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700284 stats.AutoBallPickedUp1 = false
285 stats.AutoBallPickedUp2 = false
286 stats.AutoBallPickedUp3 = false
287 stats.AutoBallPickedUp4 = false
288 stats.AutoBallPickedUp5 = false
Milo Lina72e2002022-04-06 20:31:13 -0700289}
290
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800291func (database *Database) ReturnStats() ([]Stats, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700292 var stats []Stats
293 result := database.Find(&stats)
294 // Pack the auto balls array.
295 for i := range stats {
296 packStats(&stats[i])
Philipp Schrader30005e42022-03-06 13:53:58 -0800297 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700298 return stats, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800299}
300
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700301func (database *Database) ReturnRankings() ([]Ranking, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700302 var rankins []Ranking
303 result := database.Find(&rankins)
304 return rankins, result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700305}
306
Philipp Schraderd1c4bef2022-02-28 22:51:30 -0800307func (database *Database) QueryMatches(teamNumber_ int32) ([]Match, error) {
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800308 var matches []Match
Philipp Schradereecb8962022-06-01 21:02:42 -0700309 result := database.
310 Where("r1 = $1 OR r2 = $1 OR r3 = $1 OR b1 = $1 OR b2 = $1 OR b3 = $1", teamNumber_).
311 Find(&matches)
312 return matches, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800313}
314
Sabina Leaver759090b2023-01-14 20:42:56 -0800315func (database *Database) QueryMatchesString(teamNumber_ string) ([]Match, error) {
316 var matches []Match
317 result := database.
318 Where("r1 = $1 OR r2 = $1 OR r3 = $1 OR b1 = $1 OR b2 = $1 OR b3 = $1", teamNumber_).
319 Find(&matches)
320 return matches, result.Error
321}
322
Milo Lina72e2002022-04-06 20:31:13 -0700323func (database *Database) QueryAllShifts(matchNumber_ int) ([]Shift, error) {
Milo Lina72e2002022-04-06 20:31:13 -0700324 var shifts []Shift
Philipp Schradereecb8962022-06-01 21:02:42 -0700325 result := database.Where("match_number = ?", matchNumber_).Find(&shifts)
326 return shifts, result.Error
Milo Lina72e2002022-04-06 20:31:13 -0700327}
328
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800329func (database *Database) QueryStats(teamNumber_ int) ([]Stats, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700330 var stats []Stats
331 result := database.Where("team_number = ?", teamNumber_).Find(&stats)
332 // Pack the auto balls array.
333 for i := range stats {
334 packStats(&stats[i])
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800335 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700336 return stats, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800337}
Alex Perry871eab92022-03-12 17:43:52 -0800338
Sabina Leaver759090b2023-01-14 20:42:56 -0800339func (database *Database) QueryActions(teamNumber_ int) ([]Action, error) {
340 var actions []Action
341 result := database.
342 Where("team_number = ?", teamNumber_).Find(&actions)
343 return actions, result.Error
344}
345
Philipp Schradereecb8962022-06-01 21:02:42 -0700346func (database *Database) QueryNotes(TeamNumber int32) ([]string, error) {
347 var rawNotes []NotesData
348 result := database.Where("team_number = ?", TeamNumber).Find(&rawNotes)
349 if result.Error != nil {
350 return nil, result.Error
Alex Perry871eab92022-03-12 17:43:52 -0800351 }
Alex Perry871eab92022-03-12 17:43:52 -0800352
Philipp Schradereecb8962022-06-01 21:02:42 -0700353 notes := make([]string, len(rawNotes))
354 for i := range rawNotes {
355 notes[i] = rawNotes[i].Notes
Alex Perry871eab92022-03-12 17:43:52 -0800356 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700357 return notes, nil
Alex Perry871eab92022-03-12 17:43:52 -0800358}
359
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700360func (database *Database) QueryRankings(TeamNumber int) ([]Ranking, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700361 var rankins []Ranking
362 result := database.Where("team_number = ?", TeamNumber).Find(&rankins)
363 return rankins, result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700364}
365
Filip Kujawaf947cb42022-11-21 10:00:30 -0800366func (database *Database) AddNotes(data NotesData) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700367 result := database.Create(&NotesData{
Filip Kujawaf947cb42022-11-21 10:00:30 -0800368 TeamNumber: data.TeamNumber,
369 Notes: data.Notes,
370 GoodDriving: data.GoodDriving,
371 BadDriving: data.BadDriving,
372 SketchyClimb: data.SketchyClimb,
373 SolidClimb: data.SolidClimb,
374 GoodDefense: data.GoodDefense,
375 BadDefense: data.BadDefense,
Philipp Schradereecb8962022-06-01 21:02:42 -0700376 })
377 return result.Error
Alex Perry871eab92022-03-12 17:43:52 -0800378}
Filip Kujawa210a03b2022-11-24 14:41:11 -0800379
380func (database *Database) AddDriverRanking(data DriverRankingData) error {
381 result := database.Create(&DriverRankingData{
382 MatchNumber: data.MatchNumber,
383 Rank1: data.Rank1,
384 Rank2: data.Rank2,
385 Rank3: data.Rank3,
386 })
387 return result.Error
388}
389
390func (database *Database) QueryDriverRanking(MatchNumber int) ([]DriverRankingData, error) {
391 var data []DriverRankingData
392 result := database.Where("match_number = ?", MatchNumber).Find(&data)
393 return data, result.Error
394}