From cba57ba7086c41f8cf7573c85004b5a74a4d63b4 Mon Sep 17 00:00:00 2001 From: Niko Abeler Date: Fri, 17 May 2024 21:05:13 +0200 Subject: [PATCH] first working toot --- app/activity_pub_service.go | 74 ++++++++++++++++++++++++++++++++++++- cmd/owl/main.go | 2 +- domain/model/entry.go | 8 ++++ domain/model/entry_test.go | 34 +++++++++++++++++ web/activity_pub_handler.go | 2 +- 5 files changed, 117 insertions(+), 3 deletions(-) create mode 100644 domain/model/entry_test.go diff --git a/app/activity_pub_service.go b/app/activity_pub_service.go index 23dfb7a..3a2fb10 100644 --- a/app/activity_pub_service.go +++ b/app/activity_pub_service.go @@ -14,6 +14,7 @@ import ( "owl-blogs/app/repository" "owl-blogs/config" "owl-blogs/domain/model" + entrytypes "owl-blogs/entry_types" "owl-blogs/render" "reflect" "time" @@ -62,12 +63,17 @@ func NewActivityPubService( followersRepo repository.FollowerRepository, configRepo repository.ConfigRepository, siteConfigServcie *SiteConfigService, + bus *EventBus, ) *ActivityPubService { - return &ActivityPubService{ + service := &ActivityPubService{ followersRepo: followersRepo, configRepo: configRepo, siteConfigServcie: siteConfigServcie, } + + bus.Subscribe(service) + + return service } func (svc *ActivityPubService) defaultConfig() ActivityPubConfig { @@ -313,3 +319,69 @@ func (s *ActivityPubService) sendObject(to vocab.Actor, data []byte) error { slog.Info("Retrieved", "status", resp.Status, "body", string(body)) return nil } + +/* + * Notifiers + */ + +func (svc *ActivityPubService) NotifyEntryCreated(entry model.Entry) { + // limit to notes for now + noteEntry, ok := entry.(*entrytypes.Note) + if !ok { + slog.Info("not an image") + return + } + + siteCfg, _ := svc.siteConfigServcie.GetSiteConfig() + followers, err := svc.AllFollowers() + if err != nil { + slog.Error("Cannot retrieve followers") + } + + note := vocab.Note{ + ID: vocab.ID(noteEntry.FullUrl(siteCfg)), + Type: "Note", + To: vocab.ItemCollection{ + vocab.PublicNS, + vocab.IRI(svc.FollowersUrl()), + }, + Published: *noteEntry.PublishedAt(), + AttributedTo: vocab.ID(svc.ActorUrl()), + Content: vocab.NaturalLanguageValues{ + {Value: vocab.Content(noteEntry.Content())}, + }, + } + + create := vocab.CreateNew(vocab.IRI(noteEntry.FullUrl(siteCfg)), note) + create.Actor = note.AttributedTo + create.To = note.To + create.Published = note.Published + data, err := jsonld.WithContext( + jsonld.IRI(vocab.ActivityBaseURI), + jsonld.Context{ + jsonld.ContextElement{ + Term: "toot", + IRI: jsonld.IRI("http://joinmastodon.org/ns#"), + }, + }, + ).Marshal(create) + if err != nil { + slog.Error("marshalling error", "err", err) + } + + for _, follower := range followers { + actor, err := svc.GetActor(follower) + if err != nil { + slog.Error("Unable to retrieve follower actor", "err", err) + } + svc.sendObject(actor, data) + } +} + +func (svc *ActivityPubService) NotifyEntryUpdated(entry model.Entry) { + +} + +func (svc *ActivityPubService) NotifyEntryDeleted(entry model.Entry) { + +} diff --git a/cmd/owl/main.go b/cmd/owl/main.go index a8457d4..2324f07 100644 --- a/cmd/owl/main.go +++ b/cmd/owl/main.go @@ -66,7 +66,7 @@ func App(db infra.Database) *web.WebApp { webmentionService := app.NewWebmentionService( siteConfigService, interactionRepo, entryRepo, httpClient, eventBus, ) - apService := app.NewActivityPubService(followersRepo, configRepo, siteConfigService) + apService := app.NewActivityPubService(followersRepo, configRepo, siteConfigService, eventBus) // setup render functions render.SiteConfigService = siteConfigService diff --git a/domain/model/entry.go b/domain/model/entry.go index e59562f..15fb2c3 100644 --- a/domain/model/entry.go +++ b/domain/model/entry.go @@ -1,6 +1,7 @@ package model import ( + "net/url" "time" ) @@ -21,6 +22,8 @@ type Entry interface { SetPublishedAt(publishedAt *time.Time) SetMetaData(metaData EntryMetaData) SetAuthorId(authorId string) + + FullUrl(cfg SiteConfig) string } type EntryMetaData interface { @@ -60,3 +63,8 @@ func (e *EntryBase) AuthorId() string { func (e *EntryBase) SetAuthorId(authorId string) { e.authorId = authorId } + +func (e *EntryBase) FullUrl(cfg SiteConfig) string { + u, _ := url.JoinPath(cfg.FullUrl, "/posts/", e.ID(), "/") + return u +} diff --git a/domain/model/entry_test.go b/domain/model/entry_test.go new file mode 100644 index 0000000..19f157f --- /dev/null +++ b/domain/model/entry_test.go @@ -0,0 +1,34 @@ +package model_test + +import ( + "owl-blogs/domain/model" + "testing" + + "github.com/stretchr/testify/require" +) + +func TestEntryFullUrl(t *testing.T) { + + type testCase struct { + Id string + Url string + Want string + } + + testCases := []testCase{ + {Id: "foobar", Url: "https://example.com", Want: "https://example.com/posts/foobar/"}, + {Id: "foobar", Url: "https://example.com/", Want: "https://example.com/posts/foobar/"}, + {Id: "foobar", Url: "http://example.com", Want: "http://example.com/posts/foobar/"}, + {Id: "foobar", Url: "http://example.com/", Want: "http://example.com/posts/foobar/"}, + {Id: "bi-bar-buz", Url: "https://example.com", Want: "https://example.com/posts/bi-bar-buz/"}, + {Id: "foobar", Url: "https://example.com/lol/", Want: "https://example.com/lol/posts/foobar/"}, + } + + for _, test := range testCases { + e := model.EntryBase{} + e.SetID(test.Id) + cfg := model.SiteConfig{FullUrl: test.Url} + require.Equal(t, e.FullUrl(cfg), test.Want) + } + +} diff --git a/web/activity_pub_handler.go b/web/activity_pub_handler.go index 36de33b..d5ea1d7 100644 --- a/web/activity_pub_handler.go +++ b/web/activity_pub_handler.go @@ -171,7 +171,7 @@ func (s *ActivityPubServer) processUndo(r *http.Request, act *vocab.Activity) er return err } - // go acpub.Accept(gameName, act) + go s.apService.Accept(act) return nil }