Add an ‘Account’ page to the application
Your goal for this exercise is to add a new ‘Your Account’ page to the application. It should be mapped to a new GET /account/view
route and display the name, email address, and signup date for the currently authenticated user, similar to this:

Step 1
In the internal/models/users.go
file create a new UserModel.Get()
method. This should accept the ID of a user as a parameter, and return a User
struct containing all the information for this user (except for their hashed password, which we don’t need). If no user is found with the ID, it should return an ErrNoRecord
error.
Also, update the UserModelInterface
type to include this new Get()
method, and add a corresponding method to our mock mocks.UserModel
so that it continues to satisfy the interface.
Step 2
Create a GET /account/view
route which maps to a new accountView
handler. The route should be restricted to authenticated users only.
Step 3
Update the accountView
handler to get the "authenticatedUserID"
from the session, fetch the details of the relevant user from the database (using the new UserModel.Get()
method), and dump them out in a plain text HTTP response. If no user matching the "authenticatedUserID"
from the session could be found, redirect the client to GET /user/login
to force re-authentication.
When you visit https://localhost:4000/account/view
in your browser as an authenticated user, you should get a response similar to this:

Step 4
Create a new ui/html/pages/account.tmpl
file which displays the user information in a table. Then update the accountView
handler to render this new template, passing through the user’s details via the templateData
struct.
Step 5
Additionally, update the main navigation bar for the site to include a link to the view account page (visible to authenticated users only). Then sanity check that the new page and navigation works as expected by visiting https://localhost:4000/account/view
in your browser while logged in.
Suggested code
Suggested code for step 1
package models ... type UserModelInterface interface { Insert(name, email, password string) error Authenticate(email, password string) (int, error) Exists(id int) (bool, error) Get(id int) (User, error) } ... func (m *UserModel) Get(id int) (User, error) { var user User stmt := `SELECT id, name, email, created FROM users WHERE id = ?` err := m.DB.QueryRow(stmt, id).Scan(&user.ID, &user.Name, &user.Email, &user.Created) if err != nil { if errors.Is(err, sql.ErrNoRows) { return User{}, ErrNoRecord } else { return User{}, err } } return user, nil }
package mocks import ( "time" // New import "snippetbox.alexedwards.net/internal/models" ) ... func (m *UserModel) Get(id int) (models.User, error) { if id == 1 { u := models.User{ ID: 1, Name: "Alice", Email: "alice@example.com", Created: time.Now(), } return u, nil } return models.User{}, models.ErrNoRecord }
Suggested code for step 2
... func (app *application) accountView(w http.ResponseWriter, r *http.Request) { // Some code will go here later... }
package main ... func (app *application) routes() http.Handler { mux := http.NewServeMux() mux.Handle("GET /static/", http.FileServerFS(ui.Files)) mux.HandleFunc("GET /ping", ping) dynamic := alice.New(app.sessionManager.LoadAndSave, noSurf, app.authenticate) mux.Handle("GET /{$}", dynamic.ThenFunc(app.home)) mux.Handle("GET /about", dynamic.ThenFunc(app.about)) mux.Handle("GET /snippet/view/{id}", dynamic.ThenFunc(app.snippetView)) mux.Handle("GET /user/signup", dynamic.ThenFunc(app.userSignup)) mux.Handle("POST /user/signup", dynamic.ThenFunc(app.userSignupPost)) mux.Handle("GET /user/login", dynamic.ThenFunc(app.userLogin)) mux.Handle("POST /user/login", dynamic.ThenFunc(app.userLoginPost)) protected := dynamic.Append(app.requireAuthentication) mux.Handle("GET /snippet/create", protected.ThenFunc(app.snippetCreate)) mux.Handle("POST /snippet/create", protected.ThenFunc(app.snippetCreatePost)) // Add the view account route, using the protected middleware chain. mux.Handle("GET /account/view", protected.ThenFunc(app.accountView)) mux.Handle("POST /user/logout", protected.ThenFunc(app.userLogoutPost)) standard := alice.New(app.recoverPanic, app.logRequest, commonHeaders) return standard.Then(mux) }
Suggested code for step 3
... func (app *application) accountView(w http.ResponseWriter, r *http.Request) { userID := app.sessionManager.GetInt(r.Context(), "authenticatedUserID") user, err := app.users.Get(userID) if err != nil { if errors.Is(err, models.ErrNoRecord) { http.Redirect(w, r, "/user/login", http.StatusSeeOther) } else { app.serverError(w, r, err) } return } fmt.Fprintf(w, "%+v", user) }
Suggested code for step 4
... type templateData struct { CurrentYear int Snippet models.Snippet Snippets []models.Snippet Form any Flash string IsAuthenticated bool CSRFToken string User models.User } ...
{{define "title"}}Your Account{{end}} {{define "main"}} <h2>Your Account</h2> {{with .User}} <table> <tr> <th>Name</th> <td>{{.Name}}</td> </tr> <tr> <th>Email</th> <td>{{.Email}}</td> </tr> <tr> <th>Joined</th> <td>{{humanDate .Created}}</td> </tr> </table> {{end }} {{end}}
... func (app *application) accountView(w http.ResponseWriter, r *http.Request) { userID := app.sessionManager.GetInt(r.Context(), "authenticatedUserID") user, err := app.users.Get(userID) if err != nil { if errors.Is(err, models.ErrNoRecord) { http.Redirect(w, r, "/user/login", http.StatusSeeOther) } else { app.serverError(w, r, err) } return } data := app.newTemplateData(r) data.User = user app.render(w, r, http.StatusOK, "account.tmpl", data) }
Suggested code for step 5
{{define "nav"}} <nav> <div> <a href='/'>Home</a> <a href='/about'>About</a> {{if .IsAuthenticated}} <a href='/snippet/create'>Create snippet</a> {{end}} </div> <div> {{if .IsAuthenticated}} <!-- Add the view account link for authenticated users --> <a href='/account/view'>Account</a> <form action='/user/logout' method='POST'> <input type='hidden' name='csrf_token' value='{{.CSRFToken}}'> <button>Logout</button> </form> {{else}} <a href='/user/signup'>Signup</a> <a href='/user/login'>Login</a> {{end}} </div> </nav> {{end}}