diff --git a/README.md b/README.md
index e1e9b60..3804d05 100644
--- a/README.md
+++ b/README.md
@@ -401,19 +401,19 @@ These patterns are not required, but were designed to make development as easy a
To declare a new route that will have methods to handle a GET and POST request, for example, start with a new _struct_ type, that embeds the `Controller`:
```go
-type Home struct {
+type home struct {
controller.Controller
}
-func (c *Home) Get(ctx echo.Context) error {}
+func (c *home) Get(ctx echo.Context) error {}
-func (c *Home) Post(ctx echo.Context) error {}
+func (c *home) Post(ctx echo.Context) error {}
```
Then create the route and add to the router:
```go
-home := Home{Controller: controller.NewController(c)}
+home := home{Controller: controller.NewController(c)}
g.GET("/", home.Get).Name = "home"
g.POST("/", home.Post).Name = "home.post"
```
@@ -485,7 +485,7 @@ As you develop your application, the `Page` can be easily extended to include wh
Initializing a new page is simple:
```go
-func (c *Home) Get(ctx echo.Context) error {
+func (c *home) Get(ctx echo.Context) error {
page := controller.NewPage(ctx)
}
```
@@ -814,7 +814,7 @@ If [CSRF](#csrf) protection is enabled, the token value will automatically be pa
Once your `Page` is fully built, rendering it via the embedded `Controller` in your _route_ can be done simply by calling `RenderPage()`:
```go
-func (c *Home) Get(ctx echo.Context) error {
+func (c *home) Get(ctx echo.Context) error {
page := controller.NewPage(ctx)
page.Layout = "main"
page.Name = "home"
@@ -1191,7 +1191,7 @@ To use _Let's Encrypt_ follow [this guide](https://echo.labstack.com/cookbook/au
Logging is provided by [Echo](https://echo.labstack.com/guide/customization/#logging) and is accessible within the _Echo_ instance, which is located in the `Web` field of the `Container`, or within any of the _context_ parameters, for example:
```go
-func (c *Home) Get(ctx echo.Context) error {
+func (c *home) Get(ctx echo.Context) error {
ctx.Logger().Info("something happened")
if err := someOperation(); err != nil {
diff --git a/config/config.go b/config/config.go
index 4e7b5b2..f5c10eb 100644
--- a/config/config.go
+++ b/config/config.go
@@ -21,21 +21,32 @@ const (
StaticPrefix = "files"
)
-type Environment string
+type environment string
const (
- EnvLocal Environment = "local"
- EnvTest Environment = "test"
- EnvDevelop Environment = "dev"
- EnvStaging Environment = "staging"
- EnvQA Environment = "qa"
- EnvProduction Environment = "prod"
+ // EnvLocal represents the local environment
+ EnvLocal environment = "local"
+
+ // EnvTest represents the test environment
+ EnvTest environment = "test"
+
+ // EnvDevelop represents the development environment
+ EnvDevelop environment = "dev"
+
+ // EnvStaging represents the staging environment
+ EnvStaging environment = "staging"
+
+ // EnvQA represents the qa environment
+ EnvQA environment = "qa"
+
+ // EnvProduction represents the production environment
+ EnvProduction environment = "prod"
)
// SwitchEnvironment sets the environment variable used to dictate which environment the application is
// currently running in.
// This must be called prior to loading the configuration in order for it to take effect.
-func SwitchEnvironment(env Environment) {
+func SwitchEnvironment(env environment) {
if err := os.Setenv("APP_ENVIRONMENT", string(env)); err != nil {
panic(err)
}
@@ -68,7 +79,7 @@ type (
// AppConfig stores application configuration
AppConfig struct {
Name string `env:"APP_NAME,default=Pagoda"`
- Environment Environment `env:"APP_ENVIRONMENT,default=local"`
+ Environment environment `env:"APP_ENVIRONMENT,default=local"`
// THIS MUST BE OVERRIDDEN ON ANY LIVE ENVIRONMENTS
EncryptionKey string `env:"APP_ENCRYPTION_KEY,default=?E(G+KbPeShVmYq3t6w9z$C&F)J@McQf"`
Timeout time.Duration `env:"APP_TIMEOUT,default=20s"`
diff --git a/config/config_test.go b/config/config_test.go
index 103e368..1404f7c 100644
--- a/config/config_test.go
+++ b/config/config_test.go
@@ -11,7 +11,7 @@ func TestGetConfig(t *testing.T) {
_, err := GetConfig()
require.NoError(t, err)
- var env Environment
+ var env environment
env = "abc"
SwitchEnvironment(env)
cfg, err := GetConfig()
diff --git a/msg/msg.go b/msg/msg.go
index 65626c2..50de881 100644
--- a/msg/msg.go
+++ b/msg/msg.go
@@ -6,13 +6,21 @@ import (
"github.com/labstack/echo/v4"
)
+// Type is a message type
type Type string
const (
+ // TypeSuccess represents a success message type
TypeSuccess Type = "success"
- TypeInfo Type = "info"
+
+ // TypeInfo represents a info message type
+ TypeInfo Type = "info"
+
+ // TypeWarning represents a warning message type
TypeWarning Type = "warning"
- TypeDanger Type = "danger"
+
+ // TypeDanger represents a danger message type
+ TypeDanger Type = "danger"
)
const (
diff --git a/routes/about.go b/routes/about.go
index bdd809d..f9665ae 100644
--- a/routes/about.go
+++ b/routes/about.go
@@ -9,23 +9,23 @@ import (
)
type (
- About struct {
+ about struct {
controller.Controller
}
- AboutData struct {
+ aboutData struct {
ShowCacheWarning bool
- FrontendTabs []AboutTab
- BackendTabs []AboutTab
+ FrontendTabs []aboutTab
+ BackendTabs []aboutTab
}
- AboutTab struct {
+ aboutTab struct {
Title string
Body template.HTML
}
)
-func (c *About) Get(ctx echo.Context) error {
+func (c *about) Get(ctx echo.Context) error {
page := controller.NewPage(ctx)
page.Layout = "main"
page.Name = "about"
@@ -37,9 +37,9 @@ func (c *About) Get(ctx echo.Context) error {
// A simple example of how the Data field can contain anything you want to send to the templates
// even though you wouldn't normally send markup like this
- page.Data = AboutData{
+ page.Data = aboutData{
ShowCacheWarning: true,
- FrontendTabs: []AboutTab{
+ FrontendTabs: []aboutTab{
{
Title: "HTMX",
Body: template.HTML(`Completes HTML as a hypertext by providing attributes to AJAXify anything and much more. Visit htmx.org to learn more.`),
@@ -53,7 +53,7 @@ func (c *About) Get(ctx echo.Context) error {
Body: template.HTML(`Ready-to-use frontend components that you can easily combine to build responsive web interfaces with no JavaScript requirements. Visit bulma.io to learn more.`),
},
},
- BackendTabs: []AboutTab{
+ BackendTabs: []aboutTab{
{
Title: "Echo",
Body: template.HTML(`High performance, extensible, minimalist Go web framework. Visit echo.labstack.com to learn more.`),
diff --git a/routes/contact.go b/routes/contact.go
index 0ae07d9..4a7ac99 100644
--- a/routes/contact.go
+++ b/routes/contact.go
@@ -10,33 +10,33 @@ import (
)
type (
- Contact struct {
+ contact struct {
controller.Controller
}
- ContactForm struct {
+ contactForm struct {
Email string `form:"email" validate:"required,email"`
Message string `form:"message" validate:"required"`
Submission controller.FormSubmission
}
)
-func (c *Contact) Get(ctx echo.Context) error {
+func (c *contact) Get(ctx echo.Context) error {
page := controller.NewPage(ctx)
page.Layout = "main"
page.Name = "contact"
page.Title = "Contact us"
- page.Form = ContactForm{}
+ page.Form = contactForm{}
if form := ctx.Get(context.FormKey); form != nil {
- page.Form = form.(*ContactForm)
+ page.Form = form.(*contactForm)
}
return c.RenderPage(ctx, page)
}
-func (c *Contact) Post(ctx echo.Context) error {
- var form ContactForm
+func (c *contact) Post(ctx echo.Context) error {
+ var form contactForm
ctx.Set(context.FormKey, &form)
// Parse the form values
diff --git a/routes/error.go b/routes/error.go
index 50e2bfc..d6f2270 100644
--- a/routes/error.go
+++ b/routes/error.go
@@ -9,11 +9,11 @@ import (
"github.com/labstack/echo/v4"
)
-type Error struct {
+type errorHandler struct {
controller.Controller
}
-func (e *Error) Get(err error, ctx echo.Context) {
+func (e *errorHandler) Get(err error, ctx echo.Context) {
if ctx.Response().Committed || context.IsCanceledError(err) {
return
}
diff --git a/routes/forgot_password.go b/routes/forgot_password.go
index d380079..52243fc 100644
--- a/routes/forgot_password.go
+++ b/routes/forgot_password.go
@@ -14,32 +14,32 @@ import (
)
type (
- ForgotPassword struct {
+ forgotPassword struct {
controller.Controller
}
- ForgotPasswordForm struct {
+ forgotPasswordForm struct {
Email string `form:"email" validate:"required,email"`
Submission controller.FormSubmission
}
)
-func (c *ForgotPassword) Get(ctx echo.Context) error {
+func (c *forgotPassword) Get(ctx echo.Context) error {
page := controller.NewPage(ctx)
page.Layout = "auth"
page.Name = "forgot-password"
page.Title = "Forgot password"
- page.Form = ForgotPasswordForm{}
+ page.Form = forgotPasswordForm{}
if form := ctx.Get(context.FormKey); form != nil {
- page.Form = form.(*ForgotPasswordForm)
+ page.Form = form.(*forgotPasswordForm)
}
return c.RenderPage(ctx, page)
}
-func (c *ForgotPassword) Post(ctx echo.Context) error {
- var form ForgotPasswordForm
+func (c *forgotPassword) Post(ctx echo.Context) error {
+ var form forgotPasswordForm
ctx.Set(context.FormKey, &form)
succeed := func() error {
diff --git a/routes/home.go b/routes/home.go
index 20ae798..721d97b 100644
--- a/routes/home.go
+++ b/routes/home.go
@@ -9,17 +9,17 @@ import (
)
type (
- Home struct {
+ home struct {
controller.Controller
}
- Post struct {
+ post struct {
Title string
Body string
}
)
-func (c *Home) Get(ctx echo.Context) error {
+func (c *home) Get(ctx echo.Context) error {
page := controller.NewPage(ctx)
page.Layout = "main"
page.Name = "home"
@@ -32,12 +32,12 @@ func (c *Home) Get(ctx echo.Context) error {
}
// fetchPosts is an mock example of fetching posts to illustrate how paging works
-func (c *Home) fetchPosts(pager *controller.Pager) []Post {
+func (c *home) fetchPosts(pager *controller.Pager) []post {
pager.SetItems(20)
- posts := make([]Post, 20)
+ posts := make([]post, 20)
for k := range posts {
- posts[k] = Post{
+ posts[k] = post{
Title: fmt.Sprintf("Post example #%d", k+1),
Body: fmt.Sprintf("Lorem ipsum example #%d ddolor sit amet, consectetur adipiscing elit. Nam elementum vulputate tristique.", k+1),
}
diff --git a/routes/login.go b/routes/login.go
index b7cc415..a8e60bd 100644
--- a/routes/login.go
+++ b/routes/login.go
@@ -14,33 +14,33 @@ import (
)
type (
- Login struct {
+ login struct {
controller.Controller
}
- LoginForm struct {
+ loginForm struct {
Email string `form:"email" validate:"required,email"`
Password string `form:"password" validate:"required"`
Submission controller.FormSubmission
}
)
-func (c *Login) Get(ctx echo.Context) error {
+func (c *login) Get(ctx echo.Context) error {
page := controller.NewPage(ctx)
page.Layout = "auth"
page.Name = "login"
page.Title = "Log in"
- page.Form = LoginForm{}
+ page.Form = loginForm{}
if form := ctx.Get(context.FormKey); form != nil {
- page.Form = form.(*LoginForm)
+ page.Form = form.(*loginForm)
}
return c.RenderPage(ctx, page)
}
-func (c *Login) Post(ctx echo.Context) error {
- var form LoginForm
+func (c *login) Post(ctx echo.Context) error {
+ var form loginForm
ctx.Set(context.FormKey, &form)
authFailed := func() error {
diff --git a/routes/logout.go b/routes/logout.go
index 78cc051..70a9cb8 100644
--- a/routes/logout.go
+++ b/routes/logout.go
@@ -7,11 +7,11 @@ import (
"github.com/labstack/echo/v4"
)
-type Logout struct {
+type logout struct {
controller.Controller
}
-func (l *Logout) Get(c echo.Context) error {
+func (l *logout) Get(c echo.Context) error {
if err := l.Container.Auth.Logout(c); err == nil {
msg.Success(c, "You have been logged out successfully.")
} else {
diff --git a/routes/register.go b/routes/register.go
index dde596d..ecadccd 100644
--- a/routes/register.go
+++ b/routes/register.go
@@ -12,11 +12,11 @@ import (
)
type (
- Register struct {
+ register struct {
controller.Controller
}
- RegisterForm struct {
+ registerForm struct {
Name string `form:"name" validate:"required"`
Email string `form:"email" validate:"required,email"`
Password string `form:"password" validate:"required"`
@@ -25,22 +25,22 @@ type (
}
)
-func (c *Register) Get(ctx echo.Context) error {
+func (c *register) Get(ctx echo.Context) error {
page := controller.NewPage(ctx)
page.Layout = "auth"
page.Name = "register"
page.Title = "Register"
- page.Form = RegisterForm{}
+ page.Form = registerForm{}
if form := ctx.Get(context.FormKey); form != nil {
- page.Form = form.(*RegisterForm)
+ page.Form = form.(*registerForm)
}
return c.RenderPage(ctx, page)
}
-func (c *Register) Post(ctx echo.Context) error {
- var form RegisterForm
+func (c *register) Post(ctx echo.Context) error {
+ var form registerForm
ctx.Set(context.FormKey, &form)
// Parse the form values
@@ -96,7 +96,7 @@ func (c *Register) Post(ctx echo.Context) error {
return c.Redirect(ctx, "home")
}
-func (c *Register) sendVerificationEmail(ctx echo.Context, usr *ent.User) {
+func (c *register) sendVerificationEmail(ctx echo.Context, usr *ent.User) {
// Generate a token
token, err := c.Container.Auth.GenerateEmailVerificationToken(usr.Email)
if err != nil {
diff --git a/routes/reset_password.go b/routes/reset_password.go
index 577e6b7..d32d290 100644
--- a/routes/reset_password.go
+++ b/routes/reset_password.go
@@ -10,33 +10,33 @@ import (
)
type (
- ResetPassword struct {
+ resetPassword struct {
controller.Controller
}
- ResetPasswordForm struct {
+ resetPasswordForm struct {
Password string `form:"password" validate:"required"`
ConfirmPassword string `form:"password-confirm" validate:"required,eqfield=Password"`
Submission controller.FormSubmission
}
)
-func (c *ResetPassword) Get(ctx echo.Context) error {
+func (c *resetPassword) Get(ctx echo.Context) error {
page := controller.NewPage(ctx)
page.Layout = "auth"
page.Name = "reset-password"
page.Title = "Reset password"
- page.Form = ResetPasswordForm{}
+ page.Form = resetPasswordForm{}
if form := ctx.Get(context.FormKey); form != nil {
- page.Form = form.(*ResetPasswordForm)
+ page.Form = form.(*resetPasswordForm)
}
return c.RenderPage(ctx, page)
}
-func (c *ResetPassword) Post(ctx echo.Context) error {
- var form ResetPasswordForm
+func (c *resetPassword) Post(ctx echo.Context) error {
+ var form resetPasswordForm
ctx.Set(context.FormKey, &form)
// Parse the form values
diff --git a/routes/router.go b/routes/router.go
index b6d3972..ca5c0b1 100644
--- a/routes/router.go
+++ b/routes/router.go
@@ -56,7 +56,7 @@ func BuildRouter(c *services.Container) {
ctr := controller.NewController(c)
// Error handler
- err := Error{Controller: ctr}
+ err := errorHandler{Controller: ctr}
c.Web.HTTPErrorHandler = err.Get
// Example routes
@@ -65,37 +65,37 @@ func BuildRouter(c *services.Container) {
}
func navRoutes(c *services.Container, g *echo.Group, ctr controller.Controller) {
- home := Home{Controller: ctr}
+ home := home{Controller: ctr}
g.GET("/", home.Get).Name = "home"
- search := Search{Controller: ctr}
+ search := search{Controller: ctr}
g.GET("/search", search.Get).Name = "search"
- about := About{Controller: ctr}
+ about := about{Controller: ctr}
g.GET("/about", about.Get).Name = "about"
- contact := Contact{Controller: ctr}
+ contact := contact{Controller: ctr}
g.GET("/contact", contact.Get).Name = "contact"
g.POST("/contact", contact.Post).Name = "contact.post"
}
func userRoutes(c *services.Container, g *echo.Group, ctr controller.Controller) {
- logout := Logout{Controller: ctr}
+ logout := logout{Controller: ctr}
g.GET("/logout", logout.Get, middleware.RequireAuthentication()).Name = "logout"
- verifyEmail := VerifyEmail{Controller: ctr}
+ verifyEmail := verifyEmail{Controller: ctr}
g.GET("/email/verify/:token", verifyEmail.Get).Name = "verify_email"
noAuth := g.Group("/user", middleware.RequireNoAuthentication())
- login := Login{Controller: ctr}
+ login := login{Controller: ctr}
noAuth.GET("/login", login.Get).Name = "login"
noAuth.POST("/login", login.Post).Name = "login.post"
- register := Register{Controller: ctr}
+ register := register{Controller: ctr}
noAuth.GET("/register", register.Get).Name = "register"
noAuth.POST("/register", register.Post).Name = "register.post"
- forgot := ForgotPassword{Controller: ctr}
+ forgot := forgotPassword{Controller: ctr}
noAuth.GET("/password", forgot.Get).Name = "forgot_password"
noAuth.POST("/password", forgot.Post).Name = "forgot_password.post"
@@ -103,7 +103,7 @@ func userRoutes(c *services.Container, g *echo.Group, ctr controller.Controller)
middleware.LoadUser(c.ORM),
middleware.LoadValidPasswordToken(c.Auth),
)
- reset := ResetPassword{Controller: ctr}
+ reset := resetPassword{Controller: ctr}
resetGroup.GET("/token/:user/:password_token/:token", reset.Get).Name = "reset_password"
resetGroup.POST("/token/:user/:password_token/:token", reset.Post).Name = "reset_password.post"
}
diff --git a/routes/search.go b/routes/search.go
index 3292f5a..ca99b3b 100644
--- a/routes/search.go
+++ b/routes/search.go
@@ -10,29 +10,29 @@ import (
)
type (
- Search struct {
+ search struct {
controller.Controller
}
- SearchResult struct {
+ searchResult struct {
Title string
URL string
}
)
-func (c *Search) Get(ctx echo.Context) error {
+func (c *search) Get(ctx echo.Context) error {
page := controller.NewPage(ctx)
page.Layout = "main"
page.Name = "search"
// Fake search results
- var results []SearchResult
+ var results []searchResult
if search := ctx.QueryParam("query"); search != "" {
for i := 0; i < 5; i++ {
title := "Lorem ipsum example ddolor sit amet"
index := rand.Intn(len(title))
title = title[:index] + search + title[index:]
- results = append(results, SearchResult{
+ results = append(results, searchResult{
Title: title,
URL: fmt.Sprintf("https://www.%s.com", search),
})
diff --git a/routes/verify_email.go b/routes/verify_email.go
index 59d137c..6bd88f9 100644
--- a/routes/verify_email.go
+++ b/routes/verify_email.go
@@ -9,11 +9,11 @@ import (
"github.com/mikestefanello/pagoda/msg"
)
-type VerifyEmail struct {
+type verifyEmail struct {
controller.Controller
}
-func (c *VerifyEmail) Get(ctx echo.Context) error {
+func (c *verifyEmail) Get(ctx echo.Context) error {
var usr *ent.User
// Validate the token
diff --git a/services/container.go b/services/container.go
index 977572c..87543fa 100644
--- a/services/container.go
+++ b/services/container.go
@@ -8,12 +8,16 @@ import (
"entgo.io/ent/dialect"
entsql "entgo.io/ent/dialect/sql"
"entgo.io/ent/dialect/sql/schema"
+
+ // Required by ent
_ "github.com/jackc/pgx/v4/stdlib"
"github.com/labstack/echo/v4"
"github.com/labstack/gommon/log"
"github.com/mikestefanello/pagoda/config"
"github.com/mikestefanello/pagoda/ent"
+
+ // Require by ent
_ "github.com/mikestefanello/pagoda/ent/runtime"
)
diff --git a/tests/tests.go b/tests/tests.go
index d5127c5..aa43980 100644
--- a/tests/tests.go
+++ b/tests/tests.go
@@ -20,17 +20,20 @@ import (
"github.com/labstack/echo/v4"
)
+// NewContext creates a new Echo context for tests using an HTTP test request and response recorder
func NewContext(e *echo.Echo, url string) (echo.Context, *httptest.ResponseRecorder) {
req := httptest.NewRequest(http.MethodGet, url, strings.NewReader(""))
rec := httptest.NewRecorder()
return e.NewContext(req, rec), rec
}
+// InitSession initializes a session for a given Echo context
func InitSession(ctx echo.Context) {
mw := session.Middleware(sessions.NewCookieStore([]byte("secret")))
_ = ExecuteMiddleware(ctx, mw)
}
+// ExecuteMiddleware executes a middleware function on a given Echo context
func ExecuteMiddleware(ctx echo.Context, mw echo.MiddlewareFunc) error {
handler := mw(func(c echo.Context) error {
return nil
@@ -38,12 +41,14 @@ func ExecuteMiddleware(ctx echo.Context, mw echo.MiddlewareFunc) error {
return handler(ctx)
}
+// AssertHTTPErrorCode asserts an HTTP status code on a given Echo HTTP error
func AssertHTTPErrorCode(t *testing.T, err error, code int) {
httpError, ok := err.(*echo.HTTPError)
require.True(t, ok)
assert.Equal(t, code, httpError.Code)
}
+// CreateUser creates a random user entity
func CreateUser(orm *ent.Client) (*ent.User, error) {
seed := fmt.Sprintf("%d-%d", time.Now().UnixMilli(), rand.Intn(1000000))
return orm.User.