Compare commits

...

9 Commits

Author SHA1 Message Date
Teajey dcba801dde
feat: user deletion and get user as json 2024-11-22 10:48:41 +09:00
Teajey 20761920d3
feat: page not found 2024-11-21 20:17:04 +09:00
Teajey b48140e9c3
fix: correctly assign member 2024-11-21 20:17:04 +09:00
Teajey 98a39f8e4f
feat: group members always initialized 2024-11-21 20:17:04 +09:00
Teajey 271163a889
feat: groups json interface 2024-11-21 20:17:04 +09:00
Teajey 5a4097f4fe
feat: remove register json post endpoint 2024-11-21 20:17:04 +09:00
Teajey 994f4ee64a
feat: json login support 2024-11-21 20:17:04 +09:00
Teajey fac92511ee
feat: list users json endpoint 2024-11-21 20:17:04 +09:00
Teajey d2fb0fa707
feat: register via json
also lots of refactoring (sorry)
2024-11-21 20:17:03 +09:00
24 changed files with 592 additions and 257 deletions

85
server/api/login.go Normal file
View File

@ -0,0 +1,85 @@
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,6 +17,8 @@ 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
@ -69,24 +71,27 @@ 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.Println("Registration error:", err) log.Printf("Failed to create user: %s\n", err)
props.GeneralError = "Something went wrong. Error code: Ozai" props.GeneralError = "Something went wrong. Error code: Ozai"
return props return props
} }

View File

@ -1,61 +1,121 @@
package db package db
import "database/sql" import (
"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 id string var group Group
var name string err := database.QueryRow(query, args...).Scan(&group.Id, &group.Name, &group.Reference)
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: id, Id: strconv.FormatInt(id, 10),
Name: name, Name: name,
Reference: reference, Reference: reference,
} }
return &group, nil return &group, nil
} }
func GetGroupByReference(reference string) (*Group, error) { func (g *Group) AddUser(userId string) error {
stmt := "SELECT [group].id, [group].name, [group].reference FROM [group] WHERE [group].reference = ?" stmt := "INSERT INTO group_member (group_id, user_id) VALUES (?, ?)"
return queryForGroup(stmt, reference) _, err := database.Exec(stmt, g.Id, userId)
if err != nil {
return err
}
return nil
} }
func (g *Group) GetMembers() ([]User, error) { func (g *Group) RemoveUser(userId string) 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 = ?" stmt := "DELETE FROM group_member WHERE group_id = ? AND user_id = ?"
rows, err := database.Query(stmt, g.Id) _, err := database.Exec(stmt, g.Id, userId)
users := []User{}
if err != nil { if err != nil {
return users, err return err
} }
defer rows.Close() return nil
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,8 +3,10 @@ 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, "motto" TEXT NOT NULL DEFAULT "",
"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" (
@ -37,4 +39,15 @@ 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;

22
server/db/migration/1.sql Normal file
View File

@ -0,0 +1,22 @@
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,6 +11,8 @@ 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 {
@ -27,35 +29,75 @@ type Gift struct {
} }
func queryForUser(query string, args ...any) (*User, error) { func queryForUser(query string, args ...any) (*User, error) {
var id string var u User
var name string err := database.QueryRow(query, args...).Scan(&u.Id, &u.Name, &u.Reference, &u.IsAdmin, &u.IsLive)
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
} }
user := User{ return &u, nil
Id: id, }
Name: name,
Reference: reference, func queryForUsers(query string, args ...any) ([]User, error) {
rows, err := database.Query(query, args...)
if err != nil {
return nil, err
} }
return &user, nil 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 user.id, user.name, user.reference FROM user WHERE user.name = ?" stmt := "SELECT id, name, reference, is_admin, is_live FROM v_user WHERE name = ?"
return queryForUser(stmt, username) return queryForUser(stmt, username)
} }
func GetUserByReference(reference string) (*User, error) { func GetUserByReference(reference string) (*User, error) {
stmt := "SELECT user.id, user.name, user.reference FROM user WHERE user.reference = ?" stmt := "SELECT id, name, reference, is_admin, is_live FROM v_user WHERE 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, motto, reference, password_hash) VALUES (?, '', ?, ?)" stmt := "INSERT INTO user (name, reference, password_hash) VALUES (?, ?, ?)"
reference, err := uuid.NewRandom() reference, err := uuid.NewRandom()
if err != nil { if err != nil {
return nil, err return nil, err
@ -76,7 +118,7 @@ func CreateUser(username string, passHash []byte) (*User, error) {
} }
func (u *User) GetPassHash() ([]byte, error) { func (u *User) GetPassHash() ([]byte, error) {
stmt := "SELECT user.password_hash FROM user WHERE user.id = ?" stmt := "SELECT password_hash FROM v_user WHERE 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 {
@ -86,7 +128,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 JOIN user ON gift.recipient_id = user.id LEFT JOIN user AS claimant ON gift.claimant_id = claimant.id WHERE gift.creator_id = user.id AND user.id = ?" stmt := "SELECT COUNT(gift.id) AS gift_count FROM gift WHERE gift.creator_id = ?1 AND gift.recipient_id = ?1"
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 {
@ -96,7 +138,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, claimant.id, claimant.name, gift.sent FROM gift JOIN user ON gift.recipient_id = user.id LEFT JOIN user AS claimant ON gift.claimant_id = claimant.id WHERE gift.creator_id = user.id AND user.id = ?" stmt := "SELECT gift.id, gift.name, gift.sent FROM gift WHERE gift.creator_id = ?1 AND gift.recipient_id = ?1"
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
@ -106,19 +148,15 @@ 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, &claimantId, &claimantName, &sent) err = rows.Scan(&id, &name, &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, Sent: sent,
ClaimantName: claimantName.String,
Sent: sent,
} }
gifts = append(gifts, gift) gifts = append(gifts, gift)
} }
@ -132,15 +170,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, err return nil, fmt.Errorf("Failed to get other user: %w", 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 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 = ?" 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 = ?"
rows, err := database.Query(stmt, otherUser.Id) rows, err := database.Query(stmt, otherUser.Id)
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("Failed to execute query: %w", err)
} }
defer rows.Close() defer rows.Close()
gifts := []Gift{} gifts := []Gift{}
@ -155,7 +193,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, err return nil, fmt.Errorf("Failed to scan row: %w", err)
} }
gift := Gift{ gift := Gift{
Id: id, Id: id,
@ -171,13 +209,13 @@ func (u *User) GetOtherUserGifts(otherUserReference string) ([]Gift, error) {
} }
err = rows.Err() err = rows.Err()
if err != nil { if err != nil {
return nil, err return nil, fmt.Errorf("Rows returned an error: %w", 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 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" 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"
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
@ -367,7 +405,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 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 v_user AS 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
@ -395,35 +433,6 @@ 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(&routing.LoginProps{}) gob.Register(&api.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 ") log.Fatalf("Failed to initialize session store: %s\n", err)
} }
store.Options.MaxAge = 86_400 store.Options.MaxAge = 86_400
store.Options.Secure = !env.InDev store.Options.Secure = !env.InDev
@ -44,14 +44,23 @@ 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.HandleFunc("GET /{$}", route.Home) r.Html.Private.Handle("GET /{$}", route.ExpectUser(route.Home))
r.Html.Private.HandleFunc("POST /{$}", route.HomePost) r.Html.Private.Handle("POST /{$}", route.ExpectUser(route.HomePost))
r.Html.Private.HandleFunc("GET /list/{userReference}", route.ForeignWishlist) r.Html.Private.Handle("GET /list/{userReference}", route.ExpectUser(route.ForeignWishlist))
r.Html.Private.HandleFunc("POST /list/{userReference}", route.ForeignWishlistPost) r.Html.Private.Handle("POST /list/{userReference}", route.ExpectUser(route.ForeignWishlistPost))
r.Html.Private.HandleFunc("GET /group/{groupReference}", route.GroupPage) r.Html.Private.Handle("GET /group/{groupReference}", route.ExpectUser(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("POST /register", route.RegisterPostJson) r.Json.Public.HandleFunc("GET /", routing.NotFoundJson)
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,6 +2,7 @@ package router
import ( import (
"net/http" "net/http"
"strings"
"github.com/Teajey/sqlstore" "github.com/Teajey/sqlstore"
) )
@ -29,10 +30,10 @@ type Router struct {
} }
func (s *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) { func (s *Router) ServeHTTP(w http.ResponseWriter, r *http.Request) {
contentType := r.Header.Get("Content-Type") accept := r.Header.Get("Accept")
switch contentType { switch {
case "application/json": case strings.HasPrefix(accept, "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,16 +18,23 @@ func NewContext(store *sqlstore.Store) *Context {
} }
} }
func (auth *Context) ExpectUser(r *http.Request) *db.User { func (ctx *Context) ExpectUser(next func(*db.User, http.ResponseWriter, *http.Request)) http.Handler {
session, _ := auth.store.Get(r, "lishwist_user") return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
username, ok := session.Values["username"].(string) session, _ := ctx.store.Get(r, "lishwist_user")
if !ok { username, ok := session.Values["username"].(string)
log.Fatalln("Failed to get username") if !ok {
} log.Println("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.Fatalf("Failed to get user: %s\n", err) log.Printf("Failed to get user: %s\n", err)
} http.Error(w, "", http.StatusInternalServerError)
return user return
}
next(user, w, r)
})
} }

View File

@ -2,13 +2,16 @@ package routing
import ( import (
"fmt" "fmt"
"log"
"net/http" "net/http"
"strings" "strings"
) )
func writeGeneralError(w http.ResponseWriter, msg string, status int) { func writeGeneralErrorJson(w http.ResponseWriter, status int, format string, a ...any) {
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,10 +14,9 @@ type foreignWishlistProps struct {
Gifts []db.Gift Gifts []db.Gift
} }
func (ctx *Context) ForeignWishlist(w http.ResponseWriter, r *http.Request) { func (ctx *Context) ForeignWishlist(currentUser *db.User, w http.ResponseWriter, r *http.Request) {
userReference := r.PathValue("userReference") userReference := r.PathValue("userReference")
user := ctx.ExpectUser(r) if currentUser.Reference == userReference {
if user.Reference == userReference {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
return return
} }
@ -30,12 +29,12 @@ func (ctx *Context) ForeignWishlist(w http.ResponseWriter, r *http.Request) {
error.Page(w, "User not found", http.StatusNotFound, err) error.Page(w, "User not found", http.StatusNotFound, err)
return return
} }
gifts, err := user.GetOtherUserGifts(userReference) gifts, err := currentUser.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: user.Id, CurrentUserName: user.Name, Username: otherUser.Name, Gifts: gifts} p := foreignWishlistProps{CurrentUserId: currentUser.Id, CurrentUserName: currentUser.Name, Username: otherUser.Name, Gifts: gifts}
templates.Execute(w, "foreign_wishlist.gotmpl", p) templates.Execute(w, "foreign_wishlist.gotmpl", p)
} }

View File

@ -1,7 +1,9 @@
package routing package routing
import ( import (
"encoding/json"
"net/http" "net/http"
"slices"
"lishwist/db" "lishwist/db"
"lishwist/error" "lishwist/error"
@ -9,15 +11,13 @@ import (
) )
type GroupProps struct { type GroupProps struct {
Name string Group *db.Group
Members []db.User
CurrentUsername string CurrentUsername string
} }
func (ctx *Context) GroupPage(w http.ResponseWriter, r *http.Request) { func (ctx *Context) GroupPage(currentUser *db.User, w http.ResponseWriter, r *http.Request) {
user := ctx.ExpectUser(r)
groupReference := r.PathValue("groupReference") groupReference := r.PathValue("groupReference")
group, err := user.GetGroupByReference(groupReference) group, err := currentUser.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,15 +26,11 @@ func (ctx *Context) GroupPage(w http.ResponseWriter, r *http.Request) {
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
} }
peers, err := user.GetPeers(group.Id) index := group.MemberIndex(currentUser.Id)
if err != nil { group.Members = slices.Delete(group.Members, index, index+1)
error.Page(w, "An error occurred while fetching this group :(", http.StatusInternalServerError, err)
return
}
p := GroupProps{ p := GroupProps{
Name: group.Name, Group: group,
Members: peers, CurrentUsername: currentUser.Name,
CurrentUsername: user.Name,
} }
templates.Execute(w, "group_page.gotmpl", p) templates.Execute(w, "group_page.gotmpl", p)
} }
@ -46,14 +42,114 @@ 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{
Name: group.Name, Group: group,
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,41 +18,40 @@ type HomeProps struct {
Groups []db.Group Groups []db.Group
} }
func (ctx *Context) Home(w http.ResponseWriter, r *http.Request) { func (ctx *Context) Home(currentUser *db.User, w http.ResponseWriter, r *http.Request) {
user := ctx.ExpectUser(r) gifts, err := currentUser.GetGifts()
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 := user.GetTodo() todo, err := currentUser.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 := user.GetGroups() groups, err := currentUser.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: user.Name, Gifts: gifts, Todo: todo, Reference: user.Reference, HostUrl: env.HostUrl.String(), Groups: groups} p := HomeProps{Username: currentUser.Name, Gifts: gifts, Todo: todo, Reference: currentUser.Reference, HostUrl: env.HostUrl.String(), Groups: groups}
templates.Execute(w, "home.gotmpl", p) templates.Execute(w, "home.gotmpl", p)
} }
func (ctx *Context) HomePost(w http.ResponseWriter, r *http.Request) { func (ctx *Context) HomePost(currentUser *db.User, 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(w, r) ctx.WishlistAdd(currentUser, w, r)
return return
case "delete_idea": case "delete_idea":
ctx.WishlistDelete(w, r) ctx.WishlistDelete(currentUser, w, r)
return return
default: default:
ctx.TodoUpdate(w, r) ctx.TodoUpdate(currentUser, w, r)
return return
} }
} }

View File

@ -1,13 +0,0 @@
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,41 +1,18 @@
package routing package routing
import ( import (
"lishwist/db" "encoding/json"
"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 := NewLoginProps() props := api.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 {
@ -43,7 +20,7 @@ func (ctx *Context) Login(w http.ResponseWriter, r *http.Request) {
return return
} }
flashProps, ok := flash.(*LoginProps) flashProps, ok := flash.(*api.LoginProps)
if ok { if ok {
props.Username.Value = flashProps.Username.Value props.Username.Value = flashProps.Username.Value
@ -75,28 +52,10 @@ 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 := NewLoginProps() props := api.Login(username, password)
props.Username.Value = username if props != nil {
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

@ -0,0 +1,17 @@
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,6 +13,7 @@ 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
@ -44,27 +45,9 @@ 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(w http.ResponseWriter, r *http.Request) { func (ctx *Context) TodoUpdate(currentUser *db.User, 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(w http.ResponseWriter, r *http.Request) {
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 := user.ClaimGifts([]string{}, unclaims) err := currentUser.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 := user.CompleteGifts(claims) err := currentUser.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,6 +29,7 @@ func (ctx *Context) TodoUpdate(w http.ResponseWriter, r *http.Request) {
} }
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)
} }

82
server/routing/users.go Normal file
View File

@ -0,0 +1,82 @@
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(w http.ResponseWriter, r *http.Request) { func (ctx *Context) WishlistAdd(currentUser *db.User, 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 := user.AddGift(newGiftName) err := currentUser.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,14 +20,13 @@ func (ctx *Context) WishlistAdd(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
} }
func (ctx *Context) WishlistDelete(w http.ResponseWriter, r *http.Request) { func (ctx *Context) WishlistDelete(currentUser *db.User, 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 := user.RemoveGifts(targets...) err := currentUser.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
@ -35,8 +34,7 @@ func (ctx *Context) WishlistDelete(w http.ResponseWriter, r *http.Request) {
http.Redirect(w, r, "/", http.StatusSeeOther) http.Redirect(w, r, "/", http.StatusSeeOther)
} }
func (ctx *Context) ForeignWishlistPost(w http.ResponseWriter, r *http.Request) { func (ctx *Context) ForeignWishlistPost(currentUser *db.User, 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
@ -46,14 +44,14 @@ func (ctx *Context) ForeignWishlistPost(w http.ResponseWriter, r *http.Request)
case "claim": case "claim":
claims := r.Form["unclaimed"] claims := r.Form["unclaimed"]
unclaims := r.Form["claimed"] unclaims := r.Form["claimed"]
err := user.ClaimGifts(claims, unclaims) err := currentUser.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 := user.CompleteGifts(claims) err := currentUser.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
@ -64,7 +62,7 @@ func (ctx *Context) ForeignWishlistPost(w http.ResponseWriter, r *http.Request)
error.Page(w, "Gift name not provided", http.StatusBadRequest, nil) error.Page(w, "Gift name not provided", http.StatusBadRequest, nil)
return return
} }
err := user.AddGiftToUser(userReference, giftName) err := currentUser.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
@ -73,7 +71,7 @@ func (ctx *Context) ForeignWishlistPost(w http.ResponseWriter, r *http.Request)
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 := user.RemoveGifts(gifts...) err := currentUser.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>{{.Name}}</em> group members</h2> <h2><em>{{.Group.Name}}</em> group members</h2>
{{with .Members}} {{with .Group.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="mode" value="complete_todo" <button id="completeSubmit" class="btn btn-success" type="submit" name="intent" 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>{{.Name}}</em> group members</h2> <h2><em>{{.Group.Name}}</em> group members</h2>
<p>{{template "login_prompt"}} to see your groups</p> <p>{{template "login_prompt"}} to see your groups</p>
{{with .Members}} {{with .Group.Members}}
<ul class="list-group"> <ul class="list-group">
{{range .}} {{range .}}
<li class="list-group-item"> <li class="list-group-item">