Merge pull request 'Core separation' (#13) from core-separation into main
Reviewed-on: #13
This commit is contained in:
commit
4494932246
|
|
@ -2,5 +2,5 @@
|
|||
gin-bin
|
||||
*lishwist.db
|
||||
.env*.local
|
||||
server/api/db/init_sql.go
|
||||
init_sql.go
|
||||
.ignored/
|
||||
|
|
|
|||
|
|
@ -0,0 +1,13 @@
|
|||
package lishwist
|
||||
|
||||
type Admin struct {
|
||||
session *Session
|
||||
}
|
||||
|
||||
func (s *Session) Admin() *Admin {
|
||||
if s.User.IsAdmin {
|
||||
return &Admin{s}
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,55 @@
|
|||
package lishwist
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"log"
|
||||
)
|
||||
|
||||
func PrintViews(d *sql.DB) {
|
||||
rows, err := d.Query("SELECT name FROM sqlite_master WHERE type = 'view';")
|
||||
if err != nil {
|
||||
log.Println("Query failed: %w", err)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
fmt.Println("Printing view names...")
|
||||
for rows.Next() {
|
||||
var name string
|
||||
err = rows.Scan(&name)
|
||||
if err != nil {
|
||||
log.Println("Scan failed: %w", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("name: %s\n", name)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
log.Println("Rows returned error: %w", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
func PrintTables(d *sql.DB) {
|
||||
rows, err := d.Query("SELECT name FROM sqlite_master WHERE type = 'table';")
|
||||
if err != nil {
|
||||
log.Println("Query failed: %w", err)
|
||||
return
|
||||
}
|
||||
defer rows.Close()
|
||||
fmt.Println("Printing table names...")
|
||||
for rows.Next() {
|
||||
var name string
|
||||
err = rows.Scan(&name)
|
||||
if err != nil {
|
||||
fmt.Println("Scan failed: %w", err)
|
||||
return
|
||||
}
|
||||
fmt.Printf("name: %s\n", name)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
log.Println("Rows returned error: %w", err)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,18 @@
|
|||
module lishwist/core
|
||||
|
||||
go 1.23.0
|
||||
|
||||
toolchain go1.23.3
|
||||
|
||||
require golang.org/x/crypto v0.39.0
|
||||
|
||||
require (
|
||||
github.com/google/uuid v1.6.0
|
||||
github.com/ncruces/go-sqlite3 v0.26.1
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/ncruces/julianday v1.0.0 // indirect
|
||||
github.com/tetratelabs/wazero v1.9.0 // indirect
|
||||
golang.org/x/sys v0.33.0 // indirect
|
||||
)
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0=
|
||||
github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||
github.com/ncruces/go-sqlite3 v0.26.1 h1:lBXmbmucH1Bsj57NUQR6T84UoMN7jnNImhF+ibEITJU=
|
||||
github.com/ncruces/go-sqlite3 v0.26.1/go.mod h1:XFTPtFIo1DmGCh+XVP8KGn9b/o2f+z0WZuT09x2N6eo=
|
||||
github.com/ncruces/julianday v1.0.0 h1:fH0OKwa7NWvniGQtxdJRxAgkBMolni2BjDHaWTxqt7M=
|
||||
github.com/ncruces/julianday v1.0.0/go.mod h1:Dusn2KvZrrovOMJuOt0TNXL6tB7U2E8kvza5fFc9G7g=
|
||||
github.com/tetratelabs/wazero v1.9.0 h1:IcZ56OuxrtaEz8UYNRHBrUa9bYeX9oVY93KspZZBf/I=
|
||||
github.com/tetratelabs/wazero v1.9.0/go.mod h1:TSbcXCfFP0L2FGkRPxHphadXPjo1T6W+CseNNY7EkjM=
|
||||
golang.org/x/crypto v0.39.0 h1:SHs+kF4LP+f+p14esP5jAoDpHU8Gu/v9lFRK6IT5imM=
|
||||
golang.org/x/crypto v0.39.0/go.mod h1:L+Xg3Wf6HoL4Bn4238Z6ft6KfEpN0tJGo53AAPC632U=
|
||||
golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw=
|
||||
golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k=
|
||||
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
|
||||
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
|
||||
|
|
@ -1,9 +1,10 @@
|
|||
package db
|
||||
package lishwist
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"lishwist/normalize"
|
||||
"lishwist/core/internal/db"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
type Group struct {
|
||||
|
|
@ -24,7 +25,7 @@ func (g *Group) MemberIndex(userId string) int {
|
|||
|
||||
func queryManyGroups(query string, args ...any) ([]Group, error) {
|
||||
groups := []Group{}
|
||||
rows, err := database.Query(query, args...)
|
||||
rows, err := db.Connection.Query(query, args...)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Query failed: %w", err)
|
||||
}
|
||||
|
|
@ -64,25 +65,31 @@ 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, 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 (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 GetAllGroups() ([]Group, error) {
|
||||
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)
|
||||
}
|
||||
|
||||
func CreateGroup(name string, reference string) (*Group, error) {
|
||||
name = normalize.Trim(name)
|
||||
func (a *Admin) CreateGroup(name string, reference string) (*Group, error) {
|
||||
name = strings.TrimSpace(name)
|
||||
reference = strings.TrimSpace(reference)
|
||||
stmt := "INSERT INTO [group] (name, reference) VALUES (?, ?)"
|
||||
result, err := database.Exec(stmt, name, reference)
|
||||
result, err := db.Connection.Exec(stmt, name, reference)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
|
@ -98,20 +105,26 @@ func CreateGroup(name string, reference string) (*Group, error) {
|
|||
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)
|
||||
func (a *Admin) AddUserToGroup(userId, groupId string) error {
|
||||
stmt := "INSERT INTO group_member (user_id, group_id) VALUES (?, ?)"
|
||||
_, err := db.Connection.Exec(stmt, userId, groupId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (g *Group) RemoveUser(userId string) error {
|
||||
func (a *Admin) RemoveUserFromGroup(userId, groupId string) error {
|
||||
stmt := "DELETE FROM group_member WHERE group_id = ? AND user_id = ?"
|
||||
_, err := database.Exec(stmt, g.Id, userId)
|
||||
_, err := db.Connection.Exec(stmt, userId, groupId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
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)
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
package lishwist_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
lishwist "lishwist/core"
|
||||
"lishwist/core/internal/fixtures"
|
||||
)
|
||||
|
||||
func TestCreateGroup(t *testing.T) {
|
||||
s := fixtures.Login(t, "thomas", "123")
|
||||
|
||||
group, err := s.Admin().CreateGroup(" My Friends ", " my-friends ")
|
||||
fixtures.FailIfErr(t, err, "Failed to create group")
|
||||
|
||||
fixtures.AssertEq(t, "Number of users", "My Friends", group.Name)
|
||||
fixtures.AssertEq(t, "Number of users", "my-friends", group.Reference)
|
||||
}
|
||||
|
||||
func TestCantSeeSelfInGroup(t *testing.T) {
|
||||
s := fixtures.Login(t, "thomas", "123")
|
||||
|
||||
caleb, err := lishwist.Register("caleb", "123")
|
||||
fixtures.FailIfErr(t, err, "Failed to register caleb")
|
||||
|
||||
group, err := s.Admin().CreateGroup(" My Friends ", " my-friends ")
|
||||
fixtures.FailIfErr(t, err, "Failed to create group")
|
||||
|
||||
err = s.Admin().AddUserToGroup(s.User.Id, group.Id)
|
||||
fixtures.FailIfErr(t, err, "Failed to add self to group")
|
||||
|
||||
err = s.Admin().AddUserToGroup(caleb.Id, group.Id)
|
||||
fixtures.FailIfErr(t, err, "Failed to add caleb to group")
|
||||
|
||||
group, err = s.GetGroupByReference("my-friends")
|
||||
fixtures.FailIfErr(t, err, "Failed to get group")
|
||||
fixtures.FatalAssert(t, "Group not nil", group != nil)
|
||||
|
||||
fixtures.AssertEq(t, "Group contains 2 users", 2, len(group.Members))
|
||||
fixtures.AssertEq(t, "Group user 1 is thomas", "thomas", group.Members[0].Name)
|
||||
fixtures.AssertEq(t, "Group user 2 is caleb", "caleb", group.Members[1].Name)
|
||||
}
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
package lishwist
|
||||
|
||||
import (
|
||||
"lishwist/core/internal/db"
|
||||
)
|
||||
|
||||
func Init(dataSourceName string) error {
|
||||
return db.Init(dataSourceName)
|
||||
}
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
//go:generate go run gen_init_sql.go
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/ncruces/go-sqlite3/driver"
|
||||
_ "github.com/ncruces/go-sqlite3/embed"
|
||||
)
|
||||
|
||||
var Connection *sql.DB
|
||||
|
||||
func Init(dataSourceName string) error {
|
||||
db, err := sql.Open("sqlite3", dataSourceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to open db connection: %w", err)
|
||||
}
|
||||
|
||||
_, err = db.Exec(initQuery)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to initialize db: %w", err)
|
||||
}
|
||||
|
||||
Connection = db
|
||||
|
||||
return nil
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ CREATE TABLE IF NOT EXISTS "user" (
|
|||
"is_live" INTEGER NOT NULL DEFAULT 1,
|
||||
PRIMARY KEY("id" AUTOINCREMENT)
|
||||
);
|
||||
CREATE TABLE IF NOT EXISTS "gift" (
|
||||
CREATE TABLE IF NOT EXISTS "wish" (
|
||||
"id" INTEGER NOT NULL UNIQUE,
|
||||
"name" TEXT NOT NULL,
|
||||
"recipient_id" INTEGER NOT NULL,
|
||||
|
|
@ -49,6 +49,6 @@ 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;
|
||||
-- SELECT wish.id, wish.name, wish.sent FROM wish JOIN user AS recipient;
|
||||
|
||||
COMMIT;
|
||||
|
|
@ -0,0 +1,6 @@
|
|||
BEGIN TRANSACTION;
|
||||
|
||||
ALTER TABLE gift RENAME TO wish;
|
||||
|
||||
COMMIT;
|
||||
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package fixtures
|
||||
|
||||
import "testing"
|
||||
|
||||
func AssertEq[C comparable](t *testing.T, context string, expected, actual C) {
|
||||
if expected != actual {
|
||||
t.Errorf("%s: %#v != %#v", context, expected, actual)
|
||||
}
|
||||
}
|
||||
|
||||
func Assert(t *testing.T, context string, condition bool) {
|
||||
if !condition {
|
||||
t.Errorf("%s", context)
|
||||
}
|
||||
}
|
||||
|
||||
func FatalAssert(t *testing.T, context string, condition bool) {
|
||||
if !condition {
|
||||
t.Fatalf("%s", context)
|
||||
}
|
||||
}
|
||||
|
||||
func FailIfErr(t *testing.T, err error, context string) {
|
||||
if err != nil {
|
||||
t.Fatalf("%s: %s\n", context, err)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,35 @@
|
|||
package fixtures
|
||||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
|
||||
lishwist "lishwist/core"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func TestInit(t *testing.T) error {
|
||||
uri := memdb.TestDB(t)
|
||||
return lishwist.Init(uri)
|
||||
}
|
||||
|
||||
func Login(t *testing.T, username, password string) *lishwist.Session {
|
||||
uri := memdb.TestDB(t)
|
||||
err := lishwist.Init(uri)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to init db: %s\n", err)
|
||||
}
|
||||
|
||||
_, err = lishwist.Register(username, password)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to register on login fixture: %s\n", err)
|
||||
}
|
||||
|
||||
session, err := lishwist.Login(username, password)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to login on fixture: %s\n", err)
|
||||
}
|
||||
|
||||
return session
|
||||
}
|
||||
|
|
@ -0,0 +1,10 @@
|
|||
package normalize
|
||||
|
||||
import (
|
||||
"strings"
|
||||
)
|
||||
|
||||
func Name(name string) string {
|
||||
name = strings.TrimSpace(name)
|
||||
return strings.ToLower(name)
|
||||
}
|
||||
|
|
@ -0,0 +1,31 @@
|
|||
package lishwist
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
type ErrorInvalidCredentials error
|
||||
|
||||
func Login(username, password string) (*Session, error) {
|
||||
user, err := getUserByName(username)
|
||||
if err != nil {
|
||||
return nil, ErrorInvalidCredentials(fmt.Errorf("Failed to fetch user: %w", err))
|
||||
}
|
||||
if user == nil {
|
||||
return nil, ErrorInvalidCredentials(fmt.Errorf("User not found by name: %s", username))
|
||||
}
|
||||
|
||||
passHash, err := user.getPassHash()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to get password hash: %w", err)
|
||||
}
|
||||
|
||||
err = bcrypt.CompareHashAndPassword(passHash, []byte(password))
|
||||
if err != nil {
|
||||
return nil, ErrorInvalidCredentials(fmt.Errorf("Password compare failed: %w", err))
|
||||
}
|
||||
|
||||
return &Session{*user}, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,25 @@
|
|||
package lishwist_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
lishwist "lishwist/core"
|
||||
"lishwist/core/internal/fixtures"
|
||||
)
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
err := fixtures.TestInit(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to init db: %s\n", err)
|
||||
}
|
||||
|
||||
_, err = lishwist.Register("thomas", "123")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to register: %s\n", err)
|
||||
}
|
||||
|
||||
_, err = lishwist.Login("thomas", "123")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to login: %s\n", err)
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package lishwist
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
"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")
|
||||
}
|
||||
if newPassword == "" {
|
||||
return nil, errors.New("newPassword required")
|
||||
}
|
||||
|
||||
existingUser, _ := getUserByName(username)
|
||||
if existingUser != nil {
|
||||
return nil, ErrorUsernameTaken
|
||||
}
|
||||
|
||||
hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to hash password: %w", err)
|
||||
}
|
||||
|
||||
usersExist, err := hasUsers()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to count users: %w", err)
|
||||
}
|
||||
|
||||
user, err := createUser(username, hashedPasswordBytes, !usersExist)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to create user: %w\n", err)
|
||||
}
|
||||
|
||||
return user, nil
|
||||
}
|
||||
|
|
@ -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": []
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package lishwist
|
||||
|
||||
import "fmt"
|
||||
|
||||
type Session struct {
|
||||
User
|
||||
}
|
||||
|
||||
func SessionFromUsername(username string) (*Session, error) {
|
||||
user, err := getUserByName(username)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to get user: %w", err)
|
||||
}
|
||||
return &Session{*user}, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
package session
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"lishwist/core/internal/db"
|
||||
|
||||
"github.com/Teajey/sqlstore"
|
||||
)
|
||||
|
||||
func NewStore(keyPairs ...[]byte) (*sqlstore.Store, error) {
|
||||
deleteStmt, err := db.Connection.Prepare("DELETE FROM session WHERE id = ?;")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to prepare delete statement: %w", err)
|
||||
}
|
||||
|
||||
insertStmt, err := db.Connection.Prepare("INSERT INTO session (value) VALUES (?);")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to prepare insert statement: %w", err)
|
||||
}
|
||||
|
||||
selectStmt, err := db.Connection.Prepare("SELECT value FROM session WHERE id = ?;")
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to prepare select statement: %w", err)
|
||||
}
|
||||
|
||||
updateStmt, err := db.Connection.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(db.Connection, sqlstore.Statements{
|
||||
Delete: deleteStmt,
|
||||
Insert: insertStmt,
|
||||
Select: selectStmt,
|
||||
Update: updateStmt,
|
||||
}, keyPairs...)
|
||||
|
||||
return s, nil
|
||||
}
|
||||
|
|
@ -0,0 +1,165 @@
|
|||
package lishwist
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/google/uuid"
|
||||
|
||||
"lishwist/core/internal/db"
|
||||
"lishwist/core/internal/normalize"
|
||||
)
|
||||
|
||||
type User struct {
|
||||
Id string
|
||||
// TODO: rename to DisplayName
|
||||
NormalName string
|
||||
Name string
|
||||
Reference string
|
||||
IsAdmin bool
|
||||
IsLive bool
|
||||
}
|
||||
|
||||
func queryManyUsers(query string, args ...any) ([]User, error) {
|
||||
rows, err := db.Connection.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 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 createUser(name string, passHash []byte, isAdmin bool) (*User, error) {
|
||||
username := normalize.Name(name)
|
||||
stmt := "INSERT INTO user (name, display_name, reference, password_hash, is_admin) VALUES (?, ?, ?, ?, ?)"
|
||||
reference, err := uuid.NewRandom()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to generate reference: %w", err)
|
||||
}
|
||||
result, err := db.Connection.Exec(stmt, username, name, reference, passHash, isAdmin)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to execute query: %w", err)
|
||||
}
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to get last insert id: %w", 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 := db.Connection.QueryRow(stmt, u.Id).Scan(&passHash)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return []byte(passHash), nil
|
||||
}
|
||||
|
||||
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 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
|
||||
err := db.Connection.QueryRow(stmt).Scan(&userCount)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
return userCount > 0, nil
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
package lishwist_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
lishwist "lishwist/core"
|
||||
"lishwist/core/internal/fixtures"
|
||||
)
|
||||
|
||||
func TestFirstUserIsAdmin(t *testing.T) {
|
||||
s := fixtures.Login(t, "thomas", "123")
|
||||
|
||||
_, err := lishwist.Register("caleb", "123")
|
||||
fixtures.FailIfErr(t, err, "Failed to register caleb")
|
||||
|
||||
users, err := s.Admin().ListUsers()
|
||||
fixtures.FailIfErr(t, err, "Failed to list users")
|
||||
|
||||
fixtures.AssertEq(t, "Number of users", 2, len(users))
|
||||
fixtures.Assert(t, "User 1 is admin", users[0].IsAdmin)
|
||||
fixtures.Assert(t, "User 2 is not admin", !users[1].IsAdmin)
|
||||
}
|
||||
|
|
@ -0,0 +1,295 @@
|
|||
package lishwist
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"lishwist/core/internal/db"
|
||||
)
|
||||
|
||||
type Wish 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 (s *Session) GetWishes() ([]Wish, error) {
|
||||
stmt := "SELECT wish.id, wish.name, wish.sent FROM wish WHERE wish.creator_id = ?1 AND wish.recipient_id = ?1"
|
||||
rows, err := db.Connection.Query(stmt, s.User.Id)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Query execution failed: %w", err)
|
||||
}
|
||||
defer rows.Close()
|
||||
wishs := []Wish{}
|
||||
for rows.Next() {
|
||||
var id string
|
||||
var name string
|
||||
var sent bool
|
||||
err = rows.Scan(&id, &name, &sent)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Failed to scan a row: %w", err)
|
||||
}
|
||||
wish := Wish{
|
||||
Id: id,
|
||||
Name: name,
|
||||
Sent: sent,
|
||||
}
|
||||
wishs = append(wishs, wish)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Rows returned an error: %w", err)
|
||||
}
|
||||
return wishs, nil
|
||||
}
|
||||
|
||||
func (s *Session) MakeWish(name string) error {
|
||||
stmt := "INSERT INTO wish (name, recipient_id, creator_id) VALUES (?, ?, ?)"
|
||||
_, err := db.Connection.Exec(stmt, strings.TrimSpace(name), s.User.Id, s.User.Id)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Query execution failed: %w", err)
|
||||
}
|
||||
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 {
|
||||
return nil, fmt.Errorf("Failed to get other user: %w", err)
|
||||
}
|
||||
if otherUser.Id == s.User.Id {
|
||||
return nil, errors.New("Use (s *Session) GetWishes() to view your own wishes")
|
||||
}
|
||||
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)
|
||||
}
|
||||
defer rows.Close()
|
||||
wishes := []Wish{}
|
||||
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 a row: %w", err)
|
||||
}
|
||||
wish := Wish{
|
||||
Id: id,
|
||||
Name: name,
|
||||
ClaimantId: claimantId.String,
|
||||
ClaimantName: claimantName.String,
|
||||
Sent: sent,
|
||||
CreatorId: creatorId,
|
||||
CreatorName: creatorName,
|
||||
RecipientId: recipientId,
|
||||
}
|
||||
wishes = append(wishes, wish)
|
||||
}
|
||||
err = rows.Err()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("Rows returned an error: %w", err)
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
package lishwist_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"lishwist/core/internal/fixtures"
|
||||
)
|
||||
|
||||
func TestMakeWish(t *testing.T) {
|
||||
s := fixtures.Login(t, "thomas", "123")
|
||||
|
||||
if err := s.MakeWish("apple"); err != nil {
|
||||
t.Fatalf("Failed to make wish 1: %s\n", err)
|
||||
}
|
||||
|
||||
if err := s.MakeWish(" A car "); err != nil {
|
||||
t.Fatalf("Failed to make wish 2: %s\n", err)
|
||||
}
|
||||
|
||||
wishes, err := s.GetWishes()
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to get wishes: %s\n", err)
|
||||
}
|
||||
|
||||
fixtures.AssertEq(t, "Number of wishes", 2, len(wishes))
|
||||
fixtures.AssertEq(t, "Wish 1 name", wishes[0].Name, "apple")
|
||||
fixtures.AssertEq(t, "Wish 2 name", wishes[1].Name, "A car")
|
||||
}
|
||||
9
go.work
9
go.work
|
|
@ -1,5 +1,6 @@
|
|||
go 1.23
|
||||
go 1.23.3
|
||||
|
||||
toolchain go1.23.3
|
||||
|
||||
use ./server
|
||||
use (
|
||||
./core
|
||||
./http
|
||||
)
|
||||
|
|
|
|||
12
go.work.sum
12
go.work.sum
|
|
@ -1,12 +1,16 @@
|
|||
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=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
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/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
golang.org/x/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=
|
||||
|
|
|
|||
|
|
@ -1,11 +1,10 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"lishwist/api/db"
|
||||
"lishwist/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
|
||||
}
|
||||
|
|
@ -1,12 +1,7 @@
|
|||
package api
|
||||
|
||||
import (
|
||||
"log"
|
||||
|
||||
"lishwist/api/db"
|
||||
"lishwist/templates"
|
||||
|
||||
"golang.org/x/crypto/bcrypt"
|
||||
"lishwist/http/templates"
|
||||
)
|
||||
|
||||
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
|
||||
// }
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
module lishwist
|
||||
module lishwist/http
|
||||
|
||||
go 1.23
|
||||
|
||||
|
|
@ -5,28 +5,24 @@ import (
|
|||
"log"
|
||||
"net/http"
|
||||
|
||||
"lishwist/api"
|
||||
// TODO: lishwist/api/db ought not to be used outside lishwist/api
|
||||
"lishwist/api/db"
|
||||
"lishwist/env"
|
||||
"lishwist/router"
|
||||
"lishwist/routing"
|
||||
lishwist "lishwist/core"
|
||||
"lishwist/core/session"
|
||||
"lishwist/http/api"
|
||||
"lishwist/http/env"
|
||||
"lishwist/http/router"
|
||||
"lishwist/http/routing"
|
||||
)
|
||||
|
||||
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)
|
||||
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package router
|
||||
|
||||
import (
|
||||
"lishwist/rsvp"
|
||||
"lishwist/http/rsvp"
|
||||
"net/http"
|
||||
|
||||
"github.com/Teajey/sqlstore"
|
||||
|
|
@ -1,12 +1,12 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"lishwist/api/db"
|
||||
"lishwist/rsvp"
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,8 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"lishwist/api/db"
|
||||
"lishwist/rsvp"
|
||||
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)
|
||||
}
|
||||
|
|
@ -4,59 +4,59 @@ import (
|
|||
"net/http"
|
||||
"slices"
|
||||
|
||||
"lishwist/api/db"
|
||||
"lishwist/rsvp"
|
||||
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)
|
||||
}
|
||||
|
|
@ -3,45 +3,45 @@ package routing
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"lishwist/api/db"
|
||||
"lishwist/env"
|
||||
"lishwist/rsvp"
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
|
@ -1,8 +1,9 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"lishwist/api"
|
||||
"lishwist/rsvp"
|
||||
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)
|
||||
}
|
||||
|
|
@ -1,7 +1,7 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"lishwist/rsvp"
|
||||
"lishwist/http/rsvp"
|
||||
"net/http"
|
||||
)
|
||||
|
||||
|
|
@ -3,7 +3,7 @@ package routing
|
|||
import (
|
||||
"net/http"
|
||||
|
||||
"lishwist/rsvp"
|
||||
"lishwist/http/rsvp"
|
||||
)
|
||||
|
||||
func NotFound(h http.Header, r *rsvp.Request) rsvp.Response {
|
||||
|
|
@ -1,8 +1,10 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"lishwist/api"
|
||||
"lishwist/rsvp"
|
||||
"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)
|
||||
|
|
@ -1,24 +1,24 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"lishwist/api/db"
|
||||
"lishwist/rsvp"
|
||||
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)
|
||||
}
|
||||
|
|
@ -1,17 +1,18 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"lishwist/api/db"
|
||||
"lishwist/rsvp"
|
||||
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)
|
||||
}
|
||||
|
|
@ -1,32 +1,32 @@
|
|||
package routing
|
||||
|
||||
import (
|
||||
"lishwist/api/db"
|
||||
"lishwist/rsvp"
|
||||
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)
|
||||
}
|
||||
|
|
@ -4,7 +4,7 @@ import (
|
|||
"bytes"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"lishwist/templates"
|
||||
"lishwist/http/templates"
|
||||
"log"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
|
@ -20,6 +20,10 @@ type Response struct {
|
|||
}
|
||||
|
||||
func (res *Response) Write(w http.ResponseWriter, r *http.Request) error {
|
||||
if res.LogMessage != "" {
|
||||
log.Printf("%s --- %s\n", res.Data, res.LogMessage)
|
||||
}
|
||||
|
||||
if res.Session != nil {
|
||||
err := res.Session.inner.Save(r, w)
|
||||
if err != nil {
|
||||
|
|
@ -44,10 +48,6 @@ func (res *Response) Write(w http.ResponseWriter, r *http.Request) error {
|
|||
bodyBytes := bytes.NewBuffer([]byte{})
|
||||
accept := r.Header.Get("Accept")
|
||||
|
||||
if res.LogMessage != "" {
|
||||
log.Printf("%s --- %s\n", res.Data, res.LogMessage)
|
||||
}
|
||||
|
||||
if res.Status != 0 {
|
||||
w.WriteHeader(res.Status)
|
||||
}
|
||||
|
|
@ -1,62 +0,0 @@
|
|||
//go:generate go run gen_init_sql.go
|
||||
|
||||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"lishwist/env"
|
||||
|
||||
"github.com/Teajey/sqlstore"
|
||||
_ "github.com/glebarez/go-sqlite"
|
||||
)
|
||||
|
||||
var database *sql.DB
|
||||
|
||||
func Open() error {
|
||||
db, err := sql.Open("sqlite", 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
|
||||
}
|
||||
|
|
@ -1,419 +0,0 @@
|
|||
package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"lishwist/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)
|
||||
}
|
||||
Loading…
Reference in New Issue