aboutsummaryrefslogtreecommitdiff
path: root/backend/handlers/record.go
diff options
context:
space:
mode:
authorArda Serdar Pektezol <1669855+pektezol@users.noreply.github.com>2024-11-21 18:12:07 +0300
committerGitHub <noreply@github.com>2024-11-21 18:12:07 +0300
commitcff5e845c75e9e2751a2c1f01f8ae3fbf24f0e7d (patch)
treeb5b55b7bc02e94e6d77787328cc52e1e49efcf1a /backend/handlers/record.go
parentfeat/rankings: optimize Steam ID comparison (#236) (diff)
downloadlphub-cff5e845c75e9e2751a2c1f01f8ae3fbf24f0e7d.tar.gz
lphub-cff5e845c75e9e2751a2c1f01f8ae3fbf24f0e7d.tar.bz2
lphub-cff5e845c75e9e2751a2c1f01f8ae3fbf24f0e7d.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.go140
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
3import ( 3import (
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
26type RecordRequest struct { 22type 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
393func 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
408func 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
425func 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
430func convertSteamID(steamID64 int64) int64 { 380func convertSteamID(steamID64 int64) int64 {
431 return (steamID64 >> 1) & 0x7FFFFFF 381 return (steamID64 >> 1) & 0x7FFFFFF