newsbot-api/internal/repository/users.go

166 lines
4.0 KiB
Go
Raw Normal View History

package repository
import (
"context"
"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(ctx context.Context, name string) (domain.UserEntity, error)
Create(ctx context.Context, name, password, scope string) (int64, error)
Update(ctx context.Context, id int, entity domain.UserEntity) error
UpdatePassword(ctx context.Context, name, password string) error
CheckUserHash(ctx context.Context, name, password string) error
UpdateScopes(ctx context.Context, 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(ctx context.Context, 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.QueryContext(ctx, 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(ctx context.Context,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", "DeletedAt", "Scopes")
queryBuilder.Values(name, string(hash), dt, dt, time.Time{}, scope)
query, args := queryBuilder.Build()
_, err = ur.connection.ExecContext(ctx, query, args...)
if err != nil {
return 0, err
}
return 1, nil
}
func (ur userRepository) Update(ctx context.Context, id int, entity domain.UserEntity) error {
return errors.New("not implemented")
}
func (ur userRepository) UpdatePassword(ctx context.Context, name, password string) error {
_, err := ur.GetByName(ctx, 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(ctx context.Context,name, password string) error {
record, err := ur.GetByName(ctx, 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(ctx context.Context,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.ExecContext(ctx, 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
}