photo post type
This commit is contained in:
parent
c7834b08d5
commit
1e5ea053cc
|
@ -3,7 +3,11 @@ package web
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"h4kor/owl-blogs"
|
"h4kor/owl-blogs"
|
||||||
|
"io"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"os"
|
||||||
|
"path"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
@ -174,7 +178,12 @@ func userEditorPostHandler(repo *owl.Repository) func(http.ResponseWriter, *http
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if strings.Split(r.Header.Get("Content-Type"), ";")[0] == "multipart/form-data" {
|
||||||
|
err = r.ParseMultipartForm(32 << 20)
|
||||||
|
} else {
|
||||||
err = r.ParseForm()
|
err = r.ParseForm()
|
||||||
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
println("Error parsing form: ", err.Error())
|
println("Error parsing form: ", err.Error())
|
||||||
w.WriteHeader(http.StatusInternalServerError)
|
w.WriteHeader(http.StatusInternalServerError)
|
||||||
|
@ -213,6 +222,23 @@ func userEditorPostHandler(repo *owl.Repository) func(http.ResponseWriter, *http
|
||||||
reply_url := r.Form.Get("reply_url")
|
reply_url := r.Form.Get("reply_url")
|
||||||
bookmark_url := r.Form.Get("bookmark_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
|
// validate form values
|
||||||
if post_type == "" {
|
if post_type == "" {
|
||||||
html, _ := owl.RenderUserError(user, owl.ErrorMessage{
|
html, _ := owl.RenderUserError(user, owl.ErrorMessage{
|
||||||
|
@ -246,12 +272,20 @@ func userEditorPostHandler(repo *owl.Repository) func(http.ResponseWriter, *http
|
||||||
w.Write([]byte(html))
|
w.Write([]byte(html))
|
||||||
return
|
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 reply_url for title and description
|
||||||
// TODO: scrape bookmark_url for title and description
|
// TODO: scrape bookmark_url for title and description
|
||||||
|
|
||||||
// create post
|
// create post
|
||||||
post, err := user.CreateNewPost(owl.PostMeta{
|
meta := owl.PostMeta{
|
||||||
Type: post_type,
|
Type: post_type,
|
||||||
Title: title,
|
Title: title,
|
||||||
Description: description,
|
Description: description,
|
||||||
|
@ -268,7 +302,32 @@ func userEditorPostHandler(repo *owl.Repository) func(http.ResponseWriter, *http
|
||||||
Ingredients: strings.Split(recipe_ingredients, "\n"),
|
Ingredients: strings.Split(recipe_ingredients, "\n"),
|
||||||
Duration: recipe_duration,
|
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 {
|
if err != nil {
|
||||||
println("Error creating post: ", err.Error())
|
println("Error creating post: ", err.Error())
|
||||||
|
|
|
@ -1,14 +1,18 @@
|
||||||
package web_test
|
package web_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"bytes"
|
||||||
"h4kor/owl-blogs"
|
"h4kor/owl-blogs"
|
||||||
main "h4kor/owl-blogs/cmd/owl/web"
|
main "h4kor/owl-blogs/cmd/owl/web"
|
||||||
"h4kor/owl-blogs/test/assertions"
|
"h4kor/owl-blogs/test/assertions"
|
||||||
"h4kor/owl-blogs/test/mocks"
|
"h4kor/owl-blogs/test/mocks"
|
||||||
"io"
|
"io"
|
||||||
|
"io/ioutil"
|
||||||
|
"mime/multipart"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
"net/url"
|
"net/url"
|
||||||
|
"path"
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -280,3 +284,63 @@ func TestEditorPostWithSessionRecipe(t *testing.T) {
|
||||||
assertions.AssertStatus(t, rr, http.StatusFound)
|
assertions.AssertStatus(t, rr, http.StatusFound)
|
||||||
assertions.AssertEqual(t, rr.Header().Get("Location"), post.FullUrl())
|
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>
|
</form>
|
||||||
</details>
|
</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>
|
<details>
|
||||||
<summary>Write Note</summary>
|
<summary>Write Note</summary>
|
||||||
<form action="" method="post">
|
<form action="" method="post">
|
||||||
|
@ -54,35 +105,6 @@
|
||||||
</form>
|
</form>
|
||||||
</details>
|
</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>
|
<details>
|
||||||
<summary>Bookmark</summary>
|
<summary>Bookmark</summary>
|
||||||
<form action="" method="post">
|
<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>
|
||||||
</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>
|
</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"`
|
Reply ReplyData `yaml:"reply"`
|
||||||
Bookmark BookmarkData `yaml:"bookmark"`
|
Bookmark BookmarkData `yaml:"bookmark"`
|
||||||
Recipe RecipeData `yaml:"recipe"`
|
Recipe RecipeData `yaml:"recipe"`
|
||||||
|
PhotoPath string `yaml:"photo"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pm PostMeta) FormattedDate() string {
|
func (pm PostMeta) FormattedDate() string {
|
||||||
|
@ -104,6 +105,7 @@ func (pm *PostMeta) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
Reply ReplyData `yaml:"reply"`
|
Reply ReplyData `yaml:"reply"`
|
||||||
Bookmark BookmarkData `yaml:"bookmark"`
|
Bookmark BookmarkData `yaml:"bookmark"`
|
||||||
Recipe RecipeData `yaml:"recipe"`
|
Recipe RecipeData `yaml:"recipe"`
|
||||||
|
PhotoPath string `yaml:"photo"`
|
||||||
}
|
}
|
||||||
type S struct {
|
type S struct {
|
||||||
Date string `yaml:"date"`
|
Date string `yaml:"date"`
|
||||||
|
@ -129,6 +131,7 @@ func (pm *PostMeta) UnmarshalYAML(unmarshal func(interface{}) error) error {
|
||||||
pm.Reply = t.Reply
|
pm.Reply = t.Reply
|
||||||
pm.Bookmark = t.Bookmark
|
pm.Bookmark = t.Bookmark
|
||||||
pm.Recipe = t.Recipe
|
pm.Recipe = t.Recipe
|
||||||
|
pm.PhotoPath = t.PhotoPath
|
||||||
|
|
||||||
possibleFormats := []string{
|
possibleFormats := []string{
|
||||||
"2006-01-02",
|
"2006-01-02",
|
||||||
|
|
Loading…
Reference in New Issue