process likes from ActivityPub

This commit is contained in:
Niko Abeler 2024-05-17 22:37:18 +02:00
parent cba57ba708
commit 9cfbf0b9b7
7 changed files with 163 additions and 23 deletions

View File

@ -15,6 +15,7 @@ import (
"owl-blogs/config"
"owl-blogs/domain/model"
entrytypes "owl-blogs/entry_types"
"owl-blogs/interactions"
"owl-blogs/render"
"reflect"
"time"
@ -54,21 +55,27 @@ func (cfg *ActivityPubConfig) PrivateKey() *rsa.PrivateKey {
}
type ActivityPubService struct {
followersRepo repository.FollowerRepository
configRepo repository.ConfigRepository
siteConfigServcie *SiteConfigService
followersRepo repository.FollowerRepository
configRepo repository.ConfigRepository
interactionRepository repository.InteractionRepository
entryService *EntryService
siteConfigServcie *SiteConfigService
}
func NewActivityPubService(
followersRepo repository.FollowerRepository,
configRepo repository.ConfigRepository,
interactionRepository repository.InteractionRepository,
entryService *EntryService,
siteConfigServcie *SiteConfigService,
bus *EventBus,
) *ActivityPubService {
service := &ActivityPubService{
followersRepo: followersRepo,
configRepo: configRepo,
siteConfigServcie: siteConfigServcie,
followersRepo: followersRepo,
configRepo: configRepo,
interactionRepository: interactionRepository,
entryService: entryService,
siteConfigServcie: siteConfigServcie,
}
bus.Subscribe(service)
@ -275,6 +282,51 @@ func (s *ActivityPubService) Accept(act *vocab.Activity) error {
return s.sendObject(actor, data)
}
func (s *ActivityPubService) AddLike(sender string, liked string, likeId string) error {
entry, err := s.entryService.FindByUrl(liked)
if err != nil {
return err
}
actor, err := s.GetActor(sender)
if err != nil {
return err
}
var like *interactions.Like
interaction, err := s.interactionRepository.FindById(likeId)
if err != nil {
interaction = &interactions.Like{}
}
like, ok := interaction.(*interactions.Like)
if !ok {
return errors.New("existing interaction with same id is not a like")
}
existing := like.ID() != ""
likeMeta := interactions.LikeMetaData{
SenderUrl: sender,
SenderName: actor.Name.String(),
}
like.SetID(likeId)
like.SetMetaData(&likeMeta)
like.SetEntryID(entry.ID())
like.SetCreatedAt(time.Now())
if !existing {
return s.interactionRepository.Create(like)
} else {
return s.interactionRepository.Update(like)
}
}
func (s *ActivityPubService) RemoveLike(id string) error {
interaction, err := s.interactionRepository.FindById(id)
if err != nil {
interaction = &interactions.Like{}
}
return s.interactionRepository.Delete(interaction)
}
func (s *ActivityPubService) sendObject(to vocab.Actor, data []byte) error {
siteConfig := model.SiteConfig{}
apConfig := ActivityPubConfig{}
@ -325,10 +377,11 @@ func (s *ActivityPubService) sendObject(to vocab.Actor, data []byte) error {
*/
func (svc *ActivityPubService) NotifyEntryCreated(entry model.Entry) {
slog.Info("Processing Entry Create for ActivityPub")
// limit to notes for now
noteEntry, ok := entry.(*entrytypes.Note)
if !ok {
slog.Info("not an image")
slog.Info("not a note")
return
}

View File

@ -1,6 +1,7 @@
package app
import (
"errors"
"fmt"
"owl-blogs/app/repository"
"owl-blogs/domain/model"
@ -9,17 +10,20 @@ import (
)
type EntryService struct {
EntryRepository repository.EntryRepository
Bus *EventBus
EntryRepository repository.EntryRepository
siteConfigServcie *SiteConfigService
Bus *EventBus
}
func NewEntryService(
entryRepository repository.EntryRepository,
siteConfigServcie *SiteConfigService,
bus *EventBus,
) *EntryService {
return &EntryService{
EntryRepository: entryRepository,
Bus: bus,
EntryRepository: entryRepository,
siteConfigServcie: siteConfigServcie,
Bus: bus,
}
}
@ -70,6 +74,19 @@ func (s *EntryService) FindById(id string) (model.Entry, error) {
return s.EntryRepository.FindById(id)
}
func (s *EntryService) FindByUrl(url string) (model.Entry, error) {
cfg, _ := s.siteConfigServcie.GetSiteConfig()
if !strings.HasPrefix(url, cfg.FullUrl) {
return nil, errors.New("url does not belong to blog")
}
if strings.HasSuffix(url, "/") {
url = url[:len(url)-1]
}
parts := strings.Split(url, "/")
id := parts[len(parts)-1]
return s.FindById(id)
}
func (s *EntryService) filterEntries(entries []model.Entry, published bool, drafts bool) []model.Entry {
filteredEntries := make([]model.Entry, 0)
for _, entry := range entries {

View File

@ -14,7 +14,7 @@ func setupService() *app.EntryService {
register := app.NewEntryTypeRegistry()
register.Register(&test.MockEntry{})
repo := infra.NewEntryRepository(db, register)
service := app.NewEntryService(repo, app.NewEventBus())
service := app.NewEntryService(repo, nil, app.NewEventBus())
return service
}

View File

@ -41,6 +41,7 @@ func App(db infra.Database) *web.WebApp {
interactionRegister := app.NewInteractionTypeRegistry()
interactionRegister.Register(&interactions.Webmention{})
interactionRegister.Register(&interactions.Like{})
configRegister := app.NewConfigRegister()
@ -60,13 +61,17 @@ func App(db infra.Database) *web.WebApp {
// Create Services
siteConfigService := app.NewSiteConfigService(configRepo)
entryService := app.NewEntryService(entryRepo, eventBus)
entryService := app.NewEntryService(entryRepo, siteConfigService, eventBus)
binaryService := app.NewBinaryFileService(binRepo)
authorService := app.NewAuthorService(authorRepo, siteConfigService)
webmentionService := app.NewWebmentionService(
siteConfigService, interactionRepo, entryRepo, httpClient, eventBus,
)
apService := app.NewActivityPubService(followersRepo, configRepo, siteConfigService, eventBus)
apService := app.NewActivityPubService(
followersRepo, configRepo, interactionRepo,
entryService, siteConfigService,
eventBus,
)
// setup render functions
render.SiteConfigService = siteConfigService

33
interactions/like.go Normal file
View File

@ -0,0 +1,33 @@
package interactions
import (
"fmt"
"owl-blogs/domain/model"
"owl-blogs/render"
)
type Like struct {
model.InteractionBase
meta LikeMetaData
}
type LikeMetaData struct {
SenderUrl string
SenderName string
}
func (i *Like) Content() model.InteractionContent {
str, err := render.RenderTemplateToString("interaction/Like", i)
if err != nil {
fmt.Println(err)
}
return model.InteractionContent(str)
}
func (i *Like) MetaData() interface{} {
return &i.meta
}
func (i *Like) SetMetaData(metaData interface{}) {
i.meta = *metaData.(*LikeMetaData)
}

View File

@ -0,0 +1,3 @@
Liked by <a href="{{.MetaData.SenderUrl}}">
{{.MetaData.SenderName}}
</a>

View File

@ -160,26 +160,51 @@ func (s *ActivityPubServer) processFollow(r *http.Request, act *vocab.Activity)
}
func (s *ActivityPubServer) processUndo(r *http.Request, act *vocab.Activity) error {
follower := act.Actor.GetID().String()
err := s.apService.VerifySignature(r, follower)
sender := act.Actor.GetID().String()
err := s.apService.VerifySignature(r, sender)
return vocab.OnObject(act.Object, func(o *vocab.Object) error {
if o.Type == vocab.FollowType {
if err != nil {
slog.Error("wrong signature", "err", err)
return err
}
err = s.apService.RemoveFollower(sender)
if err != nil {
return err
}
go s.apService.Accept(act)
return nil
}
if o.Type == vocab.LikeType {
return s.apService.RemoveLike(o.ID.String())
}
slog.Warn("unsupporeted object type for undo", "object", o)
return errors.New("unsupporeted object type")
})
}
func (s *ActivityPubServer) processLike(r *http.Request, act *vocab.Activity) error {
sender := act.Actor.GetID().String()
liked := act.Object.GetID().String()
err := s.apService.VerifySignature(r, sender)
if err != nil {
slog.Error("wrong signature", "err", err)
return err
}
err = s.apService.RemoveFollower(follower)
err = s.apService.AddLike(sender, liked, act.ID.String())
if err != nil {
slog.Error("error saving like", "err", err)
return err
}
go s.apService.Accept(act)
return nil
}
func (s *ActivityPubServer) HandleInbox(ctx *fiber.Ctx) error {
// siteConfig, _ := s.siteConfigService.GetSiteConfig()
// apConfig, _ := s.apService.GetApConfig()
body := ctx.Request().Body()
data, err := vocab.UnmarshalJSON(body)
if err != nil {
@ -198,11 +223,15 @@ func (s *ActivityPubServer) HandleInbox(ctx *fiber.Ctx) error {
if act.Type == vocab.FollowType {
return s.processFollow(r, act)
}
if act.Type == vocab.UndoType {
slog.Info("processing undo")
return s.processUndo(r, act)
}
if act.Type == vocab.LikeType {
return s.processLike(r, act)
}
slog.Warn("Unsupported action", "body", body)
return errors.New("only follow and undo actions supported")
})
return err