From 3fd2359c523fb808e6e652a3876c2ae7f8d49ce2 Mon Sep 17 00:00:00 2001 From: Aldino Kemal Date: Sat, 3 Dec 2022 20:35:09 +0700 Subject: [PATCH] feat: introduce error package (#40) * feat: introduce error and add test validation * feat: update UI --- src/cmd/root.go | 7 +- src/config/settings.go | 2 +- src/internal/rest/{app_rest.go => app.go} | 26 +- src/internal/rest/middleware/recovery.go | 22 +- src/internal/rest/{send_rest.go => send.go} | 50 ++- src/internal/rest/{user_rest.go => user.go} | 21 +- src/pkg/error/auth_error.go | 19 + src/pkg/error/generic_error.go | 27 ++ src/pkg/error/validation_error.go | 19 + src/pkg/error/whatsapp_error.go | 24 ++ src/{ => pkg}/utils/general.go | 12 + src/{ => pkg}/utils/response.go | 3 +- src/{utils => pkg/whatsapp}/whatsapp.go | 30 +- src/services/{app_service.go => app.go} | 0 src/services/{send_service.go => send.go} | 100 +++-- src/services/{user_service.go => user.go} | 28 +- src/utils/errors.go | 29 -- src/validations/send_validation.go | 82 ++-- src/validations/send_validation_test.go | 431 +++++++++++++++++++- src/validations/user_validation.go | 18 +- src/validations/user_validation_test.go | 74 ++++ src/views/index.html | 20 +- 22 files changed, 800 insertions(+), 244 deletions(-) rename src/internal/rest/{app_rest.go => app.go} (65%) rename src/internal/rest/{send_rest.go => send.go} (81%) rename src/internal/rest/{user_rest.go => user.go} (83%) create mode 100644 src/pkg/error/auth_error.go create mode 100644 src/pkg/error/generic_error.go create mode 100644 src/pkg/error/validation_error.go create mode 100644 src/pkg/error/whatsapp_error.go rename src/{ => pkg}/utils/general.go (72%) rename src/{ => pkg}/utils/response.go (67%) rename src/{utils => pkg/whatsapp}/whatsapp.go (90%) rename src/services/{app_service.go => app.go} (100%) rename src/services/{send_service.go => send.go} (83%) rename src/services/{user_service.go => user.go} (80%) delete mode 100644 src/utils/errors.go create mode 100644 src/validations/user_validation_test.go diff --git a/src/cmd/root.go b/src/cmd/root.go index fda8b78..e22607f 100644 --- a/src/cmd/root.go +++ b/src/cmd/root.go @@ -7,8 +7,9 @@ import ( "github.com/aldinokemal/go-whatsapp-web-multidevice/internal/rest/helpers" "github.com/aldinokemal/go-whatsapp-web-multidevice/internal/rest/middleware" "github.com/aldinokemal/go-whatsapp-web-multidevice/internal/websocket" + "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" + "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" "github.com/aldinokemal/go-whatsapp-web-multidevice/services" - "github.com/aldinokemal/go-whatsapp-web-multidevice/utils" "github.com/dustin/go-humanize" "github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2/middleware/basicauth" @@ -90,8 +91,8 @@ func runRest(_ *cobra.Command, _ []string) { } } - db := utils.InitWaDB() - cli := utils.InitWaCLI(db) + db := whatsapp.InitWaDB() + cli := whatsapp.InitWaCLI(db) // Service appService := services.NewAppService(cli, db) diff --git a/src/config/settings.go b/src/config/settings.go index d9e45d7..5e9989d 100644 --- a/src/config/settings.go +++ b/src/config/settings.go @@ -6,7 +6,7 @@ import ( ) var ( - AppVersion = "v4.0.0" + AppVersion = "v4.2.0" AppPort = "3000" AppDebug = false AppOs = fmt.Sprintf("AldinoKemal") diff --git a/src/internal/rest/app_rest.go b/src/internal/rest/app.go similarity index 65% rename from src/internal/rest/app_rest.go rename to src/internal/rest/app.go index 8164960..d400bbf 100644 --- a/src/internal/rest/app_rest.go +++ b/src/internal/rest/app.go @@ -3,7 +3,7 @@ package rest import ( "fmt" domainApp "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app" - "github.com/aldinokemal/go-whatsapp-web-multidevice/utils" + "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" "github.com/gofiber/fiber/v2" ) @@ -21,12 +21,12 @@ func InitRestApp(app *fiber.App, service domainApp.IAppService) App { return App{Service: service} } -func (controller *App) Login(c *fiber.Ctx) error { - response, err := controller.Service.Login(c.UserContext()) +func (handler *App) Login(c *fiber.Ctx) error { + response, err := handler.Service.Login(c.UserContext()) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: "Success", Results: map[string]any{ "qr_link": fmt.Sprintf("%s://%s/%s", c.Protocol(), c.Hostname(), response.ImagePath), @@ -35,34 +35,34 @@ func (controller *App) Login(c *fiber.Ctx) error { }) } -func (controller *App) Logout(c *fiber.Ctx) error { - err := controller.Service.Logout(c.UserContext()) +func (handler *App) Logout(c *fiber.Ctx) error { + err := handler.Service.Logout(c.UserContext()) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: "Success logout", Results: nil, }) } -func (controller *App) Reconnect(c *fiber.Ctx) error { - err := controller.Service.Reconnect(c.UserContext()) +func (handler *App) Reconnect(c *fiber.Ctx) error { + err := handler.Service.Reconnect(c.UserContext()) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: "Reconnect success", Results: nil, }) } -func (controller *App) Devices(c *fiber.Ctx) error { - devices, err := controller.Service.FetchDevices(c.UserContext()) +func (handler *App) Devices(c *fiber.Ctx) error { + devices, err := handler.Service.FetchDevices(c.UserContext()) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: "Fetch device success", Results: devices, }) diff --git a/src/internal/rest/middleware/recovery.go b/src/internal/rest/middleware/recovery.go index eb9a632..54cf4c9 100644 --- a/src/internal/rest/middleware/recovery.go +++ b/src/internal/rest/middleware/recovery.go @@ -2,7 +2,8 @@ package middleware import ( "fmt" - "github.com/aldinokemal/go-whatsapp-web-multidevice/utils" + pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" + "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" "github.com/gofiber/fiber/v2" ) @@ -12,22 +13,17 @@ func Recovery() fiber.Handler { err := recover() if err != nil { var res utils.ResponseData - res.Code = 500 + res.Status = 500 res.Message = fmt.Sprintf("%s", err) - errValidation, okValidation := err.(utils.ValidationError) - if okValidation { - res.Code = 400 - res.Message = errValidation.Message + errValidation, isValidationError := err.(pkgError.GenericError) + if isValidationError { + res.Status = errValidation.StatusCode() + res.Code = errValidation.ErrCode() + res.Message = errValidation.Error() } - errAuth, okAuth := err.(utils.AuthError) - if okAuth { - res.Code = 401 - res.Message = errAuth.Message - } - - _ = ctx.Status(res.Code).JSON(res) + _ = ctx.Status(res.Status).JSON(res) } }() diff --git a/src/internal/rest/send_rest.go b/src/internal/rest/send.go similarity index 81% rename from src/internal/rest/send_rest.go rename to src/internal/rest/send.go index f381b32..df2c459 100644 --- a/src/internal/rest/send_rest.go +++ b/src/internal/rest/send.go @@ -2,7 +2,8 @@ package rest import ( domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" - "github.com/aldinokemal/go-whatsapp-web-multidevice/utils" + "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" + "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" "github.com/aldinokemal/go-whatsapp-web-multidevice/validations" "github.com/gofiber/fiber/v2" ) @@ -30,14 +31,15 @@ func (controller *Send) SendText(c *fiber.Ctx) error { utils.PanicIfNeeded(err) // add validation send message - validations.ValidateSendMessage(request) - utils.SanitizePhone(&request.Phone) + whatsapp.SanitizePhone(&request.Phone) + err = validations.ValidateSendMessage(request) + utils.PanicIfNeeded(err) response, err := controller.Service.SendText(c.UserContext(), request) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: response.Status, Results: response, }) @@ -56,14 +58,15 @@ func (controller *Send) SendImage(c *fiber.Ctx) error { request.Image = file //add validation send image - validations.ValidateSendImage(request) - utils.SanitizePhone(&request.Phone) + whatsapp.SanitizePhone(&request.Phone) + err = validations.ValidateSendImage(request) + utils.PanicIfNeeded(err) response, err := controller.Service.SendImage(c.UserContext(), request) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: response.Status, Results: response, }) @@ -80,14 +83,15 @@ func (controller *Send) SendFile(c *fiber.Ctx) error { request.File = file //add validation send image - validations.ValidateSendFile(request) - utils.SanitizePhone(&request.Phone) + whatsapp.SanitizePhone(&request.Phone) + err = validations.ValidateSendFile(request) + utils.PanicIfNeeded(err) response, err := controller.Service.SendFile(c.UserContext(), request) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: response.Status, Results: response, }) @@ -104,14 +108,15 @@ func (controller *Send) SendVideo(c *fiber.Ctx) error { request.Video = video //add validation send image - validations.ValidateSendVideo(request) - utils.SanitizePhone(&request.Phone) + whatsapp.SanitizePhone(&request.Phone) + err = validations.ValidateSendVideo(request) + utils.PanicIfNeeded(err) response, err := controller.Service.SendVideo(c.UserContext(), request) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: response.Status, Results: response, }) @@ -123,14 +128,15 @@ func (controller *Send) SendContact(c *fiber.Ctx) error { utils.PanicIfNeeded(err) // add validation send contect - validations.ValidateSendContact(request) - utils.SanitizePhone(&request.Phone) + whatsapp.SanitizePhone(&request.Phone) + err = validations.ValidateSendContact(request) + utils.PanicIfNeeded(err) response, err := controller.Service.SendContact(c.UserContext(), request) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: response.Status, Results: response, }) @@ -141,15 +147,15 @@ func (controller *Send) SendLink(c *fiber.Ctx) error { err := c.BodyParser(&request) utils.PanicIfNeeded(err) + whatsapp.SanitizePhone(&request.Phone) err = validations.ValidateSendLink(request) utils.PanicIfNeeded(err) - utils.SanitizePhone(&request.Phone) response, err := controller.Service.SendLink(c.UserContext(), request) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: response.Status, Results: response, }) @@ -163,13 +169,13 @@ func (controller *Send) RevokeMessage(c *fiber.Ctx) error { err = validations.ValidateRevokeMessage(request) utils.PanicIfNeeded(err) - utils.SanitizePhone(&request.Phone) + whatsapp.SanitizePhone(&request.Phone) response, err := controller.Service.Revoke(c.UserContext(), request) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: response.Status, Results: response, }) @@ -183,13 +189,13 @@ func (controller *Send) UpdateMessage(c *fiber.Ctx) error { err = validations.ValidateUpdateMessage(request) utils.PanicIfNeeded(err) - utils.SanitizePhone(&request.Phone) + whatsapp.SanitizePhone(&request.Phone) response, err := controller.Service.UpdateMessage(c.UserContext(), request) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: response.Status, Results: response, }) diff --git a/src/internal/rest/user_rest.go b/src/internal/rest/user.go similarity index 83% rename from src/internal/rest/user_rest.go rename to src/internal/rest/user.go index 882072f..fb121bf 100644 --- a/src/internal/rest/user_rest.go +++ b/src/internal/rest/user.go @@ -2,7 +2,8 @@ package rest import ( domainUser "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/user" - "github.com/aldinokemal/go-whatsapp-web-multidevice/utils" + "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" + "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" "github.com/aldinokemal/go-whatsapp-web-multidevice/validations" "github.com/gofiber/fiber/v2" ) @@ -34,14 +35,15 @@ func (controller *User) UserInfo(c *fiber.Ctx) error { utils.PanicIfNeeded(err) // add validation send message - validations.ValidateUserInfo(request) - utils.SanitizePhone(&request.Phone) + whatsapp.SanitizePhone(&request.Phone) + err = validations.ValidateUserInfo(request) + utils.PanicIfNeeded(err) response, err := controller.Service.Info(c.Context(), request) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: "Success", Results: response.Data[0], }) @@ -53,14 +55,15 @@ func (controller *User) UserAvatar(c *fiber.Ctx) error { utils.PanicIfNeeded(err) // add validation send message - validations.ValidateUserAvatar(request) - utils.SanitizePhone(&request.Phone) + whatsapp.SanitizePhone(&request.Phone) + err = validations.ValidateUserAvatar(request) + utils.PanicIfNeeded(err) response, err := controller.Service.Avatar(c.Context(), request) utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: "Success get avatar", Results: response, }) @@ -71,7 +74,7 @@ func (controller *User) UserMyPrivacySetting(c *fiber.Ctx) error { utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: "Success get privacy", Results: response, }) @@ -82,7 +85,7 @@ func (controller *User) UserMyListGroups(c *fiber.Ctx) error { utils.PanicIfNeeded(err) return c.JSON(utils.ResponseData{ - Code: 200, + Status: 200, Message: "Success get list groups", Results: response, }) diff --git a/src/pkg/error/auth_error.go b/src/pkg/error/auth_error.go new file mode 100644 index 0000000..72ef01e --- /dev/null +++ b/src/pkg/error/auth_error.go @@ -0,0 +1,19 @@ +package error + +import "net/http" + +type AuthError string + +func (err AuthError) Error() string { + return string(err) +} + +// ErrCode will return the error code based on the error data type +func (err AuthError) ErrCode() string { + return "AUTHENTICATION_ERROR" +} + +// StatusCode will return the HTTP status code based on the error data type +func (err AuthError) StatusCode() int { + return http.StatusUnauthorized +} diff --git a/src/pkg/error/generic_error.go b/src/pkg/error/generic_error.go new file mode 100644 index 0000000..0a0ab8d --- /dev/null +++ b/src/pkg/error/generic_error.go @@ -0,0 +1,27 @@ +package error + +import "net/http" + +// GenericError represent as the contract of generic error +type GenericError interface { + Error() string + ErrCode() string + StatusCode() int +} + +type InternalServerError string + +// Error for complying the error interface +func (e InternalServerError) Error() string { + return string(e) +} + +// ErrCode will return the error code based on the error data type +func (e InternalServerError) ErrCode() string { + return "INTERNAL_SERVER_ERROR" +} + +// StatusCode will return the HTTP status code based on the error data type +func (e InternalServerError) StatusCode() int { + return http.StatusInternalServerError +} diff --git a/src/pkg/error/validation_error.go b/src/pkg/error/validation_error.go new file mode 100644 index 0000000..ddb44d1 --- /dev/null +++ b/src/pkg/error/validation_error.go @@ -0,0 +1,19 @@ +package error + +import "net/http" + +type ValidationError string + +func (err ValidationError) Error() string { + return string(err) +} + +// ErrCode will return the error code based on the error data type +func (err ValidationError) ErrCode() string { + return "VALIDATION_ERROR" +} + +// StatusCode will return the HTTP status code based on the error data type +func (err ValidationError) StatusCode() int { + return http.StatusBadRequest +} diff --git a/src/pkg/error/whatsapp_error.go b/src/pkg/error/whatsapp_error.go new file mode 100644 index 0000000..3d9ff78 --- /dev/null +++ b/src/pkg/error/whatsapp_error.go @@ -0,0 +1,24 @@ +package error + +import "net/http" + +type InvalidJID string + +// Error for complying the error interface +func (e InvalidJID) Error() string { + return string(e) +} + +// ErrCode will return the error code based on the error data type +func (e InvalidJID) ErrCode() string { + return "INVALID_JID" +} + +// StatusCode will return the HTTP status code based on the error data type +func (e InvalidJID) StatusCode() int { + return http.StatusBadRequest +} + +const ( + ErrInvalidJID = InvalidJID("your JID is invalid") +) diff --git a/src/utils/general.go b/src/pkg/utils/general.go similarity index 72% rename from src/utils/general.go rename to src/pkg/utils/general.go index f1c07d3..133ac22 100644 --- a/src/utils/general.go +++ b/src/pkg/utils/general.go @@ -1,6 +1,7 @@ package utils import ( + "fmt" "os" "path/filepath" "time" @@ -34,3 +35,14 @@ func CreateFolder(folderPath ...string) error { } return nil } + +// PanicIfNeeded is panic if error is not nil +func PanicIfNeeded(err any, message ...string) { + if err != nil { + if fmt.Sprintf("%s", err) == "record not found" && len(message) > 0 { + panic(message[0]) + } else { + panic(err) + } + } +} diff --git a/src/utils/response.go b/src/pkg/utils/response.go similarity index 67% rename from src/utils/response.go rename to src/pkg/utils/response.go index 29a674f..74331b9 100644 --- a/src/utils/response.go +++ b/src/pkg/utils/response.go @@ -1,7 +1,8 @@ package utils type ResponseData struct { - Code int `json:"code"` + Status int + Code string `json:"code,omitempty"` Message string `json:"message"` Results any `json:"results"` } diff --git a/src/utils/whatsapp.go b/src/pkg/whatsapp/whatsapp.go similarity index 90% rename from src/utils/whatsapp.go rename to src/pkg/whatsapp/whatsapp.go index 4ba836b..f271995 100644 --- a/src/utils/whatsapp.go +++ b/src/pkg/whatsapp/whatsapp.go @@ -1,4 +1,4 @@ -package utils +package whatsapp import ( "bytes" @@ -7,6 +7,7 @@ import ( "fmt" "github.com/aldinokemal/go-whatsapp-web-multidevice/config" "github.com/aldinokemal/go-whatsapp-web-multidevice/internal/websocket" + pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/appstate" waProto "go.mau.fi/whatsmeow/binary/proto" @@ -32,9 +33,9 @@ var ( ) func SanitizePhone(phone *string) { - if phone != nil && !strings.Contains(*phone, "@") { + if phone != nil && len(*phone) > 0 && !strings.Contains(*phone, "@") { if len(*phone) <= 15 { - *phone = fmt.Sprintf("%s@.whatsapp.net", *phone) + *phone = fmt.Sprintf("%s@s.whatsapp.net", *phone) } else { *phone = fmt.Sprintf("%s@g.us", *phone) } @@ -76,25 +77,30 @@ func GetPlatformName(deviceID int) string { } } -func ParseJID(arg string) (types.JID, bool) { +func ParseJID(arg string) (types.JID, error) { if arg[0] == '+' { arg = arg[1:] } if !strings.ContainsRune(arg, '@') { - return types.NewJID(arg, types.DefaultUserServer), true + return types.NewJID(arg, types.DefaultUserServer), nil } else { recipient, err := types.ParseJID(arg) if err != nil { - _ = fmt.Errorf("invalid JID %s: %v", arg, err) - return recipient, false + fmt.Printf("invalid JID %s: %v", arg, err) + return recipient, pkgError.ErrInvalidJID } else if recipient.User == "" { - _ = fmt.Errorf("invalid JID %s: no server specified", arg) - return recipient, false + fmt.Printf("invalid JID %v: no server specified", arg) + return recipient, pkgError.ErrInvalidJID } - return recipient, true + return recipient, nil } } +func ValidateJidWithLogin(waCli *whatsmeow.Client, phone string) (types.JID, error) { + MustLogin(waCli) + return ParseJID(phone) +} + func InitWaDB() *sqlstore.Container { // Running Whatsapp log = waLog.Stdout("Main", config.WhatsappLogLevel, true) @@ -128,9 +134,9 @@ func InitWaCLI(storeContainer *sqlstore.Container) *whatsmeow.Client { func MustLogin(waCli *whatsmeow.Client) { if !waCli.IsConnected() { - panic(AuthError{Message: "you are not connect to whatsapp server, please reconnect"}) + panic(pkgError.AuthError("you are not connect to services server, please reconnect")) } else if !waCli.IsLoggedIn() { - panic(AuthError{Message: "you are not login"}) + panic(pkgError.AuthError("you are not login to services server, please login")) } } diff --git a/src/services/app_service.go b/src/services/app.go similarity index 100% rename from src/services/app_service.go rename to src/services/app.go diff --git a/src/services/send_service.go b/src/services/send.go similarity index 83% rename from src/services/send_service.go rename to src/services/send.go index 2c54e47..727a81a 100644 --- a/src/services/send_service.go +++ b/src/services/send.go @@ -2,11 +2,12 @@ package services import ( "context" - "errors" "fmt" "github.com/aldinokemal/go-whatsapp-web-multidevice/config" domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" - "github.com/aldinokemal/go-whatsapp-web-multidevice/utils" + pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" + "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/utils" + "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" fiberUtils "github.com/gofiber/fiber/v2/utils" "github.com/h2non/bimg" "github.com/valyala/fasthttp" @@ -30,16 +31,14 @@ func NewSendService(waCli *whatsmeow.Client) domainSend.ISendService { } func (service serviceSend) SendText(ctx context.Context, request domainSend.MessageRequest) (response domainSend.MessageResponse, err error) { - utils.MustLogin(service.WaCli) - - recipient, ok := utils.ParseJID(request.Phone) - if !ok { - return response, errors.New("invalid JID " + request.Phone) + dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) + if err != nil { + return response, err } msgId := whatsmeow.GenerateMessageID() msg := &waProto.Message{Conversation: proto.String(request.Message)} - ts, err := service.WaCli.SendMessage(ctx, recipient, msgId, msg) + ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msgId, msg) if err != nil { return response, err } @@ -50,7 +49,10 @@ func (service serviceSend) SendText(ctx context.Context, request domainSend.Mess } func (service serviceSend) SendImage(ctx context.Context, request domainSend.ImageRequest) (response domainSend.ImageResponse, err error) { - utils.MustLogin(service.WaCli) + dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) + if err != nil { + return response, err + } var ( imagePath string @@ -100,10 +102,6 @@ func (service serviceSend) SendImage(ctx context.Context, request domainSend.Ima // Send to WA server dataWaCaption := request.Caption - dataWaRecipient, ok := utils.ParseJID(request.Phone) - if !ok { - return response, errors.New("invalid JID " + request.Phone) - } dataWaImage, err := os.ReadFile(imagePath) if err != nil { return response, err @@ -145,7 +143,10 @@ func (service serviceSend) SendImage(ctx context.Context, request domainSend.Ima } func (service serviceSend) SendFile(ctx context.Context, request domainSend.FileRequest) (response domainSend.FileResponse, err error) { - utils.MustLogin(service.WaCli) + dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) + if err != nil { + return response, err + } oriFilePath := fmt.Sprintf("%s/%s", config.PathSendItems, request.File.Filename) err = fasthttp.SaveMultipartFile(request.File, oriFilePath) @@ -154,10 +155,6 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File } // Send to WA server - dataWaRecipient, ok := utils.ParseJID(request.Phone) - if !ok { - return response, errors.New("invalid JID " + request.Phone) - } dataWaFile, err := os.ReadFile(oriFilePath) if err != nil { return response, err @@ -197,7 +194,10 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File } func (service serviceSend) SendVideo(ctx context.Context, request domainSend.VideoRequest) (response domainSend.VideoResponse, err error) { - utils.MustLogin(service.WaCli) + dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) + if err != nil { + return response, err + } var ( videoPath string @@ -210,25 +210,27 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid oriVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+request.Video.Filename) err = fasthttp.SaveMultipartFile(request.Video, oriVideoPath) if err != nil { - return response, err + return response, pkgError.InternalServerError(fmt.Sprintf("failed to store video in server %v", err)) } // Get thumbnail video with ffmpeg thumbnailVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+".png") cmdThumbnail := exec.Command("ffmpeg", "-i", oriVideoPath, "-ss", "00:00:01.000", "-vframes", "1", thumbnailVideoPath) err = cmdThumbnail.Run() - utils.PanicIfNeeded(err, "error when getting thumbnail") + if err != nil { + return response, pkgError.InternalServerError(fmt.Sprintf("failed to create thumbnail %v", err)) + } // Resize Thumbnail openImageBuffer, err := bimg.Read(thumbnailVideoPath) resize, err := bimg.NewImage(openImageBuffer).Process(bimg.Options{Quality: 90, Width: 600, Embed: true}) if err != nil { - return response, err + return response, pkgError.InternalServerError(fmt.Sprintf("failed to resize thumbnail %v", err)) } thumbnailResizeVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+"_resize.png") err = bimg.Write(thumbnailResizeVideoPath, resize) if err != nil { - return response, err + return response, pkgError.InternalServerError(fmt.Sprintf("failed to create image thumbnail %v", err)) } deletedItems = append(deletedItems, thumbnailVideoPath) @@ -240,7 +242,9 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid // Compress video with ffmpeg cmdCompress := exec.Command("ffmpeg", "-i", oriVideoPath, "-strict", "-2", compresVideoPath) err = cmdCompress.Run() - utils.PanicIfNeeded(err, "error when compress video") + if err != nil { + return response, pkgError.InternalServerError("failed to compress video") + } videoPath = compresVideoPath deletedItems = append(deletedItems, compresVideoPath) @@ -250,18 +254,13 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid } //Send to WA server - dataWaRecipient, ok := utils.ParseJID(request.Phone) - if !ok { - return response, errors.New("invalid JID " + request.Phone) - } dataWaVideo, err := os.ReadFile(videoPath) if err != nil { return response, err } uploaded, err := service.WaCli.Upload(context.Background(), dataWaVideo, whatsmeow.MediaVideo) if err != nil { - fmt.Printf("Failed to upload file: %v", err) - return response, err + return response, pkgError.InternalServerError(fmt.Sprintf("Failed to upload file: %v", err)) } dataWaThumbnail, err := os.ReadFile(videoThumbnail) if err != nil { @@ -298,12 +297,11 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid } func (service serviceSend) SendContact(ctx context.Context, request domainSend.ContactRequest) (response domainSend.ContactResponse, err error) { - utils.MustLogin(service.WaCli) - - recipient, ok := utils.ParseJID(request.Phone) - if !ok { - return response, errors.New("invalid JID " + request.Phone) + dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) + if err != nil { + return response, err } + msgVCard := fmt.Sprintf("BEGIN:VCARD\nVERSION:3.0\nN:;%v;;;\nFN:%v\nTEL;type=CELL;waid=%v:+%v\nEND:VCARD", request.ContactName, request.ContactName, request.ContactPhone, request.ContactPhone) msgId := whatsmeow.GenerateMessageID() @@ -311,7 +309,7 @@ func (service serviceSend) SendContact(ctx context.Context, request domainSend.C DisplayName: proto.String(request.ContactName), Vcard: proto.String(msgVCard), }} - ts, err := service.WaCli.SendMessage(ctx, recipient, "", msg) + ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, "", msg) if err != nil { return response, err } @@ -322,11 +320,9 @@ func (service serviceSend) SendContact(ctx context.Context, request domainSend.C } func (service serviceSend) SendLink(ctx context.Context, request domainSend.LinkRequest) (response domainSend.LinkResponse, err error) { - utils.MustLogin(service.WaCli) - - recipient, ok := utils.ParseJID(request.Phone) - if !ok { - return response, errors.New("invalid JID " + request.Phone) + dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) + if err != nil { + return response, err } msgId := whatsmeow.GenerateMessageID() @@ -341,7 +337,7 @@ func (service serviceSend) SendLink(ctx context.Context, request domainSend.Link }, }, }} - ts, err := service.WaCli.SendMessage(ctx, recipient, msgId, msg) + ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, msgId, msg) if err != nil { return response, err } @@ -352,15 +348,13 @@ func (service serviceSend) SendLink(ctx context.Context, request domainSend.Link } func (service serviceSend) Revoke(_ context.Context, request domainSend.RevokeRequest) (response domainSend.RevokeResponse, err error) { - utils.MustLogin(service.WaCli) - - recipient, ok := utils.ParseJID(request.Phone) - if !ok { - return response, errors.New("invalid JID " + request.Phone) + dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) + if err != nil { + return response, err } msgId := whatsmeow.GenerateMessageID() - ts, err := service.WaCli.SendMessage(context.Background(), recipient, msgId, service.WaCli.BuildRevoke(recipient, types.EmptyJID, request.MessageID)) + ts, err := service.WaCli.SendMessage(context.Background(), dataWaRecipient, msgId, service.WaCli.BuildRevoke(dataWaRecipient, types.EmptyJID, request.MessageID)) if err != nil { return response, err } @@ -371,16 +365,14 @@ func (service serviceSend) Revoke(_ context.Context, request domainSend.RevokeRe } func (service serviceSend) UpdateMessage(ctx context.Context, request domainSend.UpdateMessageRequest) (response domainSend.UpdateMessageResponse, err error) { - utils.MustLogin(service.WaCli) - - recipient, ok := utils.ParseJID(request.Phone) - if !ok { - return response, errors.New("invalid JID " + request.Phone) + dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) + if err != nil { + return response, err } msgId := whatsmeow.GenerateMessageID() msg := &waProto.Message{Conversation: proto.String(request.Message)} - ts, err := service.WaCli.SendMessage(context.Background(), recipient, msgId, service.WaCli.BuildEdit(recipient, request.MessageID, msg)) + ts, err := service.WaCli.SendMessage(context.Background(), dataWaRecipient, msgId, service.WaCli.BuildEdit(dataWaRecipient, request.MessageID, msg)) if err != nil { return response, err } diff --git a/src/services/user_service.go b/src/services/user.go similarity index 80% rename from src/services/user_service.go rename to src/services/user.go index 295b758..6075ff9 100644 --- a/src/services/user_service.go +++ b/src/services/user.go @@ -5,7 +5,7 @@ import ( "errors" "fmt" domainUser "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/user" - "github.com/aldinokemal/go-whatsapp-web-multidevice/utils" + "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/whatsapp" "go.mau.fi/whatsmeow" "go.mau.fi/whatsmeow/types" ) @@ -21,15 +21,13 @@ func NewUserService(waCli *whatsmeow.Client) domainUser.IUserService { } func (service userService) Info(_ context.Context, request domainUser.InfoRequest) (response domainUser.InfoResponse, err error) { - utils.MustLogin(service.WaCli) - var jids []types.JID - jid, ok := utils.ParseJID(request.Phone) - if !ok { - return response, errors.New("invalid JID " + request.Phone) + dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) + if err != nil { + return response, err } - jids = append(jids, jid) + jids = append(jids, dataWaRecipient) resp, err := service.WaCli.GetUserInfo(jids) if err != nil { return response, err @@ -41,7 +39,7 @@ func (service userService) Info(_ context.Context, request domainUser.InfoReques device = append(device, domainUser.InfoResponseDataDevice{ User: j.User, Agent: j.Agent, - Device: utils.GetPlatformName(int(j.Device)), + Device: whatsapp.GetPlatformName(int(j.Device)), Server: j.Server, AD: j.AD, }) @@ -62,13 +60,11 @@ func (service userService) Info(_ context.Context, request domainUser.InfoReques } func (service userService) Avatar(_ context.Context, request domainUser.AvatarRequest) (response domainUser.AvatarResponse, err error) { - utils.MustLogin(service.WaCli) - - jid, ok := utils.ParseJID(request.Phone) - if !ok { - return response, errors.New("invalid JID " + request.Phone) + dataWaRecipient, err := whatsapp.ValidateJidWithLogin(service.WaCli, request.Phone) + if err != nil { + return response, err } - pic, err := service.WaCli.GetProfilePictureInfo(jid, false, "") + pic, err := service.WaCli.GetProfilePictureInfo(dataWaRecipient, false, "") if err != nil { return response, err } else if pic == nil { @@ -83,7 +79,7 @@ func (service userService) Avatar(_ context.Context, request domainUser.AvatarRe } func (service userService) MyListGroups(_ context.Context) (response domainUser.MyListGroupsResponse, err error) { - utils.MustLogin(service.WaCli) + whatsapp.MustLogin(service.WaCli) groups, err := service.WaCli.GetJoinedGroups() if err != nil { @@ -99,7 +95,7 @@ func (service userService) MyListGroups(_ context.Context) (response domainUser. } func (service userService) MyPrivacySetting(_ context.Context) (response domainUser.MyPrivacySettingResponse, err error) { - utils.MustLogin(service.WaCli) + whatsapp.MustLogin(service.WaCli) resp, err := service.WaCli.TryFetchPrivacySettings(false) if err != nil { diff --git a/src/utils/errors.go b/src/utils/errors.go deleted file mode 100644 index 78c2840..0000000 --- a/src/utils/errors.go +++ /dev/null @@ -1,29 +0,0 @@ -package utils - -import "fmt" - -func PanicIfNeeded(err any, message ...string) { - if err != nil { - if fmt.Sprintf("%s", err) == "record not found" && len(message) > 0 { - panic(message[0]) - } else { - panic(err) - } - } -} - -type ValidationError struct { - Message string -} - -func (validationError ValidationError) Error() string { - return validationError.Message -} - -type AuthError struct { - Message string -} - -func (err AuthError) Error() string { - return err.Message -} diff --git a/src/validations/send_validation.go b/src/validations/send_validation.go index eb6cb6f..101f922 100644 --- a/src/validations/send_validation.go +++ b/src/validations/send_validation.go @@ -4,32 +4,32 @@ import ( "fmt" "github.com/aldinokemal/go-whatsapp-web-multidevice/config" domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" - "github.com/aldinokemal/go-whatsapp-web-multidevice/utils" + pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" "github.com/dustin/go-humanize" validation "github.com/go-ozzo/ozzo-validation/v4" + "github.com/go-ozzo/ozzo-validation/v4/is" ) -func ValidateSendMessage(request domainSend.MessageRequest) { +func ValidateSendMessage(request domainSend.MessageRequest) error { err := validation.ValidateStruct(&request, + validation.Field(&request.Phone, validation.Required), validation.Field(&request.Message, validation.Required), ) if err != nil { - panic(utils.ValidationError{ - Message: err.Error(), - }) + return pkgError.ValidationError(err.Error()) } + return nil } -func ValidateSendImage(request domainSend.ImageRequest) { +func ValidateSendImage(request domainSend.ImageRequest) error { err := validation.ValidateStruct(&request, + validation.Field(&request.Phone, validation.Required), validation.Field(&request.Image, validation.Required), ) if err != nil { - panic(utils.ValidationError{ - Message: err.Error(), - }) + return pkgError.ValidationError(err.Error()) } availableMimes := map[string]bool{ @@ -39,40 +39,38 @@ func ValidateSendImage(request domainSend.ImageRequest) { } if !availableMimes[request.Image.Header.Get("Content-Type")] { - panic(utils.ValidationError{ - Message: "your image is not allowed. please use jpg/jpeg/png", - }) + return pkgError.ValidationError("your image is not allowed. please use jpg/jpeg/png") } + + return nil } -func ValidateSendFile(request domainSend.FileRequest) { +func ValidateSendFile(request domainSend.FileRequest) error { err := validation.ValidateStruct(&request, + validation.Field(&request.Phone, validation.Required), validation.Field(&request.File, validation.Required), ) if err != nil { - panic(utils.ValidationError{ - Message: err.Error(), - }) + return pkgError.ValidationError(err.Error()) } if request.File.Size > config.WhatsappSettingMaxFileSize { // 10MB maxSizeString := humanize.Bytes(uint64(config.WhatsappSettingMaxFileSize)) - panic(utils.ValidationError{ - Message: fmt.Sprintf("max file upload is %s, please upload in cloud and send via text if your file is higher than %s", maxSizeString, maxSizeString), - }) + return pkgError.ValidationError(fmt.Sprintf("max file upload is %s, please upload in cloud and send via text if your file is higher than %s", maxSizeString, maxSizeString)) } + + return nil } -func ValidateSendVideo(request domainSend.VideoRequest) { +func ValidateSendVideo(request domainSend.VideoRequest) error { err := validation.ValidateStruct(&request, + validation.Field(&request.Phone, validation.Required), validation.Field(&request.Video, validation.Required), ) if err != nil { - panic(utils.ValidationError{ - Message: err.Error(), - }) + return pkgError.ValidationError(err.Error()) } availableMimes := map[string]bool{ @@ -82,42 +80,40 @@ func ValidateSendVideo(request domainSend.VideoRequest) { } if !availableMimes[request.Video.Header.Get("Content-Type")] { - panic(utils.ValidationError{ - Message: "your video type is not allowed. please use mp4/mkv", - }) + return pkgError.ValidationError("your video type is not allowed. please use mp4/mkv/avi") } if request.Video.Size > config.WhatsappSettingMaxVideoSize { // 30MB maxSizeString := humanize.Bytes(uint64(config.WhatsappSettingMaxVideoSize)) - panic(utils.ValidationError{ - Message: fmt.Sprintf("max video upload is %s, please upload in cloud and send via text if your file is higher than %s", maxSizeString, maxSizeString), - }) + return pkgError.ValidationError(fmt.Sprintf("max video upload is %s, please upload in cloud and send via text if your file is higher than %s", maxSizeString, maxSizeString)) } + + return nil } -func ValidateSendContact(request domainSend.ContactRequest) { +func ValidateSendContact(request domainSend.ContactRequest) error { err := validation.ValidateStruct(&request, - validation.Field(&request.ContactName, validation.Required), + validation.Field(&request.Phone, validation.Required), validation.Field(&request.ContactPhone, validation.Required), + validation.Field(&request.ContactName, validation.Required), ) if err != nil { - panic(utils.ValidationError{ - Message: err.Error(), - }) + return pkgError.ValidationError(err.Error()) } + + return nil } func ValidateSendLink(request domainSend.LinkRequest) error { err := validation.ValidateStruct(&request, - validation.Field(&request.Link, validation.Required), + validation.Field(&request.Phone, validation.Required), + validation.Field(&request.Link, validation.Required, is.URL), validation.Field(&request.Caption, validation.Required), ) if err != nil { - return utils.ValidationError{ - Message: err.Error(), - } + return pkgError.ValidationError(err.Error()) } return nil @@ -125,13 +121,12 @@ func ValidateSendLink(request domainSend.LinkRequest) error { func ValidateRevokeMessage(request domainSend.RevokeRequest) error { err := validation.ValidateStruct(&request, + validation.Field(&request.Phone, validation.Required), validation.Field(&request.MessageID, validation.Required), ) if err != nil { - return utils.ValidationError{ - Message: err.Error(), - } + return pkgError.ValidationError(err.Error()) } return nil @@ -139,14 +134,13 @@ func ValidateRevokeMessage(request domainSend.RevokeRequest) error { func ValidateUpdateMessage(request domainSend.UpdateMessageRequest) error { err := validation.ValidateStruct(&request, + validation.Field(&request.Phone, validation.Required), validation.Field(&request.MessageID, validation.Required), validation.Field(&request.Message, validation.Required), ) if err != nil { - return utils.ValidationError{ - Message: err.Error(), - } + return pkgError.ValidationError(err.Error()) } return nil diff --git a/src/validations/send_validation_test.go b/src/validations/send_validation_test.go index 32bab2f..f04dedf 100644 --- a/src/validations/send_validation_test.go +++ b/src/validations/send_validation_test.go @@ -2,7 +2,9 @@ package validations import ( domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" + pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" "github.com/stretchr/testify/assert" + "mime/multipart" "testing" ) @@ -16,25 +18,438 @@ func TestValidateSendMessage(t *testing.T) { err any }{ { - name: "success phone & message normal", + name: "should success with phone and message", args: args{request: domainSend.MessageRequest{ Phone: "1728937129312@s.whatsapp.net", Message: "Hello this is testing", }}, err: nil, }, + { + name: "should error with empty phone", + args: args{request: domainSend.MessageRequest{ + Phone: "", + Message: "Hello this is testing", + }}, + err: pkgError.ValidationError("phone: cannot be blank."), + }, + { + name: "should error with empty message", + args: args{request: domainSend.MessageRequest{ + Phone: "1728937129312@s.whatsapp.net", + Message: "", + }}, + err: pkgError.ValidationError("message: cannot be blank."), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateSendMessage(tt.args.request) + assert.Equal(t, tt.err, err) + }) + } +} + +func TestValidateSendImage(t *testing.T) { + image := &multipart.FileHeader{ + Filename: "sample-image.png", + Size: 100, + Header: map[string][]string{"Content-Type": {"image/png"}}, + } + + type args struct { + request domainSend.ImageRequest + } + tests := []struct { + name string + args args + err any + }{ + { + name: "should success with normal condition", + args: args{request: domainSend.ImageRequest{ + Phone: "1728937129312@s.whatsapp.net", + Caption: "Hello this is testing", + Image: image, + }}, + err: nil, + }, + { + name: "should error with empty phone", + args: args{request: domainSend.ImageRequest{ + Phone: "", + Image: image, + }}, + err: pkgError.ValidationError("phone: cannot be blank."), + }, + { + name: "should error with empty image", + args: args{request: domainSend.ImageRequest{ + Phone: "1728937129312@s.whatsapp.net", + Image: nil, + }}, + err: pkgError.ValidationError("image: cannot be blank."), + }, + { + name: "should error with invalid image type", + args: args{request: domainSend.ImageRequest{ + Phone: "1728937129312@s.whatsapp.net", + Image: &multipart.FileHeader{ + Filename: "sample-image.pdf", + Size: 100, + Header: map[string][]string{"Content-Type": {"application/pdf"}}, + }, + }}, + err: pkgError.ValidationError("your image is not allowed. please use jpg/jpeg/png"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateSendImage(tt.args.request) + assert.Equal(t, tt.err, err) + }) + } +} + +func TestValidateSendFile(t *testing.T) { + file := &multipart.FileHeader{ + Filename: "sample-image.png", + Size: 100, + Header: map[string][]string{"Content-Type": {"image/png"}}, + } + + type args struct { + request domainSend.FileRequest + } + tests := []struct { + name string + args args + err any + }{ + { + name: "should success with normal condition", + args: args{request: domainSend.FileRequest{ + Phone: "1728937129312@s.whatsapp.net", + File: file, + }}, + err: nil, + }, + { + name: "should error with empty phone", + args: args{request: domainSend.FileRequest{ + Phone: "", + File: file, + }}, + err: pkgError.ValidationError("phone: cannot be blank."), + }, + { + name: "should error with empty file", + args: args{request: domainSend.FileRequest{ + Phone: "1728937129312@s.whatsapp.net", + File: nil, + }}, + err: pkgError.ValidationError("file: cannot be blank."), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateSendFile(tt.args.request) + assert.Equal(t, tt.err, err) + }) + } +} + +func TestValidateSendVideo(t *testing.T) { + file := &multipart.FileHeader{ + Filename: "sample-video.mp4", + Size: 100, + Header: map[string][]string{"Content-Type": {"video/mp4"}}, + } + + type args struct { + request domainSend.VideoRequest + } + tests := []struct { + name string + args args + err any + }{ + { + name: "should success with normal condition", + args: args{request: domainSend.VideoRequest{ + Phone: "1728937129312@s.whatsapp.net", + Caption: "simple caption", + Video: file, + ViewOnce: false, + Compress: false, + }}, + err: nil, + }, + { + name: "should error with empty phone", + args: args{request: domainSend.VideoRequest{ + Phone: "", + Caption: "simple caption", + Video: file, + ViewOnce: false, + Compress: false, + }}, + err: pkgError.ValidationError("phone: cannot be blank."), + }, + { + name: "should error with empty video", + args: args{request: domainSend.VideoRequest{ + Phone: "1728937129312@s.whatsapp.net", + Caption: "simple caption", + Video: nil, + ViewOnce: false, + Compress: false, + }}, + err: pkgError.ValidationError("video: cannot be blank."), + }, + { + name: "should error with invalid format video", + args: args{request: domainSend.VideoRequest{ + Phone: "1728937129312@s.whatsapp.net", + Caption: "simple caption", + Video: func() *multipart.FileHeader { + return &multipart.FileHeader{ + Filename: "sample-video.jpg", + Size: 100, + Header: map[string][]string{"Content-Type": {"image/png"}}, + } + }(), + ViewOnce: false, + Compress: false, + }}, + err: pkgError.ValidationError("your video type is not allowed. please use mp4/mkv/avi"), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateSendVideo(tt.args.request) + assert.Equal(t, tt.err, err) + }) + } +} + +func TestValidateSendLink(t *testing.T) { + type args struct { + request domainSend.LinkRequest + } + tests := []struct { + name string + args args + err any + }{ + { + name: "should success normal condition", + args: args{request: domainSend.LinkRequest{ + Phone: "1728937129312@s.whatsapp.net", + Caption: "description", + Link: "https://google.com", + }}, + err: nil, + }, + { + name: "should error with empty phone", + args: args{request: domainSend.LinkRequest{ + Phone: "", + Caption: "description", + Link: "https://google.com", + }}, + err: pkgError.ValidationError("phone: cannot be blank."), + }, + { + name: "should error with empty caption", + args: args{request: domainSend.LinkRequest{ + Phone: "1728937129312@s.whatsapp.net", + Caption: "", + Link: "https://google.com", + }}, + err: pkgError.ValidationError("caption: cannot be blank."), + }, + { + name: "should error with empty link", + args: args{request: domainSend.LinkRequest{ + Phone: "1728937129312@s.whatsapp.net", + Caption: "description", + Link: "", + }}, + err: pkgError.ValidationError("link: cannot be blank."), + }, + { + name: "should error with invalid link", + args: args{request: domainSend.LinkRequest{ + Phone: "1728937129312@s.whatsapp.net", + Caption: "description", + Link: "googlecom", + }}, + err: pkgError.ValidationError("link: must be a valid URL."), + }, } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if tt.err == nil { - ValidateSendMessage(tt.args.request) - } else { - assert.PanicsWithValue(t, tt.err, func() { - ValidateSendMessage(tt.args.request) - }) - } + err := ValidateSendLink(tt.args.request) + assert.Equal(t, tt.err, err) + }) + } +} +func TestValidateRevokeMessage(t *testing.T) { + type args struct { + request domainSend.RevokeRequest + } + tests := []struct { + name string + args args + err any + }{ + { + name: "should success normal condition", + args: args{request: domainSend.RevokeRequest{ + Phone: "1728937129312@s.whatsapp.net", + MessageID: "1382901271239781", + }}, + err: nil, + }, + { + name: "should error with empty phone", + args: args{request: domainSend.RevokeRequest{ + Phone: "", + MessageID: "1382901271239781", + }}, + err: pkgError.ValidationError("phone: cannot be blank."), + }, + { + name: "should error with empty message id", + args: args{request: domainSend.RevokeRequest{ + Phone: "1728937129312@s.whatsapp.net", + MessageID: "", + }}, + err: pkgError.ValidationError("message_id: cannot be blank."), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateRevokeMessage(tt.args.request) + assert.Equal(t, tt.err, err) + }) + } +} + +func TestValidateUpdateMessage(t *testing.T) { + type args struct { + request domainSend.UpdateMessageRequest + } + tests := []struct { + name string + args args + err any + }{ + { + name: "should success normal condition", + args: args{request: domainSend.UpdateMessageRequest{ + MessageID: "1382901271239781", + Message: "some update message", + Phone: "1728937129312@s.whatsapp.net", + }}, + err: nil, + }, + { + name: "should error with empty phone", + args: args{request: domainSend.UpdateMessageRequest{ + MessageID: "1382901271239781", + Message: "some update message", + Phone: "", + }}, + err: pkgError.ValidationError("phone: cannot be blank."), + }, + { + name: "should error with empty message id", + args: args{request: domainSend.UpdateMessageRequest{ + MessageID: "", + Message: "some update message", + Phone: "1728937129312@s.whatsapp.net", + }}, + err: pkgError.ValidationError("message_id: cannot be blank."), + }, + { + name: "should error with empty message update", + args: args{request: domainSend.UpdateMessageRequest{ + MessageID: "1382901271239781", + Message: "", + Phone: "1728937129312@s.whatsapp.net", + }}, + err: pkgError.ValidationError("message: cannot be blank."), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateUpdateMessage(tt.args.request) + assert.Equal(t, tt.err, err) + }) + } +} + +func TestValidateSendContact(t *testing.T) { + type args struct { + request domainSend.ContactRequest + } + tests := []struct { + name string + args args + err any + }{ + { + name: "should success normal condition", + args: args{request: domainSend.ContactRequest{ + Phone: "1728937129312@s.whatsapp.net", + ContactName: "Aldino", + ContactPhone: "62788712738123", + }}, + err: nil, + }, + { + name: "should error with empty phone", + args: args{request: domainSend.ContactRequest{ + Phone: "", + ContactName: "Aldino", + ContactPhone: "62788712738123", + }}, + err: pkgError.ValidationError("phone: cannot be blank."), + }, + { + name: "should error with empty contact name", + args: args{request: domainSend.ContactRequest{ + Phone: "1728937129312@s.whatsapp.net", + ContactName: "", + ContactPhone: "62788712738123", + }}, + err: pkgError.ValidationError("contact_name: cannot be blank."), + }, + { + name: "should error with empty contact phone", + args: args{request: domainSend.ContactRequest{ + Phone: "1728937129312@s.whatsapp.net", + ContactName: "Aldino", + ContactPhone: "", + }}, + err: pkgError.ValidationError("contact_phone: cannot be blank."), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateSendContact(tt.args.request) + assert.Equal(t, tt.err, err) }) } } diff --git a/src/validations/user_validation.go b/src/validations/user_validation.go index 4060f56..8f1b1d8 100644 --- a/src/validations/user_validation.go +++ b/src/validations/user_validation.go @@ -2,29 +2,29 @@ package validations import ( domainUser "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/user" - "github.com/aldinokemal/go-whatsapp-web-multidevice/utils" + pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" validation "github.com/go-ozzo/ozzo-validation/v4" ) -func ValidateUserInfo(request domainUser.InfoRequest) { +func ValidateUserInfo(request domainUser.InfoRequest) error { err := validation.ValidateStruct(&request, validation.Field(&request.Phone, validation.Required), ) if err != nil { - panic(utils.ValidationError{ - Message: err.Error(), - }) + return pkgError.ValidationError(err.Error()) } + + return nil } -func ValidateUserAvatar(request domainUser.AvatarRequest) { +func ValidateUserAvatar(request domainUser.AvatarRequest) error { err := validation.ValidateStruct(&request, validation.Field(&request.Phone, validation.Required), ) if err != nil { - panic(utils.ValidationError{ - Message: err.Error(), - }) + return pkgError.ValidationError(err.Error()) } + + return nil } diff --git a/src/validations/user_validation_test.go b/src/validations/user_validation_test.go new file mode 100644 index 0000000..d444887 --- /dev/null +++ b/src/validations/user_validation_test.go @@ -0,0 +1,74 @@ +package validations + +import ( + domainUser "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/user" + pkgError "github.com/aldinokemal/go-whatsapp-web-multidevice/pkg/error" + "github.com/stretchr/testify/assert" + "testing" +) + +func TestValidateUserAvatar(t *testing.T) { + type args struct { + request domainUser.AvatarRequest + } + tests := []struct { + name string + args args + err any + }{ + { + name: "should success", + args: args{request: domainUser.AvatarRequest{ + Phone: "1728937129312@s.whatsapp.net", + }}, + err: nil, + }, + { + name: "should error with empty phone", + args: args{request: domainUser.AvatarRequest{ + Phone: "", + }}, + err: pkgError.ValidationError("phone: cannot be blank."), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateUserAvatar(tt.args.request) + assert.Equal(t, tt.err, err) + }) + } +} + +func TestValidateUserInfo(t *testing.T) { + type args struct { + request domainUser.InfoRequest + } + tests := []struct { + name string + args args + err any + }{ + { + name: "should success", + args: args{request: domainUser.InfoRequest{ + Phone: "1728937129312@s.whatsapp.net", + }}, + err: nil, + }, + { + name: "should error with empty phone", + args: args{request: domainUser.InfoRequest{ + Phone: "", + }}, + err: pkgError.ValidationError("phone: cannot be blank."), + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + err := ValidateUserInfo(tt.args.request) + assert.Equal(t, tt.err, err) + }) + } +} diff --git a/src/views/index.html b/src/views/index.html index 6689029..b437a5b 100644 --- a/src/views/index.html +++ b/src/views/index.html @@ -29,7 +29,7 @@
- Device is connected + Device has connected

Device ID: [[ connected_devices[0].device ]] @@ -224,8 +224,8 @@

- +
@@ -261,8 +261,8 @@
- +
@@ -321,8 +321,8 @@
- +
@@ -362,12 +362,12 @@ +
- - +