diff --git a/cmd/web/handlers.go b/cmd/web/handlers.go
index 1f46db0..6b27d11 100644
--- a/cmd/web/handlers.go
+++ b/cmd/web/handlers.go
@@ -13,10 +13,10 @@ import (
)
type snippetCreateForm struct {
- Title string
- Content string
- Expires int
- validator.Validator
+ Title string `form:"title"`
+ Content string `form:"content"`
+ Expires int `form:"expires"`
+ validator.Validator `form:"-"`
}
func (app *application) render(w http.ResponseWriter, status int, page string, data *templateData) {
@@ -88,24 +88,13 @@ func (app *application) snippetCreate(w http.ResponseWriter, r *http.Request) {
}
func (app *application) snippetCreatePost(w http.ResponseWriter, r *http.Request) {
- err := r.ParseForm()
+ var form snippetCreateForm
+ err := app.decodePostForm(r, &form)
if err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
- expires, err := strconv.Atoi(r.PostForm.Get("expires"))
- if err != nil {
- app.clientError(w, http.StatusBadRequest)
- return
- }
-
- form := snippetCreateForm{
- Title: r.PostForm.Get("title"),
- Content: r.PostForm.Get("content"),
- Expires: expires,
- }
-
form.CheckField(validator.NotBlank(form.Title), "title", "This field cannot be blank")
form.CheckField(validator.MaxChars(form.Title, 100), "title", "This field cannot be more than 100 character long")
form.CheckField(validator.NotBlank(form.Content), "content", "This field cannot be blank")
@@ -124,5 +113,7 @@ func (app *application) snippetCreatePost(w http.ResponseWriter, r *http.Request
return
}
+ app.sessionManager.Put(r.Context(), "flash", "Snippet successfully created!")
+
http.Redirect(w, r, fmt.Sprintf("/snippet/view/%d", id), http.StatusSeeOther)
}
diff --git a/cmd/web/helpers.go b/cmd/web/helpers.go
index 37903e6..9c9ff5e 100644
--- a/cmd/web/helpers.go
+++ b/cmd/web/helpers.go
@@ -1,9 +1,12 @@
package main
import (
+ "errors"
"fmt"
"net/http"
"runtime/debug"
+
+ "github.com/go-playground/form/v4"
)
func (app *application) serverError(w http.ResponseWriter, err error) {
@@ -20,3 +23,20 @@ func (app *application) clientError(w http.ResponseWriter, status int) {
func (app *application) notFound(w http.ResponseWriter) {
app.clientError(w, http.StatusNotFound)
}
+
+func (app *application) decodePostForm(r *http.Request, snippetForm *snippetCreateForm) error {
+ err := r.ParseForm()
+ if err != nil {
+ return err
+ }
+
+ err = app.formDecoder.Decode(&snippetForm, r.PostForm)
+ if err != nil {
+ var invalidDecoderError *form.InvalidDecoderError
+ if errors.As(err, &invalidDecoderError) {
+ panic("Invalid decoder error")
+ }
+ return err
+ }
+ return nil
+}
diff --git a/cmd/web/main.go b/cmd/web/main.go
index d80a947..5659352 100644
--- a/cmd/web/main.go
+++ b/cmd/web/main.go
@@ -7,17 +7,23 @@ import (
"log"
"net/http"
"os"
+ "time"
"gitea.local.lab/Lbenedar/snippetbox/internal/models"
+ "github.com/alexedwards/scs/mysqlstore"
+ "github.com/alexedwards/scs/v2"
+ "github.com/go-playground/form/v4"
_ "github.com/go-sql-driver/mysql"
)
type application struct {
- errorLog *log.Logger
- infoLog *log.Logger
- snippets *models.SnippetModel
- templateCache map[string]*template.Template
+ errorLog *log.Logger
+ infoLog *log.Logger
+ snippets *models.SnippetModel
+ templateCache map[string]*template.Template
+ formDecoder *form.Decoder
+ sessionManager *scs.SessionManager
}
type config struct {
@@ -47,11 +53,18 @@ func main() {
errorLog.Fatal(err)
}
+ formDecoder := form.NewDecoder()
+ sessionManager := scs.New()
+ sessionManager.Store = mysqlstore.New(db)
+ sessionManager.Lifetime = 12 * time.Hour
+
app := &application{
- errorLog: errorLog,
- infoLog: infoLog,
- snippets: &models.SnippetModel{DB: db},
- templateCache: templateCache,
+ errorLog: errorLog,
+ infoLog: infoLog,
+ snippets: &models.SnippetModel{DB: db},
+ templateCache: templateCache,
+ formDecoder: formDecoder,
+ sessionManager: sessionManager,
}
srv := &http.Server{
diff --git a/cmd/web/routes.go b/cmd/web/routes.go
index 73efb38..e98fc6e 100644
--- a/cmd/web/routes.go
+++ b/cmd/web/routes.go
@@ -17,12 +17,13 @@ func (app *application) routes() http.Handler {
fileServer := http.FileServer(http.Dir("./ui/static/"))
router.Handler(http.MethodGet, "/static/", http.StripPrefix("/static", fileServer))
- router.HandlerFunc(http.MethodGet, "/", app.home)
- router.HandlerFunc(http.MethodGet, "/snippet/view/:id", app.snippetView)
- router.HandlerFunc(http.MethodGet, "/snippet/create", app.snippetCreate)
- router.HandlerFunc(http.MethodPost, "/snippet/create", app.snippetCreatePost)
+ dynamic := alice.New(app.sessionManager.LoadAndSave)
+
+ router.Handler(http.MethodGet, "/", dynamic.ThenFunc(app.home))
+ router.Handler(http.MethodGet, "/snippet/view/:id", dynamic.ThenFunc(app.snippetView))
+ router.Handler(http.MethodGet, "/snippet/create", dynamic.ThenFunc(app.snippetCreate))
+ router.Handler(http.MethodPost, "/snippet/create", dynamic.ThenFunc(app.snippetCreatePost))
standard := alice.New(app.recoverPanic, app.logRequest, secureHeaders)
-
return standard.Then(router)
}
diff --git a/cmd/web/templates.go b/cmd/web/templates.go
index d58462c..d6275b9 100644
--- a/cmd/web/templates.go
+++ b/cmd/web/templates.go
@@ -14,6 +14,7 @@ type templateData struct {
Snippet *models.Snippet
Snippets []*models.Snippet
Form any
+ Flash string
}
func humanDate(t time.Time) string {
@@ -27,6 +28,7 @@ var functions = template.FuncMap{
func (app *application) newTemplateData(r *http.Request) *templateData {
return &templateData{
CurrentYear: time.Now().Year(),
+ Flash: app.sessionManager.PopString(r.Context(), "flash"),
}
}
diff --git a/go.mod b/go.mod
index fea578c..b98643a 100644
--- a/go.mod
+++ b/go.mod
@@ -4,6 +4,9 @@ go 1.25.0
require (
filippo.io/edwards25519 v1.1.0 // indirect
+ github.com/alexedwards/scs/mysqlstore v0.0.0-20251002162104-209de6e426de // indirect
+ github.com/alexedwards/scs/v2 v2.9.0 // indirect
+ github.com/go-playground/form/v4 v4.3.0 // indirect
github.com/go-sql-driver/mysql v1.9.3 // indirect
github.com/julienschmidt/httprouter v1.3.0 // indirect
github.com/justinas/alice v1.2.0 // indirect
diff --git a/go.sum b/go.sum
index 09928e3..e2e115f 100644
--- a/go.sum
+++ b/go.sum
@@ -1,5 +1,12 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
+github.com/alexedwards/scs/mysqlstore v0.0.0-20251002162104-209de6e426de h1:/Y/iIFgV1Ofvk4Euv5gUQ74vgqFZOQ1wlJQ3yz/zYGs=
+github.com/alexedwards/scs/mysqlstore v0.0.0-20251002162104-209de6e426de/go.mod h1:p8jK3D80sw1PFrCSdlcJF1O75bp55HqbgDyyCLM0FrE=
+github.com/alexedwards/scs/v2 v2.9.0 h1:xa05mVpwTBm1iLeTMNFfAWpKUm4fXAW7CeAViqBVS90=
+github.com/alexedwards/scs/v2 v2.9.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
+github.com/go-playground/form/v4 v4.3.0 h1:OVttojbQv2WNCs4P+VnjPtrt/+30Ipw4890W3OaFlvk=
+github.com/go-playground/form/v4 v4.3.0/go.mod h1:Cpe1iYJKoXb1vILRXEwxpWMGWyQuqplQ/4cvPecy+Jo=
+github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.9.3 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=
diff --git a/ui/html/base.tmpl b/ui/html/base.tmpl
index 8344b94..27c7bbb 100644
--- a/ui/html/base.tmpl
+++ b/ui/html/base.tmpl
@@ -14,6 +14,9 @@
{{template "nav" .}}
+ {{with .Flash}}
+ {{.}}
+ {{end}}
{{template "main" .}}