diff --git a/internal/db/query/models.go b/internal/db/query/models.go index cb06db6..14d65f9 100644 --- a/internal/db/query/models.go +++ b/internal/db/query/models.go @@ -113,12 +113,11 @@ type Topic struct { } type User struct { - ID int32 `db:"id"` - Name string `db:"name"` - Email string `db:"email"` - Password string `db:"password"` - Role UserRole `db:"role"` - EmailVerified bool `db:"email_verified"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` + ID int32 `db:"id"` + Name string `db:"name"` + Email string `db:"email"` + Password string `db:"password"` + Role UserRole `db:"role"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` } diff --git a/internal/db/query/users.sql_gen.go b/internal/db/query/users.sql_gen.go index ab64625..6f8d32b 100644 --- a/internal/db/query/users.sql_gen.go +++ b/internal/db/query/users.sql_gen.go @@ -10,7 +10,7 @@ import ( const addUser = `-- name: AddUser :one INSERT INTO users(name, email, password, role) VALUES ($1, $2, $3, $4) -RETURNING id, name, email, password, role, email_verified, created_at, updated_at +RETURNING id, name, email, password, role, created_at, updated_at ` type AddUserParams struct { @@ -34,7 +34,6 @@ func (q *Queries) AddUser(ctx context.Context, arg AddUserParams) (User, error) &i.Email, &i.Password, &i.Role, - &i.EmailVerified, &i.CreatedAt, &i.UpdatedAt, ) @@ -53,7 +52,7 @@ func (q *Queries) DeleteUser(ctx context.Context, id int32) error { } const fetchAllUsers = `-- name: FetchAllUsers :many -SELECT id, name, email, password, role, email_verified, created_at, updated_at +SELECT id, name, email, password, role, created_at, updated_at FROM users ` @@ -72,7 +71,6 @@ func (q *Queries) FetchAllUsers(ctx context.Context) ([]User, error) { &i.Email, &i.Password, &i.Role, - &i.EmailVerified, &i.CreatedAt, &i.UpdatedAt, ); err != nil { @@ -87,7 +85,7 @@ func (q *Queries) FetchAllUsers(ctx context.Context) ([]User, error) { } const fetchUser = `-- name: FetchUser :one -SELECT id, name, email, password, role, email_verified, created_at, updated_at +SELECT id, name, email, password, role, created_at, updated_at FROM users WHERE id = $1 ` @@ -101,7 +99,6 @@ func (q *Queries) FetchUser(ctx context.Context, id int32) (User, error) { &i.Email, &i.Password, &i.Role, - &i.EmailVerified, &i.CreatedAt, &i.UpdatedAt, ) @@ -112,18 +109,16 @@ const updateUser = `-- name: UpdateUser :one UPDATE users SET name = $1, email = $2, - role = $3, - email_verified = $4 -WHERE id = $5 -RETURNING id, name, email, password, role, email_verified, created_at, updated_at + role = $3 +WHERE id = $4 +RETURNING id, name, email, password, role, created_at, updated_at ` type UpdateUserParams struct { - Name string `db:"name"` - Email string `db:"email"` - Role UserRole `db:"role"` - EmailVerified bool `db:"email_verified"` - ID int32 `db:"id"` + Name string `db:"name"` + Email string `db:"email"` + Role UserRole `db:"role"` + ID int32 `db:"id"` } func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) (User, error) { @@ -131,7 +126,6 @@ func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) (User, e arg.Name, arg.Email, arg.Role, - arg.EmailVerified, arg.ID, ) var i User @@ -141,7 +135,6 @@ func (q *Queries) UpdateUser(ctx context.Context, arg UpdateUserParams) (User, e &i.Email, &i.Password, &i.Role, - &i.EmailVerified, &i.CreatedAt, &i.UpdatedAt, ) @@ -152,7 +145,7 @@ const updateUserPassword = `-- name: UpdateUserPassword :one UPDATE users SET password = $1 WHERE id = $2 -RETURNING id, name, email, password, role, email_verified, created_at, updated_at +RETURNING id, name, email, password, role, created_at, updated_at ` type UpdateUserPasswordParams struct { @@ -169,7 +162,6 @@ func (q *Queries) UpdateUserPassword(ctx context.Context, arg UpdateUserPassword &i.Email, &i.Password, &i.Role, - &i.EmailVerified, &i.CreatedAt, &i.UpdatedAt, ) diff --git a/internal/db/sql/users.sql b/internal/db/sql/users.sql index 0e17a08..9040f26 100644 --- a/internal/db/sql/users.sql +++ b/internal/db/sql/users.sql @@ -13,9 +13,8 @@ RETURNING *; UPDATE users SET name = $1, email = $2, - role = $3, - email_verified = $4 -WHERE id = $5 + role = $3 +WHERE id = $4 RETURNING *; -- name: DeleteUser :exec diff --git a/internal/users/handler.go b/internal/users/handler.go index bb1b472..0328782 100644 --- a/internal/users/handler.go +++ b/internal/users/handler.go @@ -1,7 +1,9 @@ package users import ( + "github.com/gorilla/mux" "net/http" + "strconv" "time" "github.com/aveloper/blog/internal/db/query" @@ -24,6 +26,91 @@ func NewHandler(log *zap.Logger, reader *request.Reader, jsonWriter *response.JS return &Handler{log: log, reader: reader, jsonWriter: jsonWriter, repository: repository} } +func (h *Handler) getUser() http.HandlerFunc { + // TODO: Add better password validation(verify for special characters, number etc. + + type Response struct { + ID int32 `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Role string `json:"role"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + } + + return func(w http.ResponseWriter, r *http.Request) { + + params := mux.Vars(r) + id, err := strconv.Atoi(params["id"]) + if err != nil { + h.log.Error("ID should be int type", zap.Error(err)) + h.jsonWriter.BadRequest(w, r, &UserNotFound{}) + return + } + + + user, err := h.repository.GetUser(r.Context(), int32(id)) + if err != nil { + // TODO: Handle user constraint errors separately + h.log.Error("Failed get the user from DB", zap.Error(err)) + h.jsonWriter.BadRequest(w, r, &UserNotFound{ID: int32(id)}) + return + } + + resp := &Response{ + ID: user.ID, + Name: user.Name, + Email: user.Email, + Role: string(user.Role), + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + } + + + h.jsonWriter.Ok(w, r, resp) + } +} + +func (h *Handler) getAllUser() http.HandlerFunc { + type Response struct { + ID int32 `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Role string `json:"role"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + } + + return func(w http.ResponseWriter, r *http.Request) { + // FIXME: Hash password before saving + users, err := h.repository.GetAllUsers(r.Context()) + if err != nil { + // TODO: Handle user constraint errors separately + h.log.Error("Failed get all the user from DB", zap.Error(err)) + h.jsonWriter.BadRequest(w, r, &UserNotFound{}) + return + } + + resp := make([]Response, 0) + + for _, user := range users { + r := Response{ + ID: user.ID, + Name: user.Name, + Email: user.Email, + Role: string(user.Role), + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + } + + resp = append(resp, r) + } + + h.jsonWriter.Ok(w, r, resp) + } +} + + func (h *Handler) addUser() http.HandlerFunc { // TODO: Add better password validation(verify for special characters, number etc. type Request struct { @@ -38,7 +125,6 @@ func (h *Handler) addUser() http.HandlerFunc { Name string `json:"name"` Email string `json:"email"` Role string `json:"role"` - EmailVerified bool `json:"email_verified"` CreatedAt time.Time `json:"created_at"` UpdatedAt time.Time `json:"updated_at"` } @@ -70,13 +156,143 @@ func (h *Handler) addUser() http.HandlerFunc { Name: user.Name, Email: user.Email, Role: string(user.Role), - EmailVerified: user.EmailVerified, CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, } - // TODO: Send verification Email h.jsonWriter.Ok(w, r, resp) } } + +func (h *Handler) updateUser() http.HandlerFunc{ + type Request struct { + ID int32 `json:"id" validate:"required"` + Name string `json:"name" validate:"required"` + Email string `json:"email" validate:"required,email"` + Role string `json:"role" validate:"required,oneof=admin editor author contributor"` + } + + type Response struct { + ID int32 `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Role string `json:"role"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + } + + return func(w http.ResponseWriter, r *http.Request) { + req := &Request{} + + ok := h.reader.ReadJSONAndValidate(w, r, req) + if !ok { + return + } + + user, err := h.repository.UpdateUser(r.Context(), query.UpdateUserParams{ + ID: req.ID, + Name: req.Name, + Email: req.Email, + Role: query.UserRole(req.Role), + }) + if err != nil { + // TODO: Handle user constraint errors separately + h.log.Error("Failed adding user to DB", zap.Error(err)) + h.jsonWriter.DefaultError(w, r) + return + } + + resp := &Response{ + ID: user.ID, + Name: user.Name, + Email: user.Email, + Role: string(user.Role), + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + } + + + h.jsonWriter.Ok(w, r, resp) + } +} + +func (h *Handler) updateUserPassword() http.HandlerFunc{ + type Request struct { + ID int32 `json:"id" validate:"required"` + Password string `json:"password" validate:"required,gte=8"` + } + + type Response struct { + ID int32 `json:"id"` + Name string `json:"name"` + Email string `json:"email"` + Role string `json:"role"` + CreatedAt time.Time `json:"created_at"` + UpdatedAt time.Time `json:"updated_at"` + } + + return func(w http.ResponseWriter, r *http.Request) { + req := &Request{} + + ok := h.reader.ReadJSONAndValidate(w, r, req) + if !ok { + return + } + + user, err := h.repository.UpdateUserPassword(r.Context(), query.UpdateUserPasswordParams{ + ID: req.ID, + Password: req.Password, + }) + if err != nil { + // TODO: Handle user constraint errors separately + h.log.Error("Failed updating user password to DB", zap.Error(err)) + h.jsonWriter.DefaultError(w, r) + return + } + + resp := &Response{ + ID: user.ID, + Name: user.Name, + Email: user.Email, + Role: string(user.Role), + CreatedAt: user.CreatedAt, + UpdatedAt: user.UpdatedAt, + } + + + h.jsonWriter.Ok(w, r, resp) + } +} + +func (h *Handler) DeleteUser() http.HandlerFunc { + type Request struct { + ID int32 `json:"id" validate:"required"` + } + + type Response struct { + Message string `json:"message"` + } + + return func(w http.ResponseWriter, r *http.Request) { + req := &Request{} + + ok := h.reader.ReadJSONAndValidate(w, r, req) + if !ok { + return + } + + err := h.repository.DeleteUser(r.Context(), req.ID) + if err != nil { + h.log.Error("Failed to delete user from DB", zap.Error(err)) + h.jsonWriter.DefaultError(w, r) + return + } + + resp := &Response{ + Message: "User successfully deleted", + } + + h.jsonWriter.Ok(w, r, resp) + } +} \ No newline at end of file diff --git a/internal/users/repository.go b/internal/users/repository.go index 062cc86..271b9d8 100644 --- a/internal/users/repository.go +++ b/internal/users/repository.go @@ -23,6 +23,28 @@ func NewRepository(db *db.DB, log *zap.Logger) *Repository { } } +//GetUser fetch the user from users by given id +func (r *Repository) GetUser(ctx context.Context, id int32) (*query.User, error) { + u, err := r.q.FetchUser(ctx, id) + if err != nil { + r.log.Error("failed to get the user", zap.Error(err)) + return nil, err + } + + return &u, nil +} + +//GetAllUsers fetch all the users from users +func (r *Repository) GetAllUsers(ctx context.Context) ([]query.User, error) { + u, err := r.q.FetchAllUsers(ctx) + if err != nil { + r.log.Error("failed to get the user", zap.Error(err)) + return nil, err + } + + return u, nil +} + //AddUser adds a new User func (r *Repository) AddUser(ctx context.Context, user query.AddUserParams) (*query.User, error) { u, err := r.q.AddUser(ctx, user) @@ -33,3 +55,37 @@ func (r *Repository) AddUser(ctx context.Context, user query.AddUserParams) (*qu return &u, nil } + +//UpdateUser update the existing user +func (r *Repository) UpdateUser(ctx context.Context, user query.UpdateUserParams) (*query.User, error) { + u, err := r.q.UpdateUser(ctx, user) + if err != nil { + r.log.Error("failed to update user", zap.Error(err)) + return nil, err + } + + return &u, nil +} + +//UpdateUserPassword update the existing user password +func (r *Repository) UpdateUserPassword(ctx context.Context, user query.UpdateUserPasswordParams) (*query.User, error) { + u, err := r.q.UpdateUserPassword(ctx, user) + if err != nil { + r.log.Error("failed to update user password", zap.Error(err)) + return nil, err + } + + return &u, nil +} + +//DeleteUser delete the existing user +func (r *Repository) DeleteUser(ctx context.Context, id int32) error { + err := r.q.DeleteUser(ctx, id) + if err != nil { + r.log.Error("failed to delete the user ", zap.Error(err)) + return err + } + + return nil +} + diff --git a/internal/users/responseType.go b/internal/users/responseType.go new file mode 100644 index 0000000..ded89a9 --- /dev/null +++ b/internal/users/responseType.go @@ -0,0 +1,22 @@ +package users + +import ( + "fmt" + "github.com/aveloper/blog/internal/http/response" +) + +type UserNotFound struct { + ID int32 `json:"id"` +} + +func (d *UserNotFound) Message() string { + return fmt.Sprintf("User not Found for the id %d", d.ID) +} + +func (d *UserNotFound) Code() response.ErrorCode { + return response.DefaultErrorCode +} + +func (d *UserNotFound) Data() interface{} { + return nil +} \ No newline at end of file diff --git a/internal/users/route.go b/internal/users/route.go index ffc8512..68ba94f 100644 --- a/internal/users/route.go +++ b/internal/users/route.go @@ -10,5 +10,11 @@ import ( func UserRoutes(r *mux.Router, uh *Handler) { r = r.PathPrefix("/users").Subrouter() + r.HandleFunc("/edit/password", uh.updateUserPassword()).Methods(http.MethodPost) + r.HandleFunc("/edit", uh.updateUser()).Methods(http.MethodPost) + //FIXME: it can also be changed to /delete/{id} for better use + r.HandleFunc("/delete", uh.DeleteUser()).Methods(http.MethodPost) + r.HandleFunc("/all", uh.getAllUser()).Methods(http.MethodGet) + r.HandleFunc("/{id}", uh.getUser()).Methods(http.MethodGet) r.HandleFunc("/", uh.addUser()).Methods(http.MethodPost) }