adds komoot integration

This commit is contained in:
Christian Beutel
2025-02-04 21:23:54 +01:00
parent a0b6480854
commit 4d1d07922a
32 changed files with 1373 additions and 189 deletions

View File

@@ -0,0 +1,371 @@
package komoot
import (
"bytes"
"encoding/base64"
"encoding/json"
"fmt"
"io"
"net/http"
"strconv"
"strings"
"time"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase"
"github.com/pocketbase/pocketbase/forms"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/tools/filesystem"
"github.com/twpayne/go-gpx"
)
func SyncKomoot(app *pocketbase.PocketBase) error {
integrations, err := app.Dao().FindRecordsByExpr("integrations", dbx.NewExp("true"))
if err != nil {
return err
}
for _, i := range integrations {
userId := i.GetString("user")
komootString := i.GetString("komoot")
var komootIntegration KomootIntegration
json.Unmarshal([]byte(komootString), &komootIntegration)
if !komootIntegration.Active || komootIntegration.Email == "" || komootIntegration.Password == "" {
continue
}
k := &KomootApi{}
err = k.login(komootIntegration.Email, komootIntegration.Password)
if err != nil {
warning := fmt.Sprintf("komoot login failed: %v\n", err)
fmt.Print(warning)
app.Logger().Warn(warning)
continue
}
hasNewTours := true
page := 0
for hasNewTours {
tours, err := k.fetchTours(page)
if err != nil {
warning := fmt.Sprintf("error fetching tours from komoot: %v\n", err)
fmt.Print(warning)
app.Logger().Warn(warning)
break
}
hasNewTours, err = syncTrailWithTours(app, k, userId, tours)
if err != nil {
warning := fmt.Sprintf("error syncing komoot tours with trails: %v\n", err)
fmt.Print(warning)
app.Logger().Warn(warning)
break
}
page += 1
}
}
return nil
}
type BasicAuthToken struct {
Key string
Value string
}
func (b BasicAuthToken) Apply(req *http.Request) {
authStr := "Basic " + base64.StdEncoding.EncodeToString([]byte(b.Key+":"+b.Value))
req.Header.Set("Authorization", authStr)
}
type KomootApi struct {
UserID string
Token string
}
func (k *KomootApi) buildHeader() *BasicAuthToken {
if k.UserID != "" && k.Token != "" {
return &BasicAuthToken{k.UserID, k.Token}
}
return nil
}
func sendRequest(url string, auth *BasicAuthToken) ([]byte, error) {
client := &http.Client{}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
if auth != nil {
auth.Apply(req)
}
resp, err := client.Do(req)
if err != nil {
return nil, err
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
body, _ := io.ReadAll(resp.Body)
return nil, fmt.Errorf("error sending request to komoot (%d): %s", resp.StatusCode, string(body))
}
return io.ReadAll(resp.Body)
}
func (k *KomootApi) login(email, password string) error {
url := fmt.Sprintf("https://api.komoot.de/v006/account/email/%s/", email)
body, err := sendRequest(url, &BasicAuthToken{email, password})
if err != nil {
return err
}
var data LoginResponse
json.Unmarshal(body, &data)
k.UserID = data.Username
k.Token = data.Password
return nil
}
func (k *KomootApi) fetchTours(page int) ([]KomootTour, error) {
currentUri := fmt.Sprintf("https://api.komoot.de/v007/users/%s/tours/?page=%d&sort_field=date&sort_direction=desc&limit=30", k.UserID, page)
body, err := sendRequest(currentUri, k.buildHeader())
if err != nil {
return nil, err
}
var data KomootToursResponse
json.Unmarshal(body, &data)
tours := data.Embedded.Tours
return tours, nil
}
func (k *KomootApi) fetchDetailedTour(tour KomootTour) (*DetailedKomootTour, error) {
url := fmt.Sprintf("https://api.komoot.de/v007/tours/%d?_embedded=coordinates,way_types,surfaces,directions,participants,timeline&directions=v2&fields=timeline&format=coordinate_array&timeline_highlights_fields=tips,recommenders", tour.ID)
body, err := sendRequest(url, k.buildHeader())
if err != nil {
return nil, err
}
var data *DetailedKomootTour
json.Unmarshal(body, &data)
return data, nil
}
func syncTrailWithTours(app *pocketbase.PocketBase, k *KomootApi, user string, tours []KomootTour) (bool, error) {
hasNewTours := false
for _, tour := range tours {
trails, err := app.Dao().FindRecordsByFilter("trails", "external_id = {:id}", "", 1, 0, dbx.Params{"id": strconv.Itoa(int(tour.ID))})
if err != nil {
return hasNewTours, err
}
if len(trails) != 0 {
continue
}
hasNewTours = true
detailedTour, err := k.fetchDetailedTour(tour)
if err != nil {
return hasNewTours, err
}
gpx, err := generateTourGPX(detailedTour)
if err != nil {
return hasNewTours, err
}
wpIds, err := createWaypointsFromTour(app, detailedTour, user)
if err != nil {
return hasNewTours, err
}
err = createTrailFromTour(app, detailedTour, gpx, user, wpIds)
if err != nil {
return hasNewTours, err
}
}
return hasNewTours, nil
}
func createTrailFromTour(app *pocketbase.PocketBase, detailedTour *DetailedKomootTour, gpx *filesystem.File, user string, wpIds []string) error {
collection, err := app.Dao().FindCollectionByNameOrId("trails")
if err != nil {
return err
}
record := models.NewRecord(collection)
form := forms.NewRecordUpsert(app, record)
categoryMap := map[string]string{
"hike": "Hiking",
"touringbicycle": "Biking",
"mtb": "Biking",
"racebike": "Biking",
"jogging": "Walking",
"mtb_easy": "Workout",
"mtb_advanced": "Walking",
"mountaineering": "Hiking",
}
category, _ := app.Dao().FindFirstRecordByData("categories", "name", categoryMap[detailedTour.Sport])
categoryId := ""
if category != nil {
categoryId = category.Id
}
photo, err := fetchPhoto(detailedTour.MapImage.Src, "", "")
if err != nil {
return err
}
form.LoadData(map[string]any{
"name": detailedTour.Name,
"public": detailedTour.Status == "public",
"distance": detailedTour.Distance,
"elevation_gain": detailedTour.ElevationUp,
"elevation_loss": detailedTour.ElevationDown,
"duration": detailedTour.Duration / 60,
"date": detailedTour.Date,
"external_provider": "komoot",
"external_id": strconv.Itoa(detailedTour.ID),
"lat": detailedTour.StartPoint.Lat,
"lon": detailedTour.StartPoint.Lng,
"difficulty": detailedTour.Difficulty.Grade,
"category": categoryId,
"waypoints": wpIds,
"author": user,
})
form.AddFiles("photos", photo)
form.AddFiles("gpx", gpx)
if err := form.Submit(); err != nil {
return err
}
return nil
}
func createWaypointsFromTour(app *pocketbase.PocketBase, tour *DetailedKomootTour, user string) ([]string, error) {
collection, err := app.Dao().FindCollectionByNameOrId("waypoints")
if err != nil {
return nil, err
}
wpIds := make([]string, len(tour.Embedded.Timeline.Embedded.Items))
for i, wp := range tour.Embedded.Timeline.Embedded.Items {
photos, err := fetchWaypointPhotos(wp)
if err != nil {
return nil, err
}
record := models.NewRecord(collection)
form := forms.NewRecordUpsert(app, record)
wpDescription := ""
if len(wp.Embedded.Reference.Embedded.Tips.Embedded.Items) > 0 {
wpDescription = wp.Embedded.Reference.Embedded.Tips.Embedded.Items[0].Text
}
wpLat := wp.Embedded.Reference.StartPoint.Lat
if wpLat == 0 {
wpLat = tour.StartPoint.Lat
}
wpLon := wp.Embedded.Reference.StartPoint.Lng
if wpLon == 0 {
wpLon = tour.StartPoint.Lng
}
form.LoadData(map[string]any{
"name": wp.Embedded.Reference.Name,
"description": wpDescription,
"lat": wpLat,
"lon": wpLon,
"icon": "circle",
"author": user,
"distance_from_start": 0,
})
for _, photo := range photos {
form.AddFiles("photos", photo)
}
if err := form.Submit(); err != nil {
return nil, err
}
wpIds[i] = record.Id
}
return wpIds, nil
}
func fetchWaypointPhotos(wp Item) ([]*filesystem.File, error) {
photos := make([]*filesystem.File, len(wp.Embedded.Reference.Embedded.Images.Embedded.Items))
for i, img := range wp.Embedded.Reference.Embedded.Images.Embedded.Items {
photo, err := fetchPhoto(img.Src, "", "")
if err != nil {
return nil, err
}
photos[i] = photo
}
return photos, nil
}
func fetchPhoto(url string, width string, height string) (*filesystem.File, error) {
url = strings.Replace(url, "{crop}", "false", 1)
url = strings.Replace(url, "{width}", width, 1)
url = strings.Replace(url, "{height}", height, 1)
bytes, err := sendRequest(url, nil)
if err != nil {
return nil, err
}
return filesystem.NewFileFromBytes(bytes, "photo")
}
func generateTourGPX(detailedTour *DetailedKomootTour) (*filesystem.File, error) {
var points []*gpx.WptType
for _, item := range detailedTour.Embedded.Coordinates.Items {
t := detailedTour.Date.Unix() + int64(item.T)
points = append(points, &gpx.WptType{Lat: item.Lat, Lon: item.Lng, Ele: item.Alt, Time: time.Unix(t, 0)})
}
gpx := &gpx.GPX{
Version: "1.1",
Creator: "Strava GPX Exporter",
Trk: []*gpx.TrkType{
{
Name: detailedTour.Name,
TrkSeg: []*gpx.TrkSegType{
{
TrkPt: points,
},
},
},
},
}
var buf bytes.Buffer
err := gpx.Write(&buf)
if err != nil {
return nil, err
}
gpxFile, err := filesystem.NewFileFromBytes(buf.Bytes(), detailedTour.Name+".gpx")
if err != nil {
return nil, err
}
return gpxFile, nil
}

View File

@@ -0,0 +1,379 @@
package komoot
import "time"
type KomootIntegration struct {
Active bool `json:"active"`
Email string `json:"email"`
Password string `json:"password"`
}
type LoginResponse struct {
Email string `json:"email"`
Password string `json:"password"`
User User `json:"user"`
Username string `json:"username"`
}
type Content struct {
HasImage bool `json:"hasImage"`
}
type Fitness struct {
Personalised bool `json:"personalised"`
}
type User struct {
Content Content `json:"content"`
CreatedAt string `json:"createdAt"`
Displayname string `json:"displayname"`
Fitness Fitness `json:"fitness"`
ImageURL string `json:"imageUrl"`
Locale string `json:"locale"`
Metric bool `json:"metric"`
Newsletter bool `json:"newsletter"`
State string `json:"state"`
Username string `json:"username"`
WelcomeMails bool `json:"welcomeMails"`
}
type KomootToursResponse struct {
Embedded Embedded `json:"_embedded"`
Links ResponseLinks `json:"_links"`
Page Page `json:"page"`
}
type StartPoint struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
Alt float64 `json:"alt"`
}
type Surfaces struct {
Type string `json:"type"`
Amount float64 `json:"amount"`
}
type WayTypes struct {
Type string `json:"type"`
Amount float64 `json:"amount"`
}
type Summary struct {
Surfaces []Surfaces `json:"surfaces"`
WayTypes []WayTypes `json:"way_types"`
}
type Difficulty struct {
Grade string `json:"grade"`
ExplanationTechnical string `json:"explanation_technical"`
ExplanationFitness string `json:"explanation_fitness"`
}
type Location struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
}
type Path struct {
Location Location `json:"location"`
Index int `json:"index"`
Reference string `json:"reference,omitempty"`
EndIndex int `json:"end_index,omitempty"`
SegmentType string `json:"segment_type,omitempty"`
}
type Segments struct {
Type string `json:"type"`
From int `json:"from"`
To int `json:"to"`
}
type MapImage struct {
Src string `json:"src"`
Templated bool `json:"templated"`
Type string `json:"type"`
Attribution string `json:"attribution"`
}
type MapImagePreview struct {
Src string `json:"src"`
Templated bool `json:"templated"`
Type string `json:"type"`
Attribution string `json:"attribution"`
}
type VectorMapImage struct {
Src string `json:"src"`
Templated bool `json:"templated"`
Type string `json:"type"`
Attribution string `json:"attribution"`
}
type VectorMapImagePreview struct {
Src string `json:"src"`
Templated bool `json:"templated"`
Type string `json:"type"`
Attribution string `json:"attribution"`
}
type Relation struct {
Href string `json:"href"`
Templated bool `json:"templated"`
}
type CreatorLinks struct {
Relation Relation `json:"relation"`
}
type LinksEmbedded struct {
Creator Creator `json:"creator"`
}
type LinksCreator struct {
Href string `json:"href"`
}
type LinksCoordinates struct {
Href string `json:"href"`
}
type LinksTourLine struct {
Href string `json:"href"`
}
type LinksParticipants struct {
Href string `json:"href"`
}
type LinksWayTypes struct {
Href string `json:"href"`
}
type LinksSurfaces struct {
Href string `json:"href"`
}
type LinksDirections struct {
Href string `json:"href"`
}
type LinksTimeline struct {
Href string `json:"href"`
}
type LinksTranslations struct {
Href string `json:"href"`
}
type LinksCoverImages struct {
Href string `json:"href"`
}
type LinksTourRating struct {
Href string `json:"href"`
}
type TourLinks struct {
Creator LinksCreator `json:"creator"`
Coordinates LinksCoordinates `json:"coordinates"`
TourLine LinksTourLine `json:"tour_line"`
Participants LinksParticipants `json:"participants"`
WayTypes LinksWayTypes `json:"way_types"`
Surfaces LinksSurfaces `json:"surfaces"`
Directions LinksDirections `json:"directions"`
Timeline LinksTimeline `json:"timeline"`
Translations LinksTranslations `json:"translations"`
CoverImages LinksCoverImages `json:"cover_images"`
TourRating LinksTourRating `json:"tour_rating"`
}
type KomootTour struct {
ID int `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Source string `json:"source"`
RoutingVersion string `json:"routing_version"`
Status string `json:"status"`
Date time.Time `json:"date"`
KcalActive int `json:"kcal_active"`
KcalResting int `json:"kcal_resting"`
StartPoint StartPoint `json:"start_point"`
Distance float64 `json:"distance"`
Duration int `json:"duration"`
ElevationUp float64 `json:"elevation_up"`
ElevationDown float64 `json:"elevation_down"`
Sport string `json:"sport"`
Query string `json:"query"`
Constitution int `json:"constitution"`
Summary Summary `json:"summary"`
Difficulty Difficulty `json:"difficulty"`
TourInformation []any `json:"tour_information"`
Path []Path `json:"path"`
Segments []Segments `json:"segments"`
ChangedAt time.Time `json:"changed_at"`
MapImage MapImage `json:"map_image"`
MapImagePreview MapImagePreview `json:"map_image_preview"`
VectorMapImage VectorMapImage `json:"vector_map_image"`
VectorMapImagePreview VectorMapImagePreview `json:"vector_map_image_preview"`
PotentialRouteUpdate bool `json:"potential_route_update"`
Embedded Embedded `json:"_embedded"`
Links TourLinks `json:"_links"`
}
type Embedded struct {
Tours []KomootTour `json:"tours"`
}
type Next struct {
Href string `json:"href"`
}
type ResponseLinks struct {
Next Next `json:"next"`
}
type Page struct {
Size int `json:"size"`
TotalElements int `json:"totalElements"`
TotalPages int `json:"totalPages"`
Number int `json:"number"`
}
type DetailedKomootTour struct {
ID int `json:"id"`
Type string `json:"type"`
Name string `json:"name"`
Status string `json:"status"`
Date time.Time `json:"date"`
KcalActive float64 `json:"kcal_active"`
KcalResting float64 `json:"kcal_resting"`
StartPoint StartPoint `json:"start_point"`
Distance float64 `json:"distance"`
Duration int `json:"duration"`
ElevationUp float64 `json:"elevation_up"`
ElevationDown float64 `json:"elevation_down"`
Sport string `json:"sport"`
MapImage MapImage `json:"map_image"`
Difficulty Difficulty `json:"difficulty"`
ChangedAt time.Time `json:"changed_at"`
Embedded DetailedTourEmbedded `json:"_embedded"`
}
type Items struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
Alt float64 `json:"alt"`
T int `json:"t"`
}
type Coordinates struct {
Items []Items `json:"items"`
}
type DetailedTourEmbedded struct {
Coordinates Coordinates `json:"coordinates"`
Timeline Timeline `json:"timeline"`
}
type Timeline struct {
Embedded TimelineEmbedded `json:"_embedded"`
Links Links `json:"_links"`
Page Page `json:"page"`
}
type TimelineEmbedded struct {
Items []Item `json:"items"`
}
type Item struct {
Index int `json:"index"`
Cover int `json:"cover"`
Type string `json:"type"`
Embedded TimelineItemEmbedded `json:"_embedded"`
}
type TimelineItemEmbedded struct {
Reference Reference `json:"reference"`
}
type Reference struct {
ID int `json:"id"`
Type string `json:"type"`
BaseName string `json:"base_name"`
Name string `json:"name"`
CreatedAt time.Time `json:"created_at"`
ChangedAt time.Time `json:"changed_at"`
Sport string `json:"sport"`
Routable bool `json:"routable"`
StartPoint Point `json:"start_point"`
MidPoint Point `json:"mid_point"`
EndPoint Point `json:"end_point"`
Distance float64 `json:"distance"`
ElevationUp float64 `json:"elevation_up"`
ElevationDown float64 `json:"elevation_down"`
Score float64 `json:"score"`
WikiPOIID string `json:"wiki_poi_id"`
PoorQuality bool `json:"poor_quality"`
Categories []string `json:"categories"`
Flagged bool `json:"flagged"`
Links Links `json:"_links"`
Embedded SubEmbedded `json:"_embedded"`
}
type Point struct {
Lat float64 `json:"lat"`
Lng float64 `json:"lng"`
Alt float64 `json:"alt"`
}
type Links struct {
Self Link `json:"self"`
}
type Link struct {
Href string `json:"href"`
Templated bool `json:"templated,omitempty"`
}
type SubEmbedded struct {
Creator Creator `json:"creator"`
Images Images `json:"images"`
Tips Tips `json:"tips"`
}
type Creator struct {
Username string `json:"username"`
Avatar Avatar `json:"avatar"`
Status string `json:"status"`
Links Links `json:"_links"`
DisplayName string `json:"display_name"`
IsPremium bool `json:"is_premium"`
}
type Avatar struct {
Src string `json:"src"`
Templated bool `json:"templated"`
Type string `json:"type"`
}
type Images struct {
Embedded ImagesEmbedded `json:"_embedded"`
Links Links `json:"_links"`
Page Page `json:"page"`
}
type ImagesEmbedded struct {
Items []ImageItem `json:"items"`
}
type ImageItem struct {
ID int `json:"id"`
Src string `json:"src"`
Rating Rating `json:"rating"`
Templated bool `json:"templated"`
HighlightID int `json:"highlight_id"`
ClientHash string `json:"client_hash,omitempty"`
Type string `json:"type"`
Links Links `json:"_links"`
Embedded SubEmbedded `json:"_embedded"`
}
type Rating struct {
Up int `json:"up"`
Down int `json:"down"`
}
type Tips struct {
Embedded TipsEmbedded `json:"_embedded"`
Links Links `json:"_links"`
Page Page `json:"page"`
}
type TipsEmbedded struct {
Items []TipItem `json:"items"`
}
type TipItem struct {
ID int `json:"id"`
Text string `json:"text"`
Rating Rating `json:"rating"`
CreatedAt time.Time `json:"created_at"`
TextLanguage string `json:"text_language"`
TranslatedText string `json:"translated_text"`
TranslatedTextLanguage string `json:"translated_text_language"`
Attribution string `json:"attribution"`
HighlightID int `json:"highlight_id"`
Links Links `json:"_links"`
Embedded SubEmbedded `json:"_embedded"`
}

View File

@@ -1,4 +1,4 @@
package cron
package strava
import "time"
@@ -11,17 +11,17 @@ type RefreshTokenRequest struct {
type RefreshTokenResponse struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresAt *int64 `json:"expires_at"`
ExpiresAt int64 `json:"expires_at"`
}
type StravaIntegration struct {
Active bool `json:"active"`
Routes bool `json:"routes"`
Activities bool `json:"activities"`
ClientID int32 `json:"clientId"`
ClientSecret string `json:"clientSecret"`
AccessToken *string `json:"accessToken,omitempty"`
RefreshToken *string `json:"refreshToken,omitempty"`
ExpiresAt *int64 `json:"expiresAt,omitempty"`
Active bool `json:"active"`
Routes bool `json:"routes"`
Activities bool `json:"activities"`
ClientID int32 `json:"clientId"`
ClientSecret string `json:"clientSecret"`
AccessToken string `json:"accessToken,omitempty"`
RefreshToken string `json:"refreshToken,omitempty"`
ExpiresAt int64 `json:"expiresAt,omitempty"`
}
type StravaRoute struct {
Athlete Athlete `json:"athlete"`

View File

@@ -1,4 +1,4 @@
package cron
package strava
import (
"bytes"
@@ -18,6 +18,10 @@ import (
"github.com/twpayne/go-polyline"
)
type StravaApi struct {
AceessToken string
}
func SyncStrava(app *pocketbase.PocketBase) error {
integrations, err := app.Dao().FindRecordsByExpr("integrations", dbx.NewExp("true"))
if err != nil {
@@ -30,17 +34,26 @@ func SyncStrava(app *pocketbase.PocketBase) error {
var stravaIntegration StravaIntegration
json.Unmarshal([]byte(stravaString), &stravaIntegration)
if !stravaIntegration.Active || stravaIntegration.RefreshToken == nil {
if !stravaIntegration.Active || stravaIntegration.RefreshToken == "" {
continue
}
r, err := refreshStravaToken(stravaIntegration.ClientID, stravaIntegration.ClientSecret, *stravaIntegration.RefreshToken)
r, err := refreshStravaToken(stravaIntegration.ClientID, stravaIntegration.ClientSecret, stravaIntegration.RefreshToken)
if err != nil {
return err
warning := fmt.Sprintf("error refreshing strava access token: %v\n", err)
fmt.Print(warning)
app.Logger().Warn(warning)
continue
}
if r.AccessToken != "" {
stravaIntegration.AccessToken = r.AccessToken
}
if r.RefreshToken != "" {
stravaIntegration.RefreshToken = r.RefreshToken
}
if r.AccessToken != "" {
stravaIntegration.ExpiresAt = r.ExpiresAt
}
stravaIntegration.AccessToken = &r.AccessToken
stravaIntegration.RefreshToken = &r.RefreshToken
stravaIntegration.ExpiresAt = r.ExpiresAt
b, err := json.Marshal(stravaIntegration)
if err != nil {
@@ -56,11 +69,17 @@ func SyncStrava(app *pocketbase.PocketBase) error {
routes, err := fetchStravaRoutes(r.AccessToken, page)
if err != nil {
return err
warning := fmt.Sprintf("error fetching routes from strava: %v\n", err)
fmt.Print(warning)
app.Logger().Warn(warning)
break
}
hasNewRoutes, err = syncTrailsWithRoutes(app, r.AccessToken, userId, routes)
if err != nil {
return err
warning := fmt.Sprintf("error syncing strava routes with trails: %v\n", err)
fmt.Print(warning)
app.Logger().Warn(warning)
break
}
page += 1
}
@@ -71,11 +90,17 @@ func SyncStrava(app *pocketbase.PocketBase) error {
for hasNewActivities {
activities, err := fetchStravaActivities(r.AccessToken, page)
if err != nil {
return err
warning := fmt.Sprintf("error fetching activities from strava: %v", err)
fmt.Print(warning)
app.Logger().Warn(warning)
break
}
hasNewActivities, err = syncTrailsWithActivities(app, r.AccessToken, userId, activities)
if err != nil {
return err
warning := fmt.Sprintf("error syncing strava activities with trails: %v", err)
fmt.Print(warning)
app.Logger().Warn(warning)
break
}
page += 1
}
@@ -263,7 +288,7 @@ func createTrailFromRoute(app *pocketbase.PocketBase, route StravaRoute, gpx *fi
lat = coords[0][0]
lon = coords[0][1]
} else {
fmt.Println("Warning: No coordinates available, setting lat/lon to 0")
app.Logger().Warn("Warning: No coordinates available, setting lat/lon to 0")
lat, lon = 0, 0
}
@@ -316,7 +341,7 @@ func createWaypointsFromRoute(app *pocketbase.PocketBase, route StravaRoute, use
for i, wp := range route.Waypoints {
record := models.NewRecord(collection)
record.Set("name", string(i))
record.Set("name", strconv.Itoa(i))
record.Set("description", wp.Description)
record.Set("lat", wp.Latlng[0])
record.Set("lon", wp.Latlng[1])

View File

@@ -18,11 +18,12 @@ import (
"github.com/pocketbase/pocketbase/forms"
"github.com/pocketbase/pocketbase/models"
"github.com/pocketbase/pocketbase/plugins/migratecmd"
pbCron "github.com/pocketbase/pocketbase/tools/cron"
"github.com/pocketbase/pocketbase/tools/cron"
"github.com/pocketbase/pocketbase/tools/filesystem"
"github.com/pocketbase/pocketbase/tools/hook"
"pocketbase/cron"
"pocketbase/integrations/komoot"
"pocketbase/integrations/strava"
_ "pocketbase/migrations"
"pocketbase/util"
)
@@ -404,12 +405,20 @@ func registerRoutes(e *core.ServeEvent, app *pocketbase.PocketBase, client meili
}
func registerCronJobs(app *pocketbase.PocketBase) {
scheduler := pbCron.New()
scheduler := cron.New()
scheduler.MustAdd("strava", "*/15 * * * *", func() {
err := cron.SyncStrava(app)
scheduler.MustAdd("integrations", "0 * * * *", func() {
err := strava.SyncStrava(app)
if err != nil {
app.Logger().Warn(fmt.Sprintf("Error syncing with strava: %v", err))
warning := fmt.Sprintf("Error syncing with strava: %v", err)
fmt.Print(warning)
app.Logger().Error(warning)
}
err = komoot.SyncKomoot(app)
if err != nil {
warning := fmt.Sprintf("Error syncing with komoot: %v", err)
fmt.Print(warning)
app.Logger().Error(warning)
}
})

View File

@@ -0,0 +1,75 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos"
m "github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/models/schema"
)
func init() {
m.Register(func(db dbx.Builder) error {
dao := daos.New(db);
collection, err := dao.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// update
edit_external_provider := &schema.SchemaField{}
if err := json.Unmarshal([]byte(`{
"system": false,
"id": "htr35nha",
"name": "external_provider",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"strava",
"komoot"
]
}
}`), edit_external_provider); err != nil {
return err
}
collection.Schema.AddField(edit_external_provider)
return dao.SaveCollection(collection)
}, func(db dbx.Builder) error {
dao := daos.New(db);
collection, err := dao.FindCollectionByNameOrId("e864strfxo14pm4")
if err != nil {
return err
}
// update
edit_external_provider := &schema.SchemaField{}
if err := json.Unmarshal([]byte(`{
"system": false,
"id": "htr35nha",
"name": "external_provider",
"type": "select",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSelect": 1,
"values": [
"strava"
]
}
}`), edit_external_provider); err != nil {
return err
}
collection.Schema.AddField(edit_external_provider)
return dao.SaveCollection(collection)
})
}

View File

@@ -0,0 +1,53 @@
package migrations
import (
"encoding/json"
"github.com/pocketbase/dbx"
"github.com/pocketbase/pocketbase/daos"
m "github.com/pocketbase/pocketbase/migrations"
"github.com/pocketbase/pocketbase/models/schema"
)
func init() {
m.Register(func(db dbx.Builder) error {
dao := daos.New(db);
collection, err := dao.FindCollectionByNameOrId("iz4sezoehde64wp")
if err != nil {
return err
}
// add
new_komoot := &schema.SchemaField{}
if err := json.Unmarshal([]byte(`{
"system": false,
"id": "6s0oxqgp",
"name": "komoot",
"type": "json",
"required": false,
"presentable": false,
"unique": false,
"options": {
"maxSize": 2000000
}
}`), new_komoot); err != nil {
return err
}
collection.Schema.AddField(new_komoot)
return dao.SaveCollection(collection)
}, func(db dbx.Builder) error {
dao := daos.New(db);
collection, err := dao.FindCollectionByNameOrId("iz4sezoehde64wp")
if err != nil {
return err
}
// remove
collection.Schema.RemoveField("6s0oxqgp")
return dao.SaveCollection(collection)
})
}