Browse Source

base skeleton using echo v4

pull/4/head
Dimas Restu H 4 years ago
commit
49678e36c2
  1. 5
      .dockerignore
  2. 1
      .env.example
  3. 5
      .gitignore
  4. 33
      .goreleaser.yml
  5. 3
      .vscode/settings.json
  6. 28
      Dockerfile
  7. 93
      Makefile
  8. 77
      README.md
  9. 99
      cmd/main/main.go
  10. 11
      go.mod
  11. 66
      go.sum
  12. 14
      internal/index/auth/jwt.go
  13. 57
      internal/index/index.go
  14. 5
      internal/index/model/request.go
  15. 5
      internal/index/model/response.go
  16. 35
      internal/route.go
  17. 20
      internal/whatsapp/model/request.go
  18. 45
      internal/whatsapp/whatsapp.go
  19. 35
      pkg/auth/auth.go
  20. 59
      pkg/auth/basic.go
  21. 88
      pkg/env/env.go
  22. 29
      pkg/log/log.go
  23. 20
      pkg/router/handler.go
  24. 27
      pkg/router/middleware.go
  25. 180
      pkg/router/response.go
  26. 32
      pkg/router/router.go
  27. 1
      pkg/whatsapp/whatsapp.go

5
.dockerignore

@ -0,0 +1,5 @@
dist
vendor
.env
**/.DS_Store
**/.gitkeep

1
.env.example

@ -0,0 +1 @@
API_BASE_URL=/api/v1/whatsapp

5
.gitignore

@ -0,0 +1,5 @@
dist
vendor
.env
*.DS_Store
!*.gitkeep

33
.goreleaser.yml

@ -0,0 +1,33 @@
before:
hooks:
- make vendor
builds:
- main: ./cmd/main/main.go
env:
- CGO_ENABLED=0
ldflags:
- -s -w
goos:
- darwin
- linux
- windows
goarch:
- 386
- amd64
archives:
- replacements:
darwin: macos
linux: linux
windows: windows
386: 32-bit
amd64: 64-bit
format: zip
checksum:
name_template: 'checksums.txt'
snapshot:
name_template: "{{ .Version }}_{{ .ShortCommit }}"
changelog:
filters:
exclude:
- '^docs:'
- '^test:'

3
.vscode/settings.json

@ -0,0 +1,3 @@
{
"go.inferGopath": false
}

28
Dockerfile

@ -0,0 +1,28 @@
# Builder Image
# ---------------------------------------------------
FROM dimaskiddo/alpine:go-1.15 AS go-builder
WORKDIR /usr/src/app
COPY . ./
RUN go mod download \
&& CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -a -o main cmd/main/main.go
# Final Image
# ---------------------------------------------------
FROM dimaskiddo/alpine:base
MAINTAINER Dimas Restu Hidayanto <dimas.restu@student.upi.edu>
ARG SERVICE_NAME="go-whatsapp-multidevice-rest"
ENV PATH $PATH:/usr/app/${SERVICE_NAME}
WORKDIR /usr/app/${SERVICE_NAME}
COPY --from=go-builder /usr/src/app/main ./main
EXPOSE 3000
CMD ["main"]

93
Makefile

@ -0,0 +1,93 @@
BUILD_CGO_ENABLED := 0
SERVICE_NAME := go-whatsapp-multidevice-rest
SERVICE_PORT := 3000
IMAGE_NAME := go-whatsapp-multidevice-rest
IMAGE_TAG := latest
REBASE_URL := "github.com/dimaskiddo/go-whatsapp-multidevice-rest"
COMMIT_MSG := "update improvement"
.PHONY:
.SILENT:
init:
make clean
GO111MODULE=on go mod init
init-dist:
mkdir -p dist
vendor:
make clean
GO111MODULE=on go mod vendor
release:
make vendor
make clean-dist
goreleaser --snapshot --skip-publish --rm-dist
echo "Release '$(SERVICE_NAME)' complete, please check dist directory."
publish:
make vendor
make clean-dist
GITHUB_TOKEN=$(GITHUB_TOKEN) goreleaser --rm-dist
echo "Publish '$(SERVICE_NAME)' complete, please check your repository releases."
build:
make vendor
CGO_ENABLED=$(BUILD_CGO_ENABLED) go build -ldflags="-s -w" -a -o $(SERVICE_NAME) cmd/main/main.go
echo "Build '$(SERVICE_NAME)' complete."
run:
go run cmd/main/*.go
clean-dist:
rm -rf dist
clean-build:
rm -f $(SERVICE_NAME)
clean:
make clean-dist
make clean-build
rm -rf vendor
commit:
make vendor
make clean
git add .
git commit -am $(COMMIT_MSG)
rebase:
rm -rf .git
find . -type f -iname "*.go*" -exec sed -i '' -e "s%github.com/dimaskiddo/go-whatsapp-multidevice-rest%$(REBASE_URL)%g" {} \;
git init
git remote add origin https://$(REBASE_URL).git
push:
git push origin master
pull:
git pull origin master
c-build:
docker build -t $(IMAGE_NAME):$(IMAGE_TAG) --build-arg SERVICE_NAME=$(SERVICE_NAME) .
c-run:
docker run -d -p $(SERVICE_PORT):$(SERVICE_PORT) --name $(SERVICE_NAME) --rm $(IMAGE_NAME):$(IMAGE_TAG)
make c-logs
c-shell:
docker exec -it $(SERVICE_NAME) bash
c-stop:
docker stop $(SERVICE_NAME)
c-logs:
docker logs $(SERVICE_NAME)
c-push:
docker push $(IMAGE_NAME):$(IMAGE_TAG)
c-clean:
docker rmi -f $(IMAGE_NAME):$(IMAGE_TAG)

77
README.md

@ -0,0 +1,77 @@
# Go WhatsApp Multi-Device Implementation in REST API
This repository contains example of implementation [go.mau.fi/whatsmeow](https://go.mau.fi/whatsmeow/) package. This example is using a [labstack/echo](https://github.com/labstack/echo) version 4.x.
## Getting Started
These instructions will get you a copy of the project up and running on your local machine for development and testing purposes.
See deployment for notes on how to deploy the project on a live system.
### Prerequisites
Prequisites package:
* Go (Go Programming Language)
* Make (Automated Execution using Makefile)
Optional package:
* GoReleaser (Go Automated Binaries Build)
* Docker (Application Containerization)
### Installing
Below is the instructions to make this codebase running:
* Create a Go Workspace directory and export it as the extended GOPATH directory
```
cd <your_go_workspace_directory>
export GOPATH=$GOPATH:"`pwd`"
```
* Under the Go Workspace directory create a source directory
```
mkdir -p src/github.com/dimaskiddo/go-whatsapp-multidevice-rest
```
* Move to the created directory and pull codebase
```
cd src/github.com/dimaskiddo/go-whatsapp-multidevice-rest
git clone -b master https://github.com/dimaskiddo/go-whatsapp-multidevice-rest.git .
```
* Run following command to pull dependecies package
```
make vendor
```
* Until this step you already can run this code by using this command
```
make run
```
## Running The Tests
Currently the test is not ready yet :)
## Deployment
To build this code to binaries for distribution purposes you can run following command:
```
make release
```
The build result will shown in build directory
## API Access
You can access any endpoint under **API_BASE_URL** environment variable which by default located at */*.
## Built With
* [Go](https://golang.org/) - Go Programming Languange
* [GoReleaser](https://github.com/goreleaser/goreleaser) - Go Automated Binaries Build
* [Make](https://www.gnu.org/software/make/) - GNU Make Automated Execution
* [Docker](https://www.docker.com/) - Application Containerization
## Authors
* **Dimas Restu Hidayanto** - *Initial Work* - [DimasKiddo](https://github.com/dimaskiddo)
See also the list of [contributors](https://github.com/dimaskiddo/go-whatsapp-multidevice-rest/contributors) who participated in this project
## Annotation
You can seek more information for the make command parameters in the [Makefile](https://github.com/dimaskiddo/go-whatsapp-multidevice-rest/-/raw/master/Makefile)

99
cmd/main/main.go

@ -0,0 +1,99 @@
package main
import (
"context"
"net/http"
"os"
"os/signal"
"syscall"
"time"
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/go-playground/validator/v10"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/log"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal"
)
type EchoValidator struct {
Validator *validator.Validate
}
func (ev *EchoValidator) Validate(i interface{}) error {
return ev.Validator.Struct(i)
}
func main() {
// Initialize Echo
e := echo.New()
// Router Recovery
e.Use(middleware.Recover())
// Router Compression
e.Use(middleware.GzipWithConfig(middleware.GzipConfig{
Level: router.GZipLevel,
}))
// Router CORS
e.Use(middleware.CORSWithConfig(middleware.CORSConfig{
AllowOrigins: []string{router.CORSOrigin},
AllowHeaders: []string{echo.HeaderOrigin, echo.HeaderContentType, echo.HeaderAccept, echo.HeaderAuthorization, echo.HeaderXRequestedWith},
AllowMethods: []string{echo.GET, echo.POST, echo.PUT, echo.PATCH, echo.DELETE, echo.OPTIONS},
}))
// Router Security
e.Use(middleware.SecureWithConfig(middleware.SecureConfig{
ContentTypeNosniff: "nosniff",
XSSProtection: "1; mode=block",
XFrameOptions: "SAMEORIGIN",
}))
// Router Body Size Limit
e.Use(middleware.BodyLimitWithConfig(middleware.BodyLimitConfig{
Limit: router.BodyLimit,
}))
// Router RealIP
e.Use(router.HttpRealIP())
// Router Validator
e.Validator = &EchoValidator{
Validator: validator.New(),
}
// Router Default Handler
e.HTTPErrorHandler = router.HttpErrorHandler
e.GET("/favicon.ico", router.ResponseNoContent)
// Router Load Routes
internal.Routes(e)
// Start Server
go func() {
err := e.Start(":3000")
if err != nil && err != http.ErrServerClosed {
log.Print(nil).Fatal(err.Error())
}
}()
// Watch for Shutdown Signal
sigShutdown := make(chan os.Signal, 1)
signal.Notify(sigShutdown, os.Interrupt)
signal.Notify(sigShutdown, syscall.SIGTERM)
<-sigShutdown
// Wait 5 Seconds for Graceful Shutdown
ctx, cancelShutdown := context.WithTimeout(context.Background(), 5*time.Second)
defer cancelShutdown()
// Try To Shutdown Server
err := e.Shutdown(ctx)
if err != nil {
log.Print(nil).Fatal(err.Error())
}
}

11
go.mod

@ -0,0 +1,11 @@
module github.com/dimaskiddo/go-whatsapp-multidevice-rest
go 1.15
require (
github.com/go-playground/validator/v10 v10.6.1
github.com/golang-jwt/jwt v3.2.2+incompatible
github.com/joho/godotenv v1.3.0
github.com/labstack/echo/v4 v4.7.2
github.com/sirupsen/logrus v1.8.1
)

66
go.sum

@ -0,0 +1,66 @@
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4=
github.com/go-playground/locales v0.13.0 h1:HyWk6mgj5qFqCT5fjGBuRArbVDfE4hi8+e8ceBS/t7Q=
github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8=
github.com/go-playground/universal-translator v0.17.0 h1:icxd5fm+REJzpZx7ZfpaD876Lmtgy7VtROAbHHXk8no=
github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA=
github.com/go-playground/validator/v10 v10.6.1 h1:W6TRDXt4WcWp4c4nf/G+6BkGdhiIo0k417gfr+V6u4I=
github.com/go-playground/validator/v10 v10.6.1/go.mod h1:xm76BBt941f7yWdGnI2DVPFFg1UK3YY04qifoXU3lOk=
github.com/golang-jwt/jwt v3.2.2+incompatible h1:IfV12K8xAKAnZqdXVzCZ+TOjboZ2keLg81eXfW3O+oY=
github.com/golang-jwt/jwt v3.2.2+incompatible/go.mod h1:8pz2t5EyA70fFQQSrl6XZXzqecmYZeUEB8OUGHkxJ+I=
github.com/joho/godotenv v1.3.0 h1:Zjp+RcGpHhGlrMbJzXTrZZPrWj+1vfm90La1wgB6Bhc=
github.com/joho/godotenv v1.3.0/go.mod h1:7hK45KPybAkOC6peb+G5yklZfMxEjkZhHbwpqxOKXbg=
github.com/labstack/echo/v4 v4.7.2 h1:Kv2/p8OaQ+M6Ex4eGimg9b9e6icoxA42JSlOR3msKtI=
github.com/labstack/echo/v4 v4.7.2/go.mod h1:xkCDAdFCIf8jsFQ5NnbK7oqaF/yU1A1X20Ltm0OvSks=
github.com/labstack/gommon v0.3.1 h1:OomWaJXm7xR6L1HmEtGyQf26TEn7V6X88mktX9kee9o=
github.com/labstack/gommon v0.3.1/go.mod h1:uW6kP17uPlLJsD3ijUYn3/M5bAxtlZhMI6m3MFxTMTM=
github.com/leodido/go-urn v1.2.0 h1:hpXL4XnriNwQ/ABnpepYM/1vCLWNDfUNts8dX3xTG6Y=
github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII=
github.com/mattn/go-colorable v0.1.11 h1:nQ+aFkoE2TMGc0b68U2OKSexC+eq46+XwZzWXHRmPYs=
github.com/mattn/go-colorable v0.1.11/go.mod h1:u5H1YNBxpqRaxsYJYSkiCWKzEfiAb1Gb520KVy5xxl4=
github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y=
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/sirupsen/logrus v1.8.1 h1:dJKuHgqk1NNQlqoA6BTlM1Wf9DOH3NBjQyu0h9+AZZE=
github.com/sirupsen/logrus v1.8.1/go.mod h1:yWOB1SBYBC5VeMP7gHvWumXLIWorT60ONWic61uBYv0=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1 h1:TVEnxayobAdVkhQfrfes2IzOB6o+z4roRkPF52WA1u4=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5 h1:HWj/xjIHfjYU5nVXpTM0s39J9CbLn7Cc5a7IC5rwsMQ=
golang.org/x/crypto v0.0.0-20210817164053-32db794688a5/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc=
golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg=
golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f h1:OfiFi4JbukWwe3lzw+xunroH1mnC1e2Gy5cxNJApiSY=
golang.org/x/net v0.0.0-20211015210444-4f30a5c0130f/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b h1:1VkfZQv42XQlA/jchYumAnv1UPo6RgF9rJFkTgZIxO4=
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324 h1:Hir2P/De0WpUhtrKGGjvSb2YxUgyZ7EFOSLIcSSpiwE=
golang.org/x/time v0.0.0-20201208040808-7e3f01d25324/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

14
internal/index/auth/jwt.go

@ -0,0 +1,14 @@
package model
import (
"github.com/golang-jwt/jwt"
)
type AuthJWTClaims struct {
Data AuthJWTClaimsPayload `json:"dat"`
jwt.StandardClaims
}
type AuthJWTClaimsPayload struct {
MSISDN string `json:"msisdn"`
}

57
internal/index/index.go

@ -0,0 +1,57 @@
package index
import (
"encoding/json"
"time"
"github.com/golang-jwt/jwt"
"github.com/labstack/echo/v4"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/auth"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router"
indexAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index/auth"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index/model"
)
// Index
func Index(c echo.Context) error {
return router.ResponseSuccess(c, "Go WhatsApp Multi-Device REST is running")
}
// Auth
func Auth(c echo.Context) error {
var reqAuthBasicInfo model.ReqAuthBasicInfo
var resAuthJWTData model.ResAuthJWTData
// Parse Basic Auth Information from Rewrited Body Request
// By Basic Auth Middleware
_ = json.NewDecoder(c.Request().Body).Decode(&reqAuthBasicInfo)
// Create JWT Claims
jwtClaims := &indexAuth.AuthJWTClaims{
indexAuth.AuthJWTClaimsPayload{
MSISDN: reqAuthBasicInfo.Username,
},
jwt.StandardClaims{
Issuer: "go-whatsapp-multidevice-rest",
IssuedAt: time.Now().Unix(),
ExpiresAt: time.Now().Add(time.Hour * time.Duration(auth.AuthJWTExpiredHour)).Unix(),
},
}
// Create JWT Token
jwtToken := jwt.NewWithClaims(jwt.SigningMethodHS256, jwtClaims)
// Generate Encoded JWT Token
jwtTokenEncoded, err := jwtToken.SignedString([]byte(auth.AuthJWTSecret))
if err != nil {
return router.ResponseInternalError(c, "")
}
// Set Encoded JWT Token as Response Data
resAuthJWTData.Token = jwtTokenEncoded
// Return JWT Token in JSON Response
return router.ResponseSuccessWithData(c, "", resAuthJWTData)
}

5
internal/index/model/request.go

@ -0,0 +1,5 @@
package model
type ReqAuthBasicInfo struct {
Username string
}

5
internal/index/model/response.go

@ -0,0 +1,5 @@
package model
type ResAuthJWTData struct {
Token string `json:"token"`
}

35
internal/route.go

@ -0,0 +1,35 @@
package internal
import (
"github.com/labstack/echo/v4"
"github.com/labstack/echo/v4/middleware"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/auth"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index"
indexAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index/auth"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/whatsapp"
)
func Routes(e *echo.Echo) {
// Route for Index
// ---------------------------------------------
e.GET(router.BaseURL, index.Index)
e.GET(router.BaseURL+"/auth", index.Auth, auth.BasicAuth())
// Route for WhatsApp
// ---------------------------------------------
authJWTConfig := middleware.JWTConfig{
Claims: &indexAuth.AuthJWTClaims{},
SigningKey: []byte(auth.AuthJWTSecret),
}
e.POST(router.BaseURL+"/login", whatsapp.Login, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/text", whatsapp.SendText, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/location", whatsapp.SendLocation, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/document", whatsapp.SendDocument, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/audio", whatsapp.SendAudio, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/image", whatsapp.SendImage, middleware.JWTWithConfig(authJWTConfig))
e.POST(router.BaseURL+"/send/video", whatsapp.SendVideo, middleware.JWTWithConfig(authJWTConfig))
}

20
internal/whatsapp/model/request.go

@ -0,0 +1,20 @@
package model
type ReqLogin struct {
Output string
}
type ReqSendMessage struct {
MSISDN string
Message string
QuotedID string
QuotedMessage string
}
type ReqSendLocation struct {
MSISDN string
Latitude float64
Longitude float64
QuotedID string
QuotedMessage string
}

45
internal/whatsapp/whatsapp.go

@ -0,0 +1,45 @@
package whatsapp
import (
"github.com/golang-jwt/jwt"
"github.com/labstack/echo/v4"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router"
indexAuth "github.com/dimaskiddo/go-whatsapp-multidevice-rest/internal/index/auth"
)
func getJWTPayload(c echo.Context) indexAuth.AuthJWTClaimsPayload {
jwtToken := c.Get("user").(*jwt.Token)
jwtClaims := jwtToken.Claims.(*indexAuth.AuthJWTClaims)
return jwtClaims.Data
}
func Login(c echo.Context) error {
return router.ResponseSuccess(c, "")
}
func SendText(c echo.Context) error {
return router.ResponseSuccess(c, "")
}
func SendLocation(c echo.Context) error {
return router.ResponseSuccess(c, "")
}
func SendDocument(c echo.Context) error {
return router.ResponseSuccess(c, "")
}
func SendImage(c echo.Context) error {
return router.ResponseSuccess(c, "")
}
func SendAudio(c echo.Context) error {
return router.ResponseSuccess(c, "")
}
func SendVideo(c echo.Context) error {
return router.ResponseSuccess(c, "")
}

35
pkg/auth/auth.go

@ -0,0 +1,35 @@
package auth
import (
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/env"
)
var AuthBasicUsername string
var AuthBasicPassword string
var AuthJWTSecret string
var AuthJWTExpiredHour int
func init() {
var err error
AuthBasicUsername, err = env.GetEnvString("AUTH_BASIC_USERNAME")
if err != nil {
AuthBasicUsername = "administrator"
}
AuthBasicPassword, err = env.GetEnvString("AUTH_BASIC_PASSWORD")
if err != nil {
AuthBasicPassword = "83e4060e-78e1-4fe5-9977-aeeccd46a2b8"
}
AuthJWTSecret, err = env.GetEnvString("AUTH_JWT_SECRET")
if err != nil {
AuthJWTSecret = "9e4eb4cf-be25-4a29-bba3-fefb5a30f6ab"
}
AuthJWTExpiredHour, err = env.GetEnvInt("AUTH_JWT_EXPIRED_HOUR")
if err != nil {
AuthJWTExpiredHour = 24
}
}

59
pkg/auth/basic.go

@ -0,0 +1,59 @@
package auth
import (
"encoding/base64"
"io/ioutil"
"strings"
"github.com/labstack/echo/v4"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/router"
)
// BasicAuth Function as Midleware for Basic Authorization
func BasicAuth() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
// Parse HTTP Header Authorization
authHeader := strings.SplitN(c.Request().Header.Get("Authorization"), " ", 2)
// Check HTTP Header Authorization Section
// Authorization Section Length Should Be 2
// The First Authorization Section Should Be "Basic"
if len(authHeader) != 2 || authHeader[0] != "Basic" {
return router.ResponseAuthenticate(c)
}
// The Second Authorization Section Should Be The Credentials Payload
// But We Should Decode it First From Base64 Encoding
authPayload, err := base64.StdEncoding.DecodeString(authHeader[1])
if err != nil {
return router.ResponseInternalError(c, "")
}
// Split Decoded Authorization Payload Into Username and Password Credentials
authCredentials := strings.SplitN(string(authPayload), ":", 2)
// Check Credentials Section
// It Should Have 2 Section, Username and Password
if len(authCredentials) != 2 {
return router.ResponseBadRequest(c, "")
}
// Validate Authentication Password
if authCredentials[1] != AuthBasicPassword {
return router.ResponseBadRequest(c, "Invalid Authentication")
}
// Make Credentials to JSON Format
authInformation := `{"username": "` + authCredentials[0] + `"}`
// Rewrite Body Content With Credentials in JSON Format
c.Request().Header.Set("Content-Type", "application/json")
c.Request().Body = ioutil.NopCloser(strings.NewReader(authInformation))
// Call Next Handler Function With Current Request
return next(c)
}
}
}

88
pkg/env/env.go

@ -0,0 +1,88 @@
package env
import (
"errors"
"os"
"strconv"
"strings"
_ "github.com/joho/godotenv/autoload"
)
func SanitizeEnv(envName string) (string, error) {
if len(envName) == 0 {
return "", errors.New("Environment Variable Name Should Not Empty")
}
retValue := strings.TrimSpace(os.Getenv(envName))
if len(retValue) == 0 {
return "", errors.New("Environment Variable '" + envName + "' Has an Empty Value")
}
return retValue, nil
}
func GetEnvString(envName string) (string, error) {
envValue, err := SanitizeEnv(envName)
if err != nil {
return "", err
}
return envValue, nil
}
func GetEnvBool(envName string) (bool, error) {
envValue, err := SanitizeEnv(envName)
if err != nil {
return false, err
}
retValue, err := strconv.ParseBool(envValue)
if err != nil {
return false, err
}
return retValue, nil
}
func GetEnvInt(envName string) (int, error) {
envValue, err := SanitizeEnv(envName)
if err != nil {
return 0, err
}
retValue, err := strconv.ParseInt(envValue, 0, 0)
if err != nil {
return 0, err
}
return int(retValue), nil
}
func GetEnvFloat32(envName string) (float32, error) {
envValue, err := SanitizeEnv(envName)
if err != nil {
return 0, err
}
retValue, err := strconv.ParseFloat(envValue, 32)
if err != nil {
return 0, err
}
return float32(retValue), nil
}
func GetEnvFloat64(envName string) (float64, error) {
envValue, err := SanitizeEnv(envName)
if err != nil {
return 0, err
}
retValue, err := strconv.ParseFloat(envValue, 64)
if err != nil {
return 0, err
}
return float64(retValue), nil
}

29
pkg/log/log.go

@ -0,0 +1,29 @@
package log
import (
"time"
"github.com/labstack/echo/v4"
"github.com/sirupsen/logrus"
)
var logger = logrus.New()
func Print(c echo.Context) *logrus.Entry {
logger.Formatter = &logrus.TextFormatter{
TimestampFormat: time.RFC3339,
FullTimestamp: true,
DisableColors: false,
ForceColors: true,
}
if c == nil {
return logger.WithFields(logrus.Fields{})
}
return logger.WithFields(logrus.Fields{
"remote_ip": c.Request().RemoteAddr,
"method": c.Request().Method,
"uri": c.Request().URL.String(),
})
}

20
pkg/router/handler.go

@ -0,0 +1,20 @@
package router
import (
"fmt"
"github.com/labstack/echo/v4"
)
func HttpErrorHandler(err error, c echo.Context) {
report, _ := err.(*echo.HTTPError)
response := &ResError{
Status: false,
Code: report.Code,
Error: fmt.Sprintf("%v", report.Message),
}
logError(c, response.Code, response.Error)
c.JSON(response.Code, response)
}

27
pkg/router/middleware.go

@ -0,0 +1,27 @@
package router
import (
"net/http"
"strings"
"github.com/labstack/echo/v4"
)
func HttpRealIP() echo.MiddlewareFunc {
return func(next echo.HandlerFunc) echo.HandlerFunc {
return func(c echo.Context) error {
if XForwardedFor := c.Request().Header.Get(http.CanonicalHeaderKey("X-Forwarded-For")); XForwardedFor != "" {
dataIndex := strings.Index(XForwardedFor, ", ")
if dataIndex == -1 {
dataIndex = len(XForwardedFor)
}
c.Request().RemoteAddr = XForwardedFor[:dataIndex]
} else if XRealIP := c.Request().Header.Get(http.CanonicalHeaderKey("X-Real-IP")); XRealIP != "" {
c.Request().RemoteAddr = XRealIP
}
return next(c)
}
}
}

180
pkg/router/response.go

@ -0,0 +1,180 @@
package router
import (
"fmt"
"net/http"
"strings"
"github.com/labstack/echo/v4"
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/log"
)
type ResSuccess struct {
Status bool `json:"status"`
Code int `json:"code"`
Message string `json:"message"`
}
type ResSuccessWithData struct {
Status bool `json:"status"`
Code int `json:"code"`
Message string `json:"message"`
Data interface{} `json:"data"`
}
type ResError struct {
Status bool `json:"status"`
Code int `json:"code"`
Error string `json:"error"`
}
func logSuccess(c echo.Context, code int, message string) {
statusMessage := http.StatusText(code)
if statusMessage == message || c.Request().RequestURI == BaseURL {
log.Print(c).Info(fmt.Sprintf("%d %v", code, statusMessage))
} else {
log.Print(c).Info(fmt.Sprintf("%d %v", code, message))
}
}
func logError(c echo.Context, code int, message string) {
statusMessage := http.StatusText(code)
if statusMessage == message {
log.Print(c).Error(fmt.Sprintf("%d %v", code, statusMessage))
} else {
log.Print(c).Error(fmt.Sprintf("%d %v", code, message))
}
}
func ResponseSuccess(c echo.Context, message string) error {
var response ResSuccess
response.Status = true
response.Code = http.StatusOK
if strings.TrimSpace(message) == "" {
message = http.StatusText(response.Code)
}
response.Message = message
logSuccess(c, response.Code, response.Message)
return c.JSON(response.Code, response)
}
func ResponseSuccessWithData(c echo.Context, message string, data interface{}) error {
var response ResSuccessWithData
response.Status = true
response.Code = http.StatusOK
if strings.TrimSpace(message) == "" {
message = http.StatusText(response.Code)
}
response.Message = message
response.Data = data
logSuccess(c, response.Code, response.Message)
return c.JSON(response.Code, response)
}
func ResponseCreated(c echo.Context, message string) error {
var response ResSuccess
response.Status = true
response.Code = http.StatusCreated
if strings.TrimSpace(message) == "" {
message = http.StatusText(response.Code)
}
response.Message = message
logSuccess(c, response.Code, response.Message)
return c.JSON(response.Code, response)
}
func ResponseNoContent(c echo.Context) error {
return c.NoContent(http.StatusNoContent)
}
func ResponseNotFound(c echo.Context, message string) error {
var response ResError
response.Status = false
response.Code = http.StatusNotFound
if strings.TrimSpace(message) == "" {
message = http.StatusText(response.Code)
}
response.Error = message
logError(c, response.Code, response.Error)
return c.JSON(response.Code, response)
}
func ResponseAuthenticate(c echo.Context) error {
c.Response().Header().Set("WWW-Authenticate", `Basic realm="Authentication Required"`)
return ResponseUnauthorized(c, "")
}
func ResponseUnauthorized(c echo.Context, message string) error {
var response ResError
response.Status = false
response.Code = http.StatusUnauthorized
if strings.TrimSpace(message) == "" {
message = http.StatusText(response.Code)
}
response.Error = message
logError(c, response.Code, response.Error)
return c.JSON(response.Code, response)
}
func ResponseBadRequest(c echo.Context, message string) error {
var response ResError
response.Status = false
response.Code = http.StatusBadRequest
if strings.TrimSpace(message) == "" {
message = http.StatusText(response.Code)
}
response.Error = message
logError(c, response.Code, response.Error)
return c.JSON(response.Code, response)
}
func ResponseInternalError(c echo.Context, message string) error {
var response ResError
response.Status = false
response.Code = http.StatusInternalServerError
if strings.TrimSpace(message) == "" {
message = http.StatusText(response.Code)
}
response.Error = message
logError(c, response.Code, response.Error)
return c.JSON(response.Code, response)
}
func ResponseBadGateway(c echo.Context, message string) error {
var response ResError
response.Status = false
response.Code = http.StatusBadGateway
if strings.TrimSpace(message) == "" {
message = http.StatusText(response.Code)
}
response.Error = message
logError(c, response.Code, response.Error)
return c.JSON(response.Code, response)
}

32
pkg/router/router.go

@ -0,0 +1,32 @@
package router
import (
"github.com/dimaskiddo/go-whatsapp-multidevice-rest/pkg/env"
)
var BaseURL, CORSOrigin, BodyLimit string
var GZipLevel int
func init() {
var err error
BaseURL, err = env.GetEnvString("API_BASE_URL")
if err != nil {
BaseURL = "/"
}
CORSOrigin, err = env.GetEnvString("HTTP_CORS_ORIGIN")
if err != nil {
CORSOrigin = "*"
}
BodyLimit, err = env.GetEnvString("HTTP_BODY_LIMIT_SIZE")
if err != nil {
BodyLimit = "8M"
}
GZipLevel, err = env.GetEnvInt("HTTP_GZIP_LEVEL")
if err != nil {
GZipLevel = 3
}
}

1
pkg/whatsapp/whatsapp.go

@ -0,0 +1 @@
package whatsapp
Loading…
Cancel
Save