diff --git a/cmd/owl/editor_test.go b/cmd/owl/editor_test.go
index df697fe..d7e4a56 100644
--- a/cmd/owl/editor_test.go
+++ b/cmd/owl/editor_test.go
@@ -38,7 +38,7 @@ func TestEditorFormGet(t *testing.T) {
app := owlApp.FiberApp
token := getUserToken(owlApp.AuthorService)
- req := httptest.NewRequest("GET", "/editor/Image", nil)
+ req := httptest.NewRequest("GET", "/editor/new/Image", nil)
req.AddCookie(&http.Cookie{Name: "token", Value: token})
resp, err := app.Test(req)
require.NoError(t, err)
@@ -50,7 +50,7 @@ func TestEditorFormGetNoAuth(t *testing.T) {
owlApp := App(db)
app := owlApp.FiberApp
- req := httptest.NewRequest("GET", "/editor/Image", nil)
+ req := httptest.NewRequest("GET", "/editor/new/Image", nil)
req.AddCookie(&http.Cookie{Name: "token", Value: "invalid"})
resp, err := app.Test(req)
require.NoError(t, err)
@@ -83,7 +83,7 @@ func TestEditorFormPost(t *testing.T) {
io.WriteString(part, "test content")
writer.Close()
- req := httptest.NewRequest("POST", "/editor/Image", body)
+ req := httptest.NewRequest("POST", "/editor/new/Image", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
req.AddCookie(&http.Cookie{Name: "token", Value: token})
resp, err := app.Test(req)
@@ -125,7 +125,7 @@ func TestEditorFormPostNoAuth(t *testing.T) {
io.WriteString(part, "test content")
writer.Close()
- req := httptest.NewRequest("POST", "/editor/Image", body)
+ req := httptest.NewRequest("POST", "/editor/new/Image", body)
req.Header.Set("Content-Type", writer.FormDataContentType())
req.AddCookie(&http.Cookie{Name: "token", Value: "invalid"})
resp, err := app.Test(req)
diff --git a/render/templates/views/editor_list.tmpl b/render/templates/views/editor_list.tmpl
index 2555fdc..5c21f62 100644
--- a/render/templates/views/editor_list.tmpl
+++ b/render/templates/views/editor_list.tmpl
@@ -18,7 +18,7 @@
{{range .Types}}
-- {{.}}
+- {{.}}
{{end}}
{{end}}
\ No newline at end of file
diff --git a/render/templates/views/entry.tmpl b/render/templates/views/entry.tmpl
index 5d8ab78..5fd2a3e 100644
--- a/render/templates/views/entry.tmpl
+++ b/render/templates/views/entry.tmpl
@@ -29,6 +29,13 @@
+{{ if .LoggedIn }}
+
+
+
+Edit
+{{ end }}
+
{{end}}
diff --git a/web/app.go b/web/app.go
index 3e3f093..0c7a11e 100644
--- a/web/app.go
+++ b/web/app.go
@@ -36,6 +36,7 @@ func NewWebApp(
configRegister *app.ConfigRegister,
) *WebApp {
app := fiber.New()
+ app.Use(middleware.NewUserMiddleware(authorService).Handle)
indexHandler := NewIndexHandler(entryService, configRepo)
listHandler := NewListHandler(entryService, configRepo)
@@ -62,8 +63,10 @@ func NewWebApp(
editor := app.Group("/editor")
editor.Use(middleware.NewAuthMiddleware(authorService).Handle)
editor.Get("/", editorListHandler.Handle)
- editor.Get("/:editor/", editorHandler.HandleGet)
- editor.Post("/:editor/", editorHandler.HandlePost)
+ editor.Get("/new/:editor/", editorHandler.HandleGetNew)
+ editor.Post("/new/:editor/", editorHandler.HandlePostNew)
+ editor.Get("/edit/:id/", editorHandler.HandleGetEdit)
+ editor.Post("/edit/:id/", editorHandler.HandlePostEdit)
// SiteConfig
siteConfig := app.Group("/site-config")
diff --git a/web/editor/entry_form.go b/web/editor/entry_form.go
deleted file mode 100644
index 9857f2b..0000000
--- a/web/editor/entry_form.go
+++ /dev/null
@@ -1,171 +0,0 @@
-package editor
-
-import (
- "fmt"
- "mime/multipart"
- "owl-blogs/app"
- "owl-blogs/domain/model"
- "reflect"
- "strings"
-)
-
-type HttpFormData interface {
- // FormFile returns the first file by key from a MultipartForm.
- FormFile(key string) (*multipart.FileHeader, error)
- // FormValue returns the first value by key from a MultipartForm.
- // Search is performed in QueryArgs, PostArgs, MultipartForm and FormFile in this particular order.
- // Defaults to the empty string "" if the form value doesn't exist.
- // If a default value is given, it will return that value if the form value does not exist.
- // Returned value is only valid within the handler. Do not store any references.
- // Make copies or use the Immutable setting instead.
- FormValue(key string, defaultValue ...string) string
-}
-
-type EditorEntryForm struct {
- entry model.Entry
- binSvc *app.BinaryService
-}
-
-type EntryFormFieldParams struct {
- InputType string
- Widget string
-}
-
-type EntryFormField struct {
- Name string
- Params EntryFormFieldParams
-}
-
-func NewEntryForm(entry model.Entry, binaryService *app.BinaryService) *EditorEntryForm {
- return &EditorEntryForm{
- entry: entry,
- binSvc: binaryService,
- }
-}
-
-func (s *EntryFormFieldParams) ApplyTag(tagKey string, tagValue string) error {
- switch tagKey {
- case "inputType":
- s.InputType = tagValue
- case "widget":
- s.Widget = tagValue
- default:
- return fmt.Errorf("unknown tag key: %v", tagKey)
- }
- return nil
-}
-
-func (s *EntryFormField) Html() string {
- html := ""
- html += fmt.Sprintf("\n", s.Name, s.Name)
- if s.Params.InputType == "text" && s.Params.Widget == "textarea" {
- html += fmt.Sprintf("\n", s.Name, s.Name)
- } else {
- html += fmt.Sprintf("\n", s.Params.InputType, s.Name, s.Name)
- }
- return html
-}
-
-func FieldToFormField(field reflect.StructField) (EntryFormField, error) {
- formField := EntryFormField{
- Name: field.Name,
- Params: EntryFormFieldParams{},
- }
- tag := field.Tag.Get("owl")
- for _, param := range strings.Split(tag, " ") {
- parts := strings.Split(param, "=")
- if len(parts) != 2 {
- continue
- }
- err := formField.Params.ApplyTag(parts[0], parts[1])
- if err != nil {
- return EntryFormField{}, err
- }
- }
- return formField, nil
-}
-
-func StructToFormFields(meta interface{}) ([]EntryFormField, error) {
- entryType := reflect.TypeOf(meta).Elem()
- numFields := entryType.NumField()
- fields := []EntryFormField{}
- for i := 0; i < numFields; i++ {
- field, err := FieldToFormField(entryType.Field(i))
- if err != nil {
- return nil, err
- }
- fields = append(fields, field)
- }
- return fields, nil
-}
-
-func (s *EditorEntryForm) HtmlForm() (string, error) {
- meta := s.entry.MetaData()
- fields, err := StructToFormFields(meta)
- if err != nil {
- return "", err
- }
-
- html := "
\n"
-
- return html, nil
-}
-
-func (s *EditorEntryForm) Parse(ctx HttpFormData) (model.Entry, error) {
- if ctx == nil {
- return nil, fmt.Errorf("nil context")
- }
- meta := s.entry.MetaData()
- metaVal := reflect.ValueOf(meta)
- if metaVal.Kind() != reflect.Ptr {
- return nil, fmt.Errorf("meta data is not a pointer")
- }
- fields, err := StructToFormFields(meta)
- if err != nil {
- return nil, err
- }
- for _, field := range fields {
- fieldName := field.Name
-
- if field.Params.InputType == "file" {
- file, err := ctx.FormFile(fieldName)
- if err != nil {
- return nil, err
- }
- fileData, err := file.Open()
- if err != nil {
- return nil, err
- }
- defer fileData.Close()
- fileBytes := make([]byte, file.Size)
- _, err = fileData.Read(fileBytes)
- if err != nil {
- return nil, err
- }
-
- binaryFile, err := s.binSvc.Create(file.Filename, fileBytes)
- if err != nil {
- return nil, err
- }
-
- metaField := metaVal.Elem().FieldByName(fieldName)
- if metaField.IsValid() {
- metaField.SetString(binaryFile.Id)
- }
- } else {
- formValue := ctx.FormValue(fieldName)
- metaField := metaVal.Elem().FieldByName(fieldName)
- if metaField.IsValid() {
- metaField.SetString(formValue)
- }
- }
-
- }
-
- return s.entry, nil
-}
diff --git a/web/editor_handler.go b/web/editor_handler.go
index 9c83504..8b82dca 100644
--- a/web/editor_handler.go
+++ b/web/editor_handler.go
@@ -5,7 +5,7 @@ import (
"owl-blogs/app/repository"
"owl-blogs/domain/model"
"owl-blogs/render"
- "owl-blogs/web/editor"
+ "owl-blogs/web/forms"
"time"
"github.com/gofiber/fiber/v2"
@@ -41,7 +41,7 @@ func (h *EditorHandler) paramToEntry(c *fiber.Ctx) (model.Entry, error) {
return entryType, nil
}
-func (h *EditorHandler) HandleGet(c *fiber.Ctx) error {
+func (h *EditorHandler) HandleGetNew(c *fiber.Ctx) error {
c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
entryType, err := h.paramToEntry(c)
@@ -49,7 +49,7 @@ func (h *EditorHandler) HandleGet(c *fiber.Ctx) error {
return err
}
- form := editor.NewEntryForm(entryType, h.binSvc)
+ form := forms.NewForm(entryType.MetaData(), h.binSvc)
htmlForm, err := form.HtmlForm()
if err != nil {
return err
@@ -57,23 +57,24 @@ func (h *EditorHandler) HandleGet(c *fiber.Ctx) error {
return render.RenderTemplateWithBase(c, getSiteConfig(h.configRepo), "views/editor", htmlForm)
}
-func (h *EditorHandler) HandlePost(c *fiber.Ctx) error {
+func (h *EditorHandler) HandlePostNew(c *fiber.Ctx) error {
c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
- entryType, err := h.paramToEntry(c)
+ entry, err := h.paramToEntry(c)
if err != nil {
return err
}
- form := editor.NewEntryForm(entryType, h.binSvc)
+ form := forms.NewForm(entry.MetaData(), h.binSvc)
// get form data
- entry, err := form.Parse(c)
+ entryMeta, err := form.Parse(c)
if err != nil {
return err
}
// create entry
now := time.Now()
+ entry.SetMetaData(entryMeta)
entry.SetPublishedAt(&now)
entry.SetAuthorId(c.Locals("author").(string))
@@ -84,3 +85,45 @@ func (h *EditorHandler) HandlePost(c *fiber.Ctx) error {
return c.Redirect("/posts/" + entry.ID() + "/")
}
+
+func (h *EditorHandler) HandleGetEdit(c *fiber.Ctx) error {
+ c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
+
+ id := c.Params("id")
+ entry, err := h.entrySvc.FindById(id)
+ if err != nil {
+ return err
+ }
+
+ form := forms.NewForm(entry.MetaData(), h.binSvc)
+ htmlForm, err := form.HtmlForm()
+ if err != nil {
+ return err
+ }
+ return render.RenderTemplateWithBase(c, getSiteConfig(h.configRepo), "views/editor", htmlForm)
+}
+
+func (h *EditorHandler) HandlePostEdit(c *fiber.Ctx) error {
+ c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
+
+ id := c.Params("id")
+ entry, err := h.entrySvc.FindById(id)
+ if err != nil {
+ return err
+ }
+
+ form := forms.NewForm(entry.MetaData(), h.binSvc)
+ // get form data
+ meta, err := form.Parse(c)
+ if err != nil {
+ return err
+ }
+
+ // update entry
+ entry.SetMetaData(meta)
+ err = h.entrySvc.Update(entry)
+ if err != nil {
+ return err
+ }
+ return c.Redirect("/posts/" + entry.ID() + "/")
+}
diff --git a/web/entry_handler.go b/web/entry_handler.go
index 2a94070..509daef 100644
--- a/web/entry_handler.go
+++ b/web/entry_handler.go
@@ -17,8 +17,9 @@ type EntryHandler struct {
}
type entryData struct {
- Entry model.Entry
- Author *model.Author
+ Entry model.Entry
+ Author *model.Author
+ LoggedIn bool
}
func NewEntryHandler(
@@ -49,5 +50,14 @@ func (h *EntryHandler) Handle(c *fiber.Ctx) error {
author = &model.Author{}
}
- return render.RenderTemplateWithBase(c, getSiteConfig(h.configRepo), "views/entry", entryData{Entry: entry, Author: author})
+ return render.RenderTemplateWithBase(
+ c,
+ getSiteConfig(h.configRepo),
+ "views/entry",
+ entryData{
+ Entry: entry,
+ Author: author,
+ LoggedIn: c.Locals("author") != nil,
+ },
+ )
}
diff --git a/web/forms/form.go b/web/forms/form.go
index 30de998..9e68511 100644
--- a/web/forms/form.go
+++ b/web/forms/form.go
@@ -135,6 +135,14 @@ func (s *Form) Parse(ctx HttpFormData) (interface{}, error) {
if field.Params.InputType == "file" {
file, err := ctx.FormFile(fieldName)
if err != nil {
+ // If field already has a value, we can ignore the error
+ if field.Value != "" {
+ metaField := dataVal.Elem().FieldByName(fieldName)
+ if metaField.IsValid() {
+ metaField.SetString(field.Value)
+ }
+ continue
+ }
return nil, err
}
fileData, err := file.Open()
diff --git a/web/editor/entry_form_test.go b/web/forms/form_test.go
similarity index 68%
rename from web/editor/entry_form_test.go
rename to web/forms/form_test.go
index 4068254..8e08c54 100644
--- a/web/editor/entry_form_test.go
+++ b/web/forms/form_test.go
@@ -1,4 +1,4 @@
-package editor_test
+package forms_test
import (
"bytes"
@@ -6,10 +6,9 @@ import (
"mime/multipart"
"os"
"owl-blogs/app"
- "owl-blogs/domain/model"
"owl-blogs/infra"
"owl-blogs/test"
- "owl-blogs/web/editor"
+ "owl-blogs/web/forms"
"path"
"path/filepath"
"reflect"
@@ -18,7 +17,7 @@ import (
"github.com/stretchr/testify/require"
)
-type MockEntryMetaData struct {
+type MockData struct {
Image string `owl:"inputType=file"`
Content string `owl:"inputType=text"`
}
@@ -65,37 +64,16 @@ func (f *MockFormData) FormValue(key string, defaultValue ...string) string {
return key
}
-type MockEntry struct {
- model.EntryBase
- metaData MockEntryMetaData
-}
-
-func (e *MockEntry) Content() model.EntryContent {
- return model.EntryContent(e.metaData.Content)
-}
-
-func (e *MockEntry) MetaData() interface{} {
- return &e.metaData
-}
-
-func (e *MockEntry) SetMetaData(metaData interface{}) {
- e.metaData = *metaData.(*MockEntryMetaData)
-}
-
-func (e *MockEntry) Title() string {
- return ""
-}
-
func TestFieldToFormField(t *testing.T) {
- field := reflect.TypeOf(&MockEntryMetaData{}).Elem().Field(0)
- formField, err := editor.FieldToFormField(field)
+ field := reflect.TypeOf(&MockData{}).Elem().Field(0)
+ formField, err := forms.FieldToFormField(field, "")
require.NoError(t, err)
require.Equal(t, "Image", formField.Name)
require.Equal(t, "file", formField.Params.InputType)
}
func TestStructToFields(t *testing.T) {
- fields, err := editor.StructToFormFields(&MockEntryMetaData{})
+ fields, err := forms.StructToFormFields(&MockData{})
require.NoError(t, err)
require.Len(t, fields, 2)
require.Equal(t, "Image", fields[0].Name)
@@ -104,8 +82,8 @@ func TestStructToFields(t *testing.T) {
require.Equal(t, "text", fields[1].Params.InputType)
}
-func TestEditorEntryForm_HtmlForm(t *testing.T) {
- form := editor.NewEntryForm(&MockEntry{}, nil)
+func TestForm_HtmlForm(t *testing.T) {
+ form := forms.NewForm(&MockData{}, nil)
html, err := form.HtmlForm()
require.NoError(t, err)
require.Contains(t, html, "