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 9b61f2f37d
commit 09ea20b088
15 changed files with 286 additions and 11 deletions

15
.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
.gitignore vendored Normal file
View File

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

View File

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

4
go.mod
View File

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

8
go.sum
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/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=

View File

@@ -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 &notificationConsumer{}
func NewNotificationConsumer(ctx context.Context) domain.NotificationConsumerPort {
return &notificationConsumer{
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()
}

27
internal/domain/eskiz.go Normal file
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)
}
type SMSServicePort interface {
SendSMS(string, string) error
}
type NotifierPort interface {
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
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)
}
}

View File

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

27
internal/redis/client.go Normal file
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
}

1
tmp/build-errors.log Normal file
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