From 6b0bdcfff6a360ed978e71502e3dd3d37ec86d07 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 11:51:45 +0000 Subject: [PATCH 01/15] Bump actions/setup-go from 4 to 5 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 4 to 5. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/v4...v5) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 68458977..6110a793 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -58,7 +58,7 @@ jobs: ref: ${{ github.ref }} - name: Setup Go - uses: actions/setup-go@v4 + uses: actions/setup-go@v5 with: go-version: '1.22' From 9597435dffb6d9a67a83ed439a7227d05805f05a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 12:17:46 +0000 Subject: [PATCH 02/15] Bump actions/setup-node from 3 to 4 Bumps [actions/setup-node](https://github.com/actions/setup-node) from 3 to 4. - [Release notes](https://github.com/actions/setup-node/releases) - [Commits](https://github.com/actions/setup-node/compare/v3...v4) --- updated-dependencies: - dependency-name: actions/setup-node dependency-version: '4' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 68458977..9d2305b1 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -22,7 +22,7 @@ jobs: # 2. Setup node & npm - name: Setup Node.js - uses: actions/setup-node@v3 + uses: actions/setup-node@v4 with: node-version: '22' From 66e482c6fc1a244619b28a0da430fc44fa8c0d60 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 28 Aug 2025 12:36:05 +0000 Subject: [PATCH 03/15] Bump actions/checkout from 3 to 5 Bumps [actions/checkout](https://github.com/actions/checkout) from 3 to 5. - [Release notes](https://github.com/actions/checkout/releases) - [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md) - [Commits](https://github.com/actions/checkout/compare/v3...v5) --- updated-dependencies: - dependency-name: actions/checkout dependency-version: '5' dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/release.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 68458977..2dfc7551 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -18,7 +18,7 @@ jobs: steps: # 1. Checkout the repository - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v5 # 2. Setup node & npm - name: Setup Node.js @@ -53,7 +53,7 @@ jobs: steps: # 1. Checkout the repository - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v5 with: ref: ${{ github.ref }} @@ -106,7 +106,7 @@ jobs: steps: # 1. Checkout the repository - name: Checkout code - uses: actions/checkout@v3 + uses: actions/checkout@v5 with: ref: ${{ github.ref }} # 2. Extract release notes from CHANGELOG.md From c6042db9a802394379c5d0b4b9d68130e11f1158 Mon Sep 17 00:00:00 2001 From: Christian Beutel <> Date: Fri, 29 Aug 2025 15:18:24 +0200 Subject: [PATCH 04/15] fixes permission issues for non-public trails --- db/migrations/1756471290_updated_trails.go | 42 +++++++++++++++++++ db/migrations/1756473199_updated_waypoints.go | 42 +++++++++++++++++++ 2 files changed, 84 insertions(+) create mode 100644 db/migrations/1756471290_updated_trails.go create mode 100644 db/migrations/1756473199_updated_waypoints.go diff --git a/db/migrations/1756471290_updated_trails.go b/db/migrations/1756471290_updated_trails.go new file mode 100644 index 00000000..3c131c5c --- /dev/null +++ b/db/migrations/1756471290_updated_trails.go @@ -0,0 +1,42 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4") + if err != nil { + return err + } + + // update collection data + if err := json.Unmarshal([]byte(`{ + "listRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && trail_share_via_trail.actor.user ?= @request.auth.id) || (trail_link_share_via_trail.token != \"\" && trail_link_share_via_trail.token = @request.query.share)", + "viewRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && trail_share_via_trail.actor.user ?= @request.auth.id) || (trail_link_share_via_trail.token != \"\" && trail_link_share_via_trail.token = @request.query.share)" + }`), &collection); err != nil { + return err + } + + return app.Save(collection) + }, func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4") + if err != nil { + return err + } + + // update collection data + if err := json.Unmarshal([]byte(`{ + "listRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && trail_share_via_trail.actor.user ?= @request.auth.id) || trail_link_share_via_trail.token = @request.query.share", + "viewRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && trail_share_via_trail.actor.user ?= @request.auth.id) || trail_link_share_via_trail.token = @request.query.share " + }`), &collection); err != nil { + return err + } + + return app.Save(collection) + }) +} diff --git a/db/migrations/1756473199_updated_waypoints.go b/db/migrations/1756473199_updated_waypoints.go new file mode 100644 index 00000000..e8fca54f --- /dev/null +++ b/db/migrations/1756473199_updated_waypoints.go @@ -0,0 +1,42 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("goeo2ubp103rzp9") + if err != nil { + return err + } + + // update collection data + if err := json.Unmarshal([]byte(`{ + "listRule": "author = @request.auth.id || trails_via_waypoints.author.user ?= @request.auth.id || trails_via_waypoints.public ?= true || \n(@collection.trail_share.trail.id ?= trails_via_waypoints.id && @collection.trail_share.actor.user ?= @request.auth.id)\n|| \n(@collection.trail_link_share.token != \"\" && @collection.trail_link_share.trail.waypoints.id ?= id)", + "viewRule": "author = @request.auth.id || trails_via_waypoints.author.user ?= @request.auth.id || trails_via_waypoints.public ?= true || \n(@collection.trail_share.trail.id ?= trails_via_waypoints.id && @collection.trail_share.actor.user ?= @request.auth.id)\n|| \n(@collection.trail_link_share.token != \"\" && @collection.trail_link_share.trail.waypoints.id ?= id)" + }`), &collection); err != nil { + return err + } + + return app.Save(collection) + }, func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("goeo2ubp103rzp9") + if err != nil { + return err + } + + // update collection data + if err := json.Unmarshal([]byte(`{ + "listRule": "author = @request.auth.id || trails_via_waypoints.author.user ?= @request.auth.id || trails_via_waypoints.public ?= true || \n(@collection.trail_share.trail.id ?= trails_via_waypoints.id && @collection.trail_share.actor.user ?= @request.auth.id)\n|| \n(@collection.trail_link_share.trail.id ?= trails_via_waypoints.id && @collection.trail_link_share.token = @request.query.share)", + "viewRule": "author = @request.auth.id || trails_via_waypoints.author.user ?= @request.auth.id || trails_via_waypoints.public ?= true || \n(@collection.trail_share.trail.id ?= trails_via_waypoints.id && @collection.trail_share.actor.user ?= @request.auth.id)\n|| \n(@collection.trail_link_share.trail.id ?= trails_via_waypoints.id && @collection.trail_link_share.token = @request.query.share)" + }`), &collection); err != nil { + return err + } + + return app.Save(collection) + }) +} From 64579808a9b3fa1e6dd2f7303c772fc0875c38b1 Mon Sep 17 00:00:00 2001 From: Christian Beutel <> Date: Sun, 31 Aug 2025 16:41:03 +0200 Subject: [PATCH 05/15] fixes duplicate trails --- db/util/activitypub.go | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/db/util/activitypub.go b/db/util/activitypub.go index 197f25fd..b4c4f505 100644 --- a/db/util/activitypub.go +++ b/db/util/activitypub.go @@ -13,6 +13,7 @@ import ( "net/http" "net/url" "os" + "path" "strconv" "strings" "time" @@ -162,7 +163,19 @@ func TrailFromActivity(activity pub.Activity, app core.App, actor *core.Record) return nil, err } - record, err := app.FindFirstRecordByData("trails", "iri", t.ID.String()) + iri := t.ID.String() + var record *core.Record + if actor.GetBool(("isLocal")) { + trailUrl, parseErr := url.Parse(iri) + if parseErr != nil { + return nil, parseErr + } + trailId := path.Base(trailUrl.Path) + record, err = app.FindRecordById("trails", trailId) + } else { + record, err = app.FindFirstRecordByData("trails", "iri", iri) + } + if err != nil { if err == sql.ErrNoRows { collection, err := app.FindCollectionByNameOrId("trails") @@ -279,7 +292,7 @@ func TrailFromActivity(activity pub.Activity, app core.App, actor *core.Record) } if len(photoURLs) > 0 { - photos := make([]*filesystem.File, len(photoURLs)) + photos := []*filesystem.File{} for i, purl := range photoURLs { photo, err := filesystem.NewFileFromURL(context.Background(), purl) if err != nil { From 5174663104090bcd5971fac9675cb10d32e3d415 Mon Sep 17 00:00:00 2001 From: Christian Beutel <> Date: Sun, 7 Sep 2025 16:32:28 +0200 Subject: [PATCH 06/15] fixes link to new trail on empty profiles --- web/src/routes/profile/[handle]/+page.svelte | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/routes/profile/[handle]/+page.svelte b/web/src/routes/profile/[handle]/+page.svelte index 63e9887b..8f340cdc 100644 --- a/web/src/routes/profile/[handle]/+page.svelte +++ b/web/src/routes/profile/[handle]/+page.svelte @@ -136,7 +136,7 @@

Timeline

{#if !feed.items?.length && data.isOwnProfile} - + {$_("new-trail")} {:else if !feed.items?.length} From 8cc70292437671ac57ecb510e83502d32abb4c2d Mon Sep 17 00:00:00 2001 From: slothful-vassal <89943360+slothful-vassal@users.noreply.github.com> Date: Sun, 7 Sep 2025 16:34:25 +0200 Subject: [PATCH 07/15] fix opening trail from map search (#562) --- .gitignore | 2 +- web/src/routes/map/+page.svelte | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index 61487c08..5c5302b5 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,6 @@ search/dumps run.sh build*.sh -start.* +start*.* data*/ diff --git a/web/src/routes/map/+page.svelte b/web/src/routes/map/+page.svelte index 9b0e2b36..de73ddc7 100644 --- a/web/src/routes/map/+page.svelte +++ b/web/src/routes/map/+page.svelte @@ -101,7 +101,7 @@ const trailItems = r[0].hits.map((t: TrailSearchResult) => ({ text: t.name, description: `Trail ${t.location.length ? ", " + t.location : ""}`, - value: `@${t.author}${t.domain ? `@${t.domain}` : ""}/${t.id}`, + value: `@${t.author_name}${t.domain ? `@${t.domain}` : ""}/${t.id}`, icon: "route", })); const listItems = r[1].hits.map((t: ListSearchResult) => ({ From 3b7f30908cc171d7123010b0b122f9098adb291e Mon Sep 17 00:00:00 2001 From: Christian Beutel <> Date: Sun, 7 Sep 2025 17:59:35 +0200 Subject: [PATCH 08/15] fixes sorting by difficulty --- db/util/meilisearch.go | 15 ++++++++++++++- .../trail/trail_filter_panel.svelte | 6 +++--- web/src/lib/models/trail.ts | 4 ++-- web/src/lib/stores/trail_store.ts | 2 +- web/src/routes/map/+page.ts | 2 +- .../routes/profile/[handle]/trails/+page.ts | 2 +- web/src/routes/trails/+page.svelte | 19 ++++++++++++++++++- web/src/routes/trails/+page.ts | 2 +- 8 files changed, 41 insertions(+), 11 deletions(-) diff --git a/db/util/meilisearch.go b/db/util/meilisearch.go index a986909d..98a122a0 100644 --- a/db/util/meilisearch.go +++ b/db/util/meilisearch.go @@ -69,7 +69,7 @@ func documentFromTrailRecord(app core.App, r *core.Record, author *core.Record, "elevation_gain": r.GetFloat("elevation_gain"), "elevation_loss": r.GetFloat("elevation_loss"), "duration": r.GetFloat("duration"), - "difficulty": r.Get("difficulty"), + "difficulty": difficultyToNumber(r.GetString("difficulty")), "category": category, "completed": logCount > 0, "date": r.GetDateTime("date").Time().Unix(), @@ -97,6 +97,19 @@ func documentFromTrailRecord(app core.App, r *core.Record, author *core.Record, return document, nil } +func difficultyToNumber(difficulty string) int32 { + switch difficulty { + case "easy": + return 0 + case "moderate": + return 1 + case "difficult": + return 2 + } + + return 0 +} + func getPolyline(app core.App, r *core.Record) (string, error) { gpxPath := r.GetString("gpx") if len(gpxPath) == 0 { diff --git a/web/src/lib/components/trail/trail_filter_panel.svelte b/web/src/lib/components/trail/trail_filter_panel.svelte index 446bfc35..7979edfd 100644 --- a/web/src/lib/components/trail/trail_filter_panel.svelte +++ b/web/src/lib/components/trail/trail_filter_panel.svelte @@ -51,9 +51,9 @@ ]; const difficultyItems: SelectItem[] = [ - { text: $_("easy"), value: "easy" }, - { text: $_("moderate"), value: "moderate" }, - { text: $_("difficult"), value: "difficult" }, + { text: $_("easy"), value: 0 }, + { text: $_("moderate"), value: 1 }, + { text: $_("difficult"), value: 2 }, ]; let searchDropdownItems: SearchItem[] = $state([]); diff --git a/web/src/lib/models/trail.ts b/web/src/lib/models/trail.ts index 566c861b..e5946d93 100644 --- a/web/src/lib/models/trail.ts +++ b/web/src/lib/models/trail.ts @@ -111,7 +111,7 @@ interface TrailFilter { q: string, category: string[], tags: string[], - difficulty: ("easy" | "moderate" | "difficult")[] + difficulty: (0 | 1 | 2)[] author?: string; public?: boolean; shared?: boolean; @@ -169,7 +169,7 @@ interface TrailSearchResult { elevation_gain: number; elevation_loss: number; duration: number; - difficulty: "easy" | "moderate" | "difficult"; + difficulty: 0 | 1 | 2; category: string; completed: boolean; date: number; diff --git a/web/src/lib/stores/trail_store.ts b/web/src/lib/stores/trail_store.ts index 2f60d5f7..c6f90a37 100644 --- a/web/src/lib/stores/trail_store.ts +++ b/web/src/lib/stores/trail_store.ts @@ -457,7 +457,7 @@ export async function searchResultToTrailList(hits: Hits): Pr created: new Date(h.created * 1000).toISOString(), date: new Date(h.date * 1000).toISOString(), description: h.description, - difficulty: h.difficulty, + difficulty: h.difficulty == 0 ? "easy" : h.difficulty == 1 ? "moderate" : "difficult", distance: h.distance, duration: h.duration, elevation_gain: h.elevation_gain, diff --git a/web/src/routes/map/+page.ts b/web/src/routes/map/+page.ts index a5b5c7e4..aa53828a 100644 --- a/web/src/routes/map/+page.ts +++ b/web/src/routes/map/+page.ts @@ -11,7 +11,7 @@ export const load: ServerLoad = async ({ params, locals, fetch }) => { q: "", category: [], tags: [], - difficulty: ["easy", "moderate", "difficult"], + difficulty: [0, 1, 2], author: "", public: true, shared: true, diff --git a/web/src/routes/profile/[handle]/trails/+page.ts b/web/src/routes/profile/[handle]/trails/+page.ts index fead8afe..07adeb7a 100644 --- a/web/src/routes/profile/[handle]/trails/+page.ts +++ b/web/src/routes/profile/[handle]/trails/+page.ts @@ -12,7 +12,7 @@ export const load: Load = async ({ params, fetch, parent }) => { q: "", category: [], tags: [], - difficulty: ["easy", "moderate", "difficult"], + difficulty: [0, 1, 2], author: actor.id, public: true, shared: true, diff --git a/web/src/routes/trails/+page.svelte b/web/src/routes/trails/+page.svelte index feef0328..09bd7e55 100644 --- a/web/src/routes/trails/+page.svelte +++ b/web/src/routes/trails/+page.svelte @@ -25,7 +25,24 @@ export const snapshot: Snapshot = { capture: () => filter, restore: (value) => { - filter = value; + const difficultyMap: Record = { + easy: 0, + moderate: 1, + difficult: 2, + }; + // defensive copy + const migrated = { ...value }; + + if (Array.isArray(migrated.difficulty)) { + migrated.difficulty = migrated.difficulty.map((d: any) => { + if (typeof d === "string" && d in difficultyMap) { + return difficultyMap[d]; + } + return d; + }); + } + + filter = migrated; handleFilterUpdate(); }, }; diff --git a/web/src/routes/trails/+page.ts b/web/src/routes/trails/+page.ts index e9f8e12c..97e1d269 100644 --- a/web/src/routes/trails/+page.ts +++ b/web/src/routes/trails/+page.ts @@ -10,7 +10,7 @@ export const load: ServerLoad = async ({ params, locals, url, fetch }) => { q: "", category: [], tags: [], - difficulty: ["easy", "moderate", "difficult"], + difficulty: [0, 1, 2], author: "", public: true, shared: true, From 9c628c6511b0c1a7ba030f2a2f76bdea7c51f357 Mon Sep 17 00:00:00 2001 From: Christian Beutel <> Date: Fri, 12 Sep 2025 16:45:50 +0200 Subject: [PATCH 09/15] cache public meilisearch key --- db/main.go | 200 +++++++++++++++++++++++++++------------- web/src/hooks.server.ts | 9 +- 2 files changed, 143 insertions(+), 66 deletions(-) diff --git a/db/main.go b/db/main.go index 2245b938..6758ac50 100644 --- a/db/main.go +++ b/db/main.go @@ -1308,94 +1308,166 @@ func bootstrapCategories(app core.App) error { } func bootstrapMeilisearchDocuments(app core.App, client meilisearch.ServiceManager) error { - query := app.RecordQuery("trails") - trails := []*core.Record{} + // --- Trails --- + const pageSize = 100 + var page int64 = 0 - if err := query.All(&trails); err != nil { + // Clear index before re-indexing + if _, err := client.Index("trails").DeleteAllDocuments(); err != nil { return err } - _, err := client.Index("trails").DeleteAllDocuments() - if err != nil { - return err - } - for _, trail := range trails { - author, err := app.FindRecordById("activitypub_actors", trail.GetString(("author"))) + for { + trails := []*core.Record{} + err := app.RecordQuery("trails"). + Limit(pageSize). + Offset(page * int64(pageSize)). + All(&trails) if err != nil { return err } - if err := util.IndexTrail(app, trail, author, client); err != nil { - app.Logger().Warn(fmt.Sprintf("Unable to index trail '%s': %v", trail.GetString("name"), err)) - continue + if len(trails) == 0 { + break } - shares, err := app.FindAllRecords("trail_share", - dbx.NewExp("trail = {:trailId}", dbx.Params{"trailId": trail.Id}), + // Collect author IDs to fetch in batch + authorIDs := make([]string, 0, len(trails)) + for _, t := range trails { + if id := t.GetString("author"); id != "" { + authorIDs = append(authorIDs, id) + } + } + + // Fetch all authors in one go + authorArgs := make([]interface{}, len(authorIDs)) + for i, v := range authorIDs { + authorArgs[i] = v + } + + authors, err := app.FindAllRecords("activitypub_actors", + dbx.In("id", authorArgs...), ) if err != nil { return err } - actorIds := make([]string, len(shares)) - for i, r := range shares { - actorIds[i] = r.GetString("actor") + authorMap := make(map[string]*core.Record, len(authors)) + for _, a := range authors { + authorMap[a.Id] = a } - err = util.UpdateTrailShares(trail.Id, actorIds, client) + + for _, trail := range trails { + author := authorMap[trail.GetString("author")] + + if err := util.IndexTrail(app, trail, author, client); err != nil { + app.Logger().Warn(fmt.Sprintf("Unable to index trail '%s': %v", trail.GetString("name"), err)) + continue + } + + // Shares + shares, err := app.FindAllRecords("trail_share", + dbx.NewExp("trail = {:trailId}", dbx.Params{"trailId": trail.Id}), + ) + if err != nil { + app.Logger().Warn(fmt.Sprintf("Unable to fetch shares for trail '%s': %v", trail.GetString("name"), err)) + continue + } + shareActors := make([]string, len(shares)) + for i, r := range shares { + shareActors[i] = r.GetString("actor") + } + if err := util.UpdateTrailShares(trail.Id, shareActors, client); err != nil { + app.Logger().Warn(fmt.Sprintf("Unable to update trail shares '%s': %v", trail.GetString("name"), err)) + } + + // Likes + likes, err := app.FindAllRecords("trail_like", + dbx.NewExp("trail = {:trailId}", dbx.Params{"trailId": trail.Id}), + ) + if err != nil { + app.Logger().Warn(fmt.Sprintf("Unable to fetch likes for trail '%s': %v", trail.GetString("name"), err)) + continue + } + likeActors := make([]string, len(likes)) + for i, r := range likes { + likeActors[i] = r.GetString("actor") + } + if err := util.UpdateTrailLikes(trail.Id, likeActors, client); err != nil { + app.Logger().Warn(fmt.Sprintf("Unable to update trail likes '%s': %v", trail.GetString("name"), err)) + } + } + + page++ + } + + // --- Lists --- + if _, err := client.Index("lists").DeleteAllDocuments(); err != nil { + return err + } + + page = 0 + for { + lists := []*core.Record{} + err := app.RecordQuery("lists"). + Limit(pageSize). + Offset(page * pageSize). + All(&lists) if err != nil { - app.Logger().Warn(fmt.Sprintf("Unable to update trail shares '%s': %v", trail.GetString("name"), err)) - continue + return err } - likes, err := app.FindAllRecords("trail_like", - dbx.NewExp("trail = {:trailId}", dbx.Params{"trailId": trail.Id}), + if len(lists) == 0 { + break + } + + // Collect author IDs in batch + authorIDs := make([]string, 0, len(lists)) + for _, l := range lists { + if id := l.GetString("author"); id != "" { + authorIDs = append(authorIDs, id) + } + } + // Fetch all authors in one go + authorArgs := make([]interface{}, len(authorIDs)) + for i, v := range authorIDs { + authorArgs[i] = v + } + + authors, err := app.FindAllRecords("activitypub_actors", + dbx.In("id", authorArgs...), ) if err != nil { return err } - actorIds = make([]string, len(likes)) - for i, r := range likes { - actorIds[i] = r.GetString("actor") - } - err = util.UpdateTrailLikes(trail.Id, actorIds, client) - if err != nil { - app.Logger().Warn(fmt.Sprintf("Unable to update trail likes '%s': %v", trail.GetString("name"), err)) - continue - } - } - - lists, err := app.FindAllRecords("lists") - if err != nil { - return err - } - _, err = client.Index("lists").DeleteAllDocuments() - if err != nil { - return err - } - - for _, list := range lists { - author, err := app.FindRecordById("activitypub_actors", list.GetString(("author"))) - if err != nil { - return err - } - if err := util.IndexList(app, list, author, client); err != nil { - app.Logger().Warn(fmt.Sprintf("Unable to index list '%s': %v", list.GetString("name"), err)) - continue + authorMap := make(map[string]*core.Record, len(authors)) + for _, a := range authors { + authorMap[a.Id] = a } - shares, err := app.FindAllRecords("list_share", - dbx.NewExp("list = {:listId}", dbx.Params{"listId": list.Id}), - ) - if err != nil { - return err - } - actorIds := make([]string, len(shares)) - for i, r := range shares { - actorIds[i] = r.GetString("actor") - } - err = util.UpdateListShares(list.Id, actorIds, client) + for _, list := range lists { + author := authorMap[list.GetString("author")] - if err != nil { - app.Logger().Warn(fmt.Sprintf("Unable to update list shares '%s': %v", list.GetString("name"), err)) - continue + if err := util.IndexList(app, list, author, client); err != nil { + app.Logger().Warn(fmt.Sprintf("Unable to index list '%s': %v", list.GetString("name"), err)) + continue + } + + shares, err := app.FindAllRecords("list_share", + dbx.NewExp("list = {:listId}", dbx.Params{"listId": list.Id}), + ) + if err != nil { + app.Logger().Warn(fmt.Sprintf("Unable to fetch list shares '%s': %v", list.GetString("name"), err)) + continue + } + shareActors := make([]string, len(shares)) + for i, r := range shares { + shareActors[i] = r.GetString("actor") + } + if err := util.UpdateListShares(list.Id, shareActors, client); err != nil { + app.Logger().Warn(fmt.Sprintf("Unable to update list shares '%s': %v", list.GetString("name"), err)) + } } + + page++ } + return nil } diff --git a/web/src/hooks.server.ts b/web/src/hooks.server.ts index d7cf6c8c..32fdb41a 100644 --- a/web/src/hooks.server.ts +++ b/web/src/hooks.server.ts @@ -48,6 +48,7 @@ function isFormContentType(request: Request) { ); } +let publicMeilisearchKey: string | undefined = undefined; const auth: Handle = async ({ event, resolve }) => { const pb = new PocketBase(envPub.PUBLIC_POCKETBASE_URL) @@ -84,8 +85,12 @@ const auth: Handle = async ({ event, resolve }) => { settings = await pb.collection('settings').getFirstListItem(`user="${pb.authStore.record.id}"`, { requestKey: null }) actor = await pb.collection("activitypub_actors").getFirstListItem(`user='${pb.authStore.record.id}'`) } else { - const response = await pb.send("/public/search/token", { method: "GET", fetch: event.fetch }); - meiliApiKey = response.token; + if (!publicMeilisearchKey) { + const response = await pb.send("/public/search/token", { method: "GET", fetch: event.fetch }); + publicMeilisearchKey = response.token; + } + + meiliApiKey = publicMeilisearchKey!; } const ms = new MeiliSearch({ host: env.MEILI_URL, apiKey: meiliApiKey }); From 73d20b1cbcc4c3f49fd1ee9e04ff237c47992057 Mon Sep 17 00:00:00 2001 From: Christian Beutel <> Date: Fri, 12 Sep 2025 17:48:41 +0200 Subject: [PATCH 10/15] reverses waypoint relation --- db/integrations/komoot/komoot.go | 46 +++++++------- db/integrations/strava/strava.go | 39 ++++++------ db/migrations/1757689107_updated_waypoints.go | 62 +++++++++++++++++++ .../1757689185_migrate_waypoint_trails.go | 34 ++++++++++ db/migrations/1757690946_updated_trails.go | 44 +++++++++++++ .../components/trail/trail_info_panel.svelte | 2 +- .../components/trail/trail_timeline.svelte | 2 +- web/src/lib/models/api/trail_schema.ts | 2 - web/src/lib/models/api/waypoint_schema.ts | 13 ++-- web/src/lib/models/trail.ts | 6 +- web/src/lib/models/waypoint.ts | 4 +- web/src/lib/stores/trail_store.ts | 45 +++++--------- web/src/lib/util/gpx_util.ts | 4 +- web/src/routes/api/v1/trail/+server.ts | 2 +- web/src/routes/api/v1/trail/[id]/+server.ts | 4 +- .../lists/[[handle]]/[[id]]/+page.svelte | 2 +- .../map/trail/[handle]/[id]/+page.svelte | 2 +- .../trail/[handle]/[id]/print/+page.svelte | 6 +- web/src/routes/trail/edit/[id]/+page.svelte | 34 +++++----- 19 files changed, 238 insertions(+), 115 deletions(-) create mode 100644 db/migrations/1757689107_updated_waypoints.go create mode 100644 db/migrations/1757689185_migrate_waypoint_trails.go create mode 100644 db/migrations/1757690946_updated_trails.go diff --git a/db/integrations/komoot/komoot.go b/db/integrations/komoot/komoot.go index a2041d5a..51b62ee8 100644 --- a/db/integrations/komoot/komoot.go +++ b/db/integrations/komoot/komoot.go @@ -205,27 +205,27 @@ 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 } - wpIds, err := createWaypointsFromTour(app, detailedTour, user) - if err != nil { - app.Logger().Warn(fmt.Sprintf("Unable to create waypoints for tour '%s': %v", tour.Name, err)) - continue - } - err = createTrailFromTour(app, k, detailedTour, gpx, actor, wpIds) + trailid, err := createTrailFromTour(app, k, detailedTour, gpx, actor) if err != nil { app.Logger().Warn(fmt.Sprintf("Unable to create trail for tour '%s': %v", tour.Name, err)) continue } + err = createWaypointsFromTour(app, detailedTour, user, trailid) + if err != nil { + app.Logger().Warn(fmt.Sprintf("Unable to create waypoints for tour '%s': %v", tour.Name, err)) + continue + } } return hasNewTours, nil } -func createTrailFromTour(app core.App, k *KomootApi, detailedTour *DetailedKomootTour, gpx *filesystem.File, actor string, wpIds []string) error { +func createTrailFromTour(app core.App, k *KomootApi, detailedTour *DetailedKomootTour, gpx *filesystem.File, actor string) (string, error) { trailid := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet) collection, err := app.FindCollectionByNameOrId("trails") if err != nil { - return err + return "", err } record := core.NewRecord(collection) @@ -251,12 +251,12 @@ func createTrailFromTour(app core.App, k *KomootApi, detailedTour *DetailedKomoo if len(detailedTour.Embedded.CoverImages.Embedded.Items) > 0 { photos, err = fetchRoutePhotos(k, detailedTour) if err != nil { - return err + return "", err } } else { photo, err := fetchPhoto(detailedTour.MapImage.Src, "", "") if err != nil { - return err + return "", err } photos = append(photos, photo) } @@ -281,7 +281,6 @@ func createTrailFromTour(app core.App, k *KomootApi, detailedTour *DetailedKomoo "lon": detailedTour.StartPoint.Lng, "difficulty": diffculty, "category": categoryId, - "waypoints": wpIds, "author": actor, }) @@ -293,13 +292,13 @@ func createTrailFromTour(app core.App, k *KomootApi, detailedTour *DetailedKomoo } if err := app.Save(record); err != nil { - return err + return "", err } if detailedTour.Type == "tour_recorded" { collection, err := app.FindCollectionByNameOrId("summit_logs") if err != nil { - return err + return "", err } summitLogRecord := core.NewRecord(collection) @@ -313,25 +312,23 @@ func createTrailFromTour(app core.App, k *KomootApi, detailedTour *DetailedKomoo "trail": trailid, }) if err := app.Save(summitLogRecord); err != nil { - return err + return "", err } } - return nil + return trailid, nil } -func createWaypointsFromTour(app core.App, tour *DetailedKomootTour, user string) ([]string, error) { +func createWaypointsFromTour(app core.App, tour *DetailedKomootTour, user string, trailid string) error { collection, err := app.FindCollectionByNameOrId("waypoints") if err != nil { - return nil, err + return err } - wpIds := make([]string, len(tour.Embedded.Timeline.Embedded.Items)) - - for i, wp := range tour.Embedded.Timeline.Embedded.Items { + for _, wp := range tour.Embedded.Timeline.Embedded.Items { photos, err := fetchWaypointPhotos(wp) if err != nil { - return nil, err + return err } record := core.NewRecord(collection) @@ -358,6 +355,7 @@ func createWaypointsFromTour(app core.App, tour *DetailedKomootTour, user string "icon": "circle", "author": user, "distance_from_start": 0, + "trail": trailid, }) if photos != nil { @@ -365,13 +363,11 @@ func createWaypointsFromTour(app core.App, tour *DetailedKomootTour, user string } if err := app.Save(record); err != nil { - return nil, err + return err } - - wpIds[i] = record.Id } - return wpIds, nil + return nil } func fetchRoutePhotos(k *KomootApi, tour *DetailedKomootTour) ([]*filesystem.File, error) { diff --git a/db/integrations/strava/strava.go b/db/integrations/strava/strava.go index e291ea9d..974af4f0 100644 --- a/db/integrations/strava/strava.go +++ b/db/integrations/strava/strava.go @@ -251,17 +251,16 @@ 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 } - wpIds, err := createWaypointsFromRoute(app, route, user) - if err != nil { - app.Logger().Warn(fmt.Sprintf("Unable to create waypoints for route '%s': %v", route.Name, err)) - continue - } - err = createTrailFromRoute(app, route, gpx, actor, wpIds) + trailid, err := createTrailFromRoute(app, route, gpx, actor) if err != nil { app.Logger().Warn(fmt.Sprintf("Unable to create trail for route '%s': %v", route.Name, err)) continue } - + err = createWaypointsFromRoute(app, route, user, trailid) + if err != nil { + app.Logger().Warn(fmt.Sprintf("Unable to create waypoints for route '%s': %v", route.Name, err)) + continue + } } return hasNewRoutes, nil @@ -305,10 +304,12 @@ func fetchRouteGPX(route StravaRoute, accessToken string) (*filesystem.File, err return gpxFile, nil } -func createTrailFromRoute(app core.App, route StravaRoute, gpx *filesystem.File, actor string, wpIds []string) error { +func createTrailFromRoute(app core.App, route StravaRoute, gpx *filesystem.File, actor string) (string, error) { + trailid := security.RandomStringWithAlphabet(core.DefaultIdLength, core.DefaultIdAlphabet) + collection, err := app.FindCollectionByNameOrId("trails") if err != nil { - return err + return "", err } record := core.NewRecord(collection) @@ -337,6 +338,7 @@ func createTrailFromRoute(app core.App, route StravaRoute, gpx *filesystem.File, } record.Load(map[string]any{ + "id": trailid, "name": route.Name, "description": route.Description, "public": !route.Private, @@ -348,7 +350,6 @@ func createTrailFromRoute(app core.App, route StravaRoute, gpx *filesystem.File, "external_id": route.IDStr, "lat": lat, "lon": lon, - "waypoints": wpIds, "difficulty": "easy", "category": category, "author": actor, @@ -359,20 +360,18 @@ func createTrailFromRoute(app core.App, route StravaRoute, gpx *filesystem.File, } if err := app.Save(record); err != nil { - return err + return "", err } - return nil + return trailid, err } -func createWaypointsFromRoute(app core.App, route StravaRoute, user string) ([]string, error) { +func createWaypointsFromRoute(app core.App, route StravaRoute, user string, trailid string) error { collection, err := app.FindCollectionByNameOrId("waypoints") if err != nil { - return nil, err + return err } - wpIds := make([]string, len(route.Waypoints)) - for i, wp := range route.Waypoints { record := core.NewRecord(collection) @@ -383,13 +382,15 @@ func createWaypointsFromRoute(app core.App, route StravaRoute, user string) ([]s record.Set("icon", "circle") record.Set("author", user) record.Set("distance_from_start", wp.DistanceIntoRoute) + record.Set("trail", trailid) - app.Save(record) + if err := app.Save(record); err != nil { + return err + } - wpIds[i] = record.Id } - return wpIds, nil + return nil } func syncTrailsWithActivities(app core.App, accessToken string, user string, actor string, activities []StravaActivity) (bool, error) { diff --git a/db/migrations/1757689107_updated_waypoints.go b/db/migrations/1757689107_updated_waypoints.go new file mode 100644 index 00000000..14e9e99c --- /dev/null +++ b/db/migrations/1757689107_updated_waypoints.go @@ -0,0 +1,62 @@ +package migrations + +import ( + "encoding/json" + + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("goeo2ubp103rzp9") + if err != nil { + return err + } + + // update collection data + if err := json.Unmarshal([]byte(`{ + "listRule": "author = @request.auth.id || trail.author.user ?= @request.auth.id || trail.public ?= true || trail.trail_share_via_trail.actor.user ?= @request.auth.id\n|| \n(trail.trail_link_share_via_trail.token != \"\" && trail.trail_link_share_via_trail.token = @request.query.share)", + "viewRule": "author = @request.auth.id || trail.author.user ?= @request.auth.id || trail.public ?= true || trail.trail_share_via_trail.actor.user ?= @request.auth.id\n|| \n(trail.trail_link_share_via_trail.token != \"\" && trail.trail_link_share_via_trail.token = @request.query.share)" + }`), &collection); err != nil { + return err + } + + // add field + if err := collection.Fields.AddMarshaledJSONAt(9, []byte(`{ + "cascadeDelete": true, + "collectionId": "e864strfxo14pm4", + "hidden": false, + "id": "relation2993194383", + "maxSelect": 1, + "minSelect": 0, + "name": "trail", + "presentable": false, + "required": true, + "system": false, + "type": "relation" + }`)); err != nil { + return err + } + + return app.Save(collection) + }, func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("goeo2ubp103rzp9") + if err != nil { + return err + } + + // update collection data + if err := json.Unmarshal([]byte(`{ + "listRule": "author = @request.auth.id || trails_via_waypoints.author.user ?= @request.auth.id || trails_via_waypoints.public ?= true || \n(@collection.trail_share.trail.id ?= trails_via_waypoints.id && @collection.trail_share.actor.user ?= @request.auth.id)\n|| \n(@collection.trail_link_share.token != \"\" && @collection.trail_link_share.trail.waypoints.id ?= id)", + "viewRule": "author = @request.auth.id || trails_via_waypoints.author.user ?= @request.auth.id || trails_via_waypoints.public ?= true || \n(@collection.trail_share.trail.id ?= trails_via_waypoints.id && @collection.trail_share.actor.user ?= @request.auth.id)\n|| \n(@collection.trail_link_share.token != \"\" && @collection.trail_link_share.trail.waypoints.id ?= id)" + }`), &collection); err != nil { + return err + } + + // remove field + collection.Fields.RemoveById("relation2993194383") + + return app.Save(collection) + }) +} diff --git a/db/migrations/1757689185_migrate_waypoint_trails.go b/db/migrations/1757689185_migrate_waypoint_trails.go new file mode 100644 index 00000000..e92d2d1a --- /dev/null +++ b/db/migrations/1757689185_migrate_waypoint_trails.go @@ -0,0 +1,34 @@ +package migrations + +import ( + "github.com/pocketbase/dbx" + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + wps, err := app.FindAllRecords("waypoints") + if err != nil { + return err + } + + for _, wp := range wps { + trail, err := app.FindFirstRecordByFilter("trails", "waypoints ?~ {:id}", dbx.Params{"id": wp.Id}) + if err != nil { + continue + } + wp.Set("trail", trail.Id) + err = app.UnsafeWithoutHooks().Save(wp) + if err != nil { + return err + } + } + + return nil + }, func(app core.App) error { + // add down queries... + + return nil + }) +} diff --git a/db/migrations/1757690946_updated_trails.go b/db/migrations/1757690946_updated_trails.go new file mode 100644 index 00000000..d0cdeea7 --- /dev/null +++ b/db/migrations/1757690946_updated_trails.go @@ -0,0 +1,44 @@ +package migrations + +import ( + "github.com/pocketbase/pocketbase/core" + m "github.com/pocketbase/pocketbase/migrations" +) + +func init() { + m.Register(func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4") + if err != nil { + return err + } + + // remove field + collection.Fields.RemoveById("ppq2sist") + + return app.Save(collection) + }, func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("e864strfxo14pm4") + if err != nil { + return err + } + + // add field + if err := collection.Fields.AddMarshaledJSONAt(16, []byte(`{ + "cascadeDelete": false, + "collectionId": "goeo2ubp103rzp9", + "hidden": false, + "id": "ppq2sist", + "maxSelect": 2147483647, + "minSelect": 0, + "name": "waypoints", + "presentable": false, + "required": false, + "system": false, + "type": "relation" + }`)); err != nil { + return err + } + + return app.Save(collection) + }) +} diff --git a/web/src/lib/components/trail/trail_info_panel.svelte b/web/src/lib/components/trail/trail_info_panel.svelte index 47ebdb4c..d674441b 100644 --- a/web/src/lib/components/trail/trail_info_panel.svelte +++ b/web/src/lib/components/trail/trail_info_panel.svelte @@ -658,7 +658,7 @@

{$_("start")}

- {#each trail.expand?.waypoints ?? [] as wp, i} + {#each trail.expand?.waypoints_via_trail ?? [] as wp, i}