diff --git a/cmd/web/handlers.go b/cmd/web/handlers.go
index 3ed835d..d5fdec1 100644
--- a/cmd/web/handlers.go
+++ b/cmd/web/handlers.go
@@ -57,9 +57,10 @@ func (app *Application) SnippetView(w http.ResponseWriter, r *http.Request) {
return
}
- app.render(w, r, http.StatusOK, "view.tmpl", templateData{
- Snippet: snippet,
- })
+ data := app.NewTemplateData(r)
+ data.Snippet = snippet
+
+ app.render(w, r, http.StatusOK, "view.tmpl", *data)
}
@@ -122,6 +123,8 @@ func (app *Application) SnippetCreatePost(w http.ResponseWriter, r *http.Request
app.serverError(w, r, err)
return
}
+ // Saving message flash
+ app.sessionManager.Put(r.Context(), "flash", "Snippet created successfully!")
http.Redirect(w, r, fmt.Sprintf("/snippet/view/%d", id), http.StatusSeeOther)
}
diff --git a/cmd/web/handlers_test.go b/cmd/web/handlers_test.go
index 878ea1c..bc42dd8 100644
--- a/cmd/web/handlers_test.go
+++ b/cmd/web/handlers_test.go
@@ -9,6 +9,8 @@ import (
"strings"
"testing"
+ "github.com/alexedwards/scs/v2"
+ "github.com/alexedwards/scs/v2/memstore"
"snippetbox.tonidefez.net/internal/models"
)
@@ -85,13 +87,19 @@ func TestSnippetView(t *testing.T) {
t.Fatal(err)
}
+ // Create fake session manager
+ sessionManager := scs.New()
+ sessionManager.Store = memstore.New()
+
app := &Application{
- logger: dummyLogger,
- snippets: &models.MockSnippetModel{},
- templateCache: templateCache,
+ logger: dummyLogger,
+ snippets: &models.MockSnippetModel{},
+ templateCache: templateCache,
+ sessionManager: sessionManager,
}
- router := app.routes()
+ //Be sure to manage session for each route
+ router := app.sessionManager.LoadAndSave(app.routes())
router.ServeHTTP(rr, req)
// verify status
@@ -115,18 +123,22 @@ func TestSnippetCreateGet(t *testing.T) {
dummyLogger := slog.New(slog.NewTextHandler(io.Discard, nil))
dummyDB := &models.SnippetModel{DB: nil}
templateCache, err := newTemplateCache()
+ // Create fake session manager
+ sessionManager := scs.New()
+ sessionManager.Store = memstore.New()
if err != nil {
t.Fatal(err)
}
app := &Application{
- logger: dummyLogger,
- snippets: dummyDB,
- templateCache: templateCache,
+ logger: dummyLogger,
+ snippets: dummyDB,
+ templateCache: templateCache,
+ sessionManager: sessionManager,
}
- router := app.routes()
+ router := app.sessionManager.LoadAndSave(app.routes())
router.ServeHTTP(rr, req)
// verify status
@@ -144,13 +156,18 @@ func TestSnippetCreatePost(t *testing.T) {
dummyLogger := slog.New(slog.NewTextHandler(io.Discard, nil))
+ // Create fake session manager
+ sessionManager := scs.New()
+ sessionManager.Store = memstore.New()
+
// simulate dependencies
app := &Application{
- logger: dummyLogger,
- snippets: &models.MockSnippetModel{},
+ logger: dummyLogger,
+ snippets: &models.MockSnippetModel{},
+ sessionManager: sessionManager,
}
- router := app.routes()
+ router := app.sessionManager.LoadAndSave(app.routes())
router.ServeHTTP(rr, req)
// Verificar redirección
@@ -172,8 +189,14 @@ func TestSnippetCreatePost_InvalidData(t *testing.T) {
if err != nil {
t.Fatal(err)
}
+
+ // Create fake session manager
+ sessionManager := scs.New()
+ sessionManager.Store = memstore.New()
+
app := &Application{
- templateCache: templateCache,
+ templateCache: templateCache,
+ sessionManager: sessionManager,
}
form := url.Values{}
@@ -186,7 +209,7 @@ func TestSnippetCreatePost_InvalidData(t *testing.T) {
rr := httptest.NewRecorder()
- app.SnippetCreatePost(rr, req)
+ app.sessionManager.LoadAndSave(http.HandlerFunc(app.SnippetCreatePost)).ServeHTTP(rr, req)
res := rr.Result()
defer res.Body.Close()
diff --git a/cmd/web/helper.go b/cmd/web/helper.go
index 307c040..7ea88bd 100644
--- a/cmd/web/helper.go
+++ b/cmd/web/helper.go
@@ -5,6 +5,7 @@ import (
"fmt"
"net/http"
"runtime/debug"
+ "time"
"snippetbox.tonidefez.net/internal/models"
)
@@ -54,7 +55,14 @@ func (app *Application) render(w http.ResponseWriter, r *http.Request, status in
func (app *Application) NewTemplateData(r *http.Request) *templateData {
return &templateData{
- Snippet: models.Snippet{},
- Snippets: []models.Snippet{},
+ Snippet: models.Snippet{},
+ Snippets: []models.Snippet{},
+ CurrentYear: time.Now().Year(),
+ // Use the PopString() method to retrieve the value for the "flash" key.
+ // PopString() also deletes the key and value from the session data, so it
+ // acts like a one-time fetch. If there is no matching key in the session
+ // data this will return the empty string.
+
+ Flash: app.sessionManager.PopString(r.Context(), "flash"),
}
}
diff --git a/cmd/web/main.go b/cmd/web/main.go
index 5a22209..d68540f 100644
--- a/cmd/web/main.go
+++ b/cmd/web/main.go
@@ -7,7 +7,10 @@ import (
"net/http"
"os"
"text/template"
+ "time"
+ "github.com/alexedwards/scs/mysqlstore"
+ "github.com/alexedwards/scs/v2"
_ "github.com/go-sql-driver/mysql"
"snippetbox.tonidefez.net/internal/models"
)
@@ -16,10 +19,10 @@ import (
// web application. For now we'll only include the structured logger, but we'll
// add more to this as the build progresses.
type Application struct {
- logger *slog.Logger
- // implemeting an interfaz we can make with differents dependencies DIP
- snippets models.SnippetModeler
- templateCache map[string]*template.Template
+ logger *slog.Logger
+ snippets models.SnippetModeler
+ templateCache map[string]*template.Template
+ sessionManager *scs.SessionManager
}
func main() {
@@ -43,6 +46,14 @@ func main() {
// before the main() function exits.
defer db.Close()
+ // Use the scs.New() function to initialize a new session manager. Then we
+ // configure it to use our MySQL database as the session store, and set a
+ // lifetime of 12 hours (so that sessions automatically expire 12 hours
+ // after first being created).
+ sessionManager := scs.New()
+ sessionManager.Store = mysqlstore.New(db)
+ sessionManager.Lifetime = 12 * time.Hour
+
// Initialize a new template cache...
templateCache, err := newTemplateCache()
if err != nil {
@@ -51,9 +62,10 @@ func main() {
}
app := &Application{
- logger: logger,
- snippets: &models.SnippetModel{DB: db},
- templateCache: templateCache,
+ logger: logger,
+ snippets: &models.SnippetModel{DB: db},
+ templateCache: templateCache,
+ sessionManager: sessionManager,
}
logger.Info("starting server", "addr", *addr)
@@ -61,7 +73,7 @@ func main() {
// Because the err variable is now already declared in the code above, we need
// to use the assignment operator = here, instead of the := 'declare and assign'
// operator.
- err = http.ListenAndServe(*addr, app.routes())
+ err = http.ListenAndServe(*addr, app.sessionManager.LoadAndSave(app.routes()))
logger.Error(err.Error())
os.Exit(1)
}
diff --git a/cmd/web/templates.go b/cmd/web/templates.go
index 7cc669c..2a11111 100644
--- a/cmd/web/templates.go
+++ b/cmd/web/templates.go
@@ -17,6 +17,7 @@ type templateData struct {
Snippet models.Snippet
Snippets []models.Snippet
Form any
+ Flash string
}
func newTemplateCache() (map[string]*template.Template, error) {
diff --git a/docker/mysql/docker/mysql/init.sql b/docker/mysql/docker/mysql/init.sql
index 5fefb9c..d2dad82 100644
--- a/docker/mysql/docker/mysql/init.sql
+++ b/docker/mysql/docker/mysql/init.sql
@@ -34,3 +34,8 @@ INSERT INTO snippets (title, content, created, expires) VALUES (
);
+CREATE TABLE sessions (
+ token CHAR(43) PRIMARY KEY,
+ data BLOB NOT NULL,
+ expiry TIMESTAMP(6) NOT NULL
+);
\ No newline at end of file
diff --git a/go.mod b/go.mod
index 2406b07..3230f6e 100644
--- a/go.mod
+++ b/go.mod
@@ -4,5 +4,7 @@ go 1.24.1
require (
filippo.io/edwards25519 v1.1.0 // indirect
+ github.com/alexedwards/scs/mysqlstore v0.0.0-20250417082927-ab20b3feb5e9 // indirect
+ github.com/alexedwards/scs/v2 v2.8.0 // indirect
github.com/go-sql-driver/mysql v1.9.1 // indirect
)
diff --git a/go.sum b/go.sum
index 94796bd..4a1167c 100644
--- a/go.sum
+++ b/go.sum
@@ -1,4 +1,9 @@
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-20250417082927-ab20b3feb5e9 h1:HsYYLdEqKkjHrnt77Tiu8hnD4TIswIa+czpnlJldIJs=
+github.com/alexedwards/scs/mysqlstore v0.0.0-20250417082927-ab20b3feb5e9/go.mod h1:p8jK3D80sw1PFrCSdlcJF1O75bp55HqbgDyyCLM0FrE=
+github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
+github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
+github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI=
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=
diff --git a/ui/html/base.tmpl b/ui/html/base.tmpl
index ce037ea..1e02193 100644
--- a/ui/html/base.tmpl
+++ b/ui/html/base.tmpl
@@ -16,6 +16,10 @@
{{template "nav" .}}
+
+ {{with .Flash}}
+ {{.}}
+ {{end}}
{{template "main" .}}