From bfc40948e6e60e86ccffb6a156f788b05bb8ccf4 Mon Sep 17 00:00:00 2001 From: Felix Fernando Wijaya Date: Sun, 9 Jun 2024 17:52:49 +0700 Subject: [PATCH] Refactor and add db pool --- config/config.go | 13 ++++---- config/yml/config.development.yml | 3 +- database/database.go | 11 ++++--- internal/app/api/auth/auth.go | 32 +++++++++++++------ internal/app/api/handler/response.go | 8 ++--- internal/app/api/handler/user_handler.go | 14 ++++---- internal/app/constants/errors.go | 10 ++++++ internal/app/entity/auth_entity.go | 9 ++++++ .../entity/{user.entity.go => user_entity.go} | 17 +++++----- internal/app/ports/auth_ports.go | 6 ++-- 10 files changed, 82 insertions(+), 41 deletions(-) create mode 100644 internal/app/constants/errors.go create mode 100644 internal/app/entity/auth_entity.go rename internal/app/entity/{user.entity.go => user_entity.go} (51%) diff --git a/config/config.go b/config/config.go index 7b18bde..a54f913 100644 --- a/config/config.go +++ b/config/config.go @@ -27,12 +27,13 @@ type ( } Database struct { - Host string `env:"DATABASE_HOST" env-required:"true"` - PORT string `env:"DATABASE_PORT" env-required:"true"` - Username string `env:"DATABASE_USERNAME" env-required:"true"` - Password string `env:"DATABASE_PASSWORD" env-required:"true"` - Name string `env:"DATABASE_NAME" env-required:"true"` - PoolMax int `yaml:"pool_max" env-required:"true"` + Host string `env:"DATABASE_HOST" env-required:"true"` + PORT string `env:"DATABASE_PORT" env-required:"true"` + Username string `env:"DATABASE_USERNAME" env-required:"true"` + Password string `env:"DATABASE_PASSWORD" env-required:"true"` + Name string `env:"DATABASE_NAME" env-required:"true"` + MaxOpenConns int `yaml:"max_open_conns" env-required:"true"` + MaxIdleConns int `yaml:"max_idle_conns" env-required:"true"` } ) diff --git a/config/yml/config.development.yml b/config/yml/config.development.yml index 667b54d..384239e 100644 --- a/config/yml/config.development.yml +++ b/config/yml/config.development.yml @@ -4,7 +4,8 @@ app: host: 'localhost:8080' database: - pool_max: 2 + max_open_conns: 10 + max_idle_conns: 10 api: port: 8080 \ No newline at end of file diff --git a/database/database.go b/database/database.go index 4a71ef6..cd07d04 100644 --- a/database/database.go +++ b/database/database.go @@ -21,21 +21,24 @@ func InitDatabase(ctx context.Context, cfg config.Database) *DatabaseOpts { span, ctx := opentracing.StartSpanFromContext(ctx, "database.InitDatabase") defer span.Finish() + dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s", cfg.Username, cfg.Password, cfg.Host, cfg.Name) + return &DatabaseOpts{ - MasterDB: connectMySQL(ctx, cfg), + MasterDB: connectMySQL(ctx, dsn, cfg.MaxOpenConns, cfg.MaxIdleConns), } } -func connectMySQL(ctx context.Context, cfg config.Database) *sqlx.DB { +func connectMySQL(ctx context.Context, dsn string, maxOpenConns, maxIdleConns int) *sqlx.DB { span, _ := opentracing.StartSpanFromContext(ctx, "database.connectMySQL") defer span.Finish() - dsn := fmt.Sprintf("%s:%s@tcp(%s)/%s", cfg.Username, cfg.Password, cfg.Host, cfg.Name) - db, err := sqlx.Connect("mysql", dsn) if err != nil { logger.LogStdErr.Errorf("Failed to connect to MySQL: %s", err) } + db.SetMaxOpenConns(maxOpenConns) + db.SetMaxIdleConns(maxIdleConns) + return db } diff --git a/internal/app/api/auth/auth.go b/internal/app/api/auth/auth.go index 149faf9..5b33f40 100644 --- a/internal/app/api/auth/auth.go +++ b/internal/app/api/auth/auth.go @@ -9,32 +9,46 @@ import ( "github.com/voltgizerz/POS-restaurant/internal/app/ports" ) -type Auth struct { - SecretKey string +const ( + authType = "Bearer" +) + +type AuthJWT struct { + SecretKey string + ExpireDurationInHour int } func NewAuthJWT(secretKey string) ports.IAuth { - return &Auth{ - SecretKey: secretKey, + return &AuthJWT{ + SecretKey: secretKey, + ExpireDurationInHour: 24, } } -func (a *Auth) CreateToken(user *entity.User) (string, error) { +func (a *AuthJWT) CreateToken(user *entity.User) (*entity.CreateTokenResponse, error) { + expiredAt := time.Now().Add(time.Hour * time.Duration(a.ExpireDurationInHour)) + token := jwt.NewWithClaims(jwt.SigningMethodHS256, jwt.MapClaims{ "id": user.ID, - "expired_at": time.Now().Add(time.Hour * 24).Unix(), + "expired_at": expiredAt.Unix(), }) tokenString, err := token.SignedString(a.SecretKey) if err != nil { - return "", err + return nil, err + } + + resp := &entity.CreateTokenResponse{ + Token: tokenString, + ExpiredAt: expiredAt, + TokenType: authType, } - return tokenString, nil + return resp, nil } -func (a *Auth) VerifyToken(tokenString string) error { +func (a *AuthJWT) VerifyToken(tokenString string) error { token, err := jwt.Parse(tokenString, func(token *jwt.Token) (interface{}, error) { return a.SecretKey, nil }) diff --git a/internal/app/api/handler/response.go b/internal/app/api/handler/response.go index 82c2445..cca0cc7 100644 --- a/internal/app/api/handler/response.go +++ b/internal/app/api/handler/response.go @@ -15,8 +15,8 @@ type ( } ) -// sendErrorResponse generates and sends error response -func sendErrorResponse(c fiber.Ctx, statusCode int, errorMessage string) error { +// sendErrorResp generates and sends error response +func sendErrorResp(c fiber.Ctx, statusCode int, errorMessage string) error { response := errorResponse{ Code: statusCode, Message: errorMessage, @@ -25,8 +25,8 @@ func sendErrorResponse(c fiber.Ctx, statusCode int, errorMessage string) error { return c.Status(statusCode).JSON(response) } -// sendSuccessResponse generates and sends success response with dynamic data -func sendSuccessResponse(c fiber.Ctx, statusCode int, message string, data interface{}) error { +// sendSuccessResp generates and sends success response with dynamic data +func sendSuccessResp(c fiber.Ctx, statusCode int, message string, data interface{}) error { response := successResponse{ Code: statusCode, Message: message, diff --git a/internal/app/api/handler/user_handler.go b/internal/app/api/handler/user_handler.go index 7572063..5866a83 100644 --- a/internal/app/api/handler/user_handler.go +++ b/internal/app/api/handler/user_handler.go @@ -5,6 +5,7 @@ import ( "github.com/gofiber/fiber/v3" "github.com/opentracing/opentracing-go" + "github.com/voltgizerz/POS-restaurant/internal/app/constants" "github.com/voltgizerz/POS-restaurant/internal/app/interactor" "github.com/voltgizerz/POS-restaurant/internal/app/ports" ) @@ -29,29 +30,28 @@ func (h *UserHandler) Login(c fiber.Ctx) error { err := c.Bind().Body(req) if err != nil { - return sendErrorResponse(c, fiber.StatusBadRequest, "Invalid request body. Please provide both username and password.") + return sendErrorResp(c, fiber.StatusBadRequest, constants.ErrMsgInvalidUsernameAndPassword) } - // Check if the username and password are provided if req.Username == "" || req.Password == "" { - return sendErrorResponse(c, fiber.StatusBadRequest, "Username and password are required.") + return sendErrorResp(c, fiber.StatusBadRequest, constants.ErrMsgUsernameOrPasswordRequired) } dataUser, err := h.userService.Login(ctx, req.Username, req.Password) if err != nil { if err == sql.ErrNoRows { - return sendErrorResponse(c, fiber.StatusUnauthorized, "Username not found.") + return sendErrorResp(c, fiber.StatusUnauthorized, constants.ErrMsgUsernameNotFound) } - return sendErrorResponse(c, fiber.StatusUnauthorized, "Invalid username or password.") + return sendErrorResp(c, fiber.StatusUnauthorized, constants.ErrMsgInvalidUsernameOrPassword) } token, err := h.authService.CreateToken(dataUser) if err != nil { - return sendErrorResponse(c, fiber.StatusInternalServerError, "Failed create user token.") + return sendErrorResp(c, fiber.StatusInternalServerError, constants.ErrMsgInternalServerError) } - return sendSuccessResponse(c, fiber.StatusOK, "Login successful.", token) + return sendSuccessResp(c, fiber.StatusOK, "Success", token) } func (h *UserHandler) Register(c fiber.Ctx) error { diff --git a/internal/app/constants/errors.go b/internal/app/constants/errors.go new file mode 100644 index 0000000..17ac262 --- /dev/null +++ b/internal/app/constants/errors.go @@ -0,0 +1,10 @@ +package constants + +const ( + ErrMsgInvalidUsernameAndPassword = "Invalid request body. Please provide both username and password." + ErrMsgUsernameOrPasswordRequired = "Username and password are required." + ErrMsgUsernameNotFound = "Username not found." + ErrMsgInvalidUsernameOrPassword = "Invalid username or password." + + ErrMsgInternalServerError = "Internal server error." +) diff --git a/internal/app/entity/auth_entity.go b/internal/app/entity/auth_entity.go new file mode 100644 index 0000000..4b5d52f --- /dev/null +++ b/internal/app/entity/auth_entity.go @@ -0,0 +1,9 @@ +package entity + +import "time" + +type CreateTokenResponse struct { + Token string `json:"token"` + TokenType string `json:"token_type"` + ExpiredAt time.Time `json:"expired_at"` +} \ No newline at end of file diff --git a/internal/app/entity/user.entity.go b/internal/app/entity/user_entity.go similarity index 51% rename from internal/app/entity/user.entity.go rename to internal/app/entity/user_entity.go index c6f091d..4057755 100644 --- a/internal/app/entity/user.entity.go +++ b/internal/app/entity/user_entity.go @@ -4,6 +4,7 @@ import "time" type User struct { ID int64 `json:"id"` + RoleID int64 `json:"role_id"` Name string `json:"name"` Username string `json:"username"` Email string `json:"email"` @@ -13,12 +14,12 @@ type User struct { } type UserORM struct { - ID int64 `db:"id"` - Name string `db:"name"` - Username string `db:"username"` - Email string `db:"email"` - Password string `db:"password_hashed"` - IsActive bool `db:"is_active"` - CreatedAt time.Time `db:"created_at"` - UpdatedAt time.Time `db:"updated_at"` + ID int64 `db:"id"` + Name string `db:"name"` + Username string `db:"username"` + Email string `db:"email"` + Password string `db:"password_hashed"` + IsActive bool `db:"is_active"` + CreatedAt time.Time `db:"created_at"` + UpdatedAt time.Time `db:"updated_at"` } diff --git a/internal/app/ports/auth_ports.go b/internal/app/ports/auth_ports.go index dcb0d75..9712da4 100644 --- a/internal/app/ports/auth_ports.go +++ b/internal/app/ports/auth_ports.go @@ -1,8 +1,10 @@ package ports -import "github.com/voltgizerz/POS-restaurant/internal/app/entity" +import ( + "github.com/voltgizerz/POS-restaurant/internal/app/entity" +) type IAuth interface { - CreateToken(user *entity.User) (string, error) + CreateToken(user *entity.User) (*entity.CreateTokenResponse, error) VerifyToken(tokenString string) error }