diff options
| author | Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> | 2024-11-21 18:12:07 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2024-11-21 18:12:07 +0300 |
| commit | cff5e845c75e9e2751a2c1f01f8ae3fbf24f0e7d (patch) | |
| tree | b5b55b7bc02e94e6d77787328cc52e1e49efcf1a /backend/handlers/record.go | |
| parent | feat/rankings: optimize Steam ID comparison (#236) (diff) | |
| download | lphub-1.0.3.tar.gz lphub-1.0.3.tar.bz2 lphub-1.0.3.zip | |
feat/backend: gdrive to backblaze migration, improve create record (#237)v1.0.3
Diffstat (limited to 'backend/handlers/record.go')
| -rw-r--r-- | backend/handlers/record.go | 140 |
1 files changed, 45 insertions, 95 deletions
diff --git a/backend/handlers/record.go b/backend/handlers/record.go index bedde57..91e74b9 100644 --- a/backend/handlers/record.go +++ b/backend/handlers/record.go | |||
| @@ -2,10 +2,8 @@ package handlers | |||
| 2 | 2 | ||
| 3 | import ( | 3 | import ( |
| 4 | "context" | 4 | "context" |
| 5 | "encoding/base64" | ||
| 6 | "fmt" | 5 | "fmt" |
| 7 | "io" | 6 | "io" |
| 8 | "log" | ||
| 9 | "mime/multipart" | 7 | "mime/multipart" |
| 10 | "net/http" | 8 | "net/http" |
| 11 | "os" | 9 | "os" |
| @@ -16,11 +14,9 @@ import ( | |||
| 16 | "lphub/models" | 14 | "lphub/models" |
| 17 | "lphub/parser" | 15 | "lphub/parser" |
| 18 | 16 | ||
| 17 | "github.com/Backblaze/blazer/b2" | ||
| 19 | "github.com/gin-gonic/gin" | 18 | "github.com/gin-gonic/gin" |
| 20 | "github.com/google/uuid" | 19 | "github.com/google/uuid" |
| 21 | "golang.org/x/oauth2/google" | ||
| 22 | "golang.org/x/oauth2/jwt" | ||
| 23 | "google.golang.org/api/drive/v3" | ||
| 24 | ) | 20 | ) |
| 25 | 21 | ||
| 26 | type RecordRequest struct { | 22 | type RecordRequest struct { |
| @@ -79,19 +75,14 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 79 | return | 75 | return |
| 80 | } | 76 | } |
| 81 | // Demo files | 77 | // Demo files |
| 82 | demoFiles := []*multipart.FileHeader{record.HostDemo} | 78 | demoFileHeaders := []*multipart.FileHeader{record.HostDemo} |
| 83 | if isCoop { | 79 | if isCoop { |
| 84 | demoFiles = append(demoFiles, record.PartnerDemo) | 80 | demoFileHeaders = append(demoFileHeaders, record.PartnerDemo) |
| 85 | } | 81 | } |
| 86 | var hostDemoUUID, hostDemoFileID, partnerDemoUUID, partnerDemoFileID string | 82 | var hostDemoUUID, partnerDemoUUID string |
| 87 | var hostDemoScoreCount, hostDemoScoreTime int | 83 | var hostDemoScoreCount, hostDemoScoreTime int |
| 88 | var hostSteamID, partnerSteamID string | 84 | var hostSteamID, partnerSteamID string |
| 89 | var hostDemoServerNumber, partnerDemoServerNumber int | 85 | var hostDemoServerNumber, partnerDemoServerNumber int |
| 90 | srv, err := drive.New(serviceAccount()) | ||
| 91 | if err != nil { | ||
| 92 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | ||
| 93 | return | ||
| 94 | } | ||
| 95 | // Create database transaction for inserts | 86 | // Create database transaction for inserts |
| 96 | tx, err := database.DB.Begin() | 87 | tx, err := database.DB.Begin() |
| 97 | if err != nil { | 88 | if err != nil { |
| @@ -100,22 +91,16 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 100 | } | 91 | } |
| 101 | // Defer to a rollback in case anything fails | 92 | // Defer to a rollback in case anything fails |
| 102 | defer tx.Rollback() | 93 | defer tx.Rollback() |
| 103 | for i, header := range demoFiles { | 94 | for i, header := range demoFileHeaders { |
| 104 | uuid := uuid.New().String() | 95 | uuid := uuid.New().String() |
| 105 | // Upload & insert into demos | 96 | // Upload & insert into demos |
| 106 | err = c.SaveUploadedFile(header, "parser/"+uuid+".dem") | 97 | f, err := header.Open() |
| 107 | if err != nil { | ||
| 108 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | ||
| 109 | return | ||
| 110 | } | ||
| 111 | defer os.Remove("parser/" + uuid + ".dem") | ||
| 112 | f, err := os.Open("parser/" + uuid + ".dem") | ||
| 113 | if err != nil { | 98 | if err != nil { |
| 114 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 99 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| 115 | return | 100 | return |
| 116 | } | 101 | } |
| 117 | defer f.Close() | 102 | defer f.Close() |
| 118 | parserResult, err := parser.ProcessDemo("parser/" + uuid + ".dem") | 103 | parserResult, err := parser.ProcessDemo(f) |
| 119 | if err != nil { | 104 | if err != nil { |
| 120 | c.JSON(http.StatusOK, models.ErrorResponse("Error while processing demo: "+err.Error())) | 105 | c.JSON(http.StatusOK, models.ErrorResponse("Error while processing demo: "+err.Error())) |
| 121 | return | 106 | return |
| @@ -148,23 +133,15 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 148 | return | 133 | return |
| 149 | } | 134 | } |
| 150 | } | 135 | } |
| 151 | file, err := createFile(srv, uuid+".dem", "application/octet-stream", f, os.Getenv("GOOGLE_FOLDER_ID")) | ||
| 152 | if err != nil { | ||
| 153 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | ||
| 154 | return | ||
| 155 | } | ||
| 156 | if i == 0 { | 136 | if i == 0 { |
| 157 | hostDemoFileID = file.Id | ||
| 158 | hostDemoUUID = uuid | 137 | hostDemoUUID = uuid |
| 159 | hostDemoServerNumber = parserResult.ServerNumber | 138 | hostDemoServerNumber = parserResult.ServerNumber |
| 160 | } else if i == 1 { | 139 | } else if i == 1 { |
| 161 | partnerDemoFileID = file.Id | ||
| 162 | partnerDemoUUID = uuid | 140 | partnerDemoUUID = uuid |
| 163 | partnerDemoServerNumber = parserResult.ServerNumber | 141 | partnerDemoServerNumber = parserResult.ServerNumber |
| 164 | } | 142 | } |
| 165 | _, err = tx.Exec(`INSERT INTO demos (id,location_id) VALUES ($1,$2)`, uuid, file.Id) | 143 | _, err = tx.Exec(`INSERT INTO demos (id) VALUES ($1)`, uuid) |
| 166 | if err != nil { | 144 | if err != nil { |
| 167 | deleteFile(srv, file.Id) | ||
| 168 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 145 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| 169 | return | 146 | return |
| 170 | } | 147 | } |
| @@ -172,8 +149,6 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 172 | // Insert into records | 149 | // Insert into records |
| 173 | if isCoop { | 150 | if isCoop { |
| 174 | if hostDemoServerNumber != partnerDemoServerNumber { | 151 | if hostDemoServerNumber != partnerDemoServerNumber { |
| 175 | deleteFile(srv, hostDemoFileID) | ||
| 176 | deleteFile(srv, partnerDemoFileID) | ||
| 177 | c.JSON(http.StatusOK, models.ErrorResponse(fmt.Sprintf("Host and partner demo server numbers (%d & %d) does not match!", hostDemoServerNumber, partnerDemoServerNumber))) | 152 | c.JSON(http.StatusOK, models.ErrorResponse(fmt.Sprintf("Host and partner demo server numbers (%d & %d) does not match!", hostDemoServerNumber, partnerDemoServerNumber))) |
| 178 | return | 153 | return |
| 179 | } | 154 | } |
| @@ -192,8 +167,6 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 192 | // return | 167 | // return |
| 193 | // } | 168 | // } |
| 194 | if convertedHostSteamID != user.(models.User).SteamID && convertedPartnerSteamID != user.(models.User).SteamID { | 169 | if convertedHostSteamID != user.(models.User).SteamID && convertedPartnerSteamID != user.(models.User).SteamID { |
| 195 | deleteFile(srv, hostDemoFileID) | ||
| 196 | deleteFile(srv, partnerDemoFileID) | ||
| 197 | c.JSON(http.StatusOK, models.ErrorResponse("You are permitted to only upload your own runs!")) | 170 | c.JSON(http.StatusOK, models.ErrorResponse("You are permitted to only upload your own runs!")) |
| 198 | return | 171 | return |
| 199 | } | 172 | } |
| @@ -205,8 +178,6 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 205 | } | 178 | } |
| 206 | database.DB.QueryRow("SELECT steam_id FROM users WHERE steam_id = $1", checkPartnerSteamID).Scan(&verifyPartnerSteamID) | 179 | database.DB.QueryRow("SELECT steam_id FROM users WHERE steam_id = $1", checkPartnerSteamID).Scan(&verifyPartnerSteamID) |
| 207 | if verifyPartnerSteamID != checkPartnerSteamID { | 180 | if verifyPartnerSteamID != checkPartnerSteamID { |
| 208 | deleteFile(srv, hostDemoFileID) | ||
| 209 | deleteFile(srv, partnerDemoFileID) | ||
| 210 | c.JSON(http.StatusOK, models.ErrorResponse("Partner SteamID does not match an account on LPHUB.")) | 181 | c.JSON(http.StatusOK, models.ErrorResponse("Partner SteamID does not match an account on LPHUB.")) |
| 211 | return | 182 | return |
| 212 | } | 183 | } |
| @@ -214,8 +185,6 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 214 | VALUES($1, $2, $3, $4, $5, $6, $7)` | 185 | VALUES($1, $2, $3, $4, $5, $6, $7)` |
| 215 | _, err := tx.Exec(sql, mapID, hostDemoScoreCount, hostDemoScoreTime, convertedHostSteamID, convertedPartnerSteamID, hostDemoUUID, partnerDemoUUID) | 186 | _, err := tx.Exec(sql, mapID, hostDemoScoreCount, hostDemoScoreTime, convertedHostSteamID, convertedPartnerSteamID, hostDemoUUID, partnerDemoUUID) |
| 216 | if err != nil { | 187 | if err != nil { |
| 217 | deleteFile(srv, hostDemoFileID) | ||
| 218 | deleteFile(srv, partnerDemoFileID) | ||
| 219 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 188 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| 220 | return | 189 | return |
| 221 | } | 190 | } |
| @@ -224,7 +193,39 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 224 | VALUES($1, $2, $3, $4, $5)` | 193 | VALUES($1, $2, $3, $4, $5)` |
| 225 | _, err := tx.Exec(sql, mapID, hostDemoScoreCount, hostDemoScoreTime, user.(models.User).SteamID, hostDemoUUID) | 194 | _, err := tx.Exec(sql, mapID, hostDemoScoreCount, hostDemoScoreTime, user.(models.User).SteamID, hostDemoUUID) |
| 226 | if err != nil { | 195 | if err != nil { |
| 227 | deleteFile(srv, hostDemoFileID) | 196 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| 197 | return | ||
| 198 | } | ||
| 199 | } | ||
| 200 | // Everything is good, upload the demo files. | ||
| 201 | client, err := b2.NewClient(context.Background(), os.Getenv("B2_KEY_ID"), os.Getenv("B2_API_KEY")) | ||
| 202 | if err != nil { | ||
| 203 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | ||
| 204 | return | ||
| 205 | } | ||
| 206 | bucket, err := client.Bucket(context.Background(), os.Getenv("B2_BUCKET_NAME")) | ||
| 207 | if err != nil { | ||
| 208 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | ||
| 209 | return | ||
| 210 | } | ||
| 211 | for i, header := range demoFileHeaders { | ||
| 212 | f, err := header.Open() | ||
| 213 | if err != nil { | ||
| 214 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | ||
| 215 | return | ||
| 216 | } | ||
| 217 | defer f.Close() | ||
| 218 | var objectName string | ||
| 219 | if i == 0 { | ||
| 220 | objectName = hostDemoUUID + ".dem" | ||
| 221 | } else if i == 1 { | ||
| 222 | objectName = partnerDemoUUID + ".dem" | ||
| 223 | } | ||
| 224 | obj := bucket.Object(objectName) | ||
| 225 | writer := obj.NewWriter(context.Background()) | ||
| 226 | defer writer.Close() | ||
| 227 | _, err = io.Copy(writer, f) | ||
| 228 | if err != nil { | ||
| 228 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 229 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| 229 | return | 230 | return |
| 230 | } | 231 | } |
| @@ -339,29 +340,15 @@ func DownloadDemoWithID(c *gin.Context) { | |||
| 339 | c.JSON(http.StatusOK, models.ErrorResponse("Invalid id given.")) | 340 | c.JSON(http.StatusOK, models.ErrorResponse("Invalid id given.")) |
| 340 | return | 341 | return |
| 341 | } | 342 | } |
| 342 | srv, err := drive.New(serviceAccount()) | 343 | var checkedUUID string |
| 344 | err := database.DB.QueryRow("SELECT d.id FROM demos d WHERE d.id = $1", uuid).Scan(&checkedUUID) | ||
| 343 | if err != nil { | 345 | if err != nil { |
| 344 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 346 | c.JSON(http.StatusOK, models.ErrorResponse("Given id does not match a demo.")) |
| 345 | return | ||
| 346 | } | ||
| 347 | |||
| 348 | // Query drive instead of finding location id from db because SOMEONE reuploaded the demos. | ||
| 349 | // Tbf I had to reupload and will have to do time after time. Fuck you Google. | ||
| 350 | // I guess there's no need to store location id of demos anymore? | ||
| 351 | // ALSO ALSO, Google keeps track of old deleted files so sort by createdTime to get the latest demo. | ||
| 352 | fileList, err := srv.Files.List().Q(fmt.Sprintf("name = '%s.dem'", uuid)). | ||
| 353 | Fields("files(id, name, createdTime)").OrderBy("createdTime desc").PageSize(1).Do() | ||
| 354 | if err != nil { | ||
| 355 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | ||
| 356 | return | ||
| 357 | } | ||
| 358 | if len(fileList.Files) == 0 { | ||
| 359 | c.JSON(http.StatusOK, models.ErrorResponse("Demo not found.")) | ||
| 360 | return | 347 | return |
| 361 | } | 348 | } |
| 362 | 349 | ||
| 363 | url := "https://drive.google.com/uc?export=download&id=" + fileList.Files[0].Id | ||
| 364 | fileName := uuid + ".dem" | 350 | fileName := uuid + ".dem" |
| 351 | url := os.Getenv("B2_DOWNLOAD_URL") + fileName | ||
| 365 | output, err := os.Create(fileName) | 352 | output, err := os.Create(fileName) |
| 366 | if err != nil { | 353 | if err != nil { |
| 367 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 354 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| @@ -389,43 +376,6 @@ func DownloadDemoWithID(c *gin.Context) { | |||
| 389 | // c.FileAttachment() | 376 | // c.FileAttachment() |
| 390 | } | 377 | } |
| 391 | 378 | ||
| 392 | // Use Service account | ||
| 393 | func serviceAccount() *http.Client { | ||
| 394 | privateKey, _ := base64.StdEncoding.DecodeString(os.Getenv("GOOGLE_PRIVATE_KEY_BASE64")) | ||
| 395 | config := &jwt.Config{ | ||
| 396 | Email: os.Getenv("GOOGLE_CLIENT_EMAIL"), | ||
| 397 | PrivateKey: []byte(privateKey), | ||
| 398 | Scopes: []string{ | ||
| 399 | drive.DriveScope, | ||
| 400 | }, | ||
| 401 | TokenURL: google.JWTTokenURL, | ||
| 402 | } | ||
| 403 | client := config.Client(context.Background()) | ||
| 404 | return client | ||
| 405 | } | ||
| 406 | |||
| 407 | // Create Gdrive file | ||
| 408 | func createFile(service *drive.Service, name string, mimeType string, content io.Reader, parentId string) (*drive.File, error) { | ||
| 409 | f := &drive.File{ | ||
| 410 | MimeType: mimeType, | ||
| 411 | Name: name, | ||
| 412 | Parents: []string{parentId}, | ||
| 413 | } | ||
| 414 | file, err := service.Files.Create(f).Media(content).Do() | ||
| 415 | |||
| 416 | if err != nil { | ||
| 417 | log.Println("Could not create file: " + err.Error()) | ||
| 418 | return nil, err | ||
| 419 | } | ||
| 420 | |||
| 421 | return file, nil | ||
| 422 | } | ||
| 423 | |||
| 424 | // Delete Gdrive file | ||
| 425 | func deleteFile(service *drive.Service, fileId string) { | ||
| 426 | service.Files.Delete(fileId) | ||
| 427 | } | ||
| 428 | |||
| 429 | // Convert from SteamID64 to Legacy SteamID bits | 379 | // Convert from SteamID64 to Legacy SteamID bits |
| 430 | func convertSteamID(steamID64 int64) int64 { | 380 | func convertSteamID(steamID64 int64) int64 { |
| 431 | return (steamID64 >> 1) & 0x7FFFFFF | 381 | return (steamID64 >> 1) & 0x7FFFFFF |