photo post type
This commit is contained in:
parent
c7834b08d5
commit
1e5ea053cc
|
@ -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
|
||||
}
|
||||
|
||||
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())
|
||||
|
|
|
@ -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])
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -20,6 +20,57 @@
|
|||
</form>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Upload Photo</summary>
|
||||
<form action="" method="post" enctype="multipart/form-data">
|
||||
<h2>Upload Photo</h2>
|
||||
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
|
||||
<input type="hidden" name="type" value="photo">
|
||||
|
||||
<label for="title">Title</label>
|
||||
<input type="text" name="title" placeholder="Title" />
|
||||
<label for="description">Description</label>
|
||||
<input type="text" name="description" placeholder="Description" />
|
||||
<label for="content">Content</label>
|
||||
<textarea name="content" placeholder="Content" rows="24"></textarea>
|
||||
|
||||
<label for="photo">Photo</label>
|
||||
<input type="file" name="photo" placeholder="Photo" />
|
||||
|
||||
<br><br>
|
||||
<input type="submit" value="Create Article" />
|
||||
</form>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Write Recipe</summary>
|
||||
<form action="" method="post">
|
||||
<h2>Create new Recipe</h2>
|
||||
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
|
||||
<input type="hidden" name="type" value="recipe">
|
||||
|
||||
<label for="title">Title</label>
|
||||
<input type="text" name="title" placeholder="Title" />
|
||||
|
||||
<label for="yield">Yield</label>
|
||||
<input type="text" name="yield" placeholder="Yield" />
|
||||
|
||||
<label for="duration">Duration</label>
|
||||
<input type="text" name="duration" placeholder="Duration" />
|
||||
|
||||
<label for="description">Description</label>
|
||||
<input type="text" name="description" placeholder="Description" />
|
||||
<label for="ingredients">Ingredients (1 per line)</label>
|
||||
<textarea name="ingredients" placeholder="Ingredients" rows="8"></textarea>
|
||||
|
||||
<label for="content">Instructions</label>
|
||||
<textarea name="content" placeholder="Ingredients" rows="24"></textarea>
|
||||
|
||||
<br><br>
|
||||
<input type="submit" value="Create Reply" />
|
||||
</form>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Write Note</summary>
|
||||
<form action="" method="post">
|
||||
|
@ -54,35 +105,6 @@
|
|||
</form>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Write Recipe</summary>
|
||||
<form action="" method="post">
|
||||
<h2>Create new Recipe</h2>
|
||||
<input type="hidden" name="csrf_token" value="{{.CsrfToken}}">
|
||||
<input type="hidden" name="type" value="recipe">
|
||||
|
||||
<label for="title">Title</label>
|
||||
<input type="text" name="title" placeholder="Title" />
|
||||
|
||||
<label for="yield">Yield</label>
|
||||
<input type="text" name="yield" placeholder="Yield" />
|
||||
|
||||
<label for="duration">Duration</label>
|
||||
<input type="text" name="duration" placeholder="Duration" />
|
||||
|
||||
<label for="description">Description</label>
|
||||
<input type="text" name="description" placeholder="Description" />
|
||||
<label for="ingredients">Ingredients (1 per line)</label>
|
||||
<textarea name="ingredients" placeholder="Ingredients" rows="8"></textarea>
|
||||
|
||||
<label for="content">Instructions</label>
|
||||
<textarea name="content" placeholder="Ingredients" rows="24"></textarea>
|
||||
|
||||
<br><br>
|
||||
<input type="submit" value="Create Reply" />
|
||||
</form>
|
||||
</details>
|
||||
|
||||
<details>
|
||||
<summary>Bookmark</summary>
|
||||
<form action="" method="post">
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<div class="h-entry">
|
||||
<hgroup>
|
||||
<h1 class="p-name">{{.Title}}</h1>
|
||||
<small>
|
||||
<a class="u-url" href="{{.Post.FullUrl}}">#</a>
|
||||
Published:
|
||||
<time class="dt-published" datetime="{{.Post.Meta.Date}}">
|
||||
{{.Post.Meta.FormattedDate}}
|
||||
</time>
|
||||
{{ if .Post.User.Config.AuthorName }}
|
||||
by
|
||||
<a class="p-author h-card" href="{{.Post.User.FullUrl}}">
|
||||
{{ if .Post.User.AvatarUrl }}
|
||||
<img class="u-photo u-logo" style="height: 1em;" src="{{ .Post.User.AvatarUrl }}"
|
||||
alt="{{ .Post.User.Config.Title }}" />
|
||||
{{ end }}
|
||||
{{.Post.User.Config.AuthorName}}
|
||||
</a>
|
||||
{{ end }}
|
||||
</small>
|
||||
</hgroup>
|
||||
<hr>
|
||||
<br>
|
||||
|
||||
{{ if .Post.Meta.PhotoPath }}
|
||||
<img class="u-photo" src="media/{{.Post.Meta.PhotoPath}}" alt="{{.Post.Meta.Description}}" />
|
||||
{{ end }}
|
||||
|
||||
<div class="e-content">
|
||||
{{.Content}}
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
{{if .Post.ApprovedIncomingWebmentions}}
|
||||
<h3>
|
||||
Webmentions
|
||||
</h3>
|
||||
<ul>
|
||||
{{range .Post.ApprovedIncomingWebmentions}}
|
||||
<li>
|
||||
<a href="{{.Source}}">
|
||||
{{if .Title}}
|
||||
{{.Title}}
|
||||
{{else}}
|
||||
{{.Source}}
|
||||
{{end}}
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
</div>
|
|
@ -56,4 +56,23 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
{{if .Post.ApprovedIncomingWebmentions}}
|
||||
<h3>
|
||||
Webmentions
|
||||
</h3>
|
||||
<ul>
|
||||
{{range .Post.ApprovedIncomingWebmentions}}
|
||||
<li>
|
||||
<a href="{{.Source}}">
|
||||
{{if .Title}}
|
||||
{{.Title}}
|
||||
{{else}}
|
||||
{{.Source}}
|
||||
{{end}}
|
||||
</a>
|
||||
</li>
|
||||
{{end}}
|
||||
</ul>
|
||||
{{end}}
|
||||
</div>
|
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
3
post.go
3
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",
|
||||
|
|
Loading…
Reference in New Issue