Skip to content

Commit

Permalink
Add admin role and screens
Browse files Browse the repository at this point in the history
  • Loading branch information
abneed committed Jul 24, 2022
1 parent e8231a7 commit 80e783c
Show file tree
Hide file tree
Showing 154 changed files with 41,067 additions and 18 deletions.
Binary file modified bookings
Binary file not shown.
12 changes: 12 additions & 0 deletions cmd/web/middleware.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"net/http"

"github.com/abneed/bookings/helpers"
"github.com/justinas/nosurf"
)

Expand All @@ -22,3 +23,14 @@ func NoSurf(next http.Handler) http.Handler {
func SessionLoad(next http.Handler) http.Handler {
return session.LoadAndSave(next)
}

func Auth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if !helpers.IsAuthenticated(r) {
session.Put(r.Context(), "error", "Log in first!")
http.Redirect(w, r, "/user/login", http.StatusSeeOther)
return
}
next.ServeHTTP(w, r)
})
}
18 changes: 18 additions & 0 deletions cmd/web/routes.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,26 @@ func routes(app *config.AppConfig) http.Handler {
mux.Post("/make-reservation", handlers.Repo.PostReservation)
mux.Get("/reservation-summary", handlers.Repo.ReservationSummary)

mux.Get("/user/login", handlers.Repo.ShowLogin)
mux.Post("/user/login", handlers.Repo.PostShowLogin)
mux.Get("/user/logout", handlers.Repo.Logout)

fileServer := http.FileServer(http.Dir("./static/"))
mux.Handle("/static/*", http.StripPrefix("/static", fileServer))

mux.Route("/admin", func(mux chi.Router) {
mux.Use(Auth)
mux.Get("/dashboard", handlers.Repo.AdminDashboard)

mux.Get("/reservations-new", handlers.Repo.AdminNewReservations)
mux.Get("/reservations-all", handlers.Repo.AdminAllReservations)
mux.Get("/reservations-calendar", handlers.Repo.AdminReservationsCalendar)
mux.Get("/process-reservation/{src}/{id}", handlers.Repo.AdminProcessReservation)
mux.Get("/delete-reservation/{src}/{id}", handlers.Repo.AdminDeleteReservation)

mux.Get("/reservations/{src}/{id}", handlers.Repo.AdminShowReservation)
mux.Post("/reservations/{src}/{id}", handlers.Repo.AdminPostShowReservation)
})

return mux
}
5 changes: 5 additions & 0 deletions helpers/helpers.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,8 @@ func ServerError(w http.ResponseWriter, err error) {
app.ErrorLog.Println(trace)
http.Error(w, http.StatusText(http.StatusInternalServerError), http.StatusInternalServerError)
}

func IsAuthenticated(r *http.Request) bool {
exists := app.Session.Exists(r.Context(), "user_id")
return exists
}
186 changes: 186 additions & 0 deletions internal/handlers/handlers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,18 +3,21 @@ package handlers
import (
"encoding/json"
"fmt"
"log"
"net/http"
"strconv"
"strings"
"time"

"github.com/abneed/bookings/helpers"
"github.com/abneed/bookings/internal/config"
"github.com/abneed/bookings/internal/driver"
"github.com/abneed/bookings/internal/forms"
"github.com/abneed/bookings/internal/models"
"github.com/abneed/bookings/internal/render"
"github.com/abneed/bookings/internal/repository"
"github.com/abneed/bookings/internal/repository/dbrepo"
"github.com/go-chi/chi"
)

// Repo the repository used by the handlers
Expand Down Expand Up @@ -479,3 +482,186 @@ func (m *Repository) BookRoom(w http.ResponseWriter, r *http.Request) {

http.Redirect(w, r, "/make-reservation", http.StatusSeeOther)
}

// ShowLogin shows the login screen
func (m *Repository) ShowLogin(w http.ResponseWriter, r *http.Request) {
render.Template(w, r, "login.page.html", &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 {
log.Println(err)
}

email := r.Form.Get("email")
password := r.Form.Get("password")

form := forms.New(r.PostForm)
form.Required("email", "password")
form.IsEmail("email")

if !form.Valid() {
render.Template(w, r, "login.page.html", &models.TemplateData{
Form: form,
})
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)
}

// Logout logs a user out
func (m *Repository) Logout(w http.ResponseWriter, r *http.Request) {
_ = m.App.Session.Destroy(r.Context())
_ = m.App.Session.RenewToken(r.Context())

http.Redirect(w, r, "/user/login", http.StatusSeeOther)
}

// AdminDashboard shows the admin dashboard screen
func (m *Repository) AdminDashboard(w http.ResponseWriter, r *http.Request) {
render.Template(w, r, "admin-dashboard.page.html", &models.TemplateData{})
}

// AdminAllReservations shows all reservations in admin tool
func (m *Repository) AdminAllReservations(w http.ResponseWriter, r *http.Request) {
reservations, err := m.DB.AllReservations()
if err != nil {
helpers.ServerError(w, err)
return
}

data := make(map[string]interface{})
data["reservations"] = reservations

render.Template(w, r, "admin-all-reservations.page.html", &models.TemplateData{
Data: data,
})
}

// AdminNewReservations shows all new reservations in admin tool
func (m *Repository) AdminNewReservations(w http.ResponseWriter, r *http.Request) {
reservations, err := m.DB.AllNewReservations()
if err != nil {
helpers.ServerError(w, err)
return
}

data := make(map[string]interface{})
data["reservations"] = reservations
render.Template(w, r, "admin-new-reservations.page.html", &models.TemplateData{
Data: data,
})
}

// AdminShowReservation shows the reservation in the admin tool
func (m *Repository) AdminShowReservation(w http.ResponseWriter, r *http.Request) {
exploded := strings.Split(r.RequestURI, "/")
id, err := strconv.Atoi(exploded[4])
if err != nil {
helpers.ServerError(w, err)
return
}

src := exploded[3]

stringMap := make(map[string]string)
stringMap["src"] = src

// get reservation from the database
res, err := m.DB.GetReservationByID(id)
if err != nil {
helpers.ServerError(w, err)
return
}

data := make(map[string]interface{})
data["reservation"] = res

render.Template(w, r, "admin-reservations-show.page.html", &models.TemplateData{
StringMap: stringMap,
Data: data,
Form: forms.New(nil),
})
}

// AdminPostShowReservation
func (m *Repository) AdminPostShowReservation(w http.ResponseWriter, r *http.Request) {
err := r.ParseForm()
if err != nil {
helpers.ServerError(w, err)
return
}

exploded := strings.Split(r.RequestURI, "/")
id, err := strconv.Atoi(exploded[4])
if err != nil {
helpers.ServerError(w, err)
return
}

src := exploded[3]

stringMap := make(map[string]string)
stringMap["src"] = src

res, err := m.DB.GetReservationByID(id)
if err != nil {
helpers.ServerError(w, err)
return
}

res.FirstName = r.Form.Get("first_name")
res.LastName = r.Form.Get("last_name")
res.Email = r.Form.Get("email")
res.Phone = r.Form.Get("phone")

err = m.DB.UpdateReservation(res)
if err != nil {
helpers.ServerError(w, err)
return
}

m.App.Session.Put(r.Context(), "flash", "Changes saved")
http.Redirect(w, r, fmt.Sprintf("/admin/reservations-%s", src), http.StatusSeeOther)
}

// AdminReservationsCalendar displays the reservation calendar
func (m *Repository) AdminReservationsCalendar(w http.ResponseWriter, r *http.Request) {
render.Template(w, r, "admin-reservations-calendar.page.html", &models.TemplateData{})
}

// AdminProcessReservation marks a reservation as processed
func (m *Repository) AdminProcessReservation(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.Atoi(chi.URLParam(r, "id"))
src := chi.URLParam(r, "src")
_ = m.DB.UpdateProcessedForReservation(id, 1)
m.App.Session.Put(r.Context(), "flash", "Reservation marked as processed")
http.Redirect(w, r, fmt.Sprintf("/admin/reservations-%s", src), http.StatusSeeOther)
}

// AdminDeleteReservation deletes a reservation
func (m *Repository) AdminDeleteReservation(w http.ResponseWriter, r *http.Request) {
id, _ := strconv.Atoi(chi.URLParam(r, "id"))
src := chi.URLParam(r, "src")
_ = m.DB.DeleteReservation(id)
m.App.Session.Put(r.Context(), "flash", "Reservation deleted")
http.Redirect(w, r, fmt.Sprintf("/admin/reservations-%s", src), http.StatusSeeOther)
}
1 change: 1 addition & 0 deletions internal/models/models.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ type Reservation struct {
CreatedAt time.Time
UpdatedAt time.Time
Room Room
Processed int
}

// RoomRestriction is the room restriction model
Expand Down
19 changes: 10 additions & 9 deletions internal/models/templatedata.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@ import "github.com/abneed/bookings/internal/forms"

// TemplateData holds data sent from handlers to templates
type TemplateData struct {
StringMap map[string]string
IntMap map[string]int
FloatMap map[string]float32
Data map[string]interface{}
CSRFToken string
Flash string
Warning string
Error string
Form *forms.Form
StringMap map[string]string
IntMap map[string]int
FloatMap map[string]float32
Data map[string]interface{}
CSRFToken string
Flash string
Warning string
Error string
Form *forms.Form
IsAuthenticated int
}
14 changes: 13 additions & 1 deletion internal/render/render.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,16 @@ import (
"html/template"
"net/http"
"path/filepath"
"time"

"github.com/abneed/bookings/internal/config"
"github.com/abneed/bookings/internal/models"
"github.com/justinas/nosurf"
)

var functions = template.FuncMap{}
var functions = template.FuncMap{
"humanDate": HumanDate,
}

var app *config.AppConfig
var pathToTemplates = "./templates"
Expand All @@ -23,11 +26,20 @@ func NewRenderer(a *config.AppConfig) {
app = a
}

// HumanDate returns time in YYYY-MM-DD format
func HumanDate(t time.Time) string {
return t.Format("2006-01-02")
}

// AddDefaultData adds data for all templates
func AddDefaultData(td *models.TemplateData, r *http.Request) *models.TemplateData {
td.Flash = app.Session.PopString(r.Context(), "flash")
td.Error = app.Session.PopString(r.Context(), "error")
td.Warning = app.Session.PopString(r.Context(), "warning")
td.CSRFToken = nosurf.Token(r)
if app.Session.Exists(r.Context(), "user_id") {
td.IsAuthenticated = 1
}
return td
}

Expand Down
Loading

0 comments on commit 80e783c

Please sign in to comment.