diff --git a/app/config_register.go b/app/config_register.go
index 22d5f8c..01765a3 100644
--- a/app/config_register.go
+++ b/app/config_register.go
@@ -1,19 +1,26 @@
package app
+import "owl-blogs/domain/model"
+
+type AppConfig interface {
+ Form(binSvc model.BinaryStorageInterface) string
+ ParseFormData(data model.HttpFormData, binSvc model.BinaryStorageInterface) (AppConfig, error)
+}
+
type ConfigRegister struct {
- configs map[string]interface{}
+ configs map[string]AppConfig
}
type RegisteredConfig struct {
Name string
- Config interface{}
+ Config AppConfig
}
func NewConfigRegister() *ConfigRegister {
- return &ConfigRegister{configs: map[string]interface{}{}}
+ return &ConfigRegister{configs: map[string]AppConfig{}}
}
-func (r *ConfigRegister) Register(name string, config interface{}) {
+func (r *ConfigRegister) Register(name string, config AppConfig) {
r.configs[name] = config
}
@@ -28,6 +35,6 @@ func (r *ConfigRegister) Configs() []RegisteredConfig {
return configs
}
-func (r *ConfigRegister) GetConfig(name string) interface{} {
+func (r *ConfigRegister) GetConfig(name string) AppConfig {
return r.configs[name]
}
diff --git a/cmd/owl/editor_test.go b/cmd/owl/editor_test.go
index d7e4a56..7d570b6 100644
--- a/cmd/owl/editor_test.go
+++ b/cmd/owl/editor_test.go
@@ -77,9 +77,9 @@ func TestEditorFormPost(t *testing.T) {
body := &bytes.Buffer{}
writer := multipart.NewWriter(body)
- part, _ := writer.CreateFormFile("ImageId", filepath.Base(file.Name()))
+ part, _ := writer.CreateFormFile("image", filepath.Base(file.Name()))
io.Copy(part, file)
- part, _ = writer.CreateFormField("Content")
+ part, _ = writer.CreateFormField("content")
io.WriteString(part, "test content")
writer.Close()
diff --git a/domain/model/binary_service.go b/domain/model/binary_service.go
new file mode 100644
index 0000000..5d33e34
--- /dev/null
+++ b/domain/model/binary_service.go
@@ -0,0 +1,5 @@
+package model
+
+type BinaryStorageInterface interface {
+ Create(name string, file []byte) (*BinaryFile, error)
+}
diff --git a/domain/model/entry.go b/domain/model/entry.go
index b9372c2..84a4a57 100644
--- a/domain/model/entry.go
+++ b/domain/model/entry.go
@@ -1,6 +1,8 @@
package model
-import "time"
+import (
+ "time"
+)
type EntryContent string
@@ -9,18 +11,20 @@ type Entry interface {
Content() EntryContent
PublishedAt() *time.Time
AuthorId() string
- MetaData() interface{}
+ MetaData() EntryMetaData
// Optional: can return empty string
Title() string
SetID(id string)
SetPublishedAt(publishedAt *time.Time)
- SetMetaData(metaData interface{})
+ SetMetaData(metaData EntryMetaData)
SetAuthorId(authorId string)
}
type EntryMetaData interface {
+ Form(binSvc BinaryStorageInterface) string
+ ParseFormData(data HttpFormData, binSvc BinaryStorageInterface) (EntryMetaData, error)
}
type EntryBase struct {
diff --git a/domain/model/form_data.go b/domain/model/form_data.go
new file mode 100644
index 0000000..396120b
--- /dev/null
+++ b/domain/model/form_data.go
@@ -0,0 +1,15 @@
+package model
+
+import "mime/multipart"
+
+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
+}
diff --git a/entry_types/article.go b/entry_types/article.go
index 9849a24..8cd10ad 100644
--- a/entry_types/article.go
+++ b/entry_types/article.go
@@ -12,8 +12,22 @@ type Article struct {
}
type ArticleMetaData struct {
- Title string `owl:"inputType=text"`
- Content string `owl:"inputType=text widget=textarea"`
+ Title string
+ Content string
+}
+
+// Form implements model.EntryMetaData.
+func (meta *ArticleMetaData) Form(binSvc model.BinaryStorageInterface) string {
+ f, _ := render.RenderTemplateToString("forms/Article", meta)
+ return f
+}
+
+// ParseFormData implements model.EntryMetaData.
+func (*ArticleMetaData) ParseFormData(data model.HttpFormData, binSvc model.BinaryStorageInterface) (model.EntryMetaData, error) {
+ return &ArticleMetaData{
+ Title: data.FormValue("title"),
+ Content: data.FormValue("content"),
+ }, nil
}
func (e *Article) Title() string {
@@ -28,10 +42,10 @@ func (e *Article) Content() model.EntryContent {
return model.EntryContent(str)
}
-func (e *Article) MetaData() interface{} {
+func (e *Article) MetaData() model.EntryMetaData {
return &e.meta
}
-func (e *Article) SetMetaData(metaData interface{}) {
+func (e *Article) SetMetaData(metaData model.EntryMetaData) {
e.meta = *metaData.(*ArticleMetaData)
}
diff --git a/entry_types/bookmark.go b/entry_types/bookmark.go
index 2002bd0..19b5d3e 100644
--- a/entry_types/bookmark.go
+++ b/entry_types/bookmark.go
@@ -12,9 +12,24 @@ type Bookmark struct {
}
type BookmarkMetaData struct {
- Title string `owl:"inputType=text"`
- Url string `owl:"inputType=text"`
- Content string `owl:"inputType=text widget=textarea"`
+ Title string
+ Url string
+ Content string
+}
+
+// Form implements model.EntryMetaData.
+func (meta *BookmarkMetaData) Form(binSvc model.BinaryStorageInterface) string {
+ f, _ := render.RenderTemplateToString("forms/Bookmark", meta)
+ return f
+}
+
+// ParseFormData implements model.EntryMetaData.
+func (*BookmarkMetaData) ParseFormData(data model.HttpFormData, binSvc model.BinaryStorageInterface) (model.EntryMetaData, error) {
+ return &BookmarkMetaData{
+ Title: data.FormValue("title"),
+ Url: data.FormValue("url"),
+ Content: data.FormValue("content"),
+ }, nil
}
func (e *Bookmark) Title() string {
@@ -29,10 +44,10 @@ func (e *Bookmark) Content() model.EntryContent {
return model.EntryContent(str)
}
-func (e *Bookmark) MetaData() interface{} {
+func (e *Bookmark) MetaData() model.EntryMetaData {
return &e.meta
}
-func (e *Bookmark) SetMetaData(metaData interface{}) {
+func (e *Bookmark) SetMetaData(metaData model.EntryMetaData) {
e.meta = *metaData.(*BookmarkMetaData)
}
diff --git a/entry_types/image.go b/entry_types/image.go
index bd23e37..acb9cdc 100644
--- a/entry_types/image.go
+++ b/entry_types/image.go
@@ -12,9 +12,47 @@ type Image struct {
}
type ImageMetaData struct {
- ImageId string `owl:"inputType=file"`
- Title string `owl:"inputType=text"`
- Content string `owl:"inputType=text widget=textarea"`
+ ImageId string
+ Title string
+ Content string
+}
+
+// Form implements model.EntryMetaData.
+func (meta *ImageMetaData) Form(binSvc model.BinaryStorageInterface) string {
+ f, _ := render.RenderTemplateToString("forms/Image", meta)
+ return f
+}
+
+// ParseFormData implements model.EntryMetaData.
+func (meta *ImageMetaData) ParseFormData(data model.HttpFormData, binSvc model.BinaryStorageInterface) (model.EntryMetaData, error) {
+ file, err := data.FormFile("image")
+ var imgId = meta.ImageId
+ if err != nil && imgId == "" {
+ return nil, err
+ } else if err == nil {
+ 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
+ }
+ bin, err := binSvc.Create(file.Filename, fileBytes)
+ if err != nil {
+ return nil, err
+ }
+ imgId = bin.Id
+ }
+
+ return &ImageMetaData{
+ ImageId: imgId,
+ Title: data.FormValue("title"),
+ Content: data.FormValue("content"),
+ }, nil
}
func (e *Image) Title() string {
@@ -29,10 +67,10 @@ func (e *Image) Content() model.EntryContent {
return model.EntryContent(str)
}
-func (e *Image) MetaData() interface{} {
+func (e *Image) MetaData() model.EntryMetaData {
return &e.meta
}
-func (e *Image) SetMetaData(metaData interface{}) {
+func (e *Image) SetMetaData(metaData model.EntryMetaData) {
e.meta = *metaData.(*ImageMetaData)
}
diff --git a/entry_types/note.go b/entry_types/note.go
index 590e9a5..f828465 100644
--- a/entry_types/note.go
+++ b/entry_types/note.go
@@ -12,7 +12,20 @@ type Note struct {
}
type NoteMetaData struct {
- Content string `owl:"inputType=text widget=textarea"`
+ Content string
+}
+
+// Form implements model.EntryMetaData.
+func (meta *NoteMetaData) Form(binSvc model.BinaryStorageInterface) string {
+ f, _ := render.RenderTemplateToString("forms/Note", meta)
+ return f
+}
+
+// ParseFormData implements model.EntryMetaData.
+func (*NoteMetaData) ParseFormData(data model.HttpFormData, binSvc model.BinaryStorageInterface) (model.EntryMetaData, error) {
+ return &NoteMetaData{
+ Content: data.FormValue("content"),
+ }, nil
}
func (e *Note) Title() string {
@@ -27,10 +40,10 @@ func (e *Note) Content() model.EntryContent {
return model.EntryContent(str)
}
-func (e *Note) MetaData() interface{} {
+func (e *Note) MetaData() model.EntryMetaData {
return &e.meta
}
-func (e *Note) SetMetaData(metaData interface{}) {
+func (e *Note) SetMetaData(metaData model.EntryMetaData) {
e.meta = *metaData.(*NoteMetaData)
}
diff --git a/entry_types/page.go b/entry_types/page.go
index 78faece..37a7c97 100644
--- a/entry_types/page.go
+++ b/entry_types/page.go
@@ -12,8 +12,22 @@ type Page struct {
}
type PageMetaData struct {
- Title string `owl:"inputType=text"`
- Content string `owl:"inputType=text widget=textarea"`
+ Title string
+ Content string
+}
+
+// Form implements model.EntryMetaData.
+func (meta *PageMetaData) Form(binSvc model.BinaryStorageInterface) string {
+ f, _ := render.RenderTemplateToString("forms/Page", meta)
+ return f
+}
+
+// ParseFormData implements model.EntryMetaData.
+func (*PageMetaData) ParseFormData(data model.HttpFormData, binSvc model.BinaryStorageInterface) (model.EntryMetaData, error) {
+ return &PageMetaData{
+ Title: data.FormValue("title"),
+ Content: data.FormValue("content"),
+ }, nil
}
func (e *Page) Title() string {
@@ -28,10 +42,10 @@ func (e *Page) Content() model.EntryContent {
return model.EntryContent(str)
}
-func (e *Page) MetaData() interface{} {
+func (e *Page) MetaData() model.EntryMetaData {
return &e.meta
}
-func (e *Page) SetMetaData(metaData interface{}) {
+func (e *Page) SetMetaData(metaData model.EntryMetaData) {
e.meta = *metaData.(*PageMetaData)
}
diff --git a/entry_types/recipe.go b/entry_types/recipe.go
index d6c8a51..f0eb117 100644
--- a/entry_types/recipe.go
+++ b/entry_types/recipe.go
@@ -4,6 +4,7 @@ import (
"fmt"
"owl-blogs/domain/model"
"owl-blogs/render"
+ "strings"
)
type Recipe struct {
@@ -12,11 +13,35 @@ type Recipe struct {
}
type RecipeMetaData struct {
- Title string `owl:"inputType=text"`
- Yield string `owl:"inputType=text"`
- Duration string `owl:"inputType=text"`
- Ingredients []string `owl:"inputType=text widget=textlist"`
- Content string `owl:"inputType=text widget=textarea"`
+ Title string
+ Yield string
+ Duration string
+ Ingredients []string
+ Content string
+}
+
+// Form implements model.EntryMetaData.
+func (meta *RecipeMetaData) Form(binSvc model.BinaryStorageInterface) string {
+ f, _ := render.RenderTemplateToString("forms/Recipe", meta)
+ return f
+}
+
+// ParseFormData implements model.EntryMetaData.
+func (*RecipeMetaData) ParseFormData(data model.HttpFormData, binSvc model.BinaryStorageInterface) (model.EntryMetaData, error) {
+ ings := strings.Split(data.FormValue("ingredients"), "\n")
+ clean := make([]string, 0)
+ for _, ing := range ings {
+ if strings.TrimSpace(ing) != "" {
+ clean = append(clean, strings.TrimSpace(ing))
+ }
+ }
+ return &RecipeMetaData{
+ Title: data.FormValue("title"),
+ Yield: data.FormValue("yield"),
+ Duration: data.FormValue("duration"),
+ Ingredients: clean,
+ Content: data.FormValue("content"),
+ }, nil
}
func (e *Recipe) Title() string {
@@ -31,10 +56,10 @@ func (e *Recipe) Content() model.EntryContent {
return model.EntryContent(str)
}
-func (e *Recipe) MetaData() interface{} {
+func (e *Recipe) MetaData() model.EntryMetaData {
return &e.meta
}
-func (e *Recipe) SetMetaData(metaData interface{}) {
+func (e *Recipe) SetMetaData(metaData model.EntryMetaData) {
e.meta = *metaData.(*RecipeMetaData)
}
diff --git a/entry_types/reply.go b/entry_types/reply.go
index de2f751..1e088f6 100644
--- a/entry_types/reply.go
+++ b/entry_types/reply.go
@@ -12,9 +12,24 @@ type Reply struct {
}
type ReplyMetaData struct {
- Title string `owl:"inputType=text"`
- Url string `owl:"inputType=text"`
- Content string `owl:"inputType=text widget=textarea"`
+ Title string
+ Url string
+ Content string
+}
+
+// Form implements model.EntryMetaData.
+func (meta *ReplyMetaData) Form(binSvc model.BinaryStorageInterface) string {
+ f, _ := render.RenderTemplateToString("forms/Reply", meta)
+ return f
+}
+
+// ParseFormData implements model.EntryMetaData.
+func (*ReplyMetaData) ParseFormData(data model.HttpFormData, binSvc model.BinaryStorageInterface) (model.EntryMetaData, error) {
+ return &ReplyMetaData{
+ Title: data.FormValue("title"),
+ Url: data.FormValue("url"),
+ Content: data.FormValue("content"),
+ }, nil
}
func (e *Reply) Title() string {
@@ -29,10 +44,10 @@ func (e *Reply) Content() model.EntryContent {
return model.EntryContent(str)
}
-func (e *Reply) MetaData() interface{} {
+func (e *Reply) MetaData() model.EntryMetaData {
return &e.meta
}
-func (e *Reply) SetMetaData(metaData interface{}) {
+func (e *Reply) SetMetaData(metaData model.EntryMetaData) {
e.meta = *metaData.(*ReplyMetaData)
}
diff --git a/infra/entry_repository.go b/infra/entry_repository.go
index e4fc563..bde324a 100644
--- a/infra/entry_repository.go
+++ b/infra/entry_repository.go
@@ -151,7 +151,7 @@ func (r *DefaultEntryRepo) sqlEntryToEntry(entry sqlEntry) (model.Entry, error)
if err != nil {
return nil, errors.New("entry type not registered")
}
- metaData := reflect.New(reflect.TypeOf(e.MetaData()).Elem()).Interface()
+ metaData := reflect.New(reflect.TypeOf(e.MetaData()).Elem()).Interface().(model.EntryMetaData)
json.Unmarshal([]byte(*entry.MetaData), metaData)
e.SetID(entry.Id)
e.SetPublishedAt(entry.PublishedAt)
diff --git a/plugings/instagram.go b/plugings/instagram.go
index 2e52dba..5e045c4 100644
--- a/plugings/instagram.go
+++ b/plugings/instagram.go
@@ -6,6 +6,7 @@ import (
"owl-blogs/app/repository"
"owl-blogs/domain/model"
entrytypes "owl-blogs/entry_types"
+ "owl-blogs/render"
"github.com/Davincible/goinsta/v3"
)
@@ -16,8 +17,22 @@ type Instagram struct {
}
type InstagramConfig struct {
- User string `owl:"widget=text"`
- Password string `owl:"widget=password"`
+ User string
+ Password string
+}
+
+// Form implements app.AppConfig.
+func (cfg *InstagramConfig) Form(binSvc model.BinaryStorageInterface) string {
+ f, _ := render.RenderTemplateToString("forms/InstagramConfig", cfg)
+ return f
+}
+
+// ParseFormData implements app.AppConfig.
+func (*InstagramConfig) ParseFormData(data model.HttpFormData, binSvc model.BinaryStorageInterface) (app.AppConfig, error) {
+ return &InstagramConfig{
+ User: data.FormValue("User"),
+ Password: data.FormValue("Password"),
+ }, nil
}
func RegisterInstagram(
diff --git a/render/templates/forms/ActivityPubConfig.tmpl b/render/templates/forms/ActivityPubConfig.tmpl
new file mode 100644
index 0000000..596bebc
--- /dev/null
+++ b/render/templates/forms/ActivityPubConfig.tmpl
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/render/templates/forms/Article.tmpl b/render/templates/forms/Article.tmpl
new file mode 100644
index 0000000..3b5e9ce
--- /dev/null
+++ b/render/templates/forms/Article.tmpl
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/render/templates/forms/Bookmark.tmpl b/render/templates/forms/Bookmark.tmpl
new file mode 100644
index 0000000..6e69609
--- /dev/null
+++ b/render/templates/forms/Bookmark.tmpl
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/render/templates/forms/Image.tmpl b/render/templates/forms/Image.tmpl
new file mode 100644
index 0000000..9286c42
--- /dev/null
+++ b/render/templates/forms/Image.tmpl
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/render/templates/forms/InstagramConfig.tmpl b/render/templates/forms/InstagramConfig.tmpl
new file mode 100644
index 0000000..c131350
--- /dev/null
+++ b/render/templates/forms/InstagramConfig.tmpl
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/render/templates/forms/Note.tmpl b/render/templates/forms/Note.tmpl
new file mode 100644
index 0000000..63cb09d
--- /dev/null
+++ b/render/templates/forms/Note.tmpl
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/render/templates/forms/Page.tmpl b/render/templates/forms/Page.tmpl
new file mode 100644
index 0000000..3b5e9ce
--- /dev/null
+++ b/render/templates/forms/Page.tmpl
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/render/templates/forms/Recipe.tmpl b/render/templates/forms/Recipe.tmpl
new file mode 100644
index 0000000..bcf27bc
--- /dev/null
+++ b/render/templates/forms/Recipe.tmpl
@@ -0,0 +1,15 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/render/templates/forms/Reply.tmpl b/render/templates/forms/Reply.tmpl
new file mode 100644
index 0000000..aaca489
--- /dev/null
+++ b/render/templates/forms/Reply.tmpl
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/test/mock_entry.go b/test/mock_entry.go
index 1b0373e..a21754f 100644
--- a/test/mock_entry.go
+++ b/test/mock_entry.go
@@ -12,6 +12,16 @@ type MockEntryMetaData struct {
Title string
}
+// Form implements model.EntryMetaData.
+func (*MockEntryMetaData) Form(binSvc model.BinaryStorageInterface) string {
+ panic("unimplemented")
+}
+
+// ParseFormData implements model.EntryMetaData.
+func (*MockEntryMetaData) ParseFormData(data model.HttpFormData, binSvc model.BinaryStorageInterface) (model.EntryMetaData, error) {
+ panic("unimplemented")
+}
+
type MockEntry struct {
model.EntryBase
metaData *MockEntryMetaData
@@ -21,11 +31,11 @@ func (e *MockEntry) Content() model.EntryContent {
return model.EntryContent(e.metaData.Str)
}
-func (e *MockEntry) MetaData() interface{} {
+func (e *MockEntry) MetaData() model.EntryMetaData {
return e.metaData
}
-func (e *MockEntry) SetMetaData(metaData interface{}) {
+func (e *MockEntry) SetMetaData(metaData model.EntryMetaData) {
e.metaData = metaData.(*MockEntryMetaData)
}
diff --git a/tmp/build-errors.log b/tmp/build-errors.log
deleted file mode 100644
index 05e5985..0000000
--- a/tmp/build-errors.log
+++ /dev/null
@@ -1 +0,0 @@
-exit status 1
\ No newline at end of file
diff --git a/web/activity_pub_handler.go b/web/activity_pub_handler.go
index d5d5eec..6783184 100644
--- a/web/activity_pub_handler.go
+++ b/web/activity_pub_handler.go
@@ -6,6 +6,7 @@ import (
"owl-blogs/app/repository"
"owl-blogs/config"
"owl-blogs/domain/model"
+ "owl-blogs/render"
vocab "github.com/go-ap/activitypub"
@@ -20,9 +21,24 @@ type ActivityPubServer struct {
}
type ActivityPubConfig struct {
- PreferredUsername string `owl:"inputType=text"`
- PublicKeyPem string `owl:"inputType=text widget=textarea"`
- PrivateKeyPem string `owl:"inputType=text widget=textarea"`
+ PreferredUsername string
+ PublicKeyPem string
+ PrivateKeyPem string
+}
+
+// Form implements app.AppConfig.
+func (cfg *ActivityPubConfig) Form(binSvc model.BinaryStorageInterface) string {
+ f, _ := render.RenderTemplateToString("forms/ActivityPubConfig", cfg)
+ return f
+}
+
+// ParseFormData implements app.AppConfig.
+func (*ActivityPubConfig) ParseFormData(data model.HttpFormData, binSvc model.BinaryStorageInterface) (app.AppConfig, error) {
+ return &ActivityPubConfig{
+ PreferredUsername: data.FormValue("PreferredUsername"),
+ PublicKeyPem: data.FormValue("PublicKeyPem"),
+ PrivateKeyPem: data.FormValue("PrivateKeyPem"),
+ }, nil
}
type WebfingerResponse struct {
diff --git a/web/admin_handler.go b/web/admin_handler.go
index 126a750..eb249f5 100644
--- a/web/admin_handler.go
+++ b/web/admin_handler.go
@@ -4,7 +4,6 @@ import (
"owl-blogs/app"
"owl-blogs/app/repository"
"owl-blogs/render"
- "owl-blogs/web/forms"
"sort"
"github.com/gofiber/fiber/v2"
@@ -14,6 +13,7 @@ type adminHandler struct {
configRepo repository.ConfigRepository
configRegister *app.ConfigRegister
typeRegistry *app.EntryTypeRegistry
+ binSvc *app.BinaryService
}
type adminContet struct {
@@ -75,8 +75,7 @@ func (h *adminHandler) HandleConfigGet(c *fiber.Ctx) error {
}
siteConfig := getSiteConfig(h.configRepo)
- form := forms.NewForm(config, nil)
- htmlForm, err := form.HtmlForm()
+ htmlForm := config.Form(h.binSvc)
if err != nil {
return err
}
@@ -93,9 +92,7 @@ func (h *adminHandler) HandleConfigPost(c *fiber.Ctx) error {
return c.SendStatus(404)
}
- form := forms.NewForm(config, nil)
-
- newConfig, err := form.Parse(c)
+ newConfig, err := config.ParseFormData(c, h.binSvc)
if err != nil {
return err
}
diff --git a/web/editor_handler.go b/web/editor_handler.go
index 86e1be8..710ae85 100644
--- a/web/editor_handler.go
+++ b/web/editor_handler.go
@@ -5,7 +5,6 @@ import (
"owl-blogs/app/repository"
"owl-blogs/domain/model"
"owl-blogs/render"
- "owl-blogs/web/forms"
"time"
"github.com/gofiber/fiber/v2"
@@ -48,12 +47,7 @@ func (h *EditorHandler) HandleGetNew(c *fiber.Ctx) error {
if err != nil {
return err
}
-
- form := forms.NewForm(entryType.MetaData(), h.binSvc)
- htmlForm, err := form.HtmlForm()
- if err != nil {
- return err
- }
+ htmlForm := entryType.MetaData().Form(h.binSvc)
return render.RenderTemplateWithBase(c, getSiteConfig(h.configRepo), "views/editor", htmlForm)
}
@@ -65,9 +59,7 @@ func (h *EditorHandler) HandlePostNew(c *fiber.Ctx) error {
return err
}
- form := forms.NewForm(entry.MetaData(), h.binSvc)
- // get form data
- entryMeta, err := form.Parse(c)
+ entryMeta, err := entry.MetaData().ParseFormData(c, h.binSvc)
if err != nil {
return err
}
@@ -100,11 +92,7 @@ func (h *EditorHandler) HandleGetEdit(c *fiber.Ctx) error {
return err
}
- form := forms.NewForm(entry.MetaData(), h.binSvc)
- htmlForm, err := form.HtmlForm()
- if err != nil {
- return err
- }
+ htmlForm := entry.MetaData().Form(h.binSvc)
return render.RenderTemplateWithBase(c, getSiteConfig(h.configRepo), "views/editor", htmlForm)
}
@@ -117,9 +105,8 @@ func (h *EditorHandler) HandlePostEdit(c *fiber.Ctx) error {
return err
}
- form := forms.NewForm(entry.MetaData(), h.binSvc)
// get form data
- meta, err := form.Parse(c)
+ meta, err := entry.MetaData().ParseFormData(c, h.binSvc)
if err != nil {
return err
}
diff --git a/web/forms/form.go b/web/forms/form.go
deleted file mode 100644
index 1deab80..0000000
--- a/web/forms/form.go
+++ /dev/null
@@ -1,194 +0,0 @@
-package forms
-
-import (
- "fmt"
- "mime/multipart"
- "owl-blogs/app"
- "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 Form struct {
- data interface{}
- binSvc *app.BinaryService
-}
-
-type FormFieldParams struct {
- InputType string
- Widget string
-}
-
-type FormField struct {
- Name string
- Value reflect.Value
- Params FormFieldParams
-}
-
-func NewForm(data interface{}, binaryService *app.BinaryService) *Form {
- return &Form{
- data: data,
- binSvc: binaryService,
- }
-}
-
-func (s *FormFieldParams) 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 *FormField) ToWidget() Widget {
- switch s.Params.Widget {
- case "textarea":
- return &TextareaWidget{*s}
- case "textlist":
- return &TextListWidget{*s}
- case "password":
- return &PasswordWidget{*s}
- default:
- return &TextWidget{*s}
- }
-}
-
-func (s *FormField) Html() string {
- html := ""
- html += fmt.Sprintf("\n", s.Name, s.Name)
- if s.Params.InputType == "file" {
- html += fmt.Sprintf("\n", s.Params.InputType, s.Name, s.Name, s.Value)
- } else {
- html += s.ToWidget().Html()
- html += "\n"
- }
- return html
-}
-
-func FieldToFormField(field reflect.StructField, value reflect.Value) (FormField, error) {
- formField := FormField{
- Name: field.Name,
- Value: value,
- Params: FormFieldParams{},
- }
- 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 FormField{}, err
- }
- }
- return formField, nil
-}
-
-func StructToFormFields(data interface{}) ([]FormField, error) {
- dataValue := reflect.Indirect(reflect.ValueOf(data))
- dataType := reflect.TypeOf(data).Elem()
- numFields := dataType.NumField()
- fields := []FormField{}
- for i := 0; i < numFields; i++ {
- field, err := FieldToFormField(
- dataType.Field(i),
- dataValue.FieldByIndex([]int{i}),
- )
- if err != nil {
- return nil, err
- }
- fields = append(fields, field)
- }
- return fields, nil
-}
-
-func (s *Form) HtmlForm() (string, error) {
- fields, err := StructToFormFields(s.data)
- if err != nil {
- return "", err
- }
-
- html := ""
- for _, field := range fields {
- html += field.Html()
- }
-
- return html, nil
-}
-
-func (s *Form) Parse(ctx HttpFormData) (interface{}, error) {
- if ctx == nil {
- return nil, fmt.Errorf("nil context")
- }
- dataVal := reflect.ValueOf(s.data)
- if dataVal.Kind() != reflect.Ptr {
- return nil, fmt.Errorf("meta data is not a pointer")
- }
- fields, err := StructToFormFields(s.data)
- 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 {
- // If field already has a value, we can ignore the error
- if field.Value != reflect.Zero(field.Value.Type()) {
- metaField := dataVal.Elem().FieldByName(fieldName)
- if metaField.IsValid() {
- metaField.SetString(field.Value.String())
- }
- continue
- }
- 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 := dataVal.Elem().FieldByName(fieldName)
- if metaField.IsValid() {
- metaField.SetString(binaryFile.Id)
- }
- } else {
- formValue := ctx.FormValue(fieldName)
- metaField := dataVal.Elem().FieldByName(fieldName)
- if metaField.IsValid() {
- field.ToWidget().ParseValue(formValue, metaField)
- }
- }
-
- }
-
- return s.data, nil
-}
diff --git a/web/forms/form_test.go b/web/forms/form_test.go
deleted file mode 100644
index bd1bb56..0000000
--- a/web/forms/form_test.go
+++ /dev/null
@@ -1,108 +0,0 @@
-package forms_test
-
-import (
- "bytes"
- "io"
- "mime/multipart"
- "os"
- "owl-blogs/app"
- "owl-blogs/infra"
- "owl-blogs/test"
- "owl-blogs/web/forms"
- "path"
- "path/filepath"
- "reflect"
- "testing"
-
- "github.com/stretchr/testify/require"
-)
-
-type MockData struct {
- Image string `owl:"inputType=file"`
- Content string `owl:"inputType=text"`
-}
-
-type MockFormData struct {
- fileHeader *multipart.FileHeader
-}
-
-func NewMockFormData() *MockFormData {
- fileDir, _ := os.Getwd()
- fileName := "../../test/fixtures/test.png"
- filePath := path.Join(fileDir, fileName)
-
- file, err := os.Open(filePath)
- if err != nil {
- panic(err)
- }
- defer file.Close()
-
- body := &bytes.Buffer{}
- writer := multipart.NewWriter(body)
- part, err := writer.CreateFormFile("ImagePath", filepath.Base(file.Name()))
- if err != nil {
- panic(err)
- }
- io.Copy(part, file)
- writer.Close()
-
- multipartForm := multipart.NewReader(body, writer.Boundary())
- formData, err := multipartForm.ReadForm(0)
- if err != nil {
- panic(err)
- }
- fileHeader := formData.File["ImagePath"][0]
-
- return &MockFormData{fileHeader: fileHeader}
-}
-
-func (f *MockFormData) FormFile(key string) (*multipart.FileHeader, error) {
- return f.fileHeader, nil
-}
-
-func (f *MockFormData) FormValue(key string, defaultValue ...string) string {
- return key
-}
-
-func TestFieldToFormField(t *testing.T) {
- field := reflect.TypeOf(&MockData{}).Elem().Field(0)
- formField, err := forms.FieldToFormField(field, reflect.Value{})
- require.NoError(t, err)
- require.Equal(t, "Image", formField.Name)
- require.Equal(t, "file", formField.Params.InputType)
-}
-
-func TestStructToFields(t *testing.T) {
- fields, err := forms.StructToFormFields(&MockData{})
- require.NoError(t, err)
- require.Len(t, fields, 2)
- require.Equal(t, "Image", fields[0].Name)
- require.Equal(t, "file", fields[0].Params.InputType)
- require.Equal(t, "Content", fields[1].Name)
- require.Equal(t, "text", fields[1].Params.InputType)
-}
-
-func TestForm_HtmlForm(t *testing.T) {
- form := forms.NewForm(&MockData{}, nil)
- html, err := form.HtmlForm()
- require.NoError(t, err)
- require.Contains(t, html, "\n", s.Name, s.Value.String())
- return html
-}
-
-func (s *TextWidget) ParseValue(value string, output reflect.Value) error {
- output.SetString(value)
- return nil
-}
-
-type PasswordWidget struct {
- FormField
-}
-
-func (s *PasswordWidget) Html() string {
- html := ""
- html += fmt.Sprintf("\n", s.Name, s.Value.String())
- return html
-}
-
-func (s *PasswordWidget) ParseValue(value string, output reflect.Value) error {
- output.SetString(value)
- return nil
-}
-
-type TextareaWidget struct {
- FormField
-}
-
-func (s *TextareaWidget) Html() string {
- html := ""
- html += fmt.Sprintf("\n", s.Name, s.Value.String())
- return html
-}
-
-func (s *TextareaWidget) ParseValue(value string, output reflect.Value) error {
- output.SetString(value)
- return nil
-}
-
-type TextListWidget struct {
- FormField
-}
-
-func (s *TextListWidget) Html() string {
- valueList := s.Value.Interface().([]string)
- value := strings.Join(valueList, "\n")
-
- html := ""
- html += fmt.Sprintf("\n", s.Name, value)
- return html
-}
-
-func (s *TextListWidget) ParseValue(value string, output reflect.Value) error {
- list := strings.Split(value, "\n")
- // trim entries
- for i, item := range list {
- list[i] = strings.TrimSpace(item)
- }
- // remove empty entries
- for i := len(list) - 1; i >= 0; i-- {
- if list[i] == "" {
- list = append(list[:i], list[i+1:]...)
- }
- }
-
- output.Set(reflect.ValueOf(list))
- return nil
-}