From a509c797ba2d70d73dc637fa629ba26d10e3624a Mon Sep 17 00:00:00 2001 From: Maxim Lebedev Date: Thu, 15 Feb 2024 00:06:19 +0600 Subject: [PATCH] :recycle: Refactored entries HTTP delivery with ActivityPub support --- internal/cmd/home/home.go | 15 ++- internal/entry/delivery/http/entry_http.go | 104 ++++++++++++++------- internal/theme/delivery/http/theme_http.go | 49 ---------- 3 files changed, 81 insertions(+), 87 deletions(-) delete mode 100644 internal/theme/delivery/http/theme_http.go diff --git a/internal/cmd/home/home.go b/internal/cmd/home/home.go index b802518..3fe8aa4 100644 --- a/internal/cmd/home/home.go +++ b/internal/cmd/home/home.go @@ -5,13 +5,16 @@ import ( "fmt" "io/fs" "log" + "mime" "net" "net/http" "os" "strings" "time" + "source.toby3d.me/toby3d/home/internal/common" "source.toby3d.me/toby3d/home/internal/domain" + entryhttpdelivery "source.toby3d.me/toby3d/home/internal/entry/delivery/http" pagefsrepo "source.toby3d.me/toby3d/home/internal/entry/repository/fs" pageucase "source.toby3d.me/toby3d/home/internal/entry/usecase" "source.toby3d.me/toby3d/home/internal/middleware" @@ -25,7 +28,6 @@ import ( statichttpdelivery "source.toby3d.me/toby3d/home/internal/static/delivery/http" staticfsrepo "source.toby3d.me/toby3d/home/internal/static/repository/fs" staticucase "source.toby3d.me/toby3d/home/internal/static/usecase" - themehttpdelivery "source.toby3d.me/toby3d/home/internal/theme/delivery/http" themefsrepo "source.toby3d.me/toby3d/home/internal/theme/repository/fs" themeucase "source.toby3d.me/toby3d/home/internal/theme/usecase" "source.toby3d.me/toby3d/home/internal/urlutil" @@ -59,10 +61,10 @@ func NewApp(logger *log.Logger, config *domain.Config) (*App, error) { serverer := servercase.NewServerUseCase(sites) webfingerer := webfingerucase.NewWebFingerUseCase(sites) webfingerHandler := webfingerhttpdelivery.NewHandler(webfingerer) - themeHandler := themehttpdelivery.NewHandler(themer) resourceHandler := resourcehttpdelivery.NewHandler(resourcer, contentDir) staticHandler := statichttpdelivery.NewHandler(staticer) siteHandler := sitehttpdelivery.NewHandler(siter) + entryHandler := entryhttpdelivery.NewHandler(themer) handler := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { // NOTE(toby3d): any file in $HOME_STATIC_DIR is public and // unprotected by design, so it's safe to search it first before @@ -96,7 +98,12 @@ func NewApp(logger *log.Logger, config *domain.Config) (*App, error) { } if handler != nil { - handler.ServeHTTP(w, r) + mediaType, _, _ := mime.ParseMediaType(r.Header.Get(common.HeaderAccept)) + if strings.EqualFold(mediaType, common.MIMEApplicationLdJSON) { + entryHandler.ActivityPubHandler.HandleProfile(site).ServeHTTP(w, r) + } else { + handler.ServeHTTP(w, r) + } return } @@ -118,7 +125,7 @@ func NewApp(logger *log.Logger, config *domain.Config) (*App, error) { return } - themeHandler.Handle(site, entry).ServeHTTP(w, r) + entryHandler.Handle(site, entry).ServeHTTP(w, r) }) chain := middleware.Chain{ // middleware.LogFmt(), diff --git a/internal/entry/delivery/http/entry_http.go b/internal/entry/delivery/http/entry_http.go index e555597..482c755 100644 --- a/internal/entry/delivery/http/entry_http.go +++ b/internal/entry/delivery/http/entry_http.go @@ -1,27 +1,31 @@ package http import ( - "context" "encoding/json" - "fmt" "mime" "net/http" + "strings" + "time" "github.com/go-ap/activitypub" "source.toby3d.me/toby3d/home/internal/common" "source.toby3d.me/toby3d/home/internal/domain" - "source.toby3d.me/toby3d/home/internal/entry" - "source.toby3d.me/toby3d/home/internal/site" + "source.toby3d.me/toby3d/home/internal/theme" ) type ( Handler struct { - entries entry.UseCase + *ActivityPubHandler + *TemplateHandler } ActivityPubHandler struct{} + TemplateHandler struct { + themes theme.UseCase + } + /* TODO(toby3d) Profile struct { *activitypub.Person @@ -48,56 +52,88 @@ type ( */ ) -func NewHandler(sites site.UseCase, entries entry.UseCase) *Handler { +func NewHandler(themes theme.UseCase) *Handler { return &Handler{ - entries: entries, + ActivityPubHandler: NewActivityPubHandler(), + TemplateHandler: NewTemplateHandler(themes), } } -func (h *Handler) Handle(ctx context.Context, site *domain.Site, path string) (*domain.Entry, http.Handler, error) { - entry, err := h.entries.Do(ctx, site.Language, path) - if err != nil { - return nil, nil, fmt.Errorf("cannot handle entry: %w", err) - } +func (h *Handler) Handle(site *domain.Site, entry *domain.Entry) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + mediaType, _, _ := mime.ParseMediaType(r.Header.Get(common.HeaderAccept)) - return entry, http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - contentType, _, _ := mime.ParseMediaType(r.Header.Get(common.HeaderAccept)) - - switch contentType { + switch strings.ToLower(mediaType) { + default: + h.TemplateHandler.Handle(site, entry).ServeHTTP(w, r) case common.MIMEApplicationLdJSON: - h.ActivityPubHandler.Handle(s, e).ServeHTTP(w, r) + h.ActivityPubHandler.Handle(site, entry).ServeHTTP(w, r) } - }), nil + }) +} + +func NewTemplateHandler(themes theme.UseCase) *TemplateHandler { + return &TemplateHandler{themes: themes} +} + +func (h *TemplateHandler) Handle(site *domain.Site, entry *domain.Entry) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + // TODO(toby3d): handle home page. + // TODO(toby3d): handle sections. + // TODO(toby3d): handle errors. + + // NOTE(toby3d): wrap founded entry into theme template and + // answer to client. + contentLanguage := make([]string, len(entry.Translations)) + for i := range entry.Translations { + contentLanguage[i] = entry.Translations[i].Language.Code() + } + + w.Header().Set(common.HeaderContentLanguage, strings.Join(contentLanguage, ", ")) + + template, err := h.themes.Do(r.Context(), site, entry) + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + + return + } + + w.Header().Set(common.HeaderContentType, common.MIMETextHTMLCharsetUTF8) + if err = template(w); err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + } + }) } func NewActivityPubHandler() *ActivityPubHandler { return &ActivityPubHandler{} } -func (ActivityPubHandler) Handle(s *domain.Site, e *domain.Entry) http.Handler { +func (ActivityPubHandler) HandleProfile(site *domain.Site) http.Handler { return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { w.Header().Set(common.HeaderContentType, common.MIMEApplicationActivityJSONCharsetUTF8) encoder := json.NewEncoder(w) - /* TODO(toby3d): handle profiles on root page. - if head == "" { // NOTE(toby3d): base URL point to owner Profile. - langRef := activitypub.LangRef(lang.Lang()) - person := activitypub.PersonNew(activitypub.IRI(s.BaseURL.String())) - person.URL = person.ID - person.Name.Add(activitypub.LangRefValueNew(langRef, "Maxim Lebedev")) - person.Summary.Add(activitypub.LangRefValueNew(langRef, "Creative dude from russia")) - person.PreferredUsername.Add(activitypub.LangRefValueNew(langRef, "toby3d")) - person.Published = time.Date(2009, time.February, 0, 0, 0, 0, 0, time.UTC) + langRef := activitypub.LangRef(site.DefaultLanguage.Lang()) + person := activitypub.PersonNew(activitypub.IRI(site.BaseURL.String())) + person.URL = person.ID + person.Name.Add(activitypub.LangRefValueNew(langRef, "Maxim Lebedev")) + person.Summary.Add(activitypub.LangRefValueNew(langRef, "Creative dude from russia")) + person.PreferredUsername.Add(activitypub.LangRefValueNew(langRef, "toby3d")) + person.Published = time.Date(2009, time.February, 0, 0, 0, 0, 0, time.UTC) - _ = encoder.Encode(person) + _ = encoder.Encode(person) + }) +} - return - } - */ +func (ActivityPubHandler) Handle(site *domain.Site, entry *domain.Entry) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) { + w.Header().Set(common.HeaderContentType, common.MIMEApplicationActivityJSONCharsetUTF8) + encoder := json.NewEncoder(w) resp := activitypub.ObjectNew(activitypub.NoteType) - resp.ID = activitypub.ID(s.BaseURL.JoinPath(e.File.Path()).String()) - resp.Content.Add(activitypub.LangRefValueNew(activitypub.NilLangRef, string(e.Content))) + resp.ID = activitypub.ID(site.BaseURL.JoinPath(entry.File.Path()).String()) + resp.Content.Add(activitypub.LangRefValueNew(activitypub.NilLangRef, string(entry.Content))) _ = encoder.Encode(resp) }) diff --git a/internal/theme/delivery/http/theme_http.go b/internal/theme/delivery/http/theme_http.go deleted file mode 100644 index 5726c4b..0000000 --- a/internal/theme/delivery/http/theme_http.go +++ /dev/null @@ -1,49 +0,0 @@ -package http - -import ( - "net/http" - "strings" - - "source.toby3d.me/toby3d/home/internal/common" - "source.toby3d.me/toby3d/home/internal/domain" - "source.toby3d.me/toby3d/home/internal/theme" -) - -type Handler struct { - themes theme.UseCase -} - -func NewHandler(themes theme.UseCase) *Handler { - return &Handler{ - themes: themes, - } -} - -func (h *Handler) Handle(s *domain.Site, e *domain.Entry) http.HandlerFunc { - return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { - // TODO(toby3d): handle home page. - // TODO(toby3d): handle sections. - // TODO(toby3d): handle errors. - - // NOTE(toby3d): wrap founded entry into theme template and - // answer to client. - contentLanguage := make([]string, len(e.Translations)) - for i := range e.Translations { - contentLanguage[i] = e.Translations[i].Language.Code() - } - - w.Header().Set(common.HeaderContentLanguage, strings.Join(contentLanguage, ", ")) - - template, err := h.themes.Do(r.Context(), s, e) - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - - return - } - - w.Header().Set(common.HeaderContentType, common.MIMETextHTMLCharsetUTF8) - if err = template(w); err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - } - }) -}