diff --git a/db/federation/create.go b/db/federation/create.go
index a929da56..afeae539 100644
--- a/db/federation/create.go
+++ b/db/federation/create.go
@@ -466,13 +466,20 @@ func ProcessCreateOrUpdateActivity(app core.App, actor *core.Record, activity pu
}
func processCreateOrUpdateTrailActivity(activity pub.Activity, app core.App, actor *core.Record) error {
-
- // no need to do anything if the actor is local
- if actor.GetBool("isLocal") {
- return nil
+ trail, err := util.TrailFromActivity(activity, app, actor)
+ if err != nil {
+ return err
}
- trail, err := util.TrailFromActivity(activity, app, actor)
+ trailAuthor, err := app.FindRecordById("activitypub_actors", trail.GetString("author"))
+ if err != nil {
+ return err
+ }
+
+ _, err = util.InsertIntoFeed(app, actor.Id, trailAuthor.Id, trail.Id, util.TrailFeed)
+ if err != nil {
+ return err
+ }
trailObject, _ := pub.ToObject(activity.Object)
@@ -792,13 +799,20 @@ func processCreateOrUpdateSummitLogActivity(activity pub.Activity, app core.App,
}
func processCreateOrUpdateListActivity(activity pub.Activity, app core.App, actor *core.Record) error {
-
- // no need to do anything if the actor is local
- if actor.GetBool("isLocal") {
- return nil
+ list, err := util.ListFromActivity(activity, app, actor)
+ if err != nil {
+ return err
}
- _, err := util.ListFromActivity(activity, app, actor)
+ listAuthor, err := app.FindRecordById("activitypub_actors", list.GetString("author"))
+ if err != nil {
+ return err
+ }
+
+ _, err = util.InsertIntoFeed(app, actor.Id, listAuthor.Id, list.Id, util.ListFeed)
+ if err != nil {
+ return err
+ }
return err
}
diff --git a/db/main.go b/db/main.go
index bf7a6f52..3e021acb 100644
--- a/db/main.go
+++ b/db/main.go
@@ -120,6 +120,8 @@ func setupEventHandlers(app *pocketbase.PocketBase, client meilisearch.ServiceMa
app.OnRecordUpdate("integrations").BindFunc(updateIntegrationHandler())
app.OnRecordAfterUpdateSuccess("integrations").BindFunc(createUpdateIntegrationSuccessHandler())
+ app.OnRecordsListRequest("feed").BindFunc(listFeedHandler())
+
app.OnRecordCreateRequest().BindFunc(sanitizeHTML())
app.OnRecordUpdateRequest().BindFunc(sanitizeHTML())
@@ -242,6 +244,11 @@ func createTrailHandler(client meilisearch.ServiceManager) func(e *core.RecordEv
return err
}
+ _, err = util.InsertIntoFeed(e.App, author.Id, author.Id, record.Id, util.TrailFeed)
+ if err != nil {
+ return err
+ }
+
return nil
}
}
@@ -616,6 +623,11 @@ func createListHandler(client meilisearch.ServiceManager) func(e *core.RecordEve
return err
}
+ _, err = util.InsertIntoFeed(e.App, author.Id, author.Id, record.Id, util.ListFeed)
+ if err != nil {
+ return err
+ }
+
return nil
}
}
@@ -877,6 +889,31 @@ func changeUserEmailHandler() func(e *core.RecordRequestEmailChangeRequestEvent)
}
}
+func listFeedHandler() func(e *core.RecordsListRequestEvent) error {
+ return func(e *core.RecordsListRequestEvent) error {
+
+ for _, r := range e.Records {
+ var item *core.Record
+ var err error
+ switch r.GetString("type") {
+ case string(util.TrailFeed):
+ item, err = e.App.FindRecordById("trails", r.GetString("item"))
+ case string(util.ListFeed):
+ item, err = e.App.FindRecordById("lists", r.GetString("item"))
+ case string(util.SummitLogFeed):
+ item, err = e.App.FindRecordById("summit_logs", r.GetString("item"))
+ }
+
+ if err != nil {
+ return err
+ }
+
+ r.MergeExpand(map[string]any{"item": item})
+ }
+ return e.Next()
+ }
+}
+
func onBeforeServeHandler(client meilisearch.ServiceManager) func(se *core.ServeEvent) error {
return func(se *core.ServeEvent) error {
registerRoutes(se, client)
diff --git a/db/migrations/1752231390_created_feed.go b/db/migrations/1752231390_created_feed.go
new file mode 100644
index 00000000..6e18f65e
--- /dev/null
+++ b/db/migrations/1752231390_created_feed.go
@@ -0,0 +1,115 @@
+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": "pbc_1295301207",
+ "hidden": false,
+ "id": "relation1148540665",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "actor",
+ "presentable": false,
+ "required": true,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "cascadeDelete": true,
+ "collectionId": "pbc_1295301207",
+ "hidden": false,
+ "id": "relation3182418120",
+ "maxSelect": 1,
+ "minSelect": 0,
+ "name": "author",
+ "presentable": false,
+ "required": true,
+ "system": false,
+ "type": "relation"
+ },
+ {
+ "autogeneratePattern": "",
+ "hidden": false,
+ "id": "text521872670",
+ "max": 15,
+ "min": 15,
+ "name": "item",
+ "pattern": "^[a-z0-9]+$",
+ "presentable": false,
+ "primaryKey": false,
+ "required": true,
+ "system": false,
+ "type": "text"
+ },
+ {
+ "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_72164123",
+ "indexes": [],
+ "listRule": null,
+ "name": "feed",
+ "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_72164123")
+ if err != nil {
+ return err
+ }
+
+ return app.Delete(collection)
+ })
+}
diff --git a/db/migrations/1752232212_updated_feed.go b/db/migrations/1752232212_updated_feed.go
new file mode 100644
index 00000000..4393275c
--- /dev/null
+++ b/db/migrations/1752232212_updated_feed.go
@@ -0,0 +1,46 @@
+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("pbc_72164123")
+ if err != nil {
+ return err
+ }
+
+ // add field
+ if err := collection.Fields.AddMarshaledJSONAt(4, []byte(`{
+ "hidden": false,
+ "id": "select2363381545",
+ "maxSelect": 1,
+ "name": "type",
+ "presentable": false,
+ "required": true,
+ "system": false,
+ "type": "select",
+ "values": [
+ "trail",
+ "list",
+ "summit_log"
+ ]
+ }`)); err != nil {
+ return err
+ }
+
+ return app.Save(collection)
+ }, func(app core.App) error {
+ collection, err := app.FindCollectionByNameOrId("pbc_72164123")
+ if err != nil {
+ return err
+ }
+
+ // remove field
+ collection.Fields.RemoveById("select2363381545")
+
+ return app.Save(collection)
+ })
+}
diff --git a/db/migrations/1752233467_updated_feed.go b/db/migrations/1752233467_updated_feed.go
new file mode 100644
index 00000000..e2684adb
--- /dev/null
+++ b/db/migrations/1752233467_updated_feed.go
@@ -0,0 +1,40 @@
+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_72164123")
+ if err != nil {
+ return err
+ }
+
+ // update collection data
+ if err := json.Unmarshal([]byte(`{
+ "listRule": "actor.user = @request.auth.id"
+ }`), &collection); err != nil {
+ return err
+ }
+
+ return app.Save(collection)
+ }, func(app core.App) error {
+ collection, err := app.FindCollectionByNameOrId("pbc_72164123")
+ if err != nil {
+ return err
+ }
+
+ // update collection data
+ if err := json.Unmarshal([]byte(`{
+ "listRule": null
+ }`), &collection); err != nil {
+ return err
+ }
+
+ return app.Save(collection)
+ })
+}
diff --git a/db/migrations/1752239359_create_initial_feed.go b/db/migrations/1752239359_create_initial_feed.go
new file mode 100644
index 00000000..1a05bd3b
--- /dev/null
+++ b/db/migrations/1752239359_create_initial_feed.go
@@ -0,0 +1,100 @@
+package migrations
+
+import (
+ "pocketbase/util"
+
+ "github.com/pocketbase/dbx"
+ "github.com/pocketbase/pocketbase/core"
+ m "github.com/pocketbase/pocketbase/migrations"
+)
+
+func init() {
+ m.Register(func(app core.App) error {
+ trails, err := app.FindAllRecords("trails")
+ if err != nil {
+ return err
+ }
+
+ for _, t := range trails {
+ trailAuthor, err := app.FindRecordById("activitypub_actors", t.GetString("author"))
+ if err != nil {
+ return err
+ }
+
+ feed, err := util.InsertIntoFeed(app, trailAuthor.Id, trailAuthor.Id, t.Id, util.TrailFeed)
+ if err != nil {
+ return err
+ }
+
+ feed.SetRaw("created", t.GetDateTime("created"))
+ err = app.Save(feed)
+ if err != nil {
+ return err
+ }
+
+ followers, err := app.FindRecordsByFilter("follows", "followee={:author}", "", -1, 0, dbx.Params{"author": trailAuthor.Id})
+ if err != nil {
+ return err
+ }
+ for _, f := range followers {
+ feed, err = util.InsertIntoFeed(app, f.GetString("follower"), trailAuthor.Id, t.Id, util.TrailFeed)
+ if err != nil {
+ return err
+ }
+
+ feed.SetRaw("created", t.GetDateTime("created"))
+ err = app.Save(feed)
+ if err != nil {
+ return err
+ }
+ }
+ }
+
+ lists, err := app.FindAllRecords("lists")
+ if err != nil {
+ return err
+ }
+
+ for _, t := range lists {
+ listAuthor, err := app.FindRecordById("activitypub_actors", t.GetString("author"))
+ if err != nil {
+ return err
+ }
+
+ feed, err := util.InsertIntoFeed(app, listAuthor.Id, listAuthor.Id, t.Id, util.ListFeed)
+ if err != nil {
+ return err
+ }
+
+ feed.SetRaw("created", t.GetDateTime("created"))
+ err = app.Save(feed)
+ if err != nil {
+ return err
+ }
+
+ followers, err := app.FindRecordsByFilter("follows", "followee={:author}", "", -1, 0, dbx.Params{"author": listAuthor.Id})
+ if err != nil {
+ return err
+ }
+ for _, f := range followers {
+ feed, err = util.InsertIntoFeed(app, f.GetString("follower"), listAuthor.Id, t.Id, util.ListFeed)
+ if err != nil {
+ return err
+ }
+
+ feed.SetRaw("created", t.GetDateTime("created"))
+ err = app.Save(feed)
+ if err != nil {
+ return err
+ }
+ }
+ }
+ return nil
+ }, func(app core.App) error {
+ _, err := app.DB().
+ NewQuery("DELETE FROM feed;").
+ Execute()
+
+ return err
+ })
+}
diff --git a/db/util/activitypub.go b/db/util/activitypub.go
index bfb1281f..197f25fd 100644
--- a/db/util/activitypub.go
+++ b/db/util/activitypub.go
@@ -176,6 +176,11 @@ func TrailFromActivity(activity pub.Activity, app core.App, actor *core.Record)
} else {
return nil, err
}
+ } else {
+ // this trail exists already
+ // nothing more to do
+
+ return record, nil
}
var distance, duration, elevation_gain, elevation_loss float64
@@ -442,6 +447,11 @@ func ListFromActivity(activity pub.Activity, app core.App, actor *core.Record) (
} else {
return nil, err
}
+ } else {
+ // this list exists already
+ // nothing more to do
+
+ return record, nil
}
record.Set("name", l.Name.First().Value)
diff --git a/db/util/feed.go b/db/util/feed.go
new file mode 100644
index 00000000..34df48a7
--- /dev/null
+++ b/db/util/feed.go
@@ -0,0 +1,27 @@
+package util
+
+import "github.com/pocketbase/pocketbase/core"
+
+type FeedType string
+
+const (
+ ListFeed FeedType = "list"
+ TrailFeed FeedType = "trail"
+ SummitLogFeed FeedType = "summit_log"
+)
+
+func InsertIntoFeed(app core.App, actorId string, authorId string, itemId string, feedType FeedType) (*core.Record, error) {
+ collection, err := app.FindCollectionByNameOrId("feed")
+ if err != nil {
+ return nil, err
+ }
+
+ record := core.NewRecord(collection)
+
+ record.Set("actor", actorId)
+ record.Set("author", authorId)
+ record.Set("item", itemId)
+ record.Set("type", string(feedType))
+
+ return record, app.Save(record)
+}
diff --git a/web/src/lib/assets/svgs/empty_states/empty_state_feed_dark.svg b/web/src/lib/assets/svgs/empty_states/empty_state_feed_dark.svg
new file mode 100644
index 00000000..398931e4
--- /dev/null
+++ b/web/src/lib/assets/svgs/empty_states/empty_state_feed_dark.svg
@@ -0,0 +1,21 @@
+
diff --git a/web/src/lib/assets/svgs/empty_states/empty_state_feed_light.svg b/web/src/lib/assets/svgs/empty_states/empty_state_feed_light.svg
new file mode 100644
index 00000000..85240086
--- /dev/null
+++ b/web/src/lib/assets/svgs/empty_states/empty_state_feed_light.svg
@@ -0,0 +1,21 @@
+
diff --git a/web/src/lib/components/empty_states/empty_state_feed.svelte b/web/src/lib/components/empty_states/empty_state_feed.svelte
new file mode 100644
index 00000000..71e08e2a
--- /dev/null
+++ b/web/src/lib/components/empty_states/empty_state_feed.svelte
@@ -0,0 +1,20 @@
+
+
+
+

+
+
{$_("empty-feed")}...
+
+ {$_('empty-feed-explanation')}
+
+
diff --git a/web/src/lib/components/nav_bar.svelte b/web/src/lib/components/nav_bar.svelte
index 3473ec3d..1df00084 100644
--- a/web/src/lib/components/nav_bar.svelte
+++ b/web/src/lib/components/nav_bar.svelte
@@ -185,7 +185,7 @@
-