From 9f8c787634af188efad9454c4b3a6b75909b920a Mon Sep 17 00:00:00 2001 From: Aleksandr Soloshenko Date: Fri, 9 Feb 2024 17:52:05 +0700 Subject: [PATCH 1/7] Added: ValidUntil support --- api/local.http | 16 +++++++++++++++- api/requests.http | 4 ++-- api/swagger.json | 5 +++++ api/swagger.yaml | 4 ++++ internal/sms-gateway/handlers/3rdparty.go | 3 +++ internal/sms-gateway/services/messages.go | 5 +++-- pkg/smsgateway/domain.go | 17 +++++++++++++++-- pkg/smsgateway/types.go | 7 +++++++ 8 files changed, 54 insertions(+), 7 deletions(-) create mode 100644 pkg/smsgateway/types.go diff --git a/api/local.http b/api/local.http index 85d3c6a..ff71f3a 100644 --- a/api/local.http +++ b/api/local.http @@ -9,7 +9,7 @@ Authorization: Basic {{localCredentials}} { "message": "{{$localDatetime iso8601}}", - "ttl": 600, + "validUntil": "2024-02-10T12:00:00+00:00", "phoneNumbers": [ "{{phone}}" ], @@ -22,6 +22,20 @@ POST {{localUrl}}/message HTTP/1.1 Content-Type: application/json Authorization: Basic {{localCredentials}} +{ + "message": "{{$localDatetime iso8601}}", + "ttl": 86400, + "phoneNumbers": [ + "{{phone}}" + ], + "withDeliveryReport": true +} + +### +POST {{localUrl}}/message HTTP/1.1 +Content-Type: application/json +Authorization: Basic {{localCredentials}} + { "message": "17wc9/ZRf1l84LHkEK3hgA==.aH1XrMHAeMyF4PeiavV3dk8o2fP0nSo92IqseLQfg14=", "ttl": 600, diff --git a/api/requests.http b/api/requests.http index b0f3a0a..25a3714 100644 --- a/api/requests.http +++ b/api/requests.http @@ -19,11 +19,11 @@ Authorization: Basic {{credentials}} { "message": "{{$localDatetime iso8601}}", - "ttl": 600, + "validUntil": "2024-02-10T12:00:00+07:00", "phoneNumbers": [ "{{phone}}" ], - "simNumber": 1, + "simNumber": {{$randomInt 1 2}}, "withDeliveryReport": true } diff --git a/api/swagger.json b/api/swagger.json index bb35043..56a451a 100644 --- a/api/swagger.json +++ b/api/swagger.json @@ -381,6 +381,11 @@ "minimum": 5, "example": 86400 }, + "validUntil": { + "description": "Время окончания жизни сообщения", + "type": "string", + "example": "2020-01-01T00:00:00Z" + }, "withDeliveryReport": { "description": "Запрашивать отчет о доставке", "type": "boolean", diff --git a/api/swagger.yaml b/api/swagger.yaml index 589b454..814f5e4 100644 --- a/api/swagger.yaml +++ b/api/swagger.yaml @@ -47,6 +47,10 @@ definitions: example: 86400 minimum: 5 type: integer + validUntil: + description: Время окончания жизни сообщения + example: "2020-01-01T00:00:00Z" + type: string withDeliveryReport: description: Запрашивать отчет о доставке example: true diff --git a/internal/sms-gateway/handlers/3rdparty.go b/internal/sms-gateway/handlers/3rdparty.go index 1273cd9..e2be14b 100644 --- a/internal/sms-gateway/handlers/3rdparty.go +++ b/internal/sms-gateway/handlers/3rdparty.go @@ -44,6 +44,9 @@ func (h *thirdPartyHandler) postMessage(user models.User, c *fiber.Ctx) error { if err := h.BodyParserValidator(c, &req); err != nil { return fiber.NewError(fiber.StatusBadRequest, err.Error()) } + if err := req.Validate(); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } if len(user.Devices) < 1 { return fiber.NewError(fiber.StatusBadRequest, "Нет ни одного устройства в учетной записи") diff --git a/internal/sms-gateway/services/messages.go b/internal/sms-gateway/services/messages.go index ac46252..ce6def0 100644 --- a/internal/sms-gateway/services/messages.go +++ b/internal/sms-gateway/services/messages.go @@ -79,11 +79,12 @@ func (s *MessagesService) SelectPending(deviceID string) ([]smsgateway.Message, result[i] = smsgateway.Message{ ID: v.ExtID, Message: v.Message, - TTL: ttl, SimNumber: v.SimNumber, WithDeliveryReport: types.AsPointer[bool](v.WithDeliveryReport), IsEncrypted: v.IsEncrypted, PhoneNumbers: s.recipientsToDomain(v.Recipients), + TTL: ttl, + ValidUntil: v.ValidUntil, } } @@ -145,7 +146,7 @@ func (s *MessagesService) Enqeue(device models.Device, message smsgateway.Messag } } - var validUntil *time.Time = nil + var validUntil *time.Time = message.ValidUntil if message.TTL != nil && *message.TTL > 0 { validUntil = types.AsPointer(time.Now().Add(time.Duration(*message.TTL) * time.Second)) } diff --git a/pkg/smsgateway/domain.go b/pkg/smsgateway/domain.go index aca591e..3bf8938 100644 --- a/pkg/smsgateway/domain.go +++ b/pkg/smsgateway/domain.go @@ -1,6 +1,9 @@ package smsgateway -type ProcessState string +import ( + "fmt" + "time" +) const ( MessageStatePending ProcessState = "Pending" // В ожидании @@ -14,11 +17,21 @@ const ( type Message struct { ID string `json:"id,omitempty" validate:"omitempty,max=36" example:"PyDmBQZZXYmyxMwED8Fzy"` // Идентификатор Message string `json:"message" validate:"required,max=65535" example:"Hello World!"` // Текст сообщения - TTL *uint64 `json:"ttl,omitempty" validate:"omitempty,min=5" example:"86400"` // Время жизни сообщения в секундах SimNumber *uint8 `json:"simNumber,omitempty" validate:"omitempty,max=3" example:"1"` // Номер сим-карты WithDeliveryReport *bool `json:"withDeliveryReport,omitempty" example:"true"` // Запрашивать отчет о доставке IsEncrypted bool `json:"isEncrypted,omitempty" example:"true"` // Зашифровано PhoneNumbers []string `json:"phoneNumbers" validate:"required,min=1,max=100,dive,required,min=10,max=128" example:"79990001234"` // Получатели + + TTL *uint64 `json:"ttl,omitempty" validate:"omitempty,min=5" example:"86400"` // Время жизни сообщения в секундах + ValidUntil *time.Time `json:"validUntil,omitempty" example:"2020-01-01T00:00:00Z"` // Время окончания жизни сообщения +} + +func (m Message) Validate() error { + if m.TTL != nil && m.ValidUntil != nil { + return fmt.Errorf("%w: ttl and validUntil", ErrConflictFields) + } + + return nil } // Состояние сообщения diff --git a/pkg/smsgateway/types.go b/pkg/smsgateway/types.go new file mode 100644 index 0000000..44c689d --- /dev/null +++ b/pkg/smsgateway/types.go @@ -0,0 +1,7 @@ +package smsgateway + +import "errors" + +type ProcessState string + +var ErrConflictFields = errors.New("conflict fields") From 09db6d92e8caec482e1626484267f98560c94f2b Mon Sep 17 00:00:00 2001 From: Aleksandr Soloshenko Date: Tue, 13 Feb 2024 22:47:55 +0700 Subject: [PATCH 2/7] [ci] use of golangci-lint --- .github/workflows/docker-publish.yml | 4 +- .github/workflows/golangci-lint.yml | 52 +++++++++++++++++++++++ Makefile | 5 ++- internal/sms-gateway/services/messages.go | 4 +- pkg/smsgateway/client.go | 2 +- pkg/smsgateway/client_test.go | 4 +- 6 files changed, 64 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/golangci-lint.yml diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index e5e468d..d0fe463 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -14,10 +14,10 @@ jobs: uses: actions/checkout@v4 # step 2: set up go - - name: Set up Go 1.20 + - name: Set up Go 1.21 uses: actions/setup-go@v4 with: - go-version: ">=1.20" + go-version: "1.21" # step 3: install dependencies - name: Install all Go dependencies diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml new file mode 100644 index 0000000..ba7dd01 --- /dev/null +++ b/.github/workflows/golangci-lint.yml @@ -0,0 +1,52 @@ +name: golangci-lint +on: + push: + +permissions: + contents: read + # Optional: allow read access to pull request. Use with `only-new-issues` option. + # pull-requests: read + +jobs: + golangci: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - uses: actions/setup-go@v4 + with: + go-version: "1.21" + cache: false + - name: golangci-lint + uses: golangci/golangci-lint-action@v3 + with: + # Require: The version of golangci-lint to use. + # When `install-mode` is `binary` (default) the value can be v1.2 or v1.2.3 or `latest` to use the latest version. + # When `install-mode` is `goinstall` the value can be v1.2.3, `latest`, or the hash of a commit. + version: latest + + # Optional: working directory, useful for monorepos + # working-directory: somedir + + # Optional: golangci-lint command line arguments. + # + # Note: By default, the `.golangci.yml` file should be at the root of the repository. + # The location of the configuration file can be changed by using `--config=` + # args: --timeout=30m --config=/my/path/.golangci.yml --issues-exit-code=0 + args: --timeout=5m + + # Optional: show only new issues if it's a pull request. The default value is `false`. + # only-new-issues: true + + # Optional: if set to true, then all caching functionality will be completely disabled, + # takes precedence over all other caching options. + # skip-cache: true + + # Optional: if set to true, then the action won't cache or restore ~/go/pkg. + # skip-pkg-cache: true + + # Optional: if set to true, then the action won't cache or restore ~/.cache/go-build. + # skip-build-cache: true + + # Optional: The mode to install golangci-lint. It can be 'binary' or 'goinstall'. + # install-mode: "goinstall" diff --git a/Makefile b/Makefile index 5aeef60..d686723 100644 --- a/Makefile +++ b/Makefile @@ -26,8 +26,11 @@ db-upgrade-raw: run: go run cmd/$(project_name)/main.go +lint: + golangci-lint run ./... + test: - go test -cover ./... + go test -race -coverprofile=coverage.out -covermode=atomic ./... build: go build -o tmp/$(project_name) ./cmd/$(project_name) diff --git a/internal/sms-gateway/services/messages.go b/internal/sms-gateway/services/messages.go index ac46252..8827af6 100644 --- a/internal/sms-gateway/services/messages.go +++ b/internal/sms-gateway/services/messages.go @@ -202,7 +202,9 @@ func (s *MessagesService) filterTimeouted(messages []models.Message) []models.Me v.Recipients[i].State = models.MessageStateFailed v.Recipients[i].Error = types.AsPointer(ErrorTTLExpired) } - s.Messages.UpdateState(&v) + if err := s.Messages.UpdateState(&v); err != nil { + s.Logger.Error("Can't update message state", zap.Error(err)) + } } } return result diff --git a/pkg/smsgateway/client.go b/pkg/smsgateway/client.go index b5e1e93..b9b9cbd 100644 --- a/pkg/smsgateway/client.go +++ b/pkg/smsgateway/client.go @@ -75,7 +75,7 @@ func (c *Client) doRequest(ctx context.Context, method, path string, headers map return err } defer func() { - io.Copy(io.Discard, resp.Body) + _, _ = io.Copy(io.Discard, resp.Body) resp.Body.Close() }() diff --git a/pkg/smsgateway/client_test.go b/pkg/smsgateway/client_test.go index f644590..e1d43c4 100644 --- a/pkg/smsgateway/client_test.go +++ b/pkg/smsgateway/client_test.go @@ -26,12 +26,12 @@ func TestClient_Send(t *testing.T) { if string(req) != `{"message":"","phoneNumbers":null}` { w.WriteHeader(http.StatusBadRequest) - w.Write(req) + _, _ = w.Write(req) return } w.WriteHeader(http.StatusCreated) - w.Write([]byte(`{}`)) + _, _ = w.Write([]byte(`{}`)) })) defer server.Close() From 419957f5e4d5bd0a9f8fd7d149f6bc307042362a Mon Sep 17 00:00:00 2001 From: Aleksandr Soloshenko Date: Thu, 15 Feb 2024 11:40:35 +0700 Subject: [PATCH 3/7] [docs] Python encryption example --- web/mkdocs/docs/privacy/encryption.md | 65 +++++++++++++++++++++++++++ 1 file changed, 65 insertions(+) diff --git a/web/mkdocs/docs/privacy/encryption.md b/web/mkdocs/docs/privacy/encryption.md index 4308c2a..7ac2107 100644 --- a/web/mkdocs/docs/privacy/encryption.md +++ b/web/mkdocs/docs/privacy/encryption.md @@ -175,4 +175,69 @@ class Encryptor { return crypto.pbkdf2Sync(passphrase, salt, iterations, keyLength, "sha1"); } } +``` + +### Python + +Based on [pycryptodome](https://pypi.org/project/pycryptodome/) + +```python +from Crypto.Cipher import AES +from Crypto.Hash import SHA1 +from Crypto.Protocol.KDF import PBKDF2 +from Crypto.Random import get_random_bytes +from Crypto.Util.Padding import pad, unpad + +class AESEncryptor(BaseEncryptor): + def encrypt(self, cleartext: str) -> str: + saltBytes = self._generate_salt() + key = self._generate_key(saltBytes, self.iterations) + + cipher = AES.new(key, AES.MODE_CBC, iv=saltBytes) + + encrypted_bytes = cipher.encrypt(pad(cleartext.encode(), AES.block_size)) + + salt = base64.b64encode(saltBytes).decode("utf-8") + encrypted = base64.b64encode(encrypted_bytes).decode("utf-8") + + return f"$aes-256-cbc/pbkdf2-sha1$i={self.iterations}${salt}${encrypted}" + + def decrypt(self, encrypted: str) -> str: + chunks = encrypted.split("$") + + if len(chunks) < 5: + raise ValueError("Invalid encryption format") + + if chunks[1] != "aes-256-cbc/pbkdf2-sha1": + raise ValueError("Unsupported algorithm") + + params = self._parse_params(chunks[2]) + if "i" not in params: + raise ValueError("Missing iteration count") + + iterations = int(params["i"]) + salt = base64.b64decode(chunks[-2]) + encrypted_bytes = base64.b64decode(chunks[-1]) + + key = self._generate_key(salt, iterations) + cipher = AES.new(key, AES.MODE_CBC, iv=salt) + + decrypted_bytes = unpad(cipher.decrypt(encrypted_bytes), AES.block_size) + + return decrypted_bytes.decode("utf-8") + + def _generate_salt(self) -> bytes: + return get_random_bytes(16) + + def _generate_key(self, salt: bytes, iterations: int) -> bytes: + return PBKDF2( + self.passphrase, + salt, + count=iterations, + dkLen=32, + hmac_hash_module=SHA1, + ) + + def _parse_params(self, params: str) -> t.Dict[str, str]: + return {k: v for k, v in [p.split("=") for p in params.split(",")]} ``` \ No newline at end of file From 6b399625a8bb0b6bee903da2903ff5e89fe2d6b1 Mon Sep 17 00:00:00 2001 From: Aleksandr Soloshenko Date: Thu, 15 Feb 2024 11:43:31 +0700 Subject: [PATCH 4/7] [ci][cd] don't lint, test and build in every push - PR only --- .github/workflows/docker-publish.yml | 1 - .github/workflows/golangci-lint.yml | 2 +- 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index d0fe463..4f4413f 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -1,7 +1,6 @@ name: docker-publish on: - push: pull_request: jobs: diff --git a/.github/workflows/golangci-lint.yml b/.github/workflows/golangci-lint.yml index ba7dd01..a40ac16 100644 --- a/.github/workflows/golangci-lint.yml +++ b/.github/workflows/golangci-lint.yml @@ -1,6 +1,6 @@ name: golangci-lint on: - push: + pull_request: permissions: contents: read From 53c09a233cf6d33f6e1057482e7a30a14ea257ed Mon Sep 17 00:00:00 2001 From: Aleksandr Soloshenko Date: Sat, 17 Feb 2024 15:29:07 +0700 Subject: [PATCH 5/7] [handlers] unify validation --- internal/sms-gateway/handlers/handler.go | 34 +-- internal/sms-gateway/handlers/handler_test.go | 205 ++++++++++++++++++ 2 files changed, 226 insertions(+), 13 deletions(-) create mode 100644 internal/sms-gateway/handlers/handler_test.go diff --git a/internal/sms-gateway/handlers/handler.go b/internal/sms-gateway/handlers/handler.go index ef6b2f1..bec4914 100644 --- a/internal/sms-gateway/handlers/handler.go +++ b/internal/sms-gateway/handlers/handler.go @@ -8,6 +8,10 @@ import ( "go.uber.org/zap" ) +type Validatable interface { + Validate() error +} + type Handler struct { Logger *zap.Logger Validator *validator.Validate @@ -18,11 +22,7 @@ func (h *Handler) BodyParserValidator(c *fiber.Ctx, out any) error { return fmt.Errorf("can't parse body: %w", err) } - if h.Validator == nil { - return nil - } - - return h.Validator.Struct(out) + return h.validateStruct(out) } func (h *Handler) QueryParserValidator(c *fiber.Ctx, out any) error { @@ -30,11 +30,7 @@ func (h *Handler) QueryParserValidator(c *fiber.Ctx, out any) error { return fmt.Errorf("can't parse query: %w", err) } - if h.Validator == nil { - return nil - } - - return h.Validator.Struct(out) + return h.validateStruct(out) } func (h *Handler) ParamsParserValidator(c *fiber.Ctx, out any) error { @@ -42,9 +38,21 @@ func (h *Handler) ParamsParserValidator(c *fiber.Ctx, out any) error { return fmt.Errorf("can't parse params: %w", err) } - if h.Validator == nil { - return nil + return h.validateStruct(out) +} + +func (h *Handler) validateStruct(out any) error { + if h.Validator != nil { + if err := h.Validator.Struct(out); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } } - return h.Validator.Struct(out) + if req, ok := out.(Validatable); ok { + if err := req.Validate(); err != nil { + return fiber.NewError(fiber.StatusBadRequest, err.Error()) + } + } + + return nil } diff --git a/internal/sms-gateway/handlers/handler_test.go b/internal/sms-gateway/handlers/handler_test.go new file mode 100644 index 0000000..4791ff9 --- /dev/null +++ b/internal/sms-gateway/handlers/handler_test.go @@ -0,0 +1,205 @@ +package handlers + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/go-playground/validator/v10" + "github.com/gofiber/fiber/v2" + "go.uber.org/zap" + "go.uber.org/zap/zaptest" +) + +type TestRequestBody struct { + Name string `json:"name" validate:"required"` + Age int `json:"age" validate:"required"` +} + +type TestRequestBodyNoValidate struct { + Name string `json:"name" validate:"required"` + Age int `json:"age" validate:"required"` +} + +func (t *TestRequestBody) Validate() error { + if t.Age < 18 { + return fmt.Errorf("must be at least 18 years old") + } + return nil +} + +type TestQueryParams struct { + Page int `query:"page" validate:"required"` +} + +type TestURLParams struct { + ID string `params:"id" validate:"required,uuid"` +} + +func TestHandler_BodyParserValidator(t *testing.T) { + logger := zaptest.NewLogger(t) + validate := validator.New() + + handler := &Handler{ + Logger: logger, + Validator: validate, + } + + app := fiber.New() + app.Post("/test", func(c *fiber.Ctx) error { + var body TestRequestBody + return handler.BodyParserValidator(c, &body) + }) + app.Post("/test2", func(c *fiber.Ctx) error { + var body TestRequestBodyNoValidate + return handler.BodyParserValidator(c, &body) + }) + + tests := []struct { + description string + path string + payload any + expectedStatus int + }{ + { + description: "Valid request body", + path: "/test", + payload: &TestRequestBody{Name: "John Doe", Age: 25}, + expectedStatus: fiber.StatusOK, + }, + { + description: "Invalid request body - missing name", + path: "/test", + payload: &TestRequestBody{Age: 25}, + expectedStatus: fiber.StatusBadRequest, + }, + { + description: "Invalid request body - age too low", + path: "/test", + payload: &TestRequestBody{Name: "John Doe", Age: 17}, + expectedStatus: fiber.StatusBadRequest, + }, + { + description: "Valid request body - no validation", + path: "/test2", + payload: &TestRequestBodyNoValidate{Name: "John Doe", Age: 17}, + expectedStatus: fiber.StatusOK, + }, + { + description: "No request body", + path: "/test", + payload: nil, + expectedStatus: fiber.StatusUnprocessableEntity, + }, + } + + for _, test := range tests { + t.Run(test.description, func(t *testing.T) { + var req *http.Request + if test.payload != nil { + bodyBytes, _ := json.Marshal(test.payload) + req = httptest.NewRequest("POST", test.path, bytes.NewReader(bodyBytes)) + req.Header.Set("Content-Type", "application/json") + } else { + req = httptest.NewRequest("POST", test.path, nil) + } + + resp, _ := app.Test(req) + if test.expectedStatus != resp.StatusCode { + t.Errorf("Expected status code %d, got %d", test.expectedStatus, resp.StatusCode) + } + }) + } +} + +func TestHandler_QueryParserValidator(t *testing.T) { + type fields struct { + Logger *zap.Logger + Validator *validator.Validate + } + type args struct { + c *fiber.Ctx + out any + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h := &Handler{ + Logger: tt.fields.Logger, + Validator: tt.fields.Validator, + } + if err := h.QueryParserValidator(tt.args.c, tt.args.out); (err != nil) != tt.wantErr { + t.Errorf("Handler.QueryParserValidator() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestHandler_ParamsParserValidator(t *testing.T) { + type fields struct { + Logger *zap.Logger + Validator *validator.Validate + } + type args struct { + c *fiber.Ctx + out any + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h := &Handler{ + Logger: tt.fields.Logger, + Validator: tt.fields.Validator, + } + if err := h.ParamsParserValidator(tt.args.c, tt.args.out); (err != nil) != tt.wantErr { + t.Errorf("Handler.ParamsParserValidator() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} + +func TestHandler_validateStruct(t *testing.T) { + type fields struct { + Logger *zap.Logger + Validator *validator.Validate + } + type args struct { + out any + } + tests := []struct { + name string + fields fields + args args + wantErr bool + }{ + // TODO: Add test cases. + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + h := &Handler{ + Logger: tt.fields.Logger, + Validator: tt.fields.Validator, + } + if err := h.validateStruct(tt.args.out); (err != nil) != tt.wantErr { + t.Errorf("Handler.validateStruct() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} From 4396a88b3757361288e91e1c6c7683557d9428d5 Mon Sep 17 00:00:00 2001 From: Aleksandr Soloshenko Date: Thu, 22 Feb 2024 18:14:42 +0700 Subject: [PATCH 6/7] [docs] FAQ on RESULT_ERROR_LIMIT_EXCEEDED error --- web/mkdocs/docs/faq.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/web/mkdocs/docs/faq.md b/web/mkdocs/docs/faq.md index 3c9d4b4..affcf16 100644 --- a/web/mkdocs/docs/faq.md +++ b/web/mkdocs/docs/faq.md @@ -52,3 +52,7 @@ To avoid mobile operator restrictions, we introduced a delay feature in version ## Can I use long or non-standard phone numbers? Yes, starting from [1.6.1](https://github.com/capcom6/android-sms-gateway/releases/tag/v1.6.1) of the app, our system allows the use of long or non-standard phone numbers, which may be common with M2M (machine-to-machine) SIM cards or other special cases. To bypass the standard phone number validation, simply add the query parameter `skipPhoneValidation=true` to your API request. Please note that with validation disabled, you are responsible for ensuring the correctness of the phone numbers. They should still follow the E.164 format, beginning with a '+' and containing only digits. + +## What does the `RESULT_ERROR_LIMIT_EXCEEDED` error mean SMS? + +The `RESULT_ERROR_LIMIT_EXCEEDED` error occurs when you've hit the sending limit imposed by your carrier or the Android operating system. This is a safeguard against spamming and typically happens if you try to send too many messages in a short period. To avoid this, try spacing out your messages or contact your carrier to inquire about their message sending limits. See also [How can I set up delays between sending messages?](#how-can-i-set-up-delays-between-sending-messages) From 52ed8e85d00e2c4c5b7a7b36c127fb0cfc286233 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 23 Feb 2024 02:53:12 +0000 Subject: [PATCH 7/7] Bump github.com/gofiber/fiber/v2 from 2.51.0 to 2.52.1 Bumps [github.com/gofiber/fiber/v2](https://github.com/gofiber/fiber) from 2.51.0 to 2.52.1. - [Release notes](https://github.com/gofiber/fiber/releases) - [Commits](https://github.com/gofiber/fiber/compare/v2.51.0...v2.52.1) --- updated-dependencies: - dependency-name: github.com/gofiber/fiber/v2 dependency-type: direct:production ... Signed-off-by: dependabot[bot] --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 8078d11..3cd4473 100644 --- a/go.mod +++ b/go.mod @@ -8,7 +8,7 @@ require ( firebase.google.com/go/v4 v4.12.1 github.com/capcom6/go-infra-fx v0.0.0-20240104165405-d2c3993a9516 github.com/go-playground/validator/v10 v10.16.0 - github.com/gofiber/fiber/v2 v2.51.0 + github.com/gofiber/fiber/v2 v2.52.1 github.com/jaevor/go-nanoid v1.3.0 github.com/nyaruka/phonenumbers v1.3.0 go.uber.org/fx v1.20.1 @@ -37,7 +37,7 @@ require ( github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect github.com/golang/protobuf v1.5.3 // indirect github.com/google/s2a-go v0.1.7 // indirect - github.com/google/uuid v1.4.0 // indirect + github.com/google/uuid v1.5.0 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.2 // indirect github.com/googleapis/gax-go/v2 v2.12.0 // indirect github.com/jinzhu/inflection v1.0.0 // indirect diff --git a/go.sum b/go.sum index 755186a..de69f88 100644 --- a/go.sum +++ b/go.sum @@ -82,8 +82,8 @@ github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrt github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= github.com/gofiber/contrib/fiberzap/v2 v2.1.2 h1:7Z1BqS1sYK9e9jTwqPcWx9qQt46PI8oeswgAp6YNZC4= github.com/gofiber/contrib/fiberzap/v2 v2.1.2/go.mod h1:ulCCQOdDYABGsOQfbndASmCsCN86hsC96iKoOTNYfy8= -github.com/gofiber/fiber/v2 v2.51.0 h1:JNACcZy5e2tGApWB2QrRpenTWn0fq0hkFm6k0C86gKQ= -github.com/gofiber/fiber/v2 v2.51.0/go.mod h1:xaQRZQJGqnKOQnbQw+ltvku3/h8QxvNi8o6JiJ7Ll0U= +github.com/gofiber/fiber/v2 v2.52.1 h1:1RoU2NS+b98o1L77sdl5mboGPiW+0Ypsi5oLmcYlgHI= +github.com/gofiber/fiber/v2 v2.52.1/go.mod h1:KEOE+cXMhXG0zHc9d8+E38hoX+ZN7bhOtgeF2oT6jrQ= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.4.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -123,8 +123,8 @@ github.com/google/s2a-go v0.1.7/go.mod h1:50CgR4k1jNlWBu4UfS4AcfhVe1r6pdZPygJ3R8 github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 h1:El6M4kTTCOh6aBiKaUGG7oYTSPP8MxqL4YI3kZKwcP4= github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3pAXIBCelhxNneeOaAeabZDe5s4K6zSpQ= github.com/google/uuid v1.1.2/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= -github.com/google/uuid v1.4.0 h1:MtMxsa51/r9yyhkyLsVeVt0B+BGQZzpQiTQ4eHZ8bc4= -github.com/google/uuid v1.4.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/google/uuid v1.5.0 h1:1p67kYwdtXjb0gL0BPiP1Av9wiZPo5A8z2cWkTZ+eyU= +github.com/google/uuid v1.5.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/googleapis/enterprise-certificate-proxy v0.3.2 h1:Vie5ybvEvT75RniqhfFxPRy3Bf7vr3h0cechB90XaQs= github.com/googleapis/enterprise-certificate-proxy v0.3.2/go.mod h1:VLSiSSBs/ksPL8kq3OBOQ6WRI2QnaFynd1DCjZ62+V0= github.com/googleapis/gax-go/v2 v2.12.0 h1:A+gCJKdRfqXkr+BIRGtZLibNXf0m1f9E4HG56etFpas=