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
|
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"
|
"database/sql"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
_ "github.com/glebarez/go-sqlite"
|
_ "github.com/ncruces/go-sqlite3/driver"
|
||||||
|
_ "github.com/ncruces/go-sqlite3/embed"
|
||||||
)
|
)
|
||||||
|
|
||||||
var Connection *sql.DB
|
var Connection *sql.DB
|
||||||
|
|
||||||
func Init(dataSourceName string) error {
|
func Init(dataSourceName string) error {
|
||||||
db, err := sql.Open("sqlite", dataSourceName)
|
db, err := sql.Open("sqlite3", dataSourceName)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("Failed to open db connection: %w", err)
|
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) {
|
func FailIfErr(t *testing.T, err error, context string) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("%s: %s\n", context, err)
|
t.Fatalf("%s: %s\n", context, err)
|
||||||
|
|
|
||||||
|
|
@ -2,20 +2,29 @@ package fixtures
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"log"
|
"log"
|
||||||
|
"testing"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
lishwist "lishwist/core"
|
lishwist "lishwist/core"
|
||||||
|
|
||||||
|
"github.com/ncruces/go-sqlite3/vfs/memdb"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Login(username, password string) *lishwist.Session {
|
func TestInit(t *testing.T) error {
|
||||||
err := lishwist.Init(":memory:")
|
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 {
|
if err != nil {
|
||||||
log.Fatalf("Failed to init db: %s\n", err)
|
log.Fatalf("Failed to init db: %s\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
lw := lishwist.NewSessionManager(time.Second*10, 32)
|
lw := lishwist.NewSessionManager(time.Second*10, 32)
|
||||||
|
|
||||||
err = lishwist.Register(username, password)
|
_, err = lishwist.Register(username, password)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("Failed to register on login fixture: %s\n", err)
|
log.Fatalf("Failed to register on login fixture: %s\n", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,17 +5,18 @@ import (
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
lishwist "lishwist/core"
|
lishwist "lishwist/core"
|
||||||
|
"lishwist/core/internal/fixtures"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestLogin(t *testing.T) {
|
func TestLogin(t *testing.T) {
|
||||||
err := lishwist.Init(":memory:")
|
err := fixtures.TestInit(t)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to init db: %s\n", err)
|
t.Fatalf("Failed to init db: %s\n", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
lw := lishwist.NewSessionManager(time.Second*10, 32)
|
lw := lishwist.NewSessionManager(time.Second*10, 32)
|
||||||
|
|
||||||
err = lishwist.Register("thomas", "123")
|
_, err = lishwist.Register("thomas", "123")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Failed to register: %s\n", err)
|
t.Fatalf("Failed to register: %s\n", err)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -7,33 +7,33 @@ import (
|
||||||
"golang.org/x/crypto/bcrypt"
|
"golang.org/x/crypto/bcrypt"
|
||||||
)
|
)
|
||||||
|
|
||||||
func Register(username, newPassword string) error {
|
func Register(username, newPassword string) (*User, error) {
|
||||||
if username == "" {
|
if username == "" {
|
||||||
return errors.New("Username required")
|
return nil, errors.New("Username required")
|
||||||
}
|
}
|
||||||
if newPassword == "" {
|
if newPassword == "" {
|
||||||
return errors.New("newPassword required")
|
return nil, errors.New("newPassword required")
|
||||||
}
|
}
|
||||||
|
|
||||||
existingUser, _ := getUserByName(username)
|
existingUser, _ := getUserByName(username)
|
||||||
if existingUser != nil {
|
if existingUser != nil {
|
||||||
return errors.New("Username is taken")
|
return nil, errors.New("Username is taken")
|
||||||
}
|
}
|
||||||
|
|
||||||
hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost)
|
hashedPasswordBytes, err := bcrypt.GenerateFromPassword([]byte(newPassword), bcrypt.MinCost)
|
||||||
if err != nil {
|
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()
|
usersExist, err := hasUsers()
|
||||||
if err != nil {
|
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 {
|
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 (?);"
|
stmt := "INSERT INTO session (user_id) VALUES (?);"
|
||||||
result, err := db.Connection.Exec(stmt, user.Id)
|
result, err := db.Connection.Exec(stmt, user.Id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("Failed to execute query: %w", err)
|
||||||
}
|
}
|
||||||
id, err := result.LastInsertId()
|
id, err := result.LastInsertId()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, fmt.Errorf("Failed to get last insert id: %w", err)
|
||||||
}
|
}
|
||||||
|
|
||||||
token, err := generateSecureToken(sm.sessionTokenLength)
|
token, err := generateSecureToken(sm.sessionTokenLength)
|
||||||
|
|
|
||||||
|
|
@ -8,9 +8,9 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestFirstUserIsAdmin(t *testing.T) {
|
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")
|
fixtures.FailIfErr(t, err, "Failed to register caleb")
|
||||||
|
|
||||||
users, err := s.Admin().ListUsers()
|
users, err := s.Admin().ListUsers()
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMakeWish(t *testing.T) {
|
func TestMakeWish(t *testing.T) {
|
||||||
s := fixtures.Login("thomas", "123")
|
s := fixtures.Login(t, "thomas", "123")
|
||||||
|
|
||||||
if err := s.MakeWish("apple"); err != nil {
|
if err := s.MakeWish("apple"); err != nil {
|
||||||
t.Fatalf("Failed to make wish 1: %s\n", err)
|
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