Compare commits
4 Commits
e86fed2f00
...
782ffbbe6d
| Author | SHA1 | Date |
|---|---|---|
|
|
782ffbbe6d | |
|
|
f660d8c0eb | |
|
|
2612c37671 | |
|
|
d2d9e6c78d |
|
|
@ -2,4 +2,4 @@
|
|||
gin-bin
|
||||
lishwist.db
|
||||
.env*.local
|
||||
db/init_sql.go
|
||||
server/db/init_sql.go
|
||||
|
|
|
|||
50
auth/auth.go
50
auth/auth.go
|
|
@ -1,50 +0,0 @@
|
|||
package auth
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"lishwist/db"
|
||||
"lishwist/env"
|
||||
|
||||
"github.com/gorilla/sessions"
|
||||
)
|
||||
|
||||
type AuthMiddleware struct {
|
||||
Store *sessions.CookieStore
|
||||
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 := sessions.NewCookieStore([]byte(env.JwtSecret))
|
||||
store.Options.MaxAge = 86_400
|
||||
return &AuthMiddleware{store, protectedHandler, publicHandler}
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -1,62 +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
|
||||
}
|
||||
|
||||
session, err := auth.Store.Get(r, "lishwist_user")
|
||||
if err != nil {
|
||||
log.Println("Couldn't get jwt:", err)
|
||||
props.GeneralError = "Something went wrong. Error code: Sokka"
|
||||
auth.RedirectWithFlash(w, r, "/", "login_props", &props)
|
||||
return
|
||||
}
|
||||
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)
|
||||
}
|
||||
115
auth/register.go
115
auth/register.go
|
|
@ -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)
|
||||
}
|
||||
28
db/db.go
28
db/db.go
|
|
@ -1,28 +0,0 @@
|
|||
//go:generate go run gen_init_sql.go
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
|
||||
_ "github.com/glebarez/go-sqlite"
|
||||
)
|
||||
|
||||
var database *sql.DB
|
||||
|
||||
func Open() error {
|
||||
db, err := sql.Open("sqlite", "./lishwist.db")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
database = db
|
||||
return nil
|
||||
}
|
||||
|
||||
func Init() error {
|
||||
_, err := database.Exec(InitQuery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
|
||||
modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
|
||||
49
main.go
49
main.go
|
|
@ -1,49 +0,0 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"lishwist/auth"
|
||||
"lishwist/context"
|
||||
"lishwist/db"
|
||||
"lishwist/env"
|
||||
)
|
||||
|
||||
func main() {
|
||||
err := db.Open()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to open DB: %s\n", err)
|
||||
}
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
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,
|
||||
}
|
||||
|
||||
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)
|
||||
|
||||
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)
|
||||
|
||||
http.Handle("/", authMiddleware)
|
||||
|
||||
http.ListenAndServe(":"+env.ServePort, nil)
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
//go:generate go run gen_init_sql.go
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"lishwist/env"
|
||||
|
||||
"github.com/Teajey/sqlstore"
|
||||
_ "github.com/glebarez/go-sqlite"
|
||||
)
|
||||
|
||||
var database *sql.DB
|
||||
|
||||
func Open() error {
|
||||
db, err := sql.Open("sqlite", "./lishwist.db")
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
database = db
|
||||
return nil
|
||||
}
|
||||
|
||||
func Init() error {
|
||||
_, err := database.Exec(InitQuery)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewSessionStore() (*sqlstore.Store, error) {
|
||||
deleteStmt, err := database.Prepare("DELETE FROM session WHERE id = ?;")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to prepare delete statement: %w", err)
|
||||
}
|
||||
|
||||
insertStmt, err := database.Prepare("INSERT INTO session (value) VALUES (?);")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to prepare insert statement: %w", err)
|
||||
}
|
||||
|
||||
selectStmt, err := database.Prepare("SELECT value FROM session WHERE id = ?;")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to prepare select statement: %w", err)
|
||||
}
|
||||
|
||||
updateStmt, err := database.Prepare("UPDATE session SET value = ?2 WHERE id = ?1;")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to prepare update statement: %w", err)
|
||||
}
|
||||
|
||||
s := sqlstore.NewSqlStore(database, sqlstore.Statements{
|
||||
Delete: deleteStmt,
|
||||
Insert: insertStmt,
|
||||
Select: selectStmt,
|
||||
Update: updateStmt,
|
||||
}, []byte(env.SessionSecret))
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
|
@ -32,4 +32,9 @@ CREATE TABLE IF NOT EXISTS "group_member" (
|
|||
FOREIGN KEY("group_id") REFERENCES "group"("id"),
|
||||
FOREIGN KEY("user_id") REFERENCES "user"("id")
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "session" (
|
||||
"id" INTEGER NOT NULL UNIQUE,
|
||||
"value" TEXT NOT NULL,
|
||||
PRIMARY KEY("id" AUTOINCREMENT)
|
||||
);
|
||||
COMMIT;
|
||||
|
|
@ -190,7 +190,7 @@ func (u *User) GetTodo() ([]Gift, error) {
|
|||
var sent bool
|
||||
var recipientName string
|
||||
var recipientRef string
|
||||
rows.Scan(&id, &name, &sent, &recipientName, &recipientRef)
|
||||
_ = rows.Scan(&id, &name, &sent, &recipientName, &recipientRef)
|
||||
gift := Gift{
|
||||
Id: id,
|
||||
Name: name,
|
||||
|
|
@ -14,10 +14,11 @@ func GuaranteeEnv(key string) (variable string) {
|
|||
return
|
||||
}
|
||||
|
||||
var JwtSecret = GuaranteeEnv("LISHWIST_JWT_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")
|
||||
var InDev = os.Getenv("LISHWIST_IN_DEV") != ""
|
||||
var HostUrl = func() *url.URL {
|
||||
rawUrl := HostRootUrl
|
||||
if HostPort != "" {
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
module lishwist
|
||||
|
||||
go 1.22.0
|
||||
go 1.23
|
||||
|
||||
toolchain go1.23.3
|
||||
|
||||
require (
|
||||
github.com/Teajey/sqlstore v0.0.6
|
||||
github.com/glebarez/go-sqlite v1.22.0
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/gorilla/sessions v1.2.2
|
||||
github.com/gorilla/sessions v1.4.0
|
||||
golang.org/x/crypto v0.22.0
|
||||
)
|
||||
|
||||
|
|
@ -1,3 +1,11 @@
|
|||
github.com/Teajey/sqlstore v0.0.3 h1:6Y1jz9/yw1cj/Z/jrii0s87RAomKWr/07B1auDgw8pg=
|
||||
github.com/Teajey/sqlstore v0.0.3/go.mod h1:hjk0S593/2Q4QxkEXCgpThj9w5KWGTQi9JtgfziHXXk=
|
||||
github.com/Teajey/sqlstore v0.0.4 h1:ATe25BD8cV0FUw4w2qlccx5m0c5kQI0K4ksl/LnSHsc=
|
||||
github.com/Teajey/sqlstore v0.0.4/go.mod h1:hjk0S593/2Q4QxkEXCgpThj9w5KWGTQi9JtgfziHXXk=
|
||||
github.com/Teajey/sqlstore v0.0.5 h1:WZvu54baa8+9n1sKQe9GuxBVwSISw+xCkw4VFSwwIs8=
|
||||
github.com/Teajey/sqlstore v0.0.5/go.mod h1:hjk0S593/2Q4QxkEXCgpThj9w5KWGTQi9JtgfziHXXk=
|
||||
github.com/Teajey/sqlstore v0.0.6 h1:kUEpA+3BKFHZl128MuMeYY6zVcmq1QmOlNyofcFEJOA=
|
||||
github.com/Teajey/sqlstore v0.0.6/go.mod h1:hjk0S593/2Q4QxkEXCgpThj9w5KWGTQi9JtgfziHXXk=
|
||||
github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY=
|
||||
github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto=
|
||||
github.com/glebarez/go-sqlite v1.22.0 h1:uAcMJhaA6r3LHMTFgP0SifzgXg46yJkgxqyuyec+ruQ=
|
||||
|
|
@ -10,8 +18,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
|||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/gorilla/securecookie v1.1.2 h1:YCIWL56dvtr73r6715mJs5ZvhtnY73hBvEF8kXD8ePA=
|
||||
github.com/gorilla/securecookie v1.1.2/go.mod h1:NfCASbcHqRSY+3a8tlWJwsQap2VX5pwzwo4h3eOamfo=
|
||||
github.com/gorilla/sessions v1.2.2 h1:lqzMYz6bOfvn2WriPUjNByzeXIlVzURcPmgMczkmTjY=
|
||||
github.com/gorilla/sessions v1.2.2/go.mod h1:ePLdVu+jbEgHH+KWw8I1z2wqd0BAdAQh/8LRvBeoNcQ=
|
||||
github.com/gorilla/sessions v1.4.0 h1:kpIYOp/oi6MG/p5PgxApU8srsSw9tuFbt46Lt7auzqQ=
|
||||
github.com/gorilla/sessions v1.4.0/go.mod h1:FLWm50oby91+hl7p/wRxDth9bWSuk0qVL2emc7lT5ik=
|
||||
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||
github.com/remyoudompheng/bigfft v0.0.0-20230129092748-24d4a6f8daec h1:W09IVJc94icq4NjY3clb7Lk8O1qJ8BdBEF8z0ibU0rE=
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/gob"
|
||||
"log"
|
||||
"net/http"
|
||||
|
||||
"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)
|
||||
}
|
||||
err = db.Init()
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to init DB: %s\n", err)
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
r := router.New(store)
|
||||
|
||||
route := routing.NewContext(store)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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(),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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 {
|
||||
|
|
@ -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)
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -1,15 +1,17 @@
|
|||
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
|
||||
}
|
||||
|
||||
session.Options.MaxAge = 0
|
||||
session.Values = nil
|
||||
if err := session.Save(r, w); err != nil {
|
||||
http.Error(w, "Something went wrong. Error code: Azula", http.StatusInternalServerError)
|
||||
|
|
@ -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)
|
||||
|
|
@ -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, ¶ms)
|
||||
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)
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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">
|
||||
{{.}}
|
||||
|
|
@ -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()
|
||||
Loading…
Reference in New Issue