blob: ab33ae2f128705ca086ff43e79dcf57a01820c6c [file] [log] [blame]
Sabina Leaverc5fd2772022-01-29 17:00:23 -08001package db
2
3import (
Emily Markovafaecfe12023-07-01 12:40:03 -07004 "crypto/sha256"
Philipp Schrader30005e42022-03-06 13:53:58 -08005 "errors"
Sabina Leaverc5fd2772022-01-29 17:00:23 -08006 "fmt"
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
Emily Markovabf24c9e2023-02-08 20:31:11 -080017type TeamMatch struct {
18 MatchNumber int32 `gorm:"primaryKey"`
19 SetNumber int32 `gorm:"primaryKey"`
20 CompLevel string `gorm:"primaryKey"`
21 Alliance string `gorm:"primaryKey"` // "R" or "B"
22 AlliancePosition int32 `gorm:"primaryKey"` // 1, 2, or 3
Emily Markovab8551572023-03-22 19:49:39 -070023 TeamNumber string
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
Emily Markovafaecfe12023-07-01 12:40:03 -070031type PitImage struct {
32 TeamNumber string `gorm:"primaryKey"`
33 CheckSum string `gorm:"primaryKey"`
34 ImagePath string
35 ImageData []byte
36}
37
38type RequestedPitImage struct {
39 TeamNumber string
40 CheckSum string `gorm:"primaryKey"`
41 ImagePath string
42}
43
Emily Markova8cb91312024-02-02 12:30:37 -080044type Stats2024 struct {
Emily Markovacd156942024-04-07 19:32:28 -070045 TeamNumber string `gorm:"primaryKey"`
46 MatchNumber int32 `gorm:"primaryKey"`
47 SetNumber int32 `gorm:"primaryKey"`
48 CompLevel string `gorm:"primaryKey"`
Emily Markova9c18e9c2024-04-03 20:06:27 -070049 CompType string `gorm:"primaryKey"`
Emily Markovacd156942024-04-07 19:32:28 -070050 StartingQuadrant int32
51 SpeakerAuto, AmpAuto int32
52 NotesDroppedAuto int32
53 MobilityAuto bool
54 Speaker, Amp, SpeakerAmplified int32
55 NotesDropped int32
56 Shuttled, OutOfField int32
57 Penalties int32
58 AvgCycle int64
59 RobotDied bool
60 Park, OnStage, Harmony, TrapNote, Spotlight bool
Emily Markovaf17f2812024-04-03 20:55:12 -070061 NoShow bool
Emily Markova8cb91312024-02-02 12:30:37 -080062
63 // 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
67}
68
Sabina Leaver759090b2023-01-14 20:42:56 -080069type Action struct {
Sabina Leaver9b4eb312023-02-20 19:58:17 -080070 TeamNumber string `gorm:"primaryKey"`
71 MatchNumber int32 `gorm:"primaryKey"`
72 SetNumber int32 `gorm:"primaryKey"`
73 CompLevel string `gorm:"primaryKey"`
Emily Markova9c18e9c2024-04-03 20:06:27 -070074 CompType string `gorm:"primaryKey"`
Sabina Leaver759090b2023-01-14 20:42:56 -080075 // This contains a serialized scouting.webserver.requests.ActionType flatbuffer.
Sabina Leaver9b4eb312023-02-20 19:58:17 -080076 CompletedAction []byte
Philipp Schrader670a1c82023-05-17 19:42:43 -070077 Timestamp int64 `gorm:"primaryKey"`
78 CollectedBy string
Sabina Leaver759090b2023-01-14 20:42:56 -080079}
80
Alex Perry871eab92022-03-12 17:43:52 -080081type NotesData struct {
Filip Kujawa7ddd5652023-03-07 19:56:15 -080082 ID uint `gorm:"primaryKey"`
Emily Markovae68b7632023-12-30 14:17:55 -080083 TeamNumber string
Emily Markovacf893f42024-03-13 19:03:10 -070084 MatchNumber int32
85 SetNumber int32
86 CompLevel string
Filip Kujawa7ddd5652023-03-07 19:56:15 -080087 Notes string
88 GoodDriving bool
89 BadDriving bool
Filip Kujawa11dc4c92023-04-13 08:55:43 -070090 SolidPlacing bool
Filip Kujawa7ddd5652023-03-07 19:56:15 -080091 SketchyPlacing bool
92 GoodDefense bool
93 BadDefense bool
94 EasilyDefended bool
Emily Markovacf893f42024-03-13 19:03:10 -070095 NoShow bool
Alex Perry871eab92022-03-12 17:43:52 -080096}
97
Yash Chainanibcd1bb32022-04-02 17:10:24 -070098type Ranking struct {
Emily Markovae68b7632023-12-30 14:17:55 -080099 TeamNumber string `gorm:"primaryKey"`
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700100 Losses, Wins, Ties int32
101 Rank, Dq int32
102}
103
Filip Kujawa210a03b2022-11-24 14:41:11 -0800104type DriverRankingData struct {
105 // Each entry in the table is a single scout's ranking.
106 // Multiple scouts can submit a driver ranking for the same
107 // teams in the same match.
108 // The teams being ranked are stored in Rank1, Rank2, Rank3,
109 // Rank1 being the best driving and Rank3 being the worst driving.
110
111 ID uint `gorm:"primaryKey"`
112 MatchNumber int32
Emily Markovae68b7632023-12-30 14:17:55 -0800113 Rank1 string
114 Rank2 string
115 Rank3 string
Filip Kujawa210a03b2022-11-24 14:41:11 -0800116}
117
Philipp Schradera8955fb2023-03-05 15:47:19 -0800118type ParsedDriverRankingData struct {
119 // This data stores the output of DriverRank.jl.
120
121 TeamNumber string `gorm:"primaryKey"`
122
123 // The score of the team. A difference of 100 in two team's scores
124 // indicates that one team will outperform the other in 90% of the
125 // matches.
126 Score float32
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
Evelyn Yang672c1322024-08-14 19:56:28 -0700144 err = database.AutoMigrate(&TeamMatch{}, &Shift{}, &Stats2024{}, &Action{}, &PitImage{}, &NotesData{}, &Ranking{}, &DriverRankingData{}, &ParsedDriverRankingData{})
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
Emily Markovabf24c9e2023-02-08 20:31:11 -0800165func (database *Database) AddToMatch(m TeamMatch) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700166 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 {
Philipp Schrader8fdfadf2023-04-15 16:26:10 -0700180 // TODO(phil): Add check for a corresponding match in the `TeamMatch`
Evelyn Yang672c1322024-08-14 19:56:28 -0700181 // table.
Philipp Schrader8fdfadf2023-04-15 16:26:10 -0700182 result := database.Create(&a)
Sabina Leaver759090b2023-01-14 20:42:56 -0800183 return result.Error
184}
185
Emily Markovafaecfe12023-07-01 12:40:03 -0700186func (database *Database) AddPitImage(p PitImage) error {
187 result := database.Create(&p)
188 return result.Error
189}
190
Emily Markova8cb91312024-02-02 12:30:37 -0800191func (database *Database) AddToStats2024(s Stats2024) error {
Emily Markova9c18e9c2024-04-03 20:06:27 -0700192 if s.CompType == "Regular" {
Emily Markova8cb91312024-02-02 12:30:37 -0800193 matches, err := database.QueryMatchesString(s.TeamNumber)
194 if err != nil {
195 return err
196 }
197 foundMatch := false
198 for _, match := range matches {
199 if match.MatchNumber == s.MatchNumber {
200 foundMatch = true
201 break
202 }
203 }
204 if !foundMatch {
205 return errors.New(fmt.Sprint(
206 "Failed to find team ", s.TeamNumber,
207 " in match ", s.MatchNumber, " in the schedule."))
208 }
209 }
210
211 result := database.Create(&s)
212 return result.Error
213}
214
Emily Markova8cb91312024-02-02 12:30:37 -0800215func (database *Database) DeleteFromStats2024(compLevel_ string, matchNumber_ int32, setNumber_ int32, teamNumber_ string) error {
216 var stats2024 []Stats2024
217 result := database.
218 Where("comp_level = ? AND match_number = ? AND set_number = ? AND team_number = ?", compLevel_, matchNumber_, setNumber_, teamNumber_).
219 Delete(&stats2024)
220 return result.Error
221}
222
Filip Kujawac1ded372023-05-27 14:33:43 -0700223func (database *Database) DeleteFromActions(compLevel_ string, matchNumber_ int32, setNumber_ int32, teamNumber_ string) error {
224 var actions []Action
225 result := database.
226 Where("comp_level = ? AND match_number = ? AND set_number = ? AND team_number = ?", compLevel_, matchNumber_, setNumber_, teamNumber_).
227 Delete(&actions)
228 return result.Error
229}
230
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700231func (database *Database) AddOrUpdateRankings(r Ranking) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700232 result := database.Clauses(clause.OnConflict{
233 UpdateAll: true,
234 }).Create(&r)
235 return result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700236}
237
Emily Markovabf24c9e2023-02-08 20:31:11 -0800238func (database *Database) ReturnMatches() ([]TeamMatch, error) {
239 var matches []TeamMatch
Philipp Schradereecb8962022-06-01 21:02:42 -0700240 result := database.Find(&matches)
241 return matches, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800242}
243
Filip Kujawaf882e022022-12-14 13:14:08 -0800244func (database *Database) ReturnAllNotes() ([]NotesData, error) {
245 var notes []NotesData
246 result := database.Find(&notes)
247 return notes, result.Error
248}
249
250func (database *Database) ReturnAllDriverRankings() ([]DriverRankingData, error) {
251 var rankings []DriverRankingData
252 result := database.Find(&rankings)
253 return rankings, result.Error
254}
255
Philipp Schradera8955fb2023-03-05 15:47:19 -0800256func (database *Database) ReturnAllParsedDriverRankings() ([]ParsedDriverRankingData, error) {
257 var rankings []ParsedDriverRankingData
258 result := database.Find(&rankings)
259 return rankings, result.Error
260}
261
Milo Lina72e2002022-04-06 20:31:13 -0700262func (database *Database) ReturnAllShifts() ([]Shift, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700263 var shifts []Shift
264 result := database.Find(&shifts)
265 return shifts, result.Error
266}
Milo Lina72e2002022-04-06 20:31:13 -0700267
Sabina Leaver759090b2023-01-14 20:42:56 -0800268func (database *Database) ReturnActions() ([]Action, error) {
269 var actions []Action
270 result := database.Find(&actions)
271 return actions, result.Error
272}
273
Emily Markovafaecfe12023-07-01 12:40:03 -0700274func (database *Database) ReturnPitImages() ([]PitImage, error) {
275 var images []PitImage
276 result := database.Find(&images)
277 return images, result.Error
278}
279
Emily Markova8cb91312024-02-02 12:30:37 -0800280func (database *Database) ReturnStats2024() ([]Stats2024, error) {
281 var stats2024 []Stats2024
282 result := database.Find(&stats2024)
283 return stats2024, result.Error
284}
285
Emily Markova9c18e9c2024-04-03 20:06:27 -0700286func (database *Database) ReturnStats2024ForTeam(teamNumber string, matchNumber int32, setNumber int32, compLevel string, compType string) ([]Stats2024, error) {
Emily Markova8cb91312024-02-02 12:30:37 -0800287 var stats2024 []Stats2024
288 result := database.
Emily Markova9c18e9c2024-04-03 20:06:27 -0700289 Where("team_number = ? AND match_number = ? AND set_number = ? AND comp_level = ? AND comp_type = ?",
290 teamNumber, matchNumber, setNumber, compLevel, compType).
Emily Markova8cb91312024-02-02 12:30:37 -0800291 Find(&stats2024)
292 return stats2024, result.Error
293}
294
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700295func (database *Database) ReturnRankings() ([]Ranking, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700296 var rankins []Ranking
297 result := database.Find(&rankins)
298 return rankins, result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700299}
300
Emily Markovab8551572023-03-22 19:49:39 -0700301func (database *Database) queryMatches(teamNumber_ string) ([]TeamMatch, error) {
Emily Markovabf24c9e2023-02-08 20:31:11 -0800302 var matches []TeamMatch
Philipp Schradereecb8962022-06-01 21:02:42 -0700303 result := database.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800304 Where("team_number = $1", teamNumber_).
Philipp Schradereecb8962022-06-01 21:02:42 -0700305 Find(&matches)
306 return matches, result.Error
Sabina Leaverc5fd2772022-01-29 17:00:23 -0800307}
308
Emily Markovafaecfe12023-07-01 12:40:03 -0700309func (database *Database) QueryPitImages(teamNumber_ string) ([]RequestedPitImage, error) {
310 var requestedPitImages []RequestedPitImage
311 result := database.Model(&PitImage{}).
312 Where("team_number = $1", teamNumber_).
313 Find(&requestedPitImages)
314
315 return requestedPitImages, result.Error
316}
317
318func (database *Database) QueryPitImageByChecksum(checksum_ string) (PitImage, error) {
319 var pitImage PitImage
320 result := database.
321 Where("check_sum = $1", checksum_).
322 Find(&pitImage)
323 return pitImage, result.Error
324}
325
326func ComputeSha256FromByteArray(arr []byte) string {
327 sum := sha256.Sum256(arr)
328 return fmt.Sprintf("%x", sum)
329}
330
Emily Markovabf24c9e2023-02-08 20:31:11 -0800331func (database *Database) QueryMatchesString(teamNumber_ string) ([]TeamMatch, error) {
332 var matches []TeamMatch
Sabina Leaver759090b2023-01-14 20:42:56 -0800333 result := database.
Emily Markovabf24c9e2023-02-08 20:31:11 -0800334 Where("team_number = $1", teamNumber_).
Sabina Leaver759090b2023-01-14 20:42:56 -0800335 Find(&matches)
336 return matches, result.Error
337}
338
Milo Lina72e2002022-04-06 20:31:13 -0700339func (database *Database) QueryAllShifts(matchNumber_ int) ([]Shift, error) {
Milo Lina72e2002022-04-06 20:31:13 -0700340 var shifts []Shift
Philipp Schradereecb8962022-06-01 21:02:42 -0700341 result := database.Where("match_number = ?", matchNumber_).Find(&shifts)
342 return shifts, result.Error
Milo Lina72e2002022-04-06 20:31:13 -0700343}
344
Emily Markovae68b7632023-12-30 14:17:55 -0800345func (database *Database) QueryActions(teamNumber_ string) ([]Action, error) {
Sabina Leaver759090b2023-01-14 20:42:56 -0800346 var actions []Action
347 result := database.
348 Where("team_number = ?", teamNumber_).Find(&actions)
349 return actions, result.Error
350}
351
Emily Markovae68b7632023-12-30 14:17:55 -0800352func (database *Database) QueryNotes(TeamNumber string) ([]string, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700353 var rawNotes []NotesData
354 result := database.Where("team_number = ?", TeamNumber).Find(&rawNotes)
355 if result.Error != nil {
356 return nil, result.Error
Alex Perry871eab92022-03-12 17:43:52 -0800357 }
Alex Perry871eab92022-03-12 17:43:52 -0800358
Philipp Schradereecb8962022-06-01 21:02:42 -0700359 notes := make([]string, len(rawNotes))
360 for i := range rawNotes {
361 notes[i] = rawNotes[i].Notes
Alex Perry871eab92022-03-12 17:43:52 -0800362 }
Philipp Schradereecb8962022-06-01 21:02:42 -0700363 return notes, nil
Alex Perry871eab92022-03-12 17:43:52 -0800364}
365
Emily Markovae68b7632023-12-30 14:17:55 -0800366func (database *Database) QueryRankings(TeamNumber string) ([]Ranking, error) {
Philipp Schradereecb8962022-06-01 21:02:42 -0700367 var rankins []Ranking
368 result := database.Where("team_number = ?", TeamNumber).Find(&rankins)
369 return rankins, result.Error
Yash Chainanibcd1bb32022-04-02 17:10:24 -0700370}
371
Filip Kujawaf947cb42022-11-21 10:00:30 -0800372func (database *Database) AddNotes(data NotesData) error {
Philipp Schradereecb8962022-06-01 21:02:42 -0700373 result := database.Create(&NotesData{
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800374 TeamNumber: data.TeamNumber,
Emily Markovacf893f42024-03-13 19:03:10 -0700375 MatchNumber: data.MatchNumber,
376 SetNumber: data.SetNumber,
377 CompLevel: data.CompLevel,
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800378 Notes: data.Notes,
379 GoodDriving: data.GoodDriving,
380 BadDriving: data.BadDriving,
Filip Kujawa11dc4c92023-04-13 08:55:43 -0700381 SolidPlacing: data.SolidPlacing,
Filip Kujawa7ddd5652023-03-07 19:56:15 -0800382 SketchyPlacing: data.SketchyPlacing,
383 GoodDefense: data.GoodDefense,
384 BadDefense: data.BadDefense,
385 EasilyDefended: data.EasilyDefended,
Emily Markovacf893f42024-03-13 19:03:10 -0700386 NoShow: data.NoShow,
Philipp Schradereecb8962022-06-01 21:02:42 -0700387 })
388 return result.Error
Alex Perry871eab92022-03-12 17:43:52 -0800389}
Filip Kujawa210a03b2022-11-24 14:41:11 -0800390
391func (database *Database) AddDriverRanking(data DriverRankingData) error {
392 result := database.Create(&DriverRankingData{
393 MatchNumber: data.MatchNumber,
394 Rank1: data.Rank1,
395 Rank2: data.Rank2,
396 Rank3: data.Rank3,
397 })
398 return result.Error
399}
400
Philipp Schradera8955fb2023-03-05 15:47:19 -0800401func (database *Database) AddParsedDriverRanking(data ParsedDriverRankingData) error {
402 result := database.Clauses(clause.OnConflict{
403 UpdateAll: true,
404 }).Create(&data)
405 return result.Error
406}
407
Filip Kujawa210a03b2022-11-24 14:41:11 -0800408func (database *Database) QueryDriverRanking(MatchNumber int) ([]DriverRankingData, error) {
409 var data []DriverRankingData
410 result := database.Where("match_number = ?", MatchNumber).Find(&data)
411 return data, result.Error
412}