Skip to content

Commit

Permalink
feat: add user api
Browse files Browse the repository at this point in the history
  • Loading branch information
cukhoaimon committed Feb 1, 2024
1 parent 6832fc3 commit 5c0c229
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 1 deletion.
2 changes: 2 additions & 0 deletions api/server.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@ func NewServer(store db.Store) *Server {
}

// routing here
router.POST("/api/v1/user", server.createUser)

router.GET("/api/v1/account", server.listAccount)
router.GET("/api/v1/account/:id", server.getAccount)
router.POST("/api/v1/account", server.createAccount)
Expand Down
69 changes: 69 additions & 0 deletions api/user.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package api

import (
"errors"
db "github.com/cukhoaimon/SimpleBank/db/sqlc"
"github.com/cukhoaimon/SimpleBank/utils"
"github.com/gin-gonic/gin"
"github.com/lib/pq"
"net/http"
"time"
)

type createUserRequest struct {
Username string `json:"username" binding:"required,alphanum"`
Password string `json:"password" binding:"required,min=8"`
FullName string `json:"full_name" binding:"required"`
Email string `json:"email" binding:"required,email"`
}

type createUserResponse struct {
Username string `json:"username"`
FullName string `json:"full_name"`
Email string `json:"email"`
PasswordChangedAt time.Time `json:"password_changed_at"`
CreatedAt time.Time `json:"created_at"`
}

func (server *Server) createUser(ctx *gin.Context) {
var req createUserRequest

if err := ctx.ShouldBindJSON(&req); err != nil {
ctx.JSON(http.StatusBadRequest, errorResponse(err))
return
}

hashedPassword, err := utils.HashPassword(req.Password)

arg := db.CreateUserParams{
Username: req.Username,
HashedPassword: hashedPassword,
FullName: req.FullName,
Email: req.Email,
}

user, err := server.store.CreateUser(ctx, arg)
if err != nil {
var pqErr *pq.Error
if errors.As(err, &pqErr) {
switch pqErr.Code.Name() {
case "unique_violation":
ctx.JSON(http.StatusForbidden, errorResponse(err))
return
}
}

ctx.JSON(http.StatusInternalServerError, errorResponse(err))
return
}

rsp := createUserResponse{
Username: user.Username,
FullName: user.FullName,
Email: user.Email,
PasswordChangedAt: user.PasswordChangedAt,
CreatedAt: user.CreatedAt,
}

ctx.JSON(http.StatusCreated, rsp)
}
5 changes: 4 additions & 1 deletion db/sqlc/user.sql_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,12 @@ import (
)

func createRandomUser(t *testing.T) User {
hashedPassword, err := utils.HashPassword(utils.RandomString(10))
require.Nil(t, err)

arg := CreateUserParams{
Username: utils.RandomOwner(),
HashedPassword: "secret",
HashedPassword: hashedPassword,
FullName: utils.RandomOwner(),
Email: utils.RandomEmail(),
}
Expand Down
21 changes: 21 additions & 0 deletions utils/password.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package utils

import (
"fmt"
"golang.org/x/crypto/bcrypt"
)

// HashPassword returns the password hashed by bcrypt
func HashPassword(password string) (string, error) {
hashedPassword, err := bcrypt.GenerateFromPassword([]byte(password), bcrypt.DefaultCost)
if err != nil {
return "", fmt.Errorf("fail to hash password: %w", err)
}

return string(hashedPassword), nil
}

// CheckPassword check if the password is match or not
func CheckPassword(password, hashedPassword string) error {
return bcrypt.CompareHashAndPassword([]byte(hashedPassword), []byte(password))
}
47 changes: 47 additions & 0 deletions utils/password_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package utils

import (
"github.com/stretchr/testify/require"
"testing"
)

func TestPassword(t *testing.T) {
password := RandomString(10)
hashedPassword, err := HashPassword(password)

require.Nil(t, err)

type args struct {
password string
hashedPassword string
}
tests := []struct {
name string
args args
wantErr bool
}{
{
name: "Same password",
args: args{
password: password,
hashedPassword: hashedPassword,
},
wantErr: false,
},
{
name: "Different password",
args: args{
password: RandomString(10),
hashedPassword: hashedPassword,
},
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := CheckPassword(tt.args.password, tt.args.hashedPassword); (err != nil) != tt.wantErr {
t.Errorf("CheckPassword() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}

0 comments on commit 5c0c229

Please sign in to comment.