lets-go:11.0 user signup without peeking

deliberately skipping salting and encrypting passwords at this point. intending
to approach this in tandem with authenication.

also starting to want db migrations, but trying not to get distracted.
This commit is contained in:
tamsin johnson 2024-02-07 11:07:29 -08:00
parent 7479313ee8
commit 4f2dc018da
6 changed files with 133 additions and 0 deletions
snippetbox
cmd/web
db/initdb.d
internal/models
ui/html/pages

@ -101,3 +101,50 @@ func (app *application) snippetCreatePost(w http.ResponseWriter, r *http.Request
http.Redirect(w, r, fmt.Sprintf("/snippet/view/%d", id), http.StatusSeeOther) http.Redirect(w, r, fmt.Sprintf("/snippet/view/%d", id), http.StatusSeeOther)
} }
type userSignupForm struct {
Username string `form:"username"`
Password string `form:"password"`
validator.Validator `form:"-"`
}
// userSignup ...
func (app *application) userSignup(w http.ResponseWriter, r *http.Request) {
data := app.newTemplateData(r)
data.Form = userSignupForm{}
app.render(w, r, http.StatusOK, "signup.tmpl", data)
}
// userSignup ...
func (app *application) userSignupPost(w http.ResponseWriter, r *http.Request) {
var form userSignupForm
err := app.decodePostForm(r, &form)
if err != nil {
app.clientError(w, http.StatusBadRequest)
return
}
form.CheckField(validator.NotBlank(form.Username), "username", "This field cannot be blank")
form.CheckField(validator.NotBlank(form.Password), "password", "This field cannot be blank")
if !form.Valid() {
data := app.newTemplateData(r)
data.Form = form
app.render(w, r, http.StatusUnprocessableEntity, "signup.tmpl", data)
return
}
id, err := app.users.Insert(form.Username, form.Password)
if err != nil {
app.serverError(w, r, err)
return
}
app.sessionManager.Put(r.Context(), "flash", fmt.Sprintf("CREATED A USER! (%d)", id))
http.Redirect(w, r, "/", http.StatusSeeOther)
}

@ -23,6 +23,7 @@ import (
type application struct { type application struct {
logger *slog.Logger logger *slog.Logger
snippets *models.SnippetModel snippets *models.SnippetModel
users *models.UserModel
templateCache map[string]*template.Template templateCache map[string]*template.Template
formDecoder *form.Decoder formDecoder *form.Decoder
sessionManager *scs.SessionManager sessionManager *scs.SessionManager
@ -63,6 +64,7 @@ func main() {
app := &application{ app := &application{
logger: logger, logger: logger,
snippets: &models.SnippetModel{DB: db}, snippets: &models.SnippetModel{DB: db},
users: &models.UserModel{DB: db},
templateCache: templateCache, templateCache: templateCache,
formDecoder: formDecoder, formDecoder: formDecoder,
sessionManager: sessionManager, sessionManager: sessionManager,

@ -29,6 +29,10 @@ func (app *application) routes() http.Handler {
router.Handler(http.MethodGet, "/snippet/view/:id", dynamic.ThenFunc(app.snippetView)) router.Handler(http.MethodGet, "/snippet/view/:id", dynamic.ThenFunc(app.snippetView))
router.Handler(http.MethodGet, "/snippet/create", dynamic.ThenFunc(app.snippetCreate)) router.Handler(http.MethodGet, "/snippet/create", dynamic.ThenFunc(app.snippetCreate))
router.Handler(http.MethodPost, "/snippet/create", dynamic.ThenFunc(app.snippetCreatePost)) router.Handler(http.MethodPost, "/snippet/create", dynamic.ThenFunc(app.snippetCreatePost))
router.Handler(http.MethodGet, "/user/signup", dynamic.ThenFunc(app.userSignup))
router.Handler(http.MethodPost, "/user/signup", dynamic.ThenFunc(app.userSignupPost))
// router.Handler(http.MethodGet, "/user/login", dynamic.ThenFunc(app.userLogin))
// router.Handler(http.MethodPost, "/user/login", dynamic.ThenFunc(app.userLoginPost))
standard := alice.New(app.recoverPanic, app.logRequest, secureHeaders) standard := alice.New(app.recoverPanic, app.logRequest, secureHeaders)

@ -20,3 +20,11 @@ CREATE TABLE sessions (
); );
CREATE INDEX sessions_expiry_idx on sessions(expiry); CREATE INDEX sessions_expiry_idx on sessions(expiry);
CREATE TABLE users (
id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20) NOT NULL,
password VARCHAR(20) NOT NULL,
created DATETIME NOT NULL,
UNIQUE (username)
)

@ -0,0 +1,50 @@
package models
import (
"database/sql"
"errors"
)
type User struct {
Username string
}
type UserModel struct {
DB *sql.DB
}
// Insert
func (m *UserModel) Insert(username string, password string) (int, error) {
stmt := `INSERT INTO users (username, password, created)
VALUES(?, ?, UTC_TIMESTAMP())`
result, err := m.DB.Exec(stmt, username, password)
if err != nil {
return 0, err
}
id, err := result.LastInsertId()
if err != nil {
return 0, err
}
return int(id), nil
}
// Get
func (m *UserModel) Get(id int) (User, error) {
stmt := "SELECT id, username FROM users WHERE id = ?"
var u User
err := m.DB.QueryRow(stmt, id).Scan(&u.Username)
if err != nil {
if errors.Is(err, sql.ErrNoRows) {
return User{}, ErrNoRecord
} else {
return User{}, err
}
}
return u, nil
}

@ -0,0 +1,22 @@
{{define "title"}}User Signup{{end}}
{{define "main"}}
<form action='/user/signup' method='POST'>
<div>
<label>Username:</label>
{{with .Form.FieldErrors.username}}
<label class='error'>{{.}}</label>
{{end}}
<input type='text' name='username' value='{{.Form.Username}}'>
</div>
<div>
<label>Password:</label>
{{with .Form.FieldErrors.password}}
<label class='error'>{{.}}</label>
{{end}}
<input type='text' name='password' value='{{.Form.Password}}'>
</div>
<div>
<input type='submit' value='Signup'>
</div>
{{end}}