Compare commits

..

No commits in common. "dcba801dde09b077b169a95dd1f4b8179a18e719" and "782ffbbe6d715ad5703b429298abc1921e572f02" have entirely different histories.

24 changed files with 257 additions and 592 deletions

View File

@ -1,85 +0,0 @@
package api
import (
"lishwist/db"
"lishwist/templates"
"log"
"golang.org/x/crypto/bcrypt"
)
type LoginProps struct {
GeneralError string
SuccessfulRegistration bool
Username templates.InputProps
Password templates.InputProps
}
func NewLoginProps(username, password string) *LoginProps {
return &LoginProps{
Username: templates.InputProps{
Name: "username",
Required: true,
Value: username,
},
Password: templates.InputProps{
Name: "password",
Type: "password",
Required: true,
Value: password,
},
}
}
func (p *LoginProps) Validate() (valid bool) {
valid = true
if !p.Username.Validate() {
valid = false
}
if !p.Password.Validate() {
valid = false
}
return
}
func Login(username, password string) *LoginProps {
props := NewLoginProps(username, password)
valid := props.Validate()
props.Password.Value = ""
if !valid {
log.Printf("Invalid props: %#v\n", props)
return props
}
user, err := db.GetUserByName(username)
if err != nil {
log.Printf("Failed to fetch user: %s\n", err)
props.GeneralError = "Username or password invalid"
return props
}
if user == nil {
log.Printf("User not found by name: %q\n", username)
props.GeneralError = "Username or password invalid"
return props
}
passHash, err := user.GetPassHash()
if err != nil {
log.Println("Failed to get password hash: " + err.Error())
props.GeneralError = "Something went wrong. Error code: Momo"
return props
}
err = bcrypt.CompareHashAndPassword(passHash, []byte(password))
if err != nil {
log.Println("Username or password invalid: " + err.Error())
props.GeneralError = "Username or password invalid"
return props
}
return nil
}

View File

@ -17,8 +17,6 @@ type RegisterProps struct {
} }
func (p *RegisterProps) Validate() (valid bool) { func (p *RegisterProps) Validate() (valid bool) {
valid = true
if p.Password.Value != p.ConfirmPassword.Value { if p.Password.Value != p.ConfirmPassword.Value {
p.ConfirmPassword.Error = "Passwords didn't match" p.ConfirmPassword.Error = "Passwords didn't match"
valid = false valid = false
@ -71,27 +69,24 @@ func Register(username, newPassword, confirmPassword string) *RegisterProps {
props.Password.Value = "" props.Password.Value = ""
props.ConfirmPassword.Value = "" props.ConfirmPassword.Value = ""
if !valid { if !valid {
log.Printf("Invalid props: %#v\n", props)
return props return props
} }
existingUser, _ := db.GetUserByName(username) existingUser, _ := db.GetUserByName(username)
if existingUser != nil { if existingUser != nil {
log.Printf("Username is taken: %q\n", username)
props.Username.Error = "Username is taken" props.Username.Error = "Username is taken"
return props return props
} }
hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost) hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost)
if err != nil { if err != nil {
log.Printf("Failed to hash password: %s\n", err)
props.GeneralError = "Something went wrong. Error code: Aang" props.GeneralError = "Something went wrong. Error code: Aang"
return props return props
} }
_, err = db.CreateUser(username, hashedPasswordBytes) _, err = db.CreateUser(username, hashedPasswordBytes)
if err != nil { if err != nil {
log.Printf("Failed to create user: %s\n", err) log.Println("Registration error:", err)
props.GeneralError = "Something went wrong. Error code: Ozai" props.GeneralError = "Something went wrong. Error code: Ozai"
return props return props
} }

View File

@ -1,121 +1,61 @@
package db package db
import ( import "database/sql"
"database/sql"
"fmt"
"strconv"
)
type Group struct { type Group struct {
Id string Id string
Name string Name string
Reference string Reference string
Members []User
}
func (g *Group) MemberIndex(userId string) int {
for i, u := range g.Members {
if u.Id == userId {
return i
}
}
return -1
} }
func queryForGroup(query string, args ...any) (*Group, error) { func queryForGroup(query string, args ...any) (*Group, error) {
var group Group var id string
err := database.QueryRow(query, args...).Scan(&group.Id, &group.Name, &group.Reference) var name string
var reference string
err := database.QueryRow(query, args...).Scan(&id, &name, &reference)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return nil, nil return nil, nil
} else if err != nil { } else if err != nil {
return nil, err return nil, err
} }
members, err := queryForGroupMembers(group.Id)
if err != nil {
return nil, err
}
group.Members = members
return &group, nil
}
func queryForGroups(query string, args ...any) ([]Group, error) {
groups := []Group{}
rows, err := database.Query(query)
if err != nil {
return groups, err
}
defer rows.Close()
for rows.Next() {
var group Group
err := rows.Scan(&group.Id, &group.Name, &group.Reference)
if err != nil {
return groups, err
}
members, err := queryForGroupMembers(group.Id)
if err != nil {
return groups, err
}
group.Members = members
groups = append(groups, group)
}
err = rows.Err()
if err != nil {
return groups, err
}
return groups, nil
}
func queryForGroupMembers(groupId string) ([]User, error) {
query := "SELECT user.id, user.name, user.reference, user.is_admin, user.is_live FROM v_user AS user JOIN group_member ON group_member.user_id = user.id JOIN [group] ON [group].id = group_member.group_id WHERE [group].id = ? ORDER BY group_member.user_id"
members, err := queryForUsers(query, groupId)
if err != nil {
return members, fmt.Errorf("Failed to get members: %w", err)
}
return members, nil
}
func GetGroupByReference(reference string) (*Group, error) {
query := "SELECT [group].id, [group].name, [group].reference FROM [group] WHERE [group].reference = ?"
return queryForGroup(query, reference)
}
func GetAllGroups() ([]Group, error) {
query := "SELECT id, name, reference FROM [group];"
return queryForGroups(query)
}
func CreateGroup(name string, reference string) (*Group, error) {
stmt := "INSERT INTO [group] (name, reference) VALUES (?, ?)"
result, err := database.Exec(stmt, name, reference)
if err != nil {
return nil, err
}
id, err := result.LastInsertId()
if err != nil {
return nil, err
}
group := Group{ group := Group{
Id: strconv.FormatInt(id, 10), Id: id,
Name: name, Name: name,
Reference: reference, Reference: reference,
} }
return &group, nil return &group, nil
} }
func (g *Group) AddUser(userId string) error { func GetGroupByReference(reference string) (*Group, error) {
stmt := "INSERT INTO group_member (group_id, user_id) VALUES (?, ?)" stmt := "SELECT [group].id, [group].name, [group].reference FROM [group] WHERE [group].reference = ?"
_, err := database.Exec(stmt, g.Id, userId) return queryForGroup(stmt, reference)
if err != nil {
return err
}
return nil
} }
func (g *Group) RemoveUser(userId string) error { func (g *Group) GetMembers() ([]User, error) {
stmt := "DELETE FROM group_member WHERE group_id = ? AND user_id = ?" stmt := "SELECT user.id, user.name, user.reference FROM user JOIN group_member ON group_member.user_id = user.id JOIN [group] ON [group].id = group_member.group_id WHERE [group].id = ?"
_, err := database.Exec(stmt, g.Id, userId) rows, err := database.Query(stmt, g.Id)
users := []User{}
if err != nil { if err != nil {
return err return users, err
} }
return nil defer rows.Close()
for rows.Next() {
var id string
var name string
var reference string
err := rows.Scan(&id, &name, &reference)
if err != nil {
return users, err
}
users = append(users, User{
Id: id,
Name: name,
Reference: reference,
})
}
err = rows.Err()
if err != nil {
return users, err
}
return users, nil
} }

View File

@ -3,10 +3,8 @@ CREATE TABLE IF NOT EXISTS "user" (
"id" INTEGER NOT NULL UNIQUE, "id" INTEGER NOT NULL UNIQUE,
"name" TEXT NOT NULL UNIQUE, "name" TEXT NOT NULL UNIQUE,
"reference" TEXT NOT NULL UNIQUE, "reference" TEXT NOT NULL UNIQUE,
"motto" TEXT NOT NULL DEFAULT "", "motto" TEXT NOT NULL,
"password_hash" TEXT NOT NULL, "password_hash" TEXT NOT NULL,
"is_admin" INTEGER NOT NULL DEFAULT 0,
"is_live" INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY("id" AUTOINCREMENT) PRIMARY KEY("id" AUTOINCREMENT)
); );
CREATE TABLE IF NOT EXISTS "gift" ( CREATE TABLE IF NOT EXISTS "gift" (
@ -39,15 +37,4 @@ CREATE TABLE IF NOT EXISTS "session" (
"value" TEXT NOT NULL, "value" TEXT NOT NULL,
PRIMARY KEY("id" AUTOINCREMENT) PRIMARY KEY("id" AUTOINCREMENT)
); );
DROP VIEW IF EXISTS "v_user";
CREATE VIEW "v_user"
AS
SELECT * FROM user WHERE user.is_live = 1;
-- DROP VIEW IF EXISTS "v_wish";
-- CREATE VIEW "v_wish"
-- AS
-- SELECT gift.id, gift.name, gift.sent FROM gift JOIN user AS recipient;
COMMIT; COMMIT;

View File

@ -1,22 +0,0 @@
BEGIN TRANSACTION;
ALTER TABLE user ADD COLUMN "is_live" INTEGER NOT NULL DEFAULT 1;
ALTER TABLE user RENAME TO old_user;
CREATE TABLE "user" (
"id" INTEGER NOT NULL UNIQUE,
"name" TEXT NOT NULL UNIQUE,
"reference" TEXT NOT NULL UNIQUE,
"motto" TEXT NOT NULL DEFAULT "",
"password_hash" TEXT NOT NULL,
"is_admin" INTEGER NOT NULL DEFAULT 0,
"is_live" INTEGER NOT NULL DEFAULT 1,
PRIMARY KEY("id" AUTOINCREMENT)
);
INSERT INTO user SELECT * FROM old_user;
DROP TABLE "old_user";
COMMIT;

View File

@ -11,8 +11,6 @@ type User struct {
Id string Id string
Name string Name string
Reference string Reference string
IsAdmin bool
IsLive bool
} }
type Gift struct { type Gift struct {
@ -29,75 +27,35 @@ type Gift struct {
} }
func queryForUser(query string, args ...any) (*User, error) { func queryForUser(query string, args ...any) (*User, error) {
var u User var id string
err := database.QueryRow(query, args...).Scan(&u.Id, &u.Name, &u.Reference, &u.IsAdmin, &u.IsLive) var name string
var reference string
err := database.QueryRow(query, args...).Scan(&id, &name, &reference)
if err == sql.ErrNoRows { if err == sql.ErrNoRows {
return nil, nil return nil, nil
} else if err != nil { } else if err != nil {
return nil, err return nil, err
} }
return &u, nil user := User{
Id: id,
Name: name,
Reference: reference,
} }
return &user, nil
func queryForUsers(query string, args ...any) ([]User, error) {
rows, err := database.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
users := []User{}
for rows.Next() {
var u User
err = rows.Scan(&u.Id, &u.Name, &u.Reference, &u.IsAdmin, &u.IsLive)
if err != nil {
return nil, err
}
users = append(users, u)
}
err = rows.Err()
if err != nil {
return nil, err
}
return users, nil
}
func GetAllUsers() ([]User, error) {
stmt := "SELECT id, name, reference, is_admin, is_live FROM user"
return queryForUsers(stmt)
}
func GetUser(id string) (*User, error) {
stmt := "SELECT id, name, reference, is_admin, is_live FROM v_user WHERE id = ?"
return queryForUser(stmt, id)
} }
func GetUserByName(username string) (*User, error) { func GetUserByName(username string) (*User, error) {
stmt := "SELECT id, name, reference, is_admin, is_live FROM v_user WHERE name = ?" stmt := "SELECT user.id, user.name, user.reference FROM user WHERE user.name = ?"
return queryForUser(stmt, username) return queryForUser(stmt, username)
} }
func GetUserByReference(reference string) (*User, error) { func GetUserByReference(reference string) (*User, error) {
stmt := "SELECT id, name, reference, is_admin, is_live FROM v_user WHERE reference = ?" stmt := "SELECT user.id, user.name, user.reference FROM user WHERE user.reference = ?"
return queryForUser(stmt, reference) return queryForUser(stmt, reference)
} }
func GetAnyUserByReference(reference string) (*User, error) {
stmt := "SELECT id, name, reference, is_admin, is_live FROM user WHERE reference = ?"
return queryForUser(stmt, reference)
}
func (u *User) SetLive(setting bool) error {
query := "UPDATE user SET is_live = ? WHERE reference = ?"
_, err := database.Exec(query, setting, u.Reference)
if err != nil {
return err
}
u.IsLive = setting
return err
}
func CreateUser(username string, passHash []byte) (*User, error) { func CreateUser(username string, passHash []byte) (*User, error) {
stmt := "INSERT INTO user (name, reference, password_hash) VALUES (?, ?, ?)" stmt := "INSERT INTO user (name, motto, reference, password_hash) VALUES (?, '', ?, ?)"
reference, err := uuid.NewRandom() reference, err := uuid.NewRandom()
if err != nil { if err != nil {
return nil, err return nil, err
@ -118,7 +76,7 @@ func CreateUser(username string, passHash []byte) (*User, error) {
} }
func (u *User) GetPassHash() ([]byte, error) { func (u *User) GetPassHash() ([]byte, error) {
stmt := "SELECT password_hash FROM v_user WHERE id = ?" stmt := "SELECT user.password_hash FROM user WHERE user.id = ?"
var passHash string var passHash string
err := database.QueryRow(stmt, u.Id).Scan(&passHash) err := database.QueryRow(stmt, u.Id).Scan(&passHash)
if err != nil { if err != nil {
@ -128,7 +86,7 @@ func (u *User) GetPassHash() ([]byte, error) {
} }
func (u *User) CountGifts() (int, error) { func (u *User) CountGifts() (int, error) {
stmt := "SELECT COUNT(gift.id) AS gift_count FROM gift WHERE gift.creator_id = ?1 AND gift.recipient_id = ?1" 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 var giftCount int
err := database.QueryRow(stmt, u.Id).Scan(&giftCount) err := database.QueryRow(stmt, u.Id).Scan(&giftCount)
if err != nil { if err != nil {
@ -138,7 +96,7 @@ func (u *User) CountGifts() (int, error) {
} }
func (u *User) GetGifts() ([]Gift, error) { func (u *User) GetGifts() ([]Gift, error) {
stmt := "SELECT gift.id, gift.name, gift.sent FROM gift WHERE gift.creator_id = ?1 AND gift.recipient_id = ?1" 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)
if err != nil { if err != nil {
return nil, err return nil, err
@ -148,14 +106,18 @@ func (u *User) GetGifts() ([]Gift, error) {
for rows.Next() { for rows.Next() {
var id string var id string
var name string var name string
var claimantId sql.NullString
var claimantName sql.NullString
var sent bool var sent bool
err = rows.Scan(&id, &name, &sent) err = rows.Scan(&id, &name, &claimantId, &claimantName, &sent)
if err != nil { if err != nil {
return nil, err return nil, err
} }
gift := Gift{ gift := Gift{
Id: id, Id: id,
Name: name, Name: name,
ClaimantId: claimantId.String,
ClaimantName: claimantName.String,
Sent: sent, Sent: sent,
} }
gifts = append(gifts, gift) gifts = append(gifts, gift)
@ -170,15 +132,15 @@ func (u *User) GetGifts() ([]Gift, error) {
func (u *User) GetOtherUserGifts(otherUserReference string) ([]Gift, error) { func (u *User) GetOtherUserGifts(otherUserReference string) ([]Gift, error) {
otherUser, err := GetUserByReference(otherUserReference) otherUser, err := GetUserByReference(otherUserReference)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to get other user: %w", err) return nil, err
} }
if otherUser.Id == u.Id { if otherUser.Id == u.Id {
return nil, fmt.Errorf("Not allowed to view own foreign wishlist") return nil, fmt.Errorf("Not allowed to view own foreign wishlist")
} }
stmt := "SELECT gift.id, gift.name, claimant.id, claimant.name, gift.sent, gift.creator_id, creator.name, gift.recipient_id FROM gift JOIN v_user AS user ON gift.recipient_id = user.id LEFT JOIN v_user AS claimant ON gift.claimant_id = claimant.id LEFT JOIN v_user AS creator ON gift.creator_id = creator.id WHERE user.id = ?" stmt := "SELECT gift.id, gift.name, claimant.id, claimant.name, gift.sent, gift.creator_id, creator.name, gift.recipient_id FROM gift JOIN user ON gift.recipient_id = user.id LEFT JOIN user AS claimant ON gift.claimant_id = claimant.id LEFT JOIN user AS creator ON gift.creator_id = creator.id WHERE user.id = ?"
rows, err := database.Query(stmt, otherUser.Id) rows, err := database.Query(stmt, otherUser.Id)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to execute query: %w", err) return nil, err
} }
defer rows.Close() defer rows.Close()
gifts := []Gift{} gifts := []Gift{}
@ -193,7 +155,7 @@ func (u *User) GetOtherUserGifts(otherUserReference string) ([]Gift, error) {
var recipientId string var recipientId string
err = rows.Scan(&id, &name, &claimantId, &claimantName, &sent, &creatorId, &creatorName, &recipientId) err = rows.Scan(&id, &name, &claimantId, &claimantName, &sent, &creatorId, &creatorName, &recipientId)
if err != nil { if err != nil {
return nil, fmt.Errorf("Failed to scan row: %w", err) return nil, err
} }
gift := Gift{ gift := Gift{
Id: id, Id: id,
@ -209,13 +171,13 @@ func (u *User) GetOtherUserGifts(otherUserReference string) ([]Gift, error) {
} }
err = rows.Err() err = rows.Err()
if err != nil { if err != nil {
return nil, fmt.Errorf("Rows returned an error: %w", err) return nil, err
} }
return gifts, nil return gifts, nil
} }
func (u *User) GetTodo() ([]Gift, error) { func (u *User) GetTodo() ([]Gift, error) {
stmt := "SELECT gift.id, gift.name, gift.sent, recipient.name, recipient.reference FROM gift JOIN v_user AS user ON gift.claimant_id = user.id JOIN v_user AS recipient ON gift.recipient_id = recipient.id WHERE user.id = ? ORDER BY gift.sent ASC, gift.name" stmt := "SELECT gift.id, gift.name, gift.sent, recipient.name, recipient.reference FROM gift JOIN user ON gift.claimant_id = user.id JOIN user AS recipient ON gift.recipient_id = recipient.id WHERE user.id = ? ORDER BY gift.sent ASC, gift.name"
rows, err := database.Query(stmt, u.Id) rows, err := database.Query(stmt, u.Id)
if err != nil { if err != nil {
return nil, err return nil, err
@ -405,7 +367,7 @@ func (u *User) AddGiftToUser(otherUserReference string, giftName string) error {
} }
func (u *User) GetGroups() ([]Group, error) { func (u *User) GetGroups() ([]Group, error) {
stmt := "SELECT [group].id, [group].name, [group].reference FROM [group] JOIN group_member ON group_member.group_id = [group].id JOIN v_user AS user ON user.id = group_member.user_id WHERE user.id = ?" stmt := "SELECT [group].id, [group].name, [group].reference FROM [group] JOIN group_member ON group_member.group_id = [group].id JOIN user ON user.id = group_member.user_id WHERE user.id = ?"
rows, err := database.Query(stmt, u.Id) rows, err := database.Query(stmt, u.Id)
if err != nil { if err != nil {
return nil, err return nil, err
@ -433,6 +395,35 @@ func (u *User) GetGroups() ([]Group, error) {
return groups, nil return groups, nil
} }
func (u *User) GetPeers(groupId string) ([]User, error) {
stmt := "SELECT user.id, user.name, user.reference FROM user JOIN group_member ON group_member.user_id = user.id JOIN [group] ON [group].id = group_member.group_id WHERE [group].id = ? AND user.id != ?"
rows, err := database.Query(stmt, groupId, u.Id)
if err != nil {
return nil, err
}
defer rows.Close()
users := []User{}
for rows.Next() {
var id string
var name string
var reference string
err := rows.Scan(&id, &name, &reference)
if err != nil {
return nil, err
}
users = append(users, User{
Id: id,
Name: name,
Reference: reference,
})
}
err = rows.Err()
if err != nil {
return nil, err
}
return users, nil
}
func (u *User) GetGroupByReference(reference string) (*Group, error) { func (u *User) GetGroupByReference(reference string) (*Group, error) {
stmt := "SELECT [group].id, [group].name, [group].reference FROM [group] JOIN group_member ON [group].id == group_member.group_id WHERE [group].reference = ? AND group_member.user_id = ?" stmt := "SELECT [group].id, [group].name, [group].reference FROM [group] JOIN group_member ON [group].id == group_member.group_id WHERE [group].reference = ? AND group_member.user_id = ?"
return queryForGroup(stmt, reference, u.Id) return queryForGroup(stmt, reference, u.Id)

View File

@ -11,9 +11,9 @@ type pageProps struct {
} }
func Page(w http.ResponseWriter, publicMessage string, status int, err error) { func Page(w http.ResponseWriter, publicMessage string, status int, err error) {
w.WriteHeader(status)
if err != nil { if err != nil {
log.Printf("%s --- %s\n", publicMessage, err) log.Printf("%s --- %s\n", publicMessage, err)
} }
templates.Execute(w, "error_page.gotmpl", pageProps{publicMessage}) templates.Execute(w, "error_page.gotmpl", pageProps{publicMessage})
http.Error(w, "", http.StatusInternalServerError)
} }

View File

@ -14,7 +14,7 @@ import (
func main() { func main() {
gob.Register(&api.RegisterProps{}) gob.Register(&api.RegisterProps{})
gob.Register(&api.LoginProps{}) gob.Register(&routing.LoginProps{})
err := db.Open() err := db.Open()
if err != nil { if err != nil {
@ -27,7 +27,7 @@ func main() {
store, err := db.NewSessionStore() store, err := db.NewSessionStore()
if err != nil { if err != nil {
log.Fatalf("Failed to initialize session store: %s\n", err) log.Fatalf("Failed to ")
} }
store.Options.MaxAge = 86_400 store.Options.MaxAge = 86_400
store.Options.Secure = !env.InDev store.Options.Secure = !env.InDev
@ -44,23 +44,14 @@ func main() {
r.Html.Public.HandleFunc("GET /list/{userReference}", route.PublicWishlist) r.Html.Public.HandleFunc("GET /list/{userReference}", route.PublicWishlist)
r.Html.Public.HandleFunc("GET /group/{groupReference}", route.PublicGroupPage) r.Html.Public.HandleFunc("GET /group/{groupReference}", route.PublicGroupPage)
r.Html.Private.Handle("GET /{$}", route.ExpectUser(route.Home)) r.Html.Private.HandleFunc("GET /{$}", route.Home)
r.Html.Private.Handle("POST /{$}", route.ExpectUser(route.HomePost)) r.Html.Private.HandleFunc("POST /{$}", route.HomePost)
r.Html.Private.Handle("GET /list/{userReference}", route.ExpectUser(route.ForeignWishlist)) r.Html.Private.HandleFunc("GET /list/{userReference}", route.ForeignWishlist)
r.Html.Private.Handle("POST /list/{userReference}", route.ExpectUser(route.ForeignWishlistPost)) r.Html.Private.HandleFunc("POST /list/{userReference}", route.ForeignWishlistPost)
r.Html.Private.Handle("GET /group/{groupReference}", route.ExpectUser(route.GroupPage)) r.Html.Private.HandleFunc("GET /group/{groupReference}", route.GroupPage)
r.Html.Private.HandleFunc("POST /logout", route.LogoutPost) r.Html.Private.HandleFunc("POST /logout", route.LogoutPost)
r.Html.Private.HandleFunc("GET /", routing.NotFound)
r.Json.Public.HandleFunc("GET /", routing.NotFoundJson) r.Json.Public.HandleFunc("POST /register", route.RegisterPostJson)
r.Json.Private.Handle("GET /users", route.ExpectUser(route.UsersJson))
r.Json.Private.Handle("GET /users/{userReference}", route.ExpectUser(route.User))
r.Json.Private.Handle("POST /users/{userReference}", route.ExpectUser(route.UserPost))
r.Json.Private.Handle("GET /groups", route.ExpectUser(route.GroupsJson))
r.Json.Private.Handle("POST /groups/{groupReference}", route.ExpectUser(route.GroupPost))
r.Json.Private.Handle("GET /groups/{groupReference}", route.ExpectUser(route.Group))
r.Json.Private.HandleFunc("GET /", routing.NotFoundJson)
http.Handle("/", r) http.Handle("/", r)

View File

@ -2,7 +2,6 @@ package router
import ( import (
"net/http" "net/http"
"strings"
"github.com/Teajey/sqlstore" "github.com/Teajey/sqlstore"
) )
@ -30,10 +29,10 @@ type Router struct {
} }
func (s *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
accept := r.Header.Get("Accept") contentType := r.Header.Get("Content-Type")
switch { switch contentType {
case strings.HasPrefix(accept, "application/json"): case "application/json":
s.Json.ServeHTTP(w, r) s.Json.ServeHTTP(w, r)
default: default:
s.Html.ServeHTTP(w, r) s.Html.ServeHTTP(w, r)

View File

@ -18,23 +18,16 @@ func NewContext(store *sqlstore.Store) *Context {
} }
} }
func (ctx *Context) ExpectUser(next func(*db.User, http.ResponseWriter, *http.Request)) http.Handler { func (auth *Context) ExpectUser(r *http.Request) *db.User {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { session, _ := auth.store.Get(r, "lishwist_user")
session, _ := ctx.store.Get(r, "lishwist_user")
username, ok := session.Values["username"].(string) username, ok := session.Values["username"].(string)
if !ok { if !ok {
log.Println("Failed to get username") log.Fatalln("Failed to get username")
http.Error(w, "", http.StatusInternalServerError)
return
} }
user, err := db.GetUserByName(username) user, err := db.GetUserByName(username)
if err != nil { if err != nil {
log.Printf("Failed to get user: %s\n", err) log.Fatalf("Failed to get user: %s\n", err)
http.Error(w, "", http.StatusInternalServerError)
return
} }
return user
next(user, w, r)
})
} }

View File

@ -2,16 +2,13 @@ package routing
import ( import (
"fmt" "fmt"
"log"
"net/http" "net/http"
"strings" "strings"
) )
func writeGeneralErrorJson(w http.ResponseWriter, status int, format string, a ...any) { func writeGeneralError(w http.ResponseWriter, msg string, status int) {
msg := fmt.Sprintf(format, a...)
log.Printf("General error: %s\n", msg)
w.Header().Add("Content-Type", "application/json")
w.WriteHeader(status) w.WriteHeader(status)
escapedMsg := strings.ReplaceAll(msg, `"`, `\"`) escapedMsg := strings.ReplaceAll(msg, `"`, `\"`)
_, _ = w.Write([]byte(fmt.Sprintf(`{"GeneralError":"%s"}`, escapedMsg))) _, _ = w.Write([]byte(fmt.Sprintf(`{"GeneralError":"%s"}`, escapedMsg)))
w.Header().Add("Content-Type", "application/json")
} }

View File

@ -14,9 +14,10 @@ type foreignWishlistProps struct {
Gifts []db.Gift Gifts []db.Gift
} }
func (ctx *Context) ForeignWishlist(currentUser *db.User, w http.ResponseWriter, r *http.Request) { func (ctx *Context) ForeignWishlist(w http.ResponseWriter, r *http.Request) {
userReference := r.PathValue("userReference") userReference := r.PathValue("userReference")
if currentUser.Reference == userReference { user := ctx.ExpectUser(r)
if user.Reference == userReference {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
} }
@ -29,12 +30,12 @@ func (ctx *Context) ForeignWishlist(currentUser *db.User, w http.ResponseWriter,
error.Page(w, "User not found", http.StatusNotFound, err) error.Page(w, "User not found", http.StatusNotFound, err)
return return
} }
gifts, err := currentUser.GetOtherUserGifts(userReference) gifts, err := user.GetOtherUserGifts(userReference)
if err != nil { if err != nil {
error.Page(w, "An error occurred while fetching this user's wishlist :(", http.StatusInternalServerError, err) error.Page(w, "An error occurred while fetching this user's wishlist :(", http.StatusInternalServerError, err)
return return
} }
p := foreignWishlistProps{CurrentUserId: currentUser.Id, CurrentUserName: currentUser.Name, Username: otherUser.Name, 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)
} }

View File

@ -1,9 +1,7 @@
package routing package routing
import ( import (
"encoding/json"
"net/http" "net/http"
"slices"
"lishwist/db" "lishwist/db"
"lishwist/error" "lishwist/error"
@ -11,13 +9,15 @@ import (
) )
type GroupProps struct { type GroupProps struct {
Group *db.Group Name string
Members []db.User
CurrentUsername string CurrentUsername string
} }
func (ctx *Context) GroupPage(currentUser *db.User, w http.ResponseWriter, r *http.Request) { func (ctx *Context) GroupPage(w http.ResponseWriter, r *http.Request) {
user := ctx.ExpectUser(r)
groupReference := r.PathValue("groupReference") groupReference := r.PathValue("groupReference")
group, err := currentUser.GetGroupByReference(groupReference) group, err := user.GetGroupByReference(groupReference)
if err != nil { if err != nil {
error.Page(w, "An error occurred while fetching this group :(", http.StatusInternalServerError, err) error.Page(w, "An error occurred while fetching this group :(", http.StatusInternalServerError, err)
return return
@ -26,11 +26,15 @@ func (ctx *Context) GroupPage(currentUser *db.User, w http.ResponseWriter, r *ht
error.Page(w, "Group not found. (It might be because you're not a member)", http.StatusNotFound, nil) error.Page(w, "Group not found. (It might be because you're not a member)", http.StatusNotFound, nil)
return return
} }
index := group.MemberIndex(currentUser.Id) peers, err := user.GetPeers(group.Id)
group.Members = slices.Delete(group.Members, index, index+1) if err != nil {
error.Page(w, "An error occurred while fetching this group :(", http.StatusInternalServerError, err)
return
}
p := GroupProps{ p := GroupProps{
Group: group, Name: group.Name,
CurrentUsername: currentUser.Name, Members: peers,
CurrentUsername: user.Name,
} }
templates.Execute(w, "group_page.gotmpl", p) templates.Execute(w, "group_page.gotmpl", p)
} }
@ -42,114 +46,14 @@ func (ctx *Context) PublicGroupPage(w http.ResponseWriter, r *http.Request) {
error.Page(w, "An error occurred while fetching this group :(", http.StatusInternalServerError, err) error.Page(w, "An error occurred while fetching this group :(", http.StatusInternalServerError, err)
return return
} }
members, err := group.GetMembers()
if err != nil {
error.Page(w, "An error occurred while fetching this group :(", http.StatusInternalServerError, err)
return
}
p := GroupProps{ p := GroupProps{
Group: group, Name: group.Name,
Members: members,
} }
templates.Execute(w, "public_group_page.gotmpl", p) templates.Execute(w, "public_group_page.gotmpl", p)
} }
func (ctx *Context) GroupPost(currentUser *db.User, w http.ResponseWriter, r *http.Request) {
if !currentUser.IsAdmin {
NotFoundJson(w, r)
return
}
if err := r.ParseForm(); err != nil {
writeGeneralErrorJson(w, http.StatusBadRequest, "Failed to parse form: "+err.Error())
return
}
var group *db.Group
reference := r.PathValue("groupReference")
name := r.Form.Get("name")
addUsers := r.Form["addUser"]
removeUsers := r.Form["removeUser"]
if name != "" {
createdGroup, err := db.CreateGroup(name, reference)
if err != nil {
writeGeneralErrorJson(w, http.StatusBadRequest, "Failed to create group: "+err.Error())
return
}
group = createdGroup
} else {
existingGroup, err := db.GetGroupByReference(reference)
if err != nil {
writeGeneralErrorJson(w, http.StatusBadRequest, "Failed to get group: "+err.Error())
return
}
if existingGroup == nil {
writeGeneralErrorJson(w, http.StatusNotFound, "Group not found")
return
}
group = existingGroup
for _, userId := range removeUsers {
index := group.MemberIndex(userId)
if index == -1 {
writeGeneralErrorJson(w, http.StatusBadRequest, "Group %q does not contain a user with id %s", reference, userId)
return
}
err = group.RemoveUser(userId)
if err != nil {
writeGeneralErrorJson(w, http.StatusBadRequest, "On group %q failed to remove user with id %s: %s", reference, userId, err)
return
}
group.Members = slices.Delete(group.Members, index, index+1)
}
}
for _, userId := range addUsers {
user, err := db.GetUser(userId)
if err != nil {
writeGeneralErrorJson(w, http.StatusBadRequest, "Groups exists, but a user with id %s could not be fetched: %s", userId, err)
return
}
if user == nil {
writeGeneralErrorJson(w, http.StatusBadRequest, "Groups exists, but a user with id %s does not exist", userId)
return
}
err = group.AddUser(user.Id)
if err != nil {
writeGeneralErrorJson(w, http.StatusBadRequest, "Groups exists, but failed to add user with id %s: %s", userId, err)
return
}
group.Members = append(group.Members, *user)
}
_ = json.NewEncoder(w).Encode(group)
}
func (ctx *Context) GroupsJson(currentUser *db.User, w http.ResponseWriter, r *http.Request) {
if !currentUser.IsAdmin {
NotFoundJson(w, r)
return
}
groups, err := db.GetAllGroups()
if err != nil {
writeGeneralErrorJson(w, http.StatusBadRequest, "Failed to get groups: "+err.Error())
return
}
_ = json.NewEncoder(w).Encode(groups)
}
func (ctx *Context) Group(currentUser *db.User, w http.ResponseWriter, r *http.Request) {
if !currentUser.IsAdmin {
NotFoundJson(w, r)
return
}
groupReference := r.PathValue("groupReference")
group, err := db.GetGroupByReference(groupReference)
if err != nil {
writeGeneralErrorJson(w, http.StatusBadRequest, "Couldn't get group: %s", err)
return
}
if group == nil {
writeGeneralErrorJson(w, http.StatusNotFound, "Group not found.")
return
}
_ = json.NewEncoder(w).Encode(group)
}

View File

@ -18,40 +18,41 @@ type HomeProps struct {
Groups []db.Group Groups []db.Group
} }
func (ctx *Context) Home(currentUser *db.User, w http.ResponseWriter, r *http.Request) { func (ctx *Context) Home(w http.ResponseWriter, r *http.Request) {
gifts, err := currentUser.GetGifts() user := ctx.ExpectUser(r)
gifts, err := user.GetGifts()
if err != nil { if err != nil {
error.Page(w, "An error occurred while fetching your wishlist :(", http.StatusInternalServerError, err) error.Page(w, "An error occurred while fetching your wishlist :(", http.StatusInternalServerError, err)
return return
} }
todo, err := currentUser.GetTodo() todo, err := user.GetTodo()
if err != nil { if err != nil {
error.Page(w, "An error occurred while fetching your wishlist :(", http.StatusInternalServerError, err) error.Page(w, "An error occurred while fetching your wishlist :(", http.StatusInternalServerError, err)
return return
} }
groups, err := currentUser.GetGroups() groups, err := user.GetGroups()
if err != nil { if err != nil {
error.Page(w, "An error occurred while fetching your wishlist :(", http.StatusInternalServerError, err) error.Page(w, "An error occurred while fetching your wishlist :(", http.StatusInternalServerError, err)
return return
} }
p := HomeProps{Username: currentUser.Name, Gifts: gifts, Todo: todo, Reference: currentUser.Reference, HostUrl: env.HostUrl.String(), Groups: groups} p := HomeProps{Username: user.Name, Gifts: gifts, Todo: todo, Reference: user.Reference, HostUrl: env.HostUrl.String(), Groups: groups}
templates.Execute(w, "home.gotmpl", p) templates.Execute(w, "home.gotmpl", p)
} }
func (ctx *Context) HomePost(currentUser *db.User, w http.ResponseWriter, r *http.Request) { func (ctx *Context) HomePost(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)
return return
} }
switch r.Form.Get("intent") { switch r.Form.Get("intent") {
case "add_idea": case "add_idea":
ctx.WishlistAdd(currentUser, w, r) ctx.WishlistAdd(w, r)
return return
case "delete_idea": case "delete_idea":
ctx.WishlistDelete(currentUser, w, r) ctx.WishlistDelete(w, r)
return return
default: default:
ctx.TodoUpdate(currentUser, w, r) ctx.TodoUpdate(w, r)
return return
} }
} }

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
}

View File

@ -1,18 +1,41 @@
package routing package routing
import ( import (
"encoding/json" "lishwist/db"
"lishwist/api"
sesh "lishwist/session" sesh "lishwist/session"
"lishwist/templates" "lishwist/templates"
"log" "log"
"net/http" "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) { func (ctx *Context) Login(w http.ResponseWriter, r *http.Request) {
session, _ := ctx.store.Get(r, "lishwist_user") session, _ := ctx.store.Get(r, "lishwist_user")
props := api.NewLoginProps("", "") props := NewLoginProps()
flash, err := sesh.GetFirstFlash(w, r, session, "login_props") flash, err := sesh.GetFirstFlash(w, r, session, "login_props")
if err != nil { if err != nil {
@ -20,7 +43,7 @@ func (ctx *Context) Login(w http.ResponseWriter, r *http.Request) {
return return
} }
flashProps, ok := flash.(*api.LoginProps) flashProps, ok := flash.(*LoginProps)
if ok { if ok {
props.Username.Value = flashProps.Username.Value props.Username.Value = flashProps.Username.Value
@ -52,10 +75,28 @@ func (ctx *Context) 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 := api.Login(username, password) props := NewLoginProps()
if props != nil { 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) ctx.RedirectWithFlash(w, r, "/", "login_props", &props)
_ = json.NewEncoder(w).Encode(props)
return return
} }

View File

@ -1,17 +0,0 @@
package routing
import (
"net/http"
"lishwist/error"
)
func NotFoundJson(w http.ResponseWriter, r *http.Request) {
w.Header().Add("Content-Type", "application/json; charset=utf-8")
w.WriteHeader(http.StatusNotFound)
_, _ = w.Write([]byte(`{"GeneralError":"Not Found"}`))
}
func NotFound(w http.ResponseWriter, r *http.Request) {
error.Page(w, "404 -- Page not found", http.StatusNotFound, nil)
}

View File

@ -13,7 +13,6 @@ func (ctx *Context) Register(w http.ResponseWriter, r *http.Request) {
session, _ := ctx.store.Get(r, "lishwist_user") session, _ := ctx.store.Get(r, "lishwist_user")
if flashes := session.Flashes("register_props"); len(flashes) > 0 { if flashes := session.Flashes("register_props"); len(flashes) > 0 {
log.Printf("Register found flashes: %#v\n", flashes)
flashProps, _ := flashes[0].(*api.RegisterProps) flashProps, _ := flashes[0].(*api.RegisterProps)
props.Username.Value = flashProps.Username.Value props.Username.Value = flashProps.Username.Value
@ -45,9 +44,27 @@ func (ctx *Context) RegisterPost(w http.ResponseWriter, r *http.Request) {
if props != nil { if props != nil {
ctx.RedirectWithFlash(w, r, "/register", "register_props", &props) ctx.RedirectWithFlash(w, r, "/register", "register_props", &props)
_ = json.NewEncoder(w).Encode(props)
return return
} }
ctx.RedirectWithFlash(w, r, "/", "successful_registration", true) 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,12 +1,12 @@
package routing package routing
import ( import (
"lishwist/db"
"log" "log"
"net/http" "net/http"
) )
func (ctx *Context) TodoUpdate(currentUser *db.User, w http.ResponseWriter, r *http.Request) { func (ctx *Context) TodoUpdate(w http.ResponseWriter, r *http.Request) {
user := ctx.ExpectUser(r)
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
@ -14,14 +14,14 @@ func (ctx *Context) TodoUpdate(currentUser *db.User, w http.ResponseWriter, r *h
switch r.Form.Get("intent") { switch r.Form.Get("intent") {
case "unclaim_todo": case "unclaim_todo":
unclaims := r.Form["gift"] unclaims := r.Form["gift"]
err := currentUser.ClaimGifts([]string{}, unclaims) err := user.ClaimGifts([]string{}, unclaims)
if err != nil { if err != nil {
http.Error(w, "Failed to update claim...", http.StatusInternalServerError) http.Error(w, "Failed to update claim...", http.StatusInternalServerError)
return return
} }
case "complete_todo": case "complete_todo":
claims := r.Form["gift"] claims := r.Form["gift"]
err := currentUser.CompleteGifts(claims) err := user.CompleteGifts(claims)
if err != nil { if err != nil {
log.Printf("Failed to complete gifts: %s\n", err) log.Printf("Failed to complete gifts: %s\n", err)
http.Error(w, "Failed to complete gifts...", http.StatusInternalServerError) http.Error(w, "Failed to complete gifts...", http.StatusInternalServerError)
@ -29,7 +29,6 @@ func (ctx *Context) TodoUpdate(currentUser *db.User, w http.ResponseWriter, r *h
} }
default: default:
http.Error(w, "Invalid intent", http.StatusBadRequest) http.Error(w, "Invalid intent", http.StatusBadRequest)
return
} }
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
} }

View File

@ -1,82 +0,0 @@
package routing
import (
"encoding/json"
"lishwist/db"
"net/http"
)
func (ctx *Context) UsersJson(currentUser *db.User, w http.ResponseWriter, r *http.Request) {
if !currentUser.IsAdmin {
NotFoundJson(w, r)
return
}
users, err := db.GetAllUsers()
if err != nil {
writeGeneralErrorJson(w, http.StatusInternalServerError, "Failed to get users: "+err.Error())
return
}
_ = json.NewEncoder(w).Encode(users)
}
func (ctx *Context) User(currentUser *db.User, w http.ResponseWriter, r *http.Request) {
if !currentUser.IsAdmin {
NotFoundJson(w, r)
return
}
reference := r.PathValue("userReference")
user, err := db.GetUserByReference(reference)
if err != nil {
writeGeneralErrorJson(w, http.StatusInternalServerError, "Failed to get user: %s", err)
return
}
if user == nil {
writeGeneralErrorJson(w, http.StatusNotFound, "User not found")
return
}
_ = json.NewEncoder(w).Encode(user)
}
func (ctx *Context) UserPost(currentUser *db.User, w http.ResponseWriter, r *http.Request) {
if !currentUser.IsAdmin {
NotFoundJson(w, r)
return
}
if err := r.ParseForm(); err != nil {
writeGeneralErrorJson(w, http.StatusInternalServerError, "Failed to parse form: %s", err)
return
}
reference := r.PathValue("userReference")
if reference == currentUser.Reference {
writeGeneralErrorJson(w, http.StatusForbidden, "You cannot delete yourself.")
return
}
user, err := db.GetAnyUserByReference(reference)
if err != nil {
writeGeneralErrorJson(w, http.StatusInternalServerError, "Failed to get user: %s", err)
return
}
if user == nil {
writeGeneralErrorJson(w, http.StatusNotFound, "User not found")
return
}
intent := r.Form.Get("intent")
if intent != "" {
err = user.SetLive(intent != "delete")
if err != nil {
writeGeneralErrorJson(w, http.StatusInternalServerError, "Failed to delete user: "+err.Error())
return
}
}
_ = json.NewEncoder(w).Encode(user)
}

View File

@ -1,18 +1,18 @@
package routing package routing
import ( import (
"lishwist/db"
"lishwist/error" "lishwist/error"
"net/http" "net/http"
) )
func (ctx *Context) WishlistAdd(currentUser *db.User, w http.ResponseWriter, r *http.Request) { func (ctx *Context) WishlistAdd(w http.ResponseWriter, r *http.Request) {
user := ctx.ExpectUser(r)
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
newGiftName := r.Form.Get("gift_name") newGiftName := r.Form.Get("gift_name")
err := currentUser.AddGift(newGiftName) err := user.AddGift(newGiftName)
if err != nil { if err != nil {
error.Page(w, "Failed to add gift.", http.StatusInternalServerError, err) error.Page(w, "Failed to add gift.", http.StatusInternalServerError, err)
return return
@ -20,13 +20,14 @@ func (ctx *Context) WishlistAdd(currentUser *db.User, w http.ResponseWriter, r *
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
} }
func (ctx *Context) WishlistDelete(currentUser *db.User, w http.ResponseWriter, r *http.Request) { func (ctx *Context) WishlistDelete(w http.ResponseWriter, r *http.Request) {
user := ctx.ExpectUser(r)
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
http.Error(w, err.Error(), http.StatusBadRequest) http.Error(w, err.Error(), http.StatusBadRequest)
return return
} }
targets := r.Form["gift"] targets := r.Form["gift"]
err := currentUser.RemoveGifts(targets...) err := user.RemoveGifts(targets...)
if err != nil { if err != nil {
error.Page(w, "Failed to remove gifts.", http.StatusInternalServerError, err) error.Page(w, "Failed to remove gifts.", http.StatusInternalServerError, err)
return return
@ -34,7 +35,8 @@ func (ctx *Context) WishlistDelete(currentUser *db.User, w http.ResponseWriter,
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
} }
func (ctx *Context) ForeignWishlistPost(currentUser *db.User, w http.ResponseWriter, r *http.Request) { func (ctx *Context) ForeignWishlistPost(w http.ResponseWriter, r *http.Request) {
user := ctx.ExpectUser(r)
if err := r.ParseForm(); err != nil { if err := r.ParseForm(); err != nil {
error.Page(w, "Failed to parse form...", http.StatusBadRequest, err) error.Page(w, "Failed to parse form...", http.StatusBadRequest, err)
return return
@ -44,14 +46,14 @@ func (ctx *Context) ForeignWishlistPost(currentUser *db.User, w http.ResponseWri
case "claim": case "claim":
claims := r.Form["unclaimed"] claims := r.Form["unclaimed"]
unclaims := r.Form["claimed"] unclaims := r.Form["claimed"]
err := currentUser.ClaimGifts(claims, unclaims) err := user.ClaimGifts(claims, unclaims)
if err != nil { if err != nil {
error.Page(w, "Failed to update claim...", http.StatusInternalServerError, err) 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 := currentUser.CompleteGifts(claims) err := user.CompleteGifts(claims)
if err != nil { if err != nil {
error.Page(w, "Failed to complete gifts...", http.StatusInternalServerError, nil) error.Page(w, "Failed to complete gifts...", http.StatusInternalServerError, nil)
return return
@ -62,7 +64,7 @@ func (ctx *Context) ForeignWishlistPost(currentUser *db.User, w http.ResponseWri
error.Page(w, "Gift name not provided", http.StatusBadRequest, nil) error.Page(w, "Gift name not provided", http.StatusBadRequest, nil)
return return
} }
err := currentUser.AddGiftToUser(userReference, giftName) err := user.AddGiftToUser(userReference, giftName)
if err != nil { if err != nil {
error.Page(w, "Failed to add gift idea to other user...", http.StatusInternalServerError, err) error.Page(w, "Failed to add gift idea to other user...", http.StatusInternalServerError, err)
return return
@ -71,7 +73,7 @@ func (ctx *Context) ForeignWishlistPost(currentUser *db.User, w http.ResponseWri
claims := r.Form["unclaimed"] claims := r.Form["unclaimed"]
unclaims := r.Form["claimed"] unclaims := r.Form["claimed"]
gifts := append(claims, unclaims...) gifts := append(claims, unclaims...)
err := currentUser.RemoveGifts(gifts...) err := user.RemoveGifts(gifts...)
if err != nil { if err != nil {
error.Page(w, "Failed to remove gift idea for other user...", http.StatusInternalServerError, err) error.Page(w, "Failed to remove gift idea for other user...", http.StatusInternalServerError, err)
return return

View File

@ -30,8 +30,8 @@
<div class="container py-5"> <div class="container py-5">
<section class="card"> <section class="card">
<div class="card-body"> <div class="card-body">
<h2><em>{{.Group.Name}}</em> group members</h2> <h2><em>{{.Name}}</em> group members</h2>
{{with .Group.Members}} {{with .Members}}
<ul class="list-group"> <ul class="list-group">
{{range .}} {{range .}}
<li class="list-group-item"> <li class="list-group-item">

View File

@ -83,7 +83,7 @@
</ul> </ul>
<button id="unclaimSubmit" class="btn btn-warning" type="submit" name="intent" value="unclaim_todo" <button id="unclaimSubmit" class="btn btn-warning" type="submit" name="intent" value="unclaim_todo"
disabled>Unclaim</button> disabled>Unclaim</button>
<button id="completeSubmit" class="btn btn-success" type="submit" name="intent" value="complete_todo" <button id="completeSubmit" class="btn btn-success" type="submit" name="mode" value="complete_todo"
disabled>Complete</button> disabled>Complete</button>
</form> </form>
{{else}} {{else}}

View File

@ -17,9 +17,9 @@
<div class="container py-5"> <div class="container py-5">
<section class="card"> <section class="card">
<div class="card-body"> <div class="card-body">
<h2><em>{{.Group.Name}}</em> group members</h2> <h2><em>{{.Name}}</em> group members</h2>
<p>{{template "login_prompt"}} to see your groups</p> <p>{{template "login_prompt"}} to see your groups</p>
{{with .Group.Members}} {{with .Members}}
<ul class="list-group"> <ul class="list-group">
{{range .}} {{range .}}
<li class="list-group-item"> <li class="list-group-item">