diff --git a/server/entries.go b/server/entries.go new file mode 100644 index 00000000..7f85869c --- /dev/null +++ b/server/entries.go @@ -0,0 +1,169 @@ +package server + +import ( + "context" + "fmt" + "io" + "net/http" + + "github.com/gabriel-vasile/mimetype" + "github.com/karlseguin/typed" + "github.com/samber/lo" + "go.hacdias.com/eagle/core" + "go.hacdias.com/eagle/services/media" + "go.hacdias.com/indielib/micropub" +) + +func (s *Server) getEntryPhotos(e *core.Entry) ([]Photo, error) { + var photos []Photo + + for i, photo := range e.Photos { + if i >= 4 { + break + } + + photoUrl, err := s.media.GetImageURL(photo.URL, media.FormatJPEG, media.Width1000) + if err != nil { + return nil, err + } + + res, err := http.Get(photoUrl) + if err != nil { + return nil, err + } + + data, err := io.ReadAll(res.Body) + if err != nil { + return nil, err + } + + err = res.Body.Close() + if err != nil { + return nil, err + } + + mime := mimetype.Detect(data) + if mime == nil { + return nil, fmt.Errorf("cannot detect mimetype of %s", photo) + } + + photos = append(photos, Photo{ + Data: data, + MimeType: mime.String(), + }) + } + + return photos, nil +} + +func (s *Server) syndicate(e *core.Entry, syndicators []string) { + // Get the photos to use during syndication + photos, err := s.getEntryPhotos(e) + if err != nil { + s.log.Errorw("failed to get photos for syndication", "entry", e.ID, "err", err) + return + } + + // Include syndicators that have already been used for this post + for name, syndicator := range s.syndicators { + if syndicator.IsSyndicated(e) { + syndicators = append(syndicators, name) + } + } + + // Do the actual syndication + syndications := typed.New(e.Other).Strings(SyndicationField) + for _, name := range syndicators { + if syndicator, ok := s.syndicators[name]; ok { + syndication, removed, err := syndicator.Syndicate(context.Background(), e, photos) + if err != nil { + s.log.Errorw("failed to syndicate", "entry", e.ID, "syndicator", name, "err", err) + continue + } + + if removed { + syndications = lo.Without(syndications, syndication) + } else { + syndications = append(syndications, syndication) + } + } + } + + syndications = lo.Uniq(syndications) + if len(syndications) == 0 { + delete(e.Other, SyndicationField) + } else { + e.Other[SyndicationField] = lo.Uniq(syndications) + } + + err = s.core.SaveEntry(e) + if err != nil { + s.log.Errorw("failed save entry", "id", e.ID, "err", err) + } +} + +func (s *Server) preSaveEntry(e *core.Entry) error { + for name, plugin := range s.plugins { + hookPlugin, ok := plugin.(HookPlugin) + if !ok { + continue + } + + err := hookPlugin.PreSaveHook(e) + if err != nil { + return fmt.Errorf("plugin %s error: %w", name, err) + } + } + + return nil +} + +func (s *Server) postSaveEntry(e *core.Entry, req *micropub.Request, oldTargets []string, skipBuild bool) { + // Syndications + var syndicateTo []string + if req != nil { + syndicateTo, _ = getRequestSyndicateTo(req) + } + s.syndicate(e, syndicateTo) + + // Post-save hooks + for name, plugin := range s.plugins { + hookPlugin, ok := plugin.(HookPlugin) + if !ok { + continue + } + + err := hookPlugin.PostSaveHook(e) + if err != nil { + s.log.Errorw("plugin post save hook failed", "plugin", name, "err", err) + } + } + + // Search indexing + if s.meilisearch != nil { + var err error + if e.Deleted() { + err = s.meilisearch.Remove(e.ID) + } else { + err = s.meilisearch.Add(e) + } + if err != nil { + s.log.Errorw("meilisearch sync failed", "err", err) + } + } + + // Rebuild + if !skipBuild && !e.Deleted() && !e.Draft { + s.build(false) + } + + // No further action for drafts or no webmentions + if e.Draft || e.NoWebmentions { + return + } + + err := s.core.SendWebmentions(e.Permalink, oldTargets...) + if err != nil { + s.log.Errorw("failed to send webmentions", "id", e.ID, "err", err) + } +} diff --git a/server/micropub.go b/server/micropub.go index 9e6d900d..745152ca 100644 --- a/server/micropub.go +++ b/server/micropub.go @@ -5,7 +5,6 @@ import ( "context" "errors" "fmt" - "io" "net/http" "path/filepath" "reflect" @@ -13,11 +12,9 @@ import ( "time" "github.com/araddon/dateparse" - "github.com/gabriel-vasile/mimetype" "github.com/karlseguin/typed" "github.com/samber/lo" "go.hacdias.com/eagle/core" - "go.hacdias.com/eagle/services/media" "go.hacdias.com/indielib/micropub" ) @@ -131,7 +128,7 @@ func (m *micropubServer) Create(req *micropub.Request) (string, error) { e.Other[m.s.c.Micropub.ChannelsTaxonomy], _ = getRequestChannels(req) } - err = m.preSave(e) + err = m.s.preSaveEntry(e) if err != nil { return "", err } @@ -146,7 +143,7 @@ func (m *micropubServer) Create(req *micropub.Request) (string, error) { return "", err } - go m.postSave(e, req, nil) + go m.s.postSaveEntry(e, req, nil, false) return e.Permalink, nil } @@ -209,7 +206,7 @@ func (m *micropubServer) update(permalink string, req *micropub.Request, update return nil } - err = m.preSave(e) + err = m.s.preSaveEntry(e) if err != nil { return err } @@ -224,162 +221,10 @@ func (m *micropubServer) update(permalink string, req *micropub.Request, update return err } - go m.postSave(e, req, targets) + go m.s.postSaveEntry(e, req, targets, false) return nil } -func (m *micropubServer) getPhotos(e *core.Entry) ([]Photo, error) { - var photos []Photo - - for i, photo := range e.Photos { - if i >= 4 { - break - } - - photoUrl, err := m.s.media.GetImageURL(photo.URL, media.FormatJPEG, media.Width1000) - if err != nil { - return nil, err - } - - res, err := http.Get(photoUrl) - if err != nil { - return nil, err - } - - data, err := io.ReadAll(res.Body) - if err != nil { - return nil, err - } - - err = res.Body.Close() - if err != nil { - return nil, err - } - - mime := mimetype.Detect(data) - if mime == nil { - return nil, fmt.Errorf("cannot detect mimetype of %s", photo) - } - - photos = append(photos, Photo{ - Data: data, - MimeType: mime.String(), - }) - } - - return photos, nil -} - -func (m *micropubServer) syndicate(e *core.Entry, syndicators []string) { - // Get the photos to use during syndication - photos, err := m.getPhotos(e) - if err != nil { - m.s.log.Errorw("failed to get photos for syndication", "entry", e.ID, "err", err) - return - } - - // Include syndicators that have already been used for this post - for name, syndicator := range m.s.syndicators { - if syndicator.IsSyndicated(e) { - syndicators = append(syndicators, name) - } - } - - // Do the actual syndication - syndications := typed.New(e.Other).Strings(SyndicationField) - for _, name := range syndicators { - if syndicator, ok := m.s.syndicators[name]; ok { - syndication, removed, err := syndicator.Syndicate(context.Background(), e, photos) - if err != nil { - m.s.log.Errorw("failed to syndicate", "entry", e.ID, "syndicator", name, "err", err) - continue - } - - if removed { - syndications = lo.Without(syndications, syndication) - } else { - syndications = append(syndications, syndication) - } - } - } - - syndications = lo.Uniq(syndications) - if len(syndications) == 0 { - delete(e.Other, SyndicationField) - } else { - e.Other[SyndicationField] = lo.Uniq(syndications) - } - - err = m.s.core.SaveEntry(e) - if err != nil { - m.s.log.Errorw("failed save entry", "id", e.ID, "err", err) - } -} - -func (m *micropubServer) preSave(e *core.Entry) error { - for name, plugin := range m.s.plugins { - hookPlugin, ok := plugin.(HookPlugin) - if !ok { - continue - } - - err := hookPlugin.PreSaveHook(e) - if err != nil { - return fmt.Errorf("plugin %s error: %w", name, err) - } - } - - return nil -} - -func (m *micropubServer) postSave(e *core.Entry, req *micropub.Request, oldTargets []string) { - // Syndications - var syndicateTo []string - if req != nil { - syndicateTo, _ = getRequestSyndicateTo(req) - } - m.syndicate(e, syndicateTo) - - // Post-save hooks - for name, plugin := range m.s.plugins { - hookPlugin, ok := plugin.(HookPlugin) - if !ok { - continue - } - - err := hookPlugin.PostSaveHook(e) - if err != nil { - m.s.log.Errorw("plugin post save hook failed", "plugin", name, "err", err) - } - } - - // Search indexing - if m.s.meilisearch != nil { - var err error - if e.Deleted() { - err = m.s.meilisearch.Remove(e.ID) - } else { - err = m.s.meilisearch.Add(e) - } - if err != nil { - m.s.log.Errorw("meilisearch sync failed", "err", err) - } - } - - // Rebuild - m.s.build(false) - - // No further action for drafts or no webmentions - if e.Draft || e.NoWebmentions { - return - } - - err := m.s.core.SendWebmentions(e.Permalink, oldTargets...) - if err != nil { - m.s.log.Errorw("failed to send webmentions", "id", e.ID, "err", err) - } -} - func (m *micropubServer) entryToMF2(e *core.Entry) map[string]any { properties := map[string]interface{}{} diff --git a/server/server.go b/server/server.go index b779dec4..3721b23d 100644 --- a/server/server.go +++ b/server/server.go @@ -373,20 +373,13 @@ func (s *Server) syncStorage() { s.build(buildClean) - // TODO: smh call postSave here + if len(ids) > 10 { + s.log.Warn("not running post save hooks due to high quantity of changed entries") + return + } - // After building, send webmentions with new information and old links. - // This is a best effort to send webmentions to deleted links. Only works - // with deletions that use expiryDate. for _, e := range ee { - if e.Draft || e.NoWebmentions { - continue - } - - err = s.core.SendWebmentions(e.Permalink, previousLinks[e.Permalink]...) - if err != nil { - s.log.Errorw("failed to send webmentions", "id", e.Permalink, "err", err) - } + s.postSaveEntry(e, nil, previousLinks[e.Permalink], true) } }