feat: register via json

also lots of refactoring (sorry)
This commit is contained in:
Teajey 2024-11-20 17:42:20 +09:00
parent f660d8c0eb
commit 782ffbbe6d
Signed by: Teajey
GPG Key ID: 970E790FE834A713
23 changed files with 469 additions and 342 deletions

95
server/api/register.go Normal file
View File

@ -0,0 +1,95 @@
package api
import (
"log"
"lishwist/db"
"lishwist/templates"
"golang.org/x/crypto/bcrypt"
)
type RegisterProps struct {
GeneralError string `json:",omitempty"`
Username templates.InputProps
Password templates.InputProps
ConfirmPassword templates.InputProps
}
func (p *RegisterProps) Validate() (valid bool) {
if p.Password.Value != p.ConfirmPassword.Value {
p.ConfirmPassword.Error = "Passwords didn't match"
valid = false
}
if !p.Username.Validate() {
valid = false
}
if !p.Password.Validate() {
valid = false
}
if !p.ConfirmPassword.Validate() {
valid = false
}
return
}
func NewRegisterProps(usernameVal, passwordVal, confirmPassVal string) *RegisterProps {
return &RegisterProps{
GeneralError: "",
Username: templates.InputProps{
Name: "username",
Required: true,
MinLength: 4,
Value: usernameVal,
},
Password: templates.InputProps{
Type: "password",
Name: "newPassword",
Required: true,
MinLength: 5,
Value: passwordVal,
},
ConfirmPassword: templates.InputProps{
Type: "password",
Name: "confirmPassword",
Required: true,
Value: confirmPassVal,
},
}
}
func Register(username, newPassword, confirmPassword string) *RegisterProps {
props := NewRegisterProps(username, newPassword, confirmPassword)
valid := props.Validate()
props.Password.Value = ""
props.ConfirmPassword.Value = ""
if !valid {
return props
}
existingUser, _ := db.GetUserByName(username)
if existingUser != nil {
props.Username.Error = "Username is taken"
return props
}
hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost)
if err != nil {
props.GeneralError = "Something went wrong. Error code: Aang"
return props
}
_, err = db.CreateUser(username, hashedPasswordBytes)
if err != nil {
log.Println("Registration error:", err)
props.GeneralError = "Something went wrong. Error code: Ozai"
return props
}
return nil
}

View File

@ -1,55 +0,0 @@
package auth
import (
"encoding/gob"
"log"
"net/http"
"lishwist/db"
"lishwist/env"
"github.com/Teajey/sqlstore"
)
type AuthMiddleware struct {
Store *sqlstore.Store
protectedHandler http.Handler
publicHandler http.Handler
}
func (auth *AuthMiddleware) ServeHTTP(w http.ResponseWriter, r *http.Request) {
session, _ := auth.Store.Get(r, "lishwist_user")
authorized, _ := session.Values["authorized"].(bool)
if !authorized {
auth.publicHandler.ServeHTTP(w, r)
} else {
auth.protectedHandler.ServeHTTP(w, r)
}
}
func (auth *AuthMiddleware) ExpectUser(r *http.Request) *db.User {
session, _ := auth.Store.Get(r, "lishwist_user")
username, ok := session.Values["username"].(string)
if !ok {
log.Fatalln("Failed to get username")
}
user, err := db.GetUserByName(username)
if err != nil {
log.Fatalf("Failed to get user: %s\n", err)
}
return user
}
func NewAuthMiddleware(protectedHandler http.Handler, publicHandler http.Handler) *AuthMiddleware {
gob.Register(&RegisterProps{})
gob.Register(&LoginProps{})
store, err := db.NewSessionStore()
if err != nil {
log.Fatalln("Failed to create store:", err)
}
store.Options.MaxAge = 86_400
store.Options.Secure = !env.InDev
store.Options.HttpOnly = true
return &AuthMiddleware{store, protectedHandler, publicHandler}
}

View File

@ -1,62 +0,0 @@
package auth
import (
sesh "lishwist/session"
"lishwist/templates"
"net/http"
)
type LoginProps struct {
GeneralError string
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) {
session, _ := auth.Store.Get(r, "lishwist_user")
props := NewLoginProps()
flash, err := sesh.GetFirstFlash(w, r, session, "login_props")
if err != nil {
http.Error(w, "Something went wrong. Error code: Zuko", http.StatusInternalServerError)
return
}
flashProps, ok := flash.(*LoginProps)
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)
}

View File

@ -1,58 +0,0 @@
package auth
import (
"lishwist/db"
"log"
"net/http"
"time"
"golang.org/x/crypto/bcrypt"
)
func (auth *AuthMiddleware) LoginPost(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, "Couldn't parse form", http.StatusBadRequest)
return
}
username := r.Form.Get("username")
password := r.Form.Get("password")
props := NewLoginProps()
props.Username.Value = username
user, err := db.GetUserByName(username)
if user == nil || err != nil {
time.Sleep(time.Second)
props.GeneralError = "Username or password invalid"
auth.RedirectWithFlash(w, r, "/", "login_props", &props)
return
}
passHash, err := user.GetPassHash()
if err != nil {
props.GeneralError = "Something went wrong. Error code: Momo"
auth.RedirectWithFlash(w, r, "/", "login_props", &props)
return
}
err = bcrypt.CompareHashAndPassword(passHash, []byte(password))
if err != nil {
props.GeneralError = "Username or password invalid"
auth.RedirectWithFlash(w, r, "/", "login_props", &props)
return
}
// NOTE: Overwriting any existing cookie or session here. So we don't care if there's an error
session, _ := auth.Store.Get(r, "lishwist_user")
session.ID = ""
session.Values["authorized"] = true
session.Values["username"] = username
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, r.URL.Path, http.StatusSeeOther)
}

View File

@ -1,115 +0,0 @@
package auth
import (
"log"
"net/http"
"lishwist/db"
"lishwist/templates"
"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) {
if err := r.ParseForm(); err != nil {
http.Error(w, "Couldn't parse form", http.StatusBadRequest)
return
}
username := r.Form.Get("username")
newPassword := r.Form.Get("newPassword")
confirmPassword := r.Form.Get("confirmPassword")
props := NewRegisterProps()
props.Username.Value = username
props.Password.Value = newPassword
props.ConfirmPassword.Value = confirmPassword
existingUser, _ := db.GetUserByName(username)
if existingUser != nil {
props.Username.Error = "Username is taken"
auth.RedirectWithFlash(w, r, "/register", "register_props", &props)
return
}
if newPassword != confirmPassword {
props.ConfirmPassword.Error = "Password didn't match"
auth.RedirectWithFlash(w, r, "/register", "register_props", &props)
return
}
hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost)
if err != nil {
props.GeneralError = "Something went wrong. Error code: Aang"
auth.RedirectWithFlash(w, r, "/register", "register_props", &props)
return
}
_, err = db.CreateUser(username, hashedPasswordBytes)
if err != nil {
log.Println("Registration error:", err)
props.GeneralError = "Something went wrong. Error code: Ozai"
auth.RedirectWithFlash(w, r, "/register", "register_props", &props)
return
}
session, _ := auth.Store.Get(r, "lishwist_user")
session.AddFlash(true, "successful_registration")
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, "/", http.StatusSeeOther)
}

View File

@ -56,7 +56,7 @@ func NewSessionStore() (*sqlstore.Store, error) {
Insert: insertStmt,
Select: selectStmt,
Update: updateStmt,
}, []byte(env.JwtSecret))
}, []byte(env.SessionSecret))
return s, nil
}

2
server/env/env.go vendored
View File

@ -14,7 +14,7 @@ func GuaranteeEnv(key string) (variable string) {
return
}
var JwtSecret = GuaranteeEnv("LISHWIST_SESSION_SECRET")
var SessionSecret = GuaranteeEnv("LISHWIST_SESSION_SECRET")
var HostRootUrl = GuaranteeEnv("LISHWIST_HOST_ROOT_URL")
var HostPort = os.Getenv("LISHWIST_HOST_PORT")
var ServePort = GuaranteeEnv("LISHWIST_SERVE_PORT")

View File

@ -1,16 +1,21 @@
package main
import (
"encoding/gob"
"log"
"net/http"
"lishwist/auth"
"lishwist/context"
"lishwist/api"
"lishwist/db"
"lishwist/env"
"lishwist/router"
"lishwist/routing"
)
func main() {
gob.Register(&api.RegisterProps{})
gob.Register(&routing.LoginProps{})
err := db.Open()
if err != nil {
log.Fatalf("Failed to open DB: %s\n", err)
@ -20,31 +25,37 @@ func main() {
log.Fatalf("Failed to init DB: %s\n", err)
}
publicMux := http.NewServeMux()
protectedMux := http.NewServeMux()
authMiddleware := auth.NewAuthMiddleware(protectedMux, publicMux)
ctx := context.Context{
Auth: authMiddleware,
store, err := db.NewSessionStore()
if err != nil {
log.Fatalf("Failed to ")
}
store.Options.MaxAge = 86_400
store.Options.Secure = !env.InDev
store.Options.HttpOnly = true
publicMux.HandleFunc("GET /register", authMiddleware.Register)
publicMux.HandleFunc("POST /register", authMiddleware.RegisterPost)
publicMux.HandleFunc("GET /", authMiddleware.Login)
publicMux.HandleFunc("POST /", authMiddleware.LoginPost)
publicMux.HandleFunc("GET /list/{userReference}", ctx.PublicWishlist)
publicMux.HandleFunc("GET /group/{groupReference}", ctx.PublicGroupPage)
r := router.New(store)
protectedMux.HandleFunc("GET /{$}", ctx.Home)
protectedMux.HandleFunc("POST /{$}", ctx.HomePost)
protectedMux.HandleFunc("GET /list/{userReference}", ctx.ForeignWishlist)
protectedMux.HandleFunc("POST /list/{userReference}", ctx.ForeignWishlistPost)
protectedMux.HandleFunc("GET /group/{groupReference}", ctx.GroupPage)
protectedMux.HandleFunc("POST /logout", authMiddleware.LogoutPost)
route := routing.NewContext(store)
http.Handle("/", authMiddleware)
r.Html.Public.HandleFunc("GET /register", route.Register)
r.Html.Public.HandleFunc("POST /register", route.RegisterPost)
r.Html.Public.HandleFunc("GET /", route.Login)
r.Html.Public.HandleFunc("POST /", route.LoginPost)
r.Html.Public.HandleFunc("GET /list/{userReference}", route.PublicWishlist)
r.Html.Public.HandleFunc("GET /group/{groupReference}", route.PublicGroupPage)
r.Html.Private.HandleFunc("GET /{$}", route.Home)
r.Html.Private.HandleFunc("POST /{$}", route.HomePost)
r.Html.Private.HandleFunc("GET /list/{userReference}", route.ForeignWishlist)
r.Html.Private.HandleFunc("POST /list/{userReference}", route.ForeignWishlistPost)
r.Html.Private.HandleFunc("GET /group/{groupReference}", route.GroupPage)
r.Html.Private.HandleFunc("POST /logout", route.LogoutPost)
r.Json.Public.HandleFunc("POST /register", route.RegisterPostJson)
http.Handle("/", r)
log.Printf("Running at http://127.0.0.1:%s\n", env.ServePort)
err = http.ListenAndServe(":"+env.ServePort, nil)
if err != nil {
log.Fatalln("Failed to listen and server:", err)

55
server/router/router.go Normal file
View File

@ -0,0 +1,55 @@
package router
import (
"net/http"
"github.com/Teajey/sqlstore"
)
type VisibilityRouter struct {
Store *sqlstore.Store
Public *http.ServeMux
Private *http.ServeMux
}
func (s *VisibilityRouter) ServeHTTP(w http.ResponseWriter, r *http.Request) {
session, _ := s.Store.Get(r, "lishwist_user")
authorized, _ := session.Values["authorized"].(bool)
if authorized {
s.Private.ServeHTTP(w, r)
} else {
s.Public.ServeHTTP(w, r)
}
}
type Router struct {
Json VisibilityRouter
Html VisibilityRouter
}
func (s *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
contentType := r.Header.Get("Content-Type")
switch contentType {
case "application/json":
s.Json.ServeHTTP(w, r)
default:
s.Html.ServeHTTP(w, r)
}
}
func New(store *sqlstore.Store) *Router {
return &Router{
Json: VisibilityRouter{
Store: store,
Public: http.NewServeMux(),
Private: http.NewServeMux(),
},
Html: VisibilityRouter{
Store: store,
Public: http.NewServeMux(),
Private: http.NewServeMux(),
},
}
}

33
server/routing/context.go Normal file
View File

@ -0,0 +1,33 @@
package routing
import (
"lishwist/db"
"log"
"net/http"
"github.com/Teajey/sqlstore"
)
type Context struct {
store *sqlstore.Store
}
func NewContext(store *sqlstore.Store) *Context {
return &Context{
store,
}
}
func (auth *Context) ExpectUser(r *http.Request) *db.User {
session, _ := auth.store.Get(r, "lishwist_user")
username, ok := session.Values["username"].(string)
if !ok {
log.Fatalln("Failed to get username")
}
user, err := db.GetUserByName(username)
if err != nil {
log.Fatalf("Failed to get user: %s\n", err)
}
return user
}

14
server/routing/error.go Normal file
View File

@ -0,0 +1,14 @@
package routing
import (
"fmt"
"net/http"
"strings"
)
func writeGeneralError(w http.ResponseWriter, msg string, status int) {
w.WriteHeader(status)
escapedMsg := strings.ReplaceAll(msg, `"`, `\"`)
_, _ = w.Write([]byte(fmt.Sprintf(`{"GeneralError":"%s"}`, escapedMsg)))
w.Header().Add("Content-Type", "application/json")
}

View File

@ -1,4 +1,4 @@
package context
package routing
import (
"lishwist/db"
@ -16,7 +16,7 @@ type foreignWishlistProps struct {
func (ctx *Context) ForeignWishlist(w http.ResponseWriter, r *http.Request) {
userReference := r.PathValue("userReference")
user := ctx.Auth.ExpectUser(r)
user := ctx.ExpectUser(r)
if user.Reference == userReference {
http.Redirect(w, r, "/", http.StatusSeeOther)
return

View File

@ -1,4 +1,4 @@
package context
package routing
import (
"net/http"
@ -15,7 +15,7 @@ type GroupProps struct {
}
func (ctx *Context) GroupPage(w http.ResponseWriter, r *http.Request) {
user := ctx.Auth.ExpectUser(r)
user := ctx.ExpectUser(r)
groupReference := r.PathValue("groupReference")
group, err := user.GetGroupByReference(groupReference)
if err != nil {

View File

@ -1,4 +1,4 @@
package context
package routing
import (
"net/http"
@ -19,7 +19,7 @@ type HomeProps struct {
}
func (ctx *Context) Home(w http.ResponseWriter, r *http.Request) {
user := ctx.Auth.ExpectUser(r)
user := ctx.ExpectUser(r)
gifts, err := user.GetGifts()
if err != nil {
error.Page(w, "An error occurred while fetching your wishlist :(", http.StatusInternalServerError, err)

View File

@ -0,0 +1,13 @@
package routing
import (
"encoding/json"
"net/http"
)
func decodeJsonParams(r *http.Request, v any) error {
dec := json.NewDecoder(r.Body)
dec.DisallowUnknownFields()
err := dec.Decode(&v)
return err
}

115
server/routing/login.go Normal file
View File

@ -0,0 +1,115 @@
package routing
import (
"lishwist/db"
sesh "lishwist/session"
"lishwist/templates"
"log"
"net/http"
"time"
"golang.org/x/crypto/bcrypt"
)
type LoginProps struct {
GeneralError string
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 (ctx *Context) Login(w http.ResponseWriter, r *http.Request) {
session, _ := ctx.store.Get(r, "lishwist_user")
props := NewLoginProps()
flash, err := sesh.GetFirstFlash(w, r, session, "login_props")
if err != nil {
http.Error(w, "Something went wrong. Error code: Zuko", http.StatusInternalServerError)
return
}
flashProps, ok := flash.(*LoginProps)
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)
}
func (ctx *Context) LoginPost(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, "Couldn't parse form", http.StatusBadRequest)
return
}
username := r.Form.Get("username")
password := r.Form.Get("password")
props := NewLoginProps()
props.Username.Value = username
user, err := db.GetUserByName(username)
if user == nil || err != nil {
time.Sleep(time.Second)
props.GeneralError = "Username or password invalid"
ctx.RedirectWithFlash(w, r, "/", "login_props", &props)
return
}
passHash, err := user.GetPassHash()
if err != nil {
props.GeneralError = "Something went wrong. Error code: Momo"
ctx.RedirectWithFlash(w, r, "/", "login_props", &props)
return
}
err = bcrypt.CompareHashAndPassword(passHash, []byte(password))
if err != nil {
props.GeneralError = "Username or password invalid"
ctx.RedirectWithFlash(w, r, "/", "login_props", &props)
return
}
// NOTE: Overwriting any existing cookie or session here. So we don't care if there's an error
session, _ := ctx.store.Get(r, "lishwist_user")
session.ID = ""
session.Values["authorized"] = true
session.Values["username"] = username
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, r.URL.Path, http.StatusSeeOther)
}

View File

@ -1,11 +1,11 @@
package auth
package routing
import (
"net/http"
)
func (auth *AuthMiddleware) LogoutPost(w http.ResponseWriter, r *http.Request) {
session, err := auth.Store.Get(r, "lishwist_user")
func (ctx *Context) LogoutPost(w http.ResponseWriter, r *http.Request) {
session, err := ctx.store.Get(r, "lishwist_user")
if err != nil {
http.Error(w, "Something went wrong. Error code: Iroh", http.StatusInternalServerError)
return

View File

@ -1,12 +1,12 @@
package auth
package routing
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")
func (ctx *Context) RedirectWithFlash(w http.ResponseWriter, r *http.Request, url string, key string, flash any) {
session, _ := ctx.store.Get(r, "lishwist_user")
session.AddFlash(flash, key)
if err := session.Save(r, w); err != nil {
log.Println("Couldn't save session:", err)

View File

@ -0,0 +1,70 @@
package routing
import (
"encoding/json"
"lishwist/api"
"lishwist/templates"
"log"
"net/http"
)
func (ctx *Context) Register(w http.ResponseWriter, r *http.Request) {
props := api.NewRegisterProps("", "", "")
session, _ := ctx.store.Get(r, "lishwist_user")
if flashes := session.Flashes("register_props"); len(flashes) > 0 {
flashProps, _ := flashes[0].(*api.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 (ctx *Context) RegisterPost(w http.ResponseWriter, r *http.Request) {
if err := r.ParseForm(); err != nil {
http.Error(w, "Couldn't parse form", http.StatusBadRequest)
return
}
username := r.Form.Get("username")
newPassword := r.Form.Get("newPassword")
confirmPassword := r.Form.Get("confirmPassword")
props := api.Register(username, newPassword, confirmPassword)
if props != nil {
ctx.RedirectWithFlash(w, r, "/register", "register_props", &props)
return
}
ctx.RedirectWithFlash(w, r, "/", "successful_registration", true)
}
type jsonParams struct {
Username string
NewPassword string
ConfirmPassword string
}
func (ctx *Context) RegisterPostJson(w http.ResponseWriter, r *http.Request) {
var params jsonParams
err := decodeJsonParams(r, &params)
if err != nil {
writeGeneralError(w, "Failed to decode json params: "+err.Error(), http.StatusBadRequest)
return
}
props := api.Register(params.Username, params.NewPassword, params.ConfirmPassword)
_ = json.NewEncoder(w).Encode(props)
}

View File

@ -1,4 +1,4 @@
package context
package routing
import (
"log"
@ -6,7 +6,7 @@ import (
)
func (ctx *Context) TodoUpdate(w http.ResponseWriter, r *http.Request) {
user := ctx.Auth.ExpectUser(r)
user := ctx.ExpectUser(r)
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return

View File

@ -1,17 +1,12 @@
package context
package routing
import (
"lishwist/auth"
"lishwist/error"
"net/http"
)
type Context struct {
Auth *auth.AuthMiddleware
}
func (ctx *Context) WishlistAdd(w http.ResponseWriter, r *http.Request) {
user := ctx.Auth.ExpectUser(r)
user := ctx.ExpectUser(r)
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
@ -26,7 +21,7 @@ func (ctx *Context) WishlistAdd(w http.ResponseWriter, r *http.Request) {
}
func (ctx *Context) WishlistDelete(w http.ResponseWriter, r *http.Request) {
user := ctx.Auth.ExpectUser(r)
user := ctx.ExpectUser(r)
if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest)
return
@ -41,7 +36,7 @@ func (ctx *Context) WishlistDelete(w http.ResponseWriter, r *http.Request) {
}
func (ctx *Context) ForeignWishlistPost(w http.ResponseWriter, r *http.Request) {
user := ctx.Auth.ExpectUser(r)
user := ctx.ExpectUser(r)
if err := r.ParseForm(); err != nil {
error.Page(w, "Failed to parse form...", http.StatusBadRequest, err)
return

View File

@ -1,6 +1,6 @@
{{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}}">
value="{{.Value}}" {{if .Required}}required {{end}}{{if .MinLength}}minlength="{{.MinLength}}" {{end}} aria-describedby="{{if .Error}}{{.Name}}Error{{end}}">
{{with .Error}}
<div id="{{$.Name}}Error" class="invalid-feedback">
{{.}}

View File

@ -1,18 +1,34 @@
package templates
import (
"fmt"
"log"
"net/http"
"text/template"
)
type InputProps struct {
Type string
Type string `json:",omitempty"`
Name string
Required bool
Required bool `json:",omitempty"`
Value string
Error string
MinLength uint
Error string `json:",omitempty"`
MinLength uint `json:",omitempty"`
}
func (p *InputProps) Validate() bool {
if p.Required && p.Value == "" {
p.Error = fmt.Sprintf("%v is required", p.Name)
return false
}
value_len := len(p.Value)
if p.MinLength > 0 && int(p.MinLength) > value_len {
p.Error = fmt.Sprintf("%v requires at least %v characters (currently %v characters)", p.Name, p.MinLength, value_len)
return false
}
return true
}
var tmpls map[string]*template.Template = loadTemplates()