Skip to content

Commit 0a3a63b

Browse files
Merge pull request #13 from DoWithLogic/feat/add-login-and-detail-endpoint
feat: login and detail endpoint
2 parents 4c9433c + dc655e6 commit 0a3a63b

33 files changed

+550
-207
lines changed

config/config-local.yaml.example

+5-1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,10 @@ server:
1111
database:
1212
host: localhost
1313
port: 3306
14-
name: golang-clean-architecture
14+
name: users
1515
user: mysql
1616
password: pwd
17+
18+
authentication:
19+
key: DoWithLogic!@#
20+
secret_key: s3cr#tK3y!@#

config/config.go

+3-1
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@ type (
3434
}
3535

3636
AuthenticationConfig struct {
37-
Key string `mapstructure:"key"`
37+
Key string `mapstructure:"key"`
38+
SecretKey string `mapstructure:"secret_key"`
39+
SaltKey string `mapstructure:"salt_key"`
3840
}
3941
)
4042

database/mysql/migration/20230924142159_add_user_table.sql

+4-2
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,18 @@
22
-- +goose StatementBegin
33
CREATE TABLE `users` (
44
`id` int NOT NULL AUTO_INCREMENT,
5+
`email` varchar(255) NOT NULL,
6+
`password` text NOT NULL,
57
`fullname` varchar(255) NOT NULL,
68
`phone_number` varchar(15) NOT NULL,
79
`user_type` varchar(50) NOT NULL,
810
`is_active` tinyint(1) NOT NULL,
911
`created_at` timestamp NOT NULL,
1012
`created_by` varchar(255) NOT NULL,
11-
`updated_at` timestamp NULL DEFAULT NULL,
13+
`updated_at` timestamp DEFAULT NULL,
1214
`updated_by` varchar(255) DEFAULT NULL,
1315
PRIMARY KEY (`id`)
14-
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
16+
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
1517

1618
-- +goose StatementEnd
1719

internal/users/delivery/http/v1/handler.go

+64-69
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,17 @@ import (
1010
"github.com/DoWithLogic/golang-clean-architecture/internal/users/dtos"
1111
"github.com/DoWithLogic/golang-clean-architecture/internal/users/entities"
1212
usecases "github.com/DoWithLogic/golang-clean-architecture/internal/users/usecase"
13+
"github.com/DoWithLogic/golang-clean-architecture/pkg/middleware"
1314
"github.com/DoWithLogic/golang-clean-architecture/pkg/otel/zerolog"
15+
"github.com/DoWithLogic/golang-clean-architecture/pkg/utils/response"
1416
"github.com/labstack/echo/v4"
1517
)
1618

1719
type (
1820
Handlers interface {
21+
Login(c echo.Context) error
1922
CreateUser(c echo.Context) error
23+
UserDetail(c echo.Context) error
2024
UpdateUser(c echo.Context) error
2125
UpdateUserStatus(c echo.Context) error
2226
}
@@ -40,107 +44,106 @@ func NewHandlers(uc usecases.Usecase, log *zerolog.Logger) Handlers {
4044
return &handlers{uc, log}
4145
}
4246

47+
func (h *handlers) Login(c echo.Context) error {
48+
var (
49+
request dtos.UserLoginRequest
50+
)
51+
52+
if err := c.Bind(&request); err != nil {
53+
return c.JSON(http.StatusBadRequest, response.NewResponseError(http.StatusBadRequest, response.MsgFailed, err.Error()))
54+
}
55+
56+
if err := request.Validate(); err != nil {
57+
return c.JSON(http.StatusBadRequest, response.NewResponseError(http.StatusBadRequest, response.MsgFailed, err.Error()))
58+
}
59+
60+
authData, httpCode, err := h.uc.Login(c.Request().Context(), request)
61+
if err != nil {
62+
return c.JSON(httpCode, response.NewResponseError(httpCode, response.MsgFailed, err.Error()))
63+
}
64+
65+
return c.JSON(httpCode, response.NewResponse(httpCode, response.MsgSuccess, authData))
66+
}
67+
4368
func (h *handlers) CreateUser(c echo.Context) error {
4469
var (
4570
ctx, cancel = context.WithTimeout(c.Request().Context(), time.Duration(30*time.Second))
46-
payload dtos.CreateUserPayload
71+
payload dtos.CreateUserRequest
4772
)
4873
defer cancel()
4974

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

53-
return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
78+
return c.JSON(http.StatusBadRequest, response.NewResponseError(
5479
http.StatusBadRequest,
55-
dtos.MsgFailed,
80+
response.MsgFailed,
5681
err.Error()),
5782
)
5883
}
5984

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

63-
return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
88+
return c.JSON(http.StatusBadRequest, response.NewResponseError(
6489
http.StatusBadRequest,
65-
dtos.MsgFailed,
90+
response.MsgFailed,
6691
err.Error()),
6792
)
6893
}
6994

70-
argsCreateUser := entities.CreateUser{
71-
FullName: payload.FullName,
72-
PhoneNumber: payload.PhoneNumber,
73-
}
74-
75-
createdData, err := h.uc.CreateUser(ctx, argsCreateUser)
95+
userID, httpCode, err := h.uc.Create(ctx, payload)
7696
if err != nil {
77-
return c.JSON(http.StatusInternalServerError, dtos.NewResponseError(
78-
http.StatusInternalServerError,
79-
dtos.MsgFailed,
97+
return c.JSON(httpCode, response.NewResponseError(
98+
httpCode,
99+
response.MsgFailed,
80100
err.Error()),
81101
)
82102
}
83103

84-
return c.JSON(http.StatusOK, dtos.NewResponse(http.StatusOK, dtos.MsgSuccess, createdData))
104+
return c.JSON(http.StatusOK, response.NewResponse(http.StatusOK, response.MsgSuccess, map[string]int64{"id": userID}))
105+
}
106+
107+
func (h *handlers) UserDetail(c echo.Context) error {
108+
ctx, cancel := context.WithTimeout(c.Request().Context(), time.Duration(30*time.Second))
109+
defer cancel()
110+
111+
userID := c.Get("identity").(*middleware.CustomClaims).UserID
112+
113+
data, code, err := h.uc.Detail(ctx, userID)
114+
if err != nil {
115+
return c.JSON(code, response.NewResponseError(code, response.MsgFailed, err.Error()))
116+
}
117+
118+
return c.JSON(code, response.NewResponse(code, response.MsgSuccess, data))
85119
}
86120

87121
func (h *handlers) UpdateUser(c echo.Context) error {
88122
var (
89123
ctx, cancel = context.WithTimeout(c.Request().Context(), time.Duration(30*time.Second))
90-
payload dtos.UpdateUserPayload
124+
request dtos.UpdateUserRequest
91125
)
92126
defer cancel()
93127

94-
h.log.Z().Info().Msg("[handlers]UpdateUser")
95-
96-
userID, err := strconv.ParseInt(c.Param("id"), 10, 64)
97-
if err != nil {
98-
h.log.Z().Err(err).Msg("[handlers]UpdateUser.ParseParam")
99-
100-
return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
101-
http.StatusBadRequest,
102-
dtos.MsgFailed,
103-
err.Error()),
104-
)
105-
}
128+
request.UserID = c.Get("identity").(entities.Identity).UserID
106129

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

110-
return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
111-
http.StatusBadRequest,
112-
dtos.MsgFailed,
113-
err.Error()),
114-
)
133+
return c.JSON(http.StatusBadRequest, response.NewResponseError(http.StatusBadRequest, response.MsgFailed, err.Error()))
115134
}
116135

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

120-
return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
121-
http.StatusBadRequest,
122-
dtos.MsgFailed,
123-
err.Error()),
124-
)
139+
return c.JSON(http.StatusBadRequest, response.NewResponseError(http.StatusBadRequest, response.MsgFailed, err.Error()))
125140
}
126141

127-
argsUpdateUser := entities.UpdateUsers{
128-
UserID: userID,
129-
Fullname: payload.Fullname,
130-
PhoneNumber: payload.PhoneNumber,
131-
UserType: payload.UserType,
132-
}
133-
134-
err = h.uc.UpdateUser(ctx, argsUpdateUser)
135-
if err != nil {
136-
return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
137-
http.StatusInternalServerError,
138-
dtos.MsgFailed,
139-
err.Error()),
140-
)
142+
if err := h.uc.PartialUpdate(ctx, request); err != nil {
143+
return c.JSON(http.StatusBadRequest, response.NewResponseError(http.StatusInternalServerError, response.MsgFailed, err.Error()))
141144
}
142145

143-
return c.JSON(http.StatusOK, dtos.NewResponse(http.StatusOK, dtos.MsgSuccess, nil))
146+
return c.JSON(http.StatusOK, response.NewResponse(http.StatusOK, response.MsgSuccess, nil))
144147
}
145148

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

157-
return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
160+
return c.JSON(http.StatusBadRequest, response.NewResponseError(
158161
http.StatusBadRequest,
159-
dtos.MsgFailed,
162+
response.MsgFailed,
160163
err.Error()),
161164
)
162165
}
@@ -167,26 +170,18 @@ func (h *handlers) UpdateUserStatus(c echo.Context) error {
167170
case BooleanTextTrue:
168171
payload.IsActive = true
169172
default:
170-
return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
171-
http.StatusBadRequest,
172-
dtos.MsgFailed,
173-
ErrInvalidIsActive.Error()),
174-
)
173+
return c.JSON(http.StatusBadRequest, response.NewResponseError(http.StatusBadRequest, response.MsgFailed, ErrInvalidIsActive.Error()))
175174
}
176175

177176
argsUpdateUserStatus := entities.UpdateUserStatus{
178177
UserID: userID,
179178
IsActive: payload.IsActive,
180179
}
181180

182-
err = h.uc.UpdateUserStatus(ctx, argsUpdateUserStatus)
181+
err = h.uc.UpdateStatus(ctx, argsUpdateUserStatus)
183182
if err != nil {
184-
return c.JSON(http.StatusBadRequest, dtos.NewResponseError(
185-
http.StatusInternalServerError,
186-
dtos.MsgFailed,
187-
err.Error()),
188-
)
183+
return c.JSON(http.StatusBadRequest, response.NewResponseError(http.StatusInternalServerError, response.MsgFailed, err.Error()))
189184
}
190185

191-
return c.JSON(http.StatusOK, dtos.NewResponse(http.StatusOK, dtos.MsgSuccess, nil))
186+
return c.JSON(http.StatusOK, response.NewResponse(http.StatusOK, response.MsgSuccess, nil))
192187
}

internal/users/delivery/http/v1/route.go

+2
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@ import (
99
func UserPrivateRoute(version *echo.Group, h Handlers, cfg config.Config) {
1010
users := version.Group("users")
1111
users.POST("", h.CreateUser)
12+
users.POST("/login", h.Login)
13+
users.GET("/detail", h.UserDetail, middleware.AuthorizeJWT(cfg))
1214
users.PATCH("/:id", h.UpdateUser, middleware.AuthorizeJWT(cfg))
1315
users.PUT("/:id", h.UpdateUserStatus, middleware.AuthorizeJWT(cfg))
1416
}

internal/users/dtos/update_users.go

-36
This file was deleted.

internal/users/dtos/create_users.go renamed to internal/users/dtos/user_create.go

+6-2
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,11 @@ import (
44
"github.com/invopop/validation"
55
)
66

7-
type CreateUserPayload struct {
7+
type CreateUserRequest struct {
88
FullName string `json:"fullname"`
99
PhoneNumber string `json:"phone_number"`
10+
Email string `json:"email"`
11+
Password string `json:"password"`
1012
}
1113

1214
type CreateUserResponse struct {
@@ -15,9 +17,11 @@ type CreateUserResponse struct {
1517
ExpiredAt int64 `json:"expired_at"`
1618
}
1719

18-
func (cup CreateUserPayload) Validate() error {
20+
func (cup CreateUserRequest) Validate() error {
1921
return validation.ValidateStruct(&cup,
2022
validation.Field(&cup.FullName, validation.Required, validation.Length(0, 50)),
2123
validation.Field(&cup.PhoneNumber, validation.Required, validation.Length(0, 13)),
24+
validation.Field(&cup.Email, validation.Required),
25+
validation.Field(&cup.Password, validation.Required),
2226
)
2327
}

internal/users/dtos/user_detail.go

+16
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package dtos
2+
3+
import "time"
4+
5+
type (
6+
UserDetailResponse struct {
7+
UserID int64 `json:"id"`
8+
Email string `json:"email"`
9+
Fullname string `json:"fullname"`
10+
PhoneNumber string `json:"phone_number"`
11+
UserType string `json:"user_type"`
12+
IsActive bool `json:"is_active"`
13+
CreatedAt time.Time `json:"created_at"`
14+
CreatedBy string `json:"created_by"`
15+
}
16+
)

internal/users/dtos/user_login.go

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package dtos
2+
3+
import (
4+
"github.com/invopop/validation"
5+
)
6+
7+
type (
8+
UserLoginRequest struct {
9+
Email string `json:"email"`
10+
Password string `json:"password"`
11+
}
12+
13+
UserLoginResponse struct {
14+
AccessToken string `json:"access_token"`
15+
ExpiredAt int64 `json:"expired_at"`
16+
}
17+
)
18+
19+
func (ulr UserLoginRequest) Validate() error {
20+
return validation.ValidateStruct(&ulr,
21+
validation.Field(&ulr.Email, validation.Required),
22+
validation.Field(&ulr.Password, validation.Required),
23+
)
24+
}

0 commit comments

Comments
 (0)