v2 #43
|
@ -3,6 +3,7 @@ package app
|
||||||
import (
|
import (
|
||||||
"owl-blogs/app/repository"
|
"owl-blogs/app/repository"
|
||||||
"owl-blogs/domain/model"
|
"owl-blogs/domain/model"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type EntryService struct {
|
type EntryService struct {
|
||||||
|
@ -13,8 +14,8 @@ func NewEntryService(entryRepository repository.EntryRepository) *EntryService {
|
||||||
return &EntryService{EntryRepository: entryRepository}
|
return &EntryService{EntryRepository: entryRepository}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *EntryService) Create(entry model.Entry) error {
|
func (s *EntryService) Create(entry model.Entry, publishedAt *time.Time, metaData model.EntryMetaData) error {
|
||||||
return s.EntryRepository.Create(entry)
|
return s.EntryRepository.Create(entry, publishedAt, metaData)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *EntryService) Update(entry model.Entry) error {
|
func (s *EntryService) Update(entry model.Entry) error {
|
||||||
|
|
|
@ -1,9 +1,12 @@
|
||||||
package repository
|
package repository
|
||||||
|
|
||||||
import "owl-blogs/domain/model"
|
import (
|
||||||
|
"owl-blogs/domain/model"
|
||||||
|
"time"
|
||||||
|
)
|
||||||
|
|
||||||
type EntryRepository interface {
|
type EntryRepository interface {
|
||||||
Create(entry model.Entry) error
|
Create(entry model.Entry, publishedAt *time.Time, metaData model.EntryMetaData) error
|
||||||
Update(entry model.Entry) error
|
Update(entry model.Entry) error
|
||||||
Delete(entry model.Entry) error
|
Delete(entry model.Entry) error
|
||||||
FindById(id string) (model.Entry, error)
|
FindById(id string) (model.Entry, error)
|
||||||
|
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
"github.com/jmoiron/sqlx"
|
"github.com/jmoiron/sqlx"
|
||||||
_ "github.com/mattn/go-sqlite3"
|
_ "github.com/mattn/go-sqlite3"
|
||||||
)
|
)
|
||||||
|
@ -27,12 +28,7 @@ type DefaultEntryRepo struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create implements repository.EntryRepository.
|
// Create implements repository.EntryRepository.
|
||||||
func (r *DefaultEntryRepo) Create(entry model.Entry) error {
|
func (r *DefaultEntryRepo) Create(entry model.Entry, publishedAt *time.Time, metaData model.EntryMetaData) error {
|
||||||
exEntry, _ := r.FindById(entry.ID())
|
|
||||||
if exEntry != nil {
|
|
||||||
return errors.New("entry already exists")
|
|
||||||
}
|
|
||||||
|
|
||||||
t, err := r.typeRegistry.TypeName(entry)
|
t, err := r.typeRegistry.TypeName(entry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return errors.New("entry type not registered")
|
return errors.New("entry type not registered")
|
||||||
|
@ -40,10 +36,12 @@ func (r *DefaultEntryRepo) Create(entry model.Entry) error {
|
||||||
|
|
||||||
var metaDataJson []byte
|
var metaDataJson []byte
|
||||||
if entry.MetaData() != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -24,15 +24,14 @@ func TestRepoCreate(t *testing.T) {
|
||||||
|
|
||||||
entry := &test.MockEntry{}
|
entry := &test.MockEntry{}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
entry.Create("id", &now, &test.MockEntryMetaData{
|
err := repo.Create(entry, &now, &test.MockEntryMetaData{
|
||||||
Str: "str",
|
Str: "str",
|
||||||
Number: 1,
|
Number: 1,
|
||||||
Date: now,
|
Date: now,
|
||||||
})
|
})
|
||||||
err := repo.Create(entry)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
entry2, err := repo.FindById("id")
|
entry2, err := repo.FindById(entry.ID())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, entry.ID(), entry2.ID())
|
require.Equal(t, entry.ID(), entry2.ID())
|
||||||
require.Equal(t, entry.Content(), entry2.Content())
|
require.Equal(t, entry.Content(), entry2.Content())
|
||||||
|
@ -49,12 +48,11 @@ func TestRepoDelete(t *testing.T) {
|
||||||
|
|
||||||
entry := &test.MockEntry{}
|
entry := &test.MockEntry{}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
entry.Create("id", &now, &test.MockEntryMetaData{
|
err := repo.Create(entry, &now, &test.MockEntryMetaData{
|
||||||
Str: "str",
|
Str: "str",
|
||||||
Number: 1,
|
Number: 1,
|
||||||
Date: now,
|
Date: now,
|
||||||
})
|
})
|
||||||
err := repo.Create(entry)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
err = repo.Delete(entry)
|
err = repo.Delete(entry)
|
||||||
|
@ -69,22 +67,21 @@ func TestRepoFindAll(t *testing.T) {
|
||||||
|
|
||||||
entry := &test.MockEntry{}
|
entry := &test.MockEntry{}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
entry.Create("id", &now, &test.MockEntryMetaData{
|
err := repo.Create(entry, &now, &test.MockEntryMetaData{
|
||||||
Str: "str",
|
Str: "str",
|
||||||
Number: 1,
|
Number: 1,
|
||||||
Date: now,
|
Date: now,
|
||||||
})
|
})
|
||||||
err := repo.Create(entry)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
entry2 := &test.MockEntry{}
|
entry2 := &test.MockEntry{}
|
||||||
now2 := time.Now()
|
now2 := time.Now()
|
||||||
entry2.Create("id2", &now2, &test.MockEntryMetaData{
|
err = repo.Create(entry2, &now2, &test.MockEntryMetaData{
|
||||||
Str: "str2",
|
Str: "str",
|
||||||
Number: 2,
|
Number: 1,
|
||||||
Date: now2,
|
Date: now,
|
||||||
})
|
})
|
||||||
err = repo.Create(entry2)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
entries, err := repo.FindAll(nil)
|
entries, err := repo.FindAll(nil)
|
||||||
|
@ -106,25 +103,25 @@ func TestRepoUpdate(t *testing.T) {
|
||||||
|
|
||||||
entry := &test.MockEntry{}
|
entry := &test.MockEntry{}
|
||||||
now := time.Now()
|
now := time.Now()
|
||||||
entry.Create("id", &now, &test.MockEntryMetaData{
|
err := repo.Create(entry, &now, &test.MockEntryMetaData{
|
||||||
Str: "str",
|
Str: "str",
|
||||||
Number: 1,
|
Number: 1,
|
||||||
Date: now,
|
Date: now,
|
||||||
})
|
})
|
||||||
err := repo.Create(entry)
|
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
entry2 := &test.MockEntry{}
|
entry2 := &test.MockEntry{}
|
||||||
now2 := time.Now()
|
now2 := time.Now()
|
||||||
entry2.Create("id", &now2, &test.MockEntryMetaData{
|
err = repo.Create(entry2, &now2, &test.MockEntryMetaData{
|
||||||
Str: "str2",
|
Str: "str2",
|
||||||
Number: 2,
|
Number: 2,
|
||||||
Date: now2,
|
Date: now2,
|
||||||
})
|
})
|
||||||
|
require.NoError(t, err)
|
||||||
err = repo.Update(entry2)
|
err = repo.Update(entry2)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
entry3, err := repo.FindById("id")
|
entry3, err := repo.FindById(entry2.ID())
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, entry3.Content(), entry2.Content())
|
require.Equal(t, entry3.Content(), entry2.Content())
|
||||||
require.Equal(t, entry3.PublishedAt().Unix(), entry2.PublishedAt().Unix())
|
require.Equal(t, entry3.PublishedAt().Unix(), entry2.PublishedAt().Unix())
|
||||||
|
|
|
@ -1,6 +1,7 @@
|
||||||
package editor_test
|
package editor_test
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"mime/multipart"
|
||||||
"owl-blogs/domain/model"
|
"owl-blogs/domain/model"
|
||||||
"owl-blogs/web/editor"
|
"owl-blogs/web/editor"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
@ -15,10 +16,21 @@ type MockEntryMetaData struct {
|
||||||
Content string `owl:"inputType=text"`
|
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 {
|
type MockEntry struct {
|
||||||
id string
|
id string
|
||||||
publishedAt *time.Time
|
publishedAt *time.Time
|
||||||
metaData *MockEntryMetaData
|
metaData MockEntryMetaData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *MockEntry) ID() string {
|
func (e *MockEntry) ID() string {
|
||||||
|
@ -34,13 +46,13 @@ func (e *MockEntry) PublishedAt() *time.Time {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *MockEntry) MetaData() interface{} {
|
func (e *MockEntry) MetaData() interface{} {
|
||||||
return e.metaData
|
return &e.metaData
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *MockEntry) Create(id string, publishedAt *time.Time, metaData model.EntryMetaData) error {
|
func (e *MockEntry) Create(id string, publishedAt *time.Time, metaData model.EntryMetaData) error {
|
||||||
e.id = id
|
e.id = id
|
||||||
e.publishedAt = publishedAt
|
e.publishedAt = publishedAt
|
||||||
e.metaData = metaData.(*MockEntryMetaData)
|
e.metaData = *metaData.(*MockEntryMetaData)
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,13 +75,27 @@ func TestStructToFields(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestEditorEntryForm_HtmlForm(t *testing.T) {
|
func TestEditorEntryForm_HtmlForm(t *testing.T) {
|
||||||
formService := editor.NewEditorFormService(&MockEntry{})
|
form := editor.NewEntryForm(&MockEntry{})
|
||||||
form, err := formService.HtmlForm()
|
html, err := form.HtmlForm()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Contains(t, form, "<form")
|
require.Contains(t, html, "<form")
|
||||||
require.Contains(t, form, "method=\"POST\"")
|
require.Contains(t, html, "method=\"POST\"")
|
||||||
require.Contains(t, form, "<input type=\"file\" name=\"Image\" />")
|
require.Contains(t, html, "<input type=\"file\" name=\"Image\"")
|
||||||
require.Contains(t, form, "<input type=\"text\" name=\"Content\" />")
|
require.Contains(t, html, "<input type=\"text\" name=\"Content\"")
|
||||||
require.Contains(t, form, "<input type=\"submit\" value=\"Submit\" />")
|
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 (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"mime/multipart"
|
||||||
"owl-blogs/domain/model"
|
"owl-blogs/domain/model"
|
||||||
"reflect"
|
"reflect"
|
||||||
"strings"
|
"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 {
|
type EditorEntryForm struct {
|
||||||
entry model.Entry
|
entry model.Entry
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntryFormFieldParams struct {
|
type EntryFormFieldParams struct {
|
||||||
InputType string
|
InputType string
|
||||||
|
Widget string
|
||||||
}
|
}
|
||||||
|
|
||||||
type EntryFormField struct {
|
type EntryFormField struct {
|
||||||
|
@ -20,7 +34,7 @@ type EntryFormField struct {
|
||||||
Params EntryFormFieldParams
|
Params EntryFormFieldParams
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewEditorFormService(entry model.Entry) *EditorEntryForm {
|
func NewEntryForm(entry model.Entry) *EditorEntryForm {
|
||||||
return &EditorEntryForm{
|
return &EditorEntryForm{
|
||||||
entry: entry,
|
entry: entry,
|
||||||
}
|
}
|
||||||
|
@ -30,6 +44,8 @@ func (s *EntryFormFieldParams) ApplyTag(tagKey string, tagValue string) error {
|
||||||
switch tagKey {
|
switch tagKey {
|
||||||
case "inputType":
|
case "inputType":
|
||||||
s.InputType = tagValue
|
s.InputType = tagValue
|
||||||
|
case "widget":
|
||||||
|
s.Widget = tagValue
|
||||||
default:
|
default:
|
||||||
return fmt.Errorf("unknown tag key: %v", tagKey)
|
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 {
|
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) {
|
func FieldToFormField(field reflect.StructField) (EntryFormField, error) {
|
||||||
|
@ -89,3 +112,28 @@ func (s *EditorEntryForm) HtmlForm() (string, error) {
|
||||||
|
|
||||||
return html, nil
|
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/app"
|
||||||
"owl-blogs/domain/model"
|
"owl-blogs/domain/model"
|
||||||
"owl-blogs/web/editor"
|
"owl-blogs/web/editor"
|
||||||
|
"time"
|
||||||
|
|
||||||
"github.com/gofiber/fiber/v2"
|
"github.com/gofiber/fiber/v2"
|
||||||
)
|
)
|
||||||
|
@ -18,15 +19,30 @@ func NewEditorHandler(entryService *app.EntryService) *EditorHandler {
|
||||||
|
|
||||||
func (h *EditorHandler) HandleGet(c *fiber.Ctx) error {
|
func (h *EditorHandler) HandleGet(c *fiber.Ctx) error {
|
||||||
c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
|
c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
|
||||||
formService := editor.NewEditorFormService(&model.ImageEntry{})
|
form := editor.NewEntryForm(&model.ImageEntry{})
|
||||||
form, err := formService.HtmlForm()
|
htmlForm, err := form.HtmlForm()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return c.SendString(form)
|
return c.SendString(htmlForm)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *EditorHandler) HandlePost(c *fiber.Ctx) error {
|
func (h *EditorHandler) HandlePost(c *fiber.Ctx) error {
|
||||||
c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
|
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!")
|
return c.SendString("Hello, Editor!")
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue