lets-go:11.3 user signup

This commit is contained in:
tamsin johnson 2024-02-07 15:15:54 -08:00
parent f116991de2
commit 79978c4385
6 changed files with 68 additions and 42 deletions

View File

@ -104,6 +104,7 @@ func (app *application) snippetCreatePost(w http.ResponseWriter, r *http.Request
type userSignupForm struct { type userSignupForm struct {
Username string `form:"username"` Username string `form:"username"`
Email string `form:"email"`
Password string `form:"password"` Password string `form:"password"`
validator.Validator `form:"-"` validator.Validator `form:"-"`
} }
@ -129,7 +130,10 @@ func (app *application) userSignupPost(w http.ResponseWriter, r *http.Request) {
} }
form.CheckField(validator.NotBlank(form.Username), "username", "This field cannot be blank") form.CheckField(validator.NotBlank(form.Username), "username", "This field cannot be blank")
form.CheckField(validator.NotBlank(form.Email), "email", "This field cannot be blank")
form.CheckField(validator.Matches(form.Email, validator.EmailRX), "email", "This field must be a valid email address")
form.CheckField(validator.NotBlank(form.Password), "password", "This field cannot be blank") form.CheckField(validator.NotBlank(form.Password), "password", "This field cannot be blank")
form.CheckField(validator.MinChars(form.Password, 8), "password", "This field must be at least 8 characters long")
if !form.Valid() { if !form.Valid() {
data := app.newTemplateData(r) data := app.newTemplateData(r)
@ -138,13 +142,13 @@ func (app *application) userSignupPost(w http.ResponseWriter, r *http.Request) {
return return
} }
id, err := app.users.Insert(form.Username, form.Password) // id, err := app.users.Insert(form.Username, form.Email, form.Password)
if err != nil { // if err != nil {
app.serverError(w, r, err) // app.serverError(w, r, err)
return // return
} // }
app.sessionManager.Put(r.Context(), "flash", fmt.Sprintf("CREATED A USER! (%d)", id)) app.sessionManager.Put(r.Context(), "flash", fmt.Sprintf("CREATED A USER! (%d)", 1))
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
} }

View File

@ -23,8 +23,10 @@ CREATE INDEX sessions_expiry_idx on sessions(expiry);
CREATE TABLE users ( CREATE TABLE users (
id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT, id INTEGER NOT NULL PRIMARY KEY AUTO_INCREMENT,
username VARCHAR(20) NOT NULL, username VARCHAR(255) NOT NULL,
password VARCHAR(20) NOT NULL, email VARCHAR(255) NOT NULL,
hashed_password CHAR(60) NOT NULL,
created DATETIME NOT NULL, created DATETIME NOT NULL,
UNIQUE (username) );
)
ALTER TABLE users ADD CONSTRAINT users_uc_email UNIQUE (email);

View File

@ -4,4 +4,9 @@ import (
"errors" "errors"
) )
var ErrNoRecord = errors.New("models: no matching record found") var (
ErrNoRecord = errors.New("models: no matching record found")
ErrInvalidCredentials = errors.New("models: invalid credentials")
ErrDuplicateEmail = errors.New("models: duplicate email")
)

View File

@ -2,11 +2,15 @@ package models
import ( import (
"database/sql" "database/sql"
"errors" "time"
) )
type User struct { type User struct {
ID int
Username string Username string
Email string
HashedPassword []byte
Created time.Time
} }
type UserModel struct { type UserModel struct {
@ -14,37 +18,29 @@ type UserModel struct {
} }
// Insert // Insert
func (m *UserModel) Insert(username string, password string) (int, error) { func (m *UserModel) Insert(username, email, password string) (int, error) {
stmt := `INSERT INTO users (username, password, created) // stmt := `INSERT INTO users (username, email, password, created)
VALUES(?, ?, UTC_TIMESTAMP())` // VALUES(?, ?, ?, UTC_TIMESTAMP())`
result, err := m.DB.Exec(stmt, username, password) // result, err := m.DB.Exec(stmt, username, email, password)
if err != nil { // if err != nil {
return 0, err // return 0, err
// }
// id, err := result.LastInsertId()
// if err != nil {
// return 0, err
// }
return 0, nil
} }
id, err := result.LastInsertId() // Authenticate
if err != nil { func (m *UserModel) Authenticate(email int, password string) (int, error) {
return 0, err return 0, nil
} }
return int(id), nil // Exists
} func (m *UserModel) Exists(id int) (bool, error) {
return false, 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
} }

View File

@ -1,11 +1,14 @@
package validator package validator
import ( import (
"regexp"
"slices" "slices"
"strings" "strings"
"unicode/utf8" "unicode/utf8"
) )
var EmailRX = regexp.MustCompile("^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9]([a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$")
type Validator struct { type Validator struct {
FieldErrors map[string]string FieldErrors map[string]string
} }
@ -41,6 +44,15 @@ func MaxChars(value string, n int) bool {
return utf8.RuneCountInString(value) <= n return utf8.RuneCountInString(value) <= n
} }
func MinChars(value string, n int) bool {
return n <= utf8.RuneCountInString(value)
}
// Matches ...
func Matches(value string, rx *regexp.Regexp) bool {
return rx.MatchString(value)
}
// PermittedValue[T comparable] ... // PermittedValue[T comparable] ...
func PermittedValue[T comparable](value T, permittedValues ...T) bool { func PermittedValue[T comparable](value T, permittedValues ...T) bool {
return slices.Contains(permittedValues, value) return slices.Contains(permittedValues, value)

View File

@ -9,12 +9,19 @@
{{end}} {{end}}
<input type='text' name='username' value='{{.Form.Username}}'> <input type='text' name='username' value='{{.Form.Username}}'>
</div> </div>
<div>
<label>Email:</label>
{{with .Form.FieldErrors.email}}
<label class='error'>{{.}}</label>
{{end}}
<input type='text' name='email' value='{{.Form.Email}}'>
</div>
<div> <div>
<label>Password:</label> <label>Password:</label>
{{with .Form.FieldErrors.password}} {{with .Form.FieldErrors.password}}
<label class='error'>{{.}}</label> <label class='error'>{{.}}</label>
{{end}} {{end}}
<input type='text' name='password' value='{{.Form.Password}}'> <input type='text' name='password'>
</div> </div>
<div> <div>
<input type='submit' value='Signup'> <input type='submit' value='Signup'>