fixes strava integration

This commit is contained in:
Christian Beutel
2025-09-13 16:04:48 +02:00
parent 962fddc1e2
commit 72acac072c
17 changed files with 102 additions and 50 deletions

View File

@@ -29,6 +29,7 @@ type StravaIntegration struct {
AccessToken string `json:"accessToken,omitempty"` AccessToken string `json:"accessToken,omitempty"`
RefreshToken string `json:"refreshToken,omitempty"` RefreshToken string `json:"refreshToken,omitempty"`
ExpiresAt int64 `json:"expiresAt,omitempty"` ExpiresAt int64 `json:"expiresAt,omitempty"`
After string `json:"after,omitempty"`
} }
type StravaRoute struct { type StravaRoute struct {
Athlete Athlete `json:"athlete"` Athlete Athlete `json:"athlete"`

View File

@@ -89,22 +89,12 @@ func SyncStrava(app core.App) error {
stravaIntegration.ExpiresAt = r.ExpiresAt stravaIntegration.ExpiresAt = r.ExpiresAt
} }
b, err := json.Marshal(stravaIntegration)
if err != nil {
return err
}
i.Set("strava", string(b))
err = app.Save(i)
if err != nil {
return err
}
if stravaIntegration.Routes { if stravaIntegration.Routes {
page := 1 page := 1
hasNewRoutes := true hasMore := true
for hasNewRoutes { for hasMore {
routes, err := fetchStravaRoutes(r.AccessToken, page) routes, err := fetchStravaRoutes(r.AccessToken, page)
hasMore = len(routes) > 0
page += 1 page += 1
if err != nil { if err != nil {
warning := fmt.Sprintf("error fetching routes from strava: %v\n", err) warning := fmt.Sprintf("error fetching routes from strava: %v\n", err)
@@ -112,7 +102,7 @@ func SyncStrava(app core.App) error {
app.Logger().Warn(warning) app.Logger().Warn(warning)
break break
} }
hasNewRoutes, err = syncTrailsWithRoutes(app, r.AccessToken, userId, actorId, routes) err = syncTrailsWithRoutes(app, r.AccessToken, userId, actorId, routes)
if err != nil { if err != nil {
warning := fmt.Sprintf("error syncing strava routes with trails: %v\n", err) warning := fmt.Sprintf("error syncing strava routes with trails: %v\n", err)
fmt.Print(warning) fmt.Print(warning)
@@ -123,9 +113,20 @@ func SyncStrava(app core.App) error {
} }
if stravaIntegration.Activities { if stravaIntegration.Activities {
page := 1 page := 1
hasNewActivities := true hasMore := true
for hasNewActivities { for hasMore {
activities, err := fetchStravaActivities(r.AccessToken, page) var after int64 = 0
if stravaIntegration.After != "" {
t, err := time.Parse("2006-01-02", stravaIntegration.After)
if err != nil {
return err
}
t = t.UTC()
after = t.Unix()
}
activities, err := fetchStravaActivities(r.AccessToken, page, after)
hasMore = len(activities) > 0
page += 1 page += 1
if err != nil { if err != nil {
warning := fmt.Sprintf("error fetching activities from strava: %v", err) warning := fmt.Sprintf("error fetching activities from strava: %v", err)
@@ -133,7 +134,8 @@ func SyncStrava(app core.App) error {
app.Logger().Warn(warning) app.Logger().Warn(warning)
break break
} }
hasNewActivities, err = syncTrailsWithActivities(app, r.AccessToken, userId, actorId, activities) err = syncTrailsWithActivities(app, r.AccessToken, actorId, activities)
if err != nil { if err != nil {
warning := fmt.Sprintf("error syncing strava activities with trails: %v", err) warning := fmt.Sprintf("error syncing strava activities with trails: %v", err)
fmt.Print(warning) fmt.Print(warning)
@@ -141,6 +143,17 @@ func SyncStrava(app core.App) error {
continue continue
} }
} }
}
b, err := json.Marshal(stravaIntegration)
if err != nil {
return err
}
i.Set("strava", string(b))
err = app.Save(i)
if err != nil {
return err
} }
} }
@@ -208,8 +221,8 @@ func fetchStravaRoutes(accessToken string, page int) ([]StravaRoute, error) {
return routes, nil return routes, nil
} }
func fetchStravaActivities(accessToken string, page int) ([]StravaActivity, error) { func fetchStravaActivities(accessToken string, page int, after int64) ([]StravaActivity, error) {
stravaRoutesURL := fmt.Sprintf("https://www.strava.com/api/v3/athlete/activities?page=%d", page) stravaRoutesURL := fmt.Sprintf("https://www.strava.com/api/v3/athlete/activities?page=%d&after=%d", page, after)
req, err := http.NewRequest("GET", stravaRoutesURL, nil) req, err := http.NewRequest("GET", stravaRoutesURL, nil)
if err != nil { if err != nil {
return nil, err return nil, err
@@ -235,17 +248,15 @@ func fetchStravaActivities(accessToken string, page int) ([]StravaActivity, erro
return activities, nil return activities, nil
} }
func syncTrailsWithRoutes(app core.App, accessToken string, user string, actor string, routes []StravaRoute) (bool, error) { func syncTrailsWithRoutes(app core.App, accessToken string, user string, actor string, routes []StravaRoute) error {
hasNewRoutes := false
for _, route := range routes { for _, route := range routes {
trails, err := app.FindRecordsByFilter("trails", "external_id = {:id}", "", 1, 0, dbx.Params{"id": route.IDStr}) trails, err := app.FindRecordsByFilter("trails", "external_id = {:id}", "", 1, 0, dbx.Params{"id": route.IDStr})
if err != nil { if err != nil {
return hasNewRoutes, err return err
} }
if len(trails) != 0 { if len(trails) != 0 {
continue continue
} }
hasNewRoutes = true
gpx, err := fetchRouteGPX(route, accessToken) gpx, err := fetchRouteGPX(route, accessToken)
if err != nil { if err != nil {
app.Logger().Warn(fmt.Sprintf("Unable to fetch GPX for route '%s': %v", route.Name, err)) app.Logger().Warn(fmt.Sprintf("Unable to fetch GPX for route '%s': %v", route.Name, err))
@@ -263,7 +274,7 @@ func syncTrailsWithRoutes(app core.App, accessToken string, user string, actor s
} }
} }
return hasNewRoutes, nil return nil
} }
func fetchRouteGPX(route StravaRoute, accessToken string) (*filesystem.File, error) { func fetchRouteGPX(route StravaRoute, accessToken string) (*filesystem.File, error) {
@@ -393,17 +404,15 @@ func createWaypointsFromRoute(app core.App, route StravaRoute, user string, trai
return nil return nil
} }
func syncTrailsWithActivities(app core.App, accessToken string, user string, actor string, activities []StravaActivity) (bool, error) { func syncTrailsWithActivities(app core.App, accessToken string, actor string, activities []StravaActivity) error {
hasNewActivites := false
for _, activity := range activities { for _, activity := range activities {
trails, err := app.FindRecordsByFilter("trails", "external_id = {:id}", "", 1, 0, dbx.Params{"id": strconv.Itoa(int(activity.ID))}) trails, err := app.FindRecordsByFilter("trails", "external_id = {:id}", "", 1, 0, dbx.Params{"id": strconv.Itoa(int(activity.ID))})
if err != nil { if err != nil {
return hasNewActivites, err return err
} }
if len(trails) != 0 { if len(trails) != 0 {
continue continue
} }
hasNewActivites = true
detailedActivity, err := fetchDetailedActivity(activity, accessToken) detailedActivity, err := fetchDetailedActivity(activity, accessToken)
if err != nil { if err != nil {
app.Logger().Warn(fmt.Sprintf("Unable to fetch detailed activity '%s': %v", activity.Name, err)) app.Logger().Warn(fmt.Sprintf("Unable to fetch detailed activity '%s': %v", activity.Name, err))
@@ -419,10 +428,9 @@ func syncTrailsWithActivities(app core.App, accessToken string, user string, act
app.Logger().Warn(fmt.Sprintf("Unable to create trail from activity '%s': %v", activity.Name, err)) app.Logger().Warn(fmt.Sprintf("Unable to create trail from activity '%s': %v", activity.Name, err))
continue continue
} }
} }
return hasNewActivites, nil return nil
} }
func fetchDetailedActivity(activity StravaActivity, accessToken string) (*DetailedStravaActivity, error) { func fetchDetailedActivity(activity StravaActivity, accessToken string) (*DetailedStravaActivity, error) {

View File

@@ -1,4 +1,5 @@
<script lang="ts"> <script lang="ts">
import Datepicker from "$lib/components/base/datepicker.svelte";
import Modal from "$lib/components/base/modal.svelte"; import Modal from "$lib/components/base/modal.svelte";
import TextField from "$lib/components/base/text_field.svelte"; import TextField from "$lib/components/base/text_field.svelte";
import Toggle from "$lib/components/base/toggle.svelte"; import Toggle from "$lib/components/base/toggle.svelte";
@@ -25,23 +26,32 @@
modal.openModal(); modal.openModal();
} }
const { form, errors } = createForm({ const {
form,
errors,
data: formData,
} = createForm({
initialValues: { initialValues: {
clientId: integration?.strava?.clientId ?? "", clientId: integration?.strava?.clientId ?? "",
clientSecret: integration?.strava?.clientSecret ?? "", clientSecret: integration?.strava?.clientSecret ?? "",
routes: integration?.strava?.routes ?? true, routes: integration?.strava?.routes ?? true,
activities: integration?.strava?.activities ?? true, activities: integration?.strava?.activities ?? true,
active: integration?.strava?.active ?? false, active: integration?.strava?.active ?? false,
after: integration?.strava?.after,
}, },
extend: validator({ extend: validator({
schema: StravaSchema, schema: StravaSchema,
}), }),
onSubmit: async (form) => { onSubmit: async (form) => {
form.active = integration?.strava?.active ?? form.active form.active = integration?.strava?.active ?? form.active;
onsave?.(form); onsave?.(form);
modal.closeModal(); modal.closeModal();
}, },
}); });
function clearAfterDate() {
($formData as any).after = undefined;
}
</script> </script>
<Modal <Modal
@@ -60,7 +70,9 @@
></TextField> ></TextField>
<TextField <TextField
label="Client Secret" label="Client Secret"
placeholder={integration?.strava ? `(${$_("unchanged")})` : "de8b3789bd7116d..."} placeholder={integration?.strava
? `(${$_("unchanged")})`
: "de8b3789bd7116d..."}
name="clientSecret" name="clientSecret"
type="password" type="password"
error={$errors.clientSecret} error={$errors.clientSecret}
@@ -73,6 +85,25 @@
label={$_("activity", { values: { n: 2 } })} label={$_("activity", { values: { n: 2 } })}
></Toggle> ></Toggle>
</div> </div>
<p
class="text-xs text-gray-500 max-w-lg pt-4 pb-1 border-t border-input-border"
>
{$_("strava-integration-after-date-hint")}
</p>
<div class="flex items-end relative gap-x-2">
<Datepicker
error={$errors.after}
label={$_("after")}
bind:value={$formData.after}
></Datepicker>
<button
class="btn-icon mb-[10px]"
type="button"
onclick={clearAfterDate}
aria-label="Clear 'after' date"
><i class="fa fa-close"></i></button
>
</div>
</form> </form>
{/snippet} {/snippet}
{#snippet footer()} {#snippet footer()}

View File

@@ -32,6 +32,7 @@
"bakery": "Bäckerei", "bakery": "Bäckerei",
"barrier": "Barriere", "barrier": "Barriere",
"basic-info": "Basisinformation", "basic-info": "Basisinformation",
"basque": "Baskisch",
"before": "Vor", "before": "Vor",
"bicycle-parking": "Fahrrad Parkplatz", "bicycle-parking": "Fahrrad Parkplatz",
"bicycle-rental": "Fahrradverleih", "bicycle-rental": "Fahrradverleih",
@@ -56,7 +57,6 @@
"car": "Auto", "car": "Auto",
"car-motorcycle": "Auto/Motorrad", "car-motorcycle": "Auto/Motorrad",
"card": "{n, plural, =1 {Karte} other {Karten}}", "card": "{n, plural, =1 {Karte} other {Karten}}",
"basque": "Baskisch",
"categories": "Kategorien", "categories": "Kategorien",
"category": "Kategorie", "category": "Kategorie",
"change": "Ändern", "change": "Ändern",
@@ -390,6 +390,7 @@
"statistics": "Statistiken", "statistics": "Statistiken",
"stop-drawing": "Zeichnen beenden", "stop-drawing": "Zeichnen beenden",
"stop-editing": "Bearbeiten beenden", "stop-editing": "Bearbeiten beenden",
"strava-integration-after-date-hint": "",
"subway-stop": "U-Bahn Eingang", "subway-stop": "U-Bahn Eingang",
"summit": "Gipfel", "summit": "Gipfel",
"summit-book": "Gipfelbuch", "summit-book": "Gipfelbuch",

View File

@@ -32,6 +32,7 @@
"bakery": "Bakery", "bakery": "Bakery",
"barrier": "Barrier", "barrier": "Barrier",
"basic-info": "Basic Info", "basic-info": "Basic Info",
"basque": "Basque",
"before": "Before", "before": "Before",
"bicycle-parking": "Bicycle Parking", "bicycle-parking": "Bicycle Parking",
"bicycle-rental": "Bicycle Rental", "bicycle-rental": "Bicycle Rental",
@@ -56,7 +57,6 @@
"car": "Car", "car": "Car",
"car-motorcycle": "Car/Motorcycle", "car-motorcycle": "Car/Motorcycle",
"card": "{n, plural, =1 {Card} other {Cards}}", "card": "{n, plural, =1 {Card} other {Cards}}",
"basque": "Basque",
"categories": "Categories", "categories": "Categories",
"category": "Category", "category": "Category",
"change": "Change", "change": "Change",
@@ -390,6 +390,7 @@
"statistics": "Statistics", "statistics": "Statistics",
"stop-drawing": "Stop drawing", "stop-drawing": "Stop drawing",
"stop-editing": "Stop editing", "stop-editing": "Stop editing",
"strava-integration-after-date-hint": "If your account has a large amount of acitivities you may run into Strava's API rate limit preventing you from syncing all activities at once. To mitigate this issue you can set an \"After\" date below so that only activities that were recorded after this date are synced.",
"subway-stop": "Subway entrance", "subway-stop": "Subway entrance",
"summit": "Summit", "summit": "Summit",
"summit-book": "Summit Book", "summit-book": "Summit Book",

View File

@@ -32,6 +32,7 @@
"bakery": "Bakery", "bakery": "Bakery",
"barrier": "Barrier", "barrier": "Barrier",
"basic-info": "Información básica", "basic-info": "Información básica",
"basque": "Basque",
"before": "Antes", "before": "Antes",
"bicycle-parking": "Bicycle Parking", "bicycle-parking": "Bicycle Parking",
"bicycle-rental": "Bicycle Rental", "bicycle-rental": "Bicycle Rental",
@@ -56,7 +57,6 @@
"car": "Coche", "car": "Coche",
"car-motorcycle": "Car/Motorcycle", "car-motorcycle": "Car/Motorcycle",
"card": "{n, plural, one {}=1 {Ficha} other {Fichas}}", "card": "{n, plural, one {}=1 {Ficha} other {Fichas}}",
"basque": "Basque",
"categories": "Categorías", "categories": "Categorías",
"category": "Categoría", "category": "Categoría",
"change": "Modificar", "change": "Modificar",
@@ -390,6 +390,7 @@
"statistics": "Estadísticas", "statistics": "Estadísticas",
"stop-drawing": "Parar de diseñar", "stop-drawing": "Parar de diseñar",
"stop-editing": "Parar de editar", "stop-editing": "Parar de editar",
"strava-integration-after-date-hint": "",
"subway-stop": "Subway entrance", "subway-stop": "Subway entrance",
"summit": "Summit", "summit": "Summit",
"summit-book": "Libro de ascensos", "summit-book": "Libro de ascensos",

View File

@@ -32,6 +32,7 @@
"bakery": "Okindegia", "bakery": "Okindegia",
"barrier": "Oztopoa", "barrier": "Oztopoa",
"basic-info": "Oinarrizko informazioa", "basic-info": "Oinarrizko informazioa",
"basque": "Basque",
"before": "Aurretik", "before": "Aurretik",
"bicycle-parking": "Bizikleta-parkina", "bicycle-parking": "Bizikleta-parkina",
"bicycle-rental": "Bizikleta-alokairua", "bicycle-rental": "Bizikleta-alokairua",
@@ -56,7 +57,6 @@
"car": "Kotxea", "car": "Kotxea",
"car-motorcycle": "Kotxea/Motozikleta", "car-motorcycle": "Kotxea/Motozikleta",
"card": "{n, plural, one {}=1 {Txartea} other {Txartelak}}", "card": "{n, plural, one {}=1 {Txartea} other {Txartelak}}",
"basque": "Basque",
"categories": "Kategoriak", "categories": "Kategoriak",
"category": "Kategoria", "category": "Kategoria",
"change": "Aldatu", "change": "Aldatu",
@@ -338,7 +338,6 @@
"route-point": "Ibilbideko puntua", "route-point": "Ibilbideko puntua",
"russian": "Russian", "russian": "Russian",
"save": "Gorde", "save": "Gorde",
"russian": "Russian",
"save-list": "Gorde zerrenda", "save-list": "Gorde zerrenda",
"save-trail": "Gorde ibilaldia", "save-trail": "Gorde ibilaldia",
"save-your-trail-first": "Gorde zure ibilaldia lehenengo", "save-your-trail-first": "Gorde zure ibilaldia lehenengo",
@@ -391,6 +390,7 @@
"statistics": "Estatistikak", "statistics": "Estatistikak",
"stop-drawing": "Utzi marrazteari", "stop-drawing": "Utzi marrazteari",
"stop-editing": "Utzi editatzeari", "stop-editing": "Utzi editatzeari",
"strava-integration-after-date-hint": "",
"subway-stop": "Metro sarbidea", "subway-stop": "Metro sarbidea",
"summit": "Gailurra", "summit": "Gailurra",
"summit-book": "Igoeren liburua", "summit-book": "Igoeren liburua",

View File

@@ -32,6 +32,7 @@
"bakery": "Bakery", "bakery": "Bakery",
"barrier": "Barrier", "barrier": "Barrier",
"basic-info": "Informations de base", "basic-info": "Informations de base",
"basque": "Basque",
"before": "Avant le", "before": "Avant le",
"bicycle-parking": "Bicycle Parking", "bicycle-parking": "Bicycle Parking",
"bicycle-rental": "Bicycle Rental", "bicycle-rental": "Bicycle Rental",
@@ -56,7 +57,6 @@
"car": "Voiture", "car": "Voiture",
"car-motorcycle": "Car/Motorcycle", "car-motorcycle": "Car/Motorcycle",
"card": "{n, plural, =1 {Tuile} other {Tuiles}}", "card": "{n, plural, =1 {Tuile} other {Tuiles}}",
"basque": "Basque",
"categories": "Catégories", "categories": "Catégories",
"category": "Catégorie", "category": "Catégorie",
"change": "Changement", "change": "Changement",
@@ -390,6 +390,7 @@
"statistics": "Statistiques", "statistics": "Statistiques",
"stop-drawing": "Arrêter de tracer", "stop-drawing": "Arrêter de tracer",
"stop-editing": "Arrêter la modification", "stop-editing": "Arrêter la modification",
"strava-integration-after-date-hint": "",
"subway-stop": "Subway entrance", "subway-stop": "Subway entrance",
"summit": "Summit", "summit": "Summit",
"summit-book": "Liste des ascensions", "summit-book": "Liste des ascensions",

View File

@@ -32,6 +32,7 @@
"bakery": "Bakery", "bakery": "Bakery",
"barrier": "Barrier", "barrier": "Barrier",
"basic-info": "Alap információk", "basic-info": "Alap információk",
"basque": "Basque",
"before": "Before", "before": "Before",
"bicycle-parking": "Bicycle Parking", "bicycle-parking": "Bicycle Parking",
"bicycle-rental": "Bicycle Rental", "bicycle-rental": "Bicycle Rental",
@@ -56,7 +57,6 @@
"car": "Car", "car": "Car",
"car-motorcycle": "Car/Motorcycle", "car-motorcycle": "Car/Motorcycle",
"card": "{n, plural, =1 {Kártya} other {Kártyák}}", "card": "{n, plural, =1 {Kártya} other {Kártyák}}",
"basque": "Basque",
"categories": "Kategóriák", "categories": "Kategóriák",
"category": "Kategória", "category": "Kategória",
"change": "Változás", "change": "Változás",
@@ -390,6 +390,7 @@
"statistics": "Statistics", "statistics": "Statistics",
"stop-drawing": "Stop drawing", "stop-drawing": "Stop drawing",
"stop-editing": "Stop editing", "stop-editing": "Stop editing",
"strava-integration-after-date-hint": "",
"subway-stop": "Subway entrance", "subway-stop": "Subway entrance",
"summit": "Summit", "summit": "Summit",
"summit-book": "Csúcspont könyv", "summit-book": "Csúcspont könyv",

View File

@@ -32,6 +32,7 @@
"bakery": "Bakery", "bakery": "Bakery",
"barrier": "Barrier", "barrier": "Barrier",
"basic-info": "Informazioni di base", "basic-info": "Informazioni di base",
"basque": "Basque",
"before": "Prima", "before": "Prima",
"bicycle-parking": "Bicycle Parking", "bicycle-parking": "Bicycle Parking",
"bicycle-rental": "Bicycle Rental", "bicycle-rental": "Bicycle Rental",
@@ -56,7 +57,6 @@
"car": "Car", "car": "Car",
"car-motorcycle": "Car/Motorcycle", "car-motorcycle": "Car/Motorcycle",
"card": "{n, plural, =1 {Carta} other {Carte}}", "card": "{n, plural, =1 {Carta} other {Carte}}",
"basque": "Basque",
"categories": "Categorie", "categories": "Categorie",
"category": "Categoria", "category": "Categoria",
"change": "Modifica", "change": "Modifica",
@@ -390,6 +390,7 @@
"statistics": "Statistiche", "statistics": "Statistiche",
"stop-drawing": "Smettere di disegnare", "stop-drawing": "Smettere di disegnare",
"stop-editing": "Stop editing", "stop-editing": "Stop editing",
"strava-integration-after-date-hint": "",
"subway-stop": "Subway entrance", "subway-stop": "Subway entrance",
"summit": "Summit", "summit": "Summit",
"summit-book": "Libro di vetta", "summit-book": "Libro di vetta",

View File

@@ -32,6 +32,7 @@
"bakery": "Bakery", "bakery": "Bakery",
"barrier": "Barrier", "barrier": "Barrier",
"basic-info": "Algemene informatie", "basic-info": "Algemene informatie",
"basque": "Basque",
"before": "Voor", "before": "Voor",
"bicycle-parking": "Bicycle Parking", "bicycle-parking": "Bicycle Parking",
"bicycle-rental": "Bicycle Rental", "bicycle-rental": "Bicycle Rental",
@@ -56,7 +57,6 @@
"car": "Wagen", "car": "Wagen",
"car-motorcycle": "Car/Motorcycle", "car-motorcycle": "Car/Motorcycle",
"card": "{n, plural, =1 {Kaart} other {Kaarten}}", "card": "{n, plural, =1 {Kaart} other {Kaarten}}",
"basque": "Basque",
"categories": "Categorieën", "categories": "Categorieën",
"category": "Categorie", "category": "Categorie",
"change": "Wijzigen", "change": "Wijzigen",
@@ -390,6 +390,7 @@
"statistics": "Statistieken", "statistics": "Statistieken",
"stop-drawing": "Stop met tekenen", "stop-drawing": "Stop met tekenen",
"stop-editing": "Stop met bewerken", "stop-editing": "Stop met bewerken",
"strava-integration-after-date-hint": "",
"subway-stop": "Subway entrance", "subway-stop": "Subway entrance",
"summit": "Summit", "summit": "Summit",
"summit-book": "Bergtopboek", "summit-book": "Bergtopboek",

View File

@@ -32,6 +32,7 @@
"bakery": "Bakery", "bakery": "Bakery",
"barrier": "Barrier", "barrier": "Barrier",
"basic-info": "Podstawowe informacje", "basic-info": "Podstawowe informacje",
"basque": "Basque",
"before": "Przed", "before": "Przed",
"bicycle-parking": "Bicycle Parking", "bicycle-parking": "Bicycle Parking",
"bicycle-rental": "Bicycle Rental", "bicycle-rental": "Bicycle Rental",
@@ -56,7 +57,6 @@
"car": "Samochód", "car": "Samochód",
"car-motorcycle": "Car/Motorcycle", "car-motorcycle": "Car/Motorcycle",
"card": "{n, plural, =1 {Karta} other {Karty}}", "card": "{n, plural, =1 {Karta} other {Karty}}",
"basque": "Basque",
"categories": "Kategorie", "categories": "Kategorie",
"category": "Kategoria", "category": "Kategoria",
"change": "Zmień", "change": "Zmień",
@@ -390,6 +390,7 @@
"statistics": "Statystyki", "statistics": "Statystyki",
"stop-drawing": "Przestań rysować", "stop-drawing": "Przestań rysować",
"stop-editing": "Zakończ edycję", "stop-editing": "Zakończ edycję",
"strava-integration-after-date-hint": "",
"subway-stop": "Subway entrance", "subway-stop": "Subway entrance",
"summit": "Summit", "summit": "Summit",
"summit-book": "Logbook", "summit-book": "Logbook",

View File

@@ -32,6 +32,7 @@
"bakery": "Bakery", "bakery": "Bakery",
"barrier": "Barrier", "barrier": "Barrier",
"basic-info": "Informações básicas", "basic-info": "Informações básicas",
"basque": "Basque",
"before": "Antes", "before": "Antes",
"bicycle-parking": "Bicycle Parking", "bicycle-parking": "Bicycle Parking",
"bicycle-rental": "Bicycle Rental", "bicycle-rental": "Bicycle Rental",
@@ -56,7 +57,6 @@
"car": "Car", "car": "Car",
"car-motorcycle": "Car/Motorcycle", "car-motorcycle": "Car/Motorcycle",
"card": "{n, plural, =1 {Cartão} other {Cartões}}", "card": "{n, plural, =1 {Cartão} other {Cartões}}",
"basque": "Basque",
"categories": "Categorias", "categories": "Categorias",
"category": "Categoria", "category": "Categoria",
"change": "Alterar", "change": "Alterar",
@@ -390,6 +390,7 @@
"statistics": "Statistics", "statistics": "Statistics",
"stop-drawing": "Parar desenho", "stop-drawing": "Parar desenho",
"stop-editing": "Stop editing", "stop-editing": "Stop editing",
"strava-integration-after-date-hint": "",
"subway-stop": "Subway entrance", "subway-stop": "Subway entrance",
"summit": "Summit", "summit": "Summit",
"summit-book": "Livro da cimeira", "summit-book": "Livro da cimeira",

View File

@@ -32,6 +32,7 @@
"bakery": "Bakery", "bakery": "Bakery",
"barrier": "Barrier", "barrier": "Barrier",
"basic-info": "Основная информация", "basic-info": "Основная информация",
"basque": "Basque",
"before": "До", "before": "До",
"bicycle-parking": "Bicycle Parking", "bicycle-parking": "Bicycle Parking",
"bicycle-rental": "Bicycle Rental", "bicycle-rental": "Bicycle Rental",
@@ -56,7 +57,6 @@
"car": "Автомобиль:", "car": "Автомобиль:",
"car-motorcycle": "Car/Motorcycle", "car-motorcycle": "Car/Motorcycle",
"card": "{n, plural, =1 {Карточка} other {Карточки}}", "card": "{n, plural, =1 {Карточка} other {Карточки}}",
"basque": "Basque",
"categories": "Категории", "categories": "Категории",
"category": "Категория", "category": "Категория",
"change": "Изменить", "change": "Изменить",
@@ -339,7 +339,6 @@
"russian": "Russian", "russian": "Russian",
"save": "Сохранить", "save": "Сохранить",
"save-list": "Сохранить список", "save-list": "Сохранить список",
"russian": "Russian",
"save-trail": "Сохранить трек", "save-trail": "Сохранить трек",
"save-your-trail-first": "Сначала сохраните трек", "save-your-trail-first": "Сначала сохраните трек",
"search-cities": "Поиск по городам", "search-cities": "Поиск по городам",
@@ -391,6 +390,7 @@
"statistics": "Статистика", "statistics": "Статистика",
"stop-drawing": "Закончить рисование", "stop-drawing": "Закончить рисование",
"stop-editing": "Закончить редактирование", "stop-editing": "Закончить редактирование",
"strava-integration-after-date-hint": "",
"subway-stop": "Subway entrance", "subway-stop": "Subway entrance",
"summit": "Summit", "summit": "Summit",
"summit-book": "История поездок", "summit-book": "История поездок",
@@ -426,4 +426,4 @@
"width": "Ширина", "width": "Ширина",
"wrong-username-or-password": "Неверный логин или пароль", "wrong-username-or-password": "Неверный логин или пароль",
"you-can": "Вы можете" "you-can": "Вы можете"
} }

View File

@@ -32,6 +32,7 @@
"bakery": "面包店", "bakery": "面包店",
"barrier": "障碍", "barrier": "障碍",
"basic-info": "基本信息", "basic-info": "基本信息",
"basque": "Basque",
"before": "之前", "before": "之前",
"bicycle-parking": "自行车停车场", "bicycle-parking": "自行车停车场",
"bicycle-rental": "自行车租车", "bicycle-rental": "自行车租车",
@@ -56,7 +57,6 @@
"car": "汽车", "car": "汽车",
"car-motorcycle": "汽车/摩托车", "car-motorcycle": "汽车/摩托车",
"card": "{n, plural, =1 {卡片} other {卡片}}", "card": "{n, plural, =1 {卡片} other {卡片}}",
"basque": "Basque",
"categories": "分类", "categories": "分类",
"category": "分类", "category": "分类",
"change": "更换", "change": "更换",
@@ -390,6 +390,7 @@
"statistics": "统计", "statistics": "统计",
"stop-drawing": "停止绘制", "stop-drawing": "停止绘制",
"stop-editing": "停止编辑", "stop-editing": "停止编辑",
"strava-integration-after-date-hint": "",
"subway-stop": "地铁入口", "subway-stop": "地铁入口",
"summit": "山峰", "summit": "山峰",
"summit-book": "详细日程", "summit-book": "详细日程",

View File

@@ -6,7 +6,8 @@ const StravaSchema = z.object({
clientSecret: z.string().length(40).optional().or(z.literal('')), clientSecret: z.string().length(40).optional().or(z.literal('')),
routes: z.boolean(), routes: z.boolean(),
activities: z.boolean(), activities: z.boolean(),
active: z.boolean() active: z.boolean(),
after: z.string().date().optional(),
}) })
const KomootSchema = z.object({ const KomootSchema = z.object({

View File

@@ -11,6 +11,7 @@ export interface StravaIntegration extends BaseIntegration {
accessToken?: string; accessToken?: string;
refreshToken?: string; refreshToken?: string;
expiresAt?: number; expiresAt?: number;
after?: string
} }
export interface KomootIntegration extends BaseIntegration { export interface KomootIntegration extends BaseIntegration {