This commit is contained in:
Niko Abeler 2023-07-06 19:36:24 +02:00
parent bcacbf1e4d
commit ff193f62e9
7 changed files with 132 additions and 43 deletions

View File

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

View File

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

View File

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

View File

@ -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())

View File

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

View File

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

View File

@ -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!")
}