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..d2c57ee 100644 --- a/entry_types/article.go +++ b/entry_types/article.go @@ -4,6 +4,7 @@ import ( "fmt" "owl-blogs/domain/model" "owl-blogs/render" + "owl-blogs/web/forms" ) type Article struct { @@ -12,6 +13,7 @@ type Article struct { } type ArticleMetaData struct { + forms.DefaultForm Title string `owl:"inputType=text"` Content string `owl:"inputType=text widget=textarea"` } @@ -28,10 +30,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..45b6aab 100644 --- a/entry_types/bookmark.go +++ b/entry_types/bookmark.go @@ -4,6 +4,7 @@ import ( "fmt" "owl-blogs/domain/model" "owl-blogs/render" + "owl-blogs/web/forms" ) type Bookmark struct { @@ -12,6 +13,8 @@ type Bookmark struct { } type BookmarkMetaData struct { + forms.DefaultForm + Title string `owl:"inputType=text"` Url string `owl:"inputType=text"` Content string `owl:"inputType=text widget=textarea"` @@ -29,10 +32,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..5bdf251 100644 --- a/entry_types/image.go +++ b/entry_types/image.go @@ -4,6 +4,7 @@ import ( "fmt" "owl-blogs/domain/model" "owl-blogs/render" + "owl-blogs/web/forms" ) type Image struct { @@ -12,6 +13,8 @@ type Image struct { } type ImageMetaData struct { + forms.DefaultForm + ImageId string `owl:"inputType=file"` Title string `owl:"inputType=text"` Content string `owl:"inputType=text widget=textarea"` @@ -29,10 +32,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..7dd4157 100644 --- a/entry_types/note.go +++ b/entry_types/note.go @@ -4,6 +4,7 @@ import ( "fmt" "owl-blogs/domain/model" "owl-blogs/render" + "owl-blogs/web/forms" ) type Note struct { @@ -12,6 +13,8 @@ type Note struct { } type NoteMetaData struct { + forms.DefaultForm + Content string `owl:"inputType=text widget=textarea"` } @@ -27,10 +30,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..6952de4 100644 --- a/entry_types/page.go +++ b/entry_types/page.go @@ -4,6 +4,7 @@ import ( "fmt" "owl-blogs/domain/model" "owl-blogs/render" + "owl-blogs/web/forms" ) type Page struct { @@ -12,6 +13,8 @@ type Page struct { } type PageMetaData struct { + forms.DefaultForm + Title string `owl:"inputType=text"` Content string `owl:"inputType=text widget=textarea"` } @@ -28,10 +31,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..7442592 100644 --- a/entry_types/recipe.go +++ b/entry_types/recipe.go @@ -4,6 +4,7 @@ import ( "fmt" "owl-blogs/domain/model" "owl-blogs/render" + "owl-blogs/web/forms" ) type Recipe struct { @@ -12,6 +13,8 @@ type Recipe struct { } type RecipeMetaData struct { + forms.DefaultForm + Title string `owl:"inputType=text"` Yield string `owl:"inputType=text"` Duration string `owl:"inputType=text"` @@ -31,10 +34,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..ef23467 100644 --- a/entry_types/reply.go +++ b/entry_types/reply.go @@ -4,6 +4,7 @@ import ( "fmt" "owl-blogs/domain/model" "owl-blogs/render" + "owl-blogs/web/forms" ) type Reply struct { @@ -12,6 +13,8 @@ type Reply struct { } type ReplyMetaData struct { + forms.DefaultForm + Title string `owl:"inputType=text"` Url string `owl:"inputType=text"` Content string `owl:"inputType=text widget=textarea"` @@ -29,10 +32,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/test/mock_entry.go b/test/mock_entry.go index 1b0373e..e9f9301 100644 --- a/test/mock_entry.go +++ b/test/mock_entry.go @@ -2,10 +2,13 @@ package test import ( "owl-blogs/domain/model" + "owl-blogs/web/forms" "time" ) type MockEntryMetaData struct { + forms.DefaultForm + Str string Number int Date time.Time @@ -21,11 +24,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 index 05e5985..6c6d080 100644 --- a/tmp/build-errors.log +++ b/tmp/build-errors.log @@ -1 +1 @@ -exit status 1 \ No newline at end of file +exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file diff --git a/web/forms/form.go b/web/forms/form.go index 1deab80..3737bfd 100644 --- a/web/forms/form.go +++ b/web/forms/form.go @@ -2,27 +2,27 @@ package forms 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 DefaultForm struct{} + +func (meta *DefaultForm) Form(binSvc model.BinaryStorageInterface) string { + form := NewForm(meta, nil) + htmlForm, _ := form.HtmlForm() + return htmlForm } -type Form struct { - data interface{} - binSvc *app.BinaryService +func (meta *DefaultForm) ParseFormData(data model.HttpFormData, binSvc model.BinaryStorageInterface) (model.EntryMetaData, error) { + form := NewForm(meta, binSvc) + return form.Parse(data) +} + +type Form[T interface{}] struct { + data T + binSvc model.BinaryStorageInterface } type FormFieldParams struct { @@ -36,8 +36,8 @@ type FormField struct { Params FormFieldParams } -func NewForm(data interface{}, binaryService *app.BinaryService) *Form { - return &Form{ +func NewForm[T interface{}](data T, binaryService model.BinaryStorageInterface) *Form[T] { + return &Form[T]{ data: data, binSvc: binaryService, } @@ -63,8 +63,10 @@ func (s *FormField) ToWidget() Widget { return &TextListWidget{*s} case "password": return &PasswordWidget{*s} - default: + case "text": return &TextWidget{*s} + default: + return &OmitWidget{*s} } } @@ -118,7 +120,7 @@ func StructToFormFields(data interface{}) ([]FormField, error) { return fields, nil } -func (s *Form) HtmlForm() (string, error) { +func (s *Form[T]) HtmlForm() (string, error) { fields, err := StructToFormFields(s.data) if err != nil { return "", err @@ -132,17 +134,19 @@ func (s *Form) HtmlForm() (string, error) { 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 { - return nil, fmt.Errorf("nil context") + return empty, fmt.Errorf("nil context") } dataVal := reflect.ValueOf(s.data) 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) if err != nil { - return nil, err + return empty, err } for _, field := range fields { fieldName := field.Name @@ -158,22 +162,22 @@ func (s *Form) Parse(ctx HttpFormData) (interface{}, error) { } continue } - return nil, err + return empty, err } fileData, err := file.Open() if err != nil { - return nil, err + return empty, err } defer fileData.Close() fileBytes := make([]byte, file.Size) _, err = fileData.Read(fileBytes) if err != nil { - return nil, err + return empty, err } binaryFile, err := s.binSvc.Create(file.Filename, fileBytes) if err != nil { - return nil, err + return empty, err } metaField := dataVal.Elem().FieldByName(fieldName) diff --git a/web/forms/widget.go b/web/forms/widget.go index 0c0c2e6..18f38b1 100644 --- a/web/forms/widget.go +++ b/web/forms/widget.go @@ -26,6 +26,19 @@ func (s *TextWidget) ParseValue(value string, output reflect.Value) error { 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 { FormField }