package repository import ( "database/sql" "errors" "fmt" "time" "git.jamestombleson.com/jtom38/newsbot-api/internal/domain" "github.com/huandu/go-sqlbuilder" "golang.org/x/crypto/bcrypt" ) const ( TableName string = "users" ErrUserNotFound string = "requested user was not found" ) type Users interface { GetByName(name string) (domain.UserEntity, error) Create(name, password, scope string) (int64, error) Update(id int, entity domain.UserEntity) error UpdatePassword(name, password string) error CheckUserHash(name, password string) error UpdateScopes(name, scope string) error } // Creates a new instance of UserRepository with the bound sql func NewUserRepository(conn *sql.DB) userRepository { return userRepository{ connection: conn, } } type userRepository struct { connection *sql.DB } func (ur userRepository) GetByName(name string) (domain.UserEntity, error) { builder := sqlbuilder.NewSelectBuilder() builder.Select("*").From("users").Where( builder.E("Name", name), ) query, args := builder.Build() rows, err := ur.connection.Query(query, args...) if err != nil { return domain.UserEntity{}, err } data := ur.processRows(rows) if len(data) == 0 { return domain.UserEntity{}, errors.New(ErrUserNotFound) } return data[0], nil } func (ur userRepository) Create(name, password, scope string) (int64, error) { passwordBytes := []byte(password) hash, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost) if err != nil { return 0, err } dt := time.Now() queryBuilder := sqlbuilder.NewInsertBuilder() queryBuilder.InsertInto("users") queryBuilder.Cols("Name", "Hash", "UpdatedAt", "CreatedAt", "Scopes") queryBuilder.Values(name, string(hash), dt, dt, scope) query, args := queryBuilder.Build() _, err = ur.connection.Exec(query, args...) if err != nil { return 0, err } return 1, nil } func (ur userRepository) Update(id int, entity domain.UserEntity) error { return errors.New("not implemented") } func (ur userRepository) UpdatePassword(name, password string) error { _, err := ur.GetByName(name) if err != nil { return nil } queryBuilder := sqlbuilder.NewUpdateBuilder() queryBuilder.Update(TableName) //queryBuilder.Set return nil } // If the hash matches what we have in the database, an error will not be returned. // If the user does not exist or the hash does not match, an error will be returned func (ur userRepository) CheckUserHash(name, password string) error { record, err := ur.GetByName(name) if err != nil { return err } err = bcrypt.CompareHashAndPassword([]byte(record.Hash), []byte(password)) if err != nil { return err } return nil } func (ur userRepository) UpdateScopes(name, scope string) error { builder := sqlbuilder.NewUpdateBuilder() builder.Update("users") builder.Set( builder.Assign("Scopes", scope), ) builder.Where( builder.Equal("Name", name), ) query, args := builder.Build() _, err := ur.connection.Exec(query, args...) if err != nil { return err } return nil } func (ur userRepository) processRows(rows *sql.Rows) []domain.UserEntity { items := []domain.UserEntity{} for rows.Next() { var id int64 var username string var hash string var createdAt time.Time var updatedAt time.Time var deletedAt sql.NullTime var scopes string err := rows.Scan(&id, &createdAt, &updatedAt, &deletedAt, &username, &hash, &scopes) if err != nil { fmt.Println(err) } item := domain.UserEntity{ ID: id, UpdatedAt: updatedAt, Username: username, Hash: hash, Scopes: scopes, CreatedAt: createdAt, } if deletedAt.Valid { item.DeletedAt = deletedAt.Time } items = append(items, item) } return items }