diff --git a/.gitignore b/.gitignore index d1ce888..ddc0925 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ *.so *.dylib go-cook +*.db # Test binary, built with `go test -c` *.test diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..263bf0b --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,8 @@ +{ + "cSpell.words": [ + "glebarez", + "gocook", + "huandu", + "sqlbuilder" + ] +} \ No newline at end of file diff --git a/Justfile b/Justfile new file mode 100644 index 0000000..264ab7f --- /dev/null +++ b/Justfile @@ -0,0 +1,5 @@ +migrate-up: + GOOSE_DRIVER=sqlite3 GOOSE_DBSTRING=./gocook.db goose -dir ./api/migrations up + +migrate-down: + GOOSE_DRIVER=sqlite3 GOOSE_DBSTRING=./gocook.db goose -dir ./api/migrations down \ No newline at end of file diff --git a/api/migrations/20240321194227_user_table.sql b/api/migrations/20240321194227_user_table.sql new file mode 100644 index 0000000..ffc627b --- /dev/null +++ b/api/migrations/20240321194227_user_table.sql @@ -0,0 +1,16 @@ +-- +goose Up +-- +goose StatementBegin +SELECT 'up SQL query'; +CREATE TABLE USERS ( + ID INTEGER PRIMARY KEY AUTOINCREMENT, + Name TEXT NOT NULL, + Hash TEXT NOT NULL, + CreatedAt DATETIME NOT NULL, + LastUpdated DATETIME NOT NULL +) +-- +goose StatementEnd +-- +goose Down +-- +goose StatementBegin +SELECT 'down SQL query'; +DROP TABLE IF EXISTS USERS; +-- +goose StatementEnd \ No newline at end of file diff --git a/api/models/users.go b/api/models/users.go index 730153c..e47dc2b 100644 --- a/api/models/users.go +++ b/api/models/users.go @@ -1,6 +1,11 @@ package models +import "time" + type UserModel struct { - Name string - Hash string -} \ No newline at end of file + Id int + Name string + Hash string + CreatedAt time.Time + LastUpdated time.Time +} diff --git a/api/repositories/users.go b/api/repositories/users.go index c629fcc..8d52e30 100644 --- a/api/repositories/users.go +++ b/api/repositories/users.go @@ -2,9 +2,9 @@ package repositories import ( "database/sql" - "go-cook/api/models" - "errors" "fmt" + "go-cook/api/models" + "time" "github.com/huandu/go-sqlbuilder" "golang.org/x/crypto/bcrypt" @@ -25,25 +25,77 @@ func (ur UserRepository) GetByName(name string) (models.UserModel, 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 models.UserModel{}, err + } - return models.UserModel{}, errors.New("user was not found") + return ur.processRows(rows)[0], nil } -func (ur UserRepository) NewUser(name string, password string) (int, error) { +func (ur UserRepository) NewUser(name, password string) (int64, error) { passwordBytes := []byte(password) hash, err := bcrypt.GenerateFromPassword(passwordBytes, bcrypt.DefaultCost) if err != nil { return 0, err } - fmt.Println(hash) - query := sqlbuilder.NewInsertBuilder() - query.InsertInto("users") - query.Cols("name", "hash") - query.Values(name, string(hash)) - ur.connection.Query(query.Build()) + dt := time.Now() + queryBuilder := sqlbuilder.NewInsertBuilder() + queryBuilder.InsertInto("users") + queryBuilder.Cols("Name", "Hash", "LastUpdated", "CreatedAt") + queryBuilder.Values(name, string(hash), dt, dt) + query, args := queryBuilder.Build() + + _, err = ur.connection.Exec(query, args...) + if err != nil { + return 0, err + } return 1, 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) processRows(rows *sql.Rows) []models.UserModel { + items := []models.UserModel{} + + for rows.Next() { + var id int + var name string + var hash string + var createdAt time.Time + var lastUpdated time.Time + err := rows.Scan(&id, &name, &hash, &createdAt, &lastUpdated) + if err != nil { + fmt.Println(err) + } + + items = append(items, models.UserModel{ + Id: id, + Name: name, + Hash: hash, + CreatedAt: createdAt, + LastUpdated: lastUpdated, + }) + } + + return items +} diff --git a/api/repositories/users_test.go b/api/repositories/users_test.go index f72d491..c86cd89 100644 --- a/api/repositories/users_test.go +++ b/api/repositories/users_test.go @@ -1,7 +1,40 @@ package repositories_test -import "testing" +import ( + "database/sql" + "go-cook/api/repositories" + "log" + "testing" -func TestNewUser(t *testing.T) { - -} \ No newline at end of file + _ "github.com/glebarez/go-sqlite" +) + +func TestCanCreateNewUser(t *testing.T) { + db, err := sql.Open("sqlite", "../../gocook.db") + if err != nil { + log.Println("unable to open connection") + t.FailNow() + } + defer db.Close() + + repo := repositories.NewUserRepository(db) + updated, err := repo.NewUser("testing", "NotSecure") + if err != nil { + log.Println(err) + t.FailNow() + } + log.Println(updated) +} + +func TestCanFindUserInTable(t *testing.T) { + db, err := sql.Open("sqlite", "../../gocook.db") + if err != nil { + log.Println("unable to open connection") + t.FailNow() + } + defer db.Close() + + repo := repositories.NewUserRepository(db) + repo.GetByName("testing") + +}