Skip to content

Commit dcc4d8a

Browse files
authored
Merge pull request #14 from toniDefez/chapter7
Chapter7
2 parents 4a8fe67 + eea6f83 commit dcc4d8a

9 files changed

Lines changed: 89 additions & 26 deletions

File tree

cmd/web/handlers.go

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -57,9 +57,10 @@ func (app *Application) SnippetView(w http.ResponseWriter, r *http.Request) {
5757
return
5858
}
5959

60-
app.render(w, r, http.StatusOK, "view.tmpl", templateData{
61-
Snippet: snippet,
62-
})
60+
data := app.NewTemplateData(r)
61+
data.Snippet = snippet
62+
63+
app.render(w, r, http.StatusOK, "view.tmpl", *data)
6364

6465
}
6566

@@ -122,6 +123,8 @@ func (app *Application) SnippetCreatePost(w http.ResponseWriter, r *http.Request
122123
app.serverError(w, r, err)
123124
return
124125
}
126+
// Saving message flash
127+
app.sessionManager.Put(r.Context(), "flash", "Snippet created successfully!")
125128

126129
http.Redirect(w, r, fmt.Sprintf("/snippet/view/%d", id), http.StatusSeeOther)
127130
}

cmd/web/handlers_test.go

Lines changed: 36 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
"strings"
1010
"testing"
1111

12+
"github.com/alexedwards/scs/v2"
13+
"github.com/alexedwards/scs/v2/memstore"
1214
"snippetbox.tonidefez.net/internal/models"
1315
)
1416

@@ -85,13 +87,19 @@ func TestSnippetView(t *testing.T) {
8587
t.Fatal(err)
8688
}
8789

90+
// Create fake session manager
91+
sessionManager := scs.New()
92+
sessionManager.Store = memstore.New()
93+
8894
app := &Application{
89-
logger: dummyLogger,
90-
snippets: &models.MockSnippetModel{},
91-
templateCache: templateCache,
95+
logger: dummyLogger,
96+
snippets: &models.MockSnippetModel{},
97+
templateCache: templateCache,
98+
sessionManager: sessionManager,
9299
}
93100

94-
router := app.routes()
101+
//Be sure to manage session for each route
102+
router := app.sessionManager.LoadAndSave(app.routes())
95103
router.ServeHTTP(rr, req)
96104

97105
// verify status
@@ -115,18 +123,22 @@ func TestSnippetCreateGet(t *testing.T) {
115123
dummyLogger := slog.New(slog.NewTextHandler(io.Discard, nil))
116124
dummyDB := &models.SnippetModel{DB: nil}
117125
templateCache, err := newTemplateCache()
126+
// Create fake session manager
127+
sessionManager := scs.New()
128+
sessionManager.Store = memstore.New()
118129

119130
if err != nil {
120131
t.Fatal(err)
121132
}
122133

123134
app := &Application{
124-
logger: dummyLogger,
125-
snippets: dummyDB,
126-
templateCache: templateCache,
135+
logger: dummyLogger,
136+
snippets: dummyDB,
137+
templateCache: templateCache,
138+
sessionManager: sessionManager,
127139
}
128140

129-
router := app.routes()
141+
router := app.sessionManager.LoadAndSave(app.routes())
130142
router.ServeHTTP(rr, req)
131143

132144
// verify status
@@ -144,13 +156,18 @@ func TestSnippetCreatePost(t *testing.T) {
144156

145157
dummyLogger := slog.New(slog.NewTextHandler(io.Discard, nil))
146158

159+
// Create fake session manager
160+
sessionManager := scs.New()
161+
sessionManager.Store = memstore.New()
162+
147163
// simulate dependencies
148164
app := &Application{
149-
logger: dummyLogger,
150-
snippets: &models.MockSnippetModel{},
165+
logger: dummyLogger,
166+
snippets: &models.MockSnippetModel{},
167+
sessionManager: sessionManager,
151168
}
152169

153-
router := app.routes()
170+
router := app.sessionManager.LoadAndSave(app.routes())
154171
router.ServeHTTP(rr, req)
155172

156173
// Verificar redirección
@@ -172,8 +189,14 @@ func TestSnippetCreatePost_InvalidData(t *testing.T) {
172189
if err != nil {
173190
t.Fatal(err)
174191
}
192+
193+
// Create fake session manager
194+
sessionManager := scs.New()
195+
sessionManager.Store = memstore.New()
196+
175197
app := &Application{
176-
templateCache: templateCache,
198+
templateCache: templateCache,
199+
sessionManager: sessionManager,
177200
}
178201

179202
form := url.Values{}
@@ -186,7 +209,7 @@ func TestSnippetCreatePost_InvalidData(t *testing.T) {
186209

187210
rr := httptest.NewRecorder()
188211

189-
app.SnippetCreatePost(rr, req)
212+
app.sessionManager.LoadAndSave(http.HandlerFunc(app.SnippetCreatePost)).ServeHTTP(rr, req)
190213

191214
res := rr.Result()
192215
defer res.Body.Close()

cmd/web/helper.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import (
55
"fmt"
66
"net/http"
77
"runtime/debug"
8+
"time"
89

910
"snippetbox.tonidefez.net/internal/models"
1011
)
@@ -54,7 +55,14 @@ func (app *Application) render(w http.ResponseWriter, r *http.Request, status in
5455

5556
func (app *Application) NewTemplateData(r *http.Request) *templateData {
5657
return &templateData{
57-
Snippet: models.Snippet{},
58-
Snippets: []models.Snippet{},
58+
Snippet: models.Snippet{},
59+
Snippets: []models.Snippet{},
60+
CurrentYear: time.Now().Year(),
61+
// Use the PopString() method to retrieve the value for the "flash" key.
62+
// PopString() also deletes the key and value from the session data, so it
63+
// acts like a one-time fetch. If there is no matching key in the session
64+
// data this will return the empty string.
65+
66+
Flash: app.sessionManager.PopString(r.Context(), "flash"),
5967
}
6068
}

cmd/web/main.go

Lines changed: 20 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ import (
77
"net/http"
88
"os"
99
"text/template"
10+
"time"
1011

12+
"github.com/alexedwards/scs/mysqlstore"
13+
"github.com/alexedwards/scs/v2"
1114
_ "github.com/go-sql-driver/mysql"
1215
"snippetbox.tonidefez.net/internal/models"
1316
)
@@ -16,10 +19,10 @@ import (
1619
// web application. For now we'll only include the structured logger, but we'll
1720
// add more to this as the build progresses.
1821
type Application struct {
19-
logger *slog.Logger
20-
// implemeting an interfaz we can make with differents dependencies DIP
21-
snippets models.SnippetModeler
22-
templateCache map[string]*template.Template
22+
logger *slog.Logger
23+
snippets models.SnippetModeler
24+
templateCache map[string]*template.Template
25+
sessionManager *scs.SessionManager
2326
}
2427

2528
func main() {
@@ -43,6 +46,14 @@ func main() {
4346
// before the main() function exits.
4447
defer db.Close()
4548

49+
// Use the scs.New() function to initialize a new session manager. Then we
50+
// configure it to use our MySQL database as the session store, and set a
51+
// lifetime of 12 hours (so that sessions automatically expire 12 hours
52+
// after first being created).
53+
sessionManager := scs.New()
54+
sessionManager.Store = mysqlstore.New(db)
55+
sessionManager.Lifetime = 12 * time.Hour
56+
4657
// Initialize a new template cache...
4758
templateCache, err := newTemplateCache()
4859
if err != nil {
@@ -51,17 +62,18 @@ func main() {
5162
}
5263

5364
app := &Application{
54-
logger: logger,
55-
snippets: &models.SnippetModel{DB: db},
56-
templateCache: templateCache,
65+
logger: logger,
66+
snippets: &models.SnippetModel{DB: db},
67+
templateCache: templateCache,
68+
sessionManager: sessionManager,
5769
}
5870

5971
logger.Info("starting server", "addr", *addr)
6072

6173
// Because the err variable is now already declared in the code above, we need
6274
// to use the assignment operator = here, instead of the := 'declare and assign'
6375
// operator.
64-
err = http.ListenAndServe(*addr, app.routes())
76+
err = http.ListenAndServe(*addr, app.sessionManager.LoadAndSave(app.routes()))
6577
logger.Error(err.Error())
6678
os.Exit(1)
6779
}

cmd/web/templates.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ type templateData struct {
1717
Snippet models.Snippet
1818
Snippets []models.Snippet
1919
Form any
20+
Flash string
2021
}
2122

2223
func newTemplateCache() (map[string]*template.Template, error) {

docker/mysql/docker/mysql/init.sql

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,8 @@ INSERT INTO snippets (title, content, created, expires) VALUES (
3434
);
3535

3636

37+
CREATE TABLE sessions (
38+
token CHAR(43) PRIMARY KEY,
39+
data BLOB NOT NULL,
40+
expiry TIMESTAMP(6) NOT NULL
41+
);

go.mod

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,5 +4,7 @@ go 1.24.1
44

55
require (
66
filippo.io/edwards25519 v1.1.0 // indirect
7+
github.com/alexedwards/scs/mysqlstore v0.0.0-20250417082927-ab20b3feb5e9 // indirect
8+
github.com/alexedwards/scs/v2 v2.8.0 // indirect
79
github.com/go-sql-driver/mysql v1.9.1 // indirect
810
)

go.sum

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,9 @@
11
filippo.io/edwards25519 v1.1.0 h1:FNf4tywRC1HmFuKW5xopWpigGjJKiJSV0Cqo0cJWDaA=
22
filippo.io/edwards25519 v1.1.0/go.mod h1:BxyFTGdWcka3PhytdK4V28tE5sGfRvvvRV7EaN4VDT4=
3+
github.com/alexedwards/scs/mysqlstore v0.0.0-20250417082927-ab20b3feb5e9 h1:HsYYLdEqKkjHrnt77Tiu8hnD4TIswIa+czpnlJldIJs=
4+
github.com/alexedwards/scs/mysqlstore v0.0.0-20250417082927-ab20b3feb5e9/go.mod h1:p8jK3D80sw1PFrCSdlcJF1O75bp55HqbgDyyCLM0FrE=
5+
github.com/alexedwards/scs/v2 v2.8.0 h1:h31yUYoycPuL0zt14c0gd+oqxfRwIj6SOjHdKRZxhEw=
6+
github.com/alexedwards/scs/v2 v2.8.0/go.mod h1:ToaROZxyKukJKT/xLcVQAChi5k6+Pn1Gvmdl7h3RRj8=
7+
github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI=
38
github.com/go-sql-driver/mysql v1.9.1 h1:FrjNGn/BsJQjVRuSa8CBrM5BWA9BWoXXat3KrtSb/iI=
49
github.com/go-sql-driver/mysql v1.9.1/go.mod h1:qn46aNg1333BRMNU69Lq93t8du/dwxI64Gl8i5p1WMU=

ui/html/base.tmpl

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,10 @@
1616
</header>
1717
{{template "nav" .}}
1818
<main>
19+
<!-- Display the flash message if one exists -->
20+
{{with .Flash}}
21+
<div class='flash'>{{.}}</div>
22+
{{end}}
1923
{{template "main" .}}
2024
</main>
2125
<footer>Powered by <a href='https://golang.org/'>Go</a></footer>

0 commit comments

Comments
 (0)