Skip to content

Commit

Permalink
feat: login and detail endpoint
Browse files Browse the repository at this point in the history
  • Loading branch information
martinyonatann committed Oct 17, 2023
1 parent 4c9433c commit dc655e6
Show file tree
Hide file tree
Showing 33 changed files with 550 additions and 207 deletions.
6 changes: 5 additions & 1 deletion config/config-local.yaml.example
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ server:
database:
host: localhost
port: 3306
name: golang-clean-architecture
name: users
user: mysql
password: pwd

authentication:
key: DoWithLogic!@#
secret_key: s3cr#tK3y!@#
4 changes: 3 additions & 1 deletion config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,9 @@ type (
}

AuthenticationConfig struct {
Key string `mapstructure:"key"`
Key string `mapstructure:"key"`
SecretKey string `mapstructure:"secret_key"`
SaltKey string `mapstructure:"salt_key"`
}
)

Expand Down
6 changes: 4 additions & 2 deletions database/mysql/migration/20230924142159_add_user_table.sql
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,18 @@
-- +goose StatementBegin
CREATE TABLE `users` (
`id` int NOT NULL AUTO_INCREMENT,
`email` varchar(255) NOT NULL,
`password` text NOT NULL,
`fullname` varchar(255) NOT NULL,
`phone_number` varchar(15) NOT NULL,
`user_type` varchar(50) NOT NULL,
`is_active` tinyint(1) NOT NULL,
`created_at` timestamp NOT NULL,
`created_by` varchar(255) NOT NULL,
`updated_at` timestamp NULL DEFAULT NULL,
`updated_at` timestamp DEFAULT NULL,
`updated_by` varchar(255) DEFAULT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

-- +goose StatementEnd

Expand Down
133 changes: 64 additions & 69 deletions internal/users/delivery/http/v1/handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,17 @@ import (
"github.com/DoWithLogic/golang-clean-architecture/internal/users/dtos"
"github.com/DoWithLogic/golang-clean-architecture/internal/users/entities"
usecases "github.com/DoWithLogic/golang-clean-architecture/internal/users/usecase"
"github.com/DoWithLogic/golang-clean-architecture/pkg/middleware"
"github.com/DoWithLogic/golang-clean-architecture/pkg/otel/zerolog"
"github.com/DoWithLogic/golang-clean-architecture/pkg/utils/response"
"github.com/labstack/echo/v4"
)

type (
Handlers interface {
Login(c echo.Context) error
CreateUser(c echo.Context) error
UserDetail(c echo.Context) error
UpdateUser(c echo.Context) error
UpdateUserStatus(c echo.Context) error
}
Expand All @@ -40,107 +44,106 @@ func NewHandlers(uc usecases.Usecase, log *zerolog.Logger) Handlers {
return &handlers{uc, log}
}

func (h *handlers) Login(c echo.Context) error {
var (
request dtos.UserLoginRequest
)

if err := c.Bind(&request); err != nil {
return c.JSON(http.StatusBadRequest, response.NewResponseError(http.StatusBadRequest, response.MsgFailed, err.Error()))
}

if err := request.Validate(); err != nil {
return c.JSON(http.StatusBadRequest, response.NewResponseError(http.StatusBadRequest, response.MsgFailed, err.Error()))
}

authData, httpCode, err := h.uc.Login(c.Request().Context(), request)
if err != nil {
return c.JSON(httpCode, response.NewResponseError(httpCode, response.MsgFailed, err.Error()))
}

return c.JSON(httpCode, response.NewResponse(httpCode, response.MsgSuccess, authData))
}

func (h *handlers) CreateUser(c echo.Context) error {
var (
ctx, cancel = context.WithTimeout(c.Request().Context(), time.Duration(30*time.Second))
payload dtos.CreateUserPayload
payload dtos.CreateUserRequest
)
defer cancel()

if err := c.Bind(&payload); err != nil {
h.log.Z().Err(err).Msg("[handlers]CreateUser.Bind")

return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
return c.JSON(http.StatusBadRequest, response.NewResponseError(
http.StatusBadRequest,
dtos.MsgFailed,
response.MsgFailed,
err.Error()),
)
}

if err := payload.Validate(); err != nil {
h.log.Z().Err(err).Msg("[handlers]CreateUser.Validate")

return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
return c.JSON(http.StatusBadRequest, response.NewResponseError(
http.StatusBadRequest,
dtos.MsgFailed,
response.MsgFailed,
err.Error()),
)
}

argsCreateUser := entities.CreateUser{
FullName: payload.FullName,
PhoneNumber: payload.PhoneNumber,
}

createdData, err := h.uc.CreateUser(ctx, argsCreateUser)
userID, httpCode, err := h.uc.Create(ctx, payload)
if err != nil {
return c.JSON(http.StatusInternalServerError, dtos.NewResponseError(
http.StatusInternalServerError,
dtos.MsgFailed,
return c.JSON(httpCode, response.NewResponseError(
httpCode,
response.MsgFailed,
err.Error()),
)
}

return c.JSON(http.StatusOK, dtos.NewResponse(http.StatusOK, dtos.MsgSuccess, createdData))
return c.JSON(http.StatusOK, response.NewResponse(http.StatusOK, response.MsgSuccess, map[string]int64{"id": userID}))
}

func (h *handlers) UserDetail(c echo.Context) error {
ctx, cancel := context.WithTimeout(c.Request().Context(), time.Duration(30*time.Second))
defer cancel()

userID := c.Get("identity").(*middleware.CustomClaims).UserID

data, code, err := h.uc.Detail(ctx, userID)
if err != nil {
return c.JSON(code, response.NewResponseError(code, response.MsgFailed, err.Error()))
}

return c.JSON(code, response.NewResponse(code, response.MsgSuccess, data))
}

func (h *handlers) UpdateUser(c echo.Context) error {
var (
ctx, cancel = context.WithTimeout(c.Request().Context(), time.Duration(30*time.Second))
payload dtos.UpdateUserPayload
request dtos.UpdateUserRequest
)
defer cancel()

h.log.Z().Info().Msg("[handlers]UpdateUser")

userID, err := strconv.ParseInt(c.Param("id"), 10, 64)
if err != nil {
h.log.Z().Err(err).Msg("[handlers]UpdateUser.ParseParam")

return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
http.StatusBadRequest,
dtos.MsgFailed,
err.Error()),
)
}
request.UserID = c.Get("identity").(entities.Identity).UserID

if err := c.Bind(&payload); err != nil {
if err := c.Bind(&request); err != nil {
h.log.Z().Err(err).Msg("[handlers]UpdateUser.Bind")

return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
http.StatusBadRequest,
dtos.MsgFailed,
err.Error()),
)
return c.JSON(http.StatusBadRequest, response.NewResponseError(http.StatusBadRequest, response.MsgFailed, err.Error()))
}

if err := payload.Validate(); err != nil {
if err := request.Validate(); err != nil {
h.log.Z().Err(err).Msg("[handlers]UpdateUser.Validate")

return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
http.StatusBadRequest,
dtos.MsgFailed,
err.Error()),
)
return c.JSON(http.StatusBadRequest, response.NewResponseError(http.StatusBadRequest, response.MsgFailed, err.Error()))
}

argsUpdateUser := entities.UpdateUsers{
UserID: userID,
Fullname: payload.Fullname,
PhoneNumber: payload.PhoneNumber,
UserType: payload.UserType,
}

err = h.uc.UpdateUser(ctx, argsUpdateUser)
if err != nil {
return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
http.StatusInternalServerError,
dtos.MsgFailed,
err.Error()),
)
if err := h.uc.PartialUpdate(ctx, request); err != nil {
return c.JSON(http.StatusBadRequest, response.NewResponseError(http.StatusInternalServerError, response.MsgFailed, err.Error()))
}

return c.JSON(http.StatusOK, dtos.NewResponse(http.StatusOK, dtos.MsgSuccess, nil))
return c.JSON(http.StatusOK, response.NewResponse(http.StatusOK, response.MsgSuccess, nil))
}

func (h *handlers) UpdateUserStatus(c echo.Context) error {
Expand All @@ -154,9 +157,9 @@ func (h *handlers) UpdateUserStatus(c echo.Context) error {
if err != nil {
h.log.Z().Err(err).Msg("[handlers]UpdateUser.ParseParam")

return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
return c.JSON(http.StatusBadRequest, response.NewResponseError(
http.StatusBadRequest,
dtos.MsgFailed,
response.MsgFailed,
err.Error()),
)
}
Expand All @@ -167,26 +170,18 @@ func (h *handlers) UpdateUserStatus(c echo.Context) error {
case BooleanTextTrue:
payload.IsActive = true
default:
return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
http.StatusBadRequest,
dtos.MsgFailed,
ErrInvalidIsActive.Error()),
)
return c.JSON(http.StatusBadRequest, response.NewResponseError(http.StatusBadRequest, response.MsgFailed, ErrInvalidIsActive.Error()))
}

argsUpdateUserStatus := entities.UpdateUserStatus{
UserID: userID,
IsActive: payload.IsActive,
}

err = h.uc.UpdateUserStatus(ctx, argsUpdateUserStatus)
err = h.uc.UpdateStatus(ctx, argsUpdateUserStatus)
if err != nil {
return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
http.StatusInternalServerError,
dtos.MsgFailed,
err.Error()),
)
return c.JSON(http.StatusBadRequest, response.NewResponseError(http.StatusInternalServerError, response.MsgFailed, err.Error()))
}

return c.JSON(http.StatusOK, dtos.NewResponse(http.StatusOK, dtos.MsgSuccess, nil))
return c.JSON(http.StatusOK, response.NewResponse(http.StatusOK, response.MsgSuccess, nil))
}
2 changes: 2 additions & 0 deletions internal/users/delivery/http/v1/route.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ import (
func UserPrivateRoute(version *echo.Group, h Handlers, cfg config.Config) {
users := version.Group("users")
users.POST("", h.CreateUser)
users.POST("/login", h.Login)
users.GET("/detail", h.UserDetail, middleware.AuthorizeJWT(cfg))
users.PATCH("/:id", h.UpdateUser, middleware.AuthorizeJWT(cfg))
users.PUT("/:id", h.UpdateUserStatus, middleware.AuthorizeJWT(cfg))
}
36 changes: 0 additions & 36 deletions internal/users/dtos/update_users.go

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,11 @@ import (
"github.com/invopop/validation"
)

type CreateUserPayload struct {
type CreateUserRequest struct {
FullName string `json:"fullname"`
PhoneNumber string `json:"phone_number"`
Email string `json:"email"`
Password string `json:"password"`
}

type CreateUserResponse struct {
Expand All @@ -15,9 +17,11 @@ type CreateUserResponse struct {
ExpiredAt int64 `json:"expired_at"`
}

func (cup CreateUserPayload) Validate() error {
func (cup CreateUserRequest) Validate() error {
return validation.ValidateStruct(&cup,
validation.Field(&cup.FullName, validation.Required, validation.Length(0, 50)),
validation.Field(&cup.PhoneNumber, validation.Required, validation.Length(0, 13)),
validation.Field(&cup.Email, validation.Required),
validation.Field(&cup.Password, validation.Required),
)
}
16 changes: 16 additions & 0 deletions internal/users/dtos/user_detail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package dtos

import "time"

type (
UserDetailResponse struct {
UserID int64 `json:"id"`
Email string `json:"email"`
Fullname string `json:"fullname"`
PhoneNumber string `json:"phone_number"`
UserType string `json:"user_type"`
IsActive bool `json:"is_active"`
CreatedAt time.Time `json:"created_at"`
CreatedBy string `json:"created_by"`
}
)
24 changes: 24 additions & 0 deletions internal/users/dtos/user_login.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package dtos

import (
"github.com/invopop/validation"
)

type (
UserLoginRequest struct {
Email string `json:"email"`
Password string `json:"password"`
}

UserLoginResponse struct {
AccessToken string `json:"access_token"`
ExpiredAt int64 `json:"expired_at"`
}
)

func (ulr UserLoginRequest) Validate() error {
return validation.ValidateStruct(&ulr,
validation.Field(&ulr.Email, validation.Required),
validation.Field(&ulr.Password, validation.Required),
)
}
Loading

0 comments on commit dc655e6

Please sign in to comment.