From 61426e15a00e3ddcfaded88fcc9d76db00b61bc1 Mon Sep 17 00:00:00 2001 From: aunefyren Date: Tue, 13 Aug 2024 10:33:32 +0200 Subject: [PATCH] Combine Strava exercises --- controllers/exercise.go | 79 +++++++++++++++++++++++++++++++++++++ controllers/strava.go | 2 +- database/exercise.go | 5 +-- go.mod | 16 ++++---- go.sum | 33 ++++++++-------- main.go | 1 + web/js/exercise.js | 86 +++++++++++++++++++++++++++++++++++++++++ 7 files changed, 193 insertions(+), 29 deletions(-) diff --git a/controllers/exercise.go b/controllers/exercise.go index cf18041..771ebcf 100644 --- a/controllers/exercise.go +++ b/controllers/exercise.go @@ -9,6 +9,7 @@ import ( "html" "log" "net/http" + "sort" "strconv" "strings" "time" @@ -1215,3 +1216,81 @@ func GetExercisesForWeekUsingUserID(timeReq time.Time, userID uuid.UUID) (exerci return } + +func APIStravaCombine(context *gin.Context) { + // Create week request + var stravaIDs []string + var exercises []models.Exercise + + // Parse request + if err := context.ShouldBindJSON(&stravaIDs); err != nil { + log.Println("Failed to parse request. Error: " + err.Error()) + context.JSON(http.StatusBadRequest, gin.H{"error": "Failed to parse request."}) + context.Abort() + return + } + + // Get user ID + userID, err := middlewares.GetAuthUsername(context.GetHeader("Authorization")) + if err != nil { + log.Println("Failed to verify user ID. Error: " + "Failed to verify user ID.") + context.JSON(http.StatusBadRequest, gin.H{"error": "Failed to verify user ID."}) + context.Abort() + return + } + + for _, stravaID := range stravaIDs { + exercise, err := database.GetExerciseForUserWithStravaID(userID, stravaID) + if err != nil { + log.Println("Failed to get exercise. Error: " + err.Error()) + context.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to get exercise."}) + context.Abort() + return + } else if exercise == nil { + log.Println("Failed to verify exercise. Error: " + err.Error()) + context.JSON(http.StatusBadRequest, gin.H{"error": "Failed to verify exercise."}) + context.Abort() + return + } + + exercises = append(exercises, *exercise) + } + + sort.Slice(exercises, func(i, j int) bool { + return exercises[j].CreatedAt.After(exercises[i].CreatedAt) + }) + + // Build string for Strava IDs + stravaIDsString := "" + for index, stravaID := range stravaIDs { + if index != 0 { + stravaIDsString += ";" + } + stravaIDsString += stravaID + } + + for index, exercise := range exercises { + if index == 0 { + exercise.StravaID = &stravaIDsString + + _, err := database.UpdateExerciseInDB(exercise) + if err != nil { + log.Println("Failed to update exercise. Error: " + err.Error()) + context.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update exercise."}) + context.Abort() + return + } + } else { + exercise.Enabled = false + _, err := database.UpdateExerciseInDB(exercise) + if err != nil { + log.Println("Failed to update exercise. Error: " + err.Error()) + context.JSON(http.StatusInternalServerError, gin.H{"error": "Failed to update exercise."}) + context.Abort() + return + } + } + } + + context.JSON(http.StatusCreated, gin.H{"message": "Exercises combined."}) +} diff --git a/controllers/strava.go b/controllers/strava.go index 7126cdb..296a95d 100644 --- a/controllers/strava.go +++ b/controllers/strava.go @@ -305,7 +305,7 @@ func StravaSyncWeekForUser(user models.User, configFile models.ConfigStruct, poi } } - exercise, err := database.GetExerciseForUserWithStravaID(user.ID, int(activity.ID)) + exercise, err := database.GetExerciseForUserWithStravaID(user.ID, strconv.Itoa(int(activity.ID))) if err != nil { log.Println("Failed to get exercise. ID: " + user.ID.String()) return errors.New("Failed to get exercise.") diff --git a/database/exercise.go b/database/exercise.go index 14ad4fe..1bbd275 100644 --- a/database/exercise.go +++ b/database/exercise.go @@ -3,7 +3,6 @@ package database import ( "aunefyren/treningheten/models" "errors" - "strconv" "github.com/google/uuid" ) @@ -124,11 +123,11 @@ func CreateExerciseInDB(exercise models.Exercise) (models.Exercise, error) { return exercise, nil } -func GetExerciseForUserWithStravaID(userID uuid.UUID, stravaID int) (exercise *models.Exercise, err error) { +func GetExerciseForUserWithStravaID(userID uuid.UUID, stravaID string) (exercise *models.Exercise, err error) { exercise = nil err = nil - stravaIDString := "%" + strconv.Itoa(stravaID) + "%" + stravaIDString := "%" + stravaID + "%" exerciseRecord := Instance.Model(exercise).Where("`exercises`.enabled = ?", 1). Where("`exercises`.strava_id LIKE ?", stravaIDString). diff --git a/go.mod b/go.mod index f338c5f..87fa4a2 100644 --- a/go.mod +++ b/go.mod @@ -11,14 +11,14 @@ require ( github.com/mroth/weightedrand/v2 v2.1.0 github.com/nfnt/resize v0.0.0-20180221191011-83c6a9932646 github.com/thanhpk/randstr v1.0.6 - golang.org/x/crypto v0.25.0 + golang.org/x/crypto v0.26.0 gorm.io/driver/mysql v1.5.7 gorm.io/gorm v1.25.11 ) require ( filippo.io/edwards25519 v1.1.0 // indirect - github.com/bytedance/sonic/loader v0.1.1 // indirect + github.com/bytedance/sonic/loader v0.2.0 // indirect github.com/cloudwego/base64x v0.1.4 // indirect github.com/cloudwego/iasm v0.2.0 // indirect github.com/google/go-cmp v0.5.8 // indirect @@ -27,8 +27,8 @@ require ( require ( github.com/SherClockHolmes/webpush-go v1.3.0 - github.com/bytedance/sonic v1.11.9 // indirect - github.com/gabriel-vasile/mimetype v1.4.4 // indirect + github.com/bytedance/sonic v1.12.1 // indirect + github.com/gabriel-vasile/mimetype v1.4.5 // indirect github.com/gin-contrib/sse v0.1.0 // indirect github.com/go-playground/locales v0.14.1 // indirect github.com/go-playground/universal-translator v0.18.1 // indirect @@ -48,10 +48,10 @@ require ( github.com/pelletier/go-toml/v2 v2.2.2 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.2.12 // indirect - golang.org/x/arch v0.8.0 // indirect - golang.org/x/net v0.27.0 // indirect - golang.org/x/sys v0.22.0 // indirect - golang.org/x/text v0.16.0 + golang.org/x/arch v0.9.0 // indirect + golang.org/x/net v0.28.0 // indirect + golang.org/x/sys v0.24.0 // indirect + golang.org/x/text v0.17.0 google.golang.org/protobuf v1.34.2 // indirect gopkg.in/alexcesaro/quotedprintable.v3 v3.0.0-20150716171945-2caba252f4dc // indirect gopkg.in/mail.v2 v2.3.1 // indirect diff --git a/go.sum b/go.sum index 0e7cca8..bf45d00 100644 --- a/go.sum +++ b/go.sum @@ -4,10 +4,11 @@ filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= github.com/SherClockHolmes/webpush-go v1.3.0 h1:CAu3FvEE9QS4drc3iKNgpBWFfGqNthKlZhp5QpYnu6k= github.com/SherClockHolmes/webpush-go v1.3.0/go.mod h1:AxRHmJuYwKGG1PVgYzToik1lphQvDnqFYDqimHvwhIw= -github.com/bytedance/sonic v1.11.9 h1:LFHENlIY/SLzDWverzdOvgMztTxcfcF+cqNsz9pK5zg= -github.com/bytedance/sonic v1.11.9/go.mod h1:LysEHSvpvDySVdC2f87zGWf6CIKJcAvqab1ZaiQtds4= -github.com/bytedance/sonic/loader v0.1.1 h1:c+e5Pt1k/cy5wMveRDyk2X4B9hF4g7an8N3zCYjJFNM= +github.com/bytedance/sonic v1.12.1 h1:jWl5Qz1fy7X1ioY74WqO0KjAMtAGQs4sYnjiEBiyX24= +github.com/bytedance/sonic v1.12.1/go.mod h1:B8Gt/XvtZ3Fqj+iSKMypzymZxw/FVwgIGKzMzT9r/rk= github.com/bytedance/sonic/loader v0.1.1/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= +github.com/bytedance/sonic/loader v0.2.0 h1:zNprn+lsIP06C/IqCHs3gPQIvnvpKbbxyXQP1iU4kWM= +github.com/bytedance/sonic/loader v0.2.0/go.mod h1:ncP89zfokxS5LZrJxl5z0UJcsk4M4yY2JpfqGeCtNLU= github.com/cloudwego/base64x v0.1.4 h1:jwCgWpFanWmN8xoIUHa2rtzmkd5J2plF/dnLS6Xd/0Y= github.com/cloudwego/base64x v0.1.4/go.mod h1:0zlkT4Wn5C6NdauXdJRhSKRlJvmclQ1hhJgA0rcu/8w= github.com/cloudwego/iasm v0.2.0 h1:1KNIy1I1H9hNNFEEH3DVnI4UujN+1zjpuk6gwHLTssg= @@ -16,8 +17,8 @@ github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ3 github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/gabriel-vasile/mimetype v1.4.4 h1:QjV6pZ7/XZ7ryI2KuyeEDE8wnh7fHP9YnQy+R0LnH8I= -github.com/gabriel-vasile/mimetype v1.4.4/go.mod h1:JwLei5XPtWdGiMFB5Pjle1oEeoSeEuJfJE+TtfvdB/s= +github.com/gabriel-vasile/mimetype v1.4.5 h1:J7wGKdGu33ocBOhGy0z653k/lFKLFDPJMG8Gql0kxn4= +github.com/gabriel-vasile/mimetype v1.4.5/go.mod h1:ibHel+/kbxn9x2407k1izTA1S81ku1z/DlgOW2QE0M4= github.com/gin-contrib/cors v1.7.2 h1:oLDHxdg8W/XDoN/8zamqk/Drgt4oVZDvaV0YmvVICQw= github.com/gin-contrib/cors v1.7.2/go.mod h1:SUJVARKgQ40dmrzgXEVxj2m7Ig1v1qIboQkPDTQ9t2E= github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= @@ -101,14 +102,13 @@ github.com/twitchyliquid64/golang-asm v0.15.1/go.mod h1:a1lVb/DtPvCB8fslRZhAngC2 github.com/ugorji/go/codec v1.2.12 h1:9LC83zGrHhuUA9l16C9AHXAqEV/2wBQ4nkvumAE65EE= github.com/ugorji/go/codec v1.2.12/go.mod h1:UNopzCgEMSXjBc6AOMqYvWC1ktqTAfzJZUZgYf6w6lg= github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= -golang.org/x/arch v0.0.0-20210923205945-b76863e36670/go.mod h1:5om86z9Hs0C8fWVUuoMHwpExlXzs5Tkyp9hOrfG7pp8= -golang.org/x/arch v0.8.0 h1:3wRIsP3pM4yUptoR96otTUOXI367OS0+c9eeRi9doIc= -golang.org/x/arch v0.8.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= +golang.org/x/arch v0.9.0 h1:ub9TgUInamJ8mrZIGlBG6/4TqWeMszd4N8lNorbrr6k= +golang.org/x/arch v0.9.0/go.mod h1:FEVrYAQjsQXMVJ1nsMoVVXPZg6p2JE2mx8psSWTDQys= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= -golang.org/x/crypto v0.25.0 h1:ypSNr+bnYL2YhwoMt2zPxHFmbAN1KZs/njMG3hxUp30= -golang.org/x/crypto v0.25.0/go.mod h1:T+wALwcMOSE0kXgUAnPAHqTLW+XHgcELELW8VaDgm/M= +golang.org/x/crypto v0.26.0 h1:RrRspgV4mU+YwB4FYnuBoKsUapNIL5cohGAmSH3azsw= +golang.org/x/crypto v0.26.0/go.mod h1:GY7jblb9wI+FOo5y8/S2oY4zWP07AkOJ4+jxCqdqn54= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= @@ -116,8 +116,8 @@ golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= -golang.org/x/net v0.27.0 h1:5K3Njcw06/l2y9vpGCSdcxWOYHOUk3dVNGDXN+FvAys= -golang.org/x/net v0.27.0/go.mod h1:dDi0PyhWNoiUOrAS8uXv/vnScO4wnHQO4mj9fn/RytE= +golang.org/x/net v0.28.0 h1:a9JDOJc5GMUJ0+UDqmLT86WiEy7iWyIhz8gz8E4e5hE= +golang.org/x/net v0.28.0/go.mod h1:yqtgsTWOOnlGLG9GFRrK3++bGOUEkNBoHZc8MEDWPNg= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -129,8 +129,8 @@ golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.22.0 h1:RI27ohtqKCnwULzJLqkv897zojh5/DwS/ENaMzUOaWI= -golang.org/x/sys v0.22.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= +golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg= +golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= @@ -140,8 +140,8 @@ golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= -golang.org/x/text v0.16.0 h1:a94ExnEXNtEwYLGJSIUxnWoxoRz/ZcCsV63ROupILh4= -golang.org/x/text v0.16.0/go.mod h1:GhwF1Be+LQoKShO3cGOHzqOgRrGaYc9AvblQOmPVHnI= +golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc= +golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= @@ -165,4 +165,3 @@ gorm.io/gorm v1.25.7/go.mod h1:hbnx/Oo0ChWMn1BIhpy1oYozzpM15i4YPuHDmfYtwg8= gorm.io/gorm v1.25.11 h1:/Wfyg1B/je1hnDx3sMkX+gAlxrlZpn6X0BXRlwXlvHg= gorm.io/gorm v1.25.11/go.mod h1:xh7N7RHfYlNc5EmcI/El95gXusucDrQnHXe0+CgWcLQ= nullprogram.com/x/optparse v1.0.0/go.mod h1:KdyPE+Igbe0jQUrVfMqDMeJQIJZEuyV7pjYmp6pbG50= -rsc.io/pdf v0.1.1/go.mod h1:n8OzWcQ6Sp37PL01nO98y4iUCRdTGarVfzxY20ICaU4= diff --git a/main.go b/main.go index 77ba129..078c572 100644 --- a/main.go +++ b/main.go @@ -253,6 +253,7 @@ func initRouter() *gin.Engine { auth.GET("/exercises/week", controllers.APIGetWeek) auth.POST("/exercises", controllers.APICreateExercise) auth.PUT("/exercises/:exercise_id", controllers.APIUpdateExercise) + auth.POST("/exercises/strava-combine", controllers.APIStravaCombine) auth.GET("/operations", controllers.APIGetOperationsForUser) auth.POST("/operations", controllers.APICreateOperationForUser) diff --git a/web/js/exercise.js b/web/js/exercise.js index cd87d04..b6f73fe 100644 --- a/web/js/exercise.js +++ b/web/js/exercise.js @@ -57,6 +57,12 @@ function load_page(result) { + + @@ -131,6 +137,12 @@ function placeExercises(exercises) { exercisesHTML = ""; counter = 1; + try { + document.getElementById('stravaCombineButtonWrapper').style.display = "none"; + } catch(e) { + console.log("Failed to remove Strava button. Error: " + e) + } + exercises.forEach(exercise => { var exerciseGenerated = generateExerciseHTML(exercise, counter) var exerciseHTML = ` @@ -158,8 +170,10 @@ function generateExerciseHTML(exercise, count) { } stravaHTML = "" + stravaCombineHTML = "" if(exercise.strava_id && exercise.strava_id.length > 0) { stravaHTML += `
` + stravaIDString = "" for(var i = 0; i < exercise.strava_id.length; i++) { stravaHTML += `

@@ -167,14 +181,32 @@ function generateExerciseHTML(exercise, count) {

`; + + if(i != 0) { + stravaIDString += ";" + } + stravaIDString += exercise.strava_id[i] } stravaHTML += `
` + + stravaCombineHTML += ` +
+ +
+ `; + + try { + document.getElementById('stravaCombineButtonWrapper').style.display = "flex"; + } catch(e) { + console.log("Failed to show Strava button. Error: " + e) + } } exerciseHTML = `
+ ${stravaCombineHTML}
${stravaHTML} @@ -1120,4 +1152,58 @@ function closeAllLists() { updateOperation(operationID) } } +} + +function combineStravaExercises() { + checkButtons = document.getElementsByClassName('stravaCombineCheck') + + var stravaIDArray = [] + + for(var i = 0; i < checkButtons.length; i++) { + console.log(checkButtons[i]) + stravaIDArray.push(checkButtons[i].id) + } + + if(stravaIDArray.length < 2) { + alert("Choose two or more exercises to combine."); + return; + } + + if(!confirm("Are you sure you want to combine these Strava exercises?")) { + return; + } + + combineStravaExercisesAPI(stravaIDArray); +} + +function combineStravaExercisesAPI(stravaIDArray) { + var form_data = JSON.stringify(stravaIDArray); + + var xhttp = new XMLHttpRequest(); + xhttp.onreadystatechange = function() { + if (this.readyState == 4) { + + try { + result = JSON.parse(this.responseText); + } catch(e) { + console.log(e +' - Response: ' + this.responseText); + error("Could not reach API."); + return; + } + + if(result.error) { + error(result.error); + } else { + alert(result.message) + location.reload(); + } + + } + }; + xhttp.withCredentials = true; + xhttp.open("post", api_url + "auth/exercises/strava-combine"); + xhttp.setRequestHeader("Content-Type", "application/json;charset=UTF-8"); + xhttp.setRequestHeader("Authorization", jwt); + xhttp.send(form_data); + return false; } \ No newline at end of file