diff --git a/snippetbox/cmd/web/handlers.go b/snippetbox/cmd/web/handlers.go index d6bfef5..50f03dd 100644 --- a/snippetbox/cmd/web/handlers.go +++ b/snippetbox/cmd/web/handlers.go @@ -104,6 +104,7 @@ func (app *application) snippetCreatePost(w http.ResponseWriter, r *http.Request type userSignupForm struct { Username string `form:"username"` + Email string `form:"email"` Password string `form:"password"` 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.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.MinChars(form.Password, 8), "password", "This field must be at least 8 characters long") if !form.Valid() { data := app.newTemplateData(r) @@ -138,13 +142,13 @@ func (app *application) userSignupPost(w http.ResponseWriter, r *http.Request) { return } - id, err := app.users.Insert(form.Username, form.Password) - if err != nil { - app.serverError(w, r, err) - return - } + // id, err := app.users.Insert(form.Username, form.Email, form.Password) + // if err != nil { + // app.serverError(w, r, err) + // 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) } diff --git a/snippetbox/db/initdb.d/init.sql b/snippetbox/db/initdb.d/init.sql index eb83090..b940096 100644 --- a/snippetbox/db/initdb.d/init.sql +++ b/snippetbox/db/initdb.d/init.sql @@ -23,8 +23,10 @@ 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, + username VARCHAR(255) NOT NULL, + email VARCHAR(255) NOT NULL, + hashed_password CHAR(60) NOT NULL, created DATETIME NOT NULL, - UNIQUE (username) -) +); + +ALTER TABLE users ADD CONSTRAINT users_uc_email UNIQUE (email); diff --git a/snippetbox/internal/models/errors.go b/snippetbox/internal/models/errors.go index a70c7dc..2ee295b 100644 --- a/snippetbox/internal/models/errors.go +++ b/snippetbox/internal/models/errors.go @@ -4,4 +4,9 @@ import ( "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") + +) diff --git a/snippetbox/internal/models/users.go b/snippetbox/internal/models/users.go index b12fe4d..9ee2226 100644 --- a/snippetbox/internal/models/users.go +++ b/snippetbox/internal/models/users.go @@ -2,11 +2,15 @@ package models import ( "database/sql" - "errors" + "time" ) type User struct { - Username string + ID int + Username string + Email string + HashedPassword []byte + Created time.Time } type UserModel struct { @@ -14,37 +18,29 @@ type UserModel struct { } // Insert -func (m *UserModel) Insert(username string, password string) (int, error) { - stmt := `INSERT INTO users (username, password, created) - VALUES(?, ?, UTC_TIMESTAMP())` +func (m *UserModel) Insert(username, email, password string) (int, error) { + // stmt := `INSERT INTO users (username, email, password, created) + // VALUES(?, ?, ?, UTC_TIMESTAMP())` - result, err := m.DB.Exec(stmt, username, password) - if err != nil { - return 0, err - } + // result, err := m.DB.Exec(stmt, username, email, password) + // if err != nil { + // return 0, err + // } - id, err := result.LastInsertId() - if err != nil { - return 0, err - } + // id, err := result.LastInsertId() + // if err != nil { + // return 0, err + // } - return int(id), nil + return 0, 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 +// Authenticate +func (m *UserModel) Authenticate(email int, password string) (int, error) { + return 0, nil +} + +// Exists +func (m *UserModel) Exists(id int) (bool, error) { + return false, nil } diff --git a/snippetbox/internal/validator/validator.go b/snippetbox/internal/validator/validator.go index aebe156..5dc7e9e 100644 --- a/snippetbox/internal/validator/validator.go +++ b/snippetbox/internal/validator/validator.go @@ -1,11 +1,14 @@ package validator import ( + "regexp" "slices" "strings" "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 { FieldErrors map[string]string } @@ -41,6 +44,15 @@ func MaxChars(value string, n int) bool { 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] ... func PermittedValue[T comparable](value T, permittedValues ...T) bool { return slices.Contains(permittedValues, value) diff --git a/snippetbox/ui/html/pages/signup.tmpl b/snippetbox/ui/html/pages/signup.tmpl index 14d2e00..0943855 100644 --- a/snippetbox/ui/html/pages/signup.tmpl +++ b/snippetbox/ui/html/pages/signup.tmpl @@ -9,12 +9,19 @@ {{end}} +
+ + {{with .Form.FieldErrors.email}} + + {{end}} + +
{{with .Form.FieldErrors.password}} {{end}} - +