This commit is contained in:
lbenedar
2026-03-05 17:46:26 +03:00
parent c2883b9916
commit b50cd42f4f
8 changed files with 70 additions and 30 deletions

View File

@@ -13,10 +13,10 @@ import (
) )
type snippetCreateForm struct { type snippetCreateForm struct {
Title string Title string `form:"title"`
Content string Content string `form:"content"`
Expires int Expires int `form:"expires"`
validator.Validator validator.Validator `form:"-"`
} }
func (app *application) render(w http.ResponseWriter, status int, page string, data *templateData) { 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) { func (app *application) snippetCreatePost(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm() var form snippetCreateForm
err := app.decodePostForm(r, &form)
if err != nil { if err != nil {
app.clientError(w, http.StatusBadRequest) app.clientError(w, http.StatusBadRequest)
return 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.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.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") 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 return
} }
app.sessionManager.Put(r.Context(), "flash", "Snippet successfully created!")
http.Redirect(w, r, fmt.Sprintf("/snippet/view/%d", id), http.StatusSeeOther) http.Redirect(w, r, fmt.Sprintf("/snippet/view/%d", id), http.StatusSeeOther)
} }

View File

@@ -1,9 +1,12 @@
package main package main
import ( import (
"errors"
"fmt" "fmt"
"net/http" "net/http"
"runtime/debug" "runtime/debug"
"github.com/go-playground/form/v4"
) )
func (app *application) serverError(w http.ResponseWriter, err error) { 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) { func (app *application) notFound(w http.ResponseWriter) {
app.clientError(w, http.StatusNotFound) 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
}

View File

@@ -7,17 +7,23 @@ import (
"log" "log"
"net/http" "net/http"
"os" "os"
"time"
"gitea.local.lab/Lbenedar/snippetbox/internal/models" "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" _ "github.com/go-sql-driver/mysql"
) )
type application struct { type application struct {
errorLog *log.Logger errorLog *log.Logger
infoLog *log.Logger infoLog *log.Logger
snippets *models.SnippetModel snippets *models.SnippetModel
templateCache map[string]*template.Template templateCache map[string]*template.Template
formDecoder *form.Decoder
sessionManager *scs.SessionManager
} }
type config struct { type config struct {
@@ -47,11 +53,18 @@ func main() {
errorLog.Fatal(err) errorLog.Fatal(err)
} }
formDecoder := form.NewDecoder()
sessionManager := scs.New()
sessionManager.Store = mysqlstore.New(db)
sessionManager.Lifetime = 12 * time.Hour
app := &application{ app := &application{
errorLog: errorLog, errorLog: errorLog,
infoLog: infoLog, infoLog: infoLog,
snippets: &models.SnippetModel{DB: db}, snippets: &models.SnippetModel{DB: db},
templateCache: templateCache, templateCache: templateCache,
formDecoder: formDecoder,
sessionManager: sessionManager,
} }
srv := &http.Server{ srv := &http.Server{

View File

@@ -17,12 +17,13 @@ func (app *application) routes() http.Handler {
fileServer := http.FileServer(http.Dir("./ui/static/")) fileServer := http.FileServer(http.Dir("./ui/static/"))
router.Handler(http.MethodGet, "/static/", http.StripPrefix("/static", fileServer)) router.Handler(http.MethodGet, "/static/", http.StripPrefix("/static", fileServer))
router.HandlerFunc(http.MethodGet, "/", app.home) dynamic := alice.New(app.sessionManager.LoadAndSave)
router.HandlerFunc(http.MethodGet, "/snippet/view/:id", app.snippetView)
router.HandlerFunc(http.MethodGet, "/snippet/create", app.snippetCreate) router.Handler(http.MethodGet, "/", dynamic.ThenFunc(app.home))
router.HandlerFunc(http.MethodPost, "/snippet/create", app.snippetCreatePost) 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) standard := alice.New(app.recoverPanic, app.logRequest, secureHeaders)
return standard.Then(router) return standard.Then(router)
} }

View File

@@ -14,6 +14,7 @@ type templateData struct {
Snippet *models.Snippet Snippet *models.Snippet
Snippets []*models.Snippet Snippets []*models.Snippet
Form any Form any
Flash string
} }
func humanDate(t time.Time) string { func humanDate(t time.Time) string {
@@ -27,6 +28,7 @@ var functions = template.FuncMap{
func (app *application) newTemplateData(r *http.Request) *templateData { func (app *application) newTemplateData(r *http.Request) *templateData {
return &templateData{ return &templateData{
CurrentYear: time.Now().Year(), CurrentYear: time.Now().Year(),
Flash: app.sessionManager.PopString(r.Context(), "flash"),
} }
} }

3
go.mod
View File

@@ -4,6 +4,9 @@ go 1.25.0
require ( require (
filippo.io/edwards25519 v1.1.0 // indirect 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/go-sql-driver/mysql v1.9.3 // indirect
github.com/julienschmidt/httprouter v1.3.0 // indirect github.com/julienschmidt/httprouter v1.3.0 // indirect
github.com/justinas/alice v1.2.0 // indirect github.com/justinas/alice v1.2.0 // indirect

7
go.sum
View File

@@ -1,5 +1,12 @@
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA= filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4= 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 h1:U/N249h2WzJ3Ukj8SowVFjdtZKfu9vlLZxjPXV1aweo=
github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU= github.com/go-sql-driver/mysql v1.9.3/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U= github.com/julienschmidt/httprouter v1.3.0 h1:U0609e9tgbseu3rBINet9P48AI/D3oJs4dN7jwJOQ1U=

View File

@@ -14,6 +14,9 @@
</header> </header>
{{template "nav" .}} {{template "nav" .}}
<main> <main>
{{with .Flash}}
<div class='flash'>{{.}}</div>
{{end}}
{{template "main" .}} {{template "main" .}}
</main> </main>
<footer> <footer>