+notification from site +version bump fix
Some checks failed
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/bump_version Pipeline failed
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
Some checks failed
ci/woodpecker/push/billing_fees Pipeline was successful
ci/woodpecker/push/bff Pipeline was successful
ci/woodpecker/push/db Pipeline was successful
ci/woodpecker/push/chain_gateway Pipeline was successful
ci/woodpecker/push/fx_ingestor Pipeline was successful
ci/woodpecker/push/fx_oracle Pipeline was successful
ci/woodpecker/push/bump_version Pipeline failed
ci/woodpecker/push/frontend Pipeline was successful
ci/woodpecker/push/nats Pipeline was successful
ci/woodpecker/push/ledger Pipeline was successful
ci/woodpecker/push/notification Pipeline was successful
ci/woodpecker/push/payments_orchestrator Pipeline was successful
This commit is contained in:
@@ -53,14 +53,21 @@ api:
|
||||
password_env: MAIL_SECRET
|
||||
host: "smtp.mail.ru"
|
||||
port: 465
|
||||
from: "MeetX Tech"
|
||||
from: "Sendico Tech"
|
||||
network_timeout: 10
|
||||
telegram:
|
||||
bot_token_env: TELEGRAM_BOT_TOKEN
|
||||
chat_id_env: TELEGRAM_CHAT_ID
|
||||
thread_id_env: TELEGRAM_THREAD_ID
|
||||
api_url: "https://api.telegram.org"
|
||||
timeout_seconds: 10
|
||||
parse_mode: ""
|
||||
|
||||
localizer:
|
||||
path: "./i18n"
|
||||
languages: ["en", "ru", "uk"]
|
||||
service_name: "Sendico"
|
||||
support: "support@meetx.space"
|
||||
support: "support@sendico.io"
|
||||
|
||||
app:
|
||||
|
||||
@@ -82,4 +89,4 @@ database:
|
||||
collection_name_env: PERMISSION_COLLECTION
|
||||
database_name_env: MONGO_DATABASE
|
||||
timeout_seconds_env: PERMISSION_TIMEOUT
|
||||
is_filtered_env: PERMISSION_IS_FILTERED
|
||||
is_filtered_env: PERMISSION_IS_FILTERED
|
||||
|
||||
@@ -1,6 +1,16 @@
|
||||
package notificationimp
|
||||
|
||||
type Config struct {
|
||||
Driver string `yaml:"driver"`
|
||||
Settings map[string]any `yaml:"settings,omitempty"`
|
||||
Driver string `yaml:"driver"`
|
||||
Settings map[string]any `yaml:"settings,omitempty"`
|
||||
Telegram *TelegramConfig `yaml:"telegram"`
|
||||
}
|
||||
|
||||
type TelegramConfig struct {
|
||||
BotTokenEnv string `yaml:"bot_token_env"`
|
||||
ChatIDEnv string `yaml:"chat_id_env"`
|
||||
ThreadIDEnv string `yaml:"thread_id_env,omitempty"`
|
||||
APIURL string `yaml:"api_url,omitempty"`
|
||||
ParseMode string `yaml:"parse_mode,omitempty"`
|
||||
TimeoutSeconds int `yaml:"timeout_seconds"`
|
||||
}
|
||||
|
||||
@@ -2,13 +2,17 @@ package notificationimp
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
"github.com/tech/sendico/notification/interface/api"
|
||||
mmail "github.com/tech/sendico/notification/internal/server/notificationimp/mail"
|
||||
"github.com/tech/sendico/notification/internal/server/notificationimp/telegram"
|
||||
"github.com/tech/sendico/pkg/domainprovider"
|
||||
na "github.com/tech/sendico/pkg/messaging/notifications/account"
|
||||
ni "github.com/tech/sendico/pkg/messaging/notifications/invitation"
|
||||
snotifications "github.com/tech/sendico/pkg/messaging/notifications/site"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
"github.com/tech/sendico/pkg/mservice"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
@@ -17,6 +21,7 @@ type NotificationAPI struct {
|
||||
logger mlogger.Logger
|
||||
client mmail.Client
|
||||
dp domainprovider.DomainProvider
|
||||
tg telegram.Client
|
||||
}
|
||||
|
||||
func (a *NotificationAPI) Name() mservice.Type {
|
||||
@@ -33,11 +38,22 @@ func CreateAPI(a api.API) (*NotificationAPI, error) {
|
||||
}
|
||||
p.logger = a.Logger().Named(p.Name())
|
||||
|
||||
if a.Config().Notification == nil {
|
||||
return nil, fmt.Errorf("notification configuration is missing")
|
||||
}
|
||||
if a.Config().Notification.Telegram == nil {
|
||||
return nil, fmt.Errorf("telegram configuration is missing")
|
||||
}
|
||||
|
||||
var err error
|
||||
if p.client, err = mmail.CreateMailClient(p.logger.Named("mailer"), p.Name(), a.Register().Producer(), a.Localizer(), a.DomainProvider(), a.Config().Notification); err != nil {
|
||||
p.logger.Error("Failed to create mail connection", zap.Error(err), zap.String("driver", a.Config().Notification.Driver))
|
||||
return nil, err
|
||||
}
|
||||
if p.tg, err = telegram.NewClient(p.logger.Named("telegram"), a.Config().Notification.Telegram); err != nil {
|
||||
p.logger.Error("Failed to create telegram client", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
db, err := a.DBFactory().NewAccountDB()
|
||||
if err != nil {
|
||||
@@ -64,5 +80,22 @@ func CreateAPI(a api.API) (*NotificationAPI, error) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if err := a.Register().Consumer(snotifications.NewDemoRequestProcessor(p.logger, p.onDemoRequest)); err != nil {
|
||||
p.logger.Error("Failed to register demo request handler", zap.Error(err))
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return p, nil
|
||||
}
|
||||
|
||||
func (a *NotificationAPI) onDemoRequest(ctx context.Context, request *model.DemoRequest) error {
|
||||
if a.tg == nil {
|
||||
return fmt.Errorf("telegram client is not configured")
|
||||
}
|
||||
if err := a.tg.SendDemoRequest(ctx, request); err != nil {
|
||||
a.logger.Warn("Failed to send demo request via telegram", zap.Error(err))
|
||||
return err
|
||||
}
|
||||
a.logger.Info("Demo request sent via Telegram", zap.String("name", request.Name), zap.String("organization", request.OrganizationName))
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -0,0 +1,149 @@
|
||||
package telegram
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
notconfig "github.com/tech/sendico/notification/interface/services/notification/config"
|
||||
"github.com/tech/sendico/pkg/mlogger"
|
||||
"github.com/tech/sendico/pkg/model"
|
||||
)
|
||||
|
||||
const defaultAPIURL = "https://api.telegram.org"
|
||||
|
||||
type Client interface {
|
||||
SendDemoRequest(ctx context.Context, request *model.DemoRequest) error
|
||||
}
|
||||
|
||||
type client struct {
|
||||
logger mlogger.Logger
|
||||
httpClient *http.Client
|
||||
apiURL string
|
||||
botToken string
|
||||
chatID string
|
||||
threadID *int64
|
||||
parseMode string
|
||||
}
|
||||
|
||||
type sendMessagePayload struct {
|
||||
ChatID string `json:"chat_id"`
|
||||
Text string `json:"text"`
|
||||
ParseMode string `json:"parse_mode,omitempty"`
|
||||
ThreadID *int64 `json:"message_thread_id,omitempty"`
|
||||
DisablePreview bool `json:"disable_web_page_preview,omitempty"`
|
||||
DisableNotify bool `json:"disable_notification,omitempty"`
|
||||
ProtectContent bool `json:"protect_content,omitempty"`
|
||||
}
|
||||
|
||||
func NewClient(logger mlogger.Logger, cfg *notconfig.TelegramConfig) (Client, error) {
|
||||
if cfg == nil {
|
||||
return nil, fmt.Errorf("telegram configuration is not provided")
|
||||
}
|
||||
token := strings.TrimSpace(os.Getenv(cfg.BotTokenEnv))
|
||||
if token == "" {
|
||||
return nil, fmt.Errorf("telegram bot token env %s is empty", cfg.BotTokenEnv)
|
||||
}
|
||||
chatID := strings.TrimSpace(os.Getenv(cfg.ChatIDEnv))
|
||||
if chatID == "" {
|
||||
return nil, fmt.Errorf("telegram chat id env %s is empty", cfg.ChatIDEnv)
|
||||
}
|
||||
|
||||
var threadID *int64
|
||||
if env := strings.TrimSpace(cfg.ThreadIDEnv); env != "" {
|
||||
raw := strings.TrimSpace(os.Getenv(env))
|
||||
if raw != "" {
|
||||
val, err := strconv.ParseInt(raw, 10, 64)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("telegram thread id env %s is invalid: %w", env, err)
|
||||
}
|
||||
threadID = &val
|
||||
}
|
||||
}
|
||||
|
||||
timeout := time.Duration(cfg.TimeoutSeconds) * time.Second
|
||||
if timeout <= 0 {
|
||||
timeout = 10 * time.Second
|
||||
}
|
||||
|
||||
apiURL := strings.TrimSpace(cfg.APIURL)
|
||||
if apiURL == "" {
|
||||
apiURL = defaultAPIURL
|
||||
}
|
||||
|
||||
return &client{
|
||||
logger: logger.Named("telegram"),
|
||||
httpClient: &http.Client{
|
||||
Timeout: timeout,
|
||||
},
|
||||
apiURL: strings.TrimRight(apiURL, "/"),
|
||||
botToken: token,
|
||||
chatID: chatID,
|
||||
threadID: threadID,
|
||||
parseMode: strings.TrimSpace(cfg.ParseMode),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (c *client) SendDemoRequest(ctx context.Context, request *model.DemoRequest) error {
|
||||
if request == nil {
|
||||
return fmt.Errorf("demo request payload is nil")
|
||||
}
|
||||
message := buildMessage(request)
|
||||
payload := sendMessagePayload{
|
||||
ChatID: c.chatID,
|
||||
Text: message,
|
||||
ParseMode: c.parseMode,
|
||||
ThreadID: c.threadID,
|
||||
DisablePreview: true,
|
||||
}
|
||||
return c.sendMessage(ctx, payload)
|
||||
}
|
||||
|
||||
func (c *client) sendMessage(ctx context.Context, payload sendMessagePayload) error {
|
||||
body, err := json.Marshal(&payload)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req, err := http.NewRequestWithContext(ctx, http.MethodPost, c.endpoint(), bytes.NewReader(body))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
|
||||
resp, err := c.httpClient.Do(req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer resp.Body.Close()
|
||||
|
||||
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
|
||||
return nil
|
||||
}
|
||||
respBody, _ := io.ReadAll(io.LimitReader(resp.Body, 4<<10))
|
||||
return fmt.Errorf("telegram sendMessage failed with status %d: %s", resp.StatusCode, string(respBody))
|
||||
}
|
||||
|
||||
func (c *client) endpoint() string {
|
||||
return fmt.Sprintf("%s/bot%s/sendMessage", c.apiURL, c.botToken)
|
||||
}
|
||||
|
||||
func buildMessage(req *model.DemoRequest) string {
|
||||
var builder strings.Builder
|
||||
builder.WriteString("New demo request received\n")
|
||||
builder.WriteString(fmt.Sprintf("Name: %s\n", req.Name))
|
||||
builder.WriteString(fmt.Sprintf("Organization: %s\n", req.OrganizationName))
|
||||
builder.WriteString(fmt.Sprintf("Phone: %s\n", req.Phone))
|
||||
builder.WriteString(fmt.Sprintf("Work email: %s\n", req.WorkEmail))
|
||||
builder.WriteString(fmt.Sprintf("Payout volume: %s\n", req.PayoutVolume))
|
||||
if req.Comment != "" {
|
||||
builder.WriteString(fmt.Sprintf("Comment: %s\n", req.Comment))
|
||||
}
|
||||
return builder.String()
|
||||
}
|
||||
Reference in New Issue
Block a user