Generate ent schema in admin code. (#127)
This commit is contained in:
parent
75c9c5be34
commit
37104788e7
13 changed files with 303 additions and 142 deletions
|
|
@ -216,7 +216,6 @@ The container is located at `pkg/services/container.go` and is meant to house al
|
|||
- Configuration
|
||||
- Database
|
||||
- Files
|
||||
- Graph
|
||||
- Mail
|
||||
- ORM
|
||||
- Tasks
|
||||
|
|
@ -433,7 +432,7 @@ Users with admin [access](#access) will see additional links on the default side
|
|||
|
||||
In order to automatically and dynamically provide admin functionality for entities, code generation is used by means of leveraging Ent's [extension API](https://entgo.io/docs/extensions) which makes generating code using the Ent graph schema very easy. A [custom extension](https://github.com/mikestefanello/pagoda/blob/master/ent/admin/extension.go) is provided to generate code that provides flat entity type structs and handler code that work directly with Echo. So, both of those are required in order for any of this to work. Whenever you modify one of your entity types or generate a new one, the admin code will also automatically generate.
|
||||
|
||||
Without going in to too much detail here, the generated code provides a [handler](https://github.com/mikestefanello/pagoda/blob/master/ent/admin/handler.go) that is then used by a provided [web handler](https://github.com/mikestefanello/pagoda/blob/master/pkg/handlers/admin.go) to power all the routes used in the admin UI. While the rest of the related code should be simple enough to follow, it's worth calling attention to the highly-dynamic [entity form](https://github.com/mikestefanello/pagoda/blob/master/pkg/ui/forms/admin_entity.go) that is constructed using the _Ent_ graph data structure.
|
||||
Without going in to too much detail here, the generated code provides a [handler](https://github.com/mikestefanello/pagoda/blob/master/ent/admin/handler.go) that is then used by a provided [web handler](https://github.com/mikestefanello/pagoda/blob/master/pkg/handlers/admin.go) to power all the routes used in the admin UI. While the rest of the related code should be simple enough to follow, it's worth calling attention to the highly dynamic [entity form](https://github.com/mikestefanello/pagoda/blob/master/pkg/ui/forms/admin_entity.go) that is constructed using the _Ent_ graph data structure.
|
||||
|
||||
### Access
|
||||
|
||||
|
|
@ -451,7 +450,7 @@ Since the generated code is completely dynamic, all entity functionality related
|
|||
|
||||
* Determine which tests should be included and provide them.
|
||||
* Inline validation.
|
||||
* Either exposed sorting, or allow the _handler_ to be configured with sort criteria for each type.
|
||||
* Either exposed sorting or allow the _handler_ to be configured with sort criteria for each type.
|
||||
* Exposed filters.
|
||||
* Support all field types (types such as _JSON_ as currently not supported).
|
||||
* Control which fields appear in the entity list table.
|
||||
|
|
|
|||
|
|
@ -30,55 +30,55 @@ func NewHandler(client *ent.Client, cfg HandlerConfig) *Handler {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Create(ctx echo.Context, entityType string) error {
|
||||
switch entityType {
|
||||
case "PasswordToken":
|
||||
func (h *Handler) Create(ctx echo.Context, entityType EntityType) error {
|
||||
switch entityType.(type) {
|
||||
case *PasswordToken:
|
||||
return h.PasswordTokenCreate(ctx)
|
||||
case "User":
|
||||
case *User:
|
||||
return h.UserCreate(ctx)
|
||||
default:
|
||||
return fmt.Errorf("unsupported entity type: %s", entityType)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Get(ctx echo.Context, entityType string, id int) (url.Values, error) {
|
||||
switch entityType {
|
||||
case "PasswordToken":
|
||||
func (h *Handler) Get(ctx echo.Context, entityType EntityType, id int) (url.Values, error) {
|
||||
switch entityType.(type) {
|
||||
case *PasswordToken:
|
||||
return h.PasswordTokenGet(ctx, id)
|
||||
case "User":
|
||||
case *User:
|
||||
return h.UserGet(ctx, id)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported entity type: %s", entityType)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Delete(ctx echo.Context, entityType string, id int) error {
|
||||
switch entityType {
|
||||
case "PasswordToken":
|
||||
func (h *Handler) Delete(ctx echo.Context, entityType EntityType, id int) error {
|
||||
switch entityType.(type) {
|
||||
case *PasswordToken:
|
||||
return h.PasswordTokenDelete(ctx, id)
|
||||
case "User":
|
||||
case *User:
|
||||
return h.UserDelete(ctx, id)
|
||||
default:
|
||||
return fmt.Errorf("unsupported entity type: %s", entityType)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Update(ctx echo.Context, entityType string, id int) error {
|
||||
switch entityType {
|
||||
case "PasswordToken":
|
||||
func (h *Handler) Update(ctx echo.Context, entityType EntityType, id int) error {
|
||||
switch entityType.(type) {
|
||||
case *PasswordToken:
|
||||
return h.PasswordTokenUpdate(ctx, id)
|
||||
case "User":
|
||||
case *User:
|
||||
return h.UserUpdate(ctx, id)
|
||||
default:
|
||||
return fmt.Errorf("unsupported entity type: %s", entityType)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) List(ctx echo.Context, entityType string) (*EntityList, error) {
|
||||
switch entityType {
|
||||
case "PasswordToken":
|
||||
func (h *Handler) List(ctx echo.Context, entityType EntityType) (*EntityList, error) {
|
||||
switch entityType.(type) {
|
||||
case *PasswordToken:
|
||||
return h.PasswordTokenList(ctx)
|
||||
case "User":
|
||||
case *User:
|
||||
return h.UserList(ctx)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported entity type: %s", entityType)
|
||||
|
|
|
|||
101
ent/admin/schema.go
Normal file
101
ent/admin/schema.go
Normal file
|
|
@ -0,0 +1,101 @@
|
|||
// Code generated by ent, DO NOT EDIT.
|
||||
package admin
|
||||
|
||||
import (
|
||||
"entgo.io/ent/schema/field"
|
||||
)
|
||||
|
||||
type Enum struct {
|
||||
Label, Value string
|
||||
}
|
||||
|
||||
type FieldSchema struct {
|
||||
Name string
|
||||
Type field.Type
|
||||
Optional bool
|
||||
Immutable bool
|
||||
Sensitive bool
|
||||
Enums []Enum
|
||||
}
|
||||
|
||||
const NamePasswordToken = "PasswordToken"
|
||||
|
||||
var fieldsPasswordToken = []*FieldSchema{
|
||||
{
|
||||
Name: "token",
|
||||
Type: field.TypeString,
|
||||
Optional: false,
|
||||
Immutable: false,
|
||||
Sensitive: true,
|
||||
Enums: nil,
|
||||
},
|
||||
{
|
||||
Name: "user_id",
|
||||
Type: field.TypeInt,
|
||||
Optional: false,
|
||||
Immutable: false,
|
||||
Sensitive: false,
|
||||
Enums: nil,
|
||||
},
|
||||
{
|
||||
Name: "created_at",
|
||||
Type: field.TypeTime,
|
||||
Optional: false,
|
||||
Immutable: false,
|
||||
Sensitive: false,
|
||||
Enums: nil,
|
||||
},
|
||||
}
|
||||
|
||||
const NameUser = "User"
|
||||
|
||||
var fieldsUser = []*FieldSchema{
|
||||
{
|
||||
Name: "name",
|
||||
Type: field.TypeString,
|
||||
Optional: false,
|
||||
Immutable: false,
|
||||
Sensitive: false,
|
||||
Enums: nil,
|
||||
},
|
||||
{
|
||||
Name: "email",
|
||||
Type: field.TypeString,
|
||||
Optional: false,
|
||||
Immutable: false,
|
||||
Sensitive: false,
|
||||
Enums: nil,
|
||||
},
|
||||
{
|
||||
Name: "password",
|
||||
Type: field.TypeString,
|
||||
Optional: false,
|
||||
Immutable: false,
|
||||
Sensitive: true,
|
||||
Enums: nil,
|
||||
},
|
||||
{
|
||||
Name: "verified",
|
||||
Type: field.TypeBool,
|
||||
Optional: false,
|
||||
Immutable: false,
|
||||
Sensitive: false,
|
||||
Enums: nil,
|
||||
},
|
||||
{
|
||||
Name: "admin",
|
||||
Type: field.TypeBool,
|
||||
Optional: false,
|
||||
Immutable: false,
|
||||
Sensitive: false,
|
||||
Enums: nil,
|
||||
},
|
||||
{
|
||||
Name: "created_at",
|
||||
Type: field.TypeTime,
|
||||
Optional: false,
|
||||
Immutable: true,
|
||||
Sensitive: false,
|
||||
Enums: nil,
|
||||
},
|
||||
}
|
||||
|
|
@ -35,10 +35,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Create(ctx echo.Context, entityType string) error {
|
||||
switch entityType {
|
||||
func (h *Handler) Create(ctx echo.Context, entityType EntityType) error {
|
||||
switch entityType.(type) {
|
||||
{{- range $n := $.Nodes }}
|
||||
case "{{ $n.Name }}":
|
||||
case *{{ $n.Name }}:
|
||||
return h.{{ $n.Name }}Create(ctx)
|
||||
{{- end }}
|
||||
default:
|
||||
|
|
@ -46,10 +46,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Get(ctx echo.Context, entityType string, id int) (url.Values, error) {
|
||||
switch entityType {
|
||||
func (h *Handler) Get(ctx echo.Context, entityType EntityType, id int) (url.Values, error) {
|
||||
switch entityType.(type) {
|
||||
{{- range $n := $.Nodes }}
|
||||
case "{{ $n.Name }}":
|
||||
case *{{ $n.Name }}:
|
||||
return h.{{ $n.Name }}Get(ctx, id)
|
||||
{{- end }}
|
||||
default:
|
||||
|
|
@ -57,10 +57,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Delete(ctx echo.Context, entityType string, id int) error {
|
||||
switch entityType {
|
||||
func (h *Handler) Delete(ctx echo.Context, entityType EntityType, id int) error {
|
||||
switch entityType.(type) {
|
||||
{{- range $n := $.Nodes }}
|
||||
case "{{ $n.Name }}":
|
||||
case *{{ $n.Name }}:
|
||||
return h.{{ $n.Name }}Delete(ctx, id)
|
||||
{{- end }}
|
||||
default:
|
||||
|
|
@ -68,10 +68,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Update(ctx echo.Context, entityType string, id int) error {
|
||||
switch entityType {
|
||||
func (h *Handler) Update(ctx echo.Context, entityType EntityType, id int) error {
|
||||
switch entityType.(type) {
|
||||
{{- range $n := $.Nodes }}
|
||||
case "{{ $n.Name }}":
|
||||
case *{{ $n.Name }}:
|
||||
return h.{{ $n.Name }}Update(ctx, id)
|
||||
{{- end }}
|
||||
default:
|
||||
|
|
@ -79,10 +79,10 @@
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Handler) List(ctx echo.Context, entityType string) (*EntityList, error) {
|
||||
switch entityType {
|
||||
func (h *Handler) List(ctx echo.Context, entityType EntityType) (*EntityList, error) {
|
||||
switch entityType.(type) {
|
||||
{{- range $n := $.Nodes }}
|
||||
case "{{ $n.Name }}":
|
||||
case *{{ $n.Name }}:
|
||||
return h.{{ $n.Name }}List(ctx)
|
||||
{{- end }}
|
||||
default:
|
||||
|
|
|
|||
54
ent/admin/templates/schema.tmpl
Normal file
54
ent/admin/templates/schema.tmpl
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
{{/* Tell Intellij/GoLand to enable the autocompletion based on the *gen.Graph type. */}}
|
||||
{{/* gotype: entgo.io/ent/entc/gen.Graph */}}
|
||||
|
||||
{{ define "admin/schema" }}
|
||||
// Code generated by ent, DO NOT EDIT.
|
||||
package admin
|
||||
|
||||
import (
|
||||
"entgo.io/ent/schema/field"
|
||||
)
|
||||
|
||||
type Enum struct {
|
||||
Label, Value string
|
||||
}
|
||||
|
||||
type FieldSchema struct {
|
||||
Name string
|
||||
Type field.Type
|
||||
Optional bool
|
||||
Immutable bool
|
||||
Sensitive bool
|
||||
Enums []Enum
|
||||
}
|
||||
|
||||
|
||||
{{- range $n := $.Nodes }}
|
||||
const Name{{ $n.Name }} = "{{ $n.Name }}"
|
||||
|
||||
var fields{{ $n.Name }} = []*FieldSchema{
|
||||
{{- range $f := $n.Fields }}
|
||||
{
|
||||
Name: "{{ $f.Name }}",
|
||||
Type: field.{{ $f.Type.Type.ConstName }},
|
||||
Optional: {{ $f.Optional }},
|
||||
Immutable: {{ $f.Immutable }},
|
||||
Sensitive: {{ $f.Sensitive }},
|
||||
{{- if len $f.Enums }}
|
||||
Enums: []Enum{
|
||||
{{- range $e := $f.Enums }}
|
||||
{
|
||||
Label: "{{ $e.Label }}",
|
||||
Value: "{{ $e.Value }}",
|
||||
},
|
||||
{{- end }}
|
||||
},
|
||||
{{- else }}
|
||||
Enums: nil,
|
||||
{{- end }}
|
||||
},
|
||||
{{- end }}
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
{{ end }}
|
||||
|
|
@ -11,8 +11,27 @@
|
|||
{{ fieldName $f.Name }} {{ if (fieldIsPointer $f) }}*{{ end }}{{ $f.Type }} `form:"{{ $f.Name }}"`
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
func (e *{{ $n.Name }}) GetName() string {
|
||||
return Name{{ $n.Name }}
|
||||
}
|
||||
|
||||
func (e *{{ $n.Name }}) GetSchema() []*FieldSchema {
|
||||
return fields{{ $n.Name }}
|
||||
}
|
||||
{{ end }}
|
||||
|
||||
type EntityType interface {
|
||||
GetName() string
|
||||
GetSchema() []*FieldSchema
|
||||
}
|
||||
|
||||
var entityTypes = []EntityType{
|
||||
{{- range $n := $.Nodes }}
|
||||
&{{ $n.Name }}{},
|
||||
{{- end }}
|
||||
}
|
||||
|
||||
type EntityList struct {
|
||||
Columns []string
|
||||
Entities []EntityValues
|
||||
|
|
@ -31,12 +50,7 @@
|
|||
TimeFormat string
|
||||
}
|
||||
|
||||
func GetEntityTypeNames() []string {
|
||||
return []string{
|
||||
{{- range $n := $.Nodes }}
|
||||
"{{ $n.Name }}",
|
||||
{{- end }}
|
||||
}
|
||||
func GetEntityTypes() []EntityType {
|
||||
return entityTypes
|
||||
}
|
||||
|
||||
{{ end }}
|
||||
|
|
@ -9,6 +9,14 @@ type PasswordToken struct {
|
|||
CreatedAt *time.Time `form:"created_at"`
|
||||
}
|
||||
|
||||
func (e *PasswordToken) GetName() string {
|
||||
return NamePasswordToken
|
||||
}
|
||||
|
||||
func (e *PasswordToken) GetSchema() []*FieldSchema {
|
||||
return fieldsPasswordToken
|
||||
}
|
||||
|
||||
type User struct {
|
||||
Name string `form:"name"`
|
||||
Email string `form:"email"`
|
||||
|
|
@ -18,6 +26,24 @@ type User struct {
|
|||
CreatedAt *time.Time `form:"created_at"`
|
||||
}
|
||||
|
||||
func (e *User) GetName() string {
|
||||
return NameUser
|
||||
}
|
||||
|
||||
func (e *User) GetSchema() []*FieldSchema {
|
||||
return fieldsUser
|
||||
}
|
||||
|
||||
type EntityType interface {
|
||||
GetName() string
|
||||
GetSchema() []*FieldSchema
|
||||
}
|
||||
|
||||
var entityTypes = []EntityType{
|
||||
&PasswordToken{},
|
||||
&User{},
|
||||
}
|
||||
|
||||
type EntityList struct {
|
||||
Columns []string
|
||||
Entities []EntityValues
|
||||
|
|
@ -36,9 +62,6 @@ type HandlerConfig struct {
|
|||
TimeFormat string
|
||||
}
|
||||
|
||||
func GetEntityTypeNames() []string {
|
||||
return []string{
|
||||
"PasswordToken",
|
||||
"User",
|
||||
}
|
||||
func GetEntityTypes() []EntityType {
|
||||
return entityTypes
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,8 +7,6 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/entc/gen"
|
||||
"entgo.io/ent/entc/load"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/mikestefanello/backlite/ui"
|
||||
"github.com/mikestefanello/pagoda/ent"
|
||||
|
|
@ -25,7 +23,6 @@ import (
|
|||
|
||||
type Admin struct {
|
||||
orm *ent.Client
|
||||
graph *gen.Graph
|
||||
admin *admin.Handler
|
||||
backlite *ui.Handler
|
||||
}
|
||||
|
|
@ -36,7 +33,6 @@ func init() {
|
|||
|
||||
func (h *Admin) Init(c *services.Container) error {
|
||||
var err error
|
||||
h.graph = c.Graph
|
||||
h.orm = c.ORM
|
||||
h.admin = admin.NewHandler(h.orm, admin.HandlerConfig{
|
||||
ItemsPerPage: 25,
|
||||
|
|
@ -56,22 +52,22 @@ func (h *Admin) Routes(g *echo.Group) {
|
|||
ag := g.Group("/admin", middleware.RequireAdmin)
|
||||
|
||||
entities := ag.Group("/entity")
|
||||
for _, n := range h.graph.Nodes {
|
||||
ng := entities.Group(fmt.Sprintf("/%s", strings.ToLower(n.Name)))
|
||||
for _, n := range admin.GetEntityTypes() {
|
||||
ng := entities.Group(fmt.Sprintf("/%s", strings.ToLower(n.GetName())))
|
||||
ng.GET("", h.EntityList(n)).
|
||||
Name = routenames.AdminEntityList(n.Name)
|
||||
Name = routenames.AdminEntityList(n.GetName())
|
||||
ng.GET("/add", h.EntityAdd(n)).
|
||||
Name = routenames.AdminEntityAdd(n.Name)
|
||||
Name = routenames.AdminEntityAdd(n.GetName())
|
||||
ng.POST("/add", h.EntityAddSubmit(n)).
|
||||
Name = routenames.AdminEntityAddSubmit(n.Name)
|
||||
Name = routenames.AdminEntityAddSubmit(n.GetName())
|
||||
ng.GET("/:id/edit", h.EntityEdit(n), h.middlewareEntityLoad(n)).
|
||||
Name = routenames.AdminEntityEdit(n.Name)
|
||||
Name = routenames.AdminEntityEdit(n.GetName())
|
||||
ng.POST("/:id/edit", h.EntityEditSubmit(n), h.middlewareEntityLoad(n)).
|
||||
Name = routenames.AdminEntityEditSubmit(n.Name)
|
||||
Name = routenames.AdminEntityEditSubmit(n.GetName())
|
||||
ng.GET("/:id/delete", h.EntityDelete(n), h.middlewareEntityLoad(n)).
|
||||
Name = routenames.AdminEntityDelete(n.Name)
|
||||
Name = routenames.AdminEntityDelete(n.GetName())
|
||||
ng.POST("/:id/delete", h.EntityDeleteSubmit(n), h.middlewareEntityLoad(n)).
|
||||
Name = routenames.AdminEntityDeleteSubmit(n.Name)
|
||||
Name = routenames.AdminEntityDeleteSubmit(n.GetName())
|
||||
}
|
||||
|
||||
tasks := ag.Group("/tasks")
|
||||
|
|
@ -84,7 +80,7 @@ func (h *Admin) Routes(g *echo.Group) {
|
|||
}
|
||||
|
||||
// middlewareEntityLoad is middleware to extract the entity ID and attempt to load the given entity.
|
||||
func (h *Admin) middlewareEntityLoad(n *gen.Type) echo.MiddlewareFunc {
|
||||
func (h *Admin) middlewareEntityLoad(n admin.EntityType) echo.MiddlewareFunc {
|
||||
return func(next echo.HandlerFunc) echo.HandlerFunc {
|
||||
return func(ctx echo.Context) error {
|
||||
id, err := strconv.Atoi(ctx.Param("id"))
|
||||
|
|
@ -92,7 +88,7 @@ func (h *Admin) middlewareEntityLoad(n *gen.Type) echo.MiddlewareFunc {
|
|||
return echo.NewHTTPError(http.StatusBadRequest, "invalid entity ID")
|
||||
}
|
||||
|
||||
entity, err := h.admin.Get(ctx, n.Name, id)
|
||||
entity, err := h.admin.Get(ctx, n, id)
|
||||
switch {
|
||||
case err == nil:
|
||||
ctx.Set(context.AdminEntityIDKey, id)
|
||||
|
|
@ -107,100 +103,91 @@ func (h *Admin) middlewareEntityLoad(n *gen.Type) echo.MiddlewareFunc {
|
|||
}
|
||||
}
|
||||
|
||||
func (h *Admin) EntityList(n *gen.Type) echo.HandlerFunc {
|
||||
func (h *Admin) EntityList(n admin.EntityType) echo.HandlerFunc {
|
||||
return func(ctx echo.Context) error {
|
||||
list, err := h.admin.List(ctx, n.Name)
|
||||
list, err := h.admin.List(ctx, n)
|
||||
if err != nil {
|
||||
return echo.NewHTTPError(http.StatusInternalServerError, err)
|
||||
}
|
||||
|
||||
return pages.AdminEntityList(ctx, n.Name, list)
|
||||
return pages.AdminEntityList(ctx, n, list)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Admin) EntityAdd(n *gen.Type) echo.HandlerFunc {
|
||||
func (h *Admin) EntityAdd(n admin.EntityType) echo.HandlerFunc {
|
||||
return func(ctx echo.Context) error {
|
||||
return pages.AdminEntityInput(ctx, h.getEntitySchema(n), nil)
|
||||
return pages.AdminEntityInput(ctx, n, nil)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Admin) EntityAddSubmit(n *gen.Type) echo.HandlerFunc {
|
||||
func (h *Admin) EntityAddSubmit(n admin.EntityType) echo.HandlerFunc {
|
||||
return func(ctx echo.Context) error {
|
||||
err := h.admin.Create(ctx, n.Name)
|
||||
err := h.admin.Create(ctx, n)
|
||||
if err != nil {
|
||||
msg.Error(ctx, err.Error())
|
||||
return h.EntityAdd(n)(ctx)
|
||||
}
|
||||
|
||||
msg.Success(ctx, fmt.Sprintf("Successfully added %s.", n.Name))
|
||||
msg.Success(ctx, fmt.Sprintf("Successfully added %s.", n.GetName()))
|
||||
|
||||
return redirect.
|
||||
New(ctx).
|
||||
Route(routenames.AdminEntityList(n.Name)).
|
||||
Route(routenames.AdminEntityList(n.GetName())).
|
||||
StatusCode(http.StatusFound).
|
||||
Go()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Admin) EntityEdit(n *gen.Type) echo.HandlerFunc {
|
||||
func (h *Admin) EntityEdit(n admin.EntityType) echo.HandlerFunc {
|
||||
return func(ctx echo.Context) error {
|
||||
v := ctx.Get(context.AdminEntityKey).(map[string][]string)
|
||||
return pages.AdminEntityInput(ctx, h.getEntitySchema(n), v)
|
||||
return pages.AdminEntityInput(ctx, n, v)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Admin) EntityEditSubmit(n *gen.Type) echo.HandlerFunc {
|
||||
func (h *Admin) EntityEditSubmit(n admin.EntityType) echo.HandlerFunc {
|
||||
return func(ctx echo.Context) error {
|
||||
id := ctx.Get(context.AdminEntityIDKey).(int)
|
||||
err := h.admin.Update(ctx, n.Name, id)
|
||||
err := h.admin.Update(ctx, n, id)
|
||||
if err != nil {
|
||||
msg.Error(ctx, err.Error())
|
||||
return h.EntityEdit(n)(ctx)
|
||||
}
|
||||
|
||||
msg.Success(ctx, fmt.Sprintf("Updated %s.", n.Name))
|
||||
msg.Success(ctx, fmt.Sprintf("Updated %s.", n.GetName()))
|
||||
|
||||
return redirect.
|
||||
New(ctx).
|
||||
Route(routenames.AdminEntityList(n.Name)).
|
||||
Route(routenames.AdminEntityList(n.GetName())).
|
||||
StatusCode(http.StatusFound).
|
||||
Go()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Admin) EntityDelete(n *gen.Type) echo.HandlerFunc {
|
||||
func (h *Admin) EntityDelete(n admin.EntityType) echo.HandlerFunc {
|
||||
return func(ctx echo.Context) error {
|
||||
return pages.AdminEntityDelete(ctx, n.Name)
|
||||
return pages.AdminEntityDelete(ctx, n)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Admin) EntityDeleteSubmit(n *gen.Type) echo.HandlerFunc {
|
||||
func (h *Admin) EntityDeleteSubmit(n admin.EntityType) echo.HandlerFunc {
|
||||
return func(ctx echo.Context) error {
|
||||
id := ctx.Get(context.AdminEntityIDKey).(int)
|
||||
if err := h.admin.Delete(ctx, n.Name, id); err != nil {
|
||||
if err := h.admin.Delete(ctx, n, id); err != nil {
|
||||
msg.Error(ctx, err.Error())
|
||||
return h.EntityDelete(n)(ctx)
|
||||
}
|
||||
|
||||
msg.Success(ctx, fmt.Sprintf("Successfully deleted %s (ID %d).", n.Name, id))
|
||||
msg.Success(ctx, fmt.Sprintf("Successfully deleted %s (ID %d).", n.GetName(), id))
|
||||
|
||||
return redirect.
|
||||
New(ctx).
|
||||
Route(routenames.AdminEntityList(n.Name)).
|
||||
Route(routenames.AdminEntityList(n.GetName())).
|
||||
StatusCode(http.StatusFound).
|
||||
Go()
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Admin) getEntitySchema(n *gen.Type) *load.Schema {
|
||||
for _, s := range h.graph.Schemas {
|
||||
if s.Name == n.Name {
|
||||
return s
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (h *Admin) Backlite(handler func(http.ResponseWriter, *http.Request) error) echo.HandlerFunc {
|
||||
return func(c echo.Context) error {
|
||||
if id := c.Param("id"); id != "" {
|
||||
|
|
|
|||
|
|
@ -7,14 +7,9 @@ import (
|
|||
"log/slog"
|
||||
"math/rand"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
|
||||
entsql "entgo.io/ent/dialect/sql"
|
||||
"entgo.io/ent/entc"
|
||||
"entgo.io/ent/entc/gen"
|
||||
"github.com/labstack/echo/v4"
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
"github.com/mikestefanello/backlite"
|
||||
|
|
@ -51,9 +46,6 @@ type Container struct {
|
|||
// ORM stores a client to the ORM.
|
||||
ORM *ent.Client
|
||||
|
||||
// Graph is the entity graph defined by your Ent schema.
|
||||
Graph *gen.Graph
|
||||
|
||||
// Mail stores an email sending client.
|
||||
Mail *MailClient
|
||||
|
||||
|
|
@ -192,16 +184,6 @@ func (c *Container) initORM() {
|
|||
if err := c.ORM.Schema.Create(context.Background()); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// Load the graph.
|
||||
_, b, _, _ := runtime.Caller(0)
|
||||
d := path.Join(path.Dir(b))
|
||||
p := filepath.Join(filepath.Dir(d), "../ent/schema")
|
||||
g, err := entc.LoadGraph(p, &gen.Config{})
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
c.Graph = g
|
||||
}
|
||||
|
||||
// initAuth initializes the authentication client.
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"net/http"
|
||||
"net/url"
|
||||
|
||||
"entgo.io/ent/entc/load"
|
||||
"entgo.io/ent/schema/field"
|
||||
"github.com/mikestefanello/pagoda/ent/admin"
|
||||
"github.com/mikestefanello/pagoda/pkg/routenames"
|
||||
|
|
@ -14,7 +13,7 @@ import (
|
|||
. "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
func AdminEntity(r *ui.Request, schema *load.Schema, values url.Values) Node {
|
||||
func AdminEntity(r *ui.Request, entityType admin.EntityType, values url.Values) Node {
|
||||
// TODO inline validation?
|
||||
isNew := values == nil
|
||||
nodes := make(Group, 0)
|
||||
|
|
@ -34,13 +33,13 @@ func AdminEntity(r *ui.Request, schema *load.Schema, values url.Values) Node {
|
|||
}
|
||||
|
||||
// Attempt to add form elements for all editable entity fields.
|
||||
for _, f := range schema.Fields {
|
||||
for _, f := range entityType.GetSchema() {
|
||||
// TODO cardinality?
|
||||
if !isNew && f.Immutable {
|
||||
continue
|
||||
}
|
||||
|
||||
switch f.Info.Type {
|
||||
switch f.Type {
|
||||
case field.TypeString:
|
||||
p := InputFieldParams{
|
||||
Name: f.Name,
|
||||
|
|
@ -93,8 +92,8 @@ func AdminEntity(r *ui.Request, schema *load.Schema, values url.Values) Node {
|
|||
}
|
||||
for _, enum := range f.Enums {
|
||||
options = append(options, Choice{
|
||||
Label: enum.V,
|
||||
Value: enum.V,
|
||||
Label: enum.Label,
|
||||
Value: enum.Value,
|
||||
})
|
||||
}
|
||||
nodes = append(nodes, SelectList(OptionsParams{
|
||||
|
|
@ -116,7 +115,7 @@ func AdminEntity(r *ui.Request, schema *load.Schema, values url.Values) Node {
|
|||
FormButton(ColorPrimary, "Submit"),
|
||||
ButtonLink(
|
||||
ColorNone,
|
||||
r.Path(routenames.AdminEntityList(schema.Name)),
|
||||
r.Path(routenames.AdminEntityList(entityType.GetName())),
|
||||
"Cancel",
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package forms
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/mikestefanello/pagoda/ent/admin"
|
||||
"github.com/mikestefanello/pagoda/pkg/routenames"
|
||||
"github.com/mikestefanello/pagoda/pkg/ui"
|
||||
. "github.com/mikestefanello/pagoda/pkg/ui/components"
|
||||
|
|
@ -10,17 +11,17 @@ import (
|
|||
. "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
func AdminEntityDelete(r *ui.Request, entityTypeName string) Node {
|
||||
func AdminEntityDelete(r *ui.Request, entityType admin.EntityType) Node {
|
||||
return Form(
|
||||
Method(http.MethodPost),
|
||||
P(
|
||||
Textf("Are you sure you want to delete this %s?", entityTypeName),
|
||||
Textf("Are you sure you want to delete this %s?", entityType.GetName()),
|
||||
),
|
||||
ControlGroup(
|
||||
FormButton(ColorError, "Delete"),
|
||||
ButtonLink(
|
||||
ColorNone,
|
||||
r.Path(routenames.AdminEntityList(entityTypeName)),
|
||||
r.Path(routenames.AdminEntityList(entityType.GetName())),
|
||||
"Cancel",
|
||||
),
|
||||
),
|
||||
|
|
|
|||
|
|
@ -121,10 +121,12 @@ func sidebarMenu(r *ui.Request) Node {
|
|||
}
|
||||
|
||||
adminSubMenu := func() Node {
|
||||
entityTypeNames := admin.GetEntityTypeNames()
|
||||
entityTypeLinks := make(Group, len(entityTypeNames))
|
||||
for _, n := range entityTypeNames {
|
||||
entityTypeLinks = append(entityTypeLinks, MenuLink(r, icons.PencilSquare(), n, routenames.AdminEntityList(n)))
|
||||
entityTypeLinks := make(Group, len(admin.GetEntityTypes()))
|
||||
for _, n := range admin.GetEntityTypes() {
|
||||
entityTypeLinks = append(
|
||||
entityTypeLinks,
|
||||
MenuLink(r, icons.PencilSquare(), n.GetName(), routenames.AdminEntityList(n.GetName())),
|
||||
)
|
||||
}
|
||||
|
||||
return Group{
|
||||
|
|
|
|||
|
|
@ -4,7 +4,6 @@ import (
|
|||
"fmt"
|
||||
"net/url"
|
||||
|
||||
"entgo.io/ent/entc/load"
|
||||
"github.com/labstack/echo/v4"
|
||||
"github.com/mikestefanello/pagoda/ent/admin"
|
||||
"github.com/mikestefanello/pagoda/pkg/routenames"
|
||||
|
|
@ -16,37 +15,37 @@ import (
|
|||
. "maragu.dev/gomponents/html"
|
||||
)
|
||||
|
||||
func AdminEntityDelete(ctx echo.Context, entityTypeName string) error {
|
||||
func AdminEntityDelete(ctx echo.Context, entityType admin.EntityType) error {
|
||||
r := ui.NewRequest(ctx)
|
||||
r.Title = fmt.Sprintf("Delete %s", entityTypeName)
|
||||
r.Title = fmt.Sprintf("Delete %s", entityType.GetName())
|
||||
|
||||
return r.Render(
|
||||
layouts.Primary,
|
||||
forms.AdminEntityDelete(r, entityTypeName),
|
||||
forms.AdminEntityDelete(r, entityType),
|
||||
)
|
||||
}
|
||||
|
||||
func AdminEntityInput(ctx echo.Context, schema *load.Schema, values url.Values) error {
|
||||
func AdminEntityInput(ctx echo.Context, entityType admin.EntityType, values url.Values) error {
|
||||
r := ui.NewRequest(ctx)
|
||||
if values == nil {
|
||||
r.Title = fmt.Sprintf("Add %s", schema.Name)
|
||||
r.Title = fmt.Sprintf("Add %s", entityType.GetName())
|
||||
} else {
|
||||
r.Title = fmt.Sprintf("Edit %s", schema.Name)
|
||||
r.Title = fmt.Sprintf("Edit %s", entityType.GetName())
|
||||
}
|
||||
|
||||
return r.Render(
|
||||
layouts.Primary,
|
||||
forms.AdminEntity(r, schema, values),
|
||||
forms.AdminEntity(r, entityType, values),
|
||||
)
|
||||
}
|
||||
|
||||
func AdminEntityList(
|
||||
ctx echo.Context,
|
||||
entityTypeName string,
|
||||
entityType admin.EntityType,
|
||||
entityList *admin.EntityList,
|
||||
) error {
|
||||
r := ui.NewRequest(ctx)
|
||||
r.Title = entityTypeName
|
||||
r.Title = entityType.GetName()
|
||||
|
||||
genHeader := func() Node {
|
||||
g := make(Group, 0, len(entityList.Columns)+2)
|
||||
|
|
@ -68,13 +67,13 @@ func AdminEntityList(
|
|||
Td(
|
||||
ButtonLink(
|
||||
ColorInfo,
|
||||
r.Path(routenames.AdminEntityEdit(entityTypeName), row.ID),
|
||||
r.Path(routenames.AdminEntityEdit(entityType.GetName()), row.ID),
|
||||
"Edit",
|
||||
),
|
||||
Span(Class("mr-2")),
|
||||
ButtonLink(
|
||||
ColorError,
|
||||
r.Path(routenames.AdminEntityDelete(entityTypeName), row.ID),
|
||||
r.Path(routenames.AdminEntityDelete(entityType.GetName()), row.ID),
|
||||
"Delete",
|
||||
),
|
||||
),
|
||||
|
|
@ -95,8 +94,8 @@ func AdminEntityList(
|
|||
Class("form-control mb-2"),
|
||||
ButtonLink(
|
||||
ColorAccent,
|
||||
r.Path(routenames.AdminEntityAdd(entityTypeName)),
|
||||
fmt.Sprintf("Add %s", entityTypeName),
|
||||
r.Path(routenames.AdminEntityAdd(entityType.GetName())),
|
||||
fmt.Sprintf("Add %s", entityType.GetName()),
|
||||
),
|
||||
),
|
||||
Table(
|
||||
|
|
@ -108,7 +107,7 @@ func AdminEntityList(
|
|||
),
|
||||
Pager(
|
||||
entityList.Page,
|
||||
r.Path(routenames.AdminEntityAdd(entityTypeName)),
|
||||
r.Path(routenames.AdminEntityAdd(entityType.GetName())),
|
||||
entityList.HasNextPage,
|
||||
"",
|
||||
),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue