From 6bc70f0a0ed5d8daca6f8cbc6224c2b5e1d62912 Mon Sep 17 00:00:00 2001 From: Jan-Lukas Else Date: Wed, 23 Jun 2021 19:20:50 +0200 Subject: [PATCH] Various refactorings --- activityPub.go | 59 +++++----- captcha_test.go | 4 +- editor.go | 10 +- httpClient_test.go | 2 +- mediaCompression_test.go | 4 +- mediaStorage.go | 9 +- mediaStorage_test.go | 30 +++++ micropub.go | 238 +++++++++++++++++---------------------- postsFuncs.go | 32 ++++++ render.go | 80 +++++++------ templates/editor.gohtml | 6 +- 11 files changed, 262 insertions(+), 212 deletions(-) create mode 100644 mediaStorage_test.go diff --git a/activityPub.go b/activityPub.go index 1f26f62..18da9b3 100644 --- a/activityPub.go +++ b/activityPub.go @@ -82,22 +82,23 @@ func (a *goBlog) apHandleWebfinger(w http.ResponseWriter, r *http.Request) { a.serveError(w, r, "Resource not found", http.StatusNotFound) return } + apIri := a.apIri(blog) b, _ := json.Marshal(map[string]interface{}{ - "subject": a.webfingerAccts[a.apIri(blog)], + "subject": a.webfingerAccts[apIri], "aliases": []string{ - a.webfingerAccts[a.apIri(blog)], - a.apIri(blog), + a.webfingerAccts[apIri], + apIri, }, "links": []map[string]string{ { "rel": "self", "type": contenttype.AS, - "href": a.apIri(blog), + "href": apIri, }, { "rel": "http://webfinger.net/rel/profile-page", "type": "text/html", - "href": a.apIri(blog), + "href": apIri, }, }, }) @@ -107,8 +108,8 @@ func (a *goBlog) apHandleWebfinger(w http.ResponseWriter, r *http.Request) { func (a *goBlog) apHandleInbox(w http.ResponseWriter, r *http.Request) { blogName := chi.URLParam(r, "blog") - blog := a.cfg.Blogs[blogName] - if blog == nil { + blog, ok := a.cfg.Blogs[blogName] + if !ok || blog == nil { a.serveError(w, r, "Inbox not found", http.StatusNotFound) return } @@ -159,31 +160,28 @@ func (a *goBlog) apHandleInbox(w http.ResponseWriter, r *http.Request) { case "Undo": { if object, ok := activity["object"].(map[string]interface{}); ok { - if objectType, ok := object["type"].(string); ok && objectType == "Follow" { - if iri, ok := object["actor"].(string); ok && iri == activityActor { - _ = a.db.apRemoveFollower(blogName, activityActor) - } + ot := cast.ToString(object["type"]) + actor := cast.ToString(object["actor"]) + if ot == "Follow" && actor == activityActor { + _ = a.db.apRemoveFollower(blogName, activityActor) } } } case "Create": { if object, ok := activity["object"].(map[string]interface{}); ok { - inReplyTo := cast.ToString(object["inReplyTo"]) - objectId := cast.ToString(object["id"]) - objectUrl := cast.ToString(object["url"]) - baseUrl := objectId - if objectUrl != "" { - baseUrl = objectUrl + baseUrl := cast.ToString(object["id"]) + if ou := cast.ToString(object["url"]); ou != "" { + baseUrl = ou } - if inReplyTo != "" && objectId != "" && strings.Contains(inReplyTo, blogIri) { + if r := cast.ToString(object["inReplyTo"]); r != "" && baseUrl != "" && strings.HasPrefix(r, blogIri) { // It's an ActivityPub reply; save reply as webmention - _ = a.createWebmention(baseUrl, inReplyTo) - } else if content, hasContent := object["content"].(string); hasContent && objectId != "" { + _ = a.createWebmention(baseUrl, r) + } else if content := cast.ToString(object["content"]); content != "" && baseUrl != "" { // May be a mention; find links to blog and save them as webmentions if links, err := allLinksFromHTMLString(content, baseUrl); err == nil { for _, link := range links { - if strings.Contains(link, blogIri) { + if strings.HasPrefix(link, blogIri) { _ = a.createWebmention(baseUrl, link) } } @@ -191,25 +189,22 @@ func (a *goBlog) apHandleInbox(w http.ResponseWriter, r *http.Request) { } } } - case "Delete": - case "Block": + case "Delete", "Block": { - if object, ok := activity["object"].(string); ok && len(object) > 0 && object == activityActor { + if o := cast.ToString(activity["object"]); o == activityActor { _ = a.db.apRemoveFollower(blogName, activityActor) } } case "Like": { - likeObject, likeObjectOk := activity["object"].(string) - if likeObjectOk && len(likeObject) > 0 && strings.Contains(likeObject, blogIri) { - a.sendNotification(fmt.Sprintf("%s liked %s", activityActor, likeObject)) + if o := cast.ToString(activity["object"]); o != "" && strings.HasPrefix(o, blogIri) { + a.sendNotification(fmt.Sprintf("%s liked %s", activityActor, o)) } } case "Announce": { - announceObject, announceObjectOk := activity["object"].(string) - if announceObjectOk && len(announceObject) > 0 && strings.Contains(announceObject, blogIri) { - a.sendNotification(fmt.Sprintf("%s announced %s", activityActor, announceObject)) + if o := cast.ToString(activity["object"]); o != "" && strings.HasPrefix(o, blogIri) { + a.sendNotification(fmt.Sprintf("%s announced %s", activityActor, o)) } } } @@ -230,11 +225,11 @@ func (a *goBlog) apVerifySignature(r *http.Request) (*asPerson, string, int, err return nil, keyID, statusCode, err } if actor.PublicKey == nil || actor.PublicKey.PublicKeyPem == "" { - return nil, keyID, 0, errors.New("Actor has no public key") + return nil, keyID, 0, errors.New("actor has no public key") } block, _ := pem.Decode([]byte(actor.PublicKey.PublicKeyPem)) if block == nil { - return nil, keyID, 0, errors.New("Public key invalid") + return nil, keyID, 0, errors.New("public key invalid") } pubKey, err := x509.ParsePKIXPublicKey(block.Bytes) if err != nil { diff --git a/captcha_test.go b/captcha_test.go index abc3372..3769433 100644 --- a/captcha_test.go +++ b/captcha_test.go @@ -32,7 +32,7 @@ func Test_captchaMiddleware(t *testing.T) { _ = app.initRendering() h := app.captchaMiddleware(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { - rw.Write([]byte("ABC Test")) + _, _ = rw.Write([]byte("ABC Test")) })) t.Run("Default", func(t *testing.T) { @@ -58,7 +58,7 @@ func Test_captchaMiddleware(t *testing.T) { session, _ := app.captchaSessions.Get(req, "c") session.Values["captcha"] = true - session.Save(req, rec1) + _ = session.Save(req, rec1) for _, cookie := range rec1.Result().Cookies() { req.AddCookie(cookie) diff --git a/editor.go b/editor.go index dfece7c..86803ed 100644 --- a/editor.go +++ b/editor.go @@ -50,7 +50,7 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) { BlogString: blog, Data: map[string]interface{}{ "UpdatePostURL": parsedURL.String(), - "UpdatePostContent": a.toMfItem(post).Properties.Content[0], + "UpdatePostContent": a.postToMfItem(post).Properties.Content[0], "Drafts": a.db.getDrafts(blog), }, }) @@ -77,6 +77,14 @@ func (a *goBlog) serveEditorPost(w http.ResponseWriter, r *http.Request) { a.editorMicropubPost(w, req, false) case "upload": a.editorMicropubPost(w, r, true) + case "viewdraft": + parsedURL, err := url.Parse(r.FormValue("url")) + if err != nil { + a.serveError(w, r, err.Error(), http.StatusBadRequest) + return + } + http.Redirect(w, r, parsedURL.Path, http.StatusFound) + return default: a.serveError(w, r, "Unknown editoraction", http.StatusBadRequest) } diff --git a/httpClient_test.go b/httpClient_test.go index cb1ec05..06f144d 100644 --- a/httpClient_test.go +++ b/httpClient_test.go @@ -36,7 +36,7 @@ func (c *fakeHttpClient) setHandler(handler http.Handler) { func (c *fakeHttpClient) setFakeResponse(statusCode int, body string) { c.setHandler(http.HandlerFunc(func(rw http.ResponseWriter, r *http.Request) { rw.WriteHeader(statusCode) - rw.Write([]byte(body)) + _, _ = rw.Write([]byte(body)) })) } diff --git a/mediaCompression_test.go b/mediaCompression_test.go index 3e359e8..dbe1638 100644 --- a/mediaCompression_test.go +++ b/mediaCompression_test.go @@ -32,7 +32,7 @@ func Test_compress(t *testing.T) { assert.Equal(t, "https://www.cloudflare.com/cdn-cgi/image/f=jpeg,q=75,metadata=none,fit=scale-down,w=2000,h=3000/https://example.com/original.jpg", r.URL.String()) rw.WriteHeader(http.StatusOK) - rw.Write([]byte(fakeFileContent)) + _, _ = rw.Write([]byte(fakeFileContent)) })) cf := &cloudflare{} @@ -59,7 +59,7 @@ func Test_compress(t *testing.T) { assert.Equal(t, "https://example.com/original.jpg", requestJson["url"]) rw.WriteHeader(http.StatusOK) - rw.Write([]byte(fakeFileContent)) + _, _ = rw.Write([]byte(fakeFileContent)) })) cf := &shortpixel{"testkey"} diff --git a/mediaStorage.go b/mediaStorage.go index d0d2dea..4f6d968 100644 --- a/mediaStorage.go +++ b/mediaStorage.go @@ -40,10 +40,13 @@ type mediaStorage interface { type localMediaStorage struct { mediaURL string // optional + path string // required } func (a *goBlog) initLocalMediaStorage() mediaStorage { - ms := &localMediaStorage{} + ms := &localMediaStorage{ + path: mediaFilePath, + } if config := a.cfg.Micropub.MediaStorage; config != nil && config.MediaURL != "" { ms.mediaURL = config.MediaURL } @@ -51,10 +54,10 @@ func (a *goBlog) initLocalMediaStorage() mediaStorage { } func (l *localMediaStorage) save(filename string, file io.Reader) (location string, err error) { - if err = os.MkdirAll(mediaFilePath, 0644); err != nil { + if err = os.MkdirAll(l.path, 0644); err != nil { return "", err } - newFile, err := os.Create(filepath.Join(mediaFilePath, filename)) + newFile, err := os.Create(filepath.Join(l.path, filename)) if err != nil { return "", err } diff --git a/mediaStorage_test.go b/mediaStorage_test.go new file mode 100644 index 0000000..1a50588 --- /dev/null +++ b/mediaStorage_test.go @@ -0,0 +1,30 @@ +package main + +import ( + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func Test_localMediaStorage(t *testing.T) { + testDir := t.TempDir() + + l := &localMediaStorage{ + mediaURL: "https://example.com", + path: testDir, + } + + testFileContent := "This is a test" + + loc, err := l.save("test.txt", strings.NewReader(testFileContent)) + require.Nil(t, err) + assert.Equal(t, "https://example.com/test.txt", loc) + + file, err := os.ReadFile(filepath.Join(testDir, "test.txt")) + require.Nil(t, err) + assert.Equal(t, testFileContent, string(file)) +} diff --git a/micropub.go b/micropub.go index 37e53eb..bf51943 100644 --- a/micropub.go +++ b/micropub.go @@ -3,7 +3,8 @@ package main import ( "encoding/json" "errors" - "fmt" + "io" + "mime" "net/http" "net/url" "reflect" @@ -45,7 +46,7 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) { a.serveError(w, r, err.Error(), http.StatusBadRequest) return } - mf = a.toMfItem(p) + mf = a.postToMfItem(p) } else { limit, _ := strconv.Atoi(r.URL.Query().Get("limit")) offset, _ := strconv.Atoi(r.URL.Query().Get("offset")) @@ -59,7 +60,7 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) { } list := map[string][]*microformatItem{} for _, p := range posts { - list["items"] = append(list["items"], a.toMfItem(p)) + list["items"] = append(list["items"], a.postToMfItem(p)) } mf = list } @@ -88,138 +89,73 @@ func (a *goBlog) serveMicropubQuery(w http.ResponseWriter, r *http.Request) { } } -func (a *goBlog) toMfItem(p *post) *microformatItem { - params := p.Parameters - params["path"] = []string{p.Path} - params["section"] = []string{p.Section} - params["blog"] = []string{p.Blog} - params["published"] = []string{p.Published} - params["updated"] = []string{p.Updated} - params["status"] = []string{string(p.Status)} - pb, _ := yaml.Marshal(p.Parameters) - content := fmt.Sprintf("---\n%s---\n%s", string(pb), p.Content) - return µformatItem{ - Type: []string{"h-entry"}, - Properties: µformatProperties{ - Name: p.Parameters["title"], - Published: []string{p.Published}, - Updated: []string{p.Updated}, - PostStatus: []string{string(p.Status)}, - Category: p.Parameters[a.cfg.Micropub.CategoryParam], - Content: []string{content}, - URL: []string{a.fullPostURL(p)}, - InReplyTo: p.Parameters[a.cfg.Micropub.ReplyParam], - LikeOf: p.Parameters[a.cfg.Micropub.LikeParam], - BookmarkOf: p.Parameters[a.cfg.Micropub.BookmarkParam], - MpSlug: []string{p.Slug}, - Audio: p.Parameters[a.cfg.Micropub.AudioParam], - // TODO: Photos - }, - } -} - func (a *goBlog) serveMicropubPost(w http.ResponseWriter, r *http.Request) { defer r.Body.Close() - var p *post - if ct := r.Header.Get(contentType); strings.Contains(ct, contenttype.WWWForm) || strings.Contains(ct, contenttype.MultipartForm) { - var err error - if strings.Contains(ct, contenttype.MultipartForm) { - err = r.ParseMultipartForm(0) - } else { - err = r.ParseForm() - } - if err != nil { - a.serveError(w, r, err.Error(), http.StatusBadRequest) + switch mt, _, _ := mime.ParseMediaType(r.Header.Get(contentType)); mt { + case contenttype.WWWForm, contenttype.MultipartForm: + _ = r.ParseForm() + _ = r.ParseMultipartForm(0) + if r.Form == nil { + a.serveError(w, r, "Failed to parse form", http.StatusBadRequest) return } if action := micropubAction(r.Form.Get("action")); action != "" { - u, err := url.Parse(r.Form.Get("url")) - if err != nil { - a.serveError(w, r, err.Error(), http.StatusBadRequest) - return + switch action { + case actionDelete: + a.micropubDelete(w, r, r.Form.Get("url")) + default: + a.serveError(w, r, "Action not supported", http.StatusNotImplemented) } - if action == actionDelete { - a.micropubDelete(w, r, u) - return - } - a.serveError(w, r, "Action not supported", http.StatusNotImplemented) return } - p, err = a.convertMPValueMapToPost(r.Form) - if err != nil { - a.serveError(w, r, err.Error(), http.StatusInternalServerError) - return - } - } else if strings.Contains(ct, contenttype.JSON) { + a.micropubCreatePostFromForm(w, r) + case contenttype.JSON: parsedMfItem := µformatItem{} - err := json.NewDecoder(r.Body).Decode(parsedMfItem) - if err != nil { - a.serveError(w, r, err.Error(), http.StatusInternalServerError) + b, _ := io.ReadAll(io.LimitReader(r.Body, 10000000)) // 10 MB + if err := json.Unmarshal(b, parsedMfItem); err != nil { + a.serveError(w, r, err.Error(), http.StatusBadRequest) return } if parsedMfItem.Action != "" { - u, err := url.Parse(parsedMfItem.URL) - if err != nil { - a.serveError(w, r, err.Error(), http.StatusBadRequest) - return + switch parsedMfItem.Action { + case actionDelete: + a.micropubDelete(w, r, parsedMfItem.URL) + case actionUpdate: + a.micropubUpdate(w, r, parsedMfItem.URL, parsedMfItem) + default: + a.serveError(w, r, "Action not supported", http.StatusNotImplemented) } - if parsedMfItem.Action == actionDelete { - a.micropubDelete(w, r, u) - return - } - if parsedMfItem.Action == actionUpdate { - a.micropubUpdate(w, r, u, parsedMfItem) - return - } - a.serveError(w, r, "Action not supported", http.StatusNotImplemented) return } - p, err = a.convertMPMfToPost(parsedMfItem) - if err != nil { - a.serveError(w, r, err.Error(), http.StatusInternalServerError) - return - } - } else { + a.micropubCreatePostFromJson(w, r, parsedMfItem) + default: a.serveError(w, r, "wrong content type", http.StatusBadRequest) - return } - if !strings.Contains(r.Context().Value(indieAuthScope).(string), "create") { - a.serveError(w, r, "create scope missing", http.StatusForbidden) - return - } - err := a.createPost(p) - if err != nil { - a.serveError(w, r, err.Error(), http.StatusInternalServerError) - return - } - http.Redirect(w, r, a.fullPostURL(p), http.StatusAccepted) } -func (a *goBlog) convertMPValueMapToPost(values map[string][]string) (*post, error) { +func (a *goBlog) micropubParseValuePostParamsValueMap(entry *post, values map[string][]string) error { if h, ok := values["h"]; ok && (len(h) != 1 || h[0] != "entry") { - return nil, errors.New("only entry type is supported so far") + return errors.New("only entry type is supported so far") } delete(values, "h") - entry := &post{ - Parameters: map[string][]string{}, - } - if content, ok := values["content"]; ok { + entry.Parameters = map[string][]string{} + if content, ok := values["content"]; ok && len(content) > 0 { entry.Content = content[0] delete(values, "content") } - if published, ok := values["published"]; ok { + if published, ok := values["published"]; ok && len(published) > 0 { entry.Published = published[0] delete(values, "published") } - if updated, ok := values["updated"]; ok { + if updated, ok := values["updated"]; ok && len(updated) > 0 { entry.Updated = updated[0] delete(values, "updated") } - if status, ok := values["post-status"]; ok { + if status, ok := values["post-status"]; ok && len(status) > 0 { entry.Status = postStatus(status[0]) delete(values, "post-status") } - if slug, ok := values["mp-slug"]; ok { + if slug, ok := values["mp-slug"]; ok && len(slug) > 0 { entry.Slug = slug[0] delete(values, "mp-slug") } @@ -275,11 +211,7 @@ func (a *goBlog) convertMPValueMapToPost(values map[string][]string) (*post, err for n, p := range values { entry.Parameters[n] = append(entry.Parameters[n], p...) } - err := a.computeExtraPostParameters(entry) - if err != nil { - return nil, err - } - return entry, nil + return nil } type micropubAction string @@ -315,40 +247,44 @@ type microformatProperties struct { Audio []string `json:"audio,omitempty"` } -func (a *goBlog) convertMPMfToPost(mf *microformatItem) (*post, error) { +func (a *goBlog) micropubParsePostParamsMfItem(entry *post, mf *microformatItem) error { if len(mf.Type) != 1 || mf.Type[0] != "h-entry" { - return nil, errors.New("only entry type is supported so far") + return errors.New("only entry type is supported so far") } - entry := &post{ - Parameters: map[string][]string{}, + entry.Parameters = map[string][]string{} + if mf.Properties == nil { + return nil } // Content - if mf.Properties != nil && len(mf.Properties.Content) == 1 && len(mf.Properties.Content[0]) > 0 { + if len(mf.Properties.Content) > 0 && mf.Properties.Content[0] != "" { entry.Content = mf.Properties.Content[0] } - if len(mf.Properties.Published) == 1 { + if len(mf.Properties.Published) > 0 { entry.Published = mf.Properties.Published[0] } - if len(mf.Properties.Updated) == 1 { + if len(mf.Properties.Updated) > 0 { entry.Updated = mf.Properties.Updated[0] } - if len(mf.Properties.PostStatus) == 1 { + if len(mf.Properties.PostStatus) > 0 { entry.Status = postStatus(mf.Properties.PostStatus[0]) } + if len(mf.Properties.MpSlug) > 0 { + entry.Slug = mf.Properties.MpSlug[0] + } // Parameter - if len(mf.Properties.Name) == 1 { + if len(mf.Properties.Name) > 0 { entry.Parameters["title"] = mf.Properties.Name } if len(mf.Properties.Category) > 0 { entry.Parameters[a.cfg.Micropub.CategoryParam] = mf.Properties.Category } - if len(mf.Properties.InReplyTo) == 1 { + if len(mf.Properties.InReplyTo) > 0 { entry.Parameters[a.cfg.Micropub.ReplyParam] = mf.Properties.InReplyTo } - if len(mf.Properties.LikeOf) == 1 { + if len(mf.Properties.LikeOf) > 0 { entry.Parameters[a.cfg.Micropub.LikeParam] = mf.Properties.LikeOf } - if len(mf.Properties.BookmarkOf) == 1 { + if len(mf.Properties.BookmarkOf) > 0 { entry.Parameters[a.cfg.Micropub.BookmarkParam] = mf.Properties.BookmarkOf } if len(mf.Properties.Audio) > 0 { @@ -365,15 +301,7 @@ func (a *goBlog) convertMPMfToPost(mf *microformatItem) (*post, error) { } } } - if len(mf.Properties.MpSlug) == 1 { - entry.Slug = mf.Properties.MpSlug[0] - } - err := a.computeExtraPostParameters(entry) - if err != nil { - return nil, err - } - return entry, nil - + return nil } func (a *goBlog) computeExtraPostParameters(p *post) error { @@ -456,24 +384,70 @@ func (a *goBlog) computeExtraPostParameters(p *post) error { return nil } -func (a *goBlog) micropubDelete(w http.ResponseWriter, r *http.Request, u *url.URL) { +func (a *goBlog) micropubCreatePostFromForm(w http.ResponseWriter, r *http.Request) { + p := &post{} + err := a.micropubParseValuePostParamsValueMap(p, r.Form) + if err != nil { + a.serveError(w, r, err.Error(), http.StatusBadRequest) + return + } + a.micropubCreate(w, r, p) +} + +func (a *goBlog) micropubCreatePostFromJson(w http.ResponseWriter, r *http.Request, parsedMfItem *microformatItem) { + p := &post{} + err := a.micropubParsePostParamsMfItem(p, parsedMfItem) + if err != nil { + a.serveError(w, r, err.Error(), http.StatusBadRequest) + return + } + a.micropubCreate(w, r, p) +} + +func (a *goBlog) micropubCreate(w http.ResponseWriter, r *http.Request, p *post) { + if !strings.Contains(r.Context().Value(indieAuthScope).(string), "create") { + a.serveError(w, r, "create scope missing", http.StatusForbidden) + return + } + if err := a.computeExtraPostParameters(p); err != nil { + a.serveError(w, r, err.Error(), http.StatusBadRequest) + return + } + if err := a.createPost(p); err != nil { + a.serveError(w, r, err.Error(), http.StatusBadRequest) + return + } + http.Redirect(w, r, a.fullPostURL(p), http.StatusAccepted) +} + +func (a *goBlog) micropubDelete(w http.ResponseWriter, r *http.Request, u string) { if !strings.Contains(r.Context().Value(indieAuthScope).(string), "delete") { a.serveError(w, r, "delete scope missing", http.StatusForbidden) return } - if err := a.deletePost(u.Path); err != nil { + uu, err := url.Parse(u) + if err != nil { a.serveError(w, r, err.Error(), http.StatusBadRequest) return } - http.Redirect(w, r, u.String(), http.StatusNoContent) + if err := a.deletePost(uu.Path); err != nil { + a.serveError(w, r, err.Error(), http.StatusBadRequest) + return + } + http.Redirect(w, r, uu.String(), http.StatusNoContent) } -func (a *goBlog) micropubUpdate(w http.ResponseWriter, r *http.Request, u *url.URL, mf *microformatItem) { +func (a *goBlog) micropubUpdate(w http.ResponseWriter, r *http.Request, u string, mf *microformatItem) { if !strings.Contains(r.Context().Value(indieAuthScope).(string), "update") { a.serveError(w, r, "update scope missing", http.StatusForbidden) return } - p, err := a.db.getPost(u.Path) + uu, err := url.Parse(u) + if err != nil { + a.serveError(w, r, err.Error(), http.StatusBadRequest) + return + } + p, err := a.db.getPost(uu.Path) if err != nil { a.serveError(w, r, err.Error(), http.StatusBadRequest) return diff --git a/postsFuncs.go b/postsFuncs.go index e70a08e..b322bfa 100644 --- a/postsFuncs.go +++ b/postsFuncs.go @@ -1,6 +1,7 @@ package main import ( + "fmt" "html/template" "log" "strings" @@ -9,6 +10,7 @@ import ( gogeouri "git.jlel.se/jlelse/go-geouri" "github.com/PuerkitoBio/goquery" "github.com/araddon/dateparse" + "gopkg.in/yaml.v3" ) func (a *goBlog) fullPostURL(p *post) string { @@ -117,6 +119,36 @@ func (p *post) isPublishedSectionPost() bool { return p.Published != "" && p.Section != "" && p.Status == statusPublished } +func (a *goBlog) postToMfItem(p *post) *microformatItem { + params := p.Parameters + params["path"] = []string{p.Path} + params["section"] = []string{p.Section} + params["blog"] = []string{p.Blog} + params["published"] = []string{p.Published} + params["updated"] = []string{p.Updated} + params["status"] = []string{string(p.Status)} + pb, _ := yaml.Marshal(p.Parameters) + content := fmt.Sprintf("---\n%s---\n%s", string(pb), p.Content) + return µformatItem{ + Type: []string{"h-entry"}, + Properties: µformatProperties{ + Name: p.Parameters["title"], + Published: []string{p.Published}, + Updated: []string{p.Updated}, + PostStatus: []string{string(p.Status)}, + Category: p.Parameters[a.cfg.Micropub.CategoryParam], + Content: []string{content}, + URL: []string{a.fullPostURL(p)}, + InReplyTo: p.Parameters[a.cfg.Micropub.ReplyParam], + LikeOf: p.Parameters[a.cfg.Micropub.LikeParam], + BookmarkOf: p.Parameters[a.cfg.Micropub.BookmarkParam], + MpSlug: []string{p.Slug}, + Audio: p.Parameters[a.cfg.Micropub.AudioParam], + // TODO: Photos + }, + } +} + // Public because of rendering func (p *post) Title() string { diff --git a/render.go b/render.go index 41f093a..f4ddd76 100644 --- a/render.go +++ b/render.go @@ -112,42 +112,9 @@ func (a *goBlog) render(w http.ResponseWriter, r *http.Request, template string, func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, statusCode int, template string, data *renderData) { // Server timing t := servertiming.FromContext(r.Context()).NewMetric("r").Start() + defer t.Stop() // Check render data - if data.User == nil { - data.User = a.cfg.User - } - if data.Blog == nil { - if len(data.BlogString) == 0 { - data.BlogString = a.cfg.DefaultBlog - } - data.Blog = a.cfg.Blogs[data.BlogString] - } - if data.BlogString == "" { - for s, b := range a.cfg.Blogs { - if b == data.Blog { - data.BlogString = s - break - } - } - } - if a.cfg.Server.Tor && a.torAddress != "" { - data.TorAddress = fmt.Sprintf("http://%v%v", a.torAddress, r.RequestURI) - } - if data.Data == nil { - data.Data = map[string]interface{}{} - } - // Check login - if loggedIn, ok := r.Context().Value(loggedInKey).(bool); ok && loggedIn { - data.LoggedIn = true - } - // Check if comments enabled - data.CommentsEnabled = data.Blog.Comments != nil && data.Blog.Comments.Enabled - // Check if able to receive webmentions - data.WebmentionReceivingEnabled = a.cfg.Webmention == nil || !a.cfg.Webmention.DisableReceiving - // Check if Tor request - if torUsed, ok := r.Context().Value(torUsedKey).(bool); ok && torUsed { - data.TorUsed = true - } + a.checkRenderData(r, data) // Set content type w.Header().Set(contentType, contenttype.HTMLUTF8) // Minify and write response @@ -163,8 +130,47 @@ func (a *goBlog) renderWithStatusCode(w http.ResponseWriter, r *http.Request, st http.Error(w, err.Error(), http.StatusInternalServerError) return } - // Server timing - t.Stop() +} + +func (a *goBlog) checkRenderData(r *http.Request, data *renderData) { + // User + if data.User == nil { + data.User = a.cfg.User + } + // Blog + if data.Blog == nil { + if data.BlogString == "" { + data.BlogString = a.cfg.DefaultBlog + } + data.Blog = a.cfg.Blogs[data.BlogString] + } + if data.BlogString == "" { + for s, b := range a.cfg.Blogs { + if b == data.Blog { + data.BlogString = s + break + } + } + } + // Tor + if a.cfg.Server.Tor && a.torAddress != "" { + data.TorAddress = fmt.Sprintf("http://%v%v", a.torAddress, r.RequestURI) + } + if torUsed, ok := r.Context().Value(torUsedKey).(bool); ok && torUsed { + data.TorUsed = true + } + // Check login + if loggedIn, ok := r.Context().Value(loggedInKey).(bool); ok && loggedIn { + data.LoggedIn = true + } + // Check if comments enabled + data.CommentsEnabled = data.Blog.Comments != nil && data.Blog.Comments.Enabled + // Check if able to receive webmentions + data.WebmentionReceivingEnabled = a.cfg.Webmention == nil || !a.cfg.Webmention.DisableReceiving + // Data + if data.Data == nil { + data.Data = map[string]interface{}{} + } } func (a *goBlog) includeRenderedTemplate(templateName string, data ...interface{}) (template.HTML, error) { diff --git a/templates/editor.gohtml b/templates/editor.gohtml index 6ee8366..acb59f5 100644 --- a/templates/editor.gohtml +++ b/templates/editor.gohtml @@ -50,16 +50,18 @@ tags: + {{ if .Data.Drafts }}

{{ string .Blog.Lang "drafts" }}

- + - +
+ {{ end }}

{{ string .Blog.Lang "location" }}