Add dynamic admin panel for managing entities (#108)
This commit is contained in:
parent
d91774313d
commit
7dcf6bc039
47 changed files with 2173 additions and 320 deletions
319
ent/admin/handler.go
Normal file
319
ent/admin/handler.go
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
// Code generated by ent, DO NOT EDIT.
|
||||
package admin
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"net/url"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"entgo.io/ent/dialect/sql"
|
||||
"github.com/labstack/echo/v4"
|
||||
|
||||
"github.com/mikestefanello/pagoda/ent"
|
||||
"github.com/mikestefanello/pagoda/ent/passwordtoken"
|
||||
"github.com/mikestefanello/pagoda/ent/user"
|
||||
)
|
||||
|
||||
const dateTimeFormat = "2006-01-02T15:04:05"
|
||||
const dateTimeFormatNoSeconds = "2006-01-02T15:04"
|
||||
|
||||
type Handler struct {
|
||||
client *ent.Client
|
||||
Config HandlerConfig
|
||||
}
|
||||
|
||||
func NewHandler(client *ent.Client, cfg HandlerConfig) *Handler {
|
||||
return &Handler{
|
||||
client: client,
|
||||
Config: cfg,
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) Create(ctx echo.Context, entityType string) error {
|
||||
switch entityType {
|
||||
case "PasswordToken":
|
||||
return h.PasswordTokenCreate(ctx)
|
||||
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":
|
||||
return h.PasswordTokenGet(ctx, id)
|
||||
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":
|
||||
return h.PasswordTokenDelete(ctx, id)
|
||||
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":
|
||||
return h.PasswordTokenUpdate(ctx, id)
|
||||
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":
|
||||
return h.PasswordTokenList(ctx)
|
||||
case "User":
|
||||
return h.UserList(ctx)
|
||||
default:
|
||||
return nil, fmt.Errorf("unsupported entity type: %s", entityType)
|
||||
}
|
||||
}
|
||||
|
||||
func (h *Handler) PasswordTokenCreate(ctx echo.Context) error {
|
||||
var payload PasswordToken
|
||||
if err := h.bind(ctx, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
op := h.client.PasswordToken.Create()
|
||||
if payload.Token != nil {
|
||||
op.SetToken(*payload.Token)
|
||||
}
|
||||
op.SetUserID(payload.UserID)
|
||||
if payload.CreatedAt != nil {
|
||||
op.SetCreatedAt(*payload.CreatedAt)
|
||||
}
|
||||
_, err := op.Save(ctx.Request().Context())
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *Handler) PasswordTokenUpdate(ctx echo.Context, id int) error {
|
||||
entity, err := h.client.PasswordToken.Get(ctx.Request().Context(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var payload PasswordToken
|
||||
if err = h.bind(ctx, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
op := entity.Update()
|
||||
if payload.Token != nil {
|
||||
op.SetToken(*payload.Token)
|
||||
}
|
||||
op.SetUserID(payload.UserID)
|
||||
if payload.CreatedAt == nil {
|
||||
var empty time.Time
|
||||
op.SetCreatedAt(empty)
|
||||
} else {
|
||||
op.SetCreatedAt(*payload.CreatedAt)
|
||||
}
|
||||
_, err = op.Save(ctx.Request().Context())
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *Handler) PasswordTokenDelete(ctx echo.Context, id int) error {
|
||||
return h.client.PasswordToken.DeleteOneID(id).
|
||||
Exec(ctx.Request().Context())
|
||||
}
|
||||
|
||||
func (h *Handler) PasswordTokenList(ctx echo.Context) (*EntityList, error) {
|
||||
page, offset := h.getPageAndOffset(ctx)
|
||||
res, err := h.client.PasswordToken.
|
||||
Query().
|
||||
Limit(h.Config.ItemsPerPage + 1).
|
||||
Offset(offset).
|
||||
Order(passwordtoken.ByID(sql.OrderDesc())).
|
||||
All(ctx.Request().Context())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := &EntityList{
|
||||
Columns: []string{
|
||||
"User ID",
|
||||
"Created at",
|
||||
},
|
||||
Entities: make([]EntityValues, 0, len(res)),
|
||||
Page: page,
|
||||
HasNextPage: len(res) > h.Config.ItemsPerPage,
|
||||
}
|
||||
|
||||
for i := 0; i <= len(res)-1; i++ {
|
||||
list.Entities = append(list.Entities, EntityValues{
|
||||
ID: res[i].ID,
|
||||
Values: []string{
|
||||
fmt.Sprint(res[i].UserID),
|
||||
res[i].CreatedAt.Format(h.Config.TimeFormat),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return list, err
|
||||
}
|
||||
|
||||
func (h *Handler) PasswordTokenGet(ctx echo.Context, id int) (url.Values, error) {
|
||||
entity, err := h.client.PasswordToken.Get(ctx.Request().Context(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("user_id", fmt.Sprint(entity.UserID))
|
||||
v.Set("created_at", entity.CreatedAt.Format(dateTimeFormat))
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (h *Handler) UserCreate(ctx echo.Context) error {
|
||||
var payload User
|
||||
if err := h.bind(ctx, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
op := h.client.User.Create()
|
||||
op.SetName(payload.Name)
|
||||
op.SetEmail(payload.Email)
|
||||
if payload.Password != nil {
|
||||
op.SetPassword(*payload.Password)
|
||||
}
|
||||
op.SetVerified(payload.Verified)
|
||||
op.SetAdmin(payload.Admin)
|
||||
if payload.CreatedAt != nil {
|
||||
op.SetCreatedAt(*payload.CreatedAt)
|
||||
}
|
||||
_, err := op.Save(ctx.Request().Context())
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *Handler) UserUpdate(ctx echo.Context, id int) error {
|
||||
entity, err := h.client.User.Get(ctx.Request().Context(), id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
var payload User
|
||||
if err = h.bind(ctx, &payload); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
op := entity.Update()
|
||||
op.SetName(payload.Name)
|
||||
op.SetEmail(payload.Email)
|
||||
if payload.Password != nil {
|
||||
op.SetPassword(*payload.Password)
|
||||
}
|
||||
op.SetVerified(payload.Verified)
|
||||
op.SetAdmin(payload.Admin)
|
||||
_, err = op.Save(ctx.Request().Context())
|
||||
return err
|
||||
}
|
||||
|
||||
func (h *Handler) UserDelete(ctx echo.Context, id int) error {
|
||||
return h.client.User.DeleteOneID(id).
|
||||
Exec(ctx.Request().Context())
|
||||
}
|
||||
|
||||
func (h *Handler) UserList(ctx echo.Context) (*EntityList, error) {
|
||||
page, offset := h.getPageAndOffset(ctx)
|
||||
res, err := h.client.User.
|
||||
Query().
|
||||
Limit(h.Config.ItemsPerPage + 1).
|
||||
Offset(offset).
|
||||
Order(user.ByID(sql.OrderDesc())).
|
||||
All(ctx.Request().Context())
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
list := &EntityList{
|
||||
Columns: []string{
|
||||
"Name",
|
||||
"Email",
|
||||
"Verified",
|
||||
"Admin",
|
||||
"Created at",
|
||||
},
|
||||
Entities: make([]EntityValues, 0, len(res)),
|
||||
Page: page,
|
||||
HasNextPage: len(res) > h.Config.ItemsPerPage,
|
||||
}
|
||||
|
||||
for i := 0; i <= len(res)-1; i++ {
|
||||
list.Entities = append(list.Entities, EntityValues{
|
||||
ID: res[i].ID,
|
||||
Values: []string{
|
||||
res[i].Name,
|
||||
res[i].Email,
|
||||
fmt.Sprint(res[i].Verified),
|
||||
fmt.Sprint(res[i].Admin),
|
||||
res[i].CreatedAt.Format(h.Config.TimeFormat),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
return list, err
|
||||
}
|
||||
|
||||
func (h *Handler) UserGet(ctx echo.Context, id int) (url.Values, error) {
|
||||
entity, err := h.client.User.Get(ctx.Request().Context(), id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
v := url.Values{}
|
||||
v.Set("name", entity.Name)
|
||||
v.Set("email", entity.Email)
|
||||
v.Set("verified", fmt.Sprint(entity.Verified))
|
||||
v.Set("admin", fmt.Sprint(entity.Admin))
|
||||
return v, err
|
||||
}
|
||||
|
||||
func (h *Handler) getPageAndOffset(ctx echo.Context) (int, int) {
|
||||
if page, err := strconv.Atoi(ctx.QueryParam(h.Config.PageQueryKey)); err == nil {
|
||||
if page > 1 {
|
||||
return page, (page - 1) * h.Config.ItemsPerPage
|
||||
}
|
||||
}
|
||||
return 1, 0
|
||||
}
|
||||
|
||||
func (h *Handler) bind(ctx echo.Context, entity any) error {
|
||||
// Echo requires some pre-processing of form values to avoid problems.
|
||||
for k, v := range ctx.Request().Form {
|
||||
// Remove empty field values so Echo's bind does not fail when trying to parse things like
|
||||
// times, etc.
|
||||
if len(v) == 1 && len(v[0]) == 0 {
|
||||
delete(ctx.Request().Form, k)
|
||||
continue
|
||||
}
|
||||
|
||||
// Echo expects datetime values to be in a certain format but that does not align with the datetime-local
|
||||
// HTML form element format, so we will attempt to convert it here.
|
||||
for _, format := range []string{dateTimeFormatNoSeconds, dateTimeFormat} {
|
||||
if t, err := time.Parse(format, v[0]); err == nil {
|
||||
ctx.Request().Form[k][0] = t.Format(time.RFC3339)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
return ctx.Bind(entity)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue