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 0000000..538dcf9 Binary files /dev/null and b/fixtures/image.png differ 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",