Browse Source

feat: introduce error package (#40)

* feat: introduce error and add test validation

* feat: update UI
pull/41/head v4.2.0
Aldino Kemal 3 years ago
committed by GitHub
parent
commit
3fd2359c52
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
  1. 7
      src/cmd/root.go
  2. 2
      src/config/settings.go
  3. 26
      src/internal/rest/app.go
  4. 22
      src/internal/rest/middleware/recovery.go
  5. 50
      src/internal/rest/send.go
  6. 21
      src/internal/rest/user.go
  7. 19
      src/pkg/error/auth_error.go
  8. 27
      src/pkg/error/generic_error.go
  9. 19
      src/pkg/error/validation_error.go
  10. 24
      src/pkg/error/whatsapp_error.go
  11. 12
      src/pkg/utils/general.go
  12. 3
      src/pkg/utils/response.go
  13. 30
      src/pkg/whatsapp/whatsapp.go
  14. 0
      src/services/app.go
  15. 100
      src/services/send.go
  16. 28
      src/services/user.go
  17. 29
      src/utils/errors.go
  18. 82
      src/validations/send_validation.go
  19. 431
      src/validations/send_validation_test.go
  20. 18
      src/validations/user_validation.go
  21. 74
      src/validations/user_validation_test.go
  22. 20
      src/views/index.html

7
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/helpers"
"github.com/aldinokemal/go-whatsapp-web-multidevice/internal/rest/middleware" "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/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/services"
"github.com/aldinokemal/go-whatsapp-web-multidevice/utils"
"github.com/dustin/go-humanize" "github.com/dustin/go-humanize"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
"github.com/gofiber/fiber/v2/middleware/basicauth" "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 // Service
appService := services.NewAppService(cli, db) appService := services.NewAppService(cli, db)

2
src/config/settings.go

@ -6,7 +6,7 @@ import (
) )
var ( var (
AppVersion = "v4.0.0"
AppVersion = "v4.2.0"
AppPort = "3000" AppPort = "3000"
AppDebug = false AppDebug = false
AppOs = fmt.Sprintf("AldinoKemal") AppOs = fmt.Sprintf("AldinoKemal")

26
src/internal/rest/app_rest.go → src/internal/rest/app.go

@ -3,7 +3,7 @@ package rest
import ( import (
"fmt" "fmt"
domainApp "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/app" 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" "github.com/gofiber/fiber/v2"
) )
@ -21,12 +21,12 @@ func InitRestApp(app *fiber.App, service domainApp.IAppService) App {
return App{Service: service} 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) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: "Success", Message: "Success",
Results: map[string]any{ Results: map[string]any{
"qr_link": fmt.Sprintf("%s://%s/%s", c.Protocol(), c.Hostname(), response.ImagePath), "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) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: "Success logout", Message: "Success logout",
Results: nil, 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) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: "Reconnect success", Message: "Reconnect success",
Results: nil, 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) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: "Fetch device success", Message: "Fetch device success",
Results: devices, Results: devices,
}) })

22
src/internal/rest/middleware/recovery.go

@ -2,7 +2,8 @@ package middleware
import ( import (
"fmt" "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" "github.com/gofiber/fiber/v2"
) )
@ -12,22 +13,17 @@ func Recovery() fiber.Handler {
err := recover() err := recover()
if err != nil { if err != nil {
var res utils.ResponseData var res utils.ResponseData
res.Code = 500
res.Status = 500
res.Message = fmt.Sprintf("%s", err) 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)
} }
}() }()

50
src/internal/rest/send_rest.go → src/internal/rest/send.go

@ -2,7 +2,8 @@ package rest
import ( import (
domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" 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/aldinokemal/go-whatsapp-web-multidevice/validations"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
@ -30,14 +31,15 @@ func (controller *Send) SendText(c *fiber.Ctx) error {
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
// add validation send message // 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) response, err := controller.Service.SendText(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: response.Status, Message: response.Status,
Results: response, Results: response,
}) })
@ -56,14 +58,15 @@ func (controller *Send) SendImage(c *fiber.Ctx) error {
request.Image = file request.Image = file
//add validation send image //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) response, err := controller.Service.SendImage(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: response.Status, Message: response.Status,
Results: response, Results: response,
}) })
@ -80,14 +83,15 @@ func (controller *Send) SendFile(c *fiber.Ctx) error {
request.File = file request.File = file
//add validation send image //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) response, err := controller.Service.SendFile(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: response.Status, Message: response.Status,
Results: response, Results: response,
}) })
@ -104,14 +108,15 @@ func (controller *Send) SendVideo(c *fiber.Ctx) error {
request.Video = video request.Video = video
//add validation send image //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) response, err := controller.Service.SendVideo(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: response.Status, Message: response.Status,
Results: response, Results: response,
}) })
@ -123,14 +128,15 @@ func (controller *Send) SendContact(c *fiber.Ctx) error {
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
// add validation send contect // 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) response, err := controller.Service.SendContact(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: response.Status, Message: response.Status,
Results: response, Results: response,
}) })
@ -141,15 +147,15 @@ func (controller *Send) SendLink(c *fiber.Ctx) error {
err := c.BodyParser(&request) err := c.BodyParser(&request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
whatsapp.SanitizePhone(&request.Phone)
err = validations.ValidateSendLink(request) err = validations.ValidateSendLink(request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
utils.SanitizePhone(&request.Phone)
response, err := controller.Service.SendLink(c.UserContext(), request) response, err := controller.Service.SendLink(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: response.Status, Message: response.Status,
Results: response, Results: response,
}) })
@ -163,13 +169,13 @@ func (controller *Send) RevokeMessage(c *fiber.Ctx) error {
err = validations.ValidateRevokeMessage(request) err = validations.ValidateRevokeMessage(request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
utils.SanitizePhone(&request.Phone)
whatsapp.SanitizePhone(&request.Phone)
response, err := controller.Service.Revoke(c.UserContext(), request) response, err := controller.Service.Revoke(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: response.Status, Message: response.Status,
Results: response, Results: response,
}) })
@ -183,13 +189,13 @@ func (controller *Send) UpdateMessage(c *fiber.Ctx) error {
err = validations.ValidateUpdateMessage(request) err = validations.ValidateUpdateMessage(request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
utils.SanitizePhone(&request.Phone)
whatsapp.SanitizePhone(&request.Phone)
response, err := controller.Service.UpdateMessage(c.UserContext(), request) response, err := controller.Service.UpdateMessage(c.UserContext(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: response.Status, Message: response.Status,
Results: response, Results: response,
}) })

21
src/internal/rest/user_rest.go → src/internal/rest/user.go

@ -2,7 +2,8 @@ package rest
import ( import (
domainUser "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/user" 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/aldinokemal/go-whatsapp-web-multidevice/validations"
"github.com/gofiber/fiber/v2" "github.com/gofiber/fiber/v2"
) )
@ -34,14 +35,15 @@ func (controller *User) UserInfo(c *fiber.Ctx) error {
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
// add validation send message // 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) response, err := controller.Service.Info(c.Context(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: "Success", Message: "Success",
Results: response.Data[0], Results: response.Data[0],
}) })
@ -53,14 +55,15 @@ func (controller *User) UserAvatar(c *fiber.Ctx) error {
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
// add validation send message // 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) response, err := controller.Service.Avatar(c.Context(), request)
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: "Success get avatar", Message: "Success get avatar",
Results: response, Results: response,
}) })
@ -71,7 +74,7 @@ func (controller *User) UserMyPrivacySetting(c *fiber.Ctx) error {
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: "Success get privacy", Message: "Success get privacy",
Results: response, Results: response,
}) })
@ -82,7 +85,7 @@ func (controller *User) UserMyListGroups(c *fiber.Ctx) error {
utils.PanicIfNeeded(err) utils.PanicIfNeeded(err)
return c.JSON(utils.ResponseData{ return c.JSON(utils.ResponseData{
Code: 200,
Status: 200,
Message: "Success get list groups", Message: "Success get list groups",
Results: response, Results: response,
}) })

19
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
}

27
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
}

19
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
}

24
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")
)

12
src/utils/general.go → src/pkg/utils/general.go

@ -1,6 +1,7 @@
package utils package utils
import ( import (
"fmt"
"os" "os"
"path/filepath" "path/filepath"
"time" "time"
@ -34,3 +35,14 @@ func CreateFolder(folderPath ...string) error {
} }
return nil 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)
}
}
}

3
src/utils/response.go → src/pkg/utils/response.go

@ -1,7 +1,8 @@
package utils package utils
type ResponseData struct { type ResponseData struct {
Code int `json:"code"`
Status int
Code string `json:"code,omitempty"`
Message string `json:"message"` Message string `json:"message"`
Results any `json:"results"` Results any `json:"results"`
} }

30
src/utils/whatsapp.go → src/pkg/whatsapp/whatsapp.go

@ -1,4 +1,4 @@
package utils
package whatsapp
import ( import (
"bytes" "bytes"
@ -7,6 +7,7 @@ import (
"fmt" "fmt"
"github.com/aldinokemal/go-whatsapp-web-multidevice/config" "github.com/aldinokemal/go-whatsapp-web-multidevice/config"
"github.com/aldinokemal/go-whatsapp-web-multidevice/internal/websocket" "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"
"go.mau.fi/whatsmeow/appstate" "go.mau.fi/whatsmeow/appstate"
waProto "go.mau.fi/whatsmeow/binary/proto" waProto "go.mau.fi/whatsmeow/binary/proto"
@ -32,9 +33,9 @@ var (
) )
func SanitizePhone(phone *string) { func SanitizePhone(phone *string) {
if phone != nil && !strings.Contains(*phone, "@") {
if phone != nil && len(*phone) > 0 && !strings.Contains(*phone, "@") {
if len(*phone) <= 15 { if len(*phone) <= 15 {
*phone = fmt.Sprintf("%s@.whatsapp.net", *phone)
*phone = fmt.Sprintf("%s@s.whatsapp.net", *phone)
} else { } else {
*phone = fmt.Sprintf("%s@g.us", *phone) *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] == '+' { if arg[0] == '+' {
arg = arg[1:] arg = arg[1:]
} }
if !strings.ContainsRune(arg, '@') { if !strings.ContainsRune(arg, '@') {
return types.NewJID(arg, types.DefaultUserServer), true
return types.NewJID(arg, types.DefaultUserServer), nil
} else { } else {
recipient, err := types.ParseJID(arg) recipient, err := types.ParseJID(arg)
if err != nil { 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 == "" { } 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 { func InitWaDB() *sqlstore.Container {
// Running Whatsapp // Running Whatsapp
log = waLog.Stdout("Main", config.WhatsappLogLevel, true) log = waLog.Stdout("Main", config.WhatsappLogLevel, true)
@ -128,9 +134,9 @@ func InitWaCLI(storeContainer *sqlstore.Container) *whatsmeow.Client {
func MustLogin(waCli *whatsmeow.Client) { func MustLogin(waCli *whatsmeow.Client) {
if !waCli.IsConnected() { 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() { } else if !waCli.IsLoggedIn() {
panic(AuthError{Message: "you are not login"})
panic(pkgError.AuthError("you are not login to services server, please login"))
} }
} }

0
src/services/app_service.go → src/services/app.go

100
src/services/send_service.go → src/services/send.go

@ -2,11 +2,12 @@ package services
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"github.com/aldinokemal/go-whatsapp-web-multidevice/config" "github.com/aldinokemal/go-whatsapp-web-multidevice/config"
domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" 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" fiberUtils "github.com/gofiber/fiber/v2/utils"
"github.com/h2non/bimg" "github.com/h2non/bimg"
"github.com/valyala/fasthttp" "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) { 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() msgId := whatsmeow.GenerateMessageID()
msg := &waProto.Message{Conversation: proto.String(request.Message)} 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 { if err != nil {
return response, err 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) { 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 ( var (
imagePath string imagePath string
@ -100,10 +102,6 @@ func (service serviceSend) SendImage(ctx context.Context, request domainSend.Ima
// Send to WA server // Send to WA server
dataWaCaption := request.Caption dataWaCaption := request.Caption
dataWaRecipient, ok := utils.ParseJID(request.Phone)
if !ok {
return response, errors.New("invalid JID " + request.Phone)
}
dataWaImage, err := os.ReadFile(imagePath) dataWaImage, err := os.ReadFile(imagePath)
if err != nil { if err != nil {
return response, err 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) { 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) oriFilePath := fmt.Sprintf("%s/%s", config.PathSendItems, request.File.Filename)
err = fasthttp.SaveMultipartFile(request.File, oriFilePath) err = fasthttp.SaveMultipartFile(request.File, oriFilePath)
@ -154,10 +155,6 @@ func (service serviceSend) SendFile(ctx context.Context, request domainSend.File
} }
// Send to WA server // 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) dataWaFile, err := os.ReadFile(oriFilePath)
if err != nil { if err != nil {
return response, err 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) { 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 ( var (
videoPath string 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) oriVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+request.Video.Filename)
err = fasthttp.SaveMultipartFile(request.Video, oriVideoPath) err = fasthttp.SaveMultipartFile(request.Video, oriVideoPath)
if err != nil { 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 // Get thumbnail video with ffmpeg
thumbnailVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+".png") thumbnailVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+".png")
cmdThumbnail := exec.Command("ffmpeg", "-i", oriVideoPath, "-ss", "00:00:01.000", "-vframes", "1", thumbnailVideoPath) cmdThumbnail := exec.Command("ffmpeg", "-i", oriVideoPath, "-ss", "00:00:01.000", "-vframes", "1", thumbnailVideoPath)
err = cmdThumbnail.Run() 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 // Resize Thumbnail
openImageBuffer, err := bimg.Read(thumbnailVideoPath) openImageBuffer, err := bimg.Read(thumbnailVideoPath)
resize, err := bimg.NewImage(openImageBuffer).Process(bimg.Options{Quality: 90, Width: 600, Embed: true}) resize, err := bimg.NewImage(openImageBuffer).Process(bimg.Options{Quality: 90, Width: 600, Embed: true})
if err != nil { 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") thumbnailResizeVideoPath := fmt.Sprintf("%s/%s", config.PathSendItems, generateUUID+"_resize.png")
err = bimg.Write(thumbnailResizeVideoPath, resize) err = bimg.Write(thumbnailResizeVideoPath, resize)
if err != nil { if err != nil {
return response, err
return response, pkgError.InternalServerError(fmt.Sprintf("failed to create image thumbnail %v", err))
} }
deletedItems = append(deletedItems, thumbnailVideoPath) deletedItems = append(deletedItems, thumbnailVideoPath)
@ -240,7 +242,9 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid
// Compress video with ffmpeg // Compress video with ffmpeg
cmdCompress := exec.Command("ffmpeg", "-i", oriVideoPath, "-strict", "-2", compresVideoPath) cmdCompress := exec.Command("ffmpeg", "-i", oriVideoPath, "-strict", "-2", compresVideoPath)
err = cmdCompress.Run() err = cmdCompress.Run()
utils.PanicIfNeeded(err, "error when compress video")
if err != nil {
return response, pkgError.InternalServerError("failed to compress video")
}
videoPath = compresVideoPath videoPath = compresVideoPath
deletedItems = append(deletedItems, compresVideoPath) deletedItems = append(deletedItems, compresVideoPath)
@ -250,18 +254,13 @@ func (service serviceSend) SendVideo(ctx context.Context, request domainSend.Vid
} }
//Send to WA server //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) dataWaVideo, err := os.ReadFile(videoPath)
if err != nil { if err != nil {
return response, err return response, err
} }
uploaded, err := service.WaCli.Upload(context.Background(), dataWaVideo, whatsmeow.MediaVideo) uploaded, err := service.WaCli.Upload(context.Background(), dataWaVideo, whatsmeow.MediaVideo)
if err != nil { 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) dataWaThumbnail, err := os.ReadFile(videoThumbnail)
if err != nil { 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) { 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", 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) request.ContactName, request.ContactName, request.ContactPhone, request.ContactPhone)
msgId := whatsmeow.GenerateMessageID() msgId := whatsmeow.GenerateMessageID()
@ -311,7 +309,7 @@ func (service serviceSend) SendContact(ctx context.Context, request domainSend.C
DisplayName: proto.String(request.ContactName), DisplayName: proto.String(request.ContactName),
Vcard: proto.String(msgVCard), Vcard: proto.String(msgVCard),
}} }}
ts, err := service.WaCli.SendMessage(ctx, recipient, "", msg)
ts, err := service.WaCli.SendMessage(ctx, dataWaRecipient, "", msg)
if err != nil { if err != nil {
return response, err 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) { 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() 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 { if err != nil {
return response, err 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) { 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() 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 { if err != nil {
return response, err 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) { 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() msgId := whatsmeow.GenerateMessageID()
msg := &waProto.Message{Conversation: proto.String(request.Message)} 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 { if err != nil {
return response, err return response, err
} }

28
src/services/user_service.go → src/services/user.go

@ -5,7 +5,7 @@ import (
"errors" "errors"
"fmt" "fmt"
domainUser "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/user" 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"
"go.mau.fi/whatsmeow/types" "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) { func (service userService) Info(_ context.Context, request domainUser.InfoRequest) (response domainUser.InfoResponse, err error) {
utils.MustLogin(service.WaCli)
var jids []types.JID 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) resp, err := service.WaCli.GetUserInfo(jids)
if err != nil { if err != nil {
return response, err return response, err
@ -41,7 +39,7 @@ func (service userService) Info(_ context.Context, request domainUser.InfoReques
device = append(device, domainUser.InfoResponseDataDevice{ device = append(device, domainUser.InfoResponseDataDevice{
User: j.User, User: j.User,
Agent: j.Agent, Agent: j.Agent,
Device: utils.GetPlatformName(int(j.Device)),
Device: whatsapp.GetPlatformName(int(j.Device)),
Server: j.Server, Server: j.Server,
AD: j.AD, 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) { 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 { if err != nil {
return response, err return response, err
} else if pic == nil { } 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) { func (service userService) MyListGroups(_ context.Context) (response domainUser.MyListGroupsResponse, err error) {
utils.MustLogin(service.WaCli)
whatsapp.MustLogin(service.WaCli)
groups, err := service.WaCli.GetJoinedGroups() groups, err := service.WaCli.GetJoinedGroups()
if err != nil { 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) { 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) resp, err := service.WaCli.TryFetchPrivacySettings(false)
if err != nil { if err != nil {

29
src/utils/errors.go

@ -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
}

82
src/validations/send_validation.go

@ -4,32 +4,32 @@ import (
"fmt" "fmt"
"github.com/aldinokemal/go-whatsapp-web-multidevice/config" "github.com/aldinokemal/go-whatsapp-web-multidevice/config"
domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" 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" "github.com/dustin/go-humanize"
validation "github.com/go-ozzo/ozzo-validation/v4" 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, err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required),
validation.Field(&request.Message, validation.Required), validation.Field(&request.Message, validation.Required),
) )
if err != nil { 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, err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required),
validation.Field(&request.Image, validation.Required), validation.Field(&request.Image, validation.Required),
) )
if err != nil { if err != nil {
panic(utils.ValidationError{
Message: err.Error(),
})
return pkgError.ValidationError(err.Error())
} }
availableMimes := map[string]bool{ availableMimes := map[string]bool{
@ -39,40 +39,38 @@ func ValidateSendImage(request domainSend.ImageRequest) {
} }
if !availableMimes[request.Image.Header.Get("Content-Type")] { 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, err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required),
validation.Field(&request.File, validation.Required), validation.Field(&request.File, validation.Required),
) )
if err != nil { if err != nil {
panic(utils.ValidationError{
Message: err.Error(),
})
return pkgError.ValidationError(err.Error())
} }
if request.File.Size > config.WhatsappSettingMaxFileSize { // 10MB if request.File.Size > config.WhatsappSettingMaxFileSize { // 10MB
maxSizeString := humanize.Bytes(uint64(config.WhatsappSettingMaxFileSize)) 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, err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required),
validation.Field(&request.Video, validation.Required), validation.Field(&request.Video, validation.Required),
) )
if err != nil { if err != nil {
panic(utils.ValidationError{
Message: err.Error(),
})
return pkgError.ValidationError(err.Error())
} }
availableMimes := map[string]bool{ availableMimes := map[string]bool{
@ -82,42 +80,40 @@ func ValidateSendVideo(request domainSend.VideoRequest) {
} }
if !availableMimes[request.Video.Header.Get("Content-Type")] { 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 if request.Video.Size > config.WhatsappSettingMaxVideoSize { // 30MB
maxSizeString := humanize.Bytes(uint64(config.WhatsappSettingMaxVideoSize)) 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, 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.ContactPhone, validation.Required),
validation.Field(&request.ContactName, validation.Required),
) )
if err != nil { if err != nil {
panic(utils.ValidationError{
Message: err.Error(),
})
return pkgError.ValidationError(err.Error())
} }
return nil
} }
func ValidateSendLink(request domainSend.LinkRequest) error { func ValidateSendLink(request domainSend.LinkRequest) error {
err := validation.ValidateStruct(&request, 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), validation.Field(&request.Caption, validation.Required),
) )
if err != nil { if err != nil {
return utils.ValidationError{
Message: err.Error(),
}
return pkgError.ValidationError(err.Error())
} }
return nil return nil
@ -125,13 +121,12 @@ func ValidateSendLink(request domainSend.LinkRequest) error {
func ValidateRevokeMessage(request domainSend.RevokeRequest) error { func ValidateRevokeMessage(request domainSend.RevokeRequest) error {
err := validation.ValidateStruct(&request, err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required),
validation.Field(&request.MessageID, validation.Required), validation.Field(&request.MessageID, validation.Required),
) )
if err != nil { if err != nil {
return utils.ValidationError{
Message: err.Error(),
}
return pkgError.ValidationError(err.Error())
} }
return nil return nil
@ -139,14 +134,13 @@ func ValidateRevokeMessage(request domainSend.RevokeRequest) error {
func ValidateUpdateMessage(request domainSend.UpdateMessageRequest) error { func ValidateUpdateMessage(request domainSend.UpdateMessageRequest) error {
err := validation.ValidateStruct(&request, err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required),
validation.Field(&request.MessageID, validation.Required), validation.Field(&request.MessageID, validation.Required),
validation.Field(&request.Message, validation.Required), validation.Field(&request.Message, validation.Required),
) )
if err != nil { if err != nil {
return utils.ValidationError{
Message: err.Error(),
}
return pkgError.ValidationError(err.Error())
} }
return nil return nil

431
src/validations/send_validation_test.go

@ -2,7 +2,9 @@ package validations
import ( import (
domainSend "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/send" 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" "github.com/stretchr/testify/assert"
"mime/multipart"
"testing" "testing"
) )
@ -16,25 +18,438 @@ func TestValidateSendMessage(t *testing.T) {
err any err any
}{ }{
{ {
name: "success phone & message normal",
name: "should success with phone and message",
args: args{request: domainSend.MessageRequest{ args: args{request: domainSend.MessageRequest{
Phone: "1728937129312@s.whatsapp.net", Phone: "1728937129312@s.whatsapp.net",
Message: "Hello this is testing", Message: "Hello this is testing",
}}, }},
err: nil, 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 { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { 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)
}) })
} }
} }

18
src/validations/user_validation.go

@ -2,29 +2,29 @@ package validations
import ( import (
domainUser "github.com/aldinokemal/go-whatsapp-web-multidevice/domains/user" 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" validation "github.com/go-ozzo/ozzo-validation/v4"
) )
func ValidateUserInfo(request domainUser.InfoRequest) {
func ValidateUserInfo(request domainUser.InfoRequest) error {
err := validation.ValidateStruct(&request, err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required), validation.Field(&request.Phone, validation.Required),
) )
if err != nil { 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, err := validation.ValidateStruct(&request,
validation.Field(&request.Phone, validation.Required), validation.Field(&request.Phone, validation.Required),
) )
if err != nil { if err != nil {
panic(utils.ValidationError{
Message: err.Error(),
})
return pkgError.ValidationError(err.Error())
} }
return nil
} }

74
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)
})
}
}

20
src/views/index.html

@ -29,7 +29,7 @@
<div class="ui success message" v-if="connected_devices != null"> <div class="ui success message" v-if="connected_devices != null">
<div class="header"> <div class="header">
Device is connected
Device has connected
</div> </div>
<p> <p>
Device ID: <b>[[ connected_devices[0].device ]]</b> Device ID: <b>[[ connected_devices[0].device ]]</b>
@ -224,8 +224,8 @@
</div> </div>
<div class="field"> <div class="field">
<label>Message</label> <label>Message</label>
<input v-model="message_text" type="text" placeholder="Hello this is message text"
aria-label="message">
<textarea v-model="message_text" type="text" placeholder="Hello this is message text"
aria-label="message"></textarea>
</div> </div>
</form> </form>
</div> </div>
@ -261,8 +261,8 @@
</div> </div>
<div class="field"> <div class="field">
<label>Caption</label> <label>Caption</label>
<input v-model="image_caption" type="text" placeholder="Hello this is image caption"
aria-label="caption">
<textarea v-model="image_caption" type="text" placeholder="Hello this is image caption"
aria-label="caption"></textarea>
</div> </div>
<div class="field"> <div class="field">
<label>View Once</label> <label>View Once</label>
@ -321,8 +321,8 @@
</div> </div>
<div class="field"> <div class="field">
<label>Caption</label> <label>Caption</label>
<input v-model="file_caption" type="text" placeholder="Type some caption (optional)..."
aria-label="caption">
<textarea v-model="file_caption" type="text" placeholder="Type some caption (optional)..."
aria-label="caption"></textarea>
</div> </div>
<div class="field" style="padding-bottom: 30px"> <div class="field" style="padding-bottom: 30px">
<label>File</label> <label>File</label>
@ -362,12 +362,12 @@
<label>Phone / Group ID</label> <label>Phone / Group ID</label>
<input v-model="video_phone" type="text" placeholder="6289..." <input v-model="video_phone" type="text" placeholder="6289..."
aria-label="phone"> aria-label="phone">
<input :value="video_phone_id" disabled aria-label="whatsapp_id">
</div> </div>
<div class="field"> <div class="field">
<label>Caption</label> <label>Caption</label>
<input v-model="video_caption" type="text" placeholder="Type some caption (optional)..."
aria-label="caption">
<input :value="video_phone_id" disabled aria-label="whatsapp_id">
<textarea v-model="video_caption" type="text" placeholder="Type some caption (optional)..."
aria-label="caption"></textarea>
</div> </div>
<div class="field"> <div class="field">
<label>View Once</label> <label>View Once</label>

Loading…
Cancel
Save