diff --git a/app/config_register.go b/app/config_register.go
index cc34104..22d5f8c 100644
--- a/app/config_register.go
+++ b/app/config_register.go
@@ -4,6 +4,11 @@ type ConfigRegister struct {
configs map[string]interface{}
}
+type RegisteredConfig struct {
+ Name string
+ Config interface{}
+}
+
func NewConfigRegister() *ConfigRegister {
return &ConfigRegister{configs: map[string]interface{}{}}
}
@@ -11,3 +16,18 @@ func NewConfigRegister() *ConfigRegister {
func (r *ConfigRegister) Register(name string, config interface{}) {
r.configs[name] = config
}
+
+func (r *ConfigRegister) Configs() []RegisteredConfig {
+ var configs []RegisteredConfig
+ for name, config := range r.configs {
+ configs = append(configs, RegisteredConfig{
+ Name: name,
+ Config: config,
+ })
+ }
+ return configs
+}
+
+func (r *ConfigRegister) GetConfig(name string) interface{} {
+ return r.configs[name]
+}
diff --git a/cmd/owl/main.go b/cmd/owl/main.go
index 3f244c3..ae74c6e 100644
--- a/cmd/owl/main.go
+++ b/cmd/owl/main.go
@@ -44,7 +44,12 @@ func App(db infra.Database) *web.WebApp {
binaryService := app.NewBinaryFileService(binRepo)
authorService := app.NewAuthorService(authorRepo, siteConfigRepo)
- return web.NewWebApp(entryService, registry, binaryService, authorService, siteConfigRepo)
+ configRegister := app.NewConfigRegister()
+
+ return web.NewWebApp(
+ entryService, registry, binaryService,
+ authorService, siteConfigRepo, configRegister,
+ )
}
diff --git a/render/templates/views/admin.tmpl b/render/templates/views/admin.tmpl
new file mode 100644
index 0000000..439ea17
--- /dev/null
+++ b/render/templates/views/admin.tmpl
@@ -0,0 +1,9 @@
+{{define "title"}}Admin{{end}}
+
+{{define "main"}}
+
+{{end}}
\ No newline at end of file
diff --git a/render/templates/views/admin_config.tmpl b/render/templates/views/admin_config.tmpl
new file mode 100644
index 0000000..4a9f3fa
--- /dev/null
+++ b/render/templates/views/admin_config.tmpl
@@ -0,0 +1,11 @@
+{{define "title"}}Editor{{end}}
+
+{{define "main"}}
+
+Back
+
+
+
+{{.}}
+
+{{end}}
\ No newline at end of file
diff --git a/web/admin_handler.go b/web/admin_handler.go
new file mode 100644
index 0000000..eb17420
--- /dev/null
+++ b/web/admin_handler.go
@@ -0,0 +1,75 @@
+package web
+
+import (
+ "owl-blogs/app"
+ "owl-blogs/app/repository"
+ "owl-blogs/render"
+ "owl-blogs/web/forms"
+
+ "github.com/gofiber/fiber/v2"
+)
+
+type adminHandler struct {
+ configRepo repository.ConfigRepository
+ configRegister *app.ConfigRegister
+}
+
+func NewAdminHandler(configRepo repository.ConfigRepository, configRegister *app.ConfigRegister) *adminHandler {
+ return &adminHandler{
+ configRepo: configRepo,
+ configRegister: configRegister,
+ }
+}
+
+func (h *adminHandler) Handle(c *fiber.Ctx) error {
+ c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
+
+ siteConfig := getSiteConfig(h.configRepo)
+ configs := h.configRegister.Configs()
+ return render.RenderTemplateWithBase(c, siteConfig, "views/admin", configs)
+}
+
+func (h *adminHandler) HandleConfigGet(c *fiber.Ctx) error {
+ c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
+
+ configName := c.Params("config")
+ config := h.configRegister.GetConfig(configName)
+ if config == nil {
+ return c.SendStatus(404)
+ }
+ err := h.configRepo.Get(configName, config)
+ if err != nil {
+ return err
+ }
+ siteConfig := getSiteConfig(h.configRepo)
+
+ form := forms.NewForm(config, nil)
+ htmlForm, err := form.HtmlForm()
+ if err != nil {
+ return err
+ }
+
+ return render.RenderTemplateWithBase(c, siteConfig, "views/admin_config", htmlForm)
+}
+
+func (h *adminHandler) HandleConfigPost(c *fiber.Ctx) error {
+ c.Set(fiber.HeaderContentType, fiber.MIMETextHTML)
+
+ configName := c.Params("config")
+ config := h.configRegister.GetConfig(configName)
+ if config == nil {
+ return c.SendStatus(404)
+ }
+
+ form := forms.NewForm(config, nil)
+
+ newConfig, err := form.Parse(c)
+ if err != nil {
+ return err
+ }
+
+ h.configRepo.Update(configName, newConfig)
+
+ return c.Redirect("")
+
+}
diff --git a/web/app.go b/web/app.go
index 0d5cfbf..b06d845 100644
--- a/web/app.go
+++ b/web/app.go
@@ -29,6 +29,7 @@ func NewWebApp(
binService *app.BinaryService,
authorService *app.AuthorService,
configRepo repository.ConfigRepository,
+ configRegister *app.ConfigRegister,
) *WebApp {
app := fiber.New()
@@ -45,6 +46,14 @@ func NewWebApp(
app.Get("/auth/login", loginHandler.HandleGet)
app.Post("/auth/login", loginHandler.HandlePost)
+ // admin
+ adminHandler := NewAdminHandler(configRepo, configRegister)
+ admin := app.Group("/admin")
+ admin.Use(middleware.NewAuthMiddleware(authorService).Handle)
+ admin.Get("/", adminHandler.Handle)
+ admin.Get("/config/:config/", adminHandler.HandleConfigGet)
+ admin.Post("/config/:config/", adminHandler.HandleConfigPost)
+
// Editor
editor := app.Group("/editor")
editor.Use(middleware.NewAuthMiddleware(authorService).Handle)
@@ -92,6 +101,7 @@ func NewWebApp(
// ActivityPub
// activityPubServer := NewActivityPubServer(configRepo)
+ configRegister.Register(ACT_PUB_CONF_NAME, &ActivityPubConfig{})
// app.Get("/.well-known/webfinger", activityPubServer.HandleWebfinger)
// app.Route("/activitypub", activityPubServer.Router)
diff --git a/web/forms/form.go b/web/forms/form.go
new file mode 100644
index 0000000..30de998
--- /dev/null
+++ b/web/forms/form.go
@@ -0,0 +1,171 @@
+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 string
+ 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) Html() string {
+ html := ""
+ html += fmt.Sprintf("\n", s.Name, s.Name)
+ if s.Params.InputType == "text" && s.Params.Widget == "textarea" {
+ html += fmt.Sprintf("\n", s.Name, s.Name, s.Value)
+ } else {
+ html += fmt.Sprintf("\n", s.Params.InputType, s.Name, s.Name, s.Value)
+ }
+ return html
+}
+
+func FieldToFormField(field reflect.StructField, value string) (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}).String())
+ 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 := "\n"
+
+ 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 {
+ 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() {
+ metaField.SetString(formValue)
+ }
+ }
+
+ }
+
+ return s.data, nil
+}