diff options
| author | Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> | 2025-07-24 14:40:22 +0300 |
|---|---|---|
| committer | Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> | 2025-07-24 14:40:22 +0300 |
| commit | b0d199936b546c75d4b19d99591237f0bf97fe55 (patch) | |
| tree | e9391880e7db2bd1ea8ff25d91aeea8dd98f186e /backend/handlers | |
| parent | fix/frontend: fixed sidebar title size, removed unnecessary imports (diff) | |
| parent | feat/backend: add newrelic integration (#274) (diff) | |
| download | lphub-css-overhaul.tar.gz lphub-css-overhaul.tar.bz2 lphub-css-overhaul.zip | |
Merge branch 'main' into css-overhaulcss-overhaul
Diffstat (limited to 'backend/handlers')
| -rw-r--r-- | backend/handlers/home.go | 12 | ||||
| -rw-r--r-- | backend/handlers/map.go | 10 | ||||
| -rw-r--r-- | backend/handlers/record.go | 222 | ||||
| -rw-r--r-- | backend/handlers/user.go | 19 |
4 files changed, 148 insertions, 115 deletions
diff --git a/backend/handlers/home.go b/backend/handlers/home.go index 714610a..095b666 100644 --- a/backend/handlers/home.go +++ b/backend/handlers/home.go | |||
| @@ -6,6 +6,7 @@ import ( | |||
| 6 | "log" | 6 | "log" |
| 7 | "net/http" | 7 | "net/http" |
| 8 | "os" | 8 | "os" |
| 9 | "sort" | ||
| 9 | "strings" | 10 | "strings" |
| 10 | 11 | ||
| 11 | "lphub/database" | 12 | "lphub/database" |
| @@ -106,6 +107,15 @@ func RankingsLPHUB(c *gin.Context) { | |||
| 106 | } | 107 | } |
| 107 | } | 108 | } |
| 108 | } | 109 | } |
| 110 | // Sort the overall rankings | ||
| 111 | sort.Slice(response.Overall, func(i, j int) bool { | ||
| 112 | a := response.Overall[i] | ||
| 113 | b := response.Overall[j] | ||
| 114 | if a.TotalScore == b.TotalScore { | ||
| 115 | return a.User.SteamID < b.User.SteamID | ||
| 116 | } | ||
| 117 | return a.TotalScore < b.TotalScore | ||
| 118 | }) | ||
| 109 | 119 | ||
| 110 | placement := 1 | 120 | placement := 1 |
| 111 | ties := 0 | 121 | ties := 0 |
| @@ -317,7 +327,7 @@ func SearchWithQuery(c *gin.Context) { | |||
| 317 | {ID: 80, Game: "Portal 2 - Cooperative", Chapter: "Course 3 - Hard-Light Surfaces", Map: "Catapult Block"}, | 327 | {ID: 80, Game: "Portal 2 - Cooperative", Chapter: "Course 3 - Hard-Light Surfaces", Map: "Catapult Block"}, |
| 318 | {ID: 81, Game: "Portal 2 - Cooperative", Chapter: "Course 3 - Hard-Light Surfaces", Map: "Bridge Fling"}, | 328 | {ID: 81, Game: "Portal 2 - Cooperative", Chapter: "Course 3 - Hard-Light Surfaces", Map: "Bridge Fling"}, |
| 319 | {ID: 82, Game: "Portal 2 - Cooperative", Chapter: "Course 3 - Hard-Light Surfaces", Map: "Turret Walls"}, | 329 | {ID: 82, Game: "Portal 2 - Cooperative", Chapter: "Course 3 - Hard-Light Surfaces", Map: "Turret Walls"}, |
| 320 | {ID: 83, Game: "Portal 2 - Cooperative", Chapter: "Course 3 - Hard-Light Surfaces", Map: "Turret Assasin"}, | 330 | {ID: 83, Game: "Portal 2 - Cooperative", Chapter: "Course 3 - Hard-Light Surfaces", Map: "Turret Assassin"}, |
| 321 | {ID: 84, Game: "Portal 2 - Cooperative", Chapter: "Course 3 - Hard-Light Surfaces", Map: "Bridge Testing"}, | 331 | {ID: 84, Game: "Portal 2 - Cooperative", Chapter: "Course 3 - Hard-Light Surfaces", Map: "Bridge Testing"}, |
| 322 | {ID: 85, Game: "Portal 2 - Cooperative", Chapter: "Course 4 - Excursion Funnels", Map: "Cooperative Funnels"}, | 332 | {ID: 85, Game: "Portal 2 - Cooperative", Chapter: "Course 4 - Excursion Funnels", Map: "Cooperative Funnels"}, |
| 323 | {ID: 86, Game: "Portal 2 - Cooperative", Chapter: "Course 4 - Excursion Funnels", Map: "Funnel Drill"}, | 333 | {ID: 86, Game: "Portal 2 - Cooperative", Chapter: "Course 4 - Excursion Funnels", Map: "Funnel Drill"}, |
diff --git a/backend/handlers/map.go b/backend/handlers/map.go index b2a0b91..9cb0bcc 100644 --- a/backend/handlers/map.go +++ b/backend/handlers/map.go | |||
| @@ -77,12 +77,12 @@ func FetchMapSummary(c *gin.Context) { | |||
| 77 | } | 77 | } |
| 78 | // Get map data | 78 | // Get map data |
| 79 | response.Map.ID = intID | 79 | response.Map.ID = intID |
| 80 | sql := `SELECT m.id, g.name, c.name, m.name, m.image, g.is_coop, m.is_disabled | 80 | sql := `SELECT m.id, g.name, c.name, m.name, m.image, g.is_coop, m.is_disabled, m.difficulty |
| 81 | FROM maps m | 81 | FROM maps m |
| 82 | INNER JOIN games g ON m.game_id = g.id | 82 | INNER JOIN games g ON m.game_id = g.id |
| 83 | INNER JOIN chapters c ON m.chapter_id = c.id | 83 | INNER JOIN chapters c ON m.chapter_id = c.id |
| 84 | WHERE m.id = $1` | 84 | WHERE m.id = $1` |
| 85 | err = database.DB.QueryRow(sql, id).Scan(&response.Map.ID, &response.Map.GameName, &response.Map.ChapterName, &response.Map.MapName, &response.Map.Image, &response.Map.IsCoop, &response.Map.IsDisabled) | 85 | err = database.DB.QueryRow(sql, id).Scan(&response.Map.ID, &response.Map.GameName, &response.Map.ChapterName, &response.Map.MapName, &response.Map.Image, &response.Map.IsCoop, &response.Map.IsDisabled, &response.Map.Difficulty) |
| 86 | if err != nil { | 86 | if err != nil { |
| 87 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 87 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| 88 | return | 88 | return |
| @@ -492,6 +492,7 @@ func FetchMaps(c *gin.Context) { | |||
| 492 | m.id, | 492 | m.id, |
| 493 | m.name, | 493 | m.name, |
| 494 | m.is_disabled, | 494 | m.is_disabled, |
| 495 | m.difficulty, | ||
| 495 | m.image, | 496 | m.image, |
| 496 | cat.id, | 497 | cat.id, |
| 497 | cat.name, | 498 | cat.name, |
| @@ -529,7 +530,7 @@ func FetchMaps(c *gin.Context) { | |||
| 529 | for rows.Next() { | 530 | for rows.Next() { |
| 530 | var mapShort models.MapSelect | 531 | var mapShort models.MapSelect |
| 531 | var categoryPortal models.CategoryPortal | 532 | var categoryPortal models.CategoryPortal |
| 532 | if err := rows.Scan(&mapShort.ID, &mapShort.Name, &mapShort.IsDisabled, &mapShort.Image, &categoryPortal.Category.ID, &categoryPortal.Category.Name, &categoryPortal.PortalCount); err != nil { | 533 | if err := rows.Scan(&mapShort.ID, &mapShort.Name, &mapShort.IsDisabled, &mapShort.Difficulty, &mapShort.Image, &categoryPortal.Category.ID, &categoryPortal.Category.Name, &categoryPortal.PortalCount); err != nil { |
| 533 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 534 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| 534 | return | 535 | return |
| 535 | } | 536 | } |
| @@ -571,6 +572,7 @@ func FetchChapterMaps(c *gin.Context) { | |||
| 571 | m.name AS map_name, | 572 | m.name AS map_name, |
| 572 | c.name AS chapter_name, | 573 | c.name AS chapter_name, |
| 573 | m.is_disabled, | 574 | m.is_disabled, |
| 575 | m.difficulty, | ||
| 574 | m.image, | 576 | m.image, |
| 575 | cat.id, | 577 | cat.id, |
| 576 | cat.name, | 578 | cat.name, |
| @@ -610,7 +612,7 @@ func FetchChapterMaps(c *gin.Context) { | |||
| 610 | for rows.Next() { | 612 | for rows.Next() { |
| 611 | var mapShort models.MapSelect | 613 | var mapShort models.MapSelect |
| 612 | var categoryPortal models.CategoryPortal | 614 | var categoryPortal models.CategoryPortal |
| 613 | if err := rows.Scan(&mapShort.ID, &mapShort.Name, &chapterName, &mapShort.IsDisabled, &mapShort.Image, &categoryPortal.Category.ID, &categoryPortal.Category.Name, &categoryPortal.PortalCount); err != nil { | 615 | if err := rows.Scan(&mapShort.ID, &mapShort.Name, &chapterName, &mapShort.IsDisabled, &mapShort.Difficulty, &mapShort.Image, &categoryPortal.Category.ID, &categoryPortal.Category.Name, &categoryPortal.PortalCount); err != nil { |
| 614 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) | 616 | c.JSON(http.StatusOK, models.ErrorResponse(err.Error())) |
| 615 | return | 617 | return |
| 616 | } | 618 | } |
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 |
diff --git a/backend/handlers/user.go b/backend/handlers/user.go index 53f0d06..ea31065 100644 --- a/backend/handlers/user.go +++ b/backend/handlers/user.go | |||
| @@ -4,6 +4,7 @@ import ( | |||
| 4 | "net/http" | 4 | "net/http" |
| 5 | "os" | 5 | "os" |
| 6 | "regexp" | 6 | "regexp" |
| 7 | "sort" | ||
| 7 | "time" | 8 | "time" |
| 8 | 9 | ||
| 9 | "lphub/database" | 10 | "lphub/database" |
| @@ -183,6 +184,15 @@ func Profile(c *gin.Context) { | |||
| 183 | } | 184 | } |
| 184 | } | 185 | } |
| 185 | } | 186 | } |
| 187 | // Sort the overall rankings | ||
| 188 | sort.Slice(rankingsList.Overall, func(i, j int) bool { | ||
| 189 | a := rankingsList.Overall[i] | ||
| 190 | b := rankingsList.Overall[j] | ||
| 191 | if a.TotalScore == b.TotalScore { | ||
| 192 | return a.User.SteamID < b.User.SteamID | ||
| 193 | } | ||
| 194 | return a.TotalScore < b.TotalScore | ||
| 195 | }) | ||
| 186 | 196 | ||
| 187 | placement := 1 | 197 | placement := 1 |
| 188 | ties := 0 | 198 | ties := 0 |
| @@ -507,6 +517,15 @@ func FetchUser(c *gin.Context) { | |||
| 507 | } | 517 | } |
| 508 | } | 518 | } |
| 509 | } | 519 | } |
| 520 | // Sort the overall rankings | ||
| 521 | sort.Slice(rankingsList.Overall, func(i, j int) bool { | ||
| 522 | a := rankingsList.Overall[i] | ||
| 523 | b := rankingsList.Overall[j] | ||
| 524 | if a.TotalScore == b.TotalScore { | ||
| 525 | return a.User.SteamID < b.User.SteamID | ||
| 526 | } | ||
| 527 | return a.TotalScore < b.TotalScore | ||
| 528 | }) | ||
| 510 | 529 | ||
| 511 | placement := 1 | 530 | placement := 1 |
| 512 | ties := 0 | 531 | ties := 0 |