v2 #43
|
@ -3,6 +3,7 @@ package app
|
|||
import (
|
||||
"owl-blogs/app/repository"
|
||||
"owl-blogs/domain/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
type EntryService struct {
|
||||
|
@ -13,8 +14,8 @@ func NewEntryService(entryRepository repository.EntryRepository) *EntryService {
|
|||
return &EntryService{EntryRepository: entryRepository}
|
||||
}
|
||||
|
||||
func (s *EntryService) Create(entry model.Entry) error {
|
||||
return s.EntryRepository.Create(entry)
|
||||
func (s *EntryService) Create(entry model.Entry, publishedAt *time.Time, metaData model.EntryMetaData) error {
|
||||
return s.EntryRepository.Create(entry, publishedAt, metaData)
|
||||
}
|
||||
|
||||
func (s *EntryService) Update(entry model.Entry) error {
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
package repository
|
||||
|
||||
import "owl-blogs/domain/model"
|
||||
import (
|
||||
"owl-blogs/domain/model"
|
||||
"time"
|
||||
)
|
||||
|
||||
type EntryRepository interface {
|
||||
Create(entry model.Entry) error
|
||||
Create(entry model.Entry, publishedAt *time.Time, metaData model.EntryMetaData) error
|
||||
Update(entry model.Entry) error
|
||||
Delete(entry model.Entry) error
|
||||
FindById(id string) (model.Entry, error)
|
||||
|
|
|
@ -10,6 +10,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/jmoiron/sqlx"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
@ -27,12 +28,7 @@ type DefaultEntryRepo struct {
|
|||
}
|
||||
|
||||
// Create implements repository.EntryRepository.
|
||||
func (r *DefaultEntryRepo) Create(entry model.Entry) error {
|
||||
exEntry, _ := r.FindById(entry.ID())
|
||||
if exEntry != nil {
|
||||
return errors.New("entry already exists")
|
||||
}
|
||||
|
||||
func (r *DefaultEntryRepo) Create(entry model.Entry, publishedAt *time.Time, metaData model.EntryMetaData) error {
|
||||
t, err := r.typeRegistry.TypeName(entry)
|
||||
if err != nil {
|
||||
return errors.New("entry type not registered")
|
||||
|
@ -40,10 +36,12 @@ func (r *DefaultEntryRepo) Create(entry model.Entry) error {
|
|||
|
||||
var metaDataJson []byte
|
||||
if entry.MetaData() != nil {
|
||||
metaDataJson, _ = json.Marshal(entry.MetaData())
|
||||
metaDataJson, _ = json.Marshal(metaData)
|
||||
}
|
||||
|
||||
_, err = r.db.Exec("INSERT INTO entries (id, type, published_at, meta_data) VALUES (?, ?, ?, ?)", entry.ID(), t, entry.PublishedAt(), metaDataJson)
|
||||
id := uuid.New().String()
|
||||
_, err = r.db.Exec("INSERT INTO entries (id, type, published_at, meta_data) VALUES (?, ?, ?, ?)", id, t, publishedAt, metaDataJson)
|
||||
entry.Create(id, publishedAt, metaData)
|
||||
return err
|
||||
}
|
||||
|
||||
|
|
|
@ -24,15 +24,14 @@ func TestRepoCreate(t *testing.T) {
|
|||
|
||||
entry := &test.MockEntry{}
|
||||
now := time.Now()
|
||||
entry.Create("id", &now, &test.MockEntryMetaData{
|
||||
err := repo.Create(entry, &now, &test.MockEntryMetaData{
|
||||
Str: "str",
|
||||
Number: 1,
|
||||
Date: now,
|
||||
})
|
||||
err := repo.Create(entry)
|
||||
require.NoError(t, err)
|
||||
|
||||
entry2, err := repo.FindById("id")
|
||||
entry2, err := repo.FindById(entry.ID())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, entry.ID(), entry2.ID())
|
||||
require.Equal(t, entry.Content(), entry2.Content())
|
||||
|
@ -49,12 +48,11 @@ func TestRepoDelete(t *testing.T) {
|
|||
|
||||
entry := &test.MockEntry{}
|
||||
now := time.Now()
|
||||
entry.Create("id", &now, &test.MockEntryMetaData{
|
||||
err := repo.Create(entry, &now, &test.MockEntryMetaData{
|
||||
Str: "str",
|
||||
Number: 1,
|
||||
Date: now,
|
||||
})
|
||||
err := repo.Create(entry)
|
||||
require.NoError(t, err)
|
||||
|
||||
err = repo.Delete(entry)
|
||||
|
@ -69,22 +67,21 @@ func TestRepoFindAll(t *testing.T) {
|
|||
|
||||
entry := &test.MockEntry{}
|
||||
now := time.Now()
|
||||
entry.Create("id", &now, &test.MockEntryMetaData{
|
||||
err := repo.Create(entry, &now, &test.MockEntryMetaData{
|
||||
Str: "str",
|
||||
Number: 1,
|
||||
Date: now,
|
||||
})
|
||||
err := repo.Create(entry)
|
||||
|
||||
require.NoError(t, err)
|
||||
|
||||
entry2 := &test.MockEntry{}
|
||||
now2 := time.Now()
|
||||
entry2.Create("id2", &now2, &test.MockEntryMetaData{
|
||||
Str: "str2",
|
||||
Number: 2,
|
||||
Date: now2,
|
||||
err = repo.Create(entry2, &now2, &test.MockEntryMetaData{
|
||||
Str: "str",
|
||||
Number: 1,
|
||||
Date: now,
|
||||
})
|
||||
err = repo.Create(entry2)
|
||||
require.NoError(t, err)
|
||||
|
||||
entries, err := repo.FindAll(nil)
|
||||
|
@ -106,25 +103,25 @@ func TestRepoUpdate(t *testing.T) {
|
|||
|
||||
entry := &test.MockEntry{}
|
||||
now := time.Now()
|
||||
entry.Create("id", &now, &test.MockEntryMetaData{
|
||||
err := repo.Create(entry, &now, &test.MockEntryMetaData{
|
||||
Str: "str",
|
||||
Number: 1,
|
||||
Date: now,
|
||||
})
|
||||
err := repo.Create(entry)
|
||||
require.NoError(t, err)
|
||||
|
||||
entry2 := &test.MockEntry{}
|
||||
now2 := time.Now()
|
||||
entry2.Create("id", &now2, &test.MockEntryMetaData{
|
||||
err = repo.Create(entry2, &now2, &test.MockEntryMetaData{
|
||||
Str: "str2",
|
||||
Number: 2,
|
||||
Date: now2,
|
||||
})
|
||||
require.NoError(t, err)
|
||||
err = repo.Update(entry2)
|
||||
require.NoError(t, err)
|
||||
|
||||
entry3, err := repo.FindById("id")
|
||||
entry3, err := repo.FindById(entry2.ID())
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, entry3.Content(), entry2.Content())
|
||||
require.Equal(t, entry3.PublishedAt().Unix(), entry2.PublishedAt().Unix())
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package editor_test
|
||||
|
||||
import (
|
||||
"mime/multipart"
|
||||
"owl-blogs/domain/model"
|
||||
"owl-blogs/web/editor"
|
||||
"reflect"
|
||||
|
@ -15,10 +16,21 @@ type MockEntryMetaData struct {
|
|||
Content string `owl:"inputType=text"`
|
||||
}
|
||||
|
||||
type MockFormData struct {
|
||||
}
|
||||
|
||||
func (f *MockFormData) FormFile(key string) (*multipart.FileHeader, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (f *MockFormData) FormValue(key string, defaultValue ...string) string {
|
||||
return key
|
||||
}
|
||||
|
||||
type MockEntry struct {
|
||||
id string
|
||||
publishedAt *time.Time
|
||||
metaData *MockEntryMetaData
|
||||
metaData MockEntryMetaData
|
||||
}
|
||||
|
||||
func (e *MockEntry) ID() string {
|
||||
|
@ -34,13 +46,13 @@ func (e *MockEntry) PublishedAt() *time.Time {
|
|||
}
|
||||
|
||||
func (e *MockEntry) MetaData() interface{} {
|
||||
return e.metaData
|
||||
return &e.metaData
|
||||
}
|
||||
|
||||
func (e *MockEntry) Create(id string, publishedAt *time.Time, metaData model.EntryMetaData) error {
|
||||
e.id = id
|
||||
e.publishedAt = publishedAt
|
||||
e.metaData = metaData.(*MockEntryMetaData)
|
||||
e.metaData = *metaData.(*MockEntryMetaData)
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -63,13 +75,27 @@ func TestStructToFields(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestEditorEntryForm_HtmlForm(t *testing.T) {
|
||||
formService := editor.NewEditorFormService(&MockEntry{})
|
||||
form, err := formService.HtmlForm()
|
||||
form := editor.NewEntryForm(&MockEntry{})
|
||||
html, err := form.HtmlForm()
|
||||
require.NoError(t, err)
|
||||
require.Contains(t, form, "<form")
|
||||
require.Contains(t, form, "method=\"POST\"")
|
||||
require.Contains(t, form, "<input type=\"file\" name=\"Image\" />")
|
||||
require.Contains(t, form, "<input type=\"text\" name=\"Content\" />")
|
||||
require.Contains(t, form, "<input type=\"submit\" value=\"Submit\" />")
|
||||
require.Contains(t, html, "<form")
|
||||
require.Contains(t, html, "method=\"POST\"")
|
||||
require.Contains(t, html, "<input type=\"file\" name=\"Image\"")
|
||||
require.Contains(t, html, "<input type=\"text\" name=\"Content\"")
|
||||
require.Contains(t, html, "<input type=\"submit\" value=\"Submit\"")
|
||||
|
||||
}
|
||||
|
||||
func TestFormParseNil(t *testing.T) {
|
||||
form := editor.NewEntryForm(&MockEntry{})
|
||||
_, err := form.Parse(nil)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
func TestFormParse(t *testing.T) {
|
||||
form := editor.NewEntryForm(&MockEntry{})
|
||||
entry, err := form.Parse(&MockFormData{})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, "Image", entry.MetaData().(*MockEntryMetaData).Image)
|
||||
require.Equal(t, "Content", entry.MetaData().(*MockEntryMetaData).Content)
|
||||
}
|
||||
|
|
|
@ -2,17 +2,31 @@ package editor
|
|||
|
||||
import (
|
||||
"fmt"
|
||||
"mime/multipart"
|
||||
"owl-blogs/domain/model"
|
||||
"reflect"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type HttpFormData interface {
|
||||
// FormFile returns the first file by key from a MultipartForm.
|
||||
FormFile(key string) (*multipart.FileHeader, error)
|
||||
// FormValue returns the first value by key from a MultipartForm.
|
||||
// Search is performed in QueryArgs, PostArgs, MultipartForm and FormFile in this particular order.
|
||||
// Defaults to the empty string "" if the form value doesn't exist.
|
||||
// If a default value is given, it will return that value if the form value does not exist.
|
||||
// Returned value is only valid within the handler. Do not store any references.
|
||||
// Make copies or use the Immutable setting instead.
|
||||
FormValue(key string, defaultValue ...string) string
|
||||
}
|
||||
|
||||
type EditorEntryForm struct {
|
||||
entry model.Entry
|
||||
}
|
||||
|
||||
type EntryFormFieldParams struct {
|
||||
InputType string
|
||||
Widget string
|
||||
}
|
||||
|
||||
type EntryFormField struct {
|
||||
|
@ -20,7 +34,7 @@ type EntryFormField struct {
|
|||
Params EntryFormFieldParams
|
||||
}
|
||||
|
||||
func NewEditorFormService(entry model.Entry) *EditorEntryForm {
|
||||
func NewEntryForm(entry model.Entry) *EditorEntryForm {
|
||||
return &EditorEntryForm{
|
||||
entry: entry,
|
||||
}
|
||||
|
@ -30,6 +44,8 @@ func (s *EntryFormFieldParams) ApplyTag(tagKey string, tagValue string) error {
|
|||
switch tagKey {
|
||||
case "inputType":
|
||||
s.InputType = tagValue
|
||||
case "widget":
|
||||
s.Widget = tagValue
|
||||
default:
|
||||
return fmt.Errorf("unknown tag key: %v", tagKey)
|
||||
}
|
||||
|
@ -37,7 +53,14 @@ func (s *EntryFormFieldParams) ApplyTag(tagKey string, tagValue string) error {
|
|||
}
|
||||
|
||||
func (s *EntryFormField) Html() string {
|
||||
return fmt.Sprintf("<input type=\"%v\" name=\"%v\" />\n", s.Params.InputType, s.Name)
|
||||
html := ""
|
||||
html += fmt.Sprintf("<label for=\"%v\">%v</label>\n", s.Name, s.Name)
|
||||
if s.Params.InputType == "text" && s.Params.Widget == "textarea" {
|
||||
html += fmt.Sprintf("<textarea name=\"%v\" id=\"%v\" rows=\"20\"></textarea>\n", s.Name, s.Name)
|
||||
} else {
|
||||
html += fmt.Sprintf("<input type=\"%v\" name=\"%v\" id=\"%v\" />\n", s.Params.InputType, s.Name, s.Name)
|
||||
}
|
||||
return html
|
||||
}
|
||||
|
||||
func FieldToFormField(field reflect.StructField) (EntryFormField, error) {
|
||||
|
@ -89,3 +112,28 @@ func (s *EditorEntryForm) HtmlForm() (string, error) {
|
|||
|
||||
return html, nil
|
||||
}
|
||||
|
||||
func (s *EditorEntryForm) Parse(ctx HttpFormData) (model.Entry, error) {
|
||||
if ctx == nil {
|
||||
return nil, fmt.Errorf("nil context")
|
||||
}
|
||||
meta := s.entry.MetaData()
|
||||
metaVal := reflect.ValueOf(meta)
|
||||
if metaVal.Kind() != reflect.Ptr {
|
||||
return nil, fmt.Errorf("meta data is not a pointer")
|
||||
}
|
||||
fields, err := StructToFormFields(meta)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for field := range fields {
|
||||
fieldName := fields[field].Name
|
||||
fieldValue := ctx.FormValue(fieldName)
|
||||
metaField := metaVal.Elem().FieldByName(fieldName)
|
||||
if metaField.IsValid() {
|
||||
metaField.SetString(fieldValue)
|
||||
}
|
||||
}
|
||||
|
||||
return s.entry, nil
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"owl-blogs/app"
|
||||
"owl-blogs/domain/model"
|
||||
"owl-blogs/web/editor"
|
||||
"time"
|
||||
|
||||
"github.com/gofiber/fiber/v2"
|
||||
)
|
||||
|
@ -18,15 +19,30 @@ func NewEditorHandler(entryService *app.EntryService) *EditorHandler {
|
|||
|
||||
func (h *EditorHandler) HandleGet(c *fiber.Ctx) error {
|
||||
c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
|
||||
formService := editor.NewEditorFormService(&model.ImageEntry{})
|
||||
form, err := formService.HtmlForm()
|
||||
form := editor.NewEntryForm(&model.ImageEntry{})
|
||||
htmlForm, err := form.HtmlForm()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return c.SendString(form)
|
||||
return c.SendString(htmlForm)
|
||||
}
|
||||
|
||||
func (h *EditorHandler) HandlePost(c *fiber.Ctx) error {
|
||||
c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
|
||||
|
||||
form := editor.NewEntryForm(&model.ImageEntry{})
|
||||
// get form data
|
||||
metaData, err := form.Parse(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// create entry
|
||||
now := time.Now()
|
||||
err = h.entrySvc.Create(&model.ImageEntry{}, &now, metaData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return c.SendString("Hello, Editor!")
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue