~netlandish/gobwebs

e3bd294b3ba11b92849ae776fb6ef1413ede1b23 — Peter Sanchez 3 months ago 9f6e2ce
Adding BaseService for convenience
2 files changed, 76 insertions(+), 44 deletions(-)

M accounts/routes.go
A server/service.go
M accounts/routes.go => accounts/routes.go +36 -44
@@ 19,35 19,29 @@ import (

// Service is the base accounts service struct
type Service struct {
	name  string
	server.BaseService
	fetch gobwebs.UserFetch
	eg    *echo.Group
}

// RegisterRoutes ...
func (s *Service) RegisterRoutes() {
	s.eg.GET("/login", s.Login).Name = s.RouteName("login")
	s.eg.POST("/login", s.LoginPOST).Name = s.RouteName("login_post")
	s.eg.GET("/logout", s.Logout).Name = s.RouteName("logout")
	s.eg.GET("/login/email/:key", s.LoginEmailConf).Name = s.RouteName("login_email")
	s.eg.GET("/forgot-password", s.ForgotPassword).Name = s.RouteName("forgot_password")
	s.eg.POST("/forgot-password", s.ForgotPasswordPOST).Name = s.RouteName("forgot_password_post")
	s.eg.GET("/forgot-password/:key", s.ForgotPasswordConf).Name = s.RouteName("forgot_password_conf")
	s.eg.POST("/forgot-password/:key", s.ForgotPasswordConfPOST).Name = s.RouteName("forgot_password_conf_post")
	s.eg.GET("/confirm/:key", s.ConfirmEmailConf).Name = s.RouteName("confirm_email")
	s.Group.GET("/login", s.Login).Name = s.RouteName("login")
	s.Group.POST("/login", s.LoginPOST).Name = s.RouteName("login_post")
	s.Group.GET("/logout", s.Logout).Name = s.RouteName("logout")
	s.Group.GET("/login/email/:key", s.LoginEmailConf).Name = s.RouteName("login_email")
	s.Group.GET("/forgot-password", s.ForgotPassword).Name = s.RouteName("forgot_password")
	s.Group.POST("/forgot-password", s.ForgotPasswordPOST).Name = s.RouteName("forgot_password_post")
	s.Group.GET("/forgot-password/:key", s.ForgotPasswordConf).Name = s.RouteName("forgot_password_conf")
	s.Group.POST("/forgot-password/:key", s.ForgotPasswordConfPOST).Name = s.RouteName("forgot_password_conf_post")
	s.Group.GET("/confirm/:key", s.ConfirmEmailConf).Name = s.RouteName("confirm_email")

	// Login required to use the following
	s.eg.Use(auth.AuthRequired())
	s.eg.GET("/change-password", s.ChangePassword).Name = s.RouteName("change_password")
	s.eg.POST("/change-password", s.ChangePasswordPOST).Name = s.RouteName("change_password_post")
	s.eg.GET("/update-email", s.UpdateEmail).Name = s.RouteName("update_email")
	s.eg.POST("/update-email", s.UpdateEmailPOST).Name = s.RouteName("update_email_post")
	s.eg.GET("/update-email/:key", s.UpdateEmailConf).Name = s.RouteName("update_email_conf")
}

// RouteName ...
func (s *Service) RouteName(value string) string {
	return fmt.Sprintf("%s:%s", s.name, value)
	s.Group.Use(auth.AuthRequired())
	s.Group.GET("/change-password", s.ChangePassword).Name = s.RouteName("change_password")
	s.Group.POST("/change-password", s.ChangePasswordPOST).Name = s.RouteName("change_password_post")
	s.Group.GET("/update-email", s.UpdateEmail).Name = s.RouteName("update_email")
	s.Group.POST("/update-email", s.UpdateEmailPOST).Name = s.RouteName("update_email_post")
	s.Group.GET("/update-email/:key", s.UpdateEmailConf).Name = s.RouteName("update_email_conf")
}

// Logout ...


@@ 80,11 74,10 @@ func (s *Service) Login(c echo.Context) error {

// LoginAuth ...
func (s *Service) LoginAuth(c echo.Context) error {
	gctx := c.(*server.Context)
	next := c.QueryParam("next")
	gmap := s.fetch.LoginAuthTemplateVars(c)
	gmap["next"] = next
	return gctx.Render(http.StatusOK, "login.html", gmap)
	return s.Render(c, http.StatusOK, "login.html", gmap)
}

// LoginEmail ...


@@ 97,7 90,7 @@ func (s *Service) LoginEmail(c echo.Context) error {
	sent := c.QueryParam("sent")
	gmap := s.fetch.LoginEmailTemplateVars(c)
	gmap["sent"] = sent
	return gctx.Render(http.StatusOK, "login_email.html", gmap)
	return s.Render(c, http.StatusOK, "login_email.html", gmap)
}

// LoginPOST ...


@@ 123,7 116,7 @@ func (s *Service) LoginAuthPOST(c echo.Context) error {
		case validate.InputErrors:
			gmap["errors"] = err
			gmap["form"] = form
			return gctx.Render(http.StatusOK, "login.html", gmap)
			return s.Render(c, http.StatusOK, "login.html", gmap)
		default:
			// Raise ISE if error is unrelated to input validation
			return err


@@ 164,7 157,7 @@ func (s *Service) LoginEmailPOST(c echo.Context) error {
		case validate.InputErrors:
			gmap["errors"] = err
			gmap["form"] = form
			return gctx.Render(http.StatusOK, "login_email.html", gmap)
			return s.Render(c, http.StatusOK, "login_email.html", gmap)
		default:
			// Raise ISE if error is unrelated to input validation
			return err


@@ 269,9 262,8 @@ func (s *Service) LoginEmailConf(c echo.Context) error {

// ChangePassword changes the password for an authenticated user
func (s *Service) ChangePassword(c echo.Context) error {
	gctx := c.(*server.Context)
	gmap := s.fetch.ChangePasswordTemplateVars(c)
	return gctx.Render(http.StatusOK, "change_password.html", gmap)
	return s.Render(c, http.StatusOK, "change_password.html", gmap)
}

// ChangePasswordPOST changes the password for an authenticated user


@@ 284,7 276,7 @@ func (s *Service) ChangePasswordPOST(c echo.Context) error {
		case validate.InputErrors:
			gmap["errors"] = err
			gmap["form"] = form
			return gctx.Render(http.StatusOK, "change_password.html", gmap)
			return s.Render(c, http.StatusOK, "change_password.html", gmap)
		default:
			// Raise ISE if error is unrelated to input validation
			return err


@@ 307,7 299,7 @@ func (s *Service) ChangePasswordPOST(c echo.Context) error {
	}

	gmap["form"] = form
	return gctx.Render(http.StatusOK, "change_password.html", gmap)
	return s.Render(c, http.StatusOK, "change_password.html", gmap)
}

// ForgotPassword ...


@@ 325,7 317,7 @@ func (s *Service) ForgotPassword(c echo.Context) error {
	sent := c.QueryParam("sent")
	gmap := s.fetch.ForgotPasswordTemplateVars(c)
	gmap["sent"] = sent
	return gctx.Render(http.StatusOK, "forgot_password.html", gmap)
	return s.Render(c, http.StatusOK, "forgot_password.html", gmap)
}

// ForgotPasswordPOST ...


@@ 342,7 334,7 @@ func (s *Service) ForgotPasswordPOST(c echo.Context) error {
		case validate.InputErrors:
			gmap["errors"] = err
			gmap["form"] = form
			return gctx.Render(http.StatusOK, "forgot_password.html", gmap)
			return s.Render(c, http.StatusOK, "forgot_password.html", gmap)
		default:
			// Raise ISE if error is unrelated to input validation
			return err


@@ 429,7 421,6 @@ func (s *Service) ForgotPasswordConf(c echo.Context) error {
		return echo.NotFoundHandler(c)
	}

	gctx := c.(*server.Context)
	conf, err := GetConfirmation(c.Request().Context(), key)
	if err != nil {
		if err == sql.ErrNoRows {


@@ 460,7 451,7 @@ func (s *Service) ForgotPasswordConf(c echo.Context) error {
	gmap := s.fetch.ForgotPasswordConfTemplateVars(c)
	gmap["guser"] = user
	gmap["key"] = key
	return gctx.Render(http.StatusOK, "forgot_password_conf.html", gmap)
	return s.Render(c, http.StatusOK, "forgot_password_conf.html", gmap)
}

// ForgotPasswordConfPOST changes the password for an authenticated user


@@ 470,7 461,6 @@ func (s *Service) ForgotPasswordConfPOST(c echo.Context) error {
		return echo.NotFoundHandler(c)
	}

	gctx := c.(*server.Context)
	conf, err := GetConfirmation(c.Request().Context(), key)
	if err != nil {
		if err == sql.ErrNoRows {


@@ 502,7 492,7 @@ func (s *Service) ForgotPasswordConfPOST(c echo.Context) error {
			gmap["form"] = form
			gmap["guser"] = user
			gmap["key"] = key
			return gctx.Render(http.StatusOK, "forgot_password_conf.html", gobwebs.Map{
			return s.Render(c, http.StatusOK, "forgot_password_conf.html", gobwebs.Map{
				"errors": err,
				"form":   form,
				"guser":  user,


@@ 533,7 523,7 @@ func (s *Service) ForgotPasswordConfPOST(c echo.Context) error {
		return c.Redirect(http.StatusMovedPermanently, next)
	}

	return gctx.Render(http.StatusOK, "forgot_password_conf_done.html", gmap)
	return s.Render(c, http.StatusOK, "forgot_password_conf_done.html", gmap)
}

// ConfirmEmailConf confirms a users email and marks them as verified


@@ 607,7 597,7 @@ func (s *Service) UpdateEmail(c echo.Context) error {
	sent := c.QueryParam("sent")
	gmap := s.fetch.UpdateEmailTemplateVars(c)
	gmap["sent"] = sent
	return gctx.Render(http.StatusOK, "update_email.html", gmap)
	return s.Render(c, http.StatusOK, "update_email.html", gmap)
}

// UpdateEmailPOST changes the password for an authenticated user


@@ 624,7 614,7 @@ func (s *Service) UpdateEmailPOST(c echo.Context) error {
		case validate.InputErrors:
			gmap["errors"] = err
			gmap["form"] = form
			return gctx.Render(http.StatusOK, "update_email.html", gmap)
			return s.Render(c, http.StatusOK, "update_email.html", gmap)
		default:
			// Raise ISE if error is unrelated to input validation
			return err


@@ 641,7 631,7 @@ func (s *Service) UpdateEmailPOST(c echo.Context) error {
		nerr := fmt.Errorf("This email is currently registered to an existing account")
		gmap["errors"] = validate.GetInputErrors([]error{nerr})
		gmap["form"] = form
		return gctx.Render(http.StatusOK, "update_email.html", gmap)
		return s.Render(c, http.StatusOK, "update_email.html", gmap)
	}

	// Email is not in our system. Verify there is no pending


@@ 691,7 681,7 @@ func (s *Service) UpdateEmailPOST(c echo.Context) error {
		nerr := fmt.Errorf("This email is currently pending confirmation for another account")
		gmap["errors"] = validate.GetInputErrors([]error{nerr})
		gmap["form"] = form
		return gctx.Render(http.StatusOK, "update_email.html", gmap)
		return s.Render(c, http.StatusOK, "update_email.html", gmap)
	}

	// All looks good. Send confirmation email


@@ 794,7 784,9 @@ func (s *Service) UpdateEmailConf(c echo.Context) error {
}

// NewService return service
func NewService(eg *echo.Group, name string, fetch gobwebs.UserFetch) *Service {
	service := &Service{name: name, fetch: fetch, eg: eg}
func NewService(eg *echo.Group, name string,
	fetch gobwebs.UserFetch, render validate.TemplateRenderFunc) *Service {
	baseService := server.NewService(eg, name, render)
	service := &Service{BaseService: baseService, fetch: fetch}
	return service
}

A server/service.go => server/service.go +40 -0
@@ 0,0 1,40 @@
package server

import (
	"fmt"

	"github.com/labstack/echo/v4"
	"netlandish.com/x/gobwebs/validate"
)

// BaseService is can be used as the base for a WebService interface
type BaseService struct {
	Name  string
	Group *echo.Group

	RenderFunc validate.TemplateRenderFunc
}

func (b *BaseService) RegisterRoutes() {
	panic(fmt.Errorf("not implemented"))
}

func (b *BaseService) RouteName(value string) string {
	return fmt.Sprintf("%s:%s", b.Name, value)
}

func (b *BaseService) Render(c echo.Context, code int, name string, data interface{}) error {
	if b.RenderFunc != nil {
		return b.RenderFunc(c, code, name, data)
	}
	gctx := c.(*Context)
	return gctx.Render(code, name, data)
}

func NewService(eg *echo.Group, name string, render validate.TemplateRenderFunc) BaseService {
	return BaseService{
		Name:       name,
		Group:      eg,
		RenderFunc: render,
	}
}