Renamed controllers package to routes.
This commit is contained in:
parent
60dedc0944
commit
869c507737
11 changed files with 12 additions and 12 deletions
23
routes/about.go
Normal file
23
routes/about.go
Normal 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
20
routes/about_test.go
Normal 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
27
routes/contact.go
Normal 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
110
routes/controllers_test.go
Normal 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
40
routes/error.go
Normal 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
22
routes/home.go
Normal 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
85
routes/login.go
Normal 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
20
routes/logout.go
Normal 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
77
routes/register.go
Normal 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
102
routes/router.go
Normal 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"
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue