Qo'shildi: Docker konfiguratsiyasi yangilandi, .env va .gitignore fayllari yaratildi, Redis va muhit o'zgaruvchilari qo'shildi.

This commit is contained in:
A'zamov Samandar
2025-04-21 13:08:09 +05:00
parent eae9f23267
commit 86353820da
17 changed files with 291 additions and 12 deletions

View File

@@ -99,6 +99,8 @@ services:
build: build:
context: ./notification context: ./notification
dockerfile: Dockerfile dockerfile: Dockerfile
volumes:
- ./notification:/app
networks: networks:
- lamenu - lamenu
profiles: profiles:
@@ -109,6 +111,8 @@ services:
- lamenu - lamenu
restart: always restart: always
image: redis image: redis
ports:
- 6379:6379
rabbitmq: rabbitmq:
image: rabbitmq:management image: rabbitmq:management

15
notification/.env.example Normal file
View File

@@ -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=""

3
notification/.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
.env
./bin
./main

View File

@@ -1,10 +1,20 @@
package main package main
import ( import (
"context"
"github.com/JscorpTech/notification/internal/consumer" "github.com/JscorpTech/notification/internal/consumer"
"github.com/JscorpTech/notification/internal/redis"
"github.com/joho/godotenv"
) )
var ctx = context.Background()
func main() { func main() {
notification := consumer.NewNotificationConsumer() if err := godotenv.Load(); err != nil {
panic(err)
}
redis.InitRedis()
notification := consumer.NewNotificationConsumer(ctx)
notification.Start() notification.Start()
} }

View File

@@ -3,9 +3,13 @@ module github.com/JscorpTech/notification
go 1.24.0 go 1.24.0
require ( 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/k0kubun/pp/v3 v3.4.1 // indirect
github.com/mattn/go-colorable v0.1.13 // indirect github.com/mattn/go-colorable v0.1.13 // indirect
github.com/mattn/go-isatty v0.0.16 // 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 github.com/streadway/amqp v1.1.0 // indirect
golang.org/x/sys v0.5.0 // indirect golang.org/x/sys v0.5.0 // indirect
golang.org/x/text v0.19.0 // indirect golang.org/x/text v0.19.0 // indirect

View File

@@ -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 h1:1WdFZDRRqe8UsR61N/2RoOZ3ziTEqgTPVqKrHeb779Y=
github.com/k0kubun/pp/v3 v3.4.1/go.mod h1:+SiNiqKnBfw1Nkj82Lh5bIeKQOAkPy6Xw9CAZUZ8npI= 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 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg= 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 h1:bq3VjFmv/sOjHtdEhmkEV4x1AJtvUvOJ2PFAZ5+peKQ=
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= 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 h1:py12iX8XSyI7aN/3dUT8DFIDJazNJsVJdxNVEpnQTZM=
github.com/streadway/amqp v1.1.0/go.mod h1:WYSrTEYHOXHd0nwFeUXAe2G2hRnQT+deZJJf88uS9Bg= 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= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=

View File

@@ -1,6 +1,7 @@
package consumer package consumer
import ( import (
"context"
"encoding/json" "encoding/json"
"fmt" "fmt"
"log" "log"
@@ -11,10 +12,14 @@ import (
"github.com/streadway/amqp" "github.com/streadway/amqp"
) )
type notificationConsumer struct{} type notificationConsumer struct {
Ctx context.Context
}
func NewNotificationConsumer() domain.NotificationConsumerPort { func NewNotificationConsumer(ctx context.Context) domain.NotificationConsumerPort {
return &notificationConsumer{} return &notificationConsumer{
Ctx: ctx,
}
} }
func (n *notificationConsumer) Start() { func (n *notificationConsumer) Start() {
@@ -54,7 +59,7 @@ func (n *notificationConsumer) Handler(msg amqp.Delivery) {
var ntf domain.NotifierPort var ntf domain.NotifierPort
switch notification.Type { switch notification.Type {
case "sms": case "sms":
ntf = notifier.NewSmsNotifier() ntf = notifier.NewSmsNotifier(n.Ctx)
case "email": case "email":
ntf = notifier.NewEmailNotifier() ntf = notifier.NewEmailNotifier()
} }

View File

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

View File

@@ -7,6 +7,9 @@ type NotificationConsumerPort interface {
Handler(amqp.Delivery) Handler(amqp.Delivery)
} }
type SMSServicePort interface {
SendSMS(string, string) error
}
type NotifierPort interface { type NotifierPort interface {
SendMessage([]string, string) SendMessage([]string, string)
} }

View File

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

View File

@@ -1,16 +1,25 @@
package notifier package notifier
import ( import (
"context"
"github.com/JscorpTech/notification/internal/domain" "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 { func NewSmsNotifier(ctx context.Context) domain.NotifierPort {
return &smsNotifier{} return &smsNotifier{
SMSServie: services.NewEskizSMSService(ctx),
}
} }
func (n *smsNotifier) SendMessage(to []string, body string) { func (n *smsNotifier) SendMessage(to []string, body string) {
pp.Print(to, body) for _, user := range to {
n.SMSServie.SendSMS(user, body)
}
} }

View File

@@ -2,12 +2,13 @@ package rabbitmq
import ( import (
"log" "log"
"os"
"github.com/streadway/amqp" "github.com/streadway/amqp"
) )
func Connect() (*amqp.Connection, *amqp.Channel, error) { 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 { if err != nil {
return nil, nil, err return nil, nil, err
} }

View File

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

View File

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

View File

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

View File

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

View File

@@ -11,7 +11,7 @@ exchange = Exchange('notification', type='direct')
producer = Producer(channel, exchange=exchange, routing_key="notification") producer = Producer(channel, exchange=exchange, routing_key="notification")
# Xabar yuborish # Xabar yuborish
message = {'type': 'email', 'message': 'Hello, Workers!', "to": ["+998888112309", "+998943990509"]} message = {'type': 'sms', 'message': "classcom.uz sayti va mobil ilovasiga ro'yxatdan o'tishingingiz uchun tasdiqlash kodi: 1234", "to": ["+998888112309", "+998943990509"]}
producer.publish(message) producer.publish(message)
print("Message sent to all workers!") print("Message sent to all workers!")