feat: groups
This commit is contained in:
parent
bba5136cca
commit
439d4a1844
|
|
@ -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
|
||||
}
|
||||
}
|
||||
17
core/go.mod
17
core/go.mod
|
|
@ -1,3 +1,18 @@
|
|||
module lishwist/core
|
||||
|
||||
go 1.23
|
||||
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=
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
package lishwist
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"lishwist/core/internal/db"
|
||||
"log"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
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{}
|
||||
// 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)
|
||||
}
|
||||
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, err
|
||||
}
|
||||
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 (a *Admin) ListGroups() ([]Group, error) {
|
||||
query := "SELECT id, name, reference FROM [group];"
|
||||
return queryManyGroups(query)
|
||||
}
|
||||
|
||||
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 := db.Connection.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 (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 (a *Admin) RemoveUserFromGroup(userId, groupId string) error {
|
||||
stmt := "DELETE FROM group_member WHERE group_id = ? AND user_id = ?"
|
||||
_, err := db.Connection.Exec(stmt, userId, groupId)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -6,13 +6,14 @@ import (
|
|||
"database/sql"
|
||||
"fmt"
|
||||
|
||||
_ "github.com/glebarez/go-sqlite"
|
||||
_ "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("sqlite", dataSourceName)
|
||||
db, err := sql.Open("sqlite3", dataSourceName)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to open db connection: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,12 @@ func Assert(t *testing.T, context string, condition bool) {
|
|||
}
|
||||
}
|
||||
|
||||
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)
|
||||
|
|
|
|||
|
|
@ -2,20 +2,29 @@ package fixtures
|
|||
|
||||
import (
|
||||
"log"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
lishwist "lishwist/core"
|
||||
|
||||
"github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||
)
|
||||
|
||||
func Login(username, password string) *lishwist.Session {
|
||||
err := lishwist.Init(":memory:")
|
||||
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)
|
||||
}
|
||||
|
||||
lw := lishwist.NewSessionManager(time.Second*10, 32)
|
||||
|
||||
err = lishwist.Register(username, password)
|
||||
_, err = lishwist.Register(username, password)
|
||||
if err != nil {
|
||||
log.Fatalf("Failed to register on login fixture: %s\n", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,17 +5,18 @@ import (
|
|||
"time"
|
||||
|
||||
lishwist "lishwist/core"
|
||||
"lishwist/core/internal/fixtures"
|
||||
)
|
||||
|
||||
func TestLogin(t *testing.T) {
|
||||
err := lishwist.Init(":memory:")
|
||||
err := fixtures.TestInit(t)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to init db: %s\n", err)
|
||||
}
|
||||
|
||||
lw := lishwist.NewSessionManager(time.Second*10, 32)
|
||||
|
||||
err = lishwist.Register("thomas", "123")
|
||||
_, err = lishwist.Register("thomas", "123")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to register: %s\n", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,33 +7,33 @@ import (
|
|||
"golang.org/x/crypto/bcrypt"
|
||||
)
|
||||
|
||||
func Register(username, newPassword string) error {
|
||||
func Register(username, newPassword string) (*User, error) {
|
||||
if username == "" {
|
||||
return errors.New("Username required")
|
||||
return nil, errors.New("Username required")
|
||||
}
|
||||
if newPassword == "" {
|
||||
return errors.New("newPassword required")
|
||||
return nil, errors.New("newPassword required")
|
||||
}
|
||||
|
||||
existingUser, _ := getUserByName(username)
|
||||
if existingUser != nil {
|
||||
return errors.New("Username is taken")
|
||||
return nil, errors.New("Username is taken")
|
||||
}
|
||||
|
||||
hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to hash password: %w", err)
|
||||
return nil, fmt.Errorf("Failed to hash password: %w", err)
|
||||
}
|
||||
|
||||
usersExist, err := hasUsers()
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to count users: %w", err)
|
||||
return nil, fmt.Errorf("Failed to count users: %w", err)
|
||||
}
|
||||
|
||||
_, err = createUser(username, hashedPasswordBytes, !usersExist)
|
||||
user, err := createUser(username, hashedPasswordBytes, !usersExist)
|
||||
if err != nil {
|
||||
return fmt.Errorf("Failed to create user: %w\n", err)
|
||||
return nil, fmt.Errorf("Failed to create user: %w\n", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
return user, nil
|
||||
}
|
||||
|
|
|
|||
|
|
@ -40,11 +40,11 @@ func (sm *SessionManager) createSession(user *User) (*Session, error) {
|
|||
stmt := "INSERT INTO session (user_id) VALUES (?);"
|
||||
result, err := db.Connection.Exec(stmt, user.Id)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("Failed to execute query: %w", err)
|
||||
}
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, fmt.Errorf("Failed to get last insert id: %w", err)
|
||||
}
|
||||
|
||||
token, err := generateSecureToken(sm.sessionTokenLength)
|
||||
|
|
|
|||
|
|
@ -8,9 +8,9 @@ import (
|
|||
)
|
||||
|
||||
func TestFirstUserIsAdmin(t *testing.T) {
|
||||
s := fixtures.Login("thomas", "123")
|
||||
s := fixtures.Login(t, "thomas", "123")
|
||||
|
||||
err := lishwist.Register("caleb", "123")
|
||||
_, err := lishwist.Register("caleb", "123")
|
||||
fixtures.FailIfErr(t, err, "Failed to register caleb")
|
||||
|
||||
users, err := s.Admin().ListUsers()
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import (
|
|||
)
|
||||
|
||||
func TestMakeWish(t *testing.T) {
|
||||
s := fixtures.Login("thomas", "123")
|
||||
s := fixtures.Login(t, "thomas", "123")
|
||||
|
||||
if err := s.MakeWish("apple"); err != nil {
|
||||
t.Fatalf("Failed to make wish 1: %s\n", err)
|
||||
|
|
|
|||
18
go.work.sum
18
go.work.sum
|
|
@ -1,18 +0,0 @@
|
|||
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:CzGEWj7cYgsdH8dAjBGEr58BoE7ScuLd+fwFZ44+/x8=
|
||||
github.com/klauspost/cpuid/v2 v2.2.3/go.mod h1:RVVoqg1df56z8g3pUjL/3lE5UfnlrJX8tyFgg4nqhuY=
|
||||
github.com/mattn/go-sqlite3 v1.14.16/go.mod h1:2eHXhiwb8IkHr+BDWZGa96P6+rkvnG63S2DGjv9HUNg=
|
||||
golang.org/x/mod v0.14.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c=
|
||||
golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44=
|
||||
golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk=
|
||||
golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU=
|
||||
golang.org/x/tools v0.16.1/go.mod h1:kYVVN6I1mBNoB1OX+noeBjbRk4IUEPa7JJ+TJMEooJ0=
|
||||
golang.org/x/xerrors v0.0.0-20231012003039-104605ab7028/go.mod h1:NDW/Ps6MPRej6fsCIbMTohpP40sJ/P/vI1MoTEGwX90=
|
||||
lukechampine.com/uint128 v1.3.0/go.mod h1:c4eWIwlEGaxC/+H1VguhU4PHXNWDCDMUlWdIWl2j1gk=
|
||||
modernc.org/cc/v3 v3.41.0/go.mod h1:Ni4zjJYJ04CDOhG7dn640WGfwBzfE0ecX8TyMB0Fv0Y=
|
||||
modernc.org/ccgo/v3 v3.16.15/go.mod h1:yT7B+/E2m43tmMOT51GMoM98/MtHIcQQSleGnddkUNI=
|
||||
modernc.org/httpfs v1.0.6/go.mod h1:7dosgurJGp0sPaRanU53W4xZYKh14wfzX420oZADeHM=
|
||||
modernc.org/opt v0.1.3/go.mod h1:WdSiB5evDcignE70guQKxYUl14mgWtbClRi5wmkkTX0=
|
||||
modernc.org/strutil v1.2.0/go.mod h1:/mdcBmfOibveCTBxUl5B5l6W+TTH1FXPLHZE6bTosX0=
|
||||
modernc.org/tcl v1.15.2/go.mod h1:3+k/ZaEbKrC8ePv8zJWPtBSW0V7Gg9g8rkmhI1Kfs3c=
|
||||
modernc.org/token v1.1.0/go.mod h1:UGzOrNV1mAFSEB63lOFHIpNRUVMvYTc6yu1SMY/XTDM=
|
||||
modernc.org/z v1.7.3/go.mod h1:Ipv4tsdxZRbQyLq9Q1M6gdbkxYzdlrciF2Hi/lS7nWE=
|
||||
Loading…
Reference in New Issue