Activity Pub Implementation #58
|
@ -14,6 +14,7 @@ import (
|
||||||
"owl-blogs/app/repository"
|
"owl-blogs/app/repository"
|
||||||
"owl-blogs/config"
|
"owl-blogs/config"
|
||||||
"owl-blogs/domain/model"
|
"owl-blogs/domain/model"
|
||||||
|
entrytypes "owl-blogs/entry_types"
|
||||||
"owl-blogs/render"
|
"owl-blogs/render"
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
@ -62,12 +63,17 @@ func NewActivityPubService(
|
||||||
followersRepo repository.FollowerRepository,
|
followersRepo repository.FollowerRepository,
|
||||||
configRepo repository.ConfigRepository,
|
configRepo repository.ConfigRepository,
|
||||||
siteConfigServcie *SiteConfigService,
|
siteConfigServcie *SiteConfigService,
|
||||||
|
bus *EventBus,
|
||||||
) *ActivityPubService {
|
) *ActivityPubService {
|
||||||
return &ActivityPubService{
|
service := &ActivityPubService{
|
||||||
followersRepo: followersRepo,
|
followersRepo: followersRepo,
|
||||||
configRepo: configRepo,
|
configRepo: configRepo,
|
||||||
siteConfigServcie: siteConfigServcie,
|
siteConfigServcie: siteConfigServcie,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bus.Subscribe(service)
|
||||||
|
|
||||||
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
func (svc *ActivityPubService) defaultConfig() ActivityPubConfig {
|
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))
|
slog.Info("Retrieved", "status", resp.Status, "body", string(body))
|
||||||
return nil
|
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) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
|
@ -66,7 +66,7 @@ func App(db infra.Database) *web.WebApp {
|
||||||
webmentionService := app.NewWebmentionService(
|
webmentionService := app.NewWebmentionService(
|
||||||
siteConfigService, interactionRepo, entryRepo, httpClient, eventBus,
|
siteConfigService, interactionRepo, entryRepo, httpClient, eventBus,
|
||||||
)
|
)
|
||||||
apService := app.NewActivityPubService(followersRepo, configRepo, siteConfigService)
|
apService := app.NewActivityPubService(followersRepo, configRepo, siteConfigService, eventBus)
|
||||||
|
|
||||||
// setup render functions
|
// setup render functions
|
||||||
render.SiteConfigService = siteConfigService
|
render.SiteConfigService = siteConfigService
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package model
|
package model
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"net/url"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -21,6 +22,8 @@ type Entry interface {
|
||||||
SetPublishedAt(publishedAt *time.Time)
|
SetPublishedAt(publishedAt *time.Time)
|
||||||
SetMetaData(metaData EntryMetaData)
|
SetMetaData(metaData EntryMetaData)
|
||||||
SetAuthorId(authorId string)
|
SetAuthorId(authorId string)
|
||||||
|
|
||||||
|
FullUrl(cfg SiteConfig) string
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntryMetaData interface {
|
type EntryMetaData interface {
|
||||||
|
@ -60,3 +63,8 @@ func (e *EntryBase) AuthorId() string {
|
||||||
func (e *EntryBase) SetAuthorId(authorId string) {
|
func (e *EntryBase) SetAuthorId(authorId string) {
|
||||||
e.authorId = authorId
|
e.authorId = authorId
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (e *EntryBase) FullUrl(cfg SiteConfig) string {
|
||||||
|
u, _ := url.JoinPath(cfg.FullUrl, "/posts/", e.ID(), "/")
|
||||||
|
return u
|
||||||
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -171,7 +171,7 @@ func (s *ActivityPubServer) processUndo(r *http.Request, act *vocab.Activity) er
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
// go acpub.Accept(gameName, act)
|
go s.apService.Accept(act)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue