process likes from ActivityPub
This commit is contained in:
parent
cba57ba708
commit
9cfbf0b9b7
|
@ -15,6 +15,7 @@ import (
|
||||||
"owl-blogs/config"
|
"owl-blogs/config"
|
||||||
"owl-blogs/domain/model"
|
"owl-blogs/domain/model"
|
||||||
entrytypes "owl-blogs/entry_types"
|
entrytypes "owl-blogs/entry_types"
|
||||||
|
"owl-blogs/interactions"
|
||||||
"owl-blogs/render"
|
"owl-blogs/render"
|
||||||
"reflect"
|
"reflect"
|
||||||
"time"
|
"time"
|
||||||
|
@ -54,21 +55,27 @@ func (cfg *ActivityPubConfig) PrivateKey() *rsa.PrivateKey {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActivityPubService struct {
|
type ActivityPubService struct {
|
||||||
followersRepo repository.FollowerRepository
|
followersRepo repository.FollowerRepository
|
||||||
configRepo repository.ConfigRepository
|
configRepo repository.ConfigRepository
|
||||||
siteConfigServcie *SiteConfigService
|
interactionRepository repository.InteractionRepository
|
||||||
|
entryService *EntryService
|
||||||
|
siteConfigServcie *SiteConfigService
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewActivityPubService(
|
func NewActivityPubService(
|
||||||
followersRepo repository.FollowerRepository,
|
followersRepo repository.FollowerRepository,
|
||||||
configRepo repository.ConfigRepository,
|
configRepo repository.ConfigRepository,
|
||||||
|
interactionRepository repository.InteractionRepository,
|
||||||
|
entryService *EntryService,
|
||||||
siteConfigServcie *SiteConfigService,
|
siteConfigServcie *SiteConfigService,
|
||||||
bus *EventBus,
|
bus *EventBus,
|
||||||
) *ActivityPubService {
|
) *ActivityPubService {
|
||||||
service := &ActivityPubService{
|
service := &ActivityPubService{
|
||||||
followersRepo: followersRepo,
|
followersRepo: followersRepo,
|
||||||
configRepo: configRepo,
|
configRepo: configRepo,
|
||||||
siteConfigServcie: siteConfigServcie,
|
interactionRepository: interactionRepository,
|
||||||
|
entryService: entryService,
|
||||||
|
siteConfigServcie: siteConfigServcie,
|
||||||
}
|
}
|
||||||
|
|
||||||
bus.Subscribe(service)
|
bus.Subscribe(service)
|
||||||
|
@ -275,6 +282,51 @@ func (s *ActivityPubService) Accept(act *vocab.Activity) error {
|
||||||
return s.sendObject(actor, data)
|
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 {
|
func (s *ActivityPubService) sendObject(to vocab.Actor, data []byte) error {
|
||||||
siteConfig := model.SiteConfig{}
|
siteConfig := model.SiteConfig{}
|
||||||
apConfig := ActivityPubConfig{}
|
apConfig := ActivityPubConfig{}
|
||||||
|
@ -325,10 +377,11 @@ func (s *ActivityPubService) sendObject(to vocab.Actor, data []byte) error {
|
||||||
*/
|
*/
|
||||||
|
|
||||||
func (svc *ActivityPubService) NotifyEntryCreated(entry model.Entry) {
|
func (svc *ActivityPubService) NotifyEntryCreated(entry model.Entry) {
|
||||||
|
slog.Info("Processing Entry Create for ActivityPub")
|
||||||
// limit to notes for now
|
// limit to notes for now
|
||||||
noteEntry, ok := entry.(*entrytypes.Note)
|
noteEntry, ok := entry.(*entrytypes.Note)
|
||||||
if !ok {
|
if !ok {
|
||||||
slog.Info("not an image")
|
slog.Info("not a note")
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package app
|
package app
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"owl-blogs/app/repository"
|
"owl-blogs/app/repository"
|
||||||
"owl-blogs/domain/model"
|
"owl-blogs/domain/model"
|
||||||
|
@ -9,17 +10,20 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
type EntryService struct {
|
type EntryService struct {
|
||||||
EntryRepository repository.EntryRepository
|
EntryRepository repository.EntryRepository
|
||||||
Bus *EventBus
|
siteConfigServcie *SiteConfigService
|
||||||
|
Bus *EventBus
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEntryService(
|
func NewEntryService(
|
||||||
entryRepository repository.EntryRepository,
|
entryRepository repository.EntryRepository,
|
||||||
|
siteConfigServcie *SiteConfigService,
|
||||||
bus *EventBus,
|
bus *EventBus,
|
||||||
) *EntryService {
|
) *EntryService {
|
||||||
return &EntryService{
|
return &EntryService{
|
||||||
EntryRepository: entryRepository,
|
EntryRepository: entryRepository,
|
||||||
Bus: bus,
|
siteConfigServcie: siteConfigServcie,
|
||||||
|
Bus: bus,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,6 +74,19 @@ func (s *EntryService) FindById(id string) (model.Entry, error) {
|
||||||
return s.EntryRepository.FindById(id)
|
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 {
|
func (s *EntryService) filterEntries(entries []model.Entry, published bool, drafts bool) []model.Entry {
|
||||||
filteredEntries := make([]model.Entry, 0)
|
filteredEntries := make([]model.Entry, 0)
|
||||||
for _, entry := range entries {
|
for _, entry := range entries {
|
||||||
|
|
|
@ -14,7 +14,7 @@ func setupService() *app.EntryService {
|
||||||
register := app.NewEntryTypeRegistry()
|
register := app.NewEntryTypeRegistry()
|
||||||
register.Register(&test.MockEntry{})
|
register.Register(&test.MockEntry{})
|
||||||
repo := infra.NewEntryRepository(db, register)
|
repo := infra.NewEntryRepository(db, register)
|
||||||
service := app.NewEntryService(repo, app.NewEventBus())
|
service := app.NewEntryService(repo, nil, app.NewEventBus())
|
||||||
return service
|
return service
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -41,6 +41,7 @@ func App(db infra.Database) *web.WebApp {
|
||||||
|
|
||||||
interactionRegister := app.NewInteractionTypeRegistry()
|
interactionRegister := app.NewInteractionTypeRegistry()
|
||||||
interactionRegister.Register(&interactions.Webmention{})
|
interactionRegister.Register(&interactions.Webmention{})
|
||||||
|
interactionRegister.Register(&interactions.Like{})
|
||||||
|
|
||||||
configRegister := app.NewConfigRegister()
|
configRegister := app.NewConfigRegister()
|
||||||
|
|
||||||
|
@ -60,13 +61,17 @@ func App(db infra.Database) *web.WebApp {
|
||||||
|
|
||||||
// Create Services
|
// Create Services
|
||||||
siteConfigService := app.NewSiteConfigService(configRepo)
|
siteConfigService := app.NewSiteConfigService(configRepo)
|
||||||
entryService := app.NewEntryService(entryRepo, eventBus)
|
entryService := app.NewEntryService(entryRepo, siteConfigService, eventBus)
|
||||||
binaryService := app.NewBinaryFileService(binRepo)
|
binaryService := app.NewBinaryFileService(binRepo)
|
||||||
authorService := app.NewAuthorService(authorRepo, siteConfigService)
|
authorService := app.NewAuthorService(authorRepo, siteConfigService)
|
||||||
webmentionService := app.NewWebmentionService(
|
webmentionService := app.NewWebmentionService(
|
||||||
siteConfigService, interactionRepo, entryRepo, httpClient, eventBus,
|
siteConfigService, interactionRepo, entryRepo, httpClient, eventBus,
|
||||||
)
|
)
|
||||||
apService := app.NewActivityPubService(followersRepo, configRepo, siteConfigService, eventBus)
|
apService := app.NewActivityPubService(
|
||||||
|
followersRepo, configRepo, interactionRepo,
|
||||||
|
entryService, siteConfigService,
|
||||||
|
eventBus,
|
||||||
|
)
|
||||||
|
|
||||||
// setup render functions
|
// setup render functions
|
||||||
render.SiteConfigService = siteConfigService
|
render.SiteConfigService = siteConfigService
|
||||||
|
|
|
@ -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)
|
||||||
|
}
|
|
@ -0,0 +1,3 @@
|
||||||
|
Liked by <a href="{{.MetaData.SenderUrl}}">
|
||||||
|
{{.MetaData.SenderName}}
|
||||||
|
</a>
|
|
@ -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 {
|
func (s *ActivityPubServer) processUndo(r *http.Request, act *vocab.Activity) error {
|
||||||
follower := act.Actor.GetID().String()
|
sender := act.Actor.GetID().String()
|
||||||
err := s.apService.VerifySignature(r, follower)
|
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 {
|
if err != nil {
|
||||||
slog.Error("wrong signature", "err", err)
|
slog.Error("wrong signature", "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
err = s.apService.RemoveFollower(follower)
|
|
||||||
|
err = s.apService.AddLike(sender, liked, act.ID.String())
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
slog.Error("error saving like", "err", err)
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
go s.apService.Accept(act)
|
go s.apService.Accept(act)
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *ActivityPubServer) HandleInbox(ctx *fiber.Ctx) error {
|
func (s *ActivityPubServer) HandleInbox(ctx *fiber.Ctx) error {
|
||||||
// siteConfig, _ := s.siteConfigService.GetSiteConfig()
|
|
||||||
// apConfig, _ := s.apService.GetApConfig()
|
|
||||||
|
|
||||||
body := ctx.Request().Body()
|
body := ctx.Request().Body()
|
||||||
data, err := vocab.UnmarshalJSON(body)
|
data, err := vocab.UnmarshalJSON(body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -198,11 +223,15 @@ func (s *ActivityPubServer) HandleInbox(ctx *fiber.Ctx) error {
|
||||||
if act.Type == vocab.FollowType {
|
if act.Type == vocab.FollowType {
|
||||||
return s.processFollow(r, act)
|
return s.processFollow(r, act)
|
||||||
}
|
}
|
||||||
|
|
||||||
if act.Type == vocab.UndoType {
|
if act.Type == vocab.UndoType {
|
||||||
slog.Info("processing undo")
|
|
||||||
return s.processUndo(r, act)
|
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 errors.New("only follow and undo actions supported")
|
||||||
})
|
})
|
||||||
return err
|
return err
|
||||||
|
|
Loading…
Reference in New Issue