From 1e5ea053cc243e6e4fa0af90d0c6b62128e8394d Mon Sep 17 00:00:00 2001 From: Niko Abeler Date: Sat, 14 Jan 2023 11:28:13 +0100 Subject: [PATCH] photo post type --- cmd/owl/web/editor_handler.go | 65 +++++++++++++++++++++++++-- cmd/owl/web/editor_test.go | 64 +++++++++++++++++++++++++++ embed/editor/editor.html | 80 ++++++++++++++++++++++------------ embed/photo/detail.html | 52 ++++++++++++++++++++++ embed/recipe/detail.html | 19 ++++++++ fixtures/image.png | Bin 0 -> 2971 bytes post.go | 3 ++ 7 files changed, 251 insertions(+), 32 deletions(-) create mode 100644 embed/photo/detail.html create mode 100644 fixtures/image.png diff --git a/cmd/owl/web/editor_handler.go b/cmd/owl/web/editor_handler.go index 1af16b0..8d1e4e2 100644 --- a/cmd/owl/web/editor_handler.go +++ b/cmd/owl/web/editor_handler.go @@ -3,7 +3,11 @@ package web import ( "fmt" "h4kor/owl-blogs" + "io" + "mime/multipart" "net/http" + "os" + "path" "strings" "sync" "time" @@ -174,7 +178,12 @@ func userEditorPostHandler(repo *owl.Repository) func(http.ResponseWriter, *http return } - err = r.ParseForm() + if strings.Split(r.Header.Get("Content-Type"), ";")[0] == "multipart/form-data" { + err = r.ParseMultipartForm(32 << 20) + } else { + err = r.ParseForm() + } + if err != nil { println("Error parsing form: ", err.Error()) w.WriteHeader(http.StatusInternalServerError) @@ -213,6 +222,23 @@ func userEditorPostHandler(repo *owl.Repository) func(http.ResponseWriter, *http reply_url := r.Form.Get("reply_url") bookmark_url := r.Form.Get("bookmark_url") + // photo values + var photo_file multipart.File + var photo_header *multipart.FileHeader + if strings.Split(r.Header.Get("Content-Type"), ";")[0] == "multipart/form-data" { + photo_file, photo_header, err = r.FormFile("photo") + if err != nil && err != http.ErrMissingFile { + println("Error getting photo file: ", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + html, _ := owl.RenderUserError(user, owl.ErrorMessage{ + Error: "Internal server error", + Message: "Internal server error", + }) + w.Write([]byte(html)) + return + } + } + // validate form values if post_type == "" { html, _ := owl.RenderUserError(user, owl.ErrorMessage{ @@ -246,12 +272,20 @@ func userEditorPostHandler(repo *owl.Repository) func(http.ResponseWriter, *http w.Write([]byte(html)) return } + if post_type == "photo" && photo_file == nil { + html, _ := owl.RenderUserError(user, owl.ErrorMessage{ + Error: "Missing Photo", + Message: "You must provide a photo to upload", + }) + w.Write([]byte(html)) + return + } // TODO: scrape reply_url for title and description // TODO: scrape bookmark_url for title and description // create post - post, err := user.CreateNewPost(owl.PostMeta{ + meta := owl.PostMeta{ Type: post_type, Title: title, Description: description, @@ -268,7 +302,32 @@ func userEditorPostHandler(repo *owl.Repository) func(http.ResponseWriter, *http Ingredients: strings.Split(recipe_ingredients, "\n"), Duration: recipe_duration, }, - }, content) + } + + if photo_file != nil { + meta.PhotoPath = photo_header.Filename + } + + post, err := user.CreateNewPost(meta, content) + + // save photo + if photo_file != nil { + println("Saving photo: ", photo_header.Filename) + photo_path := path.Join(post.MediaDir(), photo_header.Filename) + media_file, err := os.Create(photo_path) + if err != nil { + println("Error creating photo file: ", err.Error()) + w.WriteHeader(http.StatusInternalServerError) + html, _ := owl.RenderUserError(user, owl.ErrorMessage{ + Error: "Internal server error", + Message: "Internal server error", + }) + w.Write([]byte(html)) + return + } + defer media_file.Close() + io.Copy(media_file, photo_file) + } if err != nil { println("Error creating post: ", err.Error()) diff --git a/cmd/owl/web/editor_test.go b/cmd/owl/web/editor_test.go index 1086d45..46268e6 100644 --- a/cmd/owl/web/editor_test.go +++ b/cmd/owl/web/editor_test.go @@ -1,14 +1,18 @@ package web_test import ( + "bytes" "h4kor/owl-blogs" main "h4kor/owl-blogs/cmd/owl/web" "h4kor/owl-blogs/test/assertions" "h4kor/owl-blogs/test/mocks" "io" + "io/ioutil" + "mime/multipart" "net/http" "net/http/httptest" "net/url" + "path" "strconv" "strings" "testing" @@ -280,3 +284,63 @@ func TestEditorPostWithSessionRecipe(t *testing.T) { assertions.AssertStatus(t, rr, http.StatusFound) assertions.AssertEqual(t, rr.Header().Get("Location"), post.FullUrl()) } + +func TestEditorPostWithSessionPhoto(t *testing.T) { + repo, user := getSingleUserTestRepo() + user.ResetPassword("testpassword") + sessionId := user.CreateNewSession() + + csrfToken := "test_csrf_token" + + // read photo from file + photo_data, err := ioutil.ReadFile("../../../fixtures/image.png") + assertions.AssertNoError(t, err, "Error reading photo") + + // Create Request and Response + bodyBuf := &bytes.Buffer{} + bodyWriter := multipart.NewWriter(bodyBuf) + + // write photo + fileWriter, err := bodyWriter.CreateFormFile("photo", "../../../fixtures/image.png") + assertions.AssertNoError(t, err, "Error creating form file") + _, err = fileWriter.Write(photo_data) + assertions.AssertNoError(t, err, "Error writing photo") + + // write other fields + bodyWriter.WriteField("type", "photo") + bodyWriter.WriteField("title", "testtitle") + bodyWriter.WriteField("content", "testcontent") + bodyWriter.WriteField("csrf_token", csrfToken) + + // close body writer + err = bodyWriter.Close() + assertions.AssertNoError(t, err, "Error closing body writer") + + req, err := http.NewRequest("POST", user.EditorUrl(), bodyBuf) + req.Header.Add("Content-Type", "multipart/form-data; boundary="+bodyWriter.Boundary()) + req.Header.Add("Content-Length", strconv.Itoa(len(bodyBuf.Bytes()))) + req.AddCookie(&http.Cookie{Name: "csrf_token", Value: csrfToken}) + req.AddCookie(&http.Cookie{Name: "session", Value: sessionId}) + assertions.AssertNoError(t, err, "Error creating request") + rr := httptest.NewRecorder() + router := main.SingleUserRouter(&repo) + router.ServeHTTP(rr, req) + + assertions.AssertStatus(t, rr, http.StatusFound) + + posts, _ := user.AllPosts() + assertions.AssertEqual(t, len(posts), 1) + post := posts[0] + assertions.AssertEqual(t, rr.Header().Get("Location"), post.FullUrl()) + + assertions.AssertNotEqual(t, post.Meta().PhotoPath, "") + ret_photo_data, err := ioutil.ReadFile(path.Join(post.MediaDir(), post.Meta().PhotoPath)) + assertions.AssertNoError(t, err, "Error reading photo") + assertions.AssertEqual(t, len(photo_data), len(ret_photo_data)) + if len(photo_data) == len(ret_photo_data) { + for i := range photo_data { + assertions.AssertEqual(t, photo_data[i], ret_photo_data[i]) + } + } + +} diff --git a/embed/editor/editor.html b/embed/editor/editor.html index 5221919..7f98edc 100644 --- a/embed/editor/editor.html +++ b/embed/editor/editor.html @@ -20,6 +20,57 @@ +
+ Upload Photo +
+

Upload Photo

+ + + + + + + + + + + + + +

+ +
+
+ +
+ Write Recipe +
+

Create new Recipe

+ + + + + + + + + + + + + + + + + + + + +

+ +
+
+
Write Note
@@ -54,35 +105,6 @@
-
- Write Recipe -
-

Create new Recipe

- - - - - - - - - - - - - - - - - - - - -

- -
-
-
Bookmark
diff --git a/embed/photo/detail.html b/embed/photo/detail.html new file mode 100644 index 0000000..b80e40a --- /dev/null +++ b/embed/photo/detail.html @@ -0,0 +1,52 @@ +
+
+

{{.Title}}

+ + # + Published: + + {{ if .Post.User.Config.AuthorName }} + by + + {{ if .Post.User.AvatarUrl }} + + {{ end }} + {{.Post.User.Config.AuthorName}} + + {{ end }} + +
+
+
+ + {{ if .Post.Meta.PhotoPath }} + {{.Post.Meta.Description}} + {{ end }} + +
+ {{.Content}} +
+ +
+ {{if .Post.ApprovedIncomingWebmentions}} +

+ Webmentions +

+ + {{end}} +
\ No newline at end of file diff --git a/embed/recipe/detail.html b/embed/recipe/detail.html index 02dcc8e..2b73808 100644 --- a/embed/recipe/detail.html +++ b/embed/recipe/detail.html @@ -56,4 +56,23 @@ +
+ {{if .Post.ApprovedIncomingWebmentions}} +

+ Webmentions +

+ + {{end}} \ No newline at end of file diff --git a/fixtures/image.png b/fixtures/image.png new file mode 100644 index 0000000000000000000000000000000000000000..538dcf9b926adeefbc840f544f550b0c41dfc8ff GIT binary patch literal 2971 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 Bk@)}s literal 0 HcmV?d00001 diff --git a/post.go b/post.go index e71ffe3..c759fd7 100644 --- a/post.go +++ b/post.go @@ -88,6 +88,7 @@ type PostMeta struct { Reply ReplyData `yaml:"reply"` Bookmark BookmarkData `yaml:"bookmark"` Recipe RecipeData `yaml:"recipe"` + PhotoPath string `yaml:"photo"` } func (pm PostMeta) FormattedDate() string { @@ -104,6 +105,7 @@ func (pm *PostMeta) UnmarshalYAML(unmarshal func(interface{}) error) error { Reply ReplyData `yaml:"reply"` Bookmark BookmarkData `yaml:"bookmark"` Recipe RecipeData `yaml:"recipe"` + PhotoPath string `yaml:"photo"` } type S struct { Date string `yaml:"date"` @@ -129,6 +131,7 @@ func (pm *PostMeta) UnmarshalYAML(unmarshal func(interface{}) error) error { pm.Reply = t.Reply pm.Bookmark = t.Bookmark pm.Recipe = t.Recipe + pm.PhotoPath = t.PhotoPath possibleFormats := []string{ "2006-01-02",