Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

User registration API #4

Open
wants to merge 14 commits into
base: development
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ import (

type Storer interface {
ListUsers(context.Context) ([]User, error)
CreateUser(context.Context, User) (User, error)
// CheckUserByEmail(context.Context, string) (bool, User, error)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove commented code

GetUserByEmail(context.Context, string) (User, error)
//Create(context.Context, User) error
//GetUser(context.Context) (User, error)
//Delete(context.Context, string) error
Expand Down
12 changes: 12 additions & 0 deletions db/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,15 @@ func (m *DBMockStore) ListUsers(ctx context.Context) (users []User, err error) {
args := m.Called(ctx)
return args.Get(0).([]User), args.Error(1)
}

// CreateUser - test mock
func (m *DBMockStore) CreateUser(ctx context.Context, u User) (user User, err error) {
args := m.Called(ctx, u)
return args.Get(0).(User), args.Error(1)
}

// GetUserByEmail - test mock
func (m *DBMockStore) GetUserByEmail(ctx context.Context, email string) (user User, err error) {
args := m.Called(ctx, email)
return args.Get(0).(User), args.Error(1)
}
55 changes: 53 additions & 2 deletions db/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,31 @@ package db

import (
"context"
"database/sql"

logger "github.com/sirupsen/logrus"
)

const (
insertUserQuery = `INSERT INTO users (first_name, last_name, email, mobile, country, state, city, address, password)
VALUES (:first_name, :last_name, :email, :mobile, :country, :state, :city, :address, :password)`

getUserByEmailQuery = `SELECT * FROM users WHERE email=$1 LIMIT 1`
)

// User - struct representing a user
type User struct {
Name string `db:"name" json:"full_name"`
Age int `db:"age" json:"age"`
ID int `db:"id" json:"id"`
FirstName string `db:"first_name" json:"first_name"`
LastName string `db:"last_name" json:"last_name"`
Email string `db:"email" json:"email"`
Mobile string `db:"mobile" json:"mobile"`
Country string `db:"country" json:"country"`
State string `db:"state" json:"state"`
City string `db:"city" json:"city"`
Address string `db:"address" json:"address"`
Password string `db:"password" json:"password"`
CreatedAt string `db:"created_at" json:"created_at"`
}

func (s *pgStore) ListUsers(ctx context.Context) (users []User, err error) {
Expand All @@ -20,3 +38,36 @@ func (s *pgStore) ListUsers(ctx context.Context) (users []User, err error) {

return
}

// CreateNewUser = creates a new user in database
func (s *pgStore) CreateUser(ctx context.Context, u User) (newUser User, err error) {
stmt, err := s.db.PrepareNamed(insertUserQuery)
if err != nil {
logger.WithField("err", err.Error()).Error("Error while preparing user insert query")
return
}
_, err = stmt.Exec(u)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s.db.NamedExec

if err != nil {
logger.WithField("err", err.Error()).Error("Error while inserting user into database")
return
}
newUser, err = s.GetUserByEmail(ctx, u.Email)
if err != nil {
logger.WithField("err", err.Error()).Error("Error selecting user from database with email: " + u.Email)
return
}
return
}

// GetUserByEmail - Checks if user is present in DB and if then return user
func (s *pgStore) GetUserByEmail(ctx context.Context, email string) (user User, err error) {
err = s.db.Get(&user, getUserByEmailQuery, email)
if err != nil {
if err == sql.ErrNoRows {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to add
if err == sql.ErrNoRows condition in this func
caller of the func should handle it

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have used if err == sql.ErrNoRows condition to avoid logging no row found errors and to log only database related errors

return
}
logger.WithField("err", err.Error()).Error("Error while selecting user from database by email " + email)
return
}
return
}
3 changes: 3 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,16 @@ module joshsoftware/go-e-commerce
go 1.14

require (
github.com/bxcodec/faker/v3 v3.5.0
github.com/gorilla/mux v1.8.0
github.com/jmoiron/sqlx v1.2.0
github.com/lib/pq v1.8.0
github.com/mattes/migrate v3.0.1+incompatible
github.com/rs/cors v1.7.0
github.com/sirupsen/logrus v1.6.0
github.com/spf13/viper v1.7.1
github.com/stretchr/testify v1.6.1
github.com/urfave/cli v1.22.4
github.com/urfave/negroni v1.0.0
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5
)
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,10 @@ github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24
github.com/beorn7/perks v1.0.0/go.mod h1:KWe93zE9D1o94FZ5RNwFwVgaQK1VOXiVxmqh+CedLV8=
github.com/bgentry/speakeasy v0.1.0/go.mod h1:+zsyZBPWlz7T6j88CTgSN5bM796AkVf0kBD4zp0CCIs=
github.com/bketelsen/crypt v0.0.3-0.20200106085610-5cbc8cc4026c/go.mod h1:MKsuJmJgSg28kpZDP6UIiPt0e0Oz0kqKNGyRaWEPv84=
github.com/bxcodec/faker v1.5.0 h1:RIWOeAcM3ZHye1i8bQtHU2LfNOaLmHuRiCo60mNMOcQ=
github.com/bxcodec/faker v2.0.1+incompatible h1:P0KUpUw5w6WJXwrPfv35oc91i4d8nf40Nwln+M/+faA=
github.com/bxcodec/faker/v3 v3.5.0 h1:Rahy6dwbd6up0wbwbV7dFyQb+jmdC51kpATuUdnzfMg=
github.com/bxcodec/faker/v3 v3.5.0/go.mod h1:gF31YgnMSMKgkvl+fyEo1xuSMbEuieyqfeslGYFjneM=
github.com/cespare/xxhash v1.1.0/go.mod h1:XrSqR1VqqWfGrhpAt58auRo0WTKS1nRRg3ghfAqPWnc=
github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
Expand Down Expand Up @@ -154,6 +158,8 @@ github.com/prometheus/procfs v0.0.0-20190507164030-5867b95ac084/go.mod h1:TjEm7z
github.com/prometheus/tsdb v0.7.1/go.mod h1:qhTCs0VvXwvX/y3TZrWD7rabWM+ijKTux40TwIPHuXU=
github.com/rogpeppe/fastuuid v0.0.0-20150106093220-6724a57986af/go.mod h1:XWv6SoW27p1b0cqNHllgS5HIMJraePCO15w5zCzIWYg=
github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4=
github.com/rs/cors v1.7.0 h1:+88SsELBHx5r+hZ8TCkggzSstaWNbDvThkVK8H6f9ik=
github.com/rs/cors v1.7.0/go.mod h1:gFx+x8UowdsKA9AchylcLynDq+nNFfI8FkUZdN/jGCU=
github.com/russross/blackfriday/v2 v2.0.1 h1:lPqVAte+HuHNfhJ/0LC98ESWRz8afy9tM/0RK8m9o+Q=
github.com/russross/blackfriday/v2 v2.0.1/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
github.com/ryanuber/columnize v0.0.0-20160712163229-9b3edd62028f/go.mod h1:sm1tb6uqfes/u+d4ooFouqFdy9/2g9QGwK3SQygK0Ts=
Expand Down Expand Up @@ -203,7 +209,9 @@ golang.org/x/crypto v0.0.0-20180904163835-0709b304e793/go.mod h1:6SG95UA2DQfeDnf
golang.org/x/crypto v0.0.0-20181029021203-45a5f77698d3/go.mod h1:6SG95UA2DQfeDnfUPMdvaQW0Q7yPrPDi9nlGo2tz2b4=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5 h1:58fnuSXlxZmFdJyvtTFVmVhcMLU6v5fEb/ok4wyqtNU=
golang.org/x/crypto v0.0.0-20190605123033-f99c8df09eb5/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI=
golang.org/x/crypto v0.0.0-20200820211705-5c72a883971a h1:vclmkQCjlDX5OydZ9wv8rBCcS0QyQY66Mpf/7BZbInM=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8=
Expand Down
9 changes: 9 additions & 0 deletions main.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import (
"os"
"strconv"

"github.com/rs/cors"
logger "github.com/sirupsen/logrus"
"github.com/urfave/cli"
"github.com/urfave/negroni"
Expand Down Expand Up @@ -70,6 +71,13 @@ func startApp() (err error) {
return
}

c := cors.New(cors.Options{
AllowedOrigins: []string{"*"},
AllowedMethods: []string{"POST", "GET", "DELETE", "PUT", "PATCH", "OPTIONS"},
AllowCredentials: true,
Debug: true,
})

deps := service.Dependencies{
Store: store,
}
Expand All @@ -79,6 +87,7 @@ func startApp() (err error) {

// init web server
server := negroni.Classic()
server.Use(c)
server.UseHandler(router)

port := config.AppPort() // This can be changed to the service port number via environment variable.
Expand Down
1 change: 0 additions & 1 deletion migrations/1587381324_create_users.down.sql

This file was deleted.

4 changes: 0 additions & 4 deletions migrations/1587381324_create_users.up.sql

This file was deleted.

1 change: 1 addition & 0 deletions migrations/1599540715_create_users.down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
DROP TABLE users;
13 changes: 13 additions & 0 deletions migrations/1599540715_create_users.up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
CREATE TABLE IF NOT EXISTS users (
id SERIAL NOT NULL PRIMARY KEY,
first_name VARCHAR(255) NOT NULL,
last_name VARCHAR(255),
email VARCHAR(255) NOT NULL UNIQUE,
mobile VARCHAR(20),
country VARCHAR(100),
state VARCHAR(100),
city VARCHAR(100),
address TEXT,
password TEXT,
created_at TIMESTAMP DEFAULT (NOW() AT TIME ZONE 'UTC')
);
1 change: 1 addition & 0 deletions service/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,6 @@ func InitRouter(deps Dependencies) (router *mux.Router) {
v1 := fmt.Sprintf("application/vnd.%s.v1", config.AppName())

router.HandleFunc("/users", listUsersHandler(deps)).Methods(http.MethodGet).Headers(versionHeader, v1)
router.HandleFunc("/register", registerUserHandler(deps)).Methods(http.MethodPost).Headers(versionHeader, v1)
return
}
96 changes: 96 additions & 0 deletions service/user_http.go
Original file line number Diff line number Diff line change
@@ -1,12 +1,23 @@
package service

import (
"database/sql"
"encoding/json"
"io/ioutil"
"joshsoftware/go-e-commerce/db"
"net/http"

logger "github.com/sirupsen/logrus"
"golang.org/x/crypto/bcrypt"
)

type errorResponse struct {
Error string `json:"error"`
}
type successResponse struct {
Message string `json:"message"`
}

// @Title listUsers
// @Description list all User
// @Router /users [get]
Expand All @@ -33,3 +44,88 @@ func listUsersHandler(deps Dependencies) http.HandlerFunc {
rw.Write(respBytes)
})
}

// @Title registerUser
// @Description registers new user
// @Router /register [post]
// @Accept json
// @Success 201 {object}
// @Failure 400 {object}
func registerUserHandler(deps Dependencies) http.HandlerFunc {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {

// reading data from body
reqBody, err := ioutil.ReadAll(req.Body)
if err != nil {
logger.WithField("err", err.Error()).Error("Error in reading request body")
rw.WriteHeader(http.StatusInternalServerError)
return
}
user := db.User{}
err = json.Unmarshal(reqBody, &user)
if err != nil {
logger.WithField("err", err.Error()).Error("Error while Unmarshalling request json")
rw.WriteHeader(http.StatusInternalServerError)
return
}

// For checking if user already registered
_, err = deps.Store.GetUserByEmail(req.Context(), user.Email)

// If check true then user is already registered
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

update the comment

if err != sql.ErrNoRows && err == nil {
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

only err == nil is enough

e := errorResponse{
Error: "user already registered",
}
respBytes, err := json.Marshal(e)
if err != nil {
logger.WithField("err", err.Error()).Error("Error while marshalling error msg ")
rw.WriteHeader(http.StatusInternalServerError)
return
}
rw.Header().Add("Content-Type", "application/json")
rw.WriteHeader(http.StatusBadRequest)
rw.Write(respBytes)
return
}

// For checking error occured while looking already registered user
if err != nil && err != sql.ErrNoRows {
logger.WithField("err", err.Error()).Error("Error while looking existing user")
rw.WriteHeader(http.StatusInternalServerError)
return
}

// creating hash of the password
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(user.Password), 8)
if err != nil {
logger.WithField("err", err.Error()).Error("Error while creating hash of the password")
rw.WriteHeader(http.StatusInternalServerError)
return
}
user.Password = string(hashedPassword)

// Storing new user's data in database
_, err = deps.Store.CreateUser(req.Context(), user)
if err != nil {
logger.WithField("err", err.Error()).Error("Error in inserting user in database")
rw.WriteHeader(http.StatusInternalServerError)
return
}

msg := successResponse{
Message: "user successfully registered",
}
respBytes, err := json.Marshal(msg)
if err != nil {
logger.WithField("err", err.Error()).Error("Error while marshalling success msg ")
rw.WriteHeader(http.StatusInternalServerError)
return
}
rw.Header().Add("Content-Type", "application/json")
rw.WriteHeader(http.StatusCreated)
rw.Write(respBytes)
return

})
}
Loading