Compare commits

...

4 Commits

7 changed files with 164 additions and 0 deletions

View File

@ -30,6 +30,8 @@ func routes(app *config.AppConfig) http.Handler {
mux.Get("/reservation-summary", handlers.Repo.ReservationSummary) mux.Get("/reservation-summary", handlers.Repo.ReservationSummary)
mux.Get("/choose-room/{id}", handlers.Repo.ChooseRoom) mux.Get("/choose-room/{id}", handlers.Repo.ChooseRoom)
mux.Get("/book-room", handlers.Repo.BookRoom) mux.Get("/book-room", handlers.Repo.BookRoom)
mux.Get("/user/login", handlers.Repo.ShowLogin)
mux.Post("/user/login", handlers.Repo.PostShowLogin)
fileServer := http.FileServer(http.Dir("./static/")) fileServer := http.FileServer(http.Dir("./static/"))
mux.Handle("/static/*", http.StripPrefix("/static", fileServer)) mux.Handle("/static/*", http.StripPrefix("/static", fileServer))

View File

@ -10,6 +10,7 @@ import (
"go-udemy-web-1/internal/render" "go-udemy-web-1/internal/render"
"go-udemy-web-1/internal/repository" "go-udemy-web-1/internal/repository"
"go-udemy-web-1/internal/repository/dbrepo" "go-udemy-web-1/internal/repository/dbrepo"
"log"
"net/http" "net/http"
"strconv" "strconv"
"strings" "strings"
@ -451,3 +452,43 @@ func (m *Repository) BookRoom(w http.ResponseWriter, r *http.Request) {
m.App.Session.Put(r.Context(), "reservation", res) m.App.Session.Put(r.Context(), "reservation", res)
http.Redirect(w, r, "/make-reservation", http.StatusSeeOther) http.Redirect(w, r, "/make-reservation", http.StatusSeeOther)
} }
func (m *Repository) ShowLogin(w http.ResponseWriter, r *http.Request) {
render.Template(w, r, "login.page.tmpl", &models.TemplateData{
Form: forms.New(nil),
})
}
// PostShowLogin handles logging the user in
func (m *Repository) PostShowLogin(w http.ResponseWriter, r *http.Request) {
_ = m.App.Session.RenewToken(r.Context())
err := r.ParseForm()
if err != nil {
m.App.Session.Put(r.Context(), "error", "Can't parse form")
http.Redirect(w, r, "/user/login", http.StatusSeeOther)
return
}
email := r.Form.Get("email")
password := r.Form.Get("password")
form := forms.New(r.PostForm)
form.Required("email", "password")
if !form.Valid() {
// TODO
http.Redirect(w, r, "/user/login", http.StatusSeeOther)
return
}
id, _, err := m.DB.Authenticate(email, password)
if err != nil {
log.Println(err)
m.App.Session.Put(r.Context(), "error", "Invalid login credentials")
http.Redirect(w, r, "/user/login", http.StatusSeeOther)
return
}
m.App.Session.Put(r.Context(), "user_id", id)
m.App.Session.Put(r.Context(), "flash", "Logged in successfully")
http.Redirect(w, r, "/", http.StatusSeeOther)
}

View File

@ -2,8 +2,11 @@ package dbrepo
import ( import (
"context" "context"
"errors"
"go-udemy-web-1/internal/models" "go-udemy-web-1/internal/models"
"time" "time"
"golang.org/x/crypto/bcrypt"
) )
func (m *postgresDBRepo) AllUsers() bool { func (m *postgresDBRepo) AllUsers() bool {
@ -146,3 +149,65 @@ func (m *postgresDBRepo) GetRoomById(id int) (models.Room, error) {
} }
return room, nil return room, nil
} }
// GetUserByID gets a user by id
func (m *postgresDBRepo) GetUserByID(id int) (models.User, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
query := `select id, first_name, last_name, email, password, access_level, created_at, updated_at
from users where id = $1`
row := m.DB.QueryRowContext(ctx, query, id)
var u models.User
err := row.Scan(
&u.ID,
&u.FirstName,
&u.LastName,
&u.Email,
&u.Password,
&u.AccessLevel,
&u.CreatedAt,
&u.UpdatedAt,
)
if err != nil {
return models.User{}, err
}
return u, nil
}
// UpdateUser updates a user in the database
func (m *postgresDBRepo) UpdateUser(u models.User) error {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
query := `update users set first_name = $1, last_name = $2, email = $3, access_level = $4, updated_at = $5`
_, err := m.DB.ExecContext(ctx, query, u.FirstName, u.LastName, u.Email, u.AccessLevel, time.Now())
if err != nil {
return err
}
return nil
}
// Authenticate authenticates a user
func (m *postgresDBRepo) Authenticate(email, testPassword string) (int, string, error) {
ctx, cancel := context.WithTimeout(context.Background(), 3*time.Second)
defer cancel()
var id int
var hashedPassword string
row := m.DB.QueryRowContext(ctx, "select id, password from users where email = $1", email)
err := row.Scan(&id, &hashedPassword)
if err != nil {
return id, "", err
}
err = bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(testPassword))
if err == bcrypt.ErrMismatchedHashAndPassword {
return 0, "", errors.New("incorrect password")
} else if err != nil {
return 0, "", err
}
return id, hashedPassword, nil
}

View File

@ -64,3 +64,20 @@ func (m *testDBRepo) GetRoomById(id int) (models.Room, error) {
} }
return room, nil return room, nil
} }
// GetUserByID gets a user by id
func (m *testDBRepo) GetUserByID(id int) (models.User, error) {
var u models.User
return u, nil
}
// UpdateUser updates a user in the database
func (m *testDBRepo) UpdateUser(u models.User) error {
return nil
}
// Authenticate authenticates a user
func (m *testDBRepo) Authenticate(email, testPassword string) (int, string, error) {
return 1, "", nil
}

View File

@ -13,4 +13,7 @@ type DatabaseRepo interface {
SearchAvailabilityByDatesByRoomID(start, end time.Time, roomID int) (bool, error) SearchAvailabilityByDatesByRoomID(start, end time.Time, roomID int) (bool, error)
SearchAvailabilityForAllRooms(start, end time.Time) ([]models.Room, error) SearchAvailabilityForAllRooms(start, end time.Time) ([]models.Room, error)
GetRoomById(id int) (models.Room, error) GetRoomById(id int) (models.Room, error)
GetUserByID(id int) (models.User, error)
UpdateUser(u models.User) error
Authenticate(email, testPassword string) (int, string, error)
} }

View File

@ -50,6 +50,9 @@
<li class="nav-item"> <li class="nav-item">
<a class="nav-link" href="/contact">Contact</a> <a class="nav-link" href="/contact">Contact</a>
</li> </li>
<li class="nav-item">
<a class="nav-link" href="/user/login">Login</a>
</li>
</ul> </ul>
</div> </div>
</div> </div>

33
templates/login.page.tmpl Normal file
View File

@ -0,0 +1,33 @@
{{template "base" .}}
{{define "content"}}
<div class="container">
<div class="row">
<div class="col">
<h1>Login</h1>
<form method="post" action="/user/login">
<input type="hidden" name="csrf_token" value="{{.CSRFToken}}">
<div class="form-group mt-5">
<label for="email">Email:</label>
{{with .Form.Errors.Get "email"}}
<label class="text-danger">{{.}}</label>
{{end}}
<input type="text" name="email" id="email" class="form-control {{with .Form.Errors.Get "email"}} is-invalid {{end}}"
value="" required autocomplete="off">
</div>
<div class="form-group mt-5">
<label for="password">Password:</label>
{{with .Form.Errors.Get "password"}}
<label class="text-danger">{{.}}</label>
{{end}}
<input type="text" name="password" id="password" class="form-control {{with .Form.Errors.Get "password"}} is-invalid {{end}}"
value="" required autocomplete="off">
</div>
<hr>
<input type="submit" class="btn btn-primary" value="Submit">
</form>
</div>
</div>
</div>
{{end}}