Better Editor Forms #56

Merged
h4kor merged 6 commits from better_forms into main 2024-02-21 19:05:08 +00:00
15 changed files with 112 additions and 48 deletions
Showing only changes of commit b86eee27ce - Show all commits

View File

@ -0,0 +1,5 @@
package model
type BinaryStorageInterface interface {
Create(name string, file []byte) (*BinaryFile, error)
}

View File

@ -1,6 +1,8 @@
package model package model
import "time" import (
"time"
)
type EntryContent string type EntryContent string
@ -9,18 +11,20 @@ type Entry interface {
Content() EntryContent Content() EntryContent
PublishedAt() *time.Time PublishedAt() *time.Time
AuthorId() string AuthorId() string
MetaData() interface{} MetaData() EntryMetaData
// Optional: can return empty string // Optional: can return empty string
Title() string Title() string
SetID(id string) SetID(id string)
SetPublishedAt(publishedAt *time.Time) SetPublishedAt(publishedAt *time.Time)
SetMetaData(metaData interface{}) SetMetaData(metaData EntryMetaData)
SetAuthorId(authorId string) SetAuthorId(authorId string)
} }
type EntryMetaData interface { type EntryMetaData interface {
Form(binSvc BinaryStorageInterface) string
ParseFormData(data HttpFormData, binSvc BinaryStorageInterface) (EntryMetaData, error)
} }
type EntryBase struct { type EntryBase struct {

15
domain/model/form_data.go Normal file
View File

@ -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
}

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"owl-blogs/domain/model" "owl-blogs/domain/model"
"owl-blogs/render" "owl-blogs/render"
"owl-blogs/web/forms"
) )
type Article struct { type Article struct {
@ -12,6 +13,7 @@ type Article struct {
} }
type ArticleMetaData struct { type ArticleMetaData struct {
forms.DefaultForm
Title string `owl:"inputType=text"` Title string `owl:"inputType=text"`
Content string `owl:"inputType=text widget=textarea"` Content string `owl:"inputType=text widget=textarea"`
} }
@ -28,10 +30,10 @@ func (e *Article) Content() model.EntryContent {
return model.EntryContent(str) return model.EntryContent(str)
} }
func (e *Article) MetaData() interface{} { func (e *Article) MetaData() model.EntryMetaData {
return &e.meta return &e.meta
} }
func (e *Article) SetMetaData(metaData interface{}) { func (e *Article) SetMetaData(metaData model.EntryMetaData) {
e.meta = *metaData.(*ArticleMetaData) e.meta = *metaData.(*ArticleMetaData)
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"owl-blogs/domain/model" "owl-blogs/domain/model"
"owl-blogs/render" "owl-blogs/render"
"owl-blogs/web/forms"
) )
type Bookmark struct { type Bookmark struct {
@ -12,6 +13,8 @@ type Bookmark struct {
} }
type BookmarkMetaData struct { type BookmarkMetaData struct {
forms.DefaultForm
Title string `owl:"inputType=text"` Title string `owl:"inputType=text"`
Url string `owl:"inputType=text"` Url string `owl:"inputType=text"`
Content string `owl:"inputType=text widget=textarea"` Content string `owl:"inputType=text widget=textarea"`
@ -29,10 +32,10 @@ func (e *Bookmark) Content() model.EntryContent {
return model.EntryContent(str) return model.EntryContent(str)
} }
func (e *Bookmark) MetaData() interface{} { func (e *Bookmark) MetaData() model.EntryMetaData {
return &e.meta return &e.meta
} }
func (e *Bookmark) SetMetaData(metaData interface{}) { func (e *Bookmark) SetMetaData(metaData model.EntryMetaData) {
e.meta = *metaData.(*BookmarkMetaData) e.meta = *metaData.(*BookmarkMetaData)
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"owl-blogs/domain/model" "owl-blogs/domain/model"
"owl-blogs/render" "owl-blogs/render"
"owl-blogs/web/forms"
) )
type Image struct { type Image struct {
@ -12,6 +13,8 @@ type Image struct {
} }
type ImageMetaData struct { type ImageMetaData struct {
forms.DefaultForm
ImageId string `owl:"inputType=file"` ImageId string `owl:"inputType=file"`
Title string `owl:"inputType=text"` Title string `owl:"inputType=text"`
Content string `owl:"inputType=text widget=textarea"` Content string `owl:"inputType=text widget=textarea"`
@ -29,10 +32,10 @@ func (e *Image) Content() model.EntryContent {
return model.EntryContent(str) return model.EntryContent(str)
} }
func (e *Image) MetaData() interface{} { func (e *Image) MetaData() model.EntryMetaData {
return &e.meta return &e.meta
} }
func (e *Image) SetMetaData(metaData interface{}) { func (e *Image) SetMetaData(metaData model.EntryMetaData) {
e.meta = *metaData.(*ImageMetaData) e.meta = *metaData.(*ImageMetaData)
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"owl-blogs/domain/model" "owl-blogs/domain/model"
"owl-blogs/render" "owl-blogs/render"
"owl-blogs/web/forms"
) )
type Note struct { type Note struct {
@ -12,6 +13,8 @@ type Note struct {
} }
type NoteMetaData struct { type NoteMetaData struct {
forms.DefaultForm
Content string `owl:"inputType=text widget=textarea"` Content string `owl:"inputType=text widget=textarea"`
} }
@ -27,10 +30,10 @@ func (e *Note) Content() model.EntryContent {
return model.EntryContent(str) return model.EntryContent(str)
} }
func (e *Note) MetaData() interface{} { func (e *Note) MetaData() model.EntryMetaData {
return &e.meta return &e.meta
} }
func (e *Note) SetMetaData(metaData interface{}) { func (e *Note) SetMetaData(metaData model.EntryMetaData) {
e.meta = *metaData.(*NoteMetaData) e.meta = *metaData.(*NoteMetaData)
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"owl-blogs/domain/model" "owl-blogs/domain/model"
"owl-blogs/render" "owl-blogs/render"
"owl-blogs/web/forms"
) )
type Page struct { type Page struct {
@ -12,6 +13,8 @@ type Page struct {
} }
type PageMetaData struct { type PageMetaData struct {
forms.DefaultForm
Title string `owl:"inputType=text"` Title string `owl:"inputType=text"`
Content string `owl:"inputType=text widget=textarea"` Content string `owl:"inputType=text widget=textarea"`
} }
@ -28,10 +31,10 @@ func (e *Page) Content() model.EntryContent {
return model.EntryContent(str) return model.EntryContent(str)
} }
func (e *Page) MetaData() interface{} { func (e *Page) MetaData() model.EntryMetaData {
return &e.meta return &e.meta
} }
func (e *Page) SetMetaData(metaData interface{}) { func (e *Page) SetMetaData(metaData model.EntryMetaData) {
e.meta = *metaData.(*PageMetaData) e.meta = *metaData.(*PageMetaData)
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"owl-blogs/domain/model" "owl-blogs/domain/model"
"owl-blogs/render" "owl-blogs/render"
"owl-blogs/web/forms"
) )
type Recipe struct { type Recipe struct {
@ -12,6 +13,8 @@ type Recipe struct {
} }
type RecipeMetaData struct { type RecipeMetaData struct {
forms.DefaultForm
Title string `owl:"inputType=text"` Title string `owl:"inputType=text"`
Yield string `owl:"inputType=text"` Yield string `owl:"inputType=text"`
Duration string `owl:"inputType=text"` Duration string `owl:"inputType=text"`
@ -31,10 +34,10 @@ func (e *Recipe) Content() model.EntryContent {
return model.EntryContent(str) return model.EntryContent(str)
} }
func (e *Recipe) MetaData() interface{} { func (e *Recipe) MetaData() model.EntryMetaData {
return &e.meta return &e.meta
} }
func (e *Recipe) SetMetaData(metaData interface{}) { func (e *Recipe) SetMetaData(metaData model.EntryMetaData) {
e.meta = *metaData.(*RecipeMetaData) e.meta = *metaData.(*RecipeMetaData)
} }

View File

@ -4,6 +4,7 @@ import (
"fmt" "fmt"
"owl-blogs/domain/model" "owl-blogs/domain/model"
"owl-blogs/render" "owl-blogs/render"
"owl-blogs/web/forms"
) )
type Reply struct { type Reply struct {
@ -12,6 +13,8 @@ type Reply struct {
} }
type ReplyMetaData struct { type ReplyMetaData struct {
forms.DefaultForm
Title string `owl:"inputType=text"` Title string `owl:"inputType=text"`
Url string `owl:"inputType=text"` Url string `owl:"inputType=text"`
Content string `owl:"inputType=text widget=textarea"` Content string `owl:"inputType=text widget=textarea"`
@ -29,10 +32,10 @@ func (e *Reply) Content() model.EntryContent {
return model.EntryContent(str) return model.EntryContent(str)
} }
func (e *Reply) MetaData() interface{} { func (e *Reply) MetaData() model.EntryMetaData {
return &e.meta return &e.meta
} }
func (e *Reply) SetMetaData(metaData interface{}) { func (e *Reply) SetMetaData(metaData model.EntryMetaData) {
e.meta = *metaData.(*ReplyMetaData) e.meta = *metaData.(*ReplyMetaData)
} }

View File

@ -151,7 +151,7 @@ func (r *DefaultEntryRepo) sqlEntryToEntry(entry sqlEntry) (model.Entry, error)
if err != nil { if err != nil {
return nil, errors.New("entry type not registered") 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) json.Unmarshal([]byte(*entry.MetaData), metaData)
e.SetID(entry.Id) e.SetID(entry.Id)
e.SetPublishedAt(entry.PublishedAt) e.SetPublishedAt(entry.PublishedAt)

View File

@ -2,10 +2,13 @@ package test
import ( import (
"owl-blogs/domain/model" "owl-blogs/domain/model"
"owl-blogs/web/forms"
"time" "time"
) )
type MockEntryMetaData struct { type MockEntryMetaData struct {
forms.DefaultForm
Str string Str string
Number int Number int
Date time.Time Date time.Time
@ -21,11 +24,11 @@ func (e *MockEntry) Content() model.EntryContent {
return model.EntryContent(e.metaData.Str) return model.EntryContent(e.metaData.Str)
} }
func (e *MockEntry) MetaData() interface{} { func (e *MockEntry) MetaData() model.EntryMetaData {
return e.metaData return e.metaData
} }
func (e *MockEntry) SetMetaData(metaData interface{}) { func (e *MockEntry) SetMetaData(metaData model.EntryMetaData) {
e.metaData = metaData.(*MockEntryMetaData) e.metaData = metaData.(*MockEntryMetaData)
} }

View File

@ -1 +1 @@
exit status 1 exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1

View File

@ -2,27 +2,27 @@ package forms
import ( import (
"fmt" "fmt"
"mime/multipart" "owl-blogs/domain/model"
"owl-blogs/app"
"reflect" "reflect"
"strings" "strings"
) )
type HttpFormData interface { type DefaultForm struct{}
// FormFile returns the first file by key from a MultipartForm.
FormFile(key string) (*multipart.FileHeader, error) func (meta *DefaultForm) Form(binSvc model.BinaryStorageInterface) string {
// FormValue returns the first value by key from a MultipartForm. form := NewForm(meta, nil)
// Search is performed in QueryArgs, PostArgs, MultipartForm and FormFile in this particular order. htmlForm, _ := form.HtmlForm()
// Defaults to the empty string "" if the form value doesn't exist. return htmlForm
// 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 { func (meta *DefaultForm) ParseFormData(data model.HttpFormData, binSvc model.BinaryStorageInterface) (model.EntryMetaData, error) {
data interface{} form := NewForm(meta, binSvc)
binSvc *app.BinaryService return form.Parse(data)
}
type Form[T interface{}] struct {
data T
binSvc model.BinaryStorageInterface
} }
type FormFieldParams struct { type FormFieldParams struct {
@ -36,8 +36,8 @@ type FormField struct {
Params FormFieldParams Params FormFieldParams
} }
func NewForm(data interface{}, binaryService *app.BinaryService) *Form { func NewForm[T interface{}](data T, binaryService model.BinaryStorageInterface) *Form[T] {
return &Form{ return &Form[T]{
data: data, data: data,
binSvc: binaryService, binSvc: binaryService,
} }
@ -63,8 +63,10 @@ func (s *FormField) ToWidget() Widget {
return &TextListWidget{*s} return &TextListWidget{*s}
case "password": case "password":
return &PasswordWidget{*s} return &PasswordWidget{*s}
default: case "text":
return &TextWidget{*s} return &TextWidget{*s}
default:
return &OmitWidget{*s}
} }
} }
@ -118,7 +120,7 @@ func StructToFormFields(data interface{}) ([]FormField, error) {
return fields, nil return fields, nil
} }
func (s *Form) HtmlForm() (string, error) { func (s *Form[T]) HtmlForm() (string, error) {
fields, err := StructToFormFields(s.data) fields, err := StructToFormFields(s.data)
if err != nil { if err != nil {
return "", err return "", err
@ -132,17 +134,19 @@ func (s *Form) HtmlForm() (string, error) {
return html, nil return html, nil
} }
func (s *Form) Parse(ctx HttpFormData) (interface{}, error) { func (s *Form[T]) Parse(ctx model.HttpFormData) (T, error) {
var empty T
if ctx == nil { if ctx == nil {
return nil, fmt.Errorf("nil context") return empty, fmt.Errorf("nil context")
} }
dataVal := reflect.ValueOf(s.data) dataVal := reflect.ValueOf(s.data)
if dataVal.Kind() != reflect.Ptr { if dataVal.Kind() != reflect.Ptr {
return nil, fmt.Errorf("meta data is not a pointer") return empty, fmt.Errorf("meta data is not a pointer")
} }
fields, err := StructToFormFields(s.data) fields, err := StructToFormFields(s.data)
if err != nil { if err != nil {
return nil, err return empty, err
} }
for _, field := range fields { for _, field := range fields {
fieldName := field.Name fieldName := field.Name
@ -158,22 +162,22 @@ func (s *Form) Parse(ctx HttpFormData) (interface{}, error) {
} }
continue continue
} }
return nil, err return empty, err
} }
fileData, err := file.Open() fileData, err := file.Open()
if err != nil { if err != nil {
return nil, err return empty, err
} }
defer fileData.Close() defer fileData.Close()
fileBytes := make([]byte, file.Size) fileBytes := make([]byte, file.Size)
_, err = fileData.Read(fileBytes) _, err = fileData.Read(fileBytes)
if err != nil { if err != nil {
return nil, err return empty, err
} }
binaryFile, err := s.binSvc.Create(file.Filename, fileBytes) binaryFile, err := s.binSvc.Create(file.Filename, fileBytes)
if err != nil { if err != nil {
return nil, err return empty, err
} }
metaField := dataVal.Elem().FieldByName(fieldName) metaField := dataVal.Elem().FieldByName(fieldName)

View File

@ -26,6 +26,19 @@ func (s *TextWidget) ParseValue(value string, output reflect.Value) error {
return nil return nil
} }
type OmitWidget struct {
FormField
}
func (s *OmitWidget) Html() string {
html := ""
return html
}
func (s *OmitWidget) ParseValue(value string, output reflect.Value) error {
return nil
}
type PasswordWidget struct { type PasswordWidget struct {
FormField FormField
} }