Easier form and form submission handling.
This commit is contained in:
parent
400b9b36ba
commit
28abc92e74
36
README.md
36
README.md
@ -646,7 +646,7 @@ An example of this pattern is:
|
|||||||
type ContactForm struct {
|
type ContactForm struct {
|
||||||
Email string `form:"email" validate:"required,email"`
|
Email string `form:"email" validate:"required,email"`
|
||||||
Message string `form:"message" validate:"required"`
|
Message string `form:"message" validate:"required"`
|
||||||
Submission controller.FormSubmission
|
Submission form.Submission
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -654,27 +654,23 @@ Then in your page:
|
|||||||
|
|
||||||
```go
|
```go
|
||||||
page := controller.NewPage(ctx)
|
page := controller.NewPage(ctx)
|
||||||
page.Form = ContactForm{}
|
page.Form = form.Get[ContactForm](ctx)
|
||||||
```
|
```
|
||||||
|
|
||||||
How the _form_ gets populated with values so that your template can render them is covered in the next section.
|
This will either initialize a new form to be rendered, or load one previously stored in the context (ie, if it was already submitted). How the _form_ gets populated with values so that your template can render them is covered in the next section.
|
||||||
|
|
||||||
#### Submission processing
|
#### Submission processing
|
||||||
|
|
||||||
Form submission processing is made extremely simple by leveraging functionality provided by [Echo binding](https://echo.labstack.com/guide/binding/), [validator](https://github.com/go-playground/validator) and the `FormSubmission` struct located in `pkg/controller/form.go`.
|
Form submission processing is made extremely simple by leveraging functionality provided by [Echo binding](https://echo.labstack.com/guide/binding/), [validator](https://github.com/go-playground/validator) and the `Submission` struct located in `pkg/form/form.go`.
|
||||||
|
|
||||||
Using the example form above, these are the steps you would take within the _POST_ callback for your route:
|
Using the example form above, these are the steps you would take within the _POST_ callback for your route:
|
||||||
|
|
||||||
Start by storing a pointer to the form in the context so that your _GET_ callback can access the form values, which will be showed at the end:
|
Start by setting the form in the request contxt. This stores a pointer to the form so that your _GET_ callback can access the form values (shown previously). It also will parse the input in the POST data to map to the struct so it becomes populated. This uses the `form` struct tags to map form values to the struct fields.
|
||||||
```go
|
```go
|
||||||
var form ContactForm
|
var input ContactForm
|
||||||
ctx.Set(context.FormKey, &form)
|
|
||||||
```
|
|
||||||
|
|
||||||
Parse the input in the POST data to map to the struct so it becomes populated. This uses the `form` struct tags to map form values to the struct fields.
|
if err := form.Set(ctx, &input); err != nil {
|
||||||
```go
|
return err
|
||||||
if err := ctx.Bind(&form); err != nil {
|
|
||||||
// Something went wrong...
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -695,17 +691,7 @@ if !form.Submission.HasErrors() {
|
|||||||
In the event of a validation error, you most likely want to re-render the form with the values provided and any error messages. Since you stored a pointer to the _form_ in the context in the first step, you can first have the _POST_ handler call the _GET_:
|
In the event of a validation error, you most likely want to re-render the form with the values provided and any error messages. Since you stored a pointer to the _form_ in the context in the first step, you can first have the _POST_ handler call the _GET_:
|
||||||
```go
|
```go
|
||||||
if form.Submission.HasErrors() {
|
if form.Submission.HasErrors() {
|
||||||
return c.Get(ctx)
|
return c.GetCallback(ctx)
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
Then, in your _GET_ handler, extract the form from the context so it can be passed to the templates:
|
|
||||||
```go
|
|
||||||
page := controller.NewPage(ctx)
|
|
||||||
page.Form = ContactForm{}
|
|
||||||
|
|
||||||
if form := ctx.Get(context.FormKey); form != nil {
|
|
||||||
page.Form = form.(*ContactForm)
|
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@ -716,9 +702,9 @@ And finally, your template:
|
|||||||
|
|
||||||
#### Inline validation
|
#### Inline validation
|
||||||
|
|
||||||
The `FormSubmission` makes inline validation easier because it will store all validation errors in a map, keyed by the form struct field name. It also contains helper methods that your templates can use to provide classes and extract the error messages.
|
The `Submission` makes inline validation easier because it will store all validation errors in a map, keyed by the form struct field name. It also contains helper methods that your templates can use to provide classes and extract the error messages.
|
||||||
|
|
||||||
While [validator](https://github.com/go-playground/validator) is a great package that is used to validate based on struct tags, the downside is that the messaging, by default, is not very human-readable or easy to override. Within `FormSubmission.setErrorMessages()` the validation errors are converted to more readable messages based on the tag that failed validation. Only a few tags are provided as an example, so be sure to expand on that as needed.
|
While [validator](https://github.com/go-playground/validator) is a great package that is used to validate based on struct tags, the downside is that the messaging, by default, is not very human-readable or easy to override. Within `Submission.setErrorMessages()` the validation errors are converted to more readable messages based on the tag that failed validation. Only a few tags are provided as an example, so be sure to expand on that as needed.
|
||||||
|
|
||||||
To provide the inline validation in your template, there are two things that need to be done.
|
To provide the inline validation in your template, there are two things that need to be done.
|
||||||
|
|
||||||
|
34
pkg/form/form.go
Normal file
34
pkg/form/form.go
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
package form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/mikestefanello/pagoda/pkg/context"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Get gets a form from the context or initializes a new copy if one is not set
|
||||||
|
func Get[T any](ctx echo.Context) *T {
|
||||||
|
if v := ctx.Get(context.FormKey); v != nil {
|
||||||
|
return v.(*T)
|
||||||
|
}
|
||||||
|
var v T
|
||||||
|
return &v
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set sets a form in the context and binds the request values to it
|
||||||
|
func Set(ctx echo.Context, form any) error {
|
||||||
|
ctx.Set(context.FormKey, form)
|
||||||
|
|
||||||
|
if err := ctx.Bind(form); err != nil {
|
||||||
|
return echo.NewHTTPError(http.StatusInternalServerError, fmt.Sprintf("unable to bind form: %v", err))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clear removes the form set in the context
|
||||||
|
func Clear(ctx echo.Context) {
|
||||||
|
ctx.Set(context.FormKey, nil)
|
||||||
|
}
|
59
pkg/form/form_test.go
Normal file
59
pkg/form/form_test.go
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
package form
|
||||||
|
|
||||||
|
import (
|
||||||
|
"net/http"
|
||||||
|
"net/http/httptest"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/mikestefanello/pagoda/pkg/tests"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestContextFuncs(t *testing.T) {
|
||||||
|
e := echo.New()
|
||||||
|
|
||||||
|
type example struct {
|
||||||
|
Name string `form:"name"`
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("get empty context", func(t *testing.T) {
|
||||||
|
// Empty context, still return a form
|
||||||
|
ctx, _ := tests.NewContext(e, "/")
|
||||||
|
form := Get[example](ctx)
|
||||||
|
assert.NotNil(t, form)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set bad request", func(t *testing.T) {
|
||||||
|
// Set with a bad request
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("abc=abc"))
|
||||||
|
ctx := e.NewContext(req, httptest.NewRecorder())
|
||||||
|
var form example
|
||||||
|
err := Set(ctx, &form)
|
||||||
|
assert.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set", func(t *testing.T) {
|
||||||
|
// Set and parse the values
|
||||||
|
req := httptest.NewRequest(http.MethodPost, "/", strings.NewReader("name=abc"))
|
||||||
|
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
|
||||||
|
ctx := e.NewContext(req, httptest.NewRecorder())
|
||||||
|
var form example
|
||||||
|
err := Set(ctx, &form)
|
||||||
|
require.NoError(t, err)
|
||||||
|
assert.Equal(t, "abc", form.Name)
|
||||||
|
|
||||||
|
// Get again and expect the values were stored
|
||||||
|
got := Get[example](ctx)
|
||||||
|
require.NotNil(t, got)
|
||||||
|
assert.Equal(t, "abc", form.Name)
|
||||||
|
|
||||||
|
// Clear
|
||||||
|
Clear(ctx)
|
||||||
|
got = Get[example](ctx)
|
||||||
|
require.NotNil(t, got)
|
||||||
|
assert.Empty(t, got.Name)
|
||||||
|
})
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
package controller
|
package form
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/go-playground/validator/v10"
|
"github.com/go-playground/validator/v10"
|
||||||
@ -6,8 +6,8 @@ import (
|
|||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
)
|
)
|
||||||
|
|
||||||
// FormSubmission represents the state of the submission of a form, not including the form itself
|
// Submission represents the state of the submission of a form, not including the form itself
|
||||||
type FormSubmission struct {
|
type Submission struct {
|
||||||
// IsSubmitted indicates if the form has been submitted
|
// IsSubmitted indicates if the form has been submitted
|
||||||
IsSubmitted bool
|
IsSubmitted bool
|
||||||
|
|
||||||
@ -16,7 +16,7 @@ type FormSubmission struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process processes a submission for a form
|
// Process processes a submission for a form
|
||||||
func (f *FormSubmission) Process(ctx echo.Context, form any) error {
|
func (f *Submission) Process(ctx echo.Context, form any) error {
|
||||||
f.Errors = make(map[string][]string)
|
f.Errors = make(map[string][]string)
|
||||||
f.IsSubmitted = true
|
f.IsSubmitted = true
|
||||||
|
|
||||||
@ -29,7 +29,7 @@ func (f *FormSubmission) Process(ctx echo.Context, form any) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// HasErrors indicates if the submission has any validation errors
|
// HasErrors indicates if the submission has any validation errors
|
||||||
func (f FormSubmission) HasErrors() bool {
|
func (f Submission) HasErrors() bool {
|
||||||
if f.Errors == nil {
|
if f.Errors == nil {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
@ -37,12 +37,12 @@ func (f FormSubmission) HasErrors() bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// FieldHasErrors indicates if a given field on the form has any validation errors
|
// FieldHasErrors indicates if a given field on the form has any validation errors
|
||||||
func (f FormSubmission) FieldHasErrors(fieldName string) bool {
|
func (f Submission) FieldHasErrors(fieldName string) bool {
|
||||||
return len(f.GetFieldErrors(fieldName)) > 0
|
return len(f.GetFieldErrors(fieldName)) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// SetFieldError sets an error message for a given field name
|
// SetFieldError sets an error message for a given field name
|
||||||
func (f *FormSubmission) SetFieldError(fieldName string, message string) {
|
func (f *Submission) SetFieldError(fieldName string, message string) {
|
||||||
if f.Errors == nil {
|
if f.Errors == nil {
|
||||||
f.Errors = make(map[string][]string)
|
f.Errors = make(map[string][]string)
|
||||||
}
|
}
|
||||||
@ -50,7 +50,7 @@ func (f *FormSubmission) SetFieldError(fieldName string, message string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetFieldErrors gets the errors for a given field name
|
// GetFieldErrors gets the errors for a given field name
|
||||||
func (f FormSubmission) GetFieldErrors(fieldName string) []string {
|
func (f Submission) GetFieldErrors(fieldName string) []string {
|
||||||
if f.Errors == nil {
|
if f.Errors == nil {
|
||||||
return []string{}
|
return []string{}
|
||||||
}
|
}
|
||||||
@ -58,7 +58,7 @@ func (f FormSubmission) GetFieldErrors(fieldName string) []string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// GetFieldStatusClass returns an HTML class based on the status of the field
|
// GetFieldStatusClass returns an HTML class based on the status of the field
|
||||||
func (f FormSubmission) GetFieldStatusClass(fieldName string) string {
|
func (f Submission) GetFieldStatusClass(fieldName string) string {
|
||||||
if f.IsSubmitted {
|
if f.IsSubmitted {
|
||||||
if f.FieldHasErrors(fieldName) {
|
if f.FieldHasErrors(fieldName) {
|
||||||
return "is-danger"
|
return "is-danger"
|
||||||
@ -70,12 +70,12 @@ func (f FormSubmission) GetFieldStatusClass(fieldName string) string {
|
|||||||
|
|
||||||
// IsDone indicates if the submission is considered done which is when it has been submitted
|
// IsDone indicates if the submission is considered done which is when it has been submitted
|
||||||
// and there are no errors.
|
// and there are no errors.
|
||||||
func (f FormSubmission) IsDone() bool {
|
func (f Submission) IsDone() bool {
|
||||||
return f.IsSubmitted && !f.HasErrors()
|
return f.IsSubmitted && !f.HasErrors()
|
||||||
}
|
}
|
||||||
|
|
||||||
// setErrorMessages sets errors messages on the submission for all fields that failed validation
|
// setErrorMessages sets errors messages on the submission for all fields that failed validation
|
||||||
func (f *FormSubmission) setErrorMessages(err error) {
|
func (f *Submission) setErrorMessages(err error) {
|
||||||
// Only this is supported right now
|
// Only this is supported right now
|
||||||
ves, ok := err.(validator.ValidationErrors)
|
ves, ok := err.(validator.ValidationErrors)
|
||||||
if !ok {
|
if !ok {
|
@ -1,8 +1,10 @@
|
|||||||
package controller
|
package form
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/labstack/echo/v4"
|
||||||
|
"github.com/mikestefanello/pagoda/pkg/services"
|
||||||
"github.com/mikestefanello/pagoda/pkg/tests"
|
"github.com/mikestefanello/pagoda/pkg/tests"
|
||||||
|
|
||||||
"github.com/stretchr/testify/assert"
|
"github.com/stretchr/testify/assert"
|
||||||
@ -13,16 +15,18 @@ func TestFormSubmission(t *testing.T) {
|
|||||||
type formTest struct {
|
type formTest struct {
|
||||||
Name string `validate:"required"`
|
Name string `validate:"required"`
|
||||||
Email string `validate:"required,email"`
|
Email string `validate:"required,email"`
|
||||||
Submission FormSubmission
|
Submission Submission
|
||||||
}
|
}
|
||||||
|
|
||||||
ctx, _ := tests.NewContext(c.Web, "/")
|
e := echo.New()
|
||||||
|
e.Validator = services.NewValidator()
|
||||||
|
ctx, _ := tests.NewContext(e, "/")
|
||||||
form := formTest{
|
form := formTest{
|
||||||
Name: "",
|
Name: "",
|
||||||
Email: "a@a.com",
|
Email: "a@a.com",
|
||||||
}
|
}
|
||||||
err := form.Submission.Process(ctx, form)
|
err := form.Submission.Process(ctx, form)
|
||||||
assert.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
assert.True(t, form.Submission.HasErrors())
|
assert.True(t, form.Submission.HasErrors())
|
||||||
assert.True(t, form.Submission.FieldHasErrors("Name"))
|
assert.True(t, form.Submission.FieldHasErrors("Name"))
|
@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/mikestefanello/pagoda/ent/user"
|
"github.com/mikestefanello/pagoda/ent/user"
|
||||||
"github.com/mikestefanello/pagoda/pkg/context"
|
"github.com/mikestefanello/pagoda/pkg/context"
|
||||||
"github.com/mikestefanello/pagoda/pkg/controller"
|
"github.com/mikestefanello/pagoda/pkg/controller"
|
||||||
|
"github.com/mikestefanello/pagoda/pkg/form"
|
||||||
"github.com/mikestefanello/pagoda/pkg/middleware"
|
"github.com/mikestefanello/pagoda/pkg/middleware"
|
||||||
"github.com/mikestefanello/pagoda/pkg/msg"
|
"github.com/mikestefanello/pagoda/pkg/msg"
|
||||||
"github.com/mikestefanello/pagoda/pkg/services"
|
"github.com/mikestefanello/pagoda/pkg/services"
|
||||||
@ -38,13 +39,13 @@ type (
|
|||||||
|
|
||||||
forgotPasswordForm struct {
|
forgotPasswordForm struct {
|
||||||
Email string `form:"email" validate:"required,email"`
|
Email string `form:"email" validate:"required,email"`
|
||||||
Submission controller.FormSubmission
|
Submission form.Submission
|
||||||
}
|
}
|
||||||
|
|
||||||
loginForm struct {
|
loginForm struct {
|
||||||
Email string `form:"email" validate:"required,email"`
|
Email string `form:"email" validate:"required,email"`
|
||||||
Password string `form:"password" validate:"required"`
|
Password string `form:"password" validate:"required"`
|
||||||
Submission controller.FormSubmission
|
Submission form.Submission
|
||||||
}
|
}
|
||||||
|
|
||||||
registerForm struct {
|
registerForm struct {
|
||||||
@ -52,13 +53,13 @@ type (
|
|||||||
Email string `form:"email" validate:"required,email"`
|
Email string `form:"email" validate:"required,email"`
|
||||||
Password string `form:"password" validate:"required"`
|
Password string `form:"password" validate:"required"`
|
||||||
ConfirmPassword string `form:"password-confirm" validate:"required,eqfield=Password"`
|
ConfirmPassword string `form:"password-confirm" validate:"required,eqfield=Password"`
|
||||||
Submission controller.FormSubmission
|
Submission form.Submission
|
||||||
}
|
}
|
||||||
|
|
||||||
resetPasswordForm struct {
|
resetPasswordForm struct {
|
||||||
Password string `form:"password" validate:"required"`
|
Password string `form:"password" validate:"required"`
|
||||||
ConfirmPassword string `form:"password-confirm" validate:"required,eqfield=Password"`
|
ConfirmPassword string `form:"password-confirm" validate:"required,eqfield=Password"`
|
||||||
Submission controller.FormSubmission
|
Submission form.Submission
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -99,42 +100,37 @@ func (c *Auth) ForgotPasswordPage(ctx echo.Context) error {
|
|||||||
page.Layout = templates.LayoutAuth
|
page.Layout = templates.LayoutAuth
|
||||||
page.Name = templates.PageForgotPassword
|
page.Name = templates.PageForgotPassword
|
||||||
page.Title = "Forgot password"
|
page.Title = "Forgot password"
|
||||||
page.Form = forgotPasswordForm{}
|
page.Form = form.Get[forgotPasswordForm](ctx)
|
||||||
|
|
||||||
if form := ctx.Get(context.FormKey); form != nil {
|
|
||||||
page.Form = form.(*forgotPasswordForm)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.RenderPage(ctx, page)
|
return c.RenderPage(ctx, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Auth) ForgotPasswordSubmit(ctx echo.Context) error {
|
func (c *Auth) ForgotPasswordSubmit(ctx echo.Context) error {
|
||||||
var form forgotPasswordForm
|
var input forgotPasswordForm
|
||||||
ctx.Set(context.FormKey, &form)
|
|
||||||
|
|
||||||
succeed := func() error {
|
succeed := func() error {
|
||||||
ctx.Set(context.FormKey, nil)
|
form.Clear(ctx)
|
||||||
msg.Success(ctx, "An email containing a link to reset your password will be sent to this address if it exists in our system.")
|
msg.Success(ctx, "An email containing a link to reset your password will be sent to this address if it exists in our system.")
|
||||||
return c.ForgotPasswordPage(ctx)
|
return c.ForgotPasswordPage(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the form values
|
// Set the form in context and parse the form values
|
||||||
if err := ctx.Bind(&form); err != nil {
|
if err := form.Set(ctx, &input); err != nil {
|
||||||
return c.Fail(err, "unable to parse forgot password form")
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := form.Submission.Process(ctx, form); err != nil {
|
if err := input.Submission.Process(ctx, input); err != nil {
|
||||||
return c.Fail(err, "unable to process form submission")
|
return c.Fail(err, "unable to process form submission")
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Submission.HasErrors() {
|
if input.Submission.HasErrors() {
|
||||||
return c.ForgotPasswordPage(ctx)
|
return c.ForgotPasswordPage(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to load the user
|
// Attempt to load the user
|
||||||
u, err := c.orm.User.
|
u, err := c.orm.User.
|
||||||
Query().
|
Query().
|
||||||
Where(user.Email(strings.ToLower(form.Email))).
|
Where(user.Email(strings.ToLower(input.Email))).
|
||||||
Only(ctx.Request().Context())
|
Only(ctx.Request().Context())
|
||||||
|
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
@ -174,43 +170,38 @@ func (c *Auth) LoginPage(ctx echo.Context) error {
|
|||||||
page.Layout = templates.LayoutAuth
|
page.Layout = templates.LayoutAuth
|
||||||
page.Name = templates.PageLogin
|
page.Name = templates.PageLogin
|
||||||
page.Title = "Log in"
|
page.Title = "Log in"
|
||||||
page.Form = loginForm{}
|
page.Form = form.Get[loginForm](ctx)
|
||||||
|
|
||||||
if form := ctx.Get(context.FormKey); form != nil {
|
|
||||||
page.Form = form.(*loginForm)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.RenderPage(ctx, page)
|
return c.RenderPage(ctx, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Auth) LoginSubmit(ctx echo.Context) error {
|
func (c *Auth) LoginSubmit(ctx echo.Context) error {
|
||||||
var form loginForm
|
var input loginForm
|
||||||
ctx.Set(context.FormKey, &form)
|
|
||||||
|
|
||||||
authFailed := func() error {
|
authFailed := func() error {
|
||||||
form.Submission.SetFieldError("Email", "")
|
input.Submission.SetFieldError("Email", "")
|
||||||
form.Submission.SetFieldError("Password", "")
|
input.Submission.SetFieldError("Password", "")
|
||||||
msg.Danger(ctx, "Invalid credentials. Please try again.")
|
msg.Danger(ctx, "Invalid credentials. Please try again.")
|
||||||
return c.LoginPage(ctx)
|
return c.LoginPage(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Parse the form values
|
// Set in context and parse the form values
|
||||||
if err := ctx.Bind(&form); err != nil {
|
if err := form.Set(ctx, &input); err != nil {
|
||||||
return c.Fail(err, "unable to parse login form")
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := form.Submission.Process(ctx, form); err != nil {
|
if err := input.Submission.Process(ctx, input); err != nil {
|
||||||
return c.Fail(err, "unable to process form submission")
|
return c.Fail(err, "unable to process form submission")
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Submission.HasErrors() {
|
if input.Submission.HasErrors() {
|
||||||
return c.LoginPage(ctx)
|
return c.LoginPage(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Attempt to load the user
|
// Attempt to load the user
|
||||||
u, err := c.orm.User.
|
u, err := c.orm.User.
|
||||||
Query().
|
Query().
|
||||||
Where(user.Email(strings.ToLower(form.Email))).
|
Where(user.Email(strings.ToLower(input.Email))).
|
||||||
Only(ctx.Request().Context())
|
Only(ctx.Request().Context())
|
||||||
|
|
||||||
switch err.(type) {
|
switch err.(type) {
|
||||||
@ -222,7 +213,7 @@ func (c *Auth) LoginSubmit(ctx echo.Context) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Check if the password is correct
|
// Check if the password is correct
|
||||||
err = c.auth.CheckPassword(form.Password, u.Password)
|
err = c.auth.CheckPassword(input.Password, u.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return authFailed()
|
return authFailed()
|
||||||
}
|
}
|
||||||
@ -251,34 +242,29 @@ func (c *Auth) RegisterPage(ctx echo.Context) error {
|
|||||||
page.Layout = templates.LayoutAuth
|
page.Layout = templates.LayoutAuth
|
||||||
page.Name = templates.PageRegister
|
page.Name = templates.PageRegister
|
||||||
page.Title = "Register"
|
page.Title = "Register"
|
||||||
page.Form = registerForm{}
|
page.Form = form.Get[registerForm](ctx)
|
||||||
|
|
||||||
if form := ctx.Get(context.FormKey); form != nil {
|
|
||||||
page.Form = form.(*registerForm)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.RenderPage(ctx, page)
|
return c.RenderPage(ctx, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Auth) RegisterSubmit(ctx echo.Context) error {
|
func (c *Auth) RegisterSubmit(ctx echo.Context) error {
|
||||||
var form registerForm
|
var input registerForm
|
||||||
ctx.Set(context.FormKey, &form)
|
|
||||||
|
|
||||||
// Parse the form values
|
// Set in context and parse the form values
|
||||||
if err := ctx.Bind(&form); err != nil {
|
if err := form.Set(ctx, &input); err != nil {
|
||||||
return c.Fail(err, "unable to parse register form")
|
return c.Fail(err, "unable to parse register form")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := form.Submission.Process(ctx, form); err != nil {
|
if err := input.Submission.Process(ctx, input); err != nil {
|
||||||
return c.Fail(err, "unable to process form submission")
|
return c.Fail(err, "unable to process form submission")
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Submission.HasErrors() {
|
if input.Submission.HasErrors() {
|
||||||
return c.RegisterPage(ctx)
|
return c.RegisterPage(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash the password
|
// Hash the password
|
||||||
pwHash, err := c.auth.HashPassword(form.Password)
|
pwHash, err := c.auth.HashPassword(input.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Fail(err, "unable to hash password")
|
return c.Fail(err, "unable to hash password")
|
||||||
}
|
}
|
||||||
@ -286,8 +272,8 @@ func (c *Auth) RegisterSubmit(ctx echo.Context) error {
|
|||||||
// Attempt creating the user
|
// Attempt creating the user
|
||||||
u, err := c.orm.User.
|
u, err := c.orm.User.
|
||||||
Create().
|
Create().
|
||||||
SetName(form.Name).
|
SetName(input.Name).
|
||||||
SetEmail(form.Email).
|
SetEmail(input.Email).
|
||||||
SetPassword(pwHash).
|
SetPassword(pwHash).
|
||||||
Save(ctx.Request().Context())
|
Save(ctx.Request().Context())
|
||||||
|
|
||||||
@ -347,34 +333,29 @@ func (c *Auth) ResetPasswordPage(ctx echo.Context) error {
|
|||||||
page.Layout = templates.LayoutAuth
|
page.Layout = templates.LayoutAuth
|
||||||
page.Name = templates.PageResetPassword
|
page.Name = templates.PageResetPassword
|
||||||
page.Title = "Reset password"
|
page.Title = "Reset password"
|
||||||
page.Form = resetPasswordForm{}
|
page.Form = form.Get[resetPasswordForm](ctx)
|
||||||
|
|
||||||
if form := ctx.Get(context.FormKey); form != nil {
|
|
||||||
page.Form = form.(*resetPasswordForm)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.RenderPage(ctx, page)
|
return c.RenderPage(ctx, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Auth) ResetPasswordSubmit(ctx echo.Context) error {
|
func (c *Auth) ResetPasswordSubmit(ctx echo.Context) error {
|
||||||
var form resetPasswordForm
|
var input resetPasswordForm
|
||||||
ctx.Set(context.FormKey, &form)
|
|
||||||
|
|
||||||
// Parse the form values
|
// Set in context and parse the form values
|
||||||
if err := ctx.Bind(&form); err != nil {
|
if err := form.Set(ctx, &input); err != nil {
|
||||||
return c.Fail(err, "unable to parse password reset form")
|
return c.Fail(err, "unable to parse password reset form")
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := form.Submission.Process(ctx, form); err != nil {
|
if err := input.Submission.Process(ctx, input); err != nil {
|
||||||
return c.Fail(err, "unable to process form submission")
|
return c.Fail(err, "unable to process form submission")
|
||||||
}
|
}
|
||||||
|
|
||||||
if form.Submission.HasErrors() {
|
if input.Submission.HasErrors() {
|
||||||
return c.ResetPasswordPage(ctx)
|
return c.ResetPasswordPage(ctx)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Hash the new password
|
// Hash the new password
|
||||||
hash, err := c.auth.HashPassword(form.Password)
|
hash, err := c.auth.HashPassword(input.Password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return c.Fail(err, "unable to hash password")
|
return c.Fail(err, "unable to hash password")
|
||||||
}
|
}
|
||||||
|
@ -4,8 +4,8 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/labstack/echo/v4"
|
"github.com/labstack/echo/v4"
|
||||||
"github.com/mikestefanello/pagoda/pkg/context"
|
|
||||||
"github.com/mikestefanello/pagoda/pkg/controller"
|
"github.com/mikestefanello/pagoda/pkg/controller"
|
||||||
|
"github.com/mikestefanello/pagoda/pkg/form"
|
||||||
"github.com/mikestefanello/pagoda/pkg/services"
|
"github.com/mikestefanello/pagoda/pkg/services"
|
||||||
"github.com/mikestefanello/pagoda/templates"
|
"github.com/mikestefanello/pagoda/templates"
|
||||||
)
|
)
|
||||||
@ -25,7 +25,7 @@ type (
|
|||||||
Email string `form:"email" validate:"required,email"`
|
Email string `form:"email" validate:"required,email"`
|
||||||
Department string `form:"department" validate:"required,oneof=sales marketing hr"`
|
Department string `form:"department" validate:"required,oneof=sales marketing hr"`
|
||||||
Message string `form:"message" validate:"required"`
|
Message string `form:"message" validate:"required"`
|
||||||
Submission controller.FormSubmission
|
Submission form.Submission
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -49,34 +49,29 @@ func (c *Contact) Page(ctx echo.Context) error {
|
|||||||
page.Layout = templates.LayoutMain
|
page.Layout = templates.LayoutMain
|
||||||
page.Name = templates.PageContact
|
page.Name = templates.PageContact
|
||||||
page.Title = "Contact us"
|
page.Title = "Contact us"
|
||||||
page.Form = contactForm{}
|
page.Form = form.Get[contactForm](ctx)
|
||||||
|
|
||||||
if form := ctx.Get(context.FormKey); form != nil {
|
|
||||||
page.Form = form.(*contactForm)
|
|
||||||
}
|
|
||||||
|
|
||||||
return c.RenderPage(ctx, page)
|
return c.RenderPage(ctx, page)
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Contact) Submit(ctx echo.Context) error {
|
func (c *Contact) Submit(ctx echo.Context) error {
|
||||||
var form contactForm
|
var input contactForm
|
||||||
ctx.Set(context.FormKey, &form)
|
|
||||||
|
|
||||||
// Parse the form values
|
// Store in context and parse the form values
|
||||||
if err := ctx.Bind(&form); err != nil {
|
if err := form.Set(ctx, &input); err != nil {
|
||||||
return c.Fail(err, "unable to bind form")
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := form.Submission.Process(ctx, form); err != nil {
|
if err := input.Submission.Process(ctx, input); err != nil {
|
||||||
return c.Fail(err, "unable to process form submission")
|
return c.Fail(err, "unable to process form submission")
|
||||||
}
|
}
|
||||||
|
|
||||||
if !form.Submission.HasErrors() {
|
if !input.Submission.HasErrors() {
|
||||||
err := c.mail.
|
err := c.mail.
|
||||||
Compose().
|
Compose().
|
||||||
To(form.Email).
|
To(input.Email).
|
||||||
Subject("Contact form submitted").
|
Subject("Contact form submitted").
|
||||||
Body(fmt.Sprintf("The message is: %s", form.Message)).
|
Body(fmt.Sprintf("The message is: %s", input.Message)).
|
||||||
Send(ctx)
|
Send(ctx)
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
Loading…
Reference in New Issue
Block a user