From cc0409d1dcd23d29ca7f987b49964b5f722c59ad Mon Sep 17 00:00:00 2001 From: Teajey <21069848+Teajey@users.noreply.github.com> Date: Thu, 26 Jun 2025 21:03:11 +0900 Subject: [PATCH] feat: use core library --- core/group.go | 22 +- core/internal/db/init.sql | 5 +- core/internal/fixtures/login.go | 5 +- core/login.go | 17 +- core/login_test.go | 5 +- core/register.go | 4 +- core/sesh | 20 ++ core/session.go | 61 +---- core/user.go | 57 ++++- core/wish.go | 188 +++++++++++++- go.work | 6 + go.work.sum | 22 ++ http/api/db/db.go | 62 ----- http/api/db/gen_init_sql.go | 30 --- http/api/db/group.go | 117 --------- http/api/db/init.sql | 54 ---- http/api/db/migration/1.sql | 22 -- http/api/db/user.go | 419 ------------------------------- http/api/login.go | 37 +-- http/api/register.go | 63 +++-- http/main.go | 40 ++- http/routing/context.go | 10 +- http/routing/foreign_wishlist.go | 20 +- http/routing/groups.go | 52 ++-- http/routing/home.go | 26 +- http/routing/login.go | 26 +- http/routing/register.go | 25 +- http/routing/todo.go | 8 +- http/routing/users.go | 27 +- http/routing/wishlist.go | 24 +- 30 files changed, 510 insertions(+), 964 deletions(-) create mode 100644 core/sesh create mode 100644 go.work create mode 100644 go.work.sum delete mode 100644 http/api/db/db.go delete mode 100644 http/api/db/gen_init_sql.go delete mode 100644 http/api/db/group.go delete mode 100644 http/api/db/init.sql delete mode 100644 http/api/db/migration/1.sql delete mode 100644 http/api/db/user.go diff --git a/core/group.go b/core/group.go index 86b1f80..230c475 100644 --- a/core/group.go +++ b/core/group.go @@ -3,7 +3,6 @@ package lishwist import ( "fmt" "lishwist/core/internal/db" - "log" "strconv" "strings" ) @@ -26,12 +25,7 @@ func (g *Group) MemberIndex(userId string) int { func queryManyGroups(query string, args ...any) ([]Group, error) { groups := []Group{} - // PrintTables() - // PrintViews() - log.Println(query, args) rows, err := db.Connection.Query(query, args...) - // PrintTables() - // PrintViews() if err != nil { return nil, fmt.Errorf("Query failed: %w", err) } @@ -76,16 +70,16 @@ func queryManyGroupMembers(groupId string) ([]User, error) { return members, nil } -func (a *Admin) GetGroupByReference(reference string) (*Group, error) { - query := "SELECT [group].id, [group].name, [group].reference FROM [group] WHERE [group].reference = ?;" - return queryOneGroup(query, reference) -} - func (s *Session) 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 = ?;" return queryOneGroup(stmt, reference, s.User.Id) } +func GetGroupByReference(reference string) (*Group, error) { + stmt := "SELECT [group].id, [group].name, [group].reference FROM [group] WHERE [group].reference = ?;" + return queryOneGroup(stmt, reference) +} + func (a *Admin) ListGroups() ([]Group, error) { query := "SELECT id, name, reference FROM [group];" return queryManyGroups(query) @@ -128,3 +122,9 @@ func (a *Admin) RemoveUserFromGroup(userId, groupId string) error { } return nil } + +// Get the groups the session user belongs to +func (u *Session) 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 = ?" + return queryManyGroups(stmt, u.Id) +} diff --git a/core/internal/db/init.sql b/core/internal/db/init.sql index 8da1d9b..e6d94b6 100644 --- a/core/internal/db/init.sql +++ b/core/internal/db/init.sql @@ -37,9 +37,8 @@ CREATE TABLE IF NOT EXISTS "group_member" ( ); CREATE TABLE IF NOT EXISTS "session" ( "id" INTEGER NOT NULL UNIQUE, - "user_id" INTEGER NOT NULL, - PRIMARY KEY("id" AUTOINCREMENT), - FOREIGN KEY("user_id") REFERENCES "user"("id") + "value" TEXT NOT NULL, + PRIMARY KEY("id" AUTOINCREMENT) ); DROP VIEW IF EXISTS "v_user"; diff --git a/core/internal/fixtures/login.go b/core/internal/fixtures/login.go index d2e015e..c730027 100644 --- a/core/internal/fixtures/login.go +++ b/core/internal/fixtures/login.go @@ -3,7 +3,6 @@ package fixtures import ( "log" "testing" - "time" lishwist "lishwist/core" @@ -22,14 +21,12 @@ func Login(t *testing.T, username, password string) *lishwist.Session { log.Fatalf("Failed to init db: %s\n", err) } - lw := lishwist.NewSessionManager(time.Second*10, 32) - _, err = lishwist.Register(username, password) if err != nil { log.Fatalf("Failed to register on login fixture: %s\n", err) } - session, err := lw.Login(username, password) + session, err := lishwist.Login(username, password) if err != nil { log.Fatalf("Failed to login on fixture: %s\n", err) } diff --git a/core/login.go b/core/login.go index 5cfc45f..47659a8 100644 --- a/core/login.go +++ b/core/login.go @@ -6,13 +6,15 @@ import ( "golang.org/x/crypto/bcrypt" ) -func (sm *SessionManager) Login(username, password string) (*Session, error) { +type ErrorInvalidCredentials error + +func Login(username, password string) (*Session, error) { user, err := getUserByName(username) if err != nil { - return nil, fmt.Errorf("Failed to fetch user: %w", err) + return nil, ErrorInvalidCredentials(fmt.Errorf("Failed to fetch user: %w", err)) } if user == nil { - return nil, fmt.Errorf("User not found by name: %s", username) + return nil, ErrorInvalidCredentials(fmt.Errorf("User not found by name: %s", username)) } passHash, err := user.getPassHash() @@ -22,13 +24,8 @@ func (sm *SessionManager) Login(username, password string) (*Session, error) { err = bcrypt.CompareHashAndPassword(passHash, []byte(password)) if err != nil { - return nil, err + return nil, ErrorInvalidCredentials(fmt.Errorf("Password compare failed: %w", err)) } - session, err := sm.createSession(user) - if err != nil { - return nil, fmt.Errorf("Couldn't create session: %w", err) - } - - return session, nil + return &Session{*user}, nil } diff --git a/core/login_test.go b/core/login_test.go index eca5ac0..9603a09 100644 --- a/core/login_test.go +++ b/core/login_test.go @@ -2,7 +2,6 @@ package lishwist_test import ( "testing" - "time" lishwist "lishwist/core" "lishwist/core/internal/fixtures" @@ -14,14 +13,12 @@ func TestLogin(t *testing.T) { t.Fatalf("Failed to init db: %s\n", err) } - lw := lishwist.NewSessionManager(time.Second*10, 32) - _, err = lishwist.Register("thomas", "123") if err != nil { t.Fatalf("Failed to register: %s\n", err) } - _, err = lw.Login("thomas", "123") + _, err = lishwist.Login("thomas", "123") if err != nil { t.Fatalf("Failed to login: %s\n", err) } diff --git a/core/register.go b/core/register.go index bc33707..d8148fc 100644 --- a/core/register.go +++ b/core/register.go @@ -7,6 +7,8 @@ import ( "golang.org/x/crypto/bcrypt" ) +var ErrorUsernameTaken = errors.New("Username is taken") + func Register(username, newPassword string) (*User, error) { if username == "" { return nil, errors.New("Username required") @@ -17,7 +19,7 @@ func Register(username, newPassword string) (*User, error) { existingUser, _ := getUserByName(username) if existingUser != nil { - return nil, errors.New("Username is taken") + return nil, ErrorUsernameTaken } hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost) diff --git a/core/sesh b/core/sesh new file mode 100644 index 0000000..392f621 --- /dev/null +++ b/core/sesh @@ -0,0 +1,20 @@ +{ + "__meta__": { + "about": "xh session file", + "xh": "0.24.1" + }, + "auth": { + "type": null, + "raw_auth": null + }, + "cookies": [ + { + "name": "lishwist_user", + "value": "MTc1MDg2NDE2N3xCQXdBQVRjPXw8gaasdVy--TC-_fUb-3ZL58n8UVakTqDm_0_7c50cYA==", + "expires": 1750950567, + "path": "/lists", + "domain": "127.0.0.1" + } + ], + "headers": [] +} diff --git a/core/session.go b/core/session.go index 5a02181..1a7445b 100644 --- a/core/session.go +++ b/core/session.go @@ -1,64 +1,15 @@ package lishwist -import ( - "crypto/rand" - "encoding/base64" - "fmt" - "lishwist/core/internal/db" - "time" -) +import "fmt" type Session struct { - Id string - Token string - User *User - ExpiresAt time.Time - CreatedAt time.Time + User } -type SessionManager struct { - sessionDuration time.Duration - sessionTokenLength uint -} - -func NewSessionManager(sessionDuration time.Duration, sessionTokenLength uint) SessionManager { - return SessionManager{ - sessionDuration, - sessionTokenLength, - } -} - -func generateSecureToken(size uint) (string, error) { - bytes := make([]byte, size) - if _, err := rand.Read(bytes); err != nil { - return "", err - } - return base64.URLEncoding.EncodeToString(bytes), nil -} - -func (sm *SessionManager) createSession(user *User) (*Session, error) { - stmt := "INSERT INTO session (user_id) VALUES (?);" - result, err := db.Connection.Exec(stmt, user.Id) +func SessionFromUsername(username string) (*Session, error) { + user, err := getUserByName(username) if err != nil { - return nil, fmt.Errorf("Failed to execute query: %w", err) + return nil, fmt.Errorf("Failed to get user: %w", err) } - id, err := result.LastInsertId() - if err != nil { - return nil, fmt.Errorf("Failed to get last insert id: %w", err) - } - - token, err := generateSecureToken(sm.sessionTokenLength) - if err != nil { - return nil, fmt.Errorf("Failed to generate secure token: %w", err) - } - - session := Session{ - Id: fmt.Sprintf("%d", id), - Token: token, - User: user, - ExpiresAt: time.Now().Add(sm.sessionDuration), - CreatedAt: time.Now(), - } - - return &session, nil + return &Session{*user}, nil } diff --git a/core/user.go b/core/user.go index 7fa7fac..b0207cb 100644 --- a/core/user.go +++ b/core/user.go @@ -10,7 +10,8 @@ import ( ) type User struct { - Id string + Id string + // TODO: rename to DisplayName NormalName string Name string Reference string @@ -94,6 +95,11 @@ func getUserByReference(reference string) (*User, error) { return queryOneUser(stmt, reference) } +func getUserById(id string) (*User, error) { + stmt := "SELECT id, name, display_name, reference, is_admin, is_live FROM v_user WHERE id = ?" + return queryOneUser(stmt, id) +} + func hasUsers() (bool, error) { stmt := "SELECT COUNT(id) FROM v_user LIMIT 1" var userCount uint @@ -108,3 +114,52 @@ func (*Admin) ListUsers() ([]User, error) { stmt := "SELECT id, name, display_name, reference, is_admin, is_live FROM user" return queryManyUsers(stmt) } + +func (*Admin) GetUser(id string) (*User, error) { + return getUserById(id) +} + +func GetUserByReference(reference string) (*User, error) { + return getUserByReference(reference) +} + +func (u *User) GetTodo() ([]Wish, error) { + stmt := "SELECT wish.id, wish.name, wish.sent, recipient.name, recipient.reference FROM wish JOIN v_user AS user ON wish.claimant_id = user.id JOIN v_user AS recipient ON wish.recipient_id = recipient.id WHERE user.id = ? ORDER BY wish.sent ASC, wish.name" + rows, err := db.Connection.Query(stmt, u.Id) + if err != nil { + return nil, err + } + defer rows.Close() + wishes := []Wish{} + for rows.Next() { + var id string + var name string + var sent bool + var recipientName string + var recipientRef string + _ = rows.Scan(&id, &name, &sent, &recipientName, &recipientRef) + wish := Wish{ + Id: id, + Name: name, + Sent: sent, + RecipientName: recipientName, + RecipientRef: recipientRef, + } + wishes = append(wishes, wish) + } + err = rows.Err() + if err != nil { + return nil, err + } + return wishes, nil +} + +func (u *Admin) UserSetLive(userReference string, setting bool) error { + query := "UPDATE user SET is_live = ? WHERE reference = ?" + _, err := db.Connection.Exec(query, setting, userReference) + if err != nil { + return err + } + // u.IsLive = setting + return err +} diff --git a/core/wish.go b/core/wish.go index 43befd1..3b7dffb 100644 --- a/core/wish.go +++ b/core/wish.go @@ -61,6 +61,47 @@ func (s *Session) MakeWish(name string) error { return nil } +func (u *Session) deleteWishes(tx *sql.Tx, ids []string) error { + stmt := "DELETE FROM wish WHERE wish.creator_id = ? AND wish.id = ?" + for _, id := range ids { + r, err := tx.Exec(stmt, u.Id, id) + if err != nil { + return err + } + rE, err := r.RowsAffected() + if err != nil { + return err + } + if rE < 1 { + return fmt.Errorf("Wish deletion failed for '%s'", id) + } + } + return nil +} + +func (s *Session) RevokeWishes(ids ...string) error { + if len(ids) < 1 { + return fmt.Errorf("Attempt to remove zero wishes") + } + + tx, err := db.Connection.Begin() + if err != nil { + return err + } + + err = s.deleteWishes(tx, ids) + if err != nil { + rollBackErr := tx.Rollback() + if rollBackErr != nil { + return err + } + return err + } + + err = tx.Commit() + return err +} + func (s *Session) GetOthersWishes(userReference string) ([]Wish, error) { otherUser, err := getUserByReference(userReference) if err != nil { @@ -69,7 +110,7 @@ func (s *Session) GetOthersWishes(userReference string) ([]Wish, error) { if otherUser.Id == s.User.Id { return nil, errors.New("Use (s *Session) GetWishes() to view your own wishes") } - 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 wish.id, wish.name, claimant.id, claimant.name, wish.sent, wish.creator_id, creator.name, wish.recipient_id FROM wish JOIN v_user AS user ON wish.recipient_id = user.id LEFT JOIN v_user AS claimant ON wish.claimant_id = claimant.id LEFT JOIN v_user AS creator ON wish.creator_id = creator.id WHERE user.id = ?" rows, err := db.Connection.Query(stmt, otherUser.Id) if err != nil { return nil, fmt.Errorf("Failed to execute query: %w", err) @@ -107,3 +148,148 @@ func (s *Session) GetOthersWishes(userReference string) ([]Wish, error) { } return wishes, nil } + +// NOTE: This could just be a field on the user... but only if we get this often +func (u *User) WishCount() (int, error) { + stmt := "SELECT COUNT(wish.id) AS wish_count FROM wish WHERE wish.creator_id = ?1 AND wish.recipient_id = ?1" + var wishCount int + err := db.Connection.QueryRow(stmt, u.Id).Scan(&wishCount) + if err != nil { + return 0, err + } + return wishCount, nil +} + +func (s *Session) executeClaims(tx *sql.Tx, claims, unclaims []string) error { + claimStmt := "UPDATE wish SET claimant_id = ? WHERE id = ?" + unclaimStmt := "UPDATE wish SET claimant_id = NULL WHERE id = ?" + for _, id := range claims { + r, err := tx.Exec(claimStmt, s.Id, id) + if err != nil { + return err + } + rE, err := r.RowsAffected() + if err != nil { + return err + } + if rE < 1 { + return fmt.Errorf("Wish claim failed for '%s'", id) + } + } + for _, id := range unclaims { + r, err := tx.Exec(unclaimStmt, id) + if err != nil { + return err + } + rE, err := r.RowsAffected() + if err != nil { + return err + } + if rE < 1 { + return fmt.Errorf("Wish unclaim failed for '%s'", id) + } + } + return nil +} + +// Undertake or abandon wishes made by other users +func (s *Session) ClaimWishes(claims, unclaims []string) error { + if len(claims) < 1 && len(unclaims) < 1 { + return fmt.Errorf("Attempt to claim/unclaim zero wishes") + } + + tx, err := db.Connection.Begin() + if err != nil { + return err + } + + err = s.executeClaims(tx, claims, unclaims) + if err != nil { + rollBackErr := tx.Rollback() + if rollBackErr != nil { + return err + } + return err + } + + err = tx.Commit() + return err +} + +func executeCompletions(tx *sql.Tx, claims []string) error { + claimStmt := "UPDATE wish SET sent = 1 WHERE id = ?" + for _, id := range claims { + r, err := tx.Exec(claimStmt, id) + if err != nil { + return err + } + rE, err := r.RowsAffected() + if err != nil { + return err + } + if rE < 1 { + return fmt.Errorf("Wish completion failed for '%s'", id) + } + } + return nil +} + +// TODO: User ought not be able to interact with wishes outside their group network +func (s *Session) CompleteWishes(claims []string) error { + if len(claims) < 1 { + return fmt.Errorf("Attempt to complete zero wishes") + } + + tx, err := db.Connection.Begin() + if err != nil { + return err + } + + err = executeCompletions(tx, claims) + if err != nil { + rollBackErr := tx.Rollback() + if rollBackErr != nil { + return err + } + return err + } + + err = tx.Commit() + return err +} + +func (u *Session) SuggestWishForUser(otherUserReference string, wishName string) error { + otherUser, err := GetUserByReference(otherUserReference) + if err != nil { + return err + } + stmt := "INSERT INTO wish (name, recipient_id, creator_id) VALUES (?, ?, ?)" + _, err = db.Connection.Exec(stmt, wishName, otherUser.Id, u.Id) + if err != nil { + return err + } + return nil +} + +func (s *Session) RecindWishesForUser(ids ...string) error { + if len(ids) < 1 { + return fmt.Errorf("Attempt to remove zero wishes") + } + + tx, err := db.Connection.Begin() + if err != nil { + return err + } + + err = s.deleteWishes(tx, ids) + if err != nil { + rollBackErr := tx.Rollback() + if rollBackErr != nil { + return err + } + return err + } + + err = tx.Commit() + return err +} diff --git a/go.work b/go.work new file mode 100644 index 0000000..7328ab8 --- /dev/null +++ b/go.work @@ -0,0 +1,6 @@ +go 1.23.3 + +use ( + ./core + ./http +) diff --git a/go.work.sum b/go.work.sum new file mode 100644 index 0000000..dbc3473 --- /dev/null +++ b/go.work.sum @@ -0,0 +1,22 @@ +github.com/dchest/siphash v1.2.3/go.mod h1:0NvQU092bT0ipiFN++/rXm69QG9tVxLAlQHIXMPAkHc= +github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8= +github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY= +github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg= +github.com/ncruces/sort v0.1.5/go.mod h1:obJToO4rYr6VWP0Uw5FYymgYGt3Br4RXcs/JdKaXAPk= +github.com/psanford/httpreadat v0.1.0/go.mod h1:Zg7P+TlBm3bYbyHTKv/EdtSJZn3qwbPwpfZ/I9GKCRE= +golang.org/x/mod v0.25.0/go.mod h1:IXM97Txy2VM4PJ3gI61r1YEk/gAj6zAHN3AdZt6S9Ww= +golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= +golang.org/x/sync v0.15.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/term v0.32.0/go.mod h1:uZG1FhGx848Sqfsq4/DlJr3xGGsYMu/L5GW4abiaEPQ= +golang.org/x/tools v0.33.0/go.mod h1:CIJMaWEY88juyUfo7UbgPqbC8rU2OqfAV1h2Qp0oMYI= +golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90= +lukechampine.com/adiantum v1.1.1/go.mod h1:LrAYVnTYLnUtE/yMp5bQr0HstAf060YUF8nM0B6+rUw= +lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk= +modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y= +modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI= +modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM= +modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0= +modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0= +modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c= +modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM= +modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE= diff --git a/http/api/db/db.go b/http/api/db/db.go deleted file mode 100644 index f4fb502..0000000 --- a/http/api/db/db.go +++ /dev/null @@ -1,62 +0,0 @@ -//go:generate go run gen_init_sql.go - -package db - -import ( - "database/sql" - "fmt" - "lishwist/http/env" - - "github.com/Teajey/sqlstore" - _ "github.com/glebarez/go-sqlite" -) - -var database *sql.DB - -func Open() error { - db, err := sql.Open("sqlite", env.DatabaseFile) - if err != nil { - return err - } - database = db - return nil -} - -func Init() error { - _, err := database.Exec(initQuery) - if err != nil { - return err - } - return nil -} - -func NewSessionStore() (*sqlstore.Store, error) { - deleteStmt, err := database.Prepare("DELETE FROM session WHERE id = ?;") - if err != nil { - return nil, fmt.Errorf("Failed to prepare delete statement: %w", err) - } - - insertStmt, err := database.Prepare("INSERT INTO session (value) VALUES (?);") - if err != nil { - return nil, fmt.Errorf("Failed to prepare insert statement: %w", err) - } - - selectStmt, err := database.Prepare("SELECT value FROM session WHERE id = ?;") - if err != nil { - return nil, fmt.Errorf("Failed to prepare select statement: %w", err) - } - - updateStmt, err := database.Prepare("UPDATE session SET value = ?2 WHERE id = ?1;") - if err != nil { - return nil, fmt.Errorf("Failed to prepare update statement: %w", err) - } - - s := sqlstore.NewSqlStore(database, sqlstore.Statements{ - Delete: deleteStmt, - Insert: insertStmt, - Select: selectStmt, - Update: updateStmt, - }, []byte(env.SessionSecret)) - - return s, nil -} diff --git a/http/api/db/gen_init_sql.go b/http/api/db/gen_init_sql.go deleted file mode 100644 index 0e3da1b..0000000 --- a/http/api/db/gen_init_sql.go +++ /dev/null @@ -1,30 +0,0 @@ -//go:build ignore - -package main - -import ( - "log" - "os" - "text/template" -) - -var initTemplate = template.Must(template.New("").Parse("// Code generated DO NOT EDIT.\n" + - "package db\n" + - "\n" + - "const initQuery = `{{.}}`\n", -)) - -func main() { - initStmt, err := os.ReadFile("./init.sql") - if err != nil { - log.Fatal(err) - } - - f, err := os.Create("./init_sql.go") - if err != nil { - log.Fatal(err) - } - defer f.Close() - - initTemplate.Execute(f, string(initStmt)) -} diff --git a/http/api/db/group.go b/http/api/db/group.go deleted file mode 100644 index eee6785..0000000 --- a/http/api/db/group.go +++ /dev/null @@ -1,117 +0,0 @@ -package db - -import ( - "fmt" - "lishwist/http/normalize" - "strconv" -) - -type Group struct { - Id string - Name 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 queryManyGroups(query string, args ...any) ([]Group, error) { - groups := []Group{} - rows, err := database.Query(query, args...) - if err != nil { - return nil, fmt.Errorf("Query failed: %w", err) - } - defer rows.Close() - for rows.Next() { - var group Group - err := rows.Scan(&group.Id, &group.Name, &group.Reference) - if err != nil { - return nil, fmt.Errorf("Failed to scan row: %w", err) - } - members, err := queryManyGroupMembers(group.Id) - if err != nil { - return nil, fmt.Errorf("Failed to query for group members: %w", err) - } - group.Members = members - groups = append(groups, group) - } - err = rows.Err() - if err != nil { - return nil, fmt.Errorf("Rows error: %w", err) - } - return groups, nil -} - -func queryOneGroup(query string, args ...any) (*Group, error) { - groups, err := queryManyGroups(query, args...) - if err != nil { - return nil, err - } - if len(groups) < 1 { - return nil, nil - } - return &groups[0], nil -} - -func queryManyGroupMembers(groupId string) ([]User, error) { - query := "SELECT user.id, user.name, user.display_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 := queryManyUsers(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 queryOneGroup(query, reference) -} - -func GetAllGroups() ([]Group, error) { - query := "SELECT id, name, reference FROM [group];" - return queryManyGroups(query) -} - -func CreateGroup(name string, reference string) (*Group, error) { - name = normalize.Trim(name) - 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{ - Id: strconv.FormatInt(id, 10), - Name: name, - Reference: reference, - } - return &group, nil -} - -func (g *Group) AddUser(userId string) error { - stmt := "INSERT INTO group_member (group_id, user_id) VALUES (?, ?)" - _, err := database.Exec(stmt, g.Id, userId) - if err != nil { - return err - } - return nil -} - -func (g *Group) RemoveUser(userId string) error { - stmt := "DELETE FROM group_member WHERE group_id = ? AND user_id = ?" - _, err := database.Exec(stmt, g.Id, userId) - if err != nil { - return err - } - return nil -} diff --git a/http/api/db/init.sql b/http/api/db/init.sql deleted file mode 100644 index 2780036..0000000 --- a/http/api/db/init.sql +++ /dev/null @@ -1,54 +0,0 @@ -BEGIN TRANSACTION; -CREATE TABLE IF NOT EXISTS "user" ( - "id" INTEGER NOT NULL UNIQUE, - "name" TEXT NOT NULL UNIQUE, - "display_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) -); -CREATE TABLE IF NOT EXISTS "gift" ( - "id" INTEGER NOT NULL UNIQUE, - "name" TEXT NOT NULL, - "recipient_id" INTEGER NOT NULL, - "claimant_id" INTEGER, - "creator_id" INTEGER NOT NULL, - "sent" INTEGER NOT NULL DEFAULT 0, - PRIMARY KEY("id" AUTOINCREMENT), - FOREIGN KEY("recipient_id") REFERENCES "user"("id"), - FOREIGN KEY("creator_id") REFERENCES "user"("id"), - FOREIGN KEY("claimant_id") REFERENCES "user"("id") -); -CREATE TABLE IF NOT EXISTS "group" ( - "id" INTEGER NOT NULL UNIQUE, - "name" TEXT NOT NULL UNIQUE, - "reference" TEXT NOT NULL UNIQUE, - PRIMARY KEY("id" AUTOINCREMENT) -); -CREATE TABLE IF NOT EXISTS "group_member" ( - "group_id" INTEGER NOT NULL, - "user_id" INTEGER NOT NULL, - UNIQUE("user_id","group_id"), - FOREIGN KEY("group_id") REFERENCES "group"("id"), - FOREIGN KEY("user_id") REFERENCES "user"("id") -); -CREATE TABLE IF NOT EXISTS "session" ( - "id" INTEGER NOT NULL UNIQUE, - "value" TEXT NOT NULL, - PRIMARY KEY("id" AUTOINCREMENT) -); - -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; diff --git a/http/api/db/migration/1.sql b/http/api/db/migration/1.sql deleted file mode 100644 index c286b74..0000000 --- a/http/api/db/migration/1.sql +++ /dev/null @@ -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; diff --git a/http/api/db/user.go b/http/api/db/user.go deleted file mode 100644 index b1beb15..0000000 --- a/http/api/db/user.go +++ /dev/null @@ -1,419 +0,0 @@ -package db - -import ( - "database/sql" - "fmt" - "lishwist/http/normalize" - - "github.com/google/uuid" -) - -type User struct { - Id string - NormalName string - Name string - Reference string - IsAdmin bool - IsLive bool -} - -type Gift struct { - Id string - Name string - ClaimantId string `json:",omitempty"` - ClaimantName string `json:",omitempty"` - Sent bool - RecipientId string `json:",omitempty"` - RecipientName string `json:",omitempty"` - RecipientRef string `json:",omitempty"` - CreatorId string `json:",omitempty"` - CreatorName string `json:",omitempty"` -} - -func queryManyUsers(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.NormalName, &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 queryOneUser(query string, args ...any) (*User, error) { - users, err := queryManyUsers(query, args...) - if err != nil { - return nil, err - } - if len(users) < 1 { - return nil, nil - } - return &users[0], nil -} - -func GetAllUsers() ([]User, error) { - stmt := "SELECT id, name, display_name, reference, is_admin, is_live FROM user" - return queryManyUsers(stmt) -} - -func GetUser(id string) (*User, error) { - stmt := "SELECT id, name, display_name, reference, is_admin, is_live FROM v_user WHERE id = ?" - return queryOneUser(stmt, id) -} - -func GetUserByName(username string) (*User, error) { - username = normalize.Name(username) - stmt := "SELECT id, name, display_name, reference, is_admin, is_live FROM v_user WHERE name = ?" - return queryOneUser(stmt, username) -} - -func GetUserByReference(reference string) (*User, error) { - stmt := "SELECT id, name, display_name, reference, is_admin, is_live FROM v_user WHERE reference = ?" - return queryOneUser(stmt, reference) -} - -func GetAnyUserByReference(reference string) (*User, error) { - stmt := "SELECT id, name, display_name, reference, is_admin, is_live FROM user WHERE reference = ?" - return queryOneUser(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(name string, passHash []byte) (*User, error) { - username := normalize.Name(name) - stmt := "INSERT INTO user (name, display_name, reference, password_hash) VALUES (?, ?, ?, ?)" - reference, err := uuid.NewRandom() - if err != nil { - return nil, err - } - result, err := database.Exec(stmt, username, name, reference, passHash) - if err != nil { - return nil, err - } - id, err := result.LastInsertId() - if err != nil { - return nil, err - } - user := User{ - Id: fmt.Sprintf("%d", id), - Name: name, - } - return &user, nil -} - -func (u *User) GetPassHash() ([]byte, error) { - stmt := "SELECT password_hash FROM v_user WHERE id = ?" - var passHash string - err := database.QueryRow(stmt, u.Id).Scan(&passHash) - if err != nil { - return nil, err - } - return []byte(passHash), nil -} - -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" - var giftCount int - err := database.QueryRow(stmt, u.Id).Scan(&giftCount) - if err != nil { - return 0, err - } - return giftCount, nil -} - -func (u *User) GetGifts() ([]Gift, error) { - 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) - if err != nil { - return nil, err - } - defer rows.Close() - gifts := []Gift{} - for rows.Next() { - var id string - var name string - var sent bool - err = rows.Scan(&id, &name, &sent) - if err != nil { - return nil, err - } - gift := Gift{ - Id: id, - Name: name, - Sent: sent, - } - gifts = append(gifts, gift) - } - err = rows.Err() - if err != nil { - return nil, err - } - return gifts, nil -} - -func (u *User) GetOtherUserGifts(otherUserReference string) ([]Gift, error) { - otherUser, err := GetUserByReference(otherUserReference) - if err != nil { - return nil, fmt.Errorf("Failed to get other user: %w", err) - } - if otherUser.Id == u.Id { - 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 = ?" - rows, err := database.Query(stmt, otherUser.Id) - if err != nil { - return nil, fmt.Errorf("Failed to execute query: %w", err) - } - defer rows.Close() - gifts := []Gift{} - for rows.Next() { - var id string - var name string - var claimantId sql.NullString - var claimantName sql.NullString - var sent bool - var creatorId string - var creatorName string - var recipientId string - err = rows.Scan(&id, &name, &claimantId, &claimantName, &sent, &creatorId, &creatorName, &recipientId) - if err != nil { - return nil, fmt.Errorf("Failed to scan row: %w", err) - } - gift := Gift{ - Id: id, - Name: name, - ClaimantId: claimantId.String, - ClaimantName: claimantName.String, - Sent: sent, - CreatorId: creatorId, - CreatorName: creatorName, - RecipientId: recipientId, - } - gifts = append(gifts, gift) - } - err = rows.Err() - if err != nil { - return nil, fmt.Errorf("Rows returned an error: %w", err) - } - return gifts, nil -} - -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" - rows, err := database.Query(stmt, u.Id) - if err != nil { - return nil, err - } - defer rows.Close() - gifts := []Gift{} - for rows.Next() { - var id string - var name string - var sent bool - var recipientName string - var recipientRef string - _ = rows.Scan(&id, &name, &sent, &recipientName, &recipientRef) - gift := Gift{ - Id: id, - Name: name, - Sent: sent, - RecipientName: recipientName, - RecipientRef: recipientRef, - } - gifts = append(gifts, gift) - } - err = rows.Err() - if err != nil { - return nil, err - } - return gifts, nil -} - -func (u *User) AddGift(name string) error { - stmt := "INSERT INTO gift (name, recipient_id, creator_id) VALUES (?, ?, ?)" - _, err := database.Exec(stmt, name, u.Id, u.Id) - if err != nil { - return err - } - return nil -} - -func (u *User) deleteGifts(tx *sql.Tx, ids []string) error { - stmt := "DELETE FROM gift WHERE gift.creator_id = ? AND gift.id = ?" - for _, id := range ids { - r, err := tx.Exec(stmt, u.Id, id) - if err != nil { - return err - } - rE, err := r.RowsAffected() - if err != nil { - return err - } - if rE < 1 { - return fmt.Errorf("Gift deletion failed for '%s'", id) - } - } - return nil -} - -func (u *User) RemoveGifts(ids ...string) error { - if len(ids) < 1 { - return fmt.Errorf("Attempt to remove zero gifts") - } - - tx, err := database.Begin() - if err != nil { - return err - } - - err = u.deleteGifts(tx, ids) - if err != nil { - rollBackErr := tx.Rollback() - if rollBackErr != nil { - return err - } - return err - } - - err = tx.Commit() - return err -} - -func (u *User) executeClaims(tx *sql.Tx, claims, unclaims []string) error { - claimStmt := "UPDATE gift SET claimant_id = ? WHERE id = ?" - unclaimStmt := "UPDATE gift SET claimant_id = NULL WHERE id = ?" - for _, id := range claims { - r, err := tx.Exec(claimStmt, u.Id, id) - if err != nil { - return err - } - rE, err := r.RowsAffected() - if err != nil { - return err - } - if rE < 1 { - return fmt.Errorf("Gift claim failed for '%s'", id) - } - } - for _, id := range unclaims { - r, err := tx.Exec(unclaimStmt, id) - if err != nil { - return err - } - rE, err := r.RowsAffected() - if err != nil { - return err - } - if rE < 1 { - return fmt.Errorf("Gift unclaim failed for '%s'", id) - } - } - return nil -} - -func (u *User) ClaimGifts(claims, unclaims []string) error { - if len(claims) < 1 && len(unclaims) < 1 { - return fmt.Errorf("Attempt to claim/unclaim zero gifts") - } - - tx, err := database.Begin() - if err != nil { - return err - } - - err = u.executeClaims(tx, claims, unclaims) - if err != nil { - rollBackErr := tx.Rollback() - if rollBackErr != nil { - return err - } - return err - } - - err = tx.Commit() - return err -} - -func (u *User) executeCompletions(tx *sql.Tx, claims []string) error { - claimStmt := "UPDATE gift SET sent = 1 WHERE id = ?" - for _, id := range claims { - r, err := tx.Exec(claimStmt, id) - if err != nil { - return err - } - rE, err := r.RowsAffected() - if err != nil { - return err - } - if rE < 1 { - return fmt.Errorf("Gift completion failed for '%s'", id) - } - } - return nil -} - -func (u *User) CompleteGifts(claims []string) error { - if len(claims) < 1 { - return fmt.Errorf("Attempt to complete zero gifts") - } - - tx, err := database.Begin() - if err != nil { - return err - } - - err = u.executeCompletions(tx, claims) - if err != nil { - rollBackErr := tx.Rollback() - if rollBackErr != nil { - return err - } - return err - } - - err = tx.Commit() - return err -} - -func (u *User) AddGiftToUser(otherUserReference string, giftName string) error { - otherUser, err := GetUserByReference(otherUserReference) - if err != nil { - return err - } - stmt := "INSERT INTO gift (name, recipient_id, creator_id) VALUES (?, ?, ?)" - _, err = database.Exec(stmt, giftName, otherUser.Id, u.Id) - if err != nil { - return err - } - return nil -} - -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 = ?" - return queryManyGroups(stmt, u.Id) -} - -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 = ?" - return queryOneGroup(stmt, reference, u.Id) -} diff --git a/http/api/login.go b/http/api/login.go index 64c12f5..9a8b24c 100644 --- a/http/api/login.go +++ b/http/api/login.go @@ -1,11 +1,10 @@ package api import ( - "lishwist/http/api/db" - "lishwist/http/templates" "log" - "golang.org/x/crypto/bcrypt" + lishwist "lishwist/core" + "lishwist/http/templates" ) type LoginProps struct { @@ -55,31 +54,19 @@ func Login(username, password string) *LoginProps { 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 + _, err := lishwist.Login(props.Username.Value, props.Password.Value) + if err == nil { + return nil } - 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()) + switch err.(type) { + case lishwist.ErrorInvalidCredentials: + log.Printf("Invalid credentials: %w\n", err) props.GeneralError = "Username or password invalid" return props + default: + log.Printf("Login error: %w\n", err) + props.GeneralError = "Something went wrong." + return props } - - return nil } diff --git a/http/api/register.go b/http/api/register.go index 1a4eca2..c8e4301 100644 --- a/http/api/register.go +++ b/http/api/register.go @@ -1,12 +1,7 @@ package api import ( - "log" - - "lishwist/http/api/db" "lishwist/http/templates" - - "golang.org/x/crypto/bcrypt" ) type RegisterProps struct { @@ -64,37 +59,37 @@ func NewRegisterProps(usernameVal, passwordVal, confirmPassVal string) *Register } } -func Register(username, newPassword, confirmPassword string) *RegisterProps { - props := NewRegisterProps(username, newPassword, confirmPassword) +// func Register(username, newPassword, confirmPassword string) *RegisterProps { +// props := NewRegisterProps(username, newPassword, confirmPassword) - valid := props.Validate() - props.Password.Value = "" - props.ConfirmPassword.Value = "" - if !valid { - log.Printf("Invalid props: %#v\n", props) - return props - } +// valid := props.Validate() +// props.Password.Value = "" +// props.ConfirmPassword.Value = "" +// if !valid { +// log.Printf("Invalid props: %#v\n", props) +// return props +// } - existingUser, _ := db.GetUserByName(username) - if existingUser != nil { - log.Printf("Username is taken: %q\n", existingUser.NormalName) - props.Username.Error = "Username is taken" - return props - } +// existingUser, _ := db.GetUserByName(username) +// if existingUser != nil { +// log.Printf("Username is taken: %q\n", existingUser.NormalName) +// props.Username.Error = "Username is taken" +// return props +// } - hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost) - if err != nil { - log.Printf("Failed to hash password: %s\n", err) - props.GeneralError = "Something went wrong. Error code: Aang" - return props - } +// hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost) +// if err != nil { +// log.Printf("Failed to hash password: %s\n", err) +// props.GeneralError = "Something went wrong. Error code: Aang" +// return props +// } - _, err = db.CreateUser(username, hashedPasswordBytes) - if err != nil { - log.Printf("Failed to create user: %s\n", err) - props.GeneralError = "Something went wrong. Error code: Ozai" - return props - } +// _, err = db.CreateUser(username, hashedPasswordBytes) +// if err != nil { +// log.Printf("Failed to create user: %s\n", err) +// props.GeneralError = "Something went wrong. Error code: Ozai" +// return props +// } - return nil -} +// return nil +// } diff --git a/http/main.go b/http/main.go index 640577b..ad53076 100644 --- a/http/main.go +++ b/http/main.go @@ -5,9 +5,9 @@ import ( "log" "net/http" + lishwist "lishwist/core" + "lishwist/core/session" "lishwist/http/api" - // TODO: lishwist/http/api/db ought not to be used outside lishwist/http/api - "lishwist/http/api/db" "lishwist/http/env" "lishwist/http/router" "lishwist/http/routing" @@ -17,16 +17,12 @@ func main() { gob.Register(&api.RegisterProps{}) gob.Register(&api.LoginProps{}) - err := db.Open() + err := lishwist.Init(env.DatabaseFile) if err != nil { - log.Fatalf("Failed to open DB: %s\n", err) - } - err = db.Init() - if err != nil { - log.Fatalf("Failed to init DB: %s\n", err) + log.Fatalf("Failed to init Lishwist: %s\n", err) } - store, err := db.NewSessionStore() + store, err := session.NewStore([]byte(env.SessionSecret)) if err != nil { log.Fatalf("Failed to initialize session store: %s\n", err) } @@ -38,27 +34,29 @@ func main() { r.Public.HandleFunc("GET /", routing.Login) r.Public.HandleFunc("GET /groups/{groupReference}", routing.PublicGroup) - r.Public.HandleFunc("GET /list/{userReference}", routing.PublicWishlist) + r.Public.HandleFunc("GET /lists/{userReference}", routing.PublicWishlist) r.Public.HandleFunc("GET /register", routing.Register) r.Public.HandleFunc("POST /", routing.LoginPost) r.Public.HandleFunc("POST /register", routing.RegisterPost) r.Private.HandleFunc("GET /", routing.NotFound) - r.Private.HandleFunc("GET /groups", routing.ExpectUser(routing.Groups)) - r.Private.HandleFunc("GET /groups/{groupReference}", routing.ExpectUser(routing.Group)) - r.Private.HandleFunc("GET /list/{userReference}", routing.ExpectUser(routing.ForeignWishlist)) - r.Private.HandleFunc("GET /users", routing.ExpectUser(routing.Users)) - r.Private.HandleFunc("GET /users/{userReference}", routing.ExpectUser(routing.User)) - r.Private.HandleFunc("GET /{$}", routing.ExpectUser(routing.Home)) - r.Private.HandleFunc("POST /groups/{groupReference}", routing.ExpectUser(routing.GroupPost)) - r.Private.HandleFunc("POST /list/{userReference}", routing.ExpectUser(routing.ForeignWishlistPost)) + r.Private.HandleFunc("GET /groups", routing.ExpectAppSession(routing.Groups)) + r.Private.HandleFunc("GET /groups/{groupReference}", routing.ExpectAppSession(routing.Group)) + r.Private.HandleFunc("GET /lists/{userReference}", routing.ExpectAppSession(routing.ForeignWishlist)) + r.Private.HandleFunc("GET /users", routing.ExpectAppSession(routing.Users)) + r.Private.HandleFunc("GET /users/{userReference}", routing.ExpectAppSession(routing.User)) + r.Private.HandleFunc("GET /{$}", routing.ExpectAppSession(routing.Home)) + r.Private.HandleFunc("POST /groups/{groupReference}", routing.ExpectAppSession(routing.GroupPost)) + r.Private.HandleFunc("POST /list/{userReference}", routing.ExpectAppSession(routing.ForeignWishlistPost)) r.Private.HandleFunc("POST /logout", routing.LogoutPost) - r.Private.HandleFunc("POST /users/{userReference}", routing.ExpectUser(routing.UserPost)) - r.Private.HandleFunc("POST /{$}", routing.ExpectUser(routing.HomePost)) + r.Private.HandleFunc("POST /users/{userReference}", routing.ExpectAppSession(routing.UserPost)) + r.Private.HandleFunc("POST /{$}", routing.ExpectAppSession(routing.HomePost)) // Deprecated + r.Private.HandleFunc("GET /group/{groupReference}", routing.ExpectAppSession(routing.Group)) + r.Private.HandleFunc("GET /list/{userReference}", routing.ExpectAppSession(routing.ForeignWishlist)) r.Public.HandleFunc("GET /group/{groupReference}", routing.PublicGroup) - r.Private.HandleFunc("GET /group/{groupReference}", routing.ExpectUser(routing.Group)) + r.Public.HandleFunc("GET /list/{userReference}", routing.PublicWishlist) http.Handle("/", r) diff --git a/http/routing/context.go b/http/routing/context.go index b9ec3da..a45b9f1 100644 --- a/http/routing/context.go +++ b/http/routing/context.go @@ -1,12 +1,12 @@ package routing import ( - "lishwist/http/api/db" + lishwist "lishwist/core" "lishwist/http/rsvp" "net/http" ) -func ExpectUser(next func(*db.User, http.Header, *rsvp.Request) rsvp.Response) rsvp.HandlerFunc { +func ExpectAppSession(next func(*lishwist.Session, http.Header, *rsvp.Request) rsvp.Response) rsvp.HandlerFunc { return func(w http.Header, r *rsvp.Request) rsvp.Response { session := r.GetSession() username, ok := session.GetValue("username").(string) @@ -14,11 +14,11 @@ func ExpectUser(next func(*db.User, http.Header, *rsvp.Request) rsvp.Response) r return rsvp.Error(http.StatusInternalServerError, "Something went wrong.").Log("Failed to get username from session") } - user, err := db.GetUserByName(username) + appSession, err := lishwist.SessionFromUsername(username) if err != nil { - return rsvp.Error(http.StatusInternalServerError, "Something went wrong.").Log("Failed to get user %q: %s", username, err) + return rsvp.Error(http.StatusInternalServerError, "Something went wrong.").Log("Failed to get session by username %q: %s", username, err) } - return next(user, w, r) + return next(appSession, w, r) } } diff --git a/http/routing/foreign_wishlist.go b/http/routing/foreign_wishlist.go index 6363661..9a91c88 100644 --- a/http/routing/foreign_wishlist.go +++ b/http/routing/foreign_wishlist.go @@ -1,7 +1,7 @@ package routing import ( - "lishwist/http/api/db" + lishwist "lishwist/core" "lishwist/http/rsvp" "net/http" ) @@ -10,26 +10,26 @@ type foreignWishlistProps struct { CurrentUserId string CurrentUserName string Username string - Gifts []db.Gift + Gifts []lishwist.Wish } -func ForeignWishlist(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { +func ForeignWishlist(app *lishwist.Session, h http.Header, r *rsvp.Request) rsvp.Response { userReference := r.PathValue("userReference") - if currentUser.Reference == userReference { + if app.User.Reference == userReference { return rsvp.SeeOther("/") } - otherUser, err := db.GetUserByReference(userReference) + otherUser, err := lishwist.GetUserByReference(userReference) if err != nil { return rsvp.Error(http.StatusInternalServerError, "An error occurred while fetching this user :(").Log("Couldn't get user by reference %q: %s", userReference, err) } if otherUser == nil { return rsvp.Error(http.StatusInternalServerError, "User not found") } - gifts, err := currentUser.GetOtherUserGifts(userReference) + wishes, err := app.GetOthersWishes(userReference) if err != nil { - return rsvp.Error(http.StatusInternalServerError, "An error occurred while fetching this user :(").Log("%q couldn't get wishes of other user %q: %s", currentUser.Name, otherUser.Name, err) + return rsvp.Error(http.StatusInternalServerError, "An error occurred while fetching this user :(").Log("%q couldn't get wishes of other user %q: %s", app.User.Name, otherUser.Name, err) } - p := foreignWishlistProps{CurrentUserId: currentUser.Id, CurrentUserName: currentUser.Name, Username: otherUser.Name, Gifts: gifts} + p := foreignWishlistProps{CurrentUserId: app.User.Id, CurrentUserName: app.User.Name, Username: otherUser.Name, Gifts: wishes} return rsvp.Data("foreign_wishlist.gotmpl", p) } @@ -40,14 +40,14 @@ type publicWishlistProps struct { func PublicWishlist(h http.Header, r *rsvp.Request) rsvp.Response { userReference := r.PathValue("userReference") - otherUser, err := db.GetUserByReference(userReference) + otherUser, err := lishwist.GetUserByReference(userReference) if err != nil { return rsvp.Error(http.StatusInternalServerError, "An error occurred while fetching this user :(").Log("Couldn't get user by reference %q on public wishlist: %s", userReference, err) } if otherUser == nil { return rsvp.Error(http.StatusInternalServerError, "User not found") } - giftCount, err := otherUser.CountGifts() + giftCount, err := otherUser.WishCount() if err != nil { return rsvp.Error(http.StatusInternalServerError, "An error occurred while fetching this user :(").Log("Couldn't get wishes of user %q on public wishlist: %s", otherUser.Name, err) } diff --git a/http/routing/groups.go b/http/routing/groups.go index 89f6bb6..eefdac3 100644 --- a/http/routing/groups.go +++ b/http/routing/groups.go @@ -4,59 +4,59 @@ import ( "net/http" "slices" - "lishwist/http/api/db" + lishwist "lishwist/core" "lishwist/http/rsvp" ) type GroupProps struct { - Group *db.Group + Group *lishwist.Group CurrentUsername string } -func AdminGroup(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { +func AdminGroup(app *lishwist.Session, h http.Header, r *rsvp.Request) rsvp.Response { reference := r.PathValue("groupReference") - group, err := db.GetGroupByReference(reference) + group, err := app.GetGroupByReference(reference) if err != nil { return rsvp.Error(http.StatusInternalServerError, "Couldn't get group: %s", err) } if group == nil { return rsvp.Error(http.StatusNotFound, "Group not found") } - if !currentUser.IsAdmin { - index := group.MemberIndex(currentUser.Id) + if !app.User.IsAdmin { + index := group.MemberIndex(app.User.Id) group.Members = slices.Delete(group.Members, index, index+1) } p := GroupProps{ Group: group, - CurrentUsername: currentUser.Name, + CurrentUsername: app.User.Name, } return rsvp.Data("group_page.gotmpl", p) } -func Group(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { - if currentUser.IsAdmin { - return AdminGroup(currentUser, h, r) +func Group(app *lishwist.Session, h http.Header, r *rsvp.Request) rsvp.Response { + if app.User.IsAdmin { + return AdminGroup(app, h, r) } groupReference := r.PathValue("groupReference") - group, err := currentUser.GetGroupByReference(groupReference) + group, err := app.GetGroupByReference(groupReference) if err != nil { return rsvp.Error(http.StatusInternalServerError, "An error occurred while fetching this group :(").Log("Couldn't get group: %s", err) } if group == nil { return rsvp.Error(http.StatusNotFound, "Group not found. (It might be because you're not a member)") } - index := group.MemberIndex(currentUser.Id) + index := group.MemberIndex(app.User.Id) group.Members = slices.Delete(group.Members, index, index+1) p := GroupProps{ Group: group, - CurrentUsername: currentUser.Name, + CurrentUsername: app.User.Name, } return rsvp.Data("group_page.gotmpl", p) } func PublicGroup(h http.Header, r *rsvp.Request) rsvp.Response { groupReference := r.PathValue("groupReference") - group, err := db.GetGroupByReference(groupReference) + group, err := lishwist.GetGroupByReference(groupReference) if err != nil { return rsvp.Error(http.StatusInternalServerError, "An error occurred while fetching this group :(").Log("Couldn't get group: %s", err) } @@ -66,13 +66,14 @@ func PublicGroup(h http.Header, r *rsvp.Request) rsvp.Response { return rsvp.Data("public_group_page.gotmpl", p) } -func GroupPost(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { - if !currentUser.IsAdmin { +func GroupPost(app *lishwist.Session, h http.Header, r *rsvp.Request) rsvp.Response { + admin := app.Admin() + if admin == nil { return NotFound(h, r) } form := r.ParseForm() - var group *db.Group + var group *lishwist.Group reference := r.PathValue("groupReference") name := form.Get("name") @@ -80,13 +81,13 @@ func GroupPost(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Respon removeUsers := form["removeUser"] if name != "" { - createdGroup, err := db.CreateGroup(name, reference) + createdGroup, err := admin.CreateGroup(name, reference) if err != nil { return rsvp.Error(http.StatusInternalServerError, "Failed to create group: %s", err) } group = createdGroup } else { - existingGroup, err := db.GetGroupByReference(reference) + existingGroup, err := lishwist.GetGroupByReference(reference) if err != nil { return rsvp.Error(http.StatusInternalServerError, "Failed to get group: %s", err) } @@ -100,7 +101,7 @@ func GroupPost(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Respon if index == -1 { return rsvp.Error(http.StatusBadRequest, "Group %q does not contain a user with id %s", reference, userId) } - err = group.RemoveUser(userId) + err = admin.RemoveUserFromGroup(userId, group.Id) if err != nil { return rsvp.Error(http.StatusInternalServerError, "On group %q failed to remove user with id %s: %s", reference, userId, err) } @@ -109,14 +110,14 @@ func GroupPost(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Respon } for _, userId := range addUsers { - user, err := db.GetUser(userId) + user, err := admin.GetUser(userId) if err != nil { return rsvp.Error(http.StatusInternalServerError, "Groups exists, but a user with id %s could not be fetched: %s", userId, err) } if user == nil { return rsvp.Error(http.StatusInternalServerError, "Groups exists, but a user with id %s does not exist", userId) } - err = group.AddUser(user.Id) + err = admin.AddUserToGroup(user.Id, group.Id) if err != nil { return rsvp.Error(http.StatusInternalServerError, "Groups exists, but failed to add user with id %s: %s", userId, err) } @@ -126,12 +127,13 @@ func GroupPost(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Respon return rsvp.Data("", group) } -func Groups(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { - if !currentUser.IsAdmin { +func Groups(app *lishwist.Session, h http.Header, r *rsvp.Request) rsvp.Response { + admin := app.Admin() + if admin == nil { return NotFound(h, r) } - groups, err := db.GetAllGroups() + groups, err := admin.ListGroups() if err != nil { return rsvp.Error(http.StatusInternalServerError, "Failed to get groups: %s", err) } diff --git a/http/routing/home.go b/http/routing/home.go index b8dbf54..4e4229c 100644 --- a/http/routing/home.go +++ b/http/routing/home.go @@ -3,45 +3,45 @@ package routing import ( "net/http" - "lishwist/http/api/db" + lishwist "lishwist/core" "lishwist/http/env" "lishwist/http/rsvp" ) type HomeProps struct { Username string - Gifts []db.Gift - Todo []db.Gift + Gifts []lishwist.Wish + Todo []lishwist.Wish Reference string HostUrl string - Groups []db.Group + Groups []lishwist.Group } -func Home(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { - gifts, err := currentUser.GetGifts() +func Home(app *lishwist.Session, h http.Header, r *rsvp.Request) rsvp.Response { + gifts, err := app.GetWishes() if err != nil { return rsvp.Error(http.StatusInternalServerError, "An error occurred while fetching your wishlist :(").Log("Failed to get gifts: %s", err) } - todo, err := currentUser.GetTodo() + todo, err := app.GetTodo() if err != nil { return rsvp.Error(http.StatusInternalServerError, "An error occurred while fetching your wishlist :(").Log("Failed to get todo: %s", err) } - groups, err := currentUser.GetGroups() + groups, err := app.GetGroups() if err != nil { return rsvp.Error(http.StatusInternalServerError, "An error occurred while fetching your wishlist :(").Log("Failed to get groups: %s", err) } - p := HomeProps{Username: currentUser.Name, Gifts: gifts, Todo: todo, Reference: currentUser.Reference, HostUrl: env.HostUrl.String(), Groups: groups} + p := HomeProps{Username: app.User.Name, Gifts: gifts, Todo: todo, Reference: app.User.Reference, HostUrl: env.HostUrl.String(), Groups: groups} return rsvp.Data("home.gotmpl", p) } -func HomePost(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { +func HomePost(app *lishwist.Session, h http.Header, r *rsvp.Request) rsvp.Response { form := r.ParseForm() switch form.Get("intent") { case "add_idea": - return WishlistAdd(currentUser, h, r) + return WishlistAdd(app, h, r) case "delete_idea": - return WishlistDelete(currentUser, h, r) + return WishlistDelete(app, h, r) default: - return TodoUpdate(currentUser, h, r) + return TodoUpdate(app, h, r) } } diff --git a/http/routing/login.go b/http/routing/login.go index d5214bd..b738b39 100644 --- a/http/routing/login.go +++ b/http/routing/login.go @@ -1,6 +1,7 @@ package routing import ( + lishwist "lishwist/core" "lishwist/http/api" "lishwist/http/rsvp" "net/http" @@ -37,15 +38,32 @@ func LoginPost(h http.Header, r *rsvp.Request) rsvp.Response { username := form.Get("username") password := form.Get("password") - props := api.Login(username, password) - if props != nil { + props := api.NewLoginProps(username, password) + + valid := props.Validate() + props.Password.Value = "" + if !valid { session.FlashSet(&props) - return rsvp.SeeOther("/").SaveSession(session) + return rsvp.SeeOther("/").SaveSession(session).Log("Invalid props: %#v\n", props) + } + + app, err := lishwist.Login(username, password) + if err != nil { + switch err.(type) { + case lishwist.ErrorInvalidCredentials: + props.GeneralError = "Username or password invalid" + session.FlashSet(&props) + return rsvp.SeeOther("/").SaveSession(session).Log("Invalid credentials: %#v\n", props) + default: + props.GeneralError = "Something went wrong." + session.FlashSet(&props) + return rsvp.SeeOther("/").SaveSession(session).Log("Login error: %s\n", err) + } } session.SetID("") session.SetValue("authorized", true) - session.SetValue("username", username) + session.SetValue("username", app.User.Name) return rsvp.SeeOther(r.URL().Path).SaveSession(session) } diff --git a/http/routing/register.go b/http/routing/register.go index de68d0b..b7a8d4c 100644 --- a/http/routing/register.go +++ b/http/routing/register.go @@ -1,6 +1,8 @@ package routing import ( + "errors" + lishwist "lishwist/core" "lishwist/http/api" "lishwist/http/rsvp" "net/http" @@ -26,18 +28,31 @@ func Register(h http.Header, r *rsvp.Request) rsvp.Response { func RegisterPost(h http.Header, r *rsvp.Request) rsvp.Response { form := r.ParseForm() + s := r.GetSession() username := form.Get("username") newPassword := form.Get("newPassword") confirmPassword := form.Get("confirmPassword") - props := api.Register(username, newPassword, confirmPassword) + props := api.NewRegisterProps(username, newPassword, confirmPassword) - s := r.GetSession() - - if props != nil { + valid := props.Validate() + props.Password.Value = "" + props.ConfirmPassword.Value = "" + if !valid { s.FlashSet(&props) - return rsvp.SeeOther("/register").SaveSession(s) + return rsvp.SeeOther("/").SaveSession(s).Log("Invalid props: %#v\n", props) + } + + _, err := lishwist.Register(username, newPassword) + if err != nil { + if errors.Is(err, lishwist.ErrorUsernameTaken) { + props.Username.Error = "Username is taken" + } else { + props.GeneralError = "Something went wrong." + } + s.FlashSet(&props) + return rsvp.SeeOther("/register").SaveSession(s).Log("Registration failed: %s\n", err) } s.FlashSet(true) diff --git a/http/routing/todo.go b/http/routing/todo.go index 8680659..4f6764a 100644 --- a/http/routing/todo.go +++ b/http/routing/todo.go @@ -1,24 +1,24 @@ package routing import ( - "lishwist/http/api/db" + lishwist "lishwist/core" "lishwist/http/rsvp" "net/http" ) -func TodoUpdate(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { +func TodoUpdate(app *lishwist.Session, h http.Header, r *rsvp.Request) rsvp.Response { form := r.ParseForm() switch form.Get("intent") { case "unclaim_todo": unclaims := form["gift"] - err := currentUser.ClaimGifts([]string{}, unclaims) + err := app.ClaimWishes([]string{}, unclaims) if err != nil { return rsvp.Error(http.StatusInternalServerError, "Failed to update claim...").LogError(err) } case "complete_todo": claims := form["gift"] - err := currentUser.CompleteGifts(claims) + err := app.CompleteWishes(claims) if err != nil { return rsvp.Error(http.StatusInternalServerError, "Failed to complete gifts...").LogError(err) } diff --git a/http/routing/users.go b/http/routing/users.go index abee64f..becb2e3 100644 --- a/http/routing/users.go +++ b/http/routing/users.go @@ -1,17 +1,18 @@ package routing import ( - "lishwist/http/api/db" + lishwist "lishwist/core" "lishwist/http/rsvp" "net/http" ) -func Users(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { - if !currentUser.IsAdmin { +func Users(app *lishwist.Session, h http.Header, r *rsvp.Request) rsvp.Response { + admin := app.Admin() + if admin == nil { return NotFound(h, r) } - users, err := db.GetAllUsers() + users, err := admin.ListUsers() if err != nil { return rsvp.Error(http.StatusInternalServerError, "Failed to get users: %s", err) } @@ -19,14 +20,15 @@ func Users(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { return rsvp.Data("", users) } -func User(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { - if !currentUser.IsAdmin { +func User(app *lishwist.Session, h http.Header, r *rsvp.Request) rsvp.Response { + admin := app.Admin() + if admin == nil { return NotFound(h, r) } reference := r.PathValue("userReference") - user, err := db.GetUserByReference(reference) + user, err := lishwist.GetUserByReference(reference) if err != nil { return rsvp.Error(http.StatusInternalServerError, "Failed to get user: %s", err) } @@ -37,19 +39,20 @@ func User(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { return rsvp.Data("", user) } -func UserPost(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { - if !currentUser.IsAdmin { +func UserPost(app *lishwist.Session, h http.Header, r *rsvp.Request) rsvp.Response { + admin := app.Admin() + if admin == nil { return NotFound(h, r) } form := r.ParseForm() reference := r.PathValue("userReference") - if reference == currentUser.Reference { + if reference == app.User.Reference { return rsvp.Error(http.StatusForbidden, "You cannot delete yourself.") } - user, err := db.GetAnyUserByReference(reference) + user, err := lishwist.GetUserByReference(reference) if err != nil { return rsvp.Error(http.StatusInternalServerError, "Failed to get user: %s", err) } @@ -60,7 +63,7 @@ func UserPost(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Respons intent := form.Get("intent") if intent != "" { - err = user.SetLive(intent != "delete") + err = admin.UserSetLive(reference, intent != "delete") if err != nil { return rsvp.Error(http.StatusInternalServerError, "Failed to delete user: %s", err) } diff --git a/http/routing/wishlist.go b/http/routing/wishlist.go index 88b3bc2..dec0cf3 100644 --- a/http/routing/wishlist.go +++ b/http/routing/wishlist.go @@ -1,32 +1,32 @@ package routing import ( - "lishwist/http/api/db" + lishwist "lishwist/core" "lishwist/http/rsvp" "net/http" ) -func WishlistAdd(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { +func WishlistAdd(app *lishwist.Session, h http.Header, r *rsvp.Request) rsvp.Response { form := r.ParseForm() newGiftName := form.Get("gift_name") - err := currentUser.AddGift(newGiftName) + err := app.MakeWish(newGiftName) if err != nil { return rsvp.Error(http.StatusInternalServerError, "Failed to add gift.").LogError(err) } return rsvp.SeeOther("/") } -func WishlistDelete(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { +func WishlistDelete(app *lishwist.Session, h http.Header, r *rsvp.Request) rsvp.Response { form := r.ParseForm() targets := form["gift"] - err := currentUser.RemoveGifts(targets...) + err := app.RevokeWishes(targets...) if err != nil { return rsvp.Error(http.StatusInternalServerError, "Failed to remove gifts.").LogError(err) } return rsvp.SeeOther("/") } -func ForeignWishlistPost(currentUser *db.User, h http.Header, r *rsvp.Request) rsvp.Response { +func ForeignWishlistPost(app *lishwist.Session, h http.Header, r *rsvp.Request) rsvp.Response { form := r.ParseForm() userReference := r.PathValue("userReference") intent := form.Get("intent") @@ -34,22 +34,22 @@ func ForeignWishlistPost(currentUser *db.User, h http.Header, r *rsvp.Request) r case "claim": claims := form["unclaimed"] unclaims := form["claimed"] - err := currentUser.ClaimGifts(claims, unclaims) + err := app.ClaimWishes(claims, unclaims) if err != nil { return rsvp.Error(http.StatusInternalServerError, "Failed to update claim...").LogError(err) } case "complete": claims := form["claimed"] - err := currentUser.CompleteGifts(claims) + err := app.CompleteWishes(claims) if err != nil { return rsvp.Error(http.StatusInternalServerError, "Failed to complete gifts...").LogError(err) } case "add": - giftName := form.Get("gift_name") - if giftName == "" { + wishName := form.Get("gift_name") + if wishName == "" { return rsvp.Error(http.StatusBadRequest, "Gift name not provided") } - err := currentUser.AddGiftToUser(userReference, giftName) + err := app.SuggestWishForUser(userReference, wishName) if err != nil { return rsvp.Error(http.StatusInternalServerError, "Failed to add gift idea to other user...").LogError(err) } @@ -57,7 +57,7 @@ func ForeignWishlistPost(currentUser *db.User, h http.Header, r *rsvp.Request) r claims := form["unclaimed"] unclaims := form["claimed"] gifts := append(claims, unclaims...) - err := currentUser.RemoveGifts(gifts...) + err := app.RecindWishesForUser(gifts...) if err != nil { return rsvp.Error(http.StatusInternalServerError, "Failed to remove gift idea for other user...").LogError(err) }