From 35ac5a65ae6006a669d1eaabcd0b0628b3a0334b Mon Sep 17 00:00:00 2001 From: Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> Date: Tue, 20 Jun 2023 22:10:09 +0300 Subject: feat: parser for cm portal & tick counting (#42) Former-commit-id: fceadf1acf59b2afe0a4d18cc4a7ac847fc2fc42 --- backend/parser/parser.REMOVED.git-id | 1 + 1 file changed, 1 insertion(+) create mode 100644 backend/parser/parser.REMOVED.git-id (limited to 'backend/parser') diff --git a/backend/parser/parser.REMOVED.git-id b/backend/parser/parser.REMOVED.git-id new file mode 100644 index 0000000..b09611f --- /dev/null +++ b/backend/parser/parser.REMOVED.git-id @@ -0,0 +1 @@ +72bd63d0667a78d35f643f1f8fcf71d9269d2719 \ No newline at end of file -- cgit v1.2.3 From 7050773414c550b7693c41a9bdd8cb390d7ef647 Mon Sep 17 00:00:00 2001 From: Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> Date: Thu, 29 Jun 2023 10:45:12 +0300 Subject: fix: record controller Former-commit-id: bff6b62474e02f644d93f49827145cfd92682c6f --- backend/controllers/recordController.go | 87 +++++++++-------------- backend/models/requests.go | 4 +- backend/parser/parser.go | 7 ++ docs/docs.go | 118 ++++++++++++++++++++++++-------- docs/swagger.json | 112 ++++++++++++++++++++++-------- docs/swagger.yaml | 78 +++++++++++++++------ 6 files changed, 269 insertions(+), 137 deletions(-) create mode 100644 backend/parser/parser.go (limited to 'backend/parser') diff --git a/backend/controllers/recordController.go b/backend/controllers/recordController.go index 627be57..aec31bb 100644 --- a/backend/controllers/recordController.go +++ b/backend/controllers/recordController.go @@ -5,14 +5,15 @@ import ( b64 "encoding/base64" "io" "log" + "mime/multipart" "net/http" "os" - "strconv" "github.com/gin-gonic/gin" "github.com/google/uuid" "github.com/pektezol/leastportals/backend/database" "github.com/pektezol/leastportals/backend/models" + "github.com/pektezol/leastportals/backend/parser" "golang.org/x/oauth2/google" "golang.org/x/oauth2/jwt" "google.golang.org/api/drive/v3" @@ -25,9 +26,8 @@ import ( // @Accept mpfd // @Produce json // @Param Authorization header string true "JWT Token" -// @Param demos formData []file true "Demos" -// @Param score_count formData int true "Score Count" -// @Param score_time formData int true "Score Time" +// @Param host_demo formData file true "Host Demo" +// @Param partner_demo formData file true "Partner Demo" // @Param is_partner_orange formData boolean true "Is Partner Orange" // @Param partner_id formData string true "Partner ID" // @Success 200 {object} models.Response{data=models.RecordRequest} @@ -43,11 +43,11 @@ func CreateRecordWithDemo(c *gin.Context) { return } // Check if map is sp or mp - var gameID int + var gameName string var isCoop bool var isDisabled bool - sql := `SELECT game_id, is_disabled FROM maps WHERE id = $1` - err := database.DB.QueryRow(sql, mapId).Scan(&gameID, &isDisabled) + sql := `SELECT g.name, m.is_disabled FROM maps m INNER JOIN games g ON m.game_id=g.id WHERE id = $1` + err := database.DB.QueryRow(sql, mapId).Scan(&gameName, &isDisabled) if err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return @@ -56,51 +56,23 @@ func CreateRecordWithDemo(c *gin.Context) { c.JSON(http.StatusBadRequest, models.ErrorResponse("Map is not available for competitive boards.")) return } - if gameID == 2 { + if gameName == "Portal 2 - Cooperative" { isCoop = true } // Get record request var record models.RecordRequest - score_count, err := strconv.Atoi(c.PostForm("score_count")) - if err != nil { - c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) - return - } - score_time, err := strconv.Atoi(c.PostForm("score_time")) - if err != nil { - c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) - return - } - is_partner_orange, err := strconv.ParseBool(c.PostForm("is_partner_orange")) - if err != nil { - c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) - return - } - record.ScoreCount = score_count - record.ScoreTime = score_time - record.PartnerID = c.PostForm("partner_id") - record.IsPartnerOrange = is_partner_orange - if record.PartnerID == "" { - c.JSON(http.StatusBadRequest, models.ErrorResponse("No partner id given.")) - return - } - // Multipart form - form, err := c.MultipartForm() - if err != nil { + if err := c.ShouldBind(&record); err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return } - files := form.File["demos"] - if len(files) != 2 && isCoop { - c.JSON(http.StatusBadRequest, models.ErrorResponse("Not enough demos for coop submission.")) - return - } - if len(files) != 1 && !isCoop { - c.JSON(http.StatusBadRequest, models.ErrorResponse("Too many demos for singleplayer submission.")) + if isCoop && (record.PartnerDemo == nil || record.PartnerID == "") { + c.JSON(http.StatusBadRequest, models.ErrorResponse("Invalid entry for coop record submission.")) return } - var hostDemoUUID string - var partnerDemoUUID string + // Demo files + demoFiles := []*multipart.FileHeader{record.HostDemo, record.PartnerDemo} + var hostDemoUUID, hostDemoFileID, partnerDemoUUID, partnerDemoFileID string + var hostDemoScoreCount, hostDemoScoreTime int client := serviceAccount() srv, err := drive.New(client) if err != nil { @@ -115,16 +87,15 @@ func CreateRecordWithDemo(c *gin.Context) { } // Defer to a rollback in case anything fails defer tx.Rollback() - fileID := "" - for i, header := range files { + for i, header := range demoFiles { uuid := uuid.New().String() // Upload & insert into demos - err = c.SaveUploadedFile(header, "docs/"+header.Filename) + err = c.SaveUploadedFile(header, "parser/demos/"+header.Filename) if err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return } - f, err := os.Open("docs/" + header.Filename) + f, err := os.Open("parser/demos/" + header.Filename) if err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return @@ -135,11 +106,16 @@ func CreateRecordWithDemo(c *gin.Context) { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return } - fileID = file.Id + hostDemoScoreCount, hostDemoScoreTime, err = parser.ProcessDemo(record.HostDemo) + if err != nil { + c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) + return + } if i == 0 { + hostDemoFileID = file.Id hostDemoUUID = uuid - } - if i == 1 { + } else if i == 1 { + partnerDemoFileID = file.Id partnerDemoUUID = uuid } _, err = tx.Exec(`INSERT INTO demos (id,location_id) VALUES ($1,$2)`, uuid, file.Id) @@ -148,7 +124,7 @@ func CreateRecordWithDemo(c *gin.Context) { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return } - os.Remove("docs/" + header.Filename) + os.Remove("parser/demos/" + header.Filename) } // Insert into records if isCoop { @@ -163,9 +139,10 @@ func CreateRecordWithDemo(c *gin.Context) { partnerID = user.(models.User).SteamID hostID = record.PartnerID } - _, err := tx.Exec(sql, mapId, record.ScoreCount, record.ScoreTime, hostID, partnerID, hostDemoUUID, partnerDemoUUID) + _, err := tx.Exec(sql, mapId, hostDemoScoreCount, hostDemoScoreTime, hostID, partnerID, hostDemoUUID, partnerDemoUUID) if err != nil { - deleteFile(srv, fileID) + deleteFile(srv, hostDemoFileID) + deleteFile(srv, partnerDemoFileID) c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return } @@ -180,9 +157,9 @@ func CreateRecordWithDemo(c *gin.Context) { } else { sql := `INSERT INTO records_sp(map_id,score_count,score_time,user_id,demo_id) VALUES($1, $2, $3, $4, $5)` - _, err := tx.Exec(sql, mapId, record.ScoreCount, record.ScoreTime, user.(models.User).SteamID, hostDemoUUID) + _, err := tx.Exec(sql, mapId, hostDemoScoreCount, hostDemoScoreTime, user.(models.User).SteamID, hostDemoUUID) if err != nil { - deleteFile(srv, fileID) + deleteFile(srv, hostDemoFileID) c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return } diff --git a/backend/models/requests.go b/backend/models/requests.go index e95eab6..4b4657b 100644 --- a/backend/models/requests.go +++ b/backend/models/requests.go @@ -22,8 +22,8 @@ type CreateMapHistoryRequest struct { } type RecordRequest struct { - HostDemo *multipart.FileHeader `json:"host_demo" form:"host_demo" binding:"required"` - PartnerDemo *multipart.FileHeader `json:"partner_demo" form:"partner_demo"` + HostDemo *multipart.FileHeader `json:"host_demo" form:"host_demo" binding:"required" swaggerignore:"true"` + PartnerDemo *multipart.FileHeader `json:"partner_demo" form:"partner_demo" swaggerignore:"true"` IsPartnerOrange bool `json:"is_partner_orange" form:"is_partner_orange"` PartnerID string `json:"partner_id" form:"partner_id"` } diff --git a/backend/parser/parser.go b/backend/parser/parser.go new file mode 100644 index 0000000..6f9a24f --- /dev/null +++ b/backend/parser/parser.go @@ -0,0 +1,7 @@ +package parser + +import "mime/multipart" + +func ProcessDemo(demo *multipart.FileHeader) (scoreCount int, scoreTime int, err error) { + return 0, 0, nil +} diff --git a/docs/docs.go b/docs/docs.go index a0aad6f..090d3e8 100644 --- a/docs/docs.go +++ b/docs/docs.go @@ -1,5 +1,5 @@ -// Package docs GENERATED BY SWAG; DO NOT EDIT -// This file was generated by swaggo/swag +// Code generated by swaggo/swag. DO NOT EDIT. + package docs import "github.com/swaggo/swag" @@ -345,26 +345,16 @@ const docTemplate = `{ "required": true }, { - "type": "array", - "items": { - "type": "file" - }, - "description": "Demos", - "name": "demos", + "type": "file", + "description": "Host Demo", + "name": "host_demo", "in": "formData", "required": true }, { - "type": "integer", - "description": "Score Count", - "name": "score_count", - "in": "formData", - "required": true - }, - { - "type": "integer", - "description": "Score Time", - "name": "score_time", + "type": "file", + "description": "Partner Demo", + "name": "partner_demo", "in": "formData", "required": true }, @@ -461,6 +451,50 @@ const docTemplate = `{ } } } + }, + "put": { + "produces": [ + "application/json" + ], + "tags": [ + "maps" + ], + "summary": "Edit map summary with specified map id.", + "parameters": [ + { + "type": "integer", + "description": "Map ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.EditMapSummaryRequest" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/models.Response" + } + } + } } }, "/profile": { @@ -843,6 +877,37 @@ const docTemplate = `{ } } }, + "models.EditMapSummaryRequest": { + "type": "object", + "required": [ + "description", + "record_date", + "route_id", + "score_count", + "showcase", + "user_name" + ], + "properties": { + "description": { + "type": "string" + }, + "record_date": { + "type": "string" + }, + "route_id": { + "type": "integer" + }, + "score_count": { + "type": "integer" + }, + "showcase": { + "type": "string" + }, + "user_name": { + "type": "string" + } + } + }, "models.Game": { "type": "object", "properties": { @@ -874,6 +939,9 @@ const docTemplate = `{ "id": { "type": "integer" }, + "image": { + "type": "string" + }, "map_name": { "type": "string" } @@ -1003,24 +1071,12 @@ const docTemplate = `{ }, "models.RecordRequest": { "type": "object", - "required": [ - "is_partner_orange", - "partner_id", - "score_count", - "score_time" - ], "properties": { "is_partner_orange": { "type": "boolean" }, "partner_id": { "type": "string" - }, - "score_count": { - "type": "integer" - }, - "score_time": { - "type": "integer" } } }, @@ -1100,6 +1156,8 @@ var SwaggerInfo = &swag.Spec{ Description: "Backend API endpoints for the Least Portals Database.", InfoInstanceName: "swagger", SwaggerTemplate: docTemplate, + LeftDelim: "{{", + RightDelim: "}}", } func init() { diff --git a/docs/swagger.json b/docs/swagger.json index 2279ff6..62079b1 100644 --- a/docs/swagger.json +++ b/docs/swagger.json @@ -338,26 +338,16 @@ "required": true }, { - "type": "array", - "items": { - "type": "file" - }, - "description": "Demos", - "name": "demos", + "type": "file", + "description": "Host Demo", + "name": "host_demo", "in": "formData", "required": true }, { - "type": "integer", - "description": "Score Count", - "name": "score_count", - "in": "formData", - "required": true - }, - { - "type": "integer", - "description": "Score Time", - "name": "score_time", + "type": "file", + "description": "Partner Demo", + "name": "partner_demo", "in": "formData", "required": true }, @@ -454,6 +444,50 @@ } } } + }, + "put": { + "produces": [ + "application/json" + ], + "tags": [ + "maps" + ], + "summary": "Edit map summary with specified map id.", + "parameters": [ + { + "type": "integer", + "description": "Map ID", + "name": "id", + "in": "path", + "required": true + } + ], + "responses": { + "200": { + "description": "OK", + "schema": { + "allOf": [ + { + "$ref": "#/definitions/models.Response" + }, + { + "type": "object", + "properties": { + "data": { + "$ref": "#/definitions/models.EditMapSummaryRequest" + } + } + } + ] + } + }, + "400": { + "description": "Bad Request", + "schema": { + "$ref": "#/definitions/models.Response" + } + } + } } }, "/profile": { @@ -836,6 +870,37 @@ } } }, + "models.EditMapSummaryRequest": { + "type": "object", + "required": [ + "description", + "record_date", + "route_id", + "score_count", + "showcase", + "user_name" + ], + "properties": { + "description": { + "type": "string" + }, + "record_date": { + "type": "string" + }, + "route_id": { + "type": "integer" + }, + "score_count": { + "type": "integer" + }, + "showcase": { + "type": "string" + }, + "user_name": { + "type": "string" + } + } + }, "models.Game": { "type": "object", "properties": { @@ -867,6 +932,9 @@ "id": { "type": "integer" }, + "image": { + "type": "string" + }, "map_name": { "type": "string" } @@ -996,24 +1064,12 @@ }, "models.RecordRequest": { "type": "object", - "required": [ - "is_partner_orange", - "partner_id", - "score_count", - "score_time" - ], "properties": { "is_partner_orange": { "type": "boolean" }, "partner_id": { "type": "string" - }, - "score_count": { - "type": "integer" - }, - "score_time": { - "type": "integer" } } }, diff --git a/docs/swagger.yaml b/docs/swagger.yaml index ba4775a..9d58620 100644 --- a/docs/swagger.yaml +++ b/docs/swagger.yaml @@ -32,6 +32,28 @@ definitions: game: $ref: '#/definitions/models.Game' type: object + models.EditMapSummaryRequest: + properties: + description: + type: string + record_date: + type: string + route_id: + type: integer + score_count: + type: integer + showcase: + type: string + user_name: + type: string + required: + - description + - record_date + - route_id + - score_count + - showcase + - user_name + type: object models.Game: properties: id: @@ -52,6 +74,8 @@ definitions: type: string id: type: integer + image: + type: string map_name: type: string type: object @@ -140,15 +164,6 @@ definitions: type: boolean partner_id: type: string - score_count: - type: integer - score_time: - type: integer - required: - - is_partner_orange - - partner_id - - score_count - - score_time type: object models.Response: properties: @@ -388,23 +403,16 @@ paths: name: Authorization required: true type: string - - description: Demos - in: formData - items: - type: file - name: demos - required: true - type: array - - description: Score Count + - description: Host Demo in: formData - name: score_count + name: host_demo required: true - type: integer - - description: Score Time + type: file + - description: Partner Demo in: formData - name: score_time + name: partner_demo required: true - type: integer + type: file - description: Is Partner Orange in: formData name: is_partner_orange @@ -465,6 +473,32 @@ paths: summary: Get map summary with specified id. tags: - maps + put: + parameters: + - description: Map ID + in: path + name: id + required: true + type: integer + produces: + - application/json + responses: + "200": + description: OK + schema: + allOf: + - $ref: '#/definitions/models.Response' + - properties: + data: + $ref: '#/definitions/models.EditMapSummaryRequest' + type: object + "400": + description: Bad Request + schema: + $ref: '#/definitions/models.Response' + summary: Edit map summary with specified map id. + tags: + - maps /profile: get: consumes: -- cgit v1.2.3 From 1a14309e212a697bae8acd4ddb17723b0f6670a6 Mon Sep 17 00:00:00 2001 From: Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> Date: Mon, 3 Jul 2023 08:40:43 +0000 Subject: feat: parser for getting portal and tick count (#42) Former-commit-id: 1619ece868b7009a661dcc3b622746cc09981042 --- backend/controllers/recordController.go | 2 +- backend/parser/parser.go | 41 ++++++++++++++++++++++++++++++--- 2 files changed, 39 insertions(+), 4 deletions(-) (limited to 'backend/parser') diff --git a/backend/controllers/recordController.go b/backend/controllers/recordController.go index c865bfb..183ab27 100644 --- a/backend/controllers/recordController.go +++ b/backend/controllers/recordController.go @@ -106,7 +106,7 @@ func CreateRecordWithDemo(c *gin.Context) { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return } - hostDemoScoreCount, hostDemoScoreTime, err = parser.ProcessDemo(record.HostDemo) + hostDemoScoreCount, hostDemoScoreTime, err = parser.ProcessDemo("parser/demos/" + header.Filename) if err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return diff --git a/backend/parser/parser.go b/backend/parser/parser.go index 6f9a24f..040b94a 100644 --- a/backend/parser/parser.go +++ b/backend/parser/parser.go @@ -1,7 +1,42 @@ package parser -import "mime/multipart" +import ( + "bufio" + "fmt" + "os/exec" + "strconv" + "strings" +) -func ProcessDemo(demo *multipart.FileHeader) (scoreCount int, scoreTime int, err error) { - return 0, 0, nil +func ProcessDemo(demoPath string) (int, int, error) { + cmd := exec.Command("bash", "-c", fmt.Sprintf(`echo "FEXBash" && ./parser %s`, demoPath)) + stdout, err := cmd.StdoutPipe() + if err != nil { + return 0, 0, err + } + cmd.Start() + scanner := bufio.NewScanner(stdout) + var cmTicks, portalCount int + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, "CM ticks") { + cmTicksStr := strings.TrimSpace(strings.Split(line, ":")[1]) + cmTicks, err = strconv.Atoi(cmTicksStr) + if err != nil { + return 0, 0, err + } + } + if strings.Contains(line, "Portal count") { + portalCountStr := strings.TrimSpace(strings.Split(line, ":")[1]) + portalCount, err = strconv.Atoi(portalCountStr) + if err != nil { + return 0, 0, err + } + } + } + err = cmd.Wait() + if err != nil { + return 0, 0, err + } + return cmTicks, portalCount, nil } -- cgit v1.2.3 From 50f5fff2873847b3e5df92d204e5166b641baeb2 Mon Sep 17 00:00:00 2001 From: Arda Serdar Pektezol <1669855+pektezol@users.noreply.github.com> Date: Mon, 3 Jul 2023 17:22:14 +0000 Subject: fix: parser actually works now (#42) Former-commit-id: 28378525f79e2879a1306b3bb169668d238cc117 --- backend/controllers/recordController.go | 13 ++++++++----- backend/parser/parser.go | 13 +++++++------ 2 files changed, 15 insertions(+), 11 deletions(-) (limited to 'backend/parser') diff --git a/backend/controllers/recordController.go b/backend/controllers/recordController.go index 28c55e0..1c0f3b2 100644 --- a/backend/controllers/recordController.go +++ b/backend/controllers/recordController.go @@ -71,7 +71,10 @@ func CreateRecordWithDemo(c *gin.Context) { return } // Demo files - demoFiles := []*multipart.FileHeader{record.HostDemo, record.PartnerDemo} + demoFiles := []*multipart.FileHeader{record.HostDemo} + if isCoop { + demoFiles = append(demoFiles, record.PartnerDemo) + } var hostDemoUUID, hostDemoFileID, partnerDemoUUID, partnerDemoFileID string var hostDemoScoreCount, hostDemoScoreTime int client := serviceAccount() @@ -91,12 +94,12 @@ func CreateRecordWithDemo(c *gin.Context) { for i, header := range demoFiles { uuid := uuid.New().String() // Upload & insert into demos - err = c.SaveUploadedFile(header, "backend/parser/demos/"+header.Filename) + err = c.SaveUploadedFile(header, "backend/parser/"+header.Filename) if err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return } - f, err := os.Open("backend/parser/demos/" + header.Filename) + f, err := os.Open("backend/parser/" + header.Filename) if err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return @@ -107,7 +110,7 @@ func CreateRecordWithDemo(c *gin.Context) { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return } - hostDemoScoreCount, hostDemoScoreTime, err = parser.ProcessDemo("backend/parser/demos/" + header.Filename) + hostDemoScoreCount, hostDemoScoreTime, err = parser.ProcessDemo("backend/parser/" + header.Filename) if err != nil { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return @@ -125,7 +128,7 @@ func CreateRecordWithDemo(c *gin.Context) { c.JSON(http.StatusBadRequest, models.ErrorResponse(err.Error())) return } - os.Remove("backend/parser/demos/" + header.Filename) + os.Remove("backend/parser/" + header.Filename) } // Insert into records if isCoop { diff --git a/backend/parser/parser.go b/backend/parser/parser.go index 040b94a..562b8c0 100644 --- a/backend/parser/parser.go +++ b/backend/parser/parser.go @@ -3,15 +3,17 @@ package parser import ( "bufio" "fmt" + "log" "os/exec" "strconv" "strings" ) func ProcessDemo(demoPath string) (int, int, error) { - cmd := exec.Command("bash", "-c", fmt.Sprintf(`echo "FEXBash" && ./parser %s`, demoPath)) + cmd := exec.Command("bash", "-c", fmt.Sprintf(`echo "FEXBash" && ./backend/parser/parser %s`, demoPath)) stdout, err := cmd.StdoutPipe() if err != nil { + log.Println(err) return 0, 0, err } cmd.Start() @@ -34,9 +36,8 @@ func ProcessDemo(demoPath string) (int, int, error) { } } } - err = cmd.Wait() - if err != nil { - return 0, 0, err - } - return cmTicks, portalCount, nil + cmd.Wait() + // We don't check for error in wait, since FEXBash always gives segmentation fault + // Wanted output is retrieved, so it's okay (i think) + return portalCount, cmTicks, nil } -- cgit v1.2.3