Override ogent code to always return ent errors.

This commit is contained in:
mikestefanello 2025-04-01 19:21:21 -04:00
parent 196d34cc1f
commit 9139942794
6 changed files with 85 additions and 235 deletions

View file

@ -12,13 +12,28 @@ import (
"github.com/ogen-go/ogen" "github.com/ogen-go/ogen"
) )
var returnAllErrors = gen.MustParse(gen.NewTemplate("").Parse(`
{{ define "ogent/ogent/helper/error" }}{{/* gotype: entgo.io/ent/entc/gen.typeScope */}}
{{- $pkg := base $.Type.Config.Package }}
if err != nil {
{{- with $.Scope.Tx }}
if rErr := {{ . }}.Rollback(); rErr != nil {
return nil, fmt.Errorf("%w: %v", err, rErr)
}
{{- end }}
// Let the server handle the error.
return nil, err
}
{{ end }}
`))
func main() { func main() {
spec := new(ogen.Spec) spec := new(ogen.Spec)
oas, err := entoas.NewExtension(entoas.Spec(spec)) oas, err := entoas.NewExtension(entoas.Spec(spec))
if err != nil { if err != nil {
log.Fatalf("creating entoas extension: %v", err) log.Fatalf("creating entoas extension: %v", err)
} }
ogent, err := ogent.NewExtension(spec) ogent, err := ogent.NewExtension(spec, ogent.Templates(returnAllErrors))
if err != nil { if err != nil {
log.Fatalf("creating ogent extension: %v", err) log.Fatalf("creating ogent extension: %v", err)
} }

View file

@ -4,7 +4,6 @@ package ogent
import ( import (
"context" "context"
"net/http"
"github.com/go-faster/jx" "github.com/go-faster/jx"
"github.com/mikestefanello/pagoda/ent" "github.com/mikestefanello/pagoda/ent"
@ -38,23 +37,8 @@ func (h *OgentHandler) CreatePasswordToken(ctx context.Context, req *CreatePassw
// Persist to storage. // Persist to storage.
e, err := b.Save(ctx) e, err := b.Save(ctx)
if err != nil { if err != nil {
switch { // Let the server handle the error.
case ent.IsNotSingular(err): return nil, err
return &R409{
Code: http.StatusConflict,
Status: http.StatusText(http.StatusConflict),
Errors: rawError(err),
}, nil
case ent.IsConstraintError(err):
return &R409{
Code: http.StatusConflict,
Status: http.StatusText(http.StatusConflict),
Errors: rawError(err),
}, nil
default:
// Let the server handle the error.
return nil, err
}
} }
// Reload the entity to attach all eager-loaded edges. // Reload the entity to attach all eager-loaded edges.
q := h.client.PasswordToken.Query().Where(passwordtoken.ID(e.ID)) q := h.client.PasswordToken.Query().Where(passwordtoken.ID(e.ID))
@ -71,23 +55,8 @@ func (h *OgentHandler) ReadPasswordToken(ctx context.Context, params ReadPasswor
q := h.client.PasswordToken.Query().Where(passwordtoken.IDEQ(params.ID)) q := h.client.PasswordToken.Query().Where(passwordtoken.IDEQ(params.ID))
e, err := q.Only(ctx) e, err := q.Only(ctx)
if err != nil { if err != nil {
switch { // Let the server handle the error.
case ent.IsNotFound(err): return nil, err
return &R404{
Code: http.StatusNotFound,
Status: http.StatusText(http.StatusNotFound),
Errors: rawError(err),
}, nil
case ent.IsNotSingular(err):
return &R409{
Code: http.StatusConflict,
Status: http.StatusText(http.StatusConflict),
Errors: rawError(err),
}, nil
default:
// Let the server handle the error.
return nil, err
}
} }
return NewPasswordTokenRead(e), nil return NewPasswordTokenRead(e), nil
} }
@ -109,23 +78,8 @@ func (h *OgentHandler) UpdatePasswordToken(ctx context.Context, req *UpdatePassw
// Persist to storage. // Persist to storage.
e, err := b.Save(ctx) e, err := b.Save(ctx)
if err != nil { if err != nil {
switch { // Let the server handle the error.
case ent.IsNotFound(err): return nil, err
return &R404{
Code: http.StatusNotFound,
Status: http.StatusText(http.StatusNotFound),
Errors: rawError(err),
}, nil
case ent.IsConstraintError(err):
return &R409{
Code: http.StatusConflict,
Status: http.StatusText(http.StatusConflict),
Errors: rawError(err),
}, nil
default:
// Let the server handle the error.
return nil, err
}
} }
// Reload the entity to attach all eager-loaded edges. // Reload the entity to attach all eager-loaded edges.
q := h.client.PasswordToken.Query().Where(passwordtoken.ID(e.ID)) q := h.client.PasswordToken.Query().Where(passwordtoken.ID(e.ID))
@ -141,23 +95,8 @@ func (h *OgentHandler) UpdatePasswordToken(ctx context.Context, req *UpdatePassw
func (h *OgentHandler) DeletePasswordToken(ctx context.Context, params DeletePasswordTokenParams) (DeletePasswordTokenRes, error) { func (h *OgentHandler) DeletePasswordToken(ctx context.Context, params DeletePasswordTokenParams) (DeletePasswordTokenRes, error) {
err := h.client.PasswordToken.DeleteOneID(params.ID).Exec(ctx) err := h.client.PasswordToken.DeleteOneID(params.ID).Exec(ctx)
if err != nil { if err != nil {
switch { // Let the server handle the error.
case ent.IsNotFound(err): return nil, err
return &R404{
Code: http.StatusNotFound,
Status: http.StatusText(http.StatusNotFound),
Errors: rawError(err),
}, nil
case ent.IsConstraintError(err):
return &R409{
Code: http.StatusConflict,
Status: http.StatusText(http.StatusConflict),
Errors: rawError(err),
}, nil
default:
// Let the server handle the error.
return nil, err
}
} }
return new(DeletePasswordTokenNoContent), nil return new(DeletePasswordTokenNoContent), nil
@ -178,23 +117,8 @@ func (h *OgentHandler) ListPasswordToken(ctx context.Context, params ListPasswor
es, err := q.All(ctx) es, err := q.All(ctx)
if err != nil { if err != nil {
switch { // Let the server handle the error.
case ent.IsNotFound(err): return nil, err
return &R404{
Code: http.StatusNotFound,
Status: http.StatusText(http.StatusNotFound),
Errors: rawError(err),
}, nil
case ent.IsNotSingular(err):
return &R409{
Code: http.StatusConflict,
Status: http.StatusText(http.StatusConflict),
Errors: rawError(err),
}, nil
default:
// Let the server handle the error.
return nil, err
}
} }
r := NewPasswordTokenLists(es) r := NewPasswordTokenLists(es)
return (*ListPasswordTokenOKApplicationJSON)(&r), nil return (*ListPasswordTokenOKApplicationJSON)(&r), nil
@ -205,23 +129,8 @@ func (h *OgentHandler) ReadPasswordTokenUser(ctx context.Context, params ReadPas
q := h.client.PasswordToken.Query().Where(passwordtoken.IDEQ(params.ID)).QueryUser() q := h.client.PasswordToken.Query().Where(passwordtoken.IDEQ(params.ID)).QueryUser()
e, err := q.Only(ctx) e, err := q.Only(ctx)
if err != nil { if err != nil {
switch { // Let the server handle the error.
case ent.IsNotFound(err): return nil, err
return &R404{
Code: http.StatusNotFound,
Status: http.StatusText(http.StatusNotFound),
Errors: rawError(err),
}, nil
case ent.IsNotSingular(err):
return &R409{
Code: http.StatusConflict,
Status: http.StatusText(http.StatusConflict),
Errors: rawError(err),
}, nil
default:
// Let the server handle the error.
return nil, err
}
} }
return NewPasswordTokenUserRead(e), nil return NewPasswordTokenUserRead(e), nil
} }
@ -240,23 +149,8 @@ func (h *OgentHandler) CreateUser(ctx context.Context, req *CreateUserReq) (Crea
// Persist to storage. // Persist to storage.
e, err := b.Save(ctx) e, err := b.Save(ctx)
if err != nil { if err != nil {
switch { // Let the server handle the error.
case ent.IsNotSingular(err): return nil, err
return &R409{
Code: http.StatusConflict,
Status: http.StatusText(http.StatusConflict),
Errors: rawError(err),
}, nil
case ent.IsConstraintError(err):
return &R409{
Code: http.StatusConflict,
Status: http.StatusText(http.StatusConflict),
Errors: rawError(err),
}, nil
default:
// Let the server handle the error.
return nil, err
}
} }
// Reload the entity to attach all eager-loaded edges. // Reload the entity to attach all eager-loaded edges.
q := h.client.User.Query().Where(user.ID(e.ID)) q := h.client.User.Query().Where(user.ID(e.ID))
@ -273,23 +167,8 @@ func (h *OgentHandler) ReadUser(ctx context.Context, params ReadUserParams) (Rea
q := h.client.User.Query().Where(user.IDEQ(params.ID)) q := h.client.User.Query().Where(user.IDEQ(params.ID))
e, err := q.Only(ctx) e, err := q.Only(ctx)
if err != nil { if err != nil {
switch { // Let the server handle the error.
case ent.IsNotFound(err): return nil, err
return &R404{
Code: http.StatusNotFound,
Status: http.StatusText(http.StatusNotFound),
Errors: rawError(err),
}, nil
case ent.IsNotSingular(err):
return &R409{
Code: http.StatusConflict,
Status: http.StatusText(http.StatusConflict),
Errors: rawError(err),
}, nil
default:
// Let the server handle the error.
return nil, err
}
} }
return NewUserRead(e), nil return NewUserRead(e), nil
} }
@ -317,23 +196,8 @@ func (h *OgentHandler) UpdateUser(ctx context.Context, req *UpdateUserReq, param
// Persist to storage. // Persist to storage.
e, err := b.Save(ctx) e, err := b.Save(ctx)
if err != nil { if err != nil {
switch { // Let the server handle the error.
case ent.IsNotFound(err): return nil, err
return &R404{
Code: http.StatusNotFound,
Status: http.StatusText(http.StatusNotFound),
Errors: rawError(err),
}, nil
case ent.IsConstraintError(err):
return &R409{
Code: http.StatusConflict,
Status: http.StatusText(http.StatusConflict),
Errors: rawError(err),
}, nil
default:
// Let the server handle the error.
return nil, err
}
} }
// Reload the entity to attach all eager-loaded edges. // Reload the entity to attach all eager-loaded edges.
q := h.client.User.Query().Where(user.ID(e.ID)) q := h.client.User.Query().Where(user.ID(e.ID))
@ -349,23 +213,8 @@ func (h *OgentHandler) UpdateUser(ctx context.Context, req *UpdateUserReq, param
func (h *OgentHandler) DeleteUser(ctx context.Context, params DeleteUserParams) (DeleteUserRes, error) { func (h *OgentHandler) DeleteUser(ctx context.Context, params DeleteUserParams) (DeleteUserRes, error) {
err := h.client.User.DeleteOneID(params.ID).Exec(ctx) err := h.client.User.DeleteOneID(params.ID).Exec(ctx)
if err != nil { if err != nil {
switch { // Let the server handle the error.
case ent.IsNotFound(err): return nil, err
return &R404{
Code: http.StatusNotFound,
Status: http.StatusText(http.StatusNotFound),
Errors: rawError(err),
}, nil
case ent.IsConstraintError(err):
return &R409{
Code: http.StatusConflict,
Status: http.StatusText(http.StatusConflict),
Errors: rawError(err),
}, nil
default:
// Let the server handle the error.
return nil, err
}
} }
return new(DeleteUserNoContent), nil return new(DeleteUserNoContent), nil
@ -386,23 +235,8 @@ func (h *OgentHandler) ListUser(ctx context.Context, params ListUserParams) (Lis
es, err := q.All(ctx) es, err := q.All(ctx)
if err != nil { if err != nil {
switch { // Let the server handle the error.
case ent.IsNotFound(err): return nil, err
return &R404{
Code: http.StatusNotFound,
Status: http.StatusText(http.StatusNotFound),
Errors: rawError(err),
}, nil
case ent.IsNotSingular(err):
return &R409{
Code: http.StatusConflict,
Status: http.StatusText(http.StatusConflict),
Errors: rawError(err),
}, nil
default:
// Let the server handle the error.
return nil, err
}
} }
r := NewUserLists(es) r := NewUserLists(es)
return (*ListUserOKApplicationJSON)(&r), nil return (*ListUserOKApplicationJSON)(&r), nil
@ -422,23 +256,8 @@ func (h *OgentHandler) ListUserOwner(ctx context.Context, params ListUserOwnerPa
q.Limit(itemsPerPage).Offset((page - 1) * itemsPerPage) q.Limit(itemsPerPage).Offset((page - 1) * itemsPerPage)
es, err := q.All(ctx) es, err := q.All(ctx)
if err != nil { if err != nil {
switch { // Let the server handle the error.
case ent.IsNotFound(err): return nil, err
return &R404{
Code: http.StatusNotFound,
Status: http.StatusText(http.StatusNotFound),
Errors: rawError(err),
}, nil
case ent.IsNotSingular(err):
return &R409{
Code: http.StatusConflict,
Status: http.StatusText(http.StatusConflict),
Errors: rawError(err),
}, nil
default:
// Let the server handle the error.
return nil, err
}
} }
r := NewUserOwnerLists(es) r := NewUserOwnerLists(es)
return (*ListUserOwnerOKApplicationJSON)(&r), nil return (*ListUserOwnerOKApplicationJSON)(&r), nil

View file

@ -124,8 +124,20 @@ func (h *Admin) EntityAddSubmit(p AdminEntityPlugin) echo.HandlerFunc {
if err != nil { if err != nil {
return fail(err, fmt.Sprintf("failed to bind create password token request body")) return fail(err, fmt.Sprintf("failed to bind create password token request body"))
} }
fmt.Printf("%+v", v)
return h.EntityAdd(p)(ctx) res, err := h.ogent.CreatePasswordToken(ctx.Request().Context(), &v)
if err != nil {
msg.Danger(ctx, err.Error())
return h.EntityAdd(p)(ctx)
}
fmt.Printf("%+v\n", res)
msg.Success(ctx, fmt.Sprintf("Successfully added %s.", strings.ToLower(p.Label)))
return redirect.
New(ctx).
Route(p.RouteNameList()).
Go()
} }
} }

View file

@ -1,7 +1,6 @@
package components package components
import ( import (
"fmt"
"strings" "strings"
"github.com/mikestefanello/pagoda/pkg/ui" "github.com/mikestefanello/pagoda/pkg/ui"
@ -16,32 +15,6 @@ func JS(r *ui.Request) Node {
} }
} }
func HtmxListeners(r *ui.Request) Node {
const htmxErr = `
document.body.addEventListener('htmx:beforeSwap', function(evt) {
if (evt.detail.xhr.status >= 400){
evt.detail.shouldSwap = true;
evt.detail.target = htmx.find("body");
}
});
`
const htmxCSRF = `
document.body.addEventListener('htmx:configRequest', function(evt) {
if (evt.detail.verb !== "get") {
evt.detail.parameters['csrf'] = '%s';
}
})
`
return Group{
Script(Raw(htmxErr)),
Iff(len(r.CSRF) > 0, func() Node {
return Script(Raw(fmt.Sprintf(htmxCSRF, r.CSRF)))
}),
}
}
func CSS() Node { func CSS() Node {
return Link( return Link(
Href("https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css"), Href("https://cdn.jsdelivr.net/npm/bulma@1.0.2/css/bulma.min.css"),

View file

@ -1,9 +1,39 @@
package components package components
import ( import (
"fmt"
"github.com/mikestefanello/pagoda/pkg/ui"
. "maragu.dev/gomponents" . "maragu.dev/gomponents"
. "maragu.dev/gomponents/html"
) )
func HtmxListeners(r *ui.Request) Node {
const htmxErr = `
document.body.addEventListener('htmx:beforeSwap', function(evt) {
if (evt.detail.xhr.status >= 400){
evt.detail.shouldSwap = true;
evt.detail.target = htmx.find("body");
}
});
`
const htmxCSRF = `
document.body.addEventListener('htmx:configRequest', function(evt) {
if (evt.detail.verb !== "get") {
evt.detail.parameters['csrf'] = '%s';
}
})
`
return Group{
Script(Raw(htmxErr)),
Iff(len(r.CSRF) > 0, func() Node {
return Script(Raw(fmt.Sprintf(htmxCSRF, r.CSRF)))
}),
}
}
func HxBoost() Node { func HxBoost() Node {
return Attr("hx-boost", "true") return Attr("hx-boost", "true")
} }

View file

@ -66,6 +66,7 @@ func AdminEntityAdd(ctx echo.Context, schema *load.Schema) error {
InputType: "text", InputType: "text",
Label: label(f.Name), Label: label(f.Name),
Help: fmt.Sprintf("Use the following format: %s", time.Now().Format(time.RFC3339)), Help: fmt.Sprintf("Use the following format: %s", time.Now().Format(time.RFC3339)),
Value: time.Now().Format(time.RFC3339),
})) }))
case field.TypeBool: case field.TypeBool:
nodes = append(nodes, P(Textf("%s not supported", f.Name))) nodes = append(nodes, P(Textf("%s not supported", f.Name)))