fix trail privacy (#719)

* fix trail privacy

* adds privacy setting to integrations

---------

Co-authored-by: Christian Beutel <>
This commit is contained in:
Flomp
2026-01-13 10:01:05 -08:00
committed by GitHub
parent c80dc01c7a
commit b65fbdd128
22 changed files with 210 additions and 27 deletions

View File

@@ -205,7 +205,7 @@ func syncTrailWithTours(app core.App, k *KomootApi, i KomootIntegration, user st
app.Logger().Warn(fmt.Sprintf("Unable to generate GPX for tour '%s': %v", tour.Name, err))
continue
}
trailid, err := createTrailFromTour(app, k, detailedTour, gpx, actor)
trailid, err := createTrailFromTour(app, k, detailedTour, gpx, user, actor, i.Privacy)
if err != nil {
app.Logger().Warn(fmt.Sprintf("Unable to create trail for tour '%s': %v", tour.Name, err))
continue
@@ -220,7 +220,7 @@ func syncTrailWithTours(app core.App, k *KomootApi, i KomootIntegration, user st
return hasNewTours, nil
}
func createTrailFromTour(app core.App, k *KomootApi, detailedTour *DetailedKomootTour, gpx *filesystem.File, actor string) (string, error) {
func createTrailFromTour(app core.App, k *KomootApi, detailedTour *DetailedKomootTour, gpx *filesystem.File, user string, actor string, privacy string) (string, error) {
trailid := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
collection, err := app.FindCollectionByNameOrId("trails")
@@ -266,10 +266,24 @@ func createTrailFromTour(app core.App, k *KomootApi, detailedTour *DetailedKomoo
diffculty = "easy"
}
public := detailedTour.Status == "public"
if privacy == "settings" {
privacySettings := struct {
Trails string `json:"trails"`
}{}
settings, _ := app.FindFirstRecordByData("settings", "user", user)
err = settings.UnmarshalJSONField("privacy", &privacySettings)
if err != nil {
return "", err
}
public = privacySettings.Trails == "public"
}
record.Load(map[string]any{
"id": trailid,
"name": detailedTour.Name,
"public": detailedTour.Status == "public",
"public": public,
"distance": detailedTour.Distance,
"elevation_gain": detailedTour.ElevationUp,
"elevation_loss": detailedTour.ElevationDown,

View File

@@ -8,6 +8,7 @@ type KomootIntegration struct {
Password string `json:"password"`
Planned bool `json:"planned"`
Completed bool `json:"completed"`
Privacy string `json:"privacy"`
}
type LoginResponse struct {

View File

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

View File

@@ -102,7 +102,7 @@ func SyncStrava(app core.App) error {
app.Logger().Warn(warning)
break
}
err = syncTrailsWithRoutes(app, r.AccessToken, userId, actorId, routes)
err = syncTrailsWithRoutes(app, stravaIntegration, r.AccessToken, userId, actorId, routes)
if err != nil {
warning := fmt.Sprintf("error syncing strava routes with trails: %v\n", err)
fmt.Print(warning)
@@ -134,7 +134,7 @@ func SyncStrava(app core.App) error {
app.Logger().Warn(warning)
break
}
err = syncTrailsWithActivities(app, r.AccessToken, actorId, activities)
err = syncTrailsWithActivities(app, stravaIntegration, r.AccessToken, userId, actorId, activities)
if err != nil {
warning := fmt.Sprintf("error syncing strava activities with trails: %v", err)
@@ -248,7 +248,7 @@ func fetchStravaActivities(accessToken string, page int, after int64) ([]StravaA
return activities, nil
}
func syncTrailsWithRoutes(app core.App, accessToken string, user string, actor string, routes []StravaRoute) error {
func syncTrailsWithRoutes(app core.App, i StravaIntegration, accessToken string, user string, actor string, routes []StravaRoute) error {
for _, route := range routes {
trails, err := app.FindRecordsByFilter("trails", "external_id = {:id}", "", 1, 0, dbx.Params{"id": route.IDStr})
if err != nil {
@@ -262,7 +262,7 @@ func syncTrailsWithRoutes(app core.App, accessToken string, user string, actor s
app.Logger().Warn(fmt.Sprintf("Unable to fetch GPX for route '%s': %v", route.Name, err))
continue
}
trailid, err := createTrailFromRoute(app, route, gpx, actor)
trailid, err := createTrailFromRoute(app, route, gpx, user, actor, i.Privacy)
if err != nil {
app.Logger().Warn(fmt.Sprintf("Unable to create trail for route '%s': %v", route.Name, err))
continue
@@ -315,7 +315,7 @@ func fetchRouteGPX(route StravaRoute, accessToken string) (*filesystem.File, err
return gpxFile, nil
}
func createTrailFromRoute(app core.App, route StravaRoute, gpx *filesystem.File, actor string) (string, error) {
func createTrailFromRoute(app core.App, route StravaRoute, gpx *filesystem.File, user string, actor string, privacy string) (string, error) {
trailid := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet)
collection, err := app.FindCollectionByNameOrId("trails")
@@ -348,11 +348,27 @@ func createTrailFromRoute(app core.App, route StravaRoute, gpx *filesystem.File,
category = hikeCategory.Id
}
public := !route.Private
if privacy == "settings" {
privacySettings := struct {
Trails string `json:"trails"`
}{}
settings, _ := app.FindFirstRecordByData("settings", "user", user)
err = settings.UnmarshalJSONField("privacy", &privacySettings)
if err != nil {
return "", err
}
public = privacySettings.Trails == "public"
}
record.Load(map[string]any{
"id": trailid,
"name": route.Name,
"description": route.Description,
"public": !route.Private,
"public": public,
"distance": route.Distance,
"elevation_gain": route.ElevationGain,
"duration": route.EstimatedMovingTime,
@@ -404,7 +420,7 @@ func createWaypointsFromRoute(app core.App, route StravaRoute, user string, trai
return nil
}
func syncTrailsWithActivities(app core.App, accessToken string, actor string, activities []StravaActivity) error {
func syncTrailsWithActivities(app core.App, i StravaIntegration, accessToken string, user string, actor string, activities []StravaActivity) error {
for _, activity := range activities {
trails, err := app.FindRecordsByFilter("trails", "external_id = {:id}", "", 1, 0, dbx.Params{"id": strconv.Itoa(int(activity.ID))})
if err != nil {
@@ -423,7 +439,7 @@ func syncTrailsWithActivities(app core.App, accessToken string, actor string, ac
app.Logger().Warn(fmt.Sprintf("Unable to fetch GPX for activity '%s': %v", activity.Name, err))
continue
}
err = createTrailFromActivity(app, detailedActivity, gpx, actor)
err = createTrailFromActivity(app, detailedActivity, gpx, user, actor, i.Privacy)
if err != nil {
app.Logger().Warn(fmt.Sprintf("Unable to create trail from activity '%s': %v", activity.Name, err))
continue
@@ -460,7 +476,7 @@ func fetchDetailedActivity(activity StravaActivity, accessToken string) (*Detail
return &detailedActivity, nil
}
func createTrailFromActivity(app core.App, activity *DetailedStravaActivity, gpx *filesystem.File, user string) error {
func createTrailFromActivity(app core.App, activity *DetailedStravaActivity, gpx *filesystem.File, user string, actor string, privacy string) error {
if len(activity.StartLatlng) < 2 {
return nil
}
@@ -526,10 +542,26 @@ func createTrailFromActivity(app core.App, activity *DetailedStravaActivity, gpx
categoryId = category.Id
}
public := !activity.Private
if privacy == "settings" {
privacySettings := struct {
Trails string `json:"trails"`
}{}
settings, _ := app.FindFirstRecordByData("settings", "user", user)
err = settings.UnmarshalJSONField("privacy", &privacySettings)
if err != nil {
return err
}
public = privacySettings.Trails == "public"
}
record.Load(map[string]any{
"name": activity.Name,
"description": activity.Description,
"public": !activity.Private,
"public": public,
"distance": activity.Distance,
"elevation_gain": activity.TotalElevationGain,
"duration": activity.ElapsedTime,
@@ -540,7 +572,7 @@ func createTrailFromActivity(app core.App, activity *DetailedStravaActivity, gpx
"lon": activity.StartLatlng[1],
"difficulty": "easy",
"category": categoryId,
"author": user,
"author": actor,
})
if photo != nil {

View File

@@ -1,5 +1,8 @@
<script lang="ts">
import Modal from "$lib/components/base/modal.svelte";
import Select, {
type SelectItem,
} from "$lib/components/base/select.svelte";
import TextField from "$lib/components/base/text_field.svelte";
import Toggle from "$lib/components/base/toggle.svelte";
import { KomootSchema } from "$lib/models/api/integration_schema";
@@ -20,6 +23,14 @@
let modal: Modal;
const privacySelectItems: SelectItem[] = [
{
text: $_("keep-original"),
value: "original",
},
{ text: $_("apply-user-settings"), value: "settings" },
];
export function openModal() {
errors.set({});
modal.openModal();
@@ -36,6 +47,7 @@
completed: integration?.komoot?.completed ?? true,
planned: integration?.komoot?.planned ?? true,
active: integration?.komoot?.active ?? false,
privacy: integration?.komoot?.privacy ?? "original",
},
extend: validator({
schema: KomootSchema,
@@ -70,13 +82,28 @@
error={$errors.password}
></TextField>
<div class="flex flex-wrap gap-x-4">
<Toggle name="planned" label={$_("planned-tours", { values: { n: 2 } })}
<Toggle
name="planned"
label={$_("planned-tours", { values: { n: 2 } })}
></Toggle>
<Toggle
name="completed"
label={$_("completed-tours", { values: { n: 2 } })}
></Toggle>
</div>
<Select
label={$_("privacy")}
items={privacySelectItems}
name="privacy"
></Select>
<p class="text-xs text-gray-500 max-w-lg">
{#if $d.privacy == "original"}
{$_("integration-privacy-hint-original")}
{:else}
{$_("integration-privacy-hint-user")}
{/if}
</p>
</form>
{/snippet}
{#snippet footer()}

View File

@@ -1,6 +1,8 @@
<script lang="ts">
import Datepicker from "$lib/components/base/datepicker.svelte";
import Modal from "$lib/components/base/modal.svelte";
import type { SelectItem } from "$lib/components/base/select.svelte";
import Select from "$lib/components/base/select.svelte";
import TextField from "$lib/components/base/text_field.svelte";
import Toggle from "$lib/components/base/toggle.svelte";
import { StravaSchema } from "$lib/models/api/integration_schema";
@@ -21,6 +23,14 @@
let modal: Modal;
const privacySelectItems: SelectItem[] = [
{
text: $_("keep-original"),
value: "original",
},
{ text: $_("apply-user-settings"), value: "settings" },
];
export function openModal() {
errors.set({});
modal.openModal();
@@ -38,6 +48,7 @@
activities: integration?.strava?.activities ?? true,
active: integration?.strava?.active ?? false,
after: integration?.strava?.after,
privacy: integration?.komoot?.privacy ?? "original",
},
extend: validator({
schema: StravaSchema,
@@ -85,12 +96,20 @@
label={$_("activity", { values: { n: 2 } })}
></Toggle>
</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")}
<Select
label={$_("privacy")}
items={privacySelectItems}
name="privacy"
></Select>
<p class="text-xs text-gray-500 max-w-lg">
{#if $formData.privacy == "original"}
{$_("integration-privacy-hint-original")}
{:else}
{$_("integration-privacy-hint-user")}
{/if}
</p>
<div class="flex items-end relative gap-x-2">
<div class="flex items-end relative gap-x-2 pt-2 border-t border-input-border">
<Datepicker
error={$errors.after}
label={$_("after")}
@@ -104,6 +123,11 @@
><i class="fa fa-close"></i></button
>
</div>
<p
class="text-xs text-gray-500 max-w-lg"
>
{$_("strava-integration-after-date-hint")}
</p>
</form>
{/snippet}
{#snippet footer()}

View File

@@ -22,6 +22,7 @@
"altitude": "Höhe",
"amenity": "Amenity",
"api-documentation": "API Dokumentation",
"apply-user-settings": "",
"attraction": "Sehenswürdigkeit",
"author": "Autor",
"avatar": "Avatar",
@@ -206,11 +207,14 @@
"integration-description-strava": "Synchronisiert Deine strava-Routen und -Aktivitäten regelmäßig mit wanderer.",
"integration-disabled": "Integration deaktiviert",
"integration-enabled": "Integration aktiviert",
"integration-privacy-hint-original": "",
"integration-privacy-hint-user": "",
"integrations": "Integrationen",
"invalid-date": "Ungültiges Datum",
"invalid-username": "Ungültiger Nutzername",
"italian": "Italienisch",
"joined": "Beigetreten",
"keep-original": "",
"language": "Sprache",
"latitude": "Breitengrad",
"layer": "{n, plural, =1 {Ebene} other {Ebenen}}",
@@ -344,6 +348,7 @@
"save-your-trail-first": "Route zuerst speichern",
"search-cities": "Städte suchen",
"search-for-trails-places": "Suche nach Routen, Listen, Orten",
"search-list": "Liste suchen",
"search-places": "Orte suchen",
"search-trails": "Route suchen",
"select-list": "Liste auswählen",
@@ -403,10 +408,9 @@
"top-speed": "Höchstgeschwindigkeit",
"tourism": "Tourismus",
"trail": "{n, plural, =1 {Route} other {Routen}}",
"trail-not-shared": "Mit niemandem geteilt",
"trail-not-in-list": "Trail gehört zu keiner Liste.",
"trail-not-shared": "Mit niemandem geteilt",
"trail-saved-successfully": "Route gespeichert",
"search-list": "Liste suchen",
"trails-for-you": "Routen für dich",
"tram-stop": "Tram Haltestelle",
"unchanged": "unverändert",

View File

@@ -22,6 +22,7 @@
"altitude": "Altitude",
"amenity": "Amenity",
"api-documentation": "API Documentation",
"apply-user-settings": "Apply user settings",
"attraction": "Attraction",
"author": "Author",
"avatar": "Avatar",
@@ -206,11 +207,14 @@
"integration-description-strava": "Syncs your strava routes & activities with wanderer in regular intervals.",
"integration-disabled": "integration disabled",
"integration-enabled": "integration enabled",
"integration-privacy-hint-original": "Imported trails will maintain the same visibility they have on the external platform. For example, if the original trail was public, it will be public in wanderer, even if trails are private by default according to your privacy settings.",
"integration-privacy-hint-user": "The original trail's visibility is discarded. Instead, the local privacy settings for trails are applied to all imported trails.",
"integrations": "Integrations",
"invalid-date": "Invalid Date",
"invalid-username": "Invalid username",
"italian": "Italian",
"joined": "Joined",
"keep-original": "Keep original",
"language": "Language",
"latitude": "Latitude",
"layer": "{n, plural, =1 {Layer} other {Layers}}",
@@ -344,6 +348,7 @@
"save-your-trail-first": "Save your trail first",
"search-cities": "Search cities",
"search-for-trails-places": "Search for trails, lists, places",
"search-list": "Search list",
"search-places": "Search places",
"search-trails": "Search trails",
"select-list": "Select List",
@@ -403,10 +408,9 @@
"top-speed": "Top Speed",
"tourism": "Tourism",
"trail": "{n, plural, =1 {Trail} other {Trails}}",
"trail-not-shared": "Not shared with anyone",
"trail-not-in-list": "Trail is not in any list",
"trail-not-shared": "Not shared with anyone",
"trail-saved-successfully": "Trail saved successfully",
"search-list": "Search list",
"trails-for-you": "Trails for you",
"tram-stop": "Tram stop",
"unchanged": "unchanged",

View File

@@ -22,6 +22,7 @@
"altitude": "Altitud",
"amenity": "Amenity",
"api-documentation": "Documentación API",
"apply-user-settings": "",
"attraction": "Attraction",
"author": "Autor",
"avatar": "Avatar",
@@ -206,11 +207,14 @@
"integration-description-strava": "Sincroniza tus recorridos de Strava con Wanderer en intervalos regulares.",
"integration-disabled": "integración deshabilitada",
"integration-enabled": "integración habilitada",
"integration-privacy-hint-original": "",
"integration-privacy-hint-user": "",
"integrations": "Integraciones",
"invalid-date": "Fecha no válida",
"invalid-username": "Usuario no válido",
"italian": "Italiano",
"joined": "Afiliado",
"keep-original": "",
"language": "Idioma",
"latitude": "Latitud",
"layer": "{n, plural, =1 {Layer} other {Layers}}",
@@ -220,6 +224,7 @@
"likes": "Likes",
"limited": "Limited",
"link-copied": "¡Enlace copiado!",
"linked-lists": "",
"list": "{n, plural, one {}=1 {Lista} other {Listas}}",
"list-not-shared": "No compartido con ninguno",
"list-public-warning": "Todas las rutas en esta lista serán públicas.",
@@ -343,6 +348,7 @@
"save-your-trail-first": "Guarda tu ruta primero",
"search-cities": "Buscar ciudades",
"search-for-trails-places": "Busca rutas, lugares",
"search-list": "",
"search-places": "Buscar lugares",
"search-trails": "Buscar ruta",
"select-list": "Seleccionar Lista",
@@ -402,6 +408,7 @@
"top-speed": "Top Speed",
"tourism": "Tourism",
"trail": "{n, plural, one {}=1 {Ruta} other {Rutas}}",
"trail-not-in-list": "",
"trail-not-shared": "No compartida con nadie",
"trail-saved-successfully": "Ruta guardada con éxito",
"trails-for-you": "Rutas para ti",

View File

@@ -22,6 +22,7 @@
"altitude": "Altuera",
"amenity": "Altimetria",
"api-documentation": "API dokumentazioa",
"apply-user-settings": "",
"attraction": "Erakarmena",
"author": "Egilea",
"avatar": "Iruditxoa",
@@ -206,11 +207,14 @@
"integration-description-strava": "Zure stravako ibilbideak wandererekin sinkronizatzen ditu aldian behin.",
"integration-disabled": "integrazioa desaktibatuta",
"integration-enabled": "integrazioa aktibatuta",
"integration-privacy-hint-original": "",
"integration-privacy-hint-user": "",
"integrations": "Integrazioak",
"invalid-date": "Data ez da zuzena",
"invalid-username": "Erabiltzailea ez da zuzena",
"italian": "Italiera",
"joined": "Sartu da",
"keep-original": "",
"language": "Hizkuntza",
"latitude": "Latitudea",
"layer": "{n, plural, one {}=1 {geruza} other {geruza}}",
@@ -220,6 +224,7 @@
"likes": "Atsegiteak",
"limited": "Mugatuta",
"link-copied": "Esteka kopiatu da!",
"linked-lists": "",
"list": "{n, plural, one {}=1 {zerrenda} other {zerrenda}}",
"list-not-shared": "Inorekin partekatu gabe",
"list-public-warning": "Zerreda honetako ibilbide guztiak publiko bihurtuko dira.",
@@ -343,6 +348,7 @@
"save-your-trail-first": "Gorde zure ibilaldia lehenengo",
"search-cities": "Bilatu herriak",
"search-for-trails-places": "Bilatu ibilbideak, zerrendak, tokiak",
"search-list": "",
"search-places": "Bilatu tokiak",
"search-trails": "Bilatu ibilaldiak",
"select-list": "Aukeratu zerrenda",
@@ -402,6 +408,7 @@
"top-speed": "Gehienezko abiadura",
"tourism": "Turismoa",
"trail": "{n, plural, one {}=1 {ibilbide} other {ibilbide}}",
"trail-not-in-list": "",
"trail-not-shared": "Inorekin partekatu gabe",
"trail-saved-successfully": "Ibilbidea ondo gorde da",
"trails-for-you": "Zuretzako ibilbideak",

View File

@@ -22,6 +22,7 @@
"altitude": "Altitude",
"amenity": "Amenity",
"api-documentation": "Documentation API",
"apply-user-settings": "",
"attraction": "Attraction",
"author": "Auteur",
"avatar": "Avatar",
@@ -206,11 +207,14 @@
"integration-description-strava": "Synchronisez vos itinéraires et vos activités Strava avec wanderer à intervalles réguliers.",
"integration-disabled": "Intégration désactivée",
"integration-enabled": "Intégration activée",
"integration-privacy-hint-original": "",
"integration-privacy-hint-user": "",
"integrations": "Intégrations",
"invalid-date": "Date invalide",
"invalid-username": "Nom d'utilisateur invalide",
"italian": "Italien",
"joined": "Rejoint",
"keep-original": "",
"language": "Langue",
"latitude": "Latitude",
"layer": "{n, plural, =1 {Layer} other {Layers}}",
@@ -220,6 +224,7 @@
"likes": "\"J'aime\"",
"limited": "Limited",
"link-copied": "Lien copié !",
"linked-lists": "",
"list": "{n, plural, =1 {Liste} other {Listes}}",
"list-not-shared": "Non partagé avec quiconque",
"list-public-warning": "Tous les itinéraires de cette liste seront publics.",
@@ -343,6 +348,7 @@
"save-your-trail-first": "Enregistrez d'abord votre itinéraire",
"search-cities": "Recherche une ville",
"search-for-trails-places": "Chercher un itinéraire ou un lieu",
"search-list": "",
"search-places": "Chercher des lieux",
"search-trails": "Chercher un itinéraire",
"select-list": "Liste de choix",
@@ -402,6 +408,7 @@
"top-speed": "Vitesse maximale",
"tourism": "Tourism",
"trail": "{n, plural, =1 {Itinéraire} other {Itinéraires}}",
"trail-not-in-list": "",
"trail-not-shared": "L'itinéraire n'a pas été partagé",
"trail-saved-successfully": "Itinéraire enregistrée",
"trails-for-you": "Itinéraire pour vous",

View File

@@ -22,6 +22,7 @@
"altitude": "Magasság",
"amenity": "Amenity",
"api-documentation": "API Dokumentáció",
"apply-user-settings": "",
"attraction": "Attraction",
"author": "Author",
"avatar": "Avatar",
@@ -206,11 +207,14 @@
"integration-description-strava": "Syncs your strava routes & activities with wanderer in regular intervals.",
"integration-disabled": "integration disabled",
"integration-enabled": "integration enabled",
"integration-privacy-hint-original": "",
"integration-privacy-hint-user": "",
"integrations": "Integrations",
"invalid-date": "Érvénytelen dátum",
"invalid-username": "Érvénytelen felhasználó",
"italian": "Olasz",
"joined": "Joined",
"keep-original": "",
"language": "Nyelf",
"latitude": "Szélesség",
"layer": "{n, plural, =1 {Layer} other {Layers}}",
@@ -220,6 +224,7 @@
"likes": "Likes",
"limited": "Limited",
"link-copied": "Link copied!",
"linked-lists": "",
"list": "{n, plural, =1 {Lista} other {Listák}}",
"list-not-shared": "Not shared with anyone",
"list-public-warning": "All trails in this list will become public.",
@@ -343,6 +348,7 @@
"save-your-trail-first": "Először mentsd el a nyomvonaladat",
"search-cities": "Városok keresése",
"search-for-trails-places": "Nyomvonalak, helyek keresése",
"search-list": "",
"search-places": "Search places",
"search-trails": "Nyomvonalak keresése",
"select-list": "Lista kiválasztása",
@@ -402,6 +408,7 @@
"top-speed": "Top Speed",
"tourism": "Tourism",
"trail": "{n, plural, =1 {Útvonal} other {Útvonalak}}",
"trail-not-in-list": "",
"trail-not-shared": "Not shared with anyone",
"trail-saved-successfully": "Trail saved successfully",
"trails-for-you": "Útvonalak önnek",

View File

@@ -22,6 +22,7 @@
"altitude": "Altitudine",
"amenity": "Amenity",
"api-documentation": "Documentazione API",
"apply-user-settings": "",
"attraction": "Attraction",
"author": "Autore",
"avatar": "Avatar",
@@ -206,11 +207,14 @@
"integration-description-strava": "Syncs your strava routes & activities with wanderer in regular intervals.",
"integration-disabled": "integration disabled",
"integration-enabled": "integration enabled",
"integration-privacy-hint-original": "",
"integration-privacy-hint-user": "",
"integrations": "Integrations",
"invalid-date": "Data non valida",
"invalid-username": "Nome utente non valido",
"italian": "Italiano",
"joined": "Aggiunto",
"keep-original": "",
"language": "Lingua",
"latitude": "Latitudine",
"layer": "{n, plural, =1 {Layer} other {Layers}}",
@@ -220,6 +224,7 @@
"likes": "Likes",
"limited": "Limited",
"link-copied": "Link copiato",
"linked-lists": "",
"list": "{n, plural, =1 {Lista} other {Liste}}",
"list-not-shared": "Non condivisa",
"list-public-warning": "Tutti i percorsi in questa lista diventeranno pubblici.",
@@ -343,6 +348,7 @@
"save-your-trail-first": "Salva prima il tuo percorso",
"search-cities": "Cerca città",
"search-for-trails-places": "Cerca percorsi, luoghi",
"search-list": "",
"search-places": "Search places",
"search-trails": "Cerca percorsi",
"select-list": "Seleziona lista",
@@ -402,6 +408,7 @@
"top-speed": "Top Speed",
"tourism": "Tourism",
"trail": "{n, plural, =1 {Percorso} other {Percorsi}}",
"trail-not-in-list": "",
"trail-not-shared": "Percorso non condiviso con nessuno",
"trail-saved-successfully": "Percorso salvato con successo",
"trails-for-you": "Percorsi per te",

View File

@@ -22,6 +22,7 @@
"altitude": "Hoogte",
"amenity": "Amenity",
"api-documentation": "API-documentatie",
"apply-user-settings": "",
"attraction": "Attraction",
"author": "Auteur",
"avatar": "Profielfoto",
@@ -206,11 +207,14 @@
"integration-description-strava": "Synchroniseert je Strava-routes en -activiteiten met Wanderer op regelmatige tijdstippen.",
"integration-disabled": "Integratie uitgeschakeld",
"integration-enabled": "Integratie ingeschakeld",
"integration-privacy-hint-original": "",
"integration-privacy-hint-user": "",
"integrations": "Integraties",
"invalid-date": "Ongeldige datum",
"invalid-username": "Ongeldige gebruikersnaam",
"italian": "Italiaans",
"joined": "Aangesloten",
"keep-original": "",
"language": "Taal",
"latitude": "Breedtegraad",
"layer": "{n, plural, =1 {Layer} other {Layers}}",
@@ -220,6 +224,7 @@
"likes": "Vind-ik-leuks",
"limited": "Limited",
"link-copied": "Link gekopieerd",
"linked-lists": "",
"list": "{n, plural, =1 {Lijst} other {Lijsten}}",
"list-not-shared": "Niet gedeeld met iemand",
"list-public-warning": "Alle routes in deze lijst worden publiek",
@@ -343,6 +348,7 @@
"save-your-trail-first": "Bewaar je route eerst",
"search-cities": "Zoeken naar steden",
"search-for-trails-places": "Zoek naar routes, lijsten en locaties",
"search-list": "",
"search-places": "Zoek plaatsen",
"search-trails": "Zoek routes",
"select-list": "Kies een lijst",
@@ -402,6 +408,7 @@
"top-speed": "Topsnelheid",
"tourism": "Tourism",
"trail": "{n, plural, =1 {Route} other {Routes}}",
"trail-not-in-list": "",
"trail-not-shared": "Niet gedeeld met iemand",
"trail-saved-successfully": "Route succesvol bewaard",
"trails-for-you": "Routes voor jou",

View File

@@ -22,6 +22,7 @@
"altitude": "Wysokość",
"amenity": "Amenity",
"api-documentation": "Dokumentacja API",
"apply-user-settings": "",
"attraction": "Attraction",
"author": "Autor",
"avatar": "Awatar",
@@ -206,11 +207,14 @@
"integration-description-strava": "Synchronizuje trasy i aktywność z wanderer w równych odstępach.",
"integration-disabled": "integracja wyłączona",
"integration-enabled": "integracja włączona",
"integration-privacy-hint-original": "",
"integration-privacy-hint-user": "",
"integrations": "Integracje",
"invalid-date": "Nieprawidłowa data",
"invalid-username": "Błędna nazwa użytkownika",
"italian": "Włoski",
"joined": "Dołączono",
"keep-original": "",
"language": "Język",
"latitude": "Szerokość",
"layer": "{n, plural, =1 {Layer} other {Layers}}",
@@ -220,6 +224,7 @@
"likes": "Polubienia",
"limited": "Limited",
"link-copied": "Link skopiowany!",
"linked-lists": "",
"list": "{n, plural, =1 {Lista} other {Listy}}",
"list-not-shared": "Nikomu nie udostępniony",
"list-public-warning": "Wszystkie szlaki na tej liście staną się publiczne.",
@@ -343,6 +348,7 @@
"save-your-trail-first": "Najpierw zapisz swój szlak",
"search-cities": "Szukaj miasta",
"search-for-trails-places": "Szukaj szlaków lub miejsc",
"search-list": "",
"search-places": "Szukaj miejsc",
"search-trails": "Szukaj szlaków",
"select-list": "Wybierz Listę",
@@ -402,6 +408,7 @@
"top-speed": "Maksymalna prędkość",
"tourism": "Tourism",
"trail": "{n, plural, one {Szlak} few {Szlaki} many {Szlaków}=1 {Szlak} other {Szlaki}}",
"trail-not-in-list": "",
"trail-not-shared": "Szlak nie udostępniony",
"trail-saved-successfully": "Szlak pomyślnie zapisany",
"trails-for-you": "Szlaki dla ciebie",

View File

@@ -22,6 +22,7 @@
"altitude": "Altitude",
"amenity": "Amenity",
"api-documentation": "Documentação da API",
"apply-user-settings": "",
"attraction": "Attraction",
"author": "Author",
"avatar": "Avatar",
@@ -206,11 +207,14 @@
"integration-description-strava": "Syncs your strava routes & activities with wanderer in regular intervals.",
"integration-disabled": "integration disabled",
"integration-enabled": "integration enabled",
"integration-privacy-hint-original": "",
"integration-privacy-hint-user": "",
"integrations": "Integrations",
"invalid-date": "Data inválida",
"invalid-username": "Nome de usuário inválido",
"italian": "Italiano",
"joined": "Joined",
"keep-original": "",
"language": "Língua",
"latitude": "Latitude",
"layer": "{n, plural, =1 {Layer} other {Layers}}",
@@ -220,6 +224,7 @@
"likes": "Likes",
"limited": "Limited",
"link-copied": "Link copiado!",
"linked-lists": "",
"list": "{n, plural, =1 {Lista} other {Listas}}",
"list-not-shared": "Não partilhado com ninguém",
"list-public-warning": "All trails in this list will become public.",
@@ -343,6 +348,7 @@
"save-your-trail-first": "Salve sua trilha primeiro",
"search-cities": "Procurar cidades",
"search-for-trails-places": "Procurar trilhos, locais",
"search-list": "",
"search-places": "Search places",
"search-trails": "Procurar trilhos",
"select-list": "Selecionar lista",
@@ -402,6 +408,7 @@
"top-speed": "Top Speed",
"tourism": "Tourism",
"trail": "{n, plural, =1 {Percurso} other {Percursos}}",
"trail-not-in-list": "",
"trail-not-shared": "Não partilhado com ninguém",
"trail-saved-successfully": "Percurso gravado com sucesso",
"trails-for-you": "Trilhos para si",

View File

@@ -22,6 +22,7 @@
"altitude": "Высота",
"amenity": "Amenity",
"api-documentation": "Документация API",
"apply-user-settings": "",
"attraction": "Attraction",
"author": "Автор",
"avatar": "Аватар",
@@ -206,11 +207,14 @@
"integration-description-strava": "Синхронизирует ваши данные со Strava.",
"integration-disabled": "интеграция отключена",
"integration-enabled": "интеграция включена",
"integration-privacy-hint-original": "",
"integration-privacy-hint-user": "",
"integrations": "Интеграции",
"invalid-date": "Неверная дата",
"invalid-username": "Некорректное имя пользователя",
"italian": "Итальянский",
"joined": "Зарегистрирован",
"keep-original": "",
"language": "Язык",
"latitude": "Широта",
"layer": "{n, plural, =1 {Layer} other {Layers}}",
@@ -220,6 +224,7 @@
"likes": "Лайков",
"limited": "Limited",
"link-copied": "Ссылка скопирована!",
"linked-lists": "",
"list": "{n, plural, =1 {Список} other {Списки}}",
"list-not-shared": "Нет общего доступа",
"list-public-warning": "Все треки в списке станут публичными.",
@@ -343,6 +348,7 @@
"save-your-trail-first": "Сначала сохраните трек",
"search-cities": "Поиск по городам",
"search-for-trails-places": "Поиск треков, списков, мест",
"search-list": "",
"search-places": "Поиск мест",
"search-trails": "Поиск треков",
"select-list": "Выбрать список",
@@ -402,6 +408,7 @@
"top-speed": "Макс. скорость",
"tourism": "Tourism",
"trail": "{n, plural, =1 {Трек} other {Треки}}",
"trail-not-in-list": "",
"trail-not-shared": "Нет общего доступа",
"trail-saved-successfully": "Трек сохранён",
"trails-for-you": "Треки для вас",

View File

@@ -22,6 +22,7 @@
"altitude": "海拔",
"amenity": "友好性",
"api-documentation": "API 文档",
"apply-user-settings": "",
"attraction": "景点",
"author": "作者",
"avatar": "头像",
@@ -206,11 +207,14 @@
"integration-description-strava": "定期与strava同步您的wanderer路线和活动。",
"integration-disabled": "整合已停用",
"integration-enabled": "整合已启用",
"integration-privacy-hint-original": "",
"integration-privacy-hint-user": "",
"integrations": "整合",
"invalid-date": "无效日期",
"invalid-username": "无效用户名",
"italian": "意大利语",
"joined": "已加入",
"keep-original": "",
"language": "语言",
"latitude": "纬度",
"layer": "{n, plural, =1 {层} other {层}}",
@@ -220,6 +224,7 @@
"likes": "赞",
"limited": "Limited",
"link-copied": "链接已复制",
"linked-lists": "",
"list": "{n, plural, =1 {列表} other {列表}}",
"list-not-shared": "未与任何人分享",
"list-public-warning": "此列表中的所有轨迹都将被公开。",
@@ -343,6 +348,7 @@
"save-your-trail-first": "先保存你的路线",
"search-cities": "搜索城市",
"search-for-trails-places": "搜索路线、地点",
"search-list": "",
"search-places": "搜索地点",
"search-trails": "搜索路线",
"select-list": "选择列表",
@@ -402,6 +408,7 @@
"top-speed": "最高速度",
"tourism": "旅游",
"trail": "{n, plural, =1 {路线} other {路线}}",
"trail-not-in-list": "",
"trail-not-shared": "未与任何人分享",
"trail-saved-successfully": "路线保存成功",
"trails-for-you": "推荐路线",

View File

@@ -8,6 +8,7 @@ const StravaSchema = z.object({
activities: z.boolean(),
active: z.boolean(),
after: z.string().date().optional(),
privacy: z.enum(["original", "settings"])
})
const KomootSchema = z.object({
@@ -16,6 +17,7 @@ const KomootSchema = z.object({
completed: z.boolean(),
planned: z.boolean(),
active: z.boolean(),
privacy: z.enum(["original", "settings"])
})
const IntegrationCreateSchema = z.object({
@@ -30,4 +32,4 @@ const IntegrationUpdateSchema = z.object({
komoot: KomootSchema.optional().nullable()
}) satisfies ZodType<Partial<Integration>>
export { StravaSchema, IntegrationCreateSchema, IntegrationUpdateSchema, KomootSchema };
export { StravaSchema, IntegrationCreateSchema, IntegrationUpdateSchema, KomootSchema };

View File

@@ -12,6 +12,7 @@ export interface StravaIntegration extends BaseIntegration {
refreshToken?: string;
expiresAt?: number;
after?: string
privacy: "original" | "settings"
}
export interface KomootIntegration extends BaseIntegration {
@@ -19,6 +20,7 @@ export interface KomootIntegration extends BaseIntegration {
password: string,
completed: boolean,
planned: boolean
privacy: "original" | "settings"
}

View File

@@ -15,8 +15,8 @@ const publicRoutes = [
"/api/v1/category",
"/api/v1/auth/oauth",
"/register",
"/auth"
"/auth",
"/.well-known"
]
export function isRouteProtected(url: URL | undefined) {

View File

@@ -46,6 +46,8 @@ export async function PUT(event: RequestEvent) {
trail.location ??= location;
}
trail.public = event.locals.settings.privacy.trails == "public"
// const log = new SummitLog(trail.date as string, {
// distance: trail.distance,
// elevation_gain: trail.elevation_gain,