prevent publishing of drafts + support for recipes

This commit is contained in:
Niko Abeler 2024-05-18 19:34:44 +02:00
parent 3c924ac8a4
commit 5c05f48be3
3 changed files with 115 additions and 6 deletions

View File

@ -498,7 +498,41 @@ func (svc *ActivityPubService) NotifyEntryCreated(entry model.Entry) {
} }
func (svc *ActivityPubService) NotifyEntryUpdated(entry model.Entry) { func (svc *ActivityPubService) NotifyEntryUpdated(entry model.Entry) {
slog.Info("Processing Entry Create for ActivityPub")
followers, err := svc.AllFollowers()
if err != nil {
slog.Error("Cannot retrieve followers")
}
object, err := svc.entryToObject(entry)
if err != nil {
slog.Error("Cannot convert object", "err", err)
}
update := vocab.UpdateNew(object.ID, object)
update.Actor = object.AttributedTo
update.To = object.To
update.Published = object.Published
data, err := jsonld.WithContext(
jsonld.IRI(vocab.ActivityBaseURI),
jsonld.Context{
jsonld.ContextElement{
Term: "toot",
IRI: jsonld.IRI("http://joinmastodon.org/ns#"),
},
},
).Marshal(update)
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) NotifyEntryDeleted(entry model.Entry) { func (svc *ActivityPubService) NotifyEntryDeleted(entry model.Entry) {
@ -543,6 +577,13 @@ func (svc *ActivityPubService) entryToObject(entry model.Entry) (vocab.Object, e
if imageEntry, ok := entry.(*entrytypes.Image); ok { if imageEntry, ok := entry.(*entrytypes.Image); ok {
return svc.imageToObject(imageEntry), nil return svc.imageToObject(imageEntry), nil
} }
if articleEntry, ok := entry.(*entrytypes.Article); ok {
return svc.articleToObject(articleEntry), nil
}
if recipeEntry, ok := entry.(*entrytypes.Recipe); ok {
return svc.recipeToObject(recipeEntry), nil
}
slog.Warn("entry type not yet supported for activity pub") slog.Warn("entry type not yet supported for activity pub")
return vocab.Object{}, errors.New("entry type not supported") return vocab.Object{}, errors.New("entry type not supported")
} }
@ -595,11 +636,14 @@ func (svc *ActivityPubService) imageToObject(imageEntry *entrytypes.Image) vocab
Type: vocab.DocumentType, Type: vocab.DocumentType,
MediaType: vocab.MimeType(binaryFile.Mime()), MediaType: vocab.MimeType(binaryFile.Mime()),
URL: vocab.ID(fullImageUrl), URL: vocab.ID(fullImageUrl),
Name: vocab.NaturalLanguageValues{
{Value: vocab.Content(content)},
},
}) })
image := vocab.Note{ image := vocab.Image{
ID: vocab.ID(imageEntry.FullUrl(siteCfg)), ID: vocab.ID(imageEntry.FullUrl(siteCfg)),
Type: "Note", Type: "Image",
To: vocab.ItemCollection{ To: vocab.ItemCollection{
vocab.PublicNS, vocab.PublicNS,
vocab.IRI(svc.FollowersUrl()), vocab.IRI(svc.FollowersUrl()),
@ -610,7 +654,7 @@ func (svc *ActivityPubService) imageToObject(imageEntry *entrytypes.Image) vocab
{Value: vocab.Content(imageEntry.Title())}, {Value: vocab.Content(imageEntry.Title())},
}, },
Content: vocab.NaturalLanguageValues{ Content: vocab.NaturalLanguageValues{
{Value: vocab.Content(content)}, {Value: vocab.Content(imageEntry.Title() + "<br><br>" + string(content))},
}, },
Attachment: attachments, Attachment: attachments,
// Tag: tags, // Tag: tags,
@ -618,3 +662,51 @@ func (svc *ActivityPubService) imageToObject(imageEntry *entrytypes.Image) vocab
return image return image
} }
func (svc *ActivityPubService) articleToObject(articleEntry *entrytypes.Article) vocab.Object {
siteCfg, _ := svc.siteConfigServcie.GetSiteConfig()
content := articleEntry.Content()
image := vocab.Article{
ID: vocab.ID(articleEntry.FullUrl(siteCfg)),
Type: "Article",
To: vocab.ItemCollection{
vocab.PublicNS,
vocab.IRI(svc.FollowersUrl()),
},
Published: *articleEntry.PublishedAt(),
AttributedTo: vocab.ID(svc.ActorUrl()),
Name: vocab.NaturalLanguageValues{
{Value: vocab.Content(articleEntry.Title())},
},
Content: vocab.NaturalLanguageValues{
{Value: vocab.Content(string(content))},
},
}
return image
}
func (svc *ActivityPubService) recipeToObject(recipeEntry *entrytypes.Recipe) vocab.Object {
siteCfg, _ := svc.siteConfigServcie.GetSiteConfig()
content := recipeEntry.Content()
image := vocab.Article{
ID: vocab.ID(recipeEntry.FullUrl(siteCfg)),
Type: "Article",
To: vocab.ItemCollection{
vocab.PublicNS,
vocab.IRI(svc.FollowersUrl()),
},
Published: *recipeEntry.PublishedAt(),
AttributedTo: vocab.ID(svc.ActorUrl()),
Name: vocab.NaturalLanguageValues{
{Value: vocab.Content(recipeEntry.Title())},
},
Content: vocab.NaturalLanguageValues{
{Value: vocab.Content(string(content))},
},
}
return image
}

View File

@ -48,7 +48,13 @@ func (s *EntryService) Create(entry model.Entry) error {
if err != nil { if err != nil {
return err return err
} }
s.Bus.NotifyCreated(entry) // only notify if the publishing date is set
// otherwise this is a draft.
// listeners might publish the entry to other services/platforms
// this should only happen for publshed content
if entry.PublishedAt() != nil && !entry.PublishedAt().IsZero() {
s.Bus.NotifyCreated(entry)
}
return nil return nil
} }
@ -57,7 +63,13 @@ func (s *EntryService) Update(entry model.Entry) error {
if err != nil { if err != nil {
return err return err
} }
s.Bus.NotifyUpdated(entry) // only notify if the publishing date is set
// otherwise this is a draft.
// listeners might publish the entry to other services/platforms
// this should only happen for publshed content
if entry.PublishedAt() != nil && !entry.PublishedAt().IsZero() {
s.Bus.NotifyUpdated(entry)
}
return nil return nil
} }
@ -66,6 +78,9 @@ func (s *EntryService) Delete(entry model.Entry) error {
if err != nil { if err != nil {
return err return err
} }
// deletes should always be notfied
// a published entry might be converted to a draft before deletion
// omitting the deletion in this case would prevent deletion on other platforms
s.Bus.NotifyDeleted(entry) s.Bus.NotifyDeleted(entry)
return nil return nil
} }

View File

@ -14,7 +14,9 @@ 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, nil, app.NewEventBus()) cfgRepo := infra.NewConfigRepo(db)
cfgService := app.NewSiteConfigService(cfgRepo)
service := app.NewEntryService(repo, cfgService, app.NewEventBus())
return service return service
} }