aboutsummaryrefslogtreecommitdiff
path: root/backend/handlers/record.go
diff options
context:
space:
mode:
Diffstat (limited to 'backend/handlers/record.go')
-rw-r--r--backend/handlers/record.go222
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
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,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
26type RecordRequest struct { 22type 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
32type RecordResponse struct { 27type 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 == "" {
393func 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
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 } 426 }
420 427
421 return file, nil 428 // c.FileAttachment()
422}
423
424// Delete Gdrive file
425func 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