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

View File

@ -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)
}

View File

@ -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,

View File

@ -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)

View File

@ -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)
)

View File

@ -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
}

View File

@ -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}}