From 1e22c78c22c2d60d89f1e51b50391b73ebd4cb98 Mon Sep 17 00:00:00 2001 From: Christian Beutel <> Date: Wed, 20 Aug 2025 16:51:33 +0200 Subject: [PATCH] adds trail link sharing --- .../1755695750_created_trail_link_share.go | 116 ++++++++++++++++++ .../1755696870_updated_trail_link_share.go | 42 +++++++ .../1755697071_updated_trail_link_share.go | 48 ++++++++ db/migrations/1755697879_updated_trails.go | 42 +++++++ db/migrations/1755700187_updated_waypoints.go | 42 +++++++ .../components/trail/trail_dropdown.svelte | 17 ++- .../components/trail/trail_info_panel.svelte | 24 ++-- .../components/trail/trail_share_modal.svelte | 54 +++++++- web/src/lib/i18n/locales/de.json | 5 + web/src/lib/i18n/locales/en.json | 5 + web/src/lib/i18n/locales/es.json | 5 + web/src/lib/i18n/locales/eu.json | 5 + web/src/lib/i18n/locales/fr.json | 5 + web/src/lib/i18n/locales/hu.json | 5 + web/src/lib/i18n/locales/it.json | 5 + web/src/lib/i18n/locales/nl.json | 5 + web/src/lib/i18n/locales/pl.json | 5 + web/src/lib/i18n/locales/pt.json | 5 + web/src/lib/i18n/locales/ru.json | 5 + web/src/lib/i18n/locales/zh.json | 5 + web/src/lib/models/api/base_schema.ts | 1 + .../lib/models/api/trail_link_share_schema.ts | 16 +++ web/src/lib/models/trail_link_share.ts | 13 ++ web/src/lib/stores/trail_link_share_store.ts | 66 ++++++++++ web/src/lib/stores/trail_store.ts | 6 +- web/src/lib/util/api_util.ts | 1 + web/src/lib/util/gpx_util.ts | 2 +- .../routes/api/v1/trail-link-share/+server.ts | 26 ++++ .../api/v1/trail-link-share/[id]/+server.ts | 31 +++++ web/src/routes/api/v1/trail/[id]/+server.ts | 4 +- .../lists/[[handle]]/[[id]]/+page.svelte | 4 +- web/src/routes/lists/edit/[id]/+page.svelte | 2 +- .../routes/map/trail/[handle]/[id]/+page.ts | 5 +- .../map/trail/[handle]/[id]/print/+page.ts | 4 +- web/src/routes/trail/edit/[id]/+page.ts | 4 +- .../routes/trail/view/[handle]/[id]/+page.ts | 4 +- 36 files changed, 598 insertions(+), 36 deletions(-) create mode 100644 db/migrations/1755695750_created_trail_link_share.go create mode 100644 db/migrations/1755696870_updated_trail_link_share.go create mode 100644 db/migrations/1755697071_updated_trail_link_share.go create mode 100644 db/migrations/1755697879_updated_trails.go create mode 100644 db/migrations/1755700187_updated_waypoints.go create mode 100644 web/src/lib/models/api/trail_link_share_schema.ts create mode 100644 web/src/lib/models/trail_link_share.ts create mode 100644 web/src/lib/stores/trail_link_share_store.ts create mode 100644 web/src/routes/api/v1/trail-link-share/+server.ts create mode 100644 web/src/routes/api/v1/trail-link-share/[id]/+server.ts diff --git a/db/migrations/1755695750_created_trail_link_share.go b/db/migrations/1755695750_created_trail_link_share.go new file mode 100644 index 00000000..3068f595 --- /dev/null +++ b/db/migrations/1755695750_created_trail_link_share.go @@ -0,0 +1,116 @@ +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 { + jsonData := `{ + "createRule": null, + "deleteRule": null, + "fields": [ + { + "autogeneratePattern": "[a-z0-9]{15}", + "hidden": false, + "id": "text3208210256", + "max": 15, + "min": 15, + "name": "id", + "pattern": "^[a-z0-9]+$", + "presentable": false, + "primaryKey": true, + "required": true, + "system": true, + "type": "text" + }, + { + "cascadeDelete": true, + "collectionId": "e864strfxo14pm4", + "hidden": false, + "id": "relation2993194383", + "maxSelect": 1, + "minSelect": 0, + "name": "trail", + "presentable": false, + "required": true, + "system": false, + "type": "relation" + }, + { + "autogeneratePattern": "[a-z0-9]{32}", + "hidden": false, + "id": "text1597481275", + "max": 32, + "min": 32, + "name": "token", + "pattern": "", + "presentable": false, + "primaryKey": false, + "required": true, + "system": false, + "type": "text" + }, + { + "hidden": false, + "id": "select3762918058", + "maxSelect": 1, + "name": "permission", + "presentable": false, + "required": true, + "system": false, + "type": "select", + "values": [ + "view", + "edit" + ] + }, + { + "hidden": false, + "id": "autodate2990389176", + "name": "created", + "onCreate": true, + "onUpdate": false, + "presentable": false, + "system": false, + "type": "autodate" + }, + { + "hidden": false, + "id": "autodate3332085495", + "name": "updated", + "onCreate": true, + "onUpdate": true, + "presentable": false, + "system": false, + "type": "autodate" + } + ], + "id": "pbc_2865004035", + "indexes": [], + "listRule": null, + "name": "trail_link_share", + "system": false, + "type": "base", + "updateRule": null, + "viewRule": null + }` + + collection := &core.Collection{} + if err := json.Unmarshal([]byte(jsonData), &collection); err != nil { + return err + } + + return app.Save(collection) + }, func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("pbc_2865004035") + if err != nil { + return err + } + + return app.Delete(collection) + }) +} diff --git a/db/migrations/1755696870_updated_trail_link_share.go b/db/migrations/1755696870_updated_trail_link_share.go new file mode 100644 index 00000000..ac21f85f --- /dev/null +++ b/db/migrations/1755696870_updated_trail_link_share.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("pbc_2865004035") + if err != nil { + return err + } + + // update collection data + if err := json.Unmarshal([]byte(`{ + "indexes": [ + "CREATE UNIQUE INDEX ` + "`" + `idx_YbuWvsh5la` + "`" + ` ON ` + "`" + `trail_link_share` + "`" + ` (` + "`" + `trail` + "`" + `)" + ] + }`), &collection); err != nil { + return err + } + + return app.Save(collection) + }, func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("pbc_2865004035") + if err != nil { + return err + } + + // update collection data + if err := json.Unmarshal([]byte(`{ + "indexes": [] + }`), &collection); err != nil { + return err + } + + return app.Save(collection) + }) +} diff --git a/db/migrations/1755697071_updated_trail_link_share.go b/db/migrations/1755697071_updated_trail_link_share.go new file mode 100644 index 00000000..64f95b2a --- /dev/null +++ b/db/migrations/1755697071_updated_trail_link_share.go @@ -0,0 +1,48 @@ +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("pbc_2865004035") + if err != nil { + return err + } + + // update collection data + if err := json.Unmarshal([]byte(`{ + "createRule": "trail.author.user = @request.auth.id", + "deleteRule": "trail.author.user = @request.auth.id", + "listRule": "trail.author.user = @request.auth.id", + "updateRule": "trail.author.user = @request.auth.id", + "viewRule": "trail.author.user = @request.auth.id" + }`), &collection); err != nil { + return err + } + + return app.Save(collection) + }, func(app core.App) error { + collection, err := app.FindCollectionByNameOrId("pbc_2865004035") + if err != nil { + return err + } + + // update collection data + if err := json.Unmarshal([]byte(`{ + "createRule": null, + "deleteRule": null, + "listRule": null, + "updateRule": null, + "viewRule": null + }`), &collection); err != nil { + return err + } + + return app.Save(collection) + }) +} diff --git a/db/migrations/1755697879_updated_trails.go b/db/migrations/1755697879_updated_trails.go new file mode 100644 index 00000000..f9c599e7 --- /dev/null +++ b/db/migrations/1755697879_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 = @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) + }, 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)", + "viewRule": "author.user = @request.auth.id || public = true || (@request.auth.id != \"\" && trail_share_via_trail.actor.user ?= @request.auth.id)" + }`), &collection); err != nil { + return err + } + + return app.Save(collection) + }) +} diff --git a/db/migrations/1755700187_updated_waypoints.go b/db/migrations/1755700187_updated_waypoints.go new file mode 100644 index 00000000..113d4463 --- /dev/null +++ b/db/migrations/1755700187_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.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) + }, 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)", + "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)" + }`), &collection); err != nil { + return err + } + + return app.Save(collection) + }) +} diff --git a/web/src/lib/components/trail/trail_dropdown.svelte b/web/src/lib/components/trail/trail_dropdown.svelte index 9beac18d..bd887fd1 100644 --- a/web/src/lib/components/trail/trail_dropdown.svelte +++ b/web/src/lib/components/trail/trail_dropdown.svelte @@ -22,6 +22,7 @@ import TrailShareModal from "./trail_share_modal.svelte"; import { handleFromRecordWithIRI } from "$lib/util/activitypub_util"; import type { Snippet } from "svelte"; + import { page } from "$app/state"; interface Props { trails?: Set | undefined; @@ -44,6 +45,7 @@ return ( hasTrail() && !isMultiselectMode() && + Boolean($currentUser) && (trail()!.expand?.author?.id === $currentUser?.actor || trail()!.expand?.trail_share_via_trail?.some( (s) => s.permission == "edit", @@ -143,6 +145,9 @@ } function isFromCurrentUser(uTrail?: Trail): boolean { + if (!$currentUser) { + return false; + } if (uTrail !== undefined) { return uTrail.expand?.author?.id === $currentUser?.actor; } else if (trails !== undefined && trails.size > 0) { @@ -169,14 +174,16 @@ return; } - const handle = handleFromRecordWithIRI(trail()); + const handle = page.params.handle if (item.value == "show") { if (hasTrail()) { - goto( - mode == "overview" || mode == "multi-select" + const url = mode == "overview" || mode == "multi-select" ? `/map/trail/${handle}/${trailId()}` - : `/trail/view/${handle}/${trailId()}`, + : `/trail/view/${handle}/${trailId()}` + + goto( + url + '?' + page.url.searchParams ); } } else if (item.value == "list") { @@ -199,7 +206,7 @@ } } else if (item.value == "print") { if (hasTrail()) { - goto(`/map/trail/${handle}/${trailId()}/print`); + goto(`/map/trail/${handle}/${trailId()}/print?${page.url.searchParams}`); } } else if (item.value == "share") { trailShareModal.openModal(); diff --git a/web/src/lib/components/trail/trail_info_panel.svelte b/web/src/lib/components/trail/trail_info_panel.svelte index c2298d91..745c7f02 100644 --- a/web/src/lib/components/trail/trail_info_panel.svelte +++ b/web/src/lib/components/trail/trail_info_panel.svelte @@ -398,19 +398,17 @@ - {#if ($currentUser && $currentUser.actor == trail.author) || trail.expand?.trail_share_via_trail?.length || trail.public} -
+
+ {#if ($currentUser && $currentUser.actor == trail.author) || trail.expand?.trail_share_via_trail?.length || trail.public} - ([trail])} - onDelete={() => - history.length - ? history.back() - : goto("/trails")} - {mode} - > -
- {/if} + {/if} + ([trail])} + onDelete={() => + history.length ? history.back() : goto("/trails")} + {mode} + > +
- {#if activeTab == 0 && mode != "list"} + {#if $currentUser && activeTab == 0 && mode != "list"} - updateFilter()}> + updateFilter()} + >