Better Editor Forms #56
|
@ -1,19 +1,26 @@
|
||||||
package app
|
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 {
|
type ConfigRegister struct {
|
||||||
configs map[string]interface{}
|
configs map[string]AppConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
type RegisteredConfig struct {
|
type RegisteredConfig struct {
|
||||||
Name string
|
Name string
|
||||||
Config interface{}
|
Config AppConfig
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewConfigRegister() *ConfigRegister {
|
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
|
r.configs[name] = config
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -28,6 +35,6 @@ func (r *ConfigRegister) Configs() []RegisteredConfig {
|
||||||
return configs
|
return configs
|
||||||
}
|
}
|
||||||
|
|
||||||
func (r *ConfigRegister) GetConfig(name string) interface{} {
|
func (r *ConfigRegister) GetConfig(name string) AppConfig {
|
||||||
return r.configs[name]
|
return r.configs[name]
|
||||||
}
|
}
|
||||||
|
|
|
@ -77,9 +77,9 @@ func TestEditorFormPost(t *testing.T) {
|
||||||
|
|
||||||
body := &bytes.Buffer{}
|
body := &bytes.Buffer{}
|
||||||
writer := multipart.NewWriter(body)
|
writer := multipart.NewWriter(body)
|
||||||
part, _ := writer.CreateFormFile("ImageId", filepath.Base(file.Name()))
|
part, _ := writer.CreateFormFile("image", filepath.Base(file.Name()))
|
||||||
io.Copy(part, file)
|
io.Copy(part, file)
|
||||||
part, _ = writer.CreateFormField("Content")
|
part, _ = writer.CreateFormField("content")
|
||||||
io.WriteString(part, "test content")
|
io.WriteString(part, "test content")
|
||||||
writer.Close()
|
writer.Close()
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,5 @@
|
||||||
|
package model
|
||||||
|
|
||||||
|
type BinaryStorageInterface interface {
|
||||||
|
Create(name string, file []byte) (*BinaryFile, error)
|
||||||
|
}
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
|
@ -12,8 +12,22 @@ type Article struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ArticleMetaData struct {
|
type ArticleMetaData struct {
|
||||||
Title string `owl:"inputType=text"`
|
Title string
|
||||||
Content string `owl:"inputType=text widget=textarea"`
|
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 {
|
func (e *Article) Title() string {
|
||||||
|
@ -28,10 +42,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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,24 @@ type Bookmark struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type BookmarkMetaData struct {
|
type BookmarkMetaData struct {
|
||||||
Title string `owl:"inputType=text"`
|
Title string
|
||||||
Url string `owl:"inputType=text"`
|
Url string
|
||||||
Content string `owl:"inputType=text widget=textarea"`
|
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 {
|
func (e *Bookmark) Title() string {
|
||||||
|
@ -29,10 +44,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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,47 @@ type Image struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ImageMetaData struct {
|
type ImageMetaData struct {
|
||||||
ImageId string `owl:"inputType=file"`
|
ImageId string
|
||||||
Title string `owl:"inputType=text"`
|
Title string
|
||||||
Content string `owl:"inputType=text widget=textarea"`
|
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 {
|
func (e *Image) Title() string {
|
||||||
|
@ -29,10 +67,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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,7 +12,20 @@ type Note struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type NoteMetaData 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 {
|
func (e *Note) Title() string {
|
||||||
|
@ -27,10 +40,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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,22 @@ type Page struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type PageMetaData struct {
|
type PageMetaData struct {
|
||||||
Title string `owl:"inputType=text"`
|
Title string
|
||||||
Content string `owl:"inputType=text widget=textarea"`
|
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 {
|
func (e *Page) Title() string {
|
||||||
|
@ -28,10 +42,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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"owl-blogs/domain/model"
|
"owl-blogs/domain/model"
|
||||||
"owl-blogs/render"
|
"owl-blogs/render"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type Recipe struct {
|
type Recipe struct {
|
||||||
|
@ -12,11 +13,35 @@ type Recipe struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type RecipeMetaData struct {
|
type RecipeMetaData struct {
|
||||||
Title string `owl:"inputType=text"`
|
Title string
|
||||||
Yield string `owl:"inputType=text"`
|
Yield string
|
||||||
Duration string `owl:"inputType=text"`
|
Duration string
|
||||||
Ingredients []string `owl:"inputType=text widget=textlist"`
|
Ingredients []string
|
||||||
Content string `owl:"inputType=text widget=textarea"`
|
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 {
|
func (e *Recipe) Title() string {
|
||||||
|
@ -31,10 +56,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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,9 +12,24 @@ type Reply struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ReplyMetaData struct {
|
type ReplyMetaData struct {
|
||||||
Title string `owl:"inputType=text"`
|
Title string
|
||||||
Url string `owl:"inputType=text"`
|
Url string
|
||||||
Content string `owl:"inputType=text widget=textarea"`
|
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 {
|
func (e *Reply) Title() string {
|
||||||
|
@ -29,10 +44,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)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"owl-blogs/app/repository"
|
"owl-blogs/app/repository"
|
||||||
"owl-blogs/domain/model"
|
"owl-blogs/domain/model"
|
||||||
entrytypes "owl-blogs/entry_types"
|
entrytypes "owl-blogs/entry_types"
|
||||||
|
"owl-blogs/render"
|
||||||
|
|
||||||
"github.com/Davincible/goinsta/v3"
|
"github.com/Davincible/goinsta/v3"
|
||||||
)
|
)
|
||||||
|
@ -16,8 +17,22 @@ type Instagram struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type InstagramConfig struct {
|
type InstagramConfig struct {
|
||||||
User string `owl:"widget=text"`
|
User string
|
||||||
Password string `owl:"widget=password"`
|
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(
|
func RegisterInstagram(
|
||||||
|
|
|
@ -0,0 +1,8 @@
|
||||||
|
<label for="PreferredUsername">Preferred Username</label>
|
||||||
|
<input type="text" name="PreferredUsername" value="{{.PreferredUsername}}" />
|
||||||
|
|
||||||
|
<label for="PublicKeyPem">PublicKeyPem</label>
|
||||||
|
<textarea name="PublicKeyPem" rows="4">{{.PublicKeyPem}}</textarea>
|
||||||
|
|
||||||
|
<label for="PrivateKeyPem">PrivateKeyPem</label>
|
||||||
|
<textarea name="PrivateKeyPem" rows="4">{{.PrivateKeyPem}}</textarea>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<label for="title">Title</label>
|
||||||
|
<input type="text" name="title" value="{{.Title}}" />
|
||||||
|
|
||||||
|
<label for="content">Content</label>
|
||||||
|
<textarea name="content" rows="16">{{.Content}}</textarea>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<label for="title">Title</label>
|
||||||
|
<input type="text" name="title" value="{{.Title}}" />
|
||||||
|
|
||||||
|
<label for="url">URL</label>
|
||||||
|
<input type="text" name="url" value="{{.Url}}" placeholder="https://..." />
|
||||||
|
|
||||||
|
<label for="content">Content</label>
|
||||||
|
<textarea name="content" rows="16">{{.Content}}</textarea></textarea>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<label for="image">Image</label>
|
||||||
|
<input type="file" name="image" />
|
||||||
|
|
||||||
|
<label for="title">Title</label>
|
||||||
|
<input type="text" name="title" value="{{.Title}}" />
|
||||||
|
|
||||||
|
<label for="content">Content</label>
|
||||||
|
<textarea name="content" rows="16">{{.Content}}</textarea>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<label for="User">User</label>
|
||||||
|
<input type="text" name="User" value="{{.User}}" />
|
||||||
|
|
||||||
|
<label for="Password">Password</label>
|
||||||
|
<input type="password" name="Password" value="{{.Password}}" />
|
|
@ -0,0 +1,2 @@
|
||||||
|
<label for="content">Content</label>
|
||||||
|
<textarea name="content" rows="8">{{.Content}}</textarea>
|
|
@ -0,0 +1,5 @@
|
||||||
|
<label for="title">Title</label>
|
||||||
|
<input type="text" name="title" value="{{.Title}}" />
|
||||||
|
|
||||||
|
<label for="content">Content</label>
|
||||||
|
<textarea name="content" rows="16">{{.Content}}</textarea>
|
|
@ -0,0 +1,15 @@
|
||||||
|
<label for="title">Title</label>
|
||||||
|
<input type="text" name="title" value="{{.Title}}" />
|
||||||
|
|
||||||
|
<label for="yield">Yield</label>
|
||||||
|
<input type="text" name="yield" value="{{.Yield}}" />
|
||||||
|
|
||||||
|
<label for="duration">Duration</label>
|
||||||
|
<input type="text" name="duration" value="{{.Duration}}" />
|
||||||
|
|
||||||
|
<label for="ingredients">Ingredients</label>
|
||||||
|
<textarea name="ingredients" rows="8">{{ range $i := .Ingredients }}
|
||||||
|
{{$i}}{{ end }}</textarea>
|
||||||
|
|
||||||
|
<label for="content">Content</label>
|
||||||
|
<textarea name="content" rows="16">{{.Content}}</textarea>
|
|
@ -0,0 +1,8 @@
|
||||||
|
<label for="title">Title</label>
|
||||||
|
<input type="text" name="title" value="{{.Title}}" />
|
||||||
|
|
||||||
|
<label for="url">Reply To</label>
|
||||||
|
<input type="text" name="url" value="{{.Url}}" placeholder="https://..." />
|
||||||
|
|
||||||
|
<label for="content">Content</label>
|
||||||
|
<textarea name="content" rows="16">{{.Content}}</textarea></textarea>
|
|
@ -12,6 +12,16 @@ type MockEntryMetaData struct {
|
||||||
Title string
|
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 {
|
type MockEntry struct {
|
||||||
model.EntryBase
|
model.EntryBase
|
||||||
metaData *MockEntryMetaData
|
metaData *MockEntryMetaData
|
||||||
|
@ -21,11 +31,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)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1 +0,0 @@
|
||||||
exit status 1
|
|
|
@ -6,6 +6,7 @@ import (
|
||||||
"owl-blogs/app/repository"
|
"owl-blogs/app/repository"
|
||||||
"owl-blogs/config"
|
"owl-blogs/config"
|
||||||
"owl-blogs/domain/model"
|
"owl-blogs/domain/model"
|
||||||
|
"owl-blogs/render"
|
||||||
|
|
||||||
vocab "github.com/go-ap/activitypub"
|
vocab "github.com/go-ap/activitypub"
|
||||||
|
|
||||||
|
@ -20,9 +21,24 @@ type ActivityPubServer struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
type ActivityPubConfig struct {
|
type ActivityPubConfig struct {
|
||||||
PreferredUsername string `owl:"inputType=text"`
|
PreferredUsername string
|
||||||
PublicKeyPem string `owl:"inputType=text widget=textarea"`
|
PublicKeyPem string
|
||||||
PrivateKeyPem string `owl:"inputType=text widget=textarea"`
|
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 {
|
type WebfingerResponse struct {
|
||||||
|
|
|
@ -4,7 +4,6 @@ import (
|
||||||
"owl-blogs/app"
|
"owl-blogs/app"
|
||||||
"owl-blogs/app/repository"
|
"owl-blogs/app/repository"
|
||||||
"owl-blogs/render"
|
"owl-blogs/render"
|
||||||
"owl-blogs/web/forms"
|
|
||||||
"sort"
|
"sort"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
@ -14,6 +13,7 @@ type adminHandler struct {
|
||||||
configRepo repository.ConfigRepository
|
configRepo repository.ConfigRepository
|
||||||
configRegister *app.ConfigRegister
|
configRegister *app.ConfigRegister
|
||||||
typeRegistry *app.EntryTypeRegistry
|
typeRegistry *app.EntryTypeRegistry
|
||||||
|
binSvc *app.BinaryService
|
||||||
}
|
}
|
||||||
|
|
||||||
type adminContet struct {
|
type adminContet struct {
|
||||||
|
@ -75,8 +75,7 @@ func (h *adminHandler) HandleConfigGet(c *fiber.Ctx) error {
|
||||||
}
|
}
|
||||||
siteConfig := getSiteConfig(h.configRepo)
|
siteConfig := getSiteConfig(h.configRepo)
|
||||||
|
|
||||||
form := forms.NewForm(config, nil)
|
htmlForm := config.Form(h.binSvc)
|
||||||
htmlForm, err := form.HtmlForm()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -93,9 +92,7 @@ func (h *adminHandler) HandleConfigPost(c *fiber.Ctx) error {
|
||||||
return c.SendStatus(404)
|
return c.SendStatus(404)
|
||||||
}
|
}
|
||||||
|
|
||||||
form := forms.NewForm(config, nil)
|
newConfig, err := config.ParseFormData(c, h.binSvc)
|
||||||
|
|
||||||
newConfig, err := form.Parse(c)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,7 +5,6 @@ import (
|
||||||
"owl-blogs/app/repository"
|
"owl-blogs/app/repository"
|
||||||
"owl-blogs/domain/model"
|
"owl-blogs/domain/model"
|
||||||
"owl-blogs/render"
|
"owl-blogs/render"
|
||||||
"owl-blogs/web/forms"
|
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
|
@ -48,12 +47,7 @@ func (h *EditorHandler) HandleGetNew(c *fiber.Ctx) error {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
htmlForm := entryType.MetaData().Form(h.binSvc)
|
||||||
form := forms.NewForm(entryType.MetaData(), h.binSvc)
|
|
||||||
htmlForm, err := form.HtmlForm()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return render.RenderTemplateWithBase(c, getSiteConfig(h.configRepo), "views/editor", htmlForm)
|
return render.RenderTemplateWithBase(c, getSiteConfig(h.configRepo), "views/editor", htmlForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,9 +59,7 @@ func (h *EditorHandler) HandlePostNew(c *fiber.Ctx) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
form := forms.NewForm(entry.MetaData(), h.binSvc)
|
entryMeta, err := entry.MetaData().ParseFormData(c, h.binSvc)
|
||||||
// get form data
|
|
||||||
entryMeta, err := form.Parse(c)
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
@ -100,11 +92,7 @@ func (h *EditorHandler) HandleGetEdit(c *fiber.Ctx) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
form := forms.NewForm(entry.MetaData(), h.binSvc)
|
htmlForm := entry.MetaData().Form(h.binSvc)
|
||||||
htmlForm, err := form.HtmlForm()
|
|
||||||
if err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return render.RenderTemplateWithBase(c, getSiteConfig(h.configRepo), "views/editor", htmlForm)
|
return render.RenderTemplateWithBase(c, getSiteConfig(h.configRepo), "views/editor", htmlForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -117,9 +105,8 @@ func (h *EditorHandler) HandlePostEdit(c *fiber.Ctx) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
form := forms.NewForm(entry.MetaData(), h.binSvc)
|
|
||||||
// get form data
|
// get form data
|
||||||
meta, err := form.Parse(c)
|
meta, err := entry.MetaData().ParseFormData(c, h.binSvc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
|
@ -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("<label for=\"%v\">%v</label>\n", s.Name, s.Name)
|
|
||||||
if s.Params.InputType == "file" {
|
|
||||||
html += fmt.Sprintf("<input type=\"%v\" name=\"%v\" id=\"%v\" value=\"%v\" />\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
|
|
||||||
}
|
|
|
@ -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, "<input type=\"file\" name=\"Image\"")
|
|
||||||
require.Contains(t, html, "<input type=\"text\" name=\"Content\"")
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormParseNil(t *testing.T) {
|
|
||||||
form := forms.NewForm(&MockData{}, nil)
|
|
||||||
_, err := form.Parse(nil)
|
|
||||||
require.Error(t, err)
|
|
||||||
}
|
|
||||||
|
|
||||||
func TestFormParse(t *testing.T) {
|
|
||||||
binRepo := infra.NewBinaryFileRepo(test.NewMockDb())
|
|
||||||
binService := app.NewBinaryFileService(binRepo)
|
|
||||||
form := forms.NewForm(&MockData{}, binService)
|
|
||||||
data, err := form.Parse(NewMockFormData())
|
|
||||||
require.NoError(t, err)
|
|
||||||
require.NotZero(t, data.(*MockData).Image)
|
|
||||||
require.Equal(t, "Content", data.(*MockData).Content)
|
|
||||||
}
|
|
|
@ -1,87 +0,0 @@
|
||||||
package forms
|
|
||||||
|
|
||||||
import (
|
|
||||||
"fmt"
|
|
||||||
"reflect"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Widget interface {
|
|
||||||
Html() string
|
|
||||||
ParseValue(value string, output reflect.Value) error
|
|
||||||
}
|
|
||||||
|
|
||||||
type TextWidget struct {
|
|
||||||
FormField
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s *TextWidget) Html() string {
|
|
||||||
html := ""
|
|
||||||
html += fmt.Sprintf("<input type=\"text\" name=\"%v\" value=\"%v\">\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("<input type=\"password\" name=\"%v\" value=\"%v\">\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("<textarea name=\"%v\" rows=\"20\">%v</textarea>\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("<textarea name=\"%v\" rows=\"20\">%v</textarea>\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
|
|
||||||
}
|
|
Loading…
Reference in New Issue