diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..54202b8 --- /dev/null +++ b/.env.example @@ -0,0 +1,15 @@ +RABBITMQ_URL=amqp://guest:guest@127.0.0.1:5672/ + + +REDIS_ADDRESS=127.0.0.1:6379 +REDIS_PASSWORD= +REDIS_DB=0 + +ESKIZ_DOMAIN="https://notify.eskiz.uz/api" +ESKIZ_USER="admin@gmail.com" +ESKIZ_PASSWORD="password" +ESKIZ_FROM="4546" + +PMB_DOMAIN="" +PMB_USER="" +PMB_PASSWORD="" \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..61a32ec --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.env +./bin +./main \ No newline at end of file diff --git a/cmd/main.go b/cmd/main.go index 845e0aa..0a1748b 100644 --- a/cmd/main.go +++ b/cmd/main.go @@ -1,10 +1,20 @@ package main import ( + "context" + "github.com/JscorpTech/notification/internal/consumer" + "github.com/JscorpTech/notification/internal/redis" + "github.com/joho/godotenv" ) +var ctx = context.Background() + func main() { - notification := consumer.NewNotificationConsumer() + if err := godotenv.Load(); err != nil { + panic(err) + } + redis.InitRedis() + notification := consumer.NewNotificationConsumer(ctx) notification.Start() } diff --git a/go.mod b/go.mod index 8c97b6b..9c1fe81 100644 --- a/go.mod +++ b/go.mod @@ -3,9 +3,13 @@ module github.com/JscorpTech/notification go 1.24.0 require ( + github.com/cespare/xxhash/v2 v2.2.0 // indirect + github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect + github.com/joho/godotenv v1.5.1 // indirect github.com/k0kubun/pp/v3 v3.4.1 // indirect github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-isatty v0.0.16 // indirect + github.com/redis/go-redis/v9 v9.7.3 // indirect github.com/streadway/amqp v1.1.0 // indirect golang.org/x/sys v0.5.0 // indirect golang.org/x/text v0.19.0 // indirect diff --git a/go.sum b/go.sum index 27459d5..c6bb9eb 100644 --- a/go.sum +++ b/go.sum @@ -1,9 +1,17 @@ +github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44= +github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78= +github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= +github.com/joho/godotenv v1.5.1 h1:7eLL/+HRGLY0ldzfGMeQkb7vMd0as4CfYvUVzLqw0N0= +github.com/joho/godotenv v1.5.1/go.mod h1:f4LDr5Voq0i2e/R5DDNOoa2zzDfwtkZa6DnEwAbqwq4= github.com/k0kubun/pp/v3 v3.4.1 h1:1WdFZDRRqe8UsR61N/2RoOZ3ziTEqgTPVqKrHeb779Y= github.com/k0kubun/pp/v3 v3.4.1/go.mod h1:+SiNiqKnBfw1Nkj82Lh5bIeKQOAkPy6Xw9CAZUZ8npI= github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA= github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= github.com/mattn/go-isatty v0.0.16 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= +github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= +github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= github.com/streadway/amqp v1.1.0 h1:py12iX8XSyI7aN/3dUT8DFIDJazNJsVJdxNVEpnQTZM= github.com/streadway/amqp v1.1.0/go.mod h1:WYSrTEYHOXHd0nwFeUXAe2G2hRnQT+deZJJf88uS9Bg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/internal/consumer/notification.go b/internal/consumer/notification.go index a32571c..7543116 100644 --- a/internal/consumer/notification.go +++ b/internal/consumer/notification.go @@ -1,6 +1,7 @@ package consumer import ( + "context" "encoding/json" "fmt" "log" @@ -11,10 +12,14 @@ import ( "github.com/streadway/amqp" ) -type notificationConsumer struct{} +type notificationConsumer struct { + Ctx context.Context +} -func NewNotificationConsumer() domain.NotificationConsumerPort { - return ¬ificationConsumer{} +func NewNotificationConsumer(ctx context.Context) domain.NotificationConsumerPort { + return ¬ificationConsumer{ + Ctx: ctx, + } } func (n *notificationConsumer) Start() { @@ -54,7 +59,7 @@ func (n *notificationConsumer) Handler(msg amqp.Delivery) { var ntf domain.NotifierPort switch notification.Type { case "sms": - ntf = notifier.NewSmsNotifier() + ntf = notifier.NewSmsNotifier(n.Ctx) case "email": ntf = notifier.NewEmailNotifier() } diff --git a/internal/domain/eskiz.go b/internal/domain/eskiz.go new file mode 100644 index 0000000..9cccfaa --- /dev/null +++ b/internal/domain/eskiz.go @@ -0,0 +1,27 @@ +package domain + +type EskizLogin struct { + Email string `json:"email"` + Password string `json:"password"` +} + +type EskizLoginRes struct { + Message string + TokenType string + Data struct { + Token string + } +} + +type EskizMessage struct { + Phone string `json:"mobile_phone"` + Message string `json:"message"` + From string `json:"from"` + CallbackURL string `json:"callback_url"` +} + +type EskizMessageRes struct { + ID string `json:"id"` + Message string `json:"message"` + Status string `json:"status"` +} diff --git a/internal/domain/notification.go b/internal/domain/notification.go index be5bc44..8442b1d 100644 --- a/internal/domain/notification.go +++ b/internal/domain/notification.go @@ -7,6 +7,9 @@ type NotificationConsumerPort interface { Handler(amqp.Delivery) } +type SMSServicePort interface { + SendSMS(string, string) error +} type NotifierPort interface { SendMessage([]string, string) } diff --git a/internal/domain/playmobile.go b/internal/domain/playmobile.go new file mode 100644 index 0000000..0516a12 --- /dev/null +++ b/internal/domain/playmobile.go @@ -0,0 +1,20 @@ +package domain + +type PmbContent struct { + Text string `json:"text"` +} + +type PmbSMS struct { + Originator string `json:"originator"` + Content PmbContent `json:"content"` +} + +type PmbMessage struct { + Recipient string `json:"recipient"` + MessageID string `json:"message-id"` + Sms PmbSMS `json:"sms"` +} + +type PmbPayload struct { + Messages []PmbMessage +} diff --git a/internal/notifier/sms.go b/internal/notifier/sms.go index ab495d8..a4668f2 100644 --- a/internal/notifier/sms.go +++ b/internal/notifier/sms.go @@ -1,16 +1,25 @@ package notifier import ( + "context" + "github.com/JscorpTech/notification/internal/domain" - "github.com/k0kubun/pp/v3" + "github.com/JscorpTech/notification/internal/services" ) -type smsNotifier struct{} +type smsNotifier struct { + SMSServie domain.SMSServicePort + Ctx context.Context +} -func NewSmsNotifier() domain.NotifierPort { - return &smsNotifier{} +func NewSmsNotifier(ctx context.Context) domain.NotifierPort { + return &smsNotifier{ + SMSServie: services.NewEskizSMSService(ctx), + } } func (n *smsNotifier) SendMessage(to []string, body string) { - pp.Print(to, body) + for _, user := range to { + n.SMSServie.SendSMS(user, body) + } } diff --git a/internal/rabbitmq/connection.go b/internal/rabbitmq/connection.go index fa32da8..7aba1e6 100644 --- a/internal/rabbitmq/connection.go +++ b/internal/rabbitmq/connection.go @@ -2,12 +2,13 @@ package rabbitmq import ( "log" + "os" "github.com/streadway/amqp" ) func Connect() (*amqp.Connection, *amqp.Channel, error) { - conn, err := amqp.Dial("amqp://guest:guest@rabbitmq:5672/") + conn, err := amqp.Dial(os.Getenv("RABBITMQ_URL")) if err != nil { return nil, nil, err } diff --git a/internal/redis/client.go b/internal/redis/client.go new file mode 100644 index 0000000..7969a67 --- /dev/null +++ b/internal/redis/client.go @@ -0,0 +1,27 @@ +package redis + +import ( + "context" + "log" + "os" + "strconv" + + "github.com/redis/go-redis/v9" +) + +var RDB *redis.Client + +func InitRedis() { + DB, _ := strconv.Atoi(os.Getenv("REDIS_DB")) + RDB = redis.NewClient(&redis.Options{ + Addr: os.Getenv("REDIS_ADDRESS"), + Password: os.Getenv("REDIS_PASSWORD"), + DB: DB, + }) + + // Test connection + _, err := RDB.Ping(context.Background()).Result() + if err != nil { + log.Fatalf("Redisga ulanib bo'lmadi: %v", err) + } +} diff --git a/internal/services/eskiz.go b/internal/services/eskiz.go new file mode 100644 index 0000000..91309ca --- /dev/null +++ b/internal/services/eskiz.go @@ -0,0 +1,89 @@ +package services + +import ( + "bytes" + "context" + "encoding/json" + "net/http" + "os" + "time" + + "github.com/JscorpTech/notification/internal/domain" + "github.com/JscorpTech/notification/internal/redis" + "github.com/k0kubun/pp/v3" +) + +type eskizSMSService struct { + BaseURL string + Ctx context.Context +} + +// /broker-api/send +func NewEskizSMSService(ctx context.Context) domain.SMSServicePort { + return &eskizSMSService{ + Ctx: ctx, + BaseURL: os.Getenv("ESKIZ_DOMAIN"), + } +} + +func (e *eskizSMSService) Request(payload any, path string, isAuth bool, retry bool) (*http.Response, error) { + var buf bytes.Buffer + _ = json.NewEncoder(&buf).Encode(payload) + client := http.Client{ + Timeout: 60 * time.Second, + } + req, err := http.NewRequest("POST", e.BaseURL+path, &buf) + req.Header.Add("Content-Type", "application/json") + if isAuth { + req.Header.Add("Authorization", "Bearer "+e.GetToken(true)) + } + + if err != nil { + return nil, err + } + res, err := client.Do(req) + if res.StatusCode == http.StatusUnauthorized && retry { + pp.Print("Qayta urunish") + e.GetToken(false) + return e.Request(payload, path, isAuth, false) + } + return res, err +} + +func (e *eskizSMSService) GetToken(cache bool) string { + token, err := redis.RDB.Get(e.Ctx, "eskiz_token").Result() + if err == nil && cache { + pp.Print("Eskiz token topildi 😁") + return token + } + payload := domain.EskizLogin{ + Email: os.Getenv("ESKIZ_USER"), + Password: os.Getenv("ESKIZ_PASSWORD"), + } + res, err := e.Request(payload, "/auth/login", false, true) + if err != nil { + pp.Print(err.Error()) + } + var data domain.EskizLoginRes + _ = json.NewDecoder(res.Body).Decode(&data) + token = data.Data.Token + redis.RDB.Set(e.Ctx, "eskiz_token", token, 30*24*time.Hour) + pp.Print("Eskiz yangi token olindi 😔") + return token +} + +func (e *eskizSMSService) SendSMS(to, body string) error { + payload := domain.EskizMessage{ + Phone: to, + Message: body, + From: os.Getenv("ESKIZ_FROM"), + } + res, err := e.Request(payload, "/message/sms/send", true, true) + if err != nil { + return err + } + var data domain.EskizMessageRes + json.NewDecoder(res.Body).Decode(&data) + pp.Print(data) + return nil +} diff --git a/internal/services/playmobile.go b/internal/services/playmobile.go new file mode 100644 index 0000000..b2990cc --- /dev/null +++ b/internal/services/playmobile.go @@ -0,0 +1,53 @@ +package services + +import ( + "bytes" + "encoding/json" + "fmt" + "net/http" + "time" + + "github.com/JscorpTech/notification/internal/domain" +) + +type pmbSMSService struct { + BaseURL string +} + +// /broker-api/send +func NewPmbSMSService() domain.SMSServicePort { + return &pmbSMSService{ + BaseURL: "https://send.smsxabar.uz", + } +} + +func (e *pmbSMSService) SendSMS(to, body string) error { + client := http.Client{ + Timeout: 60 * time.Second, + } + payload := domain.PmbPayload{ + Messages: []domain.PmbMessage{ + { + Recipient: "+998888112309", + MessageID: "salomsdfs", + Sms: domain.PmbSMS{ + Originator: "3600", + Content: domain.PmbContent{ + Text: "salom", + }, + }, + }, + }, + } + var buf bytes.Buffer + _ = json.NewEncoder(&buf).Encode(payload) + req, _ := http.NewRequest("POST", e.BaseURL+"/broker-api/send", &buf) + res, err := client.Do(req) + if err != nil { + return err + } + var data map[string]interface{} + json.NewDecoder(res.Body).Decode(&data) + fmt.Print(data) + return nil +} diff --git a/tmp/build-errors.log b/tmp/build-errors.log new file mode 100644 index 0000000..94241fd --- /dev/null +++ b/tmp/build-errors.log @@ -0,0 +1 @@ +exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1exit status 1 \ No newline at end of file