feat: improvements
- better error presentation - public wishlist view
This commit is contained in:
parent
fb658e16fa
commit
0cc6abe03b
|
|
@ -1,6 +1,7 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/gob"
|
||||||
"log"
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
|
|
@ -41,6 +42,8 @@ func (auth *AuthMiddleware) ExpectUser(r *http.Request) *db.User {
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewAuthMiddleware(protectedHandler http.Handler, publicHandler http.Handler) *AuthMiddleware {
|
func NewAuthMiddleware(protectedHandler http.Handler, publicHandler http.Handler) *AuthMiddleware {
|
||||||
|
gob.Register(&RegisterProps{})
|
||||||
|
gob.Register(&LoginProps{})
|
||||||
store := sessions.NewCookieStore([]byte(env.JwtSecret))
|
store := sessions.NewCookieStore([]byte(env.JwtSecret))
|
||||||
store.Options.MaxAge = 86_400
|
store.Options.MaxAge = 86_400
|
||||||
return &AuthMiddleware{store, protectedHandler, publicHandler}
|
return &AuthMiddleware{store, protectedHandler, publicHandler}
|
||||||
|
|
|
||||||
|
|
@ -1,28 +1,62 @@
|
||||||
package auth
|
package auth
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
sesh "lishwist/session"
|
||||||
"lishwist/templates"
|
"lishwist/templates"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type LoginGetProps struct {
|
type LoginProps struct {
|
||||||
|
GeneralError string
|
||||||
SuccessfulRegistration bool
|
SuccessfulRegistration bool
|
||||||
|
Username templates.InputProps
|
||||||
|
Password templates.InputProps
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewLoginProps() LoginProps {
|
||||||
|
return LoginProps{
|
||||||
|
Username: templates.InputProps{
|
||||||
|
Name: "username",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
Password: templates.InputProps{
|
||||||
|
Name: "password",
|
||||||
|
Type: "password",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (auth *AuthMiddleware) Login(w http.ResponseWriter, r *http.Request) {
|
func (auth *AuthMiddleware) Login(w http.ResponseWriter, r *http.Request) {
|
||||||
session, _ := auth.Store.Get(r, "lishwist_user")
|
session, _ := auth.Store.Get(r, "lishwist_user")
|
||||||
successfulReg, ok := session.Values["successful_registration"].(bool)
|
|
||||||
if ok {
|
props := NewLoginProps()
|
||||||
delete(session.Values, "successful_registration")
|
|
||||||
if err := session.Save(r, w); err != nil {
|
flash, err := sesh.GetFirstFlash(w, r, session, "login_props")
|
||||||
log.Println("Couldn't save session:", err)
|
if err != nil {
|
||||||
http.Error(w, "Something went wrong. Error code: Zuko", http.StatusInternalServerError)
|
http.Error(w, "Something went wrong. Error code: Zuko", http.StatusInternalServerError)
|
||||||
return
|
return
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
templates.Execute(w, "login.gotmpl", LoginGetProps{
|
flashProps, ok := flash.(*LoginProps)
|
||||||
SuccessfulRegistration: successfulReg,
|
if ok {
|
||||||
})
|
props.Username.Value = flashProps.Username.Value
|
||||||
|
|
||||||
|
props.GeneralError = flashProps.GeneralError
|
||||||
|
props.Username.Error = flashProps.Username.Error
|
||||||
|
props.Password.Error = flashProps.Password.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
flash, err = sesh.GetFirstFlash(w, r, session, "successful_registration")
|
||||||
|
if err != nil {
|
||||||
|
http.Error(w, "Something went wrong. Error code: Zuko", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
successfulReg, _ := flash.(bool)
|
||||||
|
if successfulReg {
|
||||||
|
props.SuccessfulRegistration = true
|
||||||
|
}
|
||||||
|
|
||||||
|
templates.Execute(w, "login.gotmpl", props)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,29 +18,36 @@ func (auth *AuthMiddleware) LoginPost(w http.ResponseWriter, r *http.Request) {
|
||||||
username := r.Form.Get("username")
|
username := r.Form.Get("username")
|
||||||
password := r.Form.Get("password")
|
password := r.Form.Get("password")
|
||||||
|
|
||||||
|
props := NewLoginProps()
|
||||||
|
props.Username.Value = username
|
||||||
|
|
||||||
user, err := db.GetUserByName(username)
|
user, err := db.GetUserByName(username)
|
||||||
if user == nil || err != nil {
|
if user == nil || err != nil {
|
||||||
time.Sleep(time.Second)
|
time.Sleep(time.Second)
|
||||||
http.Error(w, "Username or password invalid", http.StatusUnauthorized)
|
props.GeneralError = "Username or password invalid"
|
||||||
|
auth.RedirectWithFlash(w, r, "/", "login_props", &props)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
passHash, err := user.GetPassHash()
|
passHash, err := user.GetPassHash()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Something went wrong. Error code: Momo", http.StatusInternalServerError)
|
props.GeneralError = "Something went wrong. Error code: Momo"
|
||||||
|
auth.RedirectWithFlash(w, r, "/", "login_props", &props)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
err = bcrypt.CompareHashAndPassword(passHash, []byte(password))
|
err = bcrypt.CompareHashAndPassword(passHash, []byte(password))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Username or password invalid", http.StatusUnauthorized)
|
props.GeneralError = "Username or password invalid"
|
||||||
|
auth.RedirectWithFlash(w, r, "/", "login_props", &props)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session, err := auth.Store.Get(r, "lishwist_user")
|
session, err := auth.Store.Get(r, "lishwist_user")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Couldn't get jwt:", err)
|
log.Println("Couldn't get jwt:", err)
|
||||||
http.Error(w, "Something went wrong. Error code: Sokka", http.StatusInternalServerError)
|
props.GeneralError = "Something went wrong. Error code: Sokka"
|
||||||
|
auth.RedirectWithFlash(w, r, "/", "login_props", &props)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
session.Values["authorized"] = true
|
session.Values["authorized"] = true
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package auth
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (auth *AuthMiddleware) RedirectWithFlash(w http.ResponseWriter, r *http.Request, url string, key string, flash any) {
|
||||||
|
session, _ := auth.Store.Get(r, "lishwist_user")
|
||||||
|
session.AddFlash(flash, key)
|
||||||
|
if err := session.Save(r, w); err != nil {
|
||||||
|
log.Println("Couldn't save session:", err)
|
||||||
|
http.Error(w, "Something went wrong. Error code: Zuko", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
http.Redirect(w, r, url, http.StatusSeeOther)
|
||||||
|
}
|
||||||
|
|
@ -5,10 +5,61 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
|
|
||||||
"lishwist/db"
|
"lishwist/db"
|
||||||
|
"lishwist/templates"
|
||||||
|
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type RegisterProps struct {
|
||||||
|
GeneralError string
|
||||||
|
Username templates.InputProps
|
||||||
|
Password templates.InputProps
|
||||||
|
ConfirmPassword templates.InputProps
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRegisterProps() RegisterProps {
|
||||||
|
return RegisterProps{
|
||||||
|
GeneralError: "",
|
||||||
|
Username: templates.InputProps{
|
||||||
|
Name: "username",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
Password: templates.InputProps{
|
||||||
|
Type: "password",
|
||||||
|
Name: "newPassword",
|
||||||
|
Required: true,
|
||||||
|
MinLength: 5,
|
||||||
|
},
|
||||||
|
ConfirmPassword: templates.InputProps{
|
||||||
|
Type: "password",
|
||||||
|
Name: "confirmPassword",
|
||||||
|
Required: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (auth *AuthMiddleware) Register(w http.ResponseWriter, r *http.Request) {
|
||||||
|
props := NewRegisterProps()
|
||||||
|
|
||||||
|
session, _ := auth.Store.Get(r, "lishwist_user")
|
||||||
|
if flashes := session.Flashes("register_props"); len(flashes) > 0 {
|
||||||
|
flashProps, _ := flashes[0].(*RegisterProps)
|
||||||
|
props.Username.Value = flashProps.Username.Value
|
||||||
|
|
||||||
|
props.GeneralError = flashProps.GeneralError
|
||||||
|
props.Username.Error = flashProps.Username.Error
|
||||||
|
props.ConfirmPassword.Error = flashProps.ConfirmPassword.Error
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := session.Save(r, w); err != nil {
|
||||||
|
log.Println("Couldn't save session:", err)
|
||||||
|
http.Error(w, "Something went wrong. Error code: Zuko", http.StatusInternalServerError)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
templates.Execute(w, "register.gotmpl", props)
|
||||||
|
}
|
||||||
|
|
||||||
func (auth *AuthMiddleware) RegisterPost(w http.ResponseWriter, r *http.Request) {
|
func (auth *AuthMiddleware) RegisterPost(w http.ResponseWriter, r *http.Request) {
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
http.Error(w, "Couldn't parse form", http.StatusBadRequest)
|
http.Error(w, "Couldn't parse form", http.StatusBadRequest)
|
||||||
|
|
@ -19,31 +70,40 @@ func (auth *AuthMiddleware) RegisterPost(w http.ResponseWriter, r *http.Request)
|
||||||
newPassword := r.Form.Get("newPassword")
|
newPassword := r.Form.Get("newPassword")
|
||||||
confirmPassword := r.Form.Get("confirmPassword")
|
confirmPassword := r.Form.Get("confirmPassword")
|
||||||
|
|
||||||
|
props := NewRegisterProps()
|
||||||
|
props.Username.Value = username
|
||||||
|
props.Password.Value = newPassword
|
||||||
|
props.ConfirmPassword.Value = confirmPassword
|
||||||
|
|
||||||
existingUser, _ := db.GetUserByName(username)
|
existingUser, _ := db.GetUserByName(username)
|
||||||
if existingUser != nil {
|
if existingUser != nil {
|
||||||
http.Error(w, "Username is taken", http.StatusBadRequest)
|
props.Username.Error = "Username is taken"
|
||||||
|
auth.RedirectWithFlash(w, r, "/register", "register_props", &props)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if newPassword != confirmPassword {
|
if newPassword != confirmPassword {
|
||||||
http.Error(w, "passwords didn't match", http.StatusBadRequest)
|
props.ConfirmPassword.Error = "Password didn't match"
|
||||||
|
auth.RedirectWithFlash(w, r, "/register", "register_props", &props)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost)
|
hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Something went wrong. Error code: Aang", http.StatusInternalServerError)
|
props.GeneralError = "Something went wrong. Error code: Aang"
|
||||||
|
auth.RedirectWithFlash(w, r, "/register", "register_props", &props)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
_, err = db.CreateUser(username, hashedPasswordBytes)
|
_, err = db.CreateUser(username, hashedPasswordBytes)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Something went wrong. Error code: Ozai", http.StatusInternalServerError)
|
props.GeneralError = "Something went wrong. Error code: Ozai"
|
||||||
|
auth.RedirectWithFlash(w, r, "/register", "register_props", &props)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
session, _ := auth.Store.Get(r, "lishwist_user")
|
session, _ := auth.Store.Get(r, "lishwist_user")
|
||||||
session.Values["successful_registration"] = true
|
session.AddFlash(true, "successful_registration")
|
||||||
if err := session.Save(r, w); err != nil {
|
if err := session.Save(r, w); err != nil {
|
||||||
log.Println("Couldn't save session:", err)
|
log.Println("Couldn't save session:", err)
|
||||||
http.Error(w, "Something went wrong. Error code: Zuko", http.StatusInternalServerError)
|
http.Error(w, "Something went wrong. Error code: Zuko", http.StatusInternalServerError)
|
||||||
|
|
|
||||||
|
|
@ -2,7 +2,7 @@ package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"lishwist/auth"
|
"lishwist/auth"
|
||||||
"log"
|
"lishwist/error"
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -19,8 +19,7 @@ func (ctx *Context) WishlistAdd(w http.ResponseWriter, r *http.Request) {
|
||||||
newGiftName := r.Form.Get("gift_name")
|
newGiftName := r.Form.Get("gift_name")
|
||||||
err := user.AddGift(newGiftName)
|
err := user.AddGift(newGiftName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to add gift: %s\n", err)
|
error.Page(w, "Failed to add gift.", http.StatusInternalServerError, err)
|
||||||
http.Error(w, "Failed to add gift.", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
|
@ -35,8 +34,7 @@ func (ctx *Context) WishlistDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
targets := r.Form["gift"]
|
targets := r.Form["gift"]
|
||||||
err := user.RemoveGifts(targets...)
|
err := user.RemoveGifts(targets...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to remove gifts: %s\n", err)
|
error.Page(w, "Failed to remove gifts.", http.StatusInternalServerError, err)
|
||||||
http.Error(w, "Failed to remove gifts.", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
http.Redirect(w, r, "/", http.StatusSeeOther)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
|
|
@ -45,7 +43,7 @@ func (ctx *Context) WishlistDelete(w http.ResponseWriter, r *http.Request) {
|
||||||
func (ctx *Context) ForeignWishlistPost(w http.ResponseWriter, r *http.Request) {
|
func (ctx *Context) ForeignWishlistPost(w http.ResponseWriter, r *http.Request) {
|
||||||
user := ctx.Auth.ExpectUser(r)
|
user := ctx.Auth.ExpectUser(r)
|
||||||
if err := r.ParseForm(); err != nil {
|
if err := r.ParseForm(); err != nil {
|
||||||
http.Error(w, err.Error(), http.StatusBadRequest)
|
error.Page(w, "Failed to parse form...", http.StatusBadRequest, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
userReference := r.PathValue("userReference")
|
userReference := r.PathValue("userReference")
|
||||||
|
|
@ -55,27 +53,25 @@ func (ctx *Context) ForeignWishlistPost(w http.ResponseWriter, r *http.Request)
|
||||||
unclaims := r.Form["claimed"]
|
unclaims := r.Form["claimed"]
|
||||||
err := user.ClaimGifts(claims, unclaims)
|
err := user.ClaimGifts(claims, unclaims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "Failed to update claim...", http.StatusInternalServerError)
|
error.Page(w, "Failed to update claim...", http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "complete":
|
case "complete":
|
||||||
claims := r.Form["claimed"]
|
claims := r.Form["claimed"]
|
||||||
err := user.CompleteGifts(claims)
|
err := user.CompleteGifts(claims)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to complete gifts: %s\n", err)
|
error.Page(w, "Failed to complete gifts...", http.StatusInternalServerError, nil)
|
||||||
http.Error(w, "Failed to complete gifts...", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "add":
|
case "add":
|
||||||
giftName := r.Form.Get("gift_name")
|
giftName := r.Form.Get("gift_name")
|
||||||
if giftName == "" {
|
if giftName == "" {
|
||||||
http.Error(w, "Gift name not provided", http.StatusBadRequest)
|
error.Page(w, "Gift name not provided", http.StatusBadRequest, nil)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
err := user.AddGiftToUser(userReference, giftName)
|
err := user.AddGiftToUser(userReference, giftName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to add gift idea to other user: %s\n", err)
|
error.Page(w, "Failed to add gift idea to other user...", http.StatusInternalServerError, err)
|
||||||
http.Error(w, "Failed to add gift idea to other user...", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
case "delete":
|
case "delete":
|
||||||
|
|
@ -84,8 +80,7 @@ func (ctx *Context) ForeignWishlistPost(w http.ResponseWriter, r *http.Request)
|
||||||
gifts := append(claims, unclaims...)
|
gifts := append(claims, unclaims...)
|
||||||
err := user.RemoveGifts(gifts...)
|
err := user.RemoveGifts(gifts...)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("Failed to remove gift idea for other user: %s\n", err)
|
error.Page(w, "Failed to remove gift idea for other user...", http.StatusInternalServerError, err)
|
||||||
http.Error(w, "Failed to remove gift idea for other user...", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
default:
|
default:
|
||||||
|
|
|
||||||
|
|
@ -2,16 +2,15 @@ package context
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"lishwist/db"
|
"lishwist/db"
|
||||||
|
"lishwist/error"
|
||||||
"lishwist/templates"
|
"lishwist/templates"
|
||||||
"log"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ForeignWishlistProps struct {
|
type foreignWishlistProps struct {
|
||||||
CurrentUserId string
|
CurrentUserId string
|
||||||
CurrentUserName string
|
CurrentUserName string
|
||||||
Username string
|
Username string
|
||||||
UserReference string
|
|
||||||
Gifts []db.Gift
|
Gifts []db.Gift
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -19,25 +18,48 @@ func (ctx *Context) ForeignWishlist(w http.ResponseWriter, r *http.Request) {
|
||||||
userReference := r.PathValue("userReference")
|
userReference := r.PathValue("userReference")
|
||||||
user := ctx.Auth.ExpectUser(r)
|
user := ctx.Auth.ExpectUser(r)
|
||||||
if user.Reference == userReference {
|
if user.Reference == userReference {
|
||||||
http.Error(w, "You can't view your own list, silly ;)", http.StatusForbidden)
|
http.Redirect(w, r, "/", http.StatusSeeOther)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
otherUser, err := db.GetUserByReference(userReference)
|
otherUser, err := db.GetUserByReference(userReference)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("An error occurred while fetching a user: %s\n", err)
|
error.Page(w, "An error occurred while fetching this user :(", http.StatusInternalServerError, err)
|
||||||
http.Error(w, "An error occurred while fetching this user :(", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if otherUser == nil {
|
if otherUser == nil {
|
||||||
http.Error(w, "User not found", http.StatusNotFound)
|
error.Page(w, "User not found", http.StatusNotFound, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
gifts, err := user.GetOtherUserGifts(userReference)
|
gifts, err := user.GetOtherUserGifts(userReference)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Printf("An error occurred while fetching %s's wishlist: %s\n", otherUser.Name, err)
|
error.Page(w, "An error occurred while fetching this user's wishlist :(", http.StatusInternalServerError, err)
|
||||||
http.Error(w, "An error occurred while fetching this user's wishlist :(", http.StatusInternalServerError)
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p := ForeignWishlistProps{CurrentUserId: user.Id, CurrentUserName: user.Name, Username: otherUser.Name, UserReference: userReference, Gifts: gifts}
|
p := foreignWishlistProps{CurrentUserId: user.Id, CurrentUserName: user.Name, Username: otherUser.Name, Gifts: gifts}
|
||||||
templates.Execute(w, "foreign_wishlist.gotmpl", p)
|
templates.Execute(w, "foreign_wishlist.gotmpl", p)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type publicForeignWishlistProps struct {
|
||||||
|
Username string
|
||||||
|
GiftCount int
|
||||||
|
}
|
||||||
|
|
||||||
|
func (ctx *Context) PublicForeignWishlist(w http.ResponseWriter, r *http.Request) {
|
||||||
|
userReference := r.PathValue("userReference")
|
||||||
|
otherUser, err := db.GetUserByReference(userReference)
|
||||||
|
if err != nil {
|
||||||
|
error.Page(w, "An error occurred while fetching this user :(", http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if otherUser == nil {
|
||||||
|
error.Page(w, "User not found", http.StatusNotFound, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
giftCount, err := otherUser.CountGifts()
|
||||||
|
if err != nil {
|
||||||
|
error.Page(w, "An error occurred while fetching data about this user :(", http.StatusInternalServerError, err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
p := publicForeignWishlistProps{Username: otherUser.Name, GiftCount: giftCount}
|
||||||
|
templates.Execute(w, "public_foreign_wishlist.gotmpl", p)
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@ import (
|
||||||
|
|
||||||
"lishwist/db"
|
"lishwist/db"
|
||||||
"lishwist/env"
|
"lishwist/env"
|
||||||
|
"lishwist/error"
|
||||||
"lishwist/templates"
|
"lishwist/templates"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
@ -20,12 +21,12 @@ func (ctx *Context) Home(w http.ResponseWriter, r *http.Request) {
|
||||||
user := ctx.Auth.ExpectUser(r)
|
user := ctx.Auth.ExpectUser(r)
|
||||||
gifts, err := user.GetGifts()
|
gifts, err := user.GetGifts()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "An error occurred while fetching your wishlist :(", http.StatusInternalServerError)
|
error.Page(w, "An error occurred while fetching your wishlist :(", http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
todo, err := user.GetTodo()
|
todo, err := user.GetTodo()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
http.Error(w, "An error occurred while fetching your wishlist :(", http.StatusInternalServerError)
|
error.Page(w, "An error occurred while fetching your wishlist :(", http.StatusInternalServerError, err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
p := HomeProps{Username: user.Name, Gifts: gifts, Todo: todo, Reference: user.Reference, HostUrl: env.HostUrl.String()}
|
p := HomeProps{Username: user.Name, Gifts: gifts, Todo: todo, Reference: user.Reference, HostUrl: env.HostUrl.String()}
|
||||||
|
|
|
||||||
10
db/user.go
10
db/user.go
|
|
@ -85,6 +85,16 @@ func (u *User) GetPassHash() ([]byte, error) {
|
||||||
return []byte(passHash), nil
|
return []byte(passHash), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (u *User) CountGifts() (int, error) {
|
||||||
|
stmt := "SELECT COUNT(gift.id) AS gift_count FROM gift JOIN user ON gift.recipient_id = user.id LEFT JOIN user AS claimant ON gift.claimant_id = claimant.id WHERE gift.creator_id = user.id AND user.id = ?"
|
||||||
|
var giftCount int
|
||||||
|
err := database.QueryRow(stmt, u.Id).Scan(&giftCount)
|
||||||
|
if err != nil {
|
||||||
|
return 0, err
|
||||||
|
}
|
||||||
|
return giftCount, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (u *User) GetGifts() ([]Gift, error) {
|
func (u *User) GetGifts() ([]Gift, error) {
|
||||||
stmt := "SELECT gift.id, gift.name, claimant.id, claimant.name, gift.sent FROM gift JOIN user ON gift.recipient_id = user.id LEFT JOIN user AS claimant ON gift.claimant_id = claimant.id WHERE gift.creator_id = user.id AND user.id = ?"
|
stmt := "SELECT gift.id, gift.name, claimant.id, claimant.name, gift.sent FROM gift JOIN user ON gift.recipient_id = user.id LEFT JOIN user AS claimant ON gift.claimant_id = claimant.id WHERE gift.creator_id = user.id AND user.id = ?"
|
||||||
rows, err := database.Query(stmt, u.Id)
|
rows, err := database.Query(stmt, u.Id)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package error
|
||||||
|
|
||||||
|
import (
|
||||||
|
"lishwist/templates"
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
)
|
||||||
|
|
||||||
|
type pageProps struct {
|
||||||
|
Message string
|
||||||
|
}
|
||||||
|
|
||||||
|
func Page(w http.ResponseWriter, publicMessage string, status int, err error) {
|
||||||
|
log.Printf("%s --- %s\n", publicMessage, err)
|
||||||
|
templates.Execute(w, "error_page.gotmpl", pageProps{publicMessage})
|
||||||
|
http.Error(w, "", http.StatusInternalServerError)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
"strconv"
|
||||||
|
|
||||||
|
"golang.org/x/crypto/bcrypt"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
password := []byte(os.Args[1])
|
||||||
|
cost, err := strconv.ParseInt(os.Args[2], 10, 0)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Failed to parse cost: ", err)
|
||||||
|
}
|
||||||
|
hash, err := bcrypt.GenerateFromPassword(password, int(cost))
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalln("Failed to hash: ", err)
|
||||||
|
}
|
||||||
|
fmt.Println(string(hash))
|
||||||
|
}
|
||||||
4
main.go
4
main.go
|
|
@ -8,7 +8,6 @@ import (
|
||||||
"lishwist/context"
|
"lishwist/context"
|
||||||
"lishwist/db"
|
"lishwist/db"
|
||||||
"lishwist/env"
|
"lishwist/env"
|
||||||
"lishwist/templates"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
@ -30,10 +29,11 @@ func main() {
|
||||||
Auth: authMiddleware,
|
Auth: authMiddleware,
|
||||||
}
|
}
|
||||||
|
|
||||||
publicMux.HandleFunc("GET /register", templates.Register)
|
publicMux.HandleFunc("GET /register", authMiddleware.Register)
|
||||||
publicMux.HandleFunc("POST /register", authMiddleware.RegisterPost)
|
publicMux.HandleFunc("POST /register", authMiddleware.RegisterPost)
|
||||||
publicMux.HandleFunc("GET /", authMiddleware.Login)
|
publicMux.HandleFunc("GET /", authMiddleware.Login)
|
||||||
publicMux.HandleFunc("POST /", authMiddleware.LoginPost)
|
publicMux.HandleFunc("POST /", authMiddleware.LoginPost)
|
||||||
|
publicMux.HandleFunc("GET /list/{userReference}", ctx.PublicForeignWishlist)
|
||||||
|
|
||||||
protectedMux.HandleFunc("GET /{$}", ctx.Home)
|
protectedMux.HandleFunc("GET /{$}", ctx.Home)
|
||||||
protectedMux.HandleFunc("POST /{$}", ctx.HomePost)
|
protectedMux.HandleFunc("POST /{$}", ctx.HomePost)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
package sesh
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"net/http"
|
||||||
|
|
||||||
|
"github.com/gorilla/sessions"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetFirstFlash(w http.ResponseWriter, r *http.Request, session *sessions.Session, key ...string) (any, error) {
|
||||||
|
flashes := session.Flashes(key...)
|
||||||
|
|
||||||
|
if len(flashes) < 1 {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
flash := flashes[0]
|
||||||
|
|
||||||
|
if err := session.Save(r, w); err != nil {
|
||||||
|
log.Println("Couldn't save session:", err)
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return flash, nil
|
||||||
|
}
|
||||||
|
|
@ -1,3 +1,17 @@
|
||||||
|
{{define "input"}}
|
||||||
|
<input style="padding: .375rem .75rem;" class="form-control{{if .Error}} is-invalid{{end}}" {{if .Type}}type="{{.Type}}" {{end}}name="{{.Name}}"
|
||||||
|
value="{{.Value}}" {{if .Required}}required {{end}}aria-describedby="{{if .Error}}{{.Name}}Error{{end}}">
|
||||||
|
{{with .Error}}
|
||||||
|
<div id="{{$.Name}}Error" class="invalid-feedback">
|
||||||
|
{{.}}
|
||||||
|
</div>
|
||||||
|
{{else}}
|
||||||
|
<div style="margin-top: .25rem; font-size: .875em;">
|
||||||
|
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
{{define "navbar"}}
|
||||||
|
<nav>
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/">Home</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "body"}}
|
||||||
|
<div class="container d-flex flex-grow-1 justify-content-center align-items-center flex-column">
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<p class="mb-0">{{.Message}}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
@ -84,7 +84,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<div class="input-group mt-3">
|
<div class="input-group mt-3">
|
||||||
<input class="form-control" name="gift_name" required aria-describedby="gift_name_help">
|
<input class="form-control" name="gift_name" required aria-describedby="gift_name_help" placeholder="Write a gift idea here" autofocus>
|
||||||
<button class="btn btn-primary" type="submit" name="intent" value="add">Add gift idea</button>
|
<button class="btn btn-primary" type="submit" name="intent" value="add">Add gift idea</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="gift_name_help" class="form-text">This will be invisible to {{.Username}}, but everyone else will be
|
<div id="gift_name_help" class="form-text">This will be invisible to {{.Username}}, but everyone else will be
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@
|
||||||
{{end}}
|
{{end}}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<div class="input-group">
|
<div class="input-group">
|
||||||
<input class="form-control" name="gift_name" required>
|
<input class="form-control" name="gift_name" required placeholder="Write a gift idea here" autofocus>
|
||||||
<button class="btn btn-primary" type="submit" name="intent" value="add_idea">Add gift idea</button>
|
<button class="btn btn-primary" type="submit" name="intent" value="add_idea">Add gift idea</button>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
|
||||||
|
|
@ -8,17 +8,22 @@
|
||||||
<p class="mb-0">Registration successful. Now you can login.</p>
|
<p class="mb-0">Registration successful. Now you can login.</p>
|
||||||
</div>
|
</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
{{with .GeneralError}}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<p class="mb-0">{{.}}</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<div class="d-flex flex-column gap-3">
|
<div class="d-flex flex-column">
|
||||||
<label>
|
<label>
|
||||||
Username
|
Username
|
||||||
<input class="form-control" name="username" required>
|
{{template "input" .Username}}
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Password
|
Password
|
||||||
<input class="form-control" name="password" type="password" required>
|
{{template "input" .Password}}
|
||||||
</label>
|
</label>
|
||||||
<div>
|
<div class="mb-3">
|
||||||
<a href="/register">Register</a>
|
<a href="/register">Register</a>
|
||||||
</div>
|
</div>
|
||||||
<input class="btn btn-primary" type="submit" value="Login">
|
<input class="btn btn-primary" type="submit" value="Login">
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
{{define "navbar"}}
|
||||||
|
<nav>
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/">Home</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "login_prompt"}}
|
||||||
|
<a href="/">Login</a> or <a href="/register">register</a>
|
||||||
|
{{end}}
|
||||||
|
|
||||||
|
{{define "body"}}
|
||||||
|
<div class="overflow-y-scroll flex-grow-1">
|
||||||
|
<div class="container py-5">
|
||||||
|
<section class="card">
|
||||||
|
<div class="card-body">
|
||||||
|
<h2>{{.Username}}'s list</h2>
|
||||||
|
{{if eq .GiftCount 0}}
|
||||||
|
<p>{{.Username}} doesn't have any gift ideas!</p>
|
||||||
|
<p>{{template "login_prompt"}} to add some! :^)</p>
|
||||||
|
{{else}}
|
||||||
|
{{if eq .GiftCount 1}}
|
||||||
|
<p>{{.Username}} only has one gift idea.</p>
|
||||||
|
<p>{{template "login_prompt"}} to claim it, or add more! :^)</p>
|
||||||
|
{{else}}
|
||||||
|
<p>{{.Username}} has {{.GiftCount}} gift ideas.</p>
|
||||||
|
<p>{{template "login_prompt"}} to claim an idea, or add more! :^)</p>
|
||||||
|
{{end}}
|
||||||
|
{{end}}
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
|
|
@ -1,9 +0,0 @@
|
||||||
package templates
|
|
||||||
|
|
||||||
import (
|
|
||||||
"net/http"
|
|
||||||
)
|
|
||||||
|
|
||||||
func Register(w http.ResponseWriter, r *http.Request) {
|
|
||||||
Execute(w, "register.gotmpl", nil)
|
|
||||||
}
|
|
||||||
|
|
@ -1,4 +1,11 @@
|
||||||
{{define "navbar"}}
|
{{define "navbar"}}
|
||||||
|
<nav>
|
||||||
|
<ul class="navbar-nav">
|
||||||
|
<li class="nav-item">
|
||||||
|
<a class="nav-link" href="/">Home</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
{{end}}
|
{{end}}
|
||||||
|
|
||||||
{{define "body"}}
|
{{define "body"}}
|
||||||
|
|
@ -7,19 +14,24 @@
|
||||||
<p>Your password will be stored in a safe, responsible manner; but don't trust my programming skills!</p>
|
<p>Your password will be stored in a safe, responsible manner; but don't trust my programming skills!</p>
|
||||||
<p class="mb-0">Maybe use a password here that you don't use for important things...</p>
|
<p class="mb-0">Maybe use a password here that you don't use for important things...</p>
|
||||||
</div>
|
</div>
|
||||||
|
{{with .GeneralError}}
|
||||||
|
<div class="alert alert-danger" role="alert">
|
||||||
|
<p class="mb-0">{{.}}</p>
|
||||||
|
</div>
|
||||||
|
{{end}}
|
||||||
<form method="post">
|
<form method="post">
|
||||||
<div class="d-flex flex-column gap-3">
|
<div class="d-flex flex-column">
|
||||||
<label>
|
<label>
|
||||||
Username
|
Username
|
||||||
<input class="form-control" name="username" required>
|
{{template "input" .Username}}
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Password
|
Password
|
||||||
<input class="form-control" name="newPassword" type="password" required minlength="5">
|
{{template "input" .Password}}
|
||||||
</label>
|
</label>
|
||||||
<label>
|
<label>
|
||||||
Confirm password
|
Confirm password
|
||||||
<input class="form-control" name="confirmPassword" type="password" required minlength="5">
|
{{template "input" .ConfirmPassword}}
|
||||||
</label>
|
</label>
|
||||||
<input class="btn btn-primary" type="submit" value="Register">
|
<input class="btn btn-primary" type="submit" value="Register">
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,15 @@ import (
|
||||||
"text/template"
|
"text/template"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
type InputProps struct {
|
||||||
|
Type string
|
||||||
|
Name string
|
||||||
|
Required bool
|
||||||
|
Value string
|
||||||
|
Error string
|
||||||
|
MinLength uint
|
||||||
|
}
|
||||||
|
|
||||||
var tmpls map[string]*template.Template = loadTemplates()
|
var tmpls map[string]*template.Template = loadTemplates()
|
||||||
|
|
||||||
func Execute(w http.ResponseWriter, name string, data any) {
|
func Execute(w http.ResponseWriter, name string, data any) {
|
||||||
|
|
@ -21,10 +30,14 @@ func loadTemplates() map[string]*template.Template {
|
||||||
loginTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/login.gotmpl"))
|
loginTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/login.gotmpl"))
|
||||||
registerTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/register.gotmpl"))
|
registerTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/register.gotmpl"))
|
||||||
foreignTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/foreign_wishlist.gotmpl"))
|
foreignTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/foreign_wishlist.gotmpl"))
|
||||||
|
publicForeignTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/public_foreign_wishlist.gotmpl"))
|
||||||
|
errorTmpl := template.Must(template.ParseFiles("templates/base.gotmpl", "templates/error_page.gotmpl"))
|
||||||
return map[string]*template.Template{
|
return map[string]*template.Template{
|
||||||
"home.gotmpl": homeTmpl,
|
"home.gotmpl": homeTmpl,
|
||||||
"login.gotmpl": loginTmpl,
|
"login.gotmpl": loginTmpl,
|
||||||
"register.gotmpl": registerTmpl,
|
"register.gotmpl": registerTmpl,
|
||||||
"foreign_wishlist.gotmpl": foreignTmpl,
|
"foreign_wishlist.gotmpl": foreignTmpl,
|
||||||
|
"public_foreign_wishlist.gotmpl": publicForeignTmpl,
|
||||||
|
"error_page.gotmpl": errorTmpl,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue