Renamed controllers package to routes.

This commit is contained in:
mikestefanello 2021-12-14 11:13:53 -05:00
parent 60dedc0944
commit 869c507737
11 changed files with 12 additions and 12 deletions

23
routes/about.go Normal file
View file

@ -0,0 +1,23 @@
package routes
import (
"goweb/controller"
"github.com/labstack/echo/v4"
)
type About struct {
controller.Controller
}
func (a *About) Get(c echo.Context) error {
p := controller.NewPage(c)
p.Layout = "main"
p.Name = "about"
p.Title = "About"
p.Data = "This is the about page"
p.Cache.Enabled = true
p.Cache.Tags = []string{"page_about", "page:list"}
return a.RenderPage(c, p)
}

20
routes/about_test.go Normal file
View file

@ -0,0 +1,20 @@
package routes
import (
"net/http"
"testing"
"github.com/stretchr/testify/assert"
)
func TestAbout_Get(t *testing.T) {
doc := request(t).
setRoute("about").
get().
assertStatusCode(http.StatusOK).
toDoc()
h1 := doc.Find("h1.title")
assert.Len(t, h1.Nodes, 1)
assert.Equal(t, "About", h1.Text())
}

27
routes/contact.go Normal file
View file

@ -0,0 +1,27 @@
package routes
import (
"goweb/controller"
"goweb/msg"
"github.com/labstack/echo/v4"
)
type Contact struct {
controller.Controller
}
func (a *Contact) Get(c echo.Context) error {
p := controller.NewPage(c)
p.Layout = "main"
p.Name = "contact"
p.Title = "Contact us"
p.Data = "This is the contact page"
return a.RenderPage(c, p)
}
func (a *Contact) Post(c echo.Context) error {
msg.Success(c, "Thank you for contacting us!")
msg.Info(c, "We will respond to you shortly.")
return a.Redirect(c, "home")
}

110
routes/controllers_test.go Normal file
View file

@ -0,0 +1,110 @@
package routes
import (
"net/http"
"net/http/httptest"
"net/url"
"os"
"testing"
"goweb/config"
"goweb/container"
"github.com/PuerkitoBio/goquery"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
var (
srv *httptest.Server
c *container.Container
)
func TestMain(m *testing.M) {
// Set the environment to test
if err := os.Setenv("APP_ENV", string(config.EnvTest)); err != nil {
panic(err)
}
// Start a test HTTP server
c = container.NewContainer()
BuildRouter(c)
srv = httptest.NewServer(c.Web)
exitVal := m.Run()
srv.Close()
os.Exit(exitVal)
}
type httpRequest struct {
route string
client http.Client
body url.Values
t *testing.T
}
func request(t *testing.T) *httpRequest {
r := httpRequest{
t: t,
}
return &r
}
func (h *httpRequest) setClient(client http.Client) *httpRequest {
h.client = client
return h
}
func (h *httpRequest) setRoute(route string, params ...interface{}) *httpRequest {
h.route = srv.URL + c.Web.Reverse(route, params)
return h
}
func (h *httpRequest) setBody(body url.Values) *httpRequest {
h.body = body
return h
}
func (h *httpRequest) get() *httpResponse {
resp, err := h.client.Get(h.route)
require.NoError(h.t, err)
r := httpResponse{
t: h.t,
Response: resp,
}
return &r
}
func (h *httpRequest) post() *httpResponse {
resp, err := h.client.PostForm(h.route, h.body)
require.NoError(h.t, err)
r := httpResponse{
t: h.t,
Response: resp,
}
return &r
}
type httpResponse struct {
*http.Response
t *testing.T
}
func (h *httpResponse) assertStatusCode(code int) *httpResponse {
assert.Equal(h.t, code, h.Response.StatusCode)
return h
}
func (h *httpResponse) assertRedirect(t *testing.T, destination string) *httpResponse {
assert.Equal(t, destination, h.Header.Get("Location"))
return h
}
func (h *httpResponse) toDoc() *goquery.Document {
doc, err := goquery.NewDocumentFromReader(h.Body)
require.NoError(h.t, err)
err = h.Body.Close()
assert.NoError(h.t, err)
return doc
}

40
routes/error.go Normal file
View file

@ -0,0 +1,40 @@
package routes
import (
"net/http"
"goweb/controller"
"github.com/labstack/echo/v4"
)
type Error struct {
controller.Controller
}
func (e *Error) Get(err error, c echo.Context) {
if c.Response().Committed {
return
}
code := http.StatusInternalServerError
if he, ok := err.(*echo.HTTPError); ok {
code = he.Code
}
if code >= 500 {
c.Logger().Error(err)
} else {
c.Logger().Info(err)
}
p := controller.NewPage(c)
p.Layout = "main"
p.Title = http.StatusText(code)
p.Name = "error"
p.StatusCode = code
if err = e.RenderPage(c, p); err != nil {
c.Logger().Error(err)
}
}

22
routes/home.go Normal file
View file

@ -0,0 +1,22 @@
package routes
import (
"goweb/controller"
"github.com/labstack/echo/v4"
)
type Home struct {
controller.Controller
}
func (h *Home) Get(c echo.Context) error {
p := controller.NewPage(c)
p.Layout = "main"
p.Name = "home"
p.Data = "Hello world"
p.Metatags.Description = "Welcome to the homepage."
p.Metatags.Keywords = []string{"Go", "MVC", "Web", "Software"}
return h.RenderPage(c, p)
}

85
routes/login.go Normal file
View file

@ -0,0 +1,85 @@
package routes
import (
"fmt"
"goweb/auth"
"goweb/controller"
"goweb/ent"
"goweb/ent/user"
"goweb/msg"
"github.com/labstack/echo/v4"
)
type (
Login struct {
controller.Controller
form LoginForm
}
LoginForm struct {
Username string `form:"username" validate:"required"`
Password string `form:"password" validate:"required"`
}
)
func (l *Login) Get(c echo.Context) error {
p := controller.NewPage(c)
p.Layout = "auth"
p.Name = "login"
p.Title = "Log in"
p.Data = l.form
return l.RenderPage(c, p)
}
func (l *Login) Post(c echo.Context) error {
fail := func(message string, err error) error {
c.Logger().Errorf("%s: %v", message, err)
msg.Danger(c, "An error occurred. Please try again.")
return l.Get(c)
}
// Parse the form values
if err := c.Bind(&l.form); err != nil {
return fail("unable to parse login form", err)
}
// Validate the form
if err := c.Validate(l.form); err != nil {
msg.Danger(c, "All fields are required.")
return l.Get(c)
}
// Attempt to load the user
u, err := l.Container.ORM.User.
Query().
Where(user.Username(l.form.Username)).
First(c.Request().Context())
if err != nil {
switch err.(type) {
case *ent.NotFoundError:
msg.Danger(c, "Invalid credentials. Please try again.")
return l.Get(c)
default:
return fail("error querying user during login", err)
}
}
// Check if the password is correct
err = auth.CheckPassword(l.form.Password, u.Password)
if err != nil {
msg.Danger(c, "Invalid credentials. Please try again.")
return l.Get(c)
}
// Log the user in
err = auth.Login(c, u.ID)
if err != nil {
return fail("unable to log in user", err)
}
msg.Success(c, fmt.Sprintf("Welcome back, %s. You are now logged in.", u.Username))
return l.Redirect(c, "home")
}

20
routes/logout.go Normal file
View file

@ -0,0 +1,20 @@
package routes
import (
"goweb/auth"
"goweb/controller"
"goweb/msg"
"github.com/labstack/echo/v4"
)
type Logout struct {
controller.Controller
}
func (l *Logout) Get(c echo.Context) error {
if err := auth.Logout(c); err == nil {
msg.Success(c, "You have been logged out successfully.")
}
return l.Redirect(c, "home")
}

77
routes/register.go Normal file
View file

@ -0,0 +1,77 @@
package routes
import (
"goweb/auth"
"goweb/controller"
"goweb/msg"
"github.com/labstack/echo/v4"
)
type (
Register struct {
controller.Controller
form RegisterForm
}
RegisterForm struct {
Username string `form:"username" validate:"required"`
Password string `form:"password" validate:"required"`
}
)
func (r *Register) Get(c echo.Context) error {
p := controller.NewPage(c)
p.Layout = "auth"
p.Name = "register"
p.Title = "Register"
p.Data = r.form
return r.RenderPage(c, p)
}
func (r *Register) Post(c echo.Context) error {
fail := func(message string, err error) error {
c.Logger().Errorf("%s: %v", message, err)
msg.Danger(c, "An error occurred. Please try again.")
return r.Get(c)
}
// Parse the form values
if err := c.Bind(&r.form); err != nil {
return fail("unable to parse form values", err)
}
// Validate the form
if err := c.Validate(r.form); err != nil {
msg.Danger(c, "All fields are required.")
return r.Get(c)
}
// Hash the password
pwHash, err := auth.HashPassword(r.form.Password)
if err != nil {
return fail("unable to hash password", err)
}
// Attempt creating the user
u, err := r.Container.ORM.User.
Create().
SetUsername(r.form.Username).
SetPassword(pwHash).
Save(c.Request().Context())
if err != nil {
return fail("unable to create user", err)
}
c.Logger().Infof("user created: %s", u.Username)
err = auth.Login(c, u.ID)
if err != nil {
// TODO
}
msg.Info(c, "Your account has been created. You are now logged in.")
return r.Redirect(c, "home")
}

102
routes/router.go Normal file
View file

@ -0,0 +1,102 @@
package routes
import (
"net/http"
"goweb/config"
"goweb/controller"
"goweb/middleware"
"github.com/go-playground/validator/v10"
"github.com/gorilla/sessions"
"github.com/labstack/echo-contrib/session"
"github.com/labstack/echo/v4"
echomw "github.com/labstack/echo/v4/middleware"
"goweb/container"
)
type Validator struct {
validator *validator.Validate
}
func (v *Validator) Validate(i interface{}) error {
if err := v.validator.Struct(i); err != nil {
return err
}
return nil
}
// TODO: This is doing more than building the router
func BuildRouter(c *container.Container) {
// Static files with proper cache control
// funcmap.File() should be used in templates to append a cache key to the URL in order to break cache
// after each server restart
c.Web.Group("", middleware.CacheControl(c.Config.Cache.Expiration.StaticFile)).
Static(config.StaticPrefix, config.StaticDir)
// Middleware
g := c.Web.Group("",
echomw.RemoveTrailingSlashWithConfig(echomw.TrailingSlashConfig{
RedirectCode: http.StatusMovedPermanently,
}),
echomw.Recover(),
echomw.Secure(),
echomw.RequestID(),
echomw.Gzip(),
echomw.Logger(),
middleware.LogRequestID(),
echomw.TimeoutWithConfig(echomw.TimeoutConfig{
Timeout: c.Config.App.Timeout,
}),
middleware.PageCache(c.Cache),
session.Middleware(sessions.NewCookieStore([]byte(c.Config.App.EncryptionKey))),
echomw.CSRFWithConfig(echomw.CSRFConfig{
TokenLookup: "form:csrf",
}),
middleware.LoadAuthenticatedUser(c.ORM),
)
// Base controller
ctr := controller.NewController(c)
// Error handler
err := Error{Controller: ctr}
c.Web.HTTPErrorHandler = err.Get
// Validator
c.Web.Validator = &Validator{validator: validator.New()}
// Routes
navRoutes(g, ctr)
userRoutes(g, ctr)
}
func navRoutes(g *echo.Group, ctr controller.Controller) {
home := Home{Controller: ctr}
g.GET("/", home.Get).Name = "home"
about := About{Controller: ctr}
g.GET("/about", about.Get).Name = "about"
contact := Contact{Controller: ctr}
g.GET("/contact", contact.Get).Name = "contact"
g.POST("/contact", contact.Post).Name = "contact.post"
}
func userRoutes(g *echo.Group, ctr controller.Controller) {
logout := Logout{Controller: ctr}
g.GET("/logout", logout.Get, middleware.RequireAuthentication()).Name = "logout"
noAuth := g.Group("/user", middleware.RequireNoAuthentication())
login := Login{Controller: ctr}
noAuth.GET("/login", login.Get).Name = "login"
noAuth.POST("/login", login.Post).Name = "login.post"
register := Register{Controller: ctr}
noAuth.GET("/register", register.Get).Name = "register"
noAuth.POST("/register", register.Post).Name = "register.post"
}