Skip to content
Open
Show file tree
Hide file tree
Changes from all 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
8 changes: 4 additions & 4 deletions db/db.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@ import (
"context"
)

//Storer - interface to add methods used for db operations
type Storer interface {
ListUsers(context.Context) ([]User, error)
//Create(context.Context, User) error
//GetUser(context.Context) (User, error)
//Delete(context.Context, string) error
ListUsers(ctx context.Context) (user []User, err error)
GetUser(ctx context.Context, id int) (user User, err error)
UpdateUserByID(ctx context.Context, user UserUpdateParams, id int) (err error)
}
13 changes: 13 additions & 0 deletions db/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,20 @@ type DBMockStore struct {
mock.Mock
}

//ListUsers mock method
func (m *DBMockStore) ListUsers(ctx context.Context) (users []User, err error) {
args := m.Called(ctx)
return args.Get(0).([]User), args.Error(1)
}

//GetUser mock method
func (m *DBMockStore) GetUser(ctx context.Context, id int) (user User, err error) {
args := m.Called(ctx)
return args.Get(0).(User), args.Error(1)
}

//UpdateUser mock method
func (m *DBMockStore) UpdateUserByID(ctx context.Context, user UserUpdateParams, id int) (err error) {
args := m.Called(ctx)
return args.Error(0)
}
122 changes: 118 additions & 4 deletions db/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,135 @@ package db

import (
"context"
"errors"
"fmt"
"time"

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

const (
updateUserQuery = `UPDATE users SET (
first_name,
last_name,
mobile,
address,
password,
country,
state,
city
) =
($1, $2, $3, $4, $5, $6 ,$7,$8) where id = $9 `

getUserQuery = `SELECT * from users where id=$1`
)

//User is a structure of the 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"`
Address string `db:"address" json:"address"`
Password string `db:"password" json:"password"`
Country string `db:"country" json:"country"`
State string `db:"state" json:"state"`
City string `db:"city" json:"city"`
IsAdmin bool `db:"isadmin" json:"isAdmin"`
IsDisabled bool `db:"isdisabled" json:"isDisabled"`
CreatedAt time.Time `db:"created_at" json:"created_at"`
}

//UserUpdateParams :user fields to be updated
type UserUpdateParams struct {
FirstName string `db:"first_name" json:"first_name"`
LastName string `db:"last_name" json:"last_name"`
Mobile string `db:"mobile" json:"mobile"`
Address string `db:"address" json:"address"`
Password string `db:"password" json:"password"`
Country string `db:"country" json:"country"`
State string `db:"state" json:"state"`
City string `db:"city" json:"city"`
}

func (s *pgStore) ListUsers(ctx context.Context) (users []User, err error) {
err = s.db.Select(&users, "SELECT * FROM users ORDER BY name ASC")
err = s.db.Select(&users, "SELECT * FROM users")
if err != nil {
logger.WithField("err", err.Error()).Error("error listing users")
return
}

return
}

func (s *pgStore) GetUser(ctx context.Context, id int) (user User, err error) {
err = s.db.Get(&user, getUserQuery, id)
if err != nil {
logger.WithField("err", err.Error()).Error("Error listing users")
logger.WithField("err", err.Error()).Error(fmt.Errorf("error selecting user from database by id %d", id))
return
}

return
}

func (s *pgStore) UpdateUserByID(ctx context.Context, user UserUpdateParams, userID int) (err error) {

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

_, err = s.db.Exec(
updateUserQuery,
user.FirstName,
user.LastName,
user.Mobile,
user.Address,
user.Password,
user.Country,
user.State,
user.City,
userID,
)
if err != nil {
logger.WithField("err", err.Error()).Error("error updating user profile")
return
}
return
}

//Validate function to check empty fields
func (user *UserUpdateParams) Validate() (err error) {

if user.FirstName == "" {
return errors.New("first name cannot be blank")
}
if user.LastName == "" {
return errors.New("last name cannot be blank")
}
if user.Mobile == "" {
return errors.New("mobile cannot be blank")
}
if user.Password == "" {
return errors.New("password cannot be blank")
}
if user.Address == "" {
return errors.New("address cannot be blank")
}
if user.Country == "" {
return errors.New("country cannot be blank")
}
if user.State == "" {
return errors.New("state cannot be blank")
}
if user.City == "" {
return errors.New("city cannot be blank")
}
return
}

//TODO add function for aunthenticating user
2 changes: 2 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ 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
Expand All @@ -12,4 +13,5 @@ require (
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
)
6 changes: 6 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 @@ -105,6 +109,7 @@ github.com/julienschmidt/httprouter v1.2.0/go.mod h1:SYymIcj16QtmaHHD7aYtjjsJG7V
github.com/kisielk/errcheck v1.1.0/go.mod h1:EZBBE59ingxPouuu3KfxchcWSUPOHkagtvWXihfKN4Q=
github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.3 h1:CE8S1cTafDpPvMhIxNJKvHsGVBgn1xWYf1NbHQhywc8=
github.com/konsorten/go-windows-terminal-sequences v1.0.3/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/kr/logfmt v0.0.0-20140226030751-b84e30acd515/go.mod h1:+0opPa2QZZtGFBFZlji/RkVcI2GknAs/DXo4wKdlNEc=
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
Expand Down Expand Up @@ -203,6 +208,7 @@ 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/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=
Expand Down
19 changes: 15 additions & 4 deletions migrations/1587381324_create_users.up.sql
Original file line number Diff line number Diff line change
@@ -1,4 +1,15 @@
CREATE TABLE users (
name text,
age integer
);
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,
isAdmin BOOLEAN DEFAULT FALSE,
isDisabled BOOLEAN DEFAULT FALSE,
created_at TIMESTAMP DEFAULT (NOW() AT TIME ZONE 'UTC')
);
39 changes: 39 additions & 0 deletions service/response.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package service

import (
"encoding/json"
"net/http"

logger "github.com/sirupsen/logrus"
)

type successResponse struct {
Data interface{} `json:"data"`
}

type errorResponse struct {
Error interface{} `json:"error"`
}

type messageObject struct {
Message string `json:"message"`
}

type errorObject struct {
Code string `json:"code"`
messageObject
Fields map[string]string `json:"fields"`
}

func repsonse(rw http.ResponseWriter, status int, responseBody interface{}) {
respBytes, err := json.Marshal(responseBody)
if err != nil {
logger.WithField("err", err.Error()).Error("Error while marshaling core values data")
rw.WriteHeader(http.StatusInternalServerError)
return
}

rw.Header().Add("Content-Type", "application/json")
rw.WriteHeader(status)
rw.Write(respBytes)
}
4 changes: 4 additions & 0 deletions service/router.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,5 +24,9 @@ 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)

//TODO :- use JWT authentication
router.HandleFunc("/user", getUserHandler(deps)).Methods(http.MethodGet).Headers(versionHeader, v1)
router.HandleFunc("/user/update", updateUserHandler(deps)).Methods(http.MethodPatch).Headers(versionHeader, v1)
return
}
85 changes: 85 additions & 0 deletions service/user_http.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package service

import (
"encoding/json"
"joshsoftware/go-e-commerce/db"
"net/http"

logger "github.com/sirupsen/logrus"
Expand Down Expand Up @@ -33,3 +34,87 @@ func listUsersHandler(deps Dependencies) http.HandlerFunc {
rw.Write(respBytes)
})
}

//get user by id
func getUserHandler(deps Dependencies) http.HandlerFunc {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
//TODO :- get the userId from JWT autentication token
userID := 1
user, err := deps.Store.GetUser(req.Context(), int(userID))
if err != nil {
logger.WithField("err", err.Error()).Error("error while fetching User")
rw.WriteHeader(http.StatusNotFound)
repsonse(rw, http.StatusNotFound, errorResponse{
Error: messageObject{
Message: "id Not Found",
},
})
return
}
repsonse(rw, http.StatusOK, successResponse{Data: user})
})

}

//update user by id
func updateUserHandler(deps Dependencies) http.HandlerFunc {
return http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {

//TODO :- get the userID from JWT autentication token
userID := 1
var user db.UserUpdateParams

err := json.NewDecoder(req.Body).Decode(&user)

if err != nil {
rw.WriteHeader(http.StatusBadRequest)
logger.WithField("err", err.Error()).Error("error while decoding user")
repsonse(rw, http.StatusBadRequest, errorResponse{
Error: messageObject{
Message: "invalid json body",
},
})
return
}
err = user.Validate()
if err != nil {
rw.WriteHeader(http.StatusBadRequest)
repsonse(rw, http.StatusBadRequest, errorResponse{
Error: messageObject{
Message: err.Error(),
},
})
logger.WithField("err", err.Error()).Error("error while validating user's profile")
return
}

err = deps.Store.UpdateUserByID(req.Context(), user, int(userID))
if err != nil {
rw.WriteHeader(http.StatusInternalServerError)
repsonse(rw, http.StatusInternalServerError, errorResponse{
Error: messageObject{
Message: "internal server error",
},
})
logger.WithField("err", err.Error()).Error("error while updating user's profile")
return
}

uUser, err := deps.Store.GetUser(req.Context(), int(userID))
if err != nil {
logger.WithField("err", err.Error()).Error("error while fetching User")
rw.WriteHeader(http.StatusNotFound)
repsonse(rw, http.StatusNotFound, errorResponse{
Error: messageObject{
Message: "error in fetching user",
},
})
return
}

repsonse(rw, http.StatusOK, successResponse{Data: uUser})
return

})

}
Loading