diff options
Diffstat (limited to '')
| -rw-r--r-- | backend/handlers/record.go | 222 |
1 files changed, 112 insertions, 110 deletions
diff --git a/backend/handlers/record.go b/backend/handlers/record.go index bedde57..25a6c6d 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,17 +14,14 @@ 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 { |
| 27 | HostDemo *multipart.FileHeader `json:"host_demo" form:"host_demo" binding:"required" swaggerignore:"true"` | 23 | HostDemo *multipart.FileHeader `json:"host_demo" form:"host_demo" binding:"required" swaggerignore:"true"` |
| 28 | PartnerDemo *multipart.FileHeader `json:"partner_demo" form:"partner_demo" swaggerignore:"true"` | 24 | PartnerDemo *multipart.FileHeader `json:"partner_demo" form:"partner_demo" swaggerignore:"true"` |
| 29 | PartnerID string `json:"partner_id" form:"partner_id"` | ||
| 30 | } | 25 | } |
| 31 | 26 | ||
| 32 | type RecordResponse struct { | 27 | type RecordResponse struct { |
| @@ -79,19 +74,14 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 79 | return | 74 | return |
| 80 | } | 75 | } |
| 81 | // Demo files | 76 | // Demo files |
| 82 | demoFiles := []*multipart.FileHeader{record.HostDemo} | 77 | demoFileHeaders := []*multipart.FileHeader{record.HostDemo} |
| 83 | if isCoop { | 78 | if isCoop { |
| 84 | demoFiles = append(demoFiles, record.PartnerDemo) | 79 | demoFileHeaders = append(demoFileHeaders, record.PartnerDemo) |
| 85 | } | 80 | } |
| 86 | var hostDemoUUID, hostDemoFileID, partnerDemoUUID, partnerDemoFileID string | 81 | var hostDemoUUID, partnerDemoUUID string |
| 87 | var hostDemoScoreCount, hostDemoScoreTime int | 82 | var hostDemoScoreCount, hostDemoScoreTime int |
| 88 | var hostSteamID, partnerSteamID string | 83 | var hostSteamID, partnerSteamID string |
| 89 | var hostDemoServerNumber, partnerDemoServerNumber int | 84 | 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 | 85 | // Create database transaction for inserts |
| 96 | tx, err := database.DB.Begin() | 86 | tx, err := database.DB.Begin() |
| 97 | if err != nil { | 87 | if err != nil { |
| @@ -100,22 +90,16 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 100 | } | 90 | } |
| 101 | // Defer to a rollback in case anything fails | 91 | // Defer to a rollback in case anything fails |
| 102 | defer tx.Rollback() | 92 | defer tx.Rollback() |
| 103 | for i, header := range demoFiles { | 93 | for i, header := range demoFileHeaders { |
| 104 | uuid := uuid.New().String() | 94 | uuid := uuid.New().String() |
| 105 | // Upload & insert into demos | 95 | // Upload & insert into demos |
| 106 | err = c.SaveUploadedFile(header, "parser/"+uuid+".dem") | 96 | 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 { | 97 | if err != nil { |
| 114 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 98 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| 115 | return | 99 | return |
| 116 | } | 100 | } |
| 117 | defer f.Close() | 101 | defer f.Close() |
| 118 | parserResult, err := parser.ProcessDemo("parser/" + uuid + ".dem") | 102 | parserResult, err := parser.ProcessDemo(f) |
| 119 | if err != nil { | 103 | if err != nil { |
| 120 | c.JSON(http.StatusOK, models.ErrorResponse("Error while processing demo: "+err.Error())) | 104 | c.JSON(http.StatusOK, models.ErrorResponse("Error while processing demo: "+err.Error())) |
| 121 | return | 105 | return |
| @@ -148,23 +132,15 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 148 | return | 132 | return |
| 149 | } | 133 | } |
| 150 | } | 134 | } |
| 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 { | 135 | if i == 0 { |
| 157 | hostDemoFileID = file.Id | ||
| 158 | hostDemoUUID = uuid | 136 | hostDemoUUID = uuid |
| 159 | hostDemoServerNumber = parserResult.ServerNumber | 137 | hostDemoServerNumber = parserResult.ServerNumber |
| 160 | } else if i == 1 { | 138 | } else if i == 1 { |
| 161 | partnerDemoFileID = file.Id | ||
| 162 | partnerDemoUUID = uuid | 139 | partnerDemoUUID = uuid |
| 163 | partnerDemoServerNumber = parserResult.ServerNumber | 140 | partnerDemoServerNumber = parserResult.ServerNumber |
| 164 | } | 141 | } |
| 165 | _, err = tx.Exec(`INSERT INTO demos (id,location_id) VALUES ($1,$2)`, uuid, file.Id) | 142 | _, err = tx.Exec(`INSERT INTO demos (id) VALUES ($1)`, uuid) |
| 166 | if err != nil { | 143 | if err != nil { |
| 167 | deleteFile(srv, file.Id) | ||
| 168 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 144 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| 169 | return | 145 | return |
| 170 | } | 146 | } |
| @@ -172,8 +148,6 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 172 | // Insert into records | 148 | // Insert into records |
| 173 | if isCoop { | 149 | if isCoop { |
| 174 | if hostDemoServerNumber != partnerDemoServerNumber { | 150 | 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))) | 151 | c.JSON(http.StatusOK, models.ErrorResponse(fmt.Sprintf("Host and partner demo server numbers (%d & %d) does not match!", hostDemoServerNumber, partnerDemoServerNumber))) |
| 178 | return | 152 | return |
| 179 | } | 153 | } |
| @@ -192,8 +166,6 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 192 | // return | 166 | // return |
| 193 | // } | 167 | // } |
| 194 | if convertedHostSteamID != user.(models.User).SteamID && convertedPartnerSteamID != user.(models.User).SteamID { | 168 | 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!")) | 169 | c.JSON(http.StatusOK, models.ErrorResponse("You are permitted to only upload your own runs!")) |
| 198 | return | 170 | return |
| 199 | } | 171 | } |
| @@ -205,8 +177,6 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 205 | } | 177 | } |
| 206 | database.DB.QueryRow("SELECT steam_id FROM users WHERE steam_id = $1", checkPartnerSteamID).Scan(&verifyPartnerSteamID) | 178 | database.DB.QueryRow("SELECT steam_id FROM users WHERE steam_id = $1", checkPartnerSteamID).Scan(&verifyPartnerSteamID) |
| 207 | if verifyPartnerSteamID != checkPartnerSteamID { | 179 | 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.")) | 180 | c.JSON(http.StatusOK, models.ErrorResponse("Partner SteamID does not match an account on LPHUB.")) |
| 211 | return | 181 | return |
| 212 | } | 182 | } |
| @@ -214,8 +184,6 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 214 | VALUES($1, $2, $3, $4, $5, $6, $7)` | 184 | VALUES($1, $2, $3, $4, $5, $6, $7)` |
| 215 | _, err := tx.Exec(sql, mapID, hostDemoScoreCount, hostDemoScoreTime, convertedHostSteamID, convertedPartnerSteamID, hostDemoUUID, partnerDemoUUID) | 185 | _, err := tx.Exec(sql, mapID, hostDemoScoreCount, hostDemoScoreTime, convertedHostSteamID, convertedPartnerSteamID, hostDemoUUID, partnerDemoUUID) |
| 216 | if err != nil { | 186 | if err != nil { |
| 217 | deleteFile(srv, hostDemoFileID) | ||
| 218 | deleteFile(srv, partnerDemoFileID) | ||
| 219 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 187 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| 220 | return | 188 | return |
| 221 | } | 189 | } |
| @@ -224,7 +192,78 @@ func CreateRecordWithDemo(c *gin.Context) { | |||
| 224 | VALUES($1, $2, $3, $4, $5)` | 192 | VALUES($1, $2, $3, $4, $5)` |
| 225 | _, err := tx.Exec(sql, mapID, hostDemoScoreCount, hostDemoScoreTime, user.(models.User).SteamID, hostDemoUUID) | 193 | _, err := tx.Exec(sql, mapID, hostDemoScoreCount, hostDemoScoreTime, user.(models.User).SteamID, hostDemoUUID) |
| 226 | if err != nil { | 194 | if err != nil { |
| 227 | deleteFile(srv, hostDemoFileID) | 195 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| 196 | return | ||
| 197 | } | ||
| 198 | } | ||
| 199 | if os.Getenv("ENV") == "DEV" { | ||
| 200 | if localPath := os.Getenv("LOCAL_DEMOS_PATH"); localPath != "" { | ||
| 201 | for i, header := range demoFileHeaders { | ||
| 202 | f, err := header.Open() | ||
| 203 | if err != nil { | ||
| 204 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | ||
| 205 | return | ||
| 206 | } | ||
| 207 | defer f.Close() | ||
| 208 | var objectName string | ||
| 209 | if i == 0 { | ||
| 210 | objectName = hostDemoUUID + ".dem" | ||
| 211 | } else if i == 1 { | ||
| 212 | objectName = partnerDemoUUID + ".dem" | ||
| 213 | } | ||
| 214 | demo, err := os.Create(localPath + objectName) | ||
| 215 | if err != nil { | ||
| 216 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | ||
| 217 | return | ||
| 218 | } | ||
| 219 | defer demo.Close() | ||
| 220 | _, err = io.Copy(demo, f) | ||
| 221 | if err != nil { | ||
| 222 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | ||
| 223 | return | ||
| 224 | } | ||
| 225 | } | ||
| 226 | if err = tx.Commit(); err != nil { | ||
| 227 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | ||
| 228 | return | ||
| 229 | } | ||
| 230 | c.JSON(http.StatusOK, models.Response{ | ||
| 231 | Success: true, | ||
| 232 | Message: "Successfully created record.", | ||
| 233 | Data: RecordResponse{ScoreCount: hostDemoScoreCount, ScoreTime: hostDemoScoreTime}, | ||
| 234 | }) | ||
| 235 | return | ||
| 236 | } | ||
| 237 | } | ||
| 238 | // Everything is good, upload the demo files. | ||
| 239 | client, err := b2.NewClient(context.Background(), os.Getenv("B2_KEY_ID"), os.Getenv("B2_API_KEY")) | ||
| 240 | if err != nil { | ||
| 241 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | ||
| 242 | return | ||
| 243 | } | ||
| 244 | bucket, err := client.Bucket(context.Background(), os.Getenv("B2_BUCKET_NAME")) | ||
| 245 | if err != nil { | ||
| 246 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | ||
| 247 | return | ||
| 248 | } | ||
| 249 | for i, header := range demoFileHeaders { | ||
| 250 | f, err := header.Open() | ||
| 251 | if err != nil { | ||
| 252 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | ||
| 253 | return | ||
| 254 | } | ||
| 255 | defer f.Close() | ||
| 256 | var objectName string | ||
| 257 | if i == 0 { | ||
| 258 | objectName = hostDemoUUID + ".dem" | ||
| 259 | } else if i == 1 { | ||
| 260 | objectName = partnerDemoUUID + ".dem" | ||
| 261 | } | ||
| 262 | obj := bucket.Object(objectName) | ||
| 263 | writer := obj.NewWriter(context.Background()) | ||
| 264 | defer writer.Close() | ||
| 265 | _, err = io.Copy(writer, f) | ||
| 266 | if err != nil { | ||
| 228 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 267 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| 229 | return | 268 | return |
| 230 | } | 269 | } |
| @@ -339,91 +378,54 @@ func DownloadDemoWithID(c *gin.Context) { | |||
| 339 | c.JSON(http.StatusOK, models.ErrorResponse("Invalid id given.")) | 378 | c.JSON(http.StatusOK, models.ErrorResponse("Invalid id given.")) |
| 340 | return | 379 | return |
| 341 | } | 380 | } |
| 342 | srv, err := drive.New(serviceAccount()) | 381 | var checkedUUID string |
| 382 | err := database.DB.QueryRow("SELECT d.id FROM demos d WHERE d.id = $1", uuid).Scan(&checkedUUID) | ||
| 343 | if err != nil { | 383 | if err != nil { |
| 344 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 384 | c.JSON(http.StatusOK, models.ErrorResponse("Given id does not match a demo.")) |
| 345 | return | 385 | return |
| 346 | } | 386 | } |
| 347 | 387 | ||
| 348 | // Query drive instead of finding location id from db because SOMEONE reuploaded the demos. | 388 | localPath := "" |
| 349 | // Tbf I had to reupload and will have to do time after time. Fuck you Google. | 389 | if os.Getenv("ENV") == "DEV" { |
| 350 | // I guess there's no need to store location id of demos anymore? | 390 | localPath = os.Getenv("LOCAL_DEMOS_PATH") |
| 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 | ||
| 361 | } | 391 | } |
| 362 | 392 | ||
| 363 | url := "https://drive.google.com/uc?export=download&id=" + fileList.Files[0].Id | ||
| 364 | fileName := uuid + ".dem" | 393 | fileName := uuid + ".dem" |
| 365 | output, err := os.Create(fileName) | 394 | if localPath == "" { |
| 366 | if err != nil { | 395 | url := os.Getenv("B2_DOWNLOAD_URL") + fileName |
| 367 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 396 | output, err := os.Create(fileName) |
| 368 | return | 397 | if err != nil { |
| 369 | } | 398 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| 370 | defer os.Remove(fileName) | 399 | return |
| 371 | defer output.Close() | 400 | } |
| 372 | response, err := http.Get(url) | 401 | defer os.Remove(fileName) |
| 373 | if err != nil { | 402 | defer output.Close() |
| 374 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 403 | response, err := http.Get(url) |
| 375 | return | 404 | if err != nil { |
| 376 | } | 405 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| 377 | defer response.Body.Close() | 406 | return |
| 378 | _, err = io.Copy(output, response.Body) | 407 | } |
| 379 | if err != nil { | 408 | defer response.Body.Close() |
| 380 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 409 | _, err = io.Copy(output, response.Body) |
| 381 | return | 410 | if err != nil { |
| 411 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | ||
| 412 | return | ||
| 413 | } | ||
| 382 | } | 414 | } |
| 415 | |||
| 383 | // Downloaded file | 416 | // Downloaded file |
| 384 | c.Header("Content-Description", "File Transfer") | 417 | c.Header("Content-Description", "File Transfer") |
| 385 | c.Header("Content-Transfer-Encoding", "binary") | 418 | c.Header("Content-Transfer-Encoding", "binary") |
| 386 | c.Header("Content-Disposition", "attachment; filename="+fileName) | 419 | c.Header("Content-Disposition", "attachment; filename="+fileName) |
| 387 | c.Header("Content-Type", "application/octet-stream") | 420 | c.Header("Content-Type", "application/octet-stream") |
| 388 | c.File(fileName) | ||
| 389 | // c.FileAttachment() | ||
| 390 | } | ||
| 391 | 421 | ||
| 392 | // Use Service account | 422 | if localPath == "" { |
| 393 | func serviceAccount() *http.Client { | 423 | c.File(fileName) |
| 394 | privateKey, _ := base64.StdEncoding.DecodeString(os.Getenv("GOOGLE_PRIVATE_KEY_BASE64")) | 424 | } else { |
| 395 | config := &jwt.Config{ | 425 | c.File(localPath + fileName) |
| 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 | } | 426 | } |
| 420 | 427 | ||
| 421 | return file, nil | 428 | // c.FileAttachment() |
| 422 | } | ||
| 423 | |||
| 424 | // Delete Gdrive file | ||
| 425 | func deleteFile(service *drive.Service, fileId string) { | ||
| 426 | service.Files.Delete(fileId) | ||
| 427 | } | 429 | } |
| 428 | 430 | ||
| 429 | // Convert from SteamID64 to Legacy SteamID bits | 431 | // Convert from SteamID64 to Legacy SteamID bits |