Skip to content

Commit

Permalink
feat: Added GPX export api
Browse files Browse the repository at this point in the history
  • Loading branch information
bahattincinic committed May 21, 2024
1 parent e7ea6b5 commit e775adb
Show file tree
Hide file tree
Showing 11 changed files with 218 additions and 77 deletions.
40 changes: 40 additions & 0 deletions api/activity.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package api

import (
"encoding/xml"
"fmt"
"net/http"

"github.com/bahattincinic/fitwave/strava"
"github.com/labstack/echo/v4"
)

Expand Down Expand Up @@ -53,3 +56,40 @@ func (a *API) getActivity(c echo.Context) error {

return c.JSON(http.StatusOK, act)
}

// getActivity godoc
//
// @Summary Export Activity GPX
// @Tags activity
// @Accept json
// @Param id path string true "Activity ID"
// @Param Authorization header string true "Strava Access Token"
// @Success 200
// @Router /activities/{id}/gpx [get]
func (a *API) exportActivityGPS(c echo.Context) error {
user := c.Get(userContextKey).(*strava.User)

act, err := a.db.GetActivity(c.Param("id"))
if err != nil {
return err
}

if act == nil {
return echo.NewHTTPError(http.StatusNotFound, "activity Not Found")
}

gpx, err := a.st.ExportGPX(user, act.Id)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err)
}

if gpx == "" {
return echo.NewHTTPError(http.StatusNotFound, "GPX Not Found")
}

c.Response().Header().Set(echo.HeaderContentDisposition,
fmt.Sprintf("attachment; filename=\"activity_%d.gpx\"", act.Id))
c.Response().Header().Set(echo.HeaderContentType,
"application/gpx+xml")
return c.Blob(http.StatusOK, "application/gpx+xml", []byte(xml.Header+gpx))
}
40 changes: 37 additions & 3 deletions api/docs/docs.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,38 @@ const docTemplate = `{
}
}
},
"/activities/{id}/gpx": {
"get": {
"consumes": [
"application/json"
],
"tags": [
"activity"
],
"summary": "Export Activity GPX",
"parameters": [
{
"type": "string",
"description": "Activity ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Strava Access Token",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/athletes": {
"get": {
"consumes": [
Expand Down Expand Up @@ -960,10 +992,10 @@ const docTemplate = `{
"athlete": {
"$ref": "#/definitions/models.Athlete"
},
"athleteID": {
"athlete_count": {
"type": "integer"
},
"athlete_count": {
"athlete_id": {
"type": "integer"
},
"average_cadence": {
Expand Down Expand Up @@ -1005,8 +1037,10 @@ const docTemplate = `{
"flagged": {
"type": "boolean"
},
"gear": {
"$ref": "#/definitions/models.Gear"
},
"gear_id": {
"description": "bike or pair of shoes",
"type": "string"
},
"has_kudos": {
Expand Down
40 changes: 37 additions & 3 deletions api/docs/swagger.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,38 @@
}
}
},
"/activities/{id}/gpx": {
"get": {
"consumes": [
"application/json"
],
"tags": [
"activity"
],
"summary": "Export Activity GPX",
"parameters": [
{
"type": "string",
"description": "Activity ID",
"name": "id",
"in": "path",
"required": true
},
{
"type": "string",
"description": "Strava Access Token",
"name": "Authorization",
"in": "header",
"required": true
}
],
"responses": {
"200": {
"description": "OK"
}
}
}
},
"/athletes": {
"get": {
"consumes": [
Expand Down Expand Up @@ -949,10 +981,10 @@
"athlete": {
"$ref": "#/definitions/models.Athlete"
},
"athleteID": {
"athlete_count": {
"type": "integer"
},
"athlete_count": {
"athlete_id": {
"type": "integer"
},
"average_cadence": {
Expand Down Expand Up @@ -994,8 +1026,10 @@
"flagged": {
"type": "boolean"
},
"gear": {
"$ref": "#/definitions/models.Gear"
},
"gear_id": {
"description": "bike or pair of shoes",
"type": "string"
},
"has_kudos": {
Expand Down
26 changes: 24 additions & 2 deletions api/docs/swagger.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ definitions:
$ref: '#/definitions/models.Athlete'
athlete_count:
type: integer
athleteID:
athlete_id:
type: integer
average_cadence:
type: number
Expand Down Expand Up @@ -81,8 +81,9 @@ definitions:
type: string
flagged:
type: boolean
gear:
$ref: '#/definitions/models.Gear'
gear_id:
description: bike or pair of shoes
type: string
has_kudos:
type: boolean
Expand Down Expand Up @@ -448,6 +449,27 @@ paths:
summary: Get Activity
tags:
- activity
/activities/{id}/gpx:
get:
consumes:
- application/json
parameters:
- description: Activity ID
in: path
name: id
required: true
type: string
- description: Strava Access Token
in: header
name: Authorization
required: true
type: string
responses:
"200":
description: OK
summary: Export Activity GPX
tags:
- activity
/athletes:
get:
consumes:
Expand Down
1 change: 1 addition & 0 deletions api/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ func (a *API) setupHandlers() {
act := a.ec.Group("/activities")
act.GET("", a.listActivities)
act.GET("/:id", a.getActivity)
act.GET("/:id/gpx", a.exportActivityGPS, requireAuth)
}

{
Expand Down
10 changes: 9 additions & 1 deletion database/activity.go
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ func (d *Database) ListActivities(offset, limit int) (int64, []models.Activity,
Offset(offset).
Order("id desc").
Preload("Athlete").
Preload("Gear").
Find(&activities).
Count(&count).
Error
Expand All @@ -65,7 +66,14 @@ func (d *Database) ListActivities(offset, limit int) (int64, []models.Activity,

func (d *Database) GetActivity(id string) (*models.Activity, error) {
var act models.Activity
if err := d.db.Preload("Athlete").First(&act, id).Error; err != nil {

err := d.db.
Preload("Athlete").
Preload("Gear").
First(&act, id).
Error

if err != nil {
if err == gorm.ErrRecordNotFound {
return nil, nil
}
Expand Down
21 changes: 9 additions & 12 deletions database/athlete.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,18 +8,15 @@ import (
"gorm.io/gorm/clause"
)

func (d *Database) UpsertAthletes(tx *gorm.DB, athletes []models.Athlete) error {
for _, row := range athletes {
currentRow := row
err := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
UpdateAll: true,
}).Create(&currentRow).Error
if err != nil {
d.log.Error("could not upsert athletes",
zap.Any("athlete", currentRow))
return err
}
func (d *Database) UpsertAthlete(tx *gorm.DB, athlete *models.Athlete) error {
err := tx.Clauses(clause.OnConflict{
Columns: []clause.Column{{Name: "id"}},
UpdateAll: true,
}).Create(&athlete).Error
if err != nil {
d.log.Error("could not upsert athletes",
zap.Any("athlete", athlete))
return err
}
return nil
}
Expand Down
Loading

0 comments on commit e775adb

Please sign in to comment.