domain validation

This commit is contained in:
Felipe M. 2024-06-08 09:39:37 +02:00
parent 64f533d588
commit b79340afa4
No known key found for this signature in database
GPG key ID: CCFBC5637D4000A8
4 changed files with 58 additions and 18 deletions

View file

@ -30,7 +30,10 @@ func (d *AccountsDomain) ListAccounts(ctx context.Context) ([]model.AccountDTO,
}
func (d *AccountsDomain) CreateAccount(ctx context.Context, account model.AccountDTO) (*model.AccountDTO, error) {
// Hash password with bcrypt
if err := account.IsValidCreate(); err != nil {
return nil, err
}
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(account.Password), 10)
if err != nil {
return nil, fmt.Errorf("error hashing provided password: %w", err)
@ -71,6 +74,10 @@ func (d *AccountsDomain) DeleteAccount(ctx context.Context, id int) error {
}
func (d *AccountsDomain) UpdateAccount(ctx context.Context, account model.AccountDTO) (*model.AccountDTO, error) {
if err := account.IsValidUpdate(); err != nil {
return nil, err
}
// Get account from database
storedAccount, _, err := d.deps.Database.GetAccount(ctx, account.ID)
if errors.Is(err, database.ErrNotFound) {

View file

@ -2,7 +2,6 @@ package api_v1
import (
"errors"
"fmt"
"net/http"
"strconv"
@ -57,21 +56,11 @@ func (r *AccountsAPIRoutes) listHandler(c *gin.Context) {
}
type createAccountPayload struct {
Username string `json:"username" validate:"required"`
Password string `json:"password" validate:"required"`
Username string `json:"username"`
Password string `json:"password"`
IsVisitor bool `json:"is_visitor"`
}
func (p *createAccountPayload) IsValid() error {
if p.Username == "" {
return fmt.Errorf("username should not be empty")
}
if p.Password == "" {
return fmt.Errorf("password should not be empty")
}
return nil
}
func (p *createAccountPayload) ToAccountDTO() model.AccountDTO {
return model.AccountDTO{
Username: p.Username,
@ -97,13 +86,12 @@ func (r *AccountsAPIRoutes) createHandler(c *gin.Context) {
return
}
if err := payload.IsValid(); err != nil {
r.logger.WithError(err).Error("error validating payload")
account, err := r.deps.Domains.Accounts.CreateAccount(c.Request.Context(), payload.ToAccountDTO())
if err, isValidationErr := err.(model.ValidationError); isValidationErr {
response.SendError(c, http.StatusBadRequest, err.Error())
return
}
account, err := r.deps.Domains.Accounts.CreateAccount(c.Request.Context(), payload.ToAccountDTO())
if err != nil {
r.logger.WithError(err).Error("error creating account")
c.AbortWithStatus(http.StatusInternalServerError)
@ -172,6 +160,11 @@ func (r *AccountsAPIRoutes) updateHandler(c *gin.Context) {
payload.ID = model.DBID(accountID)
account, err := r.deps.Domains.Accounts.UpdateAccount(c.Request.Context(), payload)
if err, isValidationErr := err.(model.ValidationError); isValidationErr {
response.SendError(c, http.StatusBadRequest, err.Error())
return
}
if err != nil {
r.logger.WithError(err).Error("error updating account")
c.AbortWithStatus(http.StatusInternalServerError)

View file

@ -61,7 +61,7 @@ func (a Account) ToDTO() AccountDTO {
type AccountDTO struct {
ID DBID `json:"id"`
Username string `json:"username"`
Password string `json:"-"` // Used only to store, not to retrieve
Password string `json:"passowrd,omitempty"` // Used only to store, not to retrieve
Owner *bool `json:"owner"`
Config *UserConfig `json:"config"`
}
@ -69,3 +69,23 @@ type AccountDTO struct {
func (adto *AccountDTO) IsOwner() bool {
return adto.Owner != nil && *adto.Owner
}
func (adto *AccountDTO) IsValidCreate() error {
if adto.Username == "" {
return NewValidationError("username", "username should not be empty")
}
if adto.Password == "" {
return NewValidationError("password", "password should not be empty")
}
return nil
}
func (adto *AccountDTO) IsValidUpdate() error {
if adto.Username == "" && adto.Password == "" && adto.Owner == nil && adto.Config == nil {
return NewValidationError("account", "no fields to update")
}
return nil
}

View file

@ -0,0 +1,20 @@
package model
// ValidationError represents a validation error.
// This errors are used in the domain layer to indicate an error that is caused generally
// by the user and has to be sent back via the API or appropriate channel.
type ValidationError struct {
Field string `json:"field"`
Message string `json:"message"`
}
func (v ValidationError) Error() string {
return v.Message
}
func NewValidationError(field, message string) ValidationError {
return ValidationError{
Field: field,
Message: message,
}
}