User signup

main
Drew Bednar 2 months ago
parent d59ae57c76
commit 202b8457b5

@ -8,4 +8,5 @@ require (
github.com/alexedwards/scs/sqlite3store v0.0.0-20240316134038-7e11d57e8885 // indirect
github.com/alexedwards/scs/v2 v2.8.0 // indirect
github.com/go-playground/form/v4 v4.2.1 // indirect
golang.org/x/crypto v0.32.0 // indirect
)

@ -8,3 +8,5 @@ github.com/go-playground/form/v4 v4.2.1/go.mod h1:q1a2BY+AQUUzhl6xA/6hBetay6dEIh
github.com/mattn/go-sqlite3 v1.14.6/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU=
github.com/mattn/go-sqlite3 v1.14.24 h1:tpSp2G2KyMnnQu99ngJ47EIkWVmliIizyZBfPrBWDRM=
github.com/mattn/go-sqlite3 v1.14.24/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y=
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=

@ -2,7 +2,12 @@ package model
import (
"database/sql"
"fmt"
"log/slog"
"time"
"github.com/mattn/go-sqlite3"
"golang.org/x/crypto/bcrypt"
)
type User struct {
@ -14,12 +19,41 @@ type User struct {
UpdatedAt time.Time
}
// TODD add logger to service
type UserService struct {
DB *sql.DB
}
func (u *UserService) Insert(name, email, password string) (int, error) {
return 0, nil
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), 12)
if err != nil {
return 0, err
}
stmt := `INSERT INTO users (name, email, hashed_password)
VALUES (?,?,?)`
result, err := u.DB.Exec(stmt, name, email, string(hashedPassword))
if err != nil {
slog.Debug(fmt.Sprintf("Error encounters on insert: %s", err.Error()))
// This is a assertion that err is of type sqlite3.Error. If it is ok is true.
if serr, ok := err.(sqlite3.Error); ok {
slog.Debug("Error is sqlite3.Error type.")
if serr.ExtendedCode == sqlite3.ErrConstraintUnique {
slog.Debug("Error is a unique contraint violation.")
return 0, ErrDuplicateEmail
}
}
return 0, err
}
lastId, err := result.LastInsertId()
if err != nil {
slog.Debug("An error occured when retrieving insert result id.")
return 0, err
}
slog.Debug(fmt.Sprintf("Inserted new user. User pk: %d", int(lastId)))
return int(lastId), nil
}
func (u *UserService) Authenticate(email, password string) (int, error) {

@ -291,7 +291,7 @@ func handleUserSignupGet(tc *TemplateCache, sm *scs.SessionManager) http.Handler
})
}
func handleUserSignupPost(logger *slog.Logger, tc *TemplateCache, fd *form.Decoder, sm *scs.SessionManager) http.Handler {
func handleUserSignupPost(logger *slog.Logger, tc *TemplateCache, fd *form.Decoder, sm *scs.SessionManager, userService *model.UserService) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
// Check that the provided name, email address and password are not blank.
@ -319,12 +319,30 @@ func handleUserSignupPost(logger *slog.Logger, tc *TemplateCache, fd *form.Decod
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")
// Todo Email allready in use validation
if !form.Valid() {
data := newTemplateData(r, sm)
data.Form = form
renderTemplate(w, r, tc, http.StatusUnprocessableEntity, "signup.go.tmpl", data)
}
fmt.Fprintln(w, "Creating new user")
_, err = userService.Insert(form.Name, form.Email, form.Password)
if err != nil {
if errors.Is(err, model.ErrDuplicateEmail) {
form.AddFieldError("email", "Email is already in use")
data := newTemplateData(r, sm)
data.Form = form
renderTemplate(w, r, tc, http.StatusUnprocessableEntity, "signup.go.tmpl", data)
} else {
serverError(w, r, err)
}
return
}
sm.Put(r.Context(), "flash", "Your signup was successful. Please log in.")
http.Redirect(w, r, "/user/login", http.StatusSeeOther)
})
}

@ -16,6 +16,7 @@ func addRoutes(mux *http.ServeMux,
db *sql.DB,
fd *form.Decoder,
sm *scs.SessionManager,
userService *model.UserService,
snippetService *model.SnippetService) http.Handler {
// /{$} is used to prevent subtree path patterns from acting like a wildcard
@ -31,7 +32,7 @@ func addRoutes(mux *http.ServeMux,
// mux.Handle("/", http.NotFoundHandler())
mux.Handle("GET /user/signup", sm.LoadAndSave(handleUserSignupGet(tc, sm)))
mux.Handle("POST /user/signup", sm.LoadAndSave(handleUserSignupPost(logger, tc, fd, sm)))
mux.Handle("POST /user/signup", sm.LoadAndSave(handleUserSignupPost(logger, tc, fd, sm, userService)))
mux.Handle("GET /user/login", sm.LoadAndSave(handleUserLoginGet()))
mux.Handle("POST /user/login", sm.LoadAndSave(handleUserLoginPost()))
mux.Handle("POST /user/logout", sm.LoadAndSave(handleUserLogoutPost()))

@ -39,7 +39,7 @@ func NewRatchetApp(logger *slog.Logger, tc *TemplateCache, db *sql.DB, sm *scs.S
// Mux Router implements the Handler interface. AKA it has a ServeHTTP receiver.
// SEE we can really clean things up by moving this into routes.go and handlers.go
wrappedMux := addRoutes(router, rs.logger, rs.templateCache, db, rs.formDecoder, sm, rs.snippetService)
wrappedMux := addRoutes(router, rs.logger, rs.templateCache, db, rs.formDecoder, sm, rs.userService, rs.snippetService)
rs.Handler = CommonHeaderMiddleware(wrappedMux)
rs.Handler = RequestLoggingMiddleware(rs.Handler, logger)
rs.Handler = RecoveryMiddleware(rs.Handler)

Loading…
Cancel
Save