From d4b9b713e4fddc3b9549c985e0ebd5b8b31379aa Mon Sep 17 00:00:00 2001 From: Elton Minetto Date: Mon, 29 Jun 2020 13:38:13 -0300 Subject: [PATCH] feat: add password and fix errors --- README.md | 26 +++++- api/handler/loan.go | 2 + api/main.go | 4 +- domain/entity/book/repository_mysql.go | 35 ++++++-- domain/entity/user/repository_mysql.go | 115 ++++++++++++++++++++++++- domain/entity/user/service.go | 11 ++- domain/entity/user/service_test.go | 10 ++- go.mod | 1 + go.sum | 1 + pkg/password/fake.go | 24 ++++++ pkg/password/interface.go | 7 ++ pkg/password/password.go | 31 +++++++ 12 files changed, 246 insertions(+), 21 deletions(-) create mode 100644 pkg/password/fake.go create mode 100644 pkg/password/interface.go create mode 100644 pkg/password/password.go diff --git a/README.md b/README.md index 140f765..2f084ab 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ curl -X "POST" "http://localhost:8080/v1/book" \ "title": "I Am Ozzy", "author": "Ozzy Osbourne", "pages": 294, - "quantity":1 + "quantity":10 }' ``` ### Search book @@ -56,10 +56,9 @@ curl -X "POST" "http://localhost:8080/v1/user" \ -H 'Accept: application/json' \ -d $'{ "email": "ozzy@metal.net", - "fist_name": "Ozzy", + "first_name": "Ozzy", "last_name": "Osbourne", - "password": "bateater666", - "quantity":1 + "password": "bateater666" }' ``` @@ -79,6 +78,25 @@ curl "http://localhost:8080/v1/user" \ -H 'Accept: application/json' ``` + +### Borrow a book + +``` +curl "http://localhost:8080/v1/loan/borrow/be8b1757-b043-4dbd-b873-63fa9ecd8bb1/282885d7-5d5e-4205-87eb-edc2b2ac5022" \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' +``` + +### Return a book + +``` +curl "http://localhost:8080/v1/loan/return/be8b1757-b043-4dbd-b873-63fa9ecd8bb1" \ + -H 'Content-Type: application/json' \ + -H 'Accept: application/json' +``` + + + ## CMD ### Search for a book diff --git a/api/handler/loan.go b/api/handler/loan.go index 27fe621..d1f1b82 100644 --- a/api/handler/loan.go +++ b/api/handler/loan.go @@ -1,6 +1,7 @@ package handler import ( + "fmt" "net/http" "github.com/eminetto/clean-architecture-go-v2/domain/entity/user" @@ -56,6 +57,7 @@ func borrowBook(bService book.UseCase, uService user.UseCase, loanService loan.U } err = loanService.Borrow(u, b) if err != nil { + fmt.Println(err) w.WriteHeader(http.StatusInternalServerError) w.Write([]byte(errorMessage)) return diff --git a/api/main.go b/api/main.go index e421fd7..6aa4be4 100644 --- a/api/main.go +++ b/api/main.go @@ -9,6 +9,8 @@ import ( "strconv" "time" + "github.com/eminetto/clean-architecture-go-v2/pkg/password" + "github.com/eminetto/clean-architecture-go-v2/domain/loan" "github.com/eminetto/clean-architecture-go-v2/domain/entity/user" @@ -39,7 +41,7 @@ func main() { bookService := book.NewService(bookRepo) userRepo := user.NewMySQLRepoRepository(db) - userService := user.NewService(userRepo) + userService := user.NewService(userRepo, password.NewService()) loanService := loan.NewService(userService, bookService) diff --git a/domain/entity/book/repository_mysql.go b/domain/entity/book/repository_mysql.go index 7df9b01..ff345f6 100644 --- a/domain/entity/book/repository_mysql.go +++ b/domain/entity/book/repository_mysql.go @@ -21,7 +21,7 @@ func NewMySQLRepoRepository(db *sql.DB) *MySQLRepo { } } -//Create an user +//Create a book func (r *MySQLRepo) Create(e *Book) (entity.ID, error) { stmt, err := r.db.Prepare(` insert into book (id, title, author, pages, quantity, created_at) @@ -47,17 +47,34 @@ func (r *MySQLRepo) Create(e *Book) (entity.ID, error) { return e.ID, nil } -//Get an user +//Get a book func (r *MySQLRepo) Get(id entity.ID) (*Book, error) { - return nil, nil + stmt, err := r.db.Prepare(`select id, title, author, pages, quantity, created_at from book where id = ?`) + if err != nil { + return nil, err + } + var b Book + rows, err := stmt.Query(id) + if err != nil { + return nil, err + } + for rows.Next() { + err = rows.Scan(&b.ID, &b.Title, &b.Author, &b.Pages, &b.Quantity, &b.CreatedAt) + } + return &b, nil } -//Update an user +//Update a book func (r *MySQLRepo) Update(e *Book) error { + e.UpdatedAt = time.Now() + _, err := r.db.Exec("update book set title = ?, author = ?, pages = ?, quantity = ?, updated_at = ? where id = ?", e.Title, e.Author, e.Pages, e.Quantity, e.UpdatedAt.Format("2006-01-02"), e.ID) + if err != nil { + return err + } return nil } -//Search users +//Search books func (r *MySQLRepo) Search(query string) ([]*Book, error) { stmt, err := r.db.Prepare(`select id, title, author, pages, quantity, created_at from book where title like ?`) if err != nil { @@ -82,7 +99,7 @@ func (r *MySQLRepo) Search(query string) ([]*Book, error) { return books, nil } -//List users +//List books func (r *MySQLRepo) List() ([]*Book, error) { stmt, err := r.db.Prepare(`select id, title, author, pages, quantity, created_at from book`) if err != nil { @@ -107,7 +124,11 @@ func (r *MySQLRepo) List() ([]*Book, error) { return books, nil } -//Delete an user +//Delete a book func (r *MySQLRepo) Delete(id entity.ID) error { + _, err := r.db.Exec("delete from book where id = ?", id) + if err != nil { + return err + } return nil } diff --git a/domain/entity/user/repository_mysql.go b/domain/entity/user/repository_mysql.go index 2032933..d9ac700 100644 --- a/domain/entity/user/repository_mysql.go +++ b/domain/entity/user/repository_mysql.go @@ -4,6 +4,8 @@ import ( "database/sql" "time" + "github.com/eminetto/clean-architecture-go-v2/domain" + "github.com/eminetto/clean-architecture-go-v2/domain/entity" ) @@ -47,26 +49,131 @@ func (r *MySQLRepo) Create(e *User) (entity.ID, error) { //Get an user func (r *MySQLRepo) Get(id entity.ID) (*User, error) { - return nil, nil + return getUser(id, r.db) +} + +func getUser(id entity.ID, db *sql.DB) (*User, error) { + stmt, err := db.Prepare(`select id, email, first_name, last_name, created_at from user where id = ?`) + if err != nil { + return nil, err + } + var u User + rows, err := stmt.Query(id) + if err != nil { + return nil, err + } + for rows.Next() { + err = rows.Scan(&u.ID, &u.Email, &u.FirstName, &u.LastName, &u.CreatedAt) + } + stmt, err = db.Prepare(`select book_id from book_user where user_id = ?`) + if err != nil { + return nil, err + } + rows, err = stmt.Query(id) + if err != nil { + return nil, err + } + for rows.Next() { + var i entity.ID + err = rows.Scan(&i) + u.Books = append(u.Books, i) + } + return &u, nil } //Update an user func (r *MySQLRepo) Update(e *User) error { + e.UpdatedAt = time.Now() + _, err := r.db.Exec("update user set email = ?, password = ?, first_name = ?, last_name = ?, updated_at = ? where id = ?", e.Email, e.Password, e.FirstName, e.LastName, e.UpdatedAt.Format("2006-01-02"), e.ID) + if err != nil { + return err + } + _, err = r.db.Exec("delete from book_user where user_id = ?", e.ID) + if err != nil { + return err + } + for _, b := range e.Books { + _, err := r.db.Exec("insert into book_user values(?,?,?)", e.ID, b, time.Now().Format("2006-01-02")) + if err != nil { + return err + } + } return nil } //Search users func (r *MySQLRepo) Search(query string) ([]*User, error) { - - return nil, nil + stmt, err := r.db.Prepare(`select id from user where name like ?`) + if err != nil { + return nil, err + } + defer stmt.Close() + var ids []entity.ID + rows, err := stmt.Query() + if err != nil { + return nil, err + } + for rows.Next() { + var i entity.ID + err = rows.Scan(&i) + if err != nil { + return nil, err + } + ids = append(ids, i) + } + if len(ids) == 0 { + return nil, domain.ErrNotFound + } + var users []*User + for _, id := range ids { + u, err := getUser(id, r.db) + if err != nil { + return nil, err + } + users = append(users, u) + } + return users, nil } //List users func (r *MySQLRepo) List() ([]*User, error) { - return nil, nil + stmt, err := r.db.Prepare(`select id from user`) + if err != nil { + return nil, err + } + defer stmt.Close() + var ids []entity.ID + rows, err := stmt.Query() + if err != nil { + return nil, err + } + for rows.Next() { + var i entity.ID + err = rows.Scan(&i) + if err != nil { + return nil, err + } + ids = append(ids, i) + } + if len(ids) == 0 { + return nil, domain.ErrNotFound + } + var users []*User + for _, id := range ids { + u, err := getUser(id, r.db) + if err != nil { + return nil, err + } + users = append(users, u) + } + return users, nil } //Delete an user func (r *MySQLRepo) Delete(id entity.ID) error { + _, err := r.db.Exec("delete from user where id = ?", id) + if err != nil { + return err + } return nil } diff --git a/domain/entity/user/service.go b/domain/entity/user/service.go index e6a2e2a..a476849 100644 --- a/domain/entity/user/service.go +++ b/domain/entity/user/service.go @@ -4,18 +4,22 @@ import ( "strings" "time" + "github.com/eminetto/clean-architecture-go-v2/pkg/password" + "github.com/eminetto/clean-architecture-go-v2/domain/entity" ) //Service service interface type Service struct { repo Repository + pwd password.UseCase } //NewService create new use case -func NewService(r Repository) *Service { +func NewService(r Repository, pwd password.UseCase) *Service { return &Service{ repo: r, + pwd: pwd, } } @@ -23,6 +27,11 @@ func NewService(r Repository) *Service { func (s *Service) Create(e *User) (entity.ID, error) { e.ID = entity.NewID() e.CreatedAt = time.Now() + pwd, err := s.pwd.Generate(e.Password) + if err != nil { + return e.ID, err + } + e.Password = pwd return s.repo.Create(e) } diff --git a/domain/entity/user/service_test.go b/domain/entity/user/service_test.go index d2ed57e..4c6f004 100644 --- a/domain/entity/user/service_test.go +++ b/domain/entity/user/service_test.go @@ -3,6 +3,8 @@ package user import ( "testing" + "github.com/eminetto/clean-architecture-go-v2/pkg/password" + "github.com/eminetto/clean-architecture-go-v2/domain" "github.com/eminetto/clean-architecture-go-v2/domain/entity" @@ -11,7 +13,7 @@ import ( func Test_Create(t *testing.T) { repo := NewInmemRepository() - service := NewService(repo) + service := NewService(repo, password.NewFakeService()) u := NewFixtureUser() id, err := service.Create(u) assert.Nil(t, err) @@ -22,7 +24,7 @@ func Test_Create(t *testing.T) { func Test_SearchAndFind(t *testing.T) { repo := NewInmemRepository() - service := NewService(repo) + service := NewService(repo, password.NewFakeService()) u1 := NewFixtureUser() u2 := NewFixtureUser() u2.FirstName = "Lemmy" @@ -55,7 +57,7 @@ func Test_SearchAndFind(t *testing.T) { func Test_Update(t *testing.T) { repo := NewInmemRepository() - service := NewService(repo) + service := NewService(repo, password.NewFakeService()) u := NewFixtureUser() id, err := service.Create(u) assert.Nil(t, err) @@ -72,7 +74,7 @@ func Test_Update(t *testing.T) { func TestDelete(t *testing.T) { repo := NewInmemRepository() - service := NewService(repo) + service := NewService(repo, password.NewFakeService()) u1 := NewFixtureUser() u2 := NewFixtureUser() u2ID, _ := service.Create(u2) diff --git a/go.mod b/go.mod index 8f0dbee..36aa8a5 100644 --- a/go.mod +++ b/go.mod @@ -15,5 +15,6 @@ require ( github.com/juju/mgosession v0.0.0-20170206150231-9ae6df2882cd github.com/prometheus/client_golang v1.7.1 github.com/stretchr/testify v1.6.1 + golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 gopkg.in/mgo.v2 v2.0.0-20180705113604-9856a29383ce ) diff --git a/go.sum b/go.sum index 3bcae80..23e5870 100644 --- a/go.sum +++ b/go.sum @@ -112,6 +112,7 @@ github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81P github.com/stretchr/testify v1.6.1 h1:hDPOHmpOpP40lSULcqw7IrRb/u7w6RpDC9399XyoNd0= github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2 h1:VklqNMn3ovrHsnt90PveolxSbWFaJdECFbxSq0Mqo2M= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= diff --git a/pkg/password/fake.go b/pkg/password/fake.go new file mode 100644 index 0000000..4e73747 --- /dev/null +++ b/pkg/password/fake.go @@ -0,0 +1,24 @@ +package password + +import "errors" + +//FakePassword password +type FakePassword struct{} + +//NewFakeService create a new fake password +func NewFakeService() *FakePassword { + return &FakePassword{} +} + +//Generate a new password +func (p *FakePassword) Generate(raw string) (string, error) { + return raw, nil +} + +//Compare compare two passwords +func (p *FakePassword) Compare(p1, p2 string) error { + if p1 == p2 { + return nil + } + return errors.New("Invalid password") +} diff --git a/pkg/password/interface.go b/pkg/password/interface.go new file mode 100644 index 0000000..ba8c7e3 --- /dev/null +++ b/pkg/password/interface.go @@ -0,0 +1,7 @@ +package password + +//UseCase interface +type UseCase interface { + Generate(raw string) (string, error) + Compare(p1, p2 string) error +} diff --git a/pkg/password/password.go b/pkg/password/password.go new file mode 100644 index 0000000..5f55d67 --- /dev/null +++ b/pkg/password/password.go @@ -0,0 +1,31 @@ +package password + +import ( + "golang.org/x/crypto/bcrypt" +) + +//Password password +type Password struct{} + +//NewService create a new fake password +func NewService() *Password { + return &Password{} +} + +//Generate a new password +func (p *Password) Generate(raw string) (string, error) { + hash, err := bcrypt.GenerateFromPassword([]byte(raw), 10) + if err != nil { + return "", err + } + return string(hash), nil +} + +//Compare compare two passwords +func (p *Password) Compare(p1, p2 string) error { + err := bcrypt.CompareHashAndPassword([]byte(p1), []byte(p2)) + if err != nil { + return err + } + return nil +}