Compare commits
16 Commits
cf2b2cd905
...
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
8c18978460 | ||
|
|
9fd19b8f7c | ||
|
|
b69a128a76 | ||
|
|
2bbe34f703 | ||
|
|
4267dec319 | ||
|
|
d8f25ed430 | ||
|
|
53bcbe16be | ||
|
|
83be69c277 | ||
|
|
1209499f6e | ||
|
|
bd8fba1d88 | ||
|
|
130f03f973 | ||
|
|
fbd002d8d4 | ||
|
|
71634fc19e | ||
|
|
40200a4649 | ||
|
|
e79718a3ea | ||
|
|
f38471756c |
12
.env.example
12
.env.example
@@ -1,15 +1,23 @@
|
|||||||
RABBITMQ_URL=amqp://guest:guest@127.0.0.1:5672/
|
RABBITMQ_URL=amqp://guest:guest@127.0.0.1:5672/
|
||||||
|
|
||||||
|
|
||||||
REDIS_ADDRESS=127.0.0.1:6379
|
REDIS_ADDRESS=redis:6379
|
||||||
REDIS_PASSWORD=
|
REDIS_PASSWORD=
|
||||||
REDIS_DB=0
|
REDIS_DB=0
|
||||||
|
|
||||||
|
BROKER=redis
|
||||||
|
TOPIC=notification
|
||||||
|
|
||||||
ESKIZ_DOMAIN="https://notify.eskiz.uz/api"
|
ESKIZ_DOMAIN="https://notify.eskiz.uz/api"
|
||||||
ESKIZ_USER="admin@gmail.com"
|
ESKIZ_USER="admin@gmail.com"
|
||||||
ESKIZ_PASSWORD="password"
|
ESKIZ_PASSWORD="password"
|
||||||
ESKIZ_FROM="4546"
|
ESKIZ_FROM="4546"
|
||||||
|
|
||||||
|
MAIL_DOMAIN=smtp.gmail.com
|
||||||
|
MAIL_USER="JscorpTech@gmail.com"
|
||||||
|
MAIL_PASSWORD="app password"
|
||||||
|
MAIL_PORT=587
|
||||||
|
|
||||||
PMB_DOMAIN=""
|
PMB_DOMAIN=""
|
||||||
PMB_USER=""
|
PMB_USER=""
|
||||||
PMB_PASSWORD=""
|
PMB_PASSWORD=""
|
||||||
|
|||||||
98
.github/workflows/deploy.yaml
vendored
Normal file
98
.github/workflows/deploy.yaml
vendored
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
|
||||||
|
name: Deploy to Production
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- main
|
||||||
|
|
||||||
|
env:
|
||||||
|
PROJECT_NAME: taxi-notification
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-deploy:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout code
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Copy env
|
||||||
|
run: |
|
||||||
|
cp .env.example .env
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Build Docker image
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
file: Dockerfile
|
||||||
|
push: false
|
||||||
|
load: true
|
||||||
|
tags: ${{ env.PROJECT_NAME }}:test
|
||||||
|
no-cache: true
|
||||||
|
|
||||||
|
- name: Login to Docker Hub
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Tag and push to Docker Hub
|
||||||
|
run: |
|
||||||
|
docker tag ${{ env.PROJECT_NAME }}:test ${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:latest
|
||||||
|
docker tag ${{ env.PROJECT_NAME }}:test ${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:${{ github.run_number }}
|
||||||
|
docker push ${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:latest
|
||||||
|
docker push ${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:${{ github.run_number }}
|
||||||
|
echo "SUCCESS TAGS: latest, ${{ github.run_number }}"
|
||||||
|
|
||||||
|
- name: Update stack.yaml and version
|
||||||
|
run: |
|
||||||
|
sed -i 's|image: ${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:.*|image: ${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:${{ github.run_number }}|' stack.yaml
|
||||||
|
|
||||||
|
- name: Commit and push updated version
|
||||||
|
run: |
|
||||||
|
git config user.name "github-actions[bot]"
|
||||||
|
git config user.email "github-actions[bot]@users.noreply.github.com"
|
||||||
|
git add .
|
||||||
|
git commit -m "🔄 Update image to ${{ github.run_number }} [CI SKIP]" || echo "No changes"
|
||||||
|
git pull origin main --rebase
|
||||||
|
git push origin main
|
||||||
|
|
||||||
|
- name: Deploy to server via SSH
|
||||||
|
uses: appleboy/ssh-action@v1.2.2
|
||||||
|
with:
|
||||||
|
host: ${{ secrets.HOST }}
|
||||||
|
username: ${{ secrets.USERNAME }}
|
||||||
|
# key: ${{ secrets.KEY }}
|
||||||
|
password: ${{ secrets.PASSWORD }}
|
||||||
|
port: ${{ secrets.PORT }}
|
||||||
|
script: |
|
||||||
|
PROJECTS=/opt/projects/
|
||||||
|
DIR=/opt/projects/${{ env.PROJECT_NAME }}/
|
||||||
|
|
||||||
|
if [ -d "$PROJECTS" ]; then
|
||||||
|
echo "projects papkasi mavjud"
|
||||||
|
else
|
||||||
|
mkdir -p $PROJECTS
|
||||||
|
echo "projects papkasi yaratildi"
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ -d "$DIR" ]; then
|
||||||
|
echo "loyiha mavjud"
|
||||||
|
else
|
||||||
|
cd $PROJECTS
|
||||||
|
git clone git@gitea.felixits.uz:${{ github.repository }}.git ${{ env.PROJECT_NAME }}
|
||||||
|
echo "Clone qilindi";
|
||||||
|
fi
|
||||||
|
|
||||||
|
cd $DIR
|
||||||
|
git fetch origin main
|
||||||
|
git reset --hard origin/main
|
||||||
|
docker stack deploy -c stack.yaml ${{ env.PROJECT_NAME }}
|
||||||
|
|
||||||
5
.gitignore
vendored
5
.gitignore
vendored
@@ -1,3 +1,4 @@
|
|||||||
.env
|
.env
|
||||||
./bin
|
bin
|
||||||
./main
|
main
|
||||||
|
release
|
||||||
@@ -15,6 +15,5 @@ FROM alpine
|
|||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
|
|
||||||
COPY --from=build /app/notification .
|
COPY --from=build /app/notification .
|
||||||
COPY ./.env /app/
|
|
||||||
|
|
||||||
CMD ["./notification"]
|
CMD ["./notification"]
|
||||||
|
|||||||
213
README.MD
213
README.MD
@@ -1,133 +1,170 @@
|
|||||||
# Notification Service
|
# Notification Service
|
||||||
|
|
||||||
A microservice for handling and delivering notifications through various channels like SMS and email using RabbitMQ as a message broker.
|
A flexible notification service that supports multiple message brokers (Redis, RabbitMQ) and notification channels (SMS, Email). This service is designed to handle asynchronous notification delivery in your applications.
|
||||||
|
|
||||||
## Overview
|
|
||||||
|
|
||||||
This notification service is designed as a standalone microservice that consumes notification requests from a RabbitMQ queue and routes them to the appropriate notification provider based on the notification type. Currently, it supports SMS and email notifications.
|
|
||||||
|
|
||||||
## Features
|
## Features
|
||||||
|
|
||||||
- Message consumption from RabbitMQ
|
- **Multiple Brokers**: Support for both Redis and RabbitMQ as message brokers
|
||||||
- Support for multiple notification channels (SMS, email)
|
- **Multiple Notification Channels**: SMS and Email notification support
|
||||||
- Extensible architecture for adding new notification types
|
- **Containerized**: Ready to deploy with Docker
|
||||||
- Asynchronous notification handling
|
- **Extensible Architecture**: Easy to add new notification channels or message brokers
|
||||||
|
|
||||||
## Architecture
|
## Architecture
|
||||||
|
|
||||||
The notification service follows a clean architecture approach:
|
The service follows a clean architecture pattern with the following components:
|
||||||
|
|
||||||
- **Domain Layer**: Contains core business logic and port interfaces
|
- **Broker**: Handles message subscription from different sources (Redis/RabbitMQ)
|
||||||
- **Infrastructure Layer**: Implements the ports with concrete adapters
|
- **Notifier**: Implements different notification channels (SMS/Email)
|
||||||
- **RabbitMQ**: Used as a message broker for consuming notification requests
|
- **Services**: Contains the business logic for each notification channel
|
||||||
|
- **Domain**: Defines interfaces and data models
|
||||||
|
|
||||||
## Installation
|
## Prerequisites
|
||||||
|
|
||||||
### Prerequisites
|
- Go 1.24 or higher
|
||||||
|
- Redis (for Redis broker)
|
||||||
- Go 1.x+
|
- RabbitMQ (for RabbitMQ broker)
|
||||||
- RabbitMQ server
|
- Docker (optional, for containerized deployment)
|
||||||
|
|
||||||
### Setup
|
|
||||||
|
|
||||||
1. Clone the repository:
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/JscorpTech/notification.git
|
|
||||||
cd notification
|
|
||||||
```
|
|
||||||
|
|
||||||
2. Install dependencies:
|
|
||||||
```bash
|
|
||||||
go mod download
|
|
||||||
```
|
|
||||||
|
|
||||||
3. Build the application:
|
|
||||||
```bash
|
|
||||||
go build -o notification-service
|
|
||||||
```
|
|
||||||
|
|
||||||
## Configuration
|
## Configuration
|
||||||
|
|
||||||
Configure your RabbitMQ connection and other settings in the appropriate configuration files.
|
Copy the provided `.env.example` to `.env` and update with your configuration:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cp .env.example .env
|
||||||
|
```
|
||||||
|
|
||||||
|
### Environment Variables
|
||||||
|
|
||||||
|
| Variable | Description | Example |
|
||||||
|
|----------|-------------|---------|
|
||||||
|
| BROKER | Message broker to use (redis or rabbitmq) | redis |
|
||||||
|
| TOPIC | Topic/queue name for notifications | notification |
|
||||||
|
| REDIS_ADDRESS | Redis server address | 127.0.0.1:6379 |
|
||||||
|
| REDIS_PASSWORD | Redis password (if any) | |
|
||||||
|
| REDIS_DB | Redis database number | 0 |
|
||||||
|
| RABBITMQ_URL | RabbitMQ connection URL | amqp://guest:guest@localhost:5672/ |
|
||||||
|
| ESKIZ_DOMAIN | Eskiz SMS API domain | https://notify.eskiz.uz/api |
|
||||||
|
| ESKIZ_USER | Eskiz SMS API username | admin@example.com |
|
||||||
|
| ESKIZ_PASSWORD | Eskiz SMS API password | password |
|
||||||
|
| ESKIZ_FROM | Eskiz SMS sender ID | 4546 |
|
||||||
|
| MAIL_DOMAIN | SMTP server domain | smtp.gmail.com |
|
||||||
|
| MAIL_USER | SMTP username | notification@example.com |
|
||||||
|
| MAIL_PASSWORD | SMTP password | yourpassword |
|
||||||
|
| MAIL_PORT | SMTP port | 587 |
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Local Development
|
||||||
|
|
||||||
|
1. Clone the repository
|
||||||
|
```bash
|
||||||
|
git clone https://github.com/JscorpTech/notification.git
|
||||||
|
cd notification
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Install dependencies
|
||||||
|
```bash
|
||||||
|
go mod download
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Build and run the application
|
||||||
|
```bash
|
||||||
|
go build -o notification ./cmd/main.go
|
||||||
|
./notification
|
||||||
|
```
|
||||||
|
|
||||||
|
### Docker Deployment
|
||||||
|
|
||||||
|
Build and run using Docker:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t notification-service .
|
||||||
|
docker run -p 8080:8080 --env-file .env notification-service
|
||||||
|
```
|
||||||
|
|
||||||
|
Or using Docker Compose:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker-compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
### Running the service
|
### Message Format
|
||||||
|
|
||||||
```bash
|
The service expects messages in the following JSON format:
|
||||||
./notification-service
|
|
||||||
```
|
|
||||||
|
|
||||||
This will start the notification consumer that listens for incoming notification requests.
|
|
||||||
|
|
||||||
### Sending a notification
|
|
||||||
|
|
||||||
Notifications should be published to the RabbitMQ exchange with the following JSON format:
|
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"type": "email",
|
"type": "email",
|
||||||
"message": "Hello, this is a test notification.",
|
"message": "Subject: Welcome\r\n\r\nHello, welcome to our service.",
|
||||||
"to": ["user@example.com"]
|
"to": ["user@example.com"]
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Python example
|
For SMS notifications:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"type": "sms",
|
||||||
|
"message": "Your verification code is 1234",
|
||||||
|
"to": ["+998901234567"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Sending Messages
|
||||||
|
|
||||||
|
#### Using Redis
|
||||||
|
|
||||||
|
```python
|
||||||
|
import redis
|
||||||
|
import json
|
||||||
|
|
||||||
|
r = redis.StrictRedis(host='127.0.0.1', port=6379, db=0)
|
||||||
|
|
||||||
|
message = {
|
||||||
|
'type': 'email',
|
||||||
|
'message': "Subject: Welcome\r\n\r\nWelcome to our service!",
|
||||||
|
'to': ["user@example.com"]
|
||||||
|
}
|
||||||
|
|
||||||
|
r.rpush('notification', json.dumps(message))
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Using RabbitMQ
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from kombu import Connection, Exchange, Producer
|
from kombu import Connection, Exchange, Producer
|
||||||
|
|
||||||
# RabbitMQ ulanishi
|
|
||||||
rabbit_url = 'amqp://guest:guest@127.0.0.1:5672/'
|
rabbit_url = 'amqp://guest:guest@127.0.0.1:5672/'
|
||||||
connection = Connection(rabbit_url)
|
connection = Connection(rabbit_url)
|
||||||
channel = connection.channel()
|
channel = connection.channel()
|
||||||
|
|
||||||
exchange = Exchange('notification', type='direct')
|
exchange = Exchange('notification', type='direct')
|
||||||
|
|
||||||
# Producer yaratish
|
|
||||||
producer = Producer(channel, exchange=exchange, routing_key="notification")
|
producer = Producer(channel, exchange=exchange, routing_key="notification")
|
||||||
|
|
||||||
# Xabar yuborish
|
message = {
|
||||||
message = {'type': 'sms', 'message': "classcom.uz sayti va mobil ilovasiga ro'yxatdan o'tishingingiz uchun tasdiqlash kodi: 1234", "to": ["+998888112309", "+998943990509"]}
|
'type': 'sms',
|
||||||
|
'message': "Your verification code is 1234",
|
||||||
|
'to': ["+998901234567"]
|
||||||
|
}
|
||||||
producer.publish(message)
|
producer.publish(message)
|
||||||
|
|
||||||
print("Message sent to all workers!")
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Available notification types:
|
## Adding New Notification Channels
|
||||||
- `email`: For email notifications
|
|
||||||
- `sms`: For SMS notifications
|
|
||||||
|
|
||||||
## Project Structure
|
1. Create a new notifier implementation in `internal/notifier/`
|
||||||
|
2. Implement the `domain.NotifierPort` interface
|
||||||
|
3. Add the new notifier type to the `Handler` function in `internal/consumer/notification.go`
|
||||||
|
|
||||||
```
|
## License
|
||||||
notification/
|
|
||||||
├── cmd/
|
MIT License - See [LICENSE](LICENSE) file for details.
|
||||||
│ └── main.go # Entry point
|
|
||||||
├── internal/
|
|
||||||
│ ├── domain/
|
|
||||||
│ │ └── ports.go # Interfaces
|
|
||||||
│ ├── notifier/
|
|
||||||
│ │ ├── email.go # Email notification implementation
|
|
||||||
│ │ └── sms.go # SMS notification implementation
|
|
||||||
│ ├── rabbitmq/
|
|
||||||
│ │ └── connection.go # RabbitMQ connection handling
|
|
||||||
│ └── consumer/
|
|
||||||
│ └── consumer.go # Implementation of the notification consumer
|
|
||||||
└── README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## Contributing
|
## Contributing
|
||||||
|
|
||||||
1. Fork the repository
|
1. Fork the repository
|
||||||
2. Create your feature branch: `git checkout -b feature/my-new-feature`
|
2. Create a feature branch (`git checkout -b feature/my-feature`)
|
||||||
3. Commit your changes: `git commit -am 'Add some feature'`
|
3. Commit your changes (`git commit -am 'Add some feature'`)
|
||||||
4. Push to the branch: `git push origin feature/my-new-feature`
|
4. Push to the branch (`git push origin feature/my-feature`)
|
||||||
5. Submit a pull request
|
5. Create a new Pull Request
|
||||||
|
|
||||||
## License
|
|
||||||
|
|
||||||
[Add your license here]
|
|
||||||
|
|
||||||
## Contact
|
|
||||||
|
|
||||||
JscorpTech - [GitHub](https://github.com/JscorpTech)
|
|
||||||
@@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"log"
|
||||||
|
|
||||||
"github.com/JscorpTech/notification/internal/consumer"
|
"github.com/JscorpTech/notification/internal/consumer"
|
||||||
"github.com/JscorpTech/notification/internal/redis"
|
"github.com/JscorpTech/notification/internal/redis"
|
||||||
@@ -12,7 +13,7 @@ var ctx = context.Background()
|
|||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
if err := godotenv.Load(); err != nil {
|
if err := godotenv.Load(); err != nil {
|
||||||
panic(err)
|
log.Println(".env not load")
|
||||||
}
|
}
|
||||||
redis.InitRedis()
|
redis.InitRedis()
|
||||||
notification := consumer.NewNotificationConsumer(ctx)
|
notification := consumer.NewNotificationConsumer(ctx)
|
||||||
|
|||||||
46
internal/broker/rabbitmq.go
Normal file
46
internal/broker/rabbitmq.go
Normal file
@@ -0,0 +1,46 @@
|
|||||||
|
package broker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/JscorpTech/notification/internal/domain"
|
||||||
|
"github.com/JscorpTech/notification/internal/rabbitmq"
|
||||||
|
)
|
||||||
|
|
||||||
|
type rabbitMQBroker struct {
|
||||||
|
Ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRabbitMQBroker(ctx context.Context) domain.BrokerPort {
|
||||||
|
return &rabbitMQBroker{
|
||||||
|
Ctx: ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r rabbitMQBroker) Subscribe(topic string, handler func(domain.NotificationMsg)) {
|
||||||
|
conn, ch, err := rabbitmq.Connect()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
defer ch.Close()
|
||||||
|
|
||||||
|
ch.ExchangeDeclare(topic, "direct", true, false, false, false, nil)
|
||||||
|
q, _ := ch.QueueDeclare(topic, true, false, false, false, nil)
|
||||||
|
ch.QueueBind(q.Name, topic, topic, false, nil)
|
||||||
|
|
||||||
|
msgs, _ := ch.Consume(q.Name, "", true, false, false, false, nil)
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
for msg := range msgs {
|
||||||
|
var notification domain.NotificationMsg
|
||||||
|
if err := json.Unmarshal(msg.Body, ¬ification); err != nil {
|
||||||
|
fmt.Print(err.Error())
|
||||||
|
}
|
||||||
|
go handler(notification)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
39
internal/broker/redis.go
Normal file
39
internal/broker/redis.go
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
package broker
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/JscorpTech/notification/internal/domain"
|
||||||
|
"github.com/JscorpTech/notification/internal/redis"
|
||||||
|
)
|
||||||
|
|
||||||
|
type redisBroker struct {
|
||||||
|
Ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewRedisBroker(ctx context.Context) domain.BrokerPort {
|
||||||
|
return &redisBroker{
|
||||||
|
Ctx: ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (r redisBroker) Subscribe(topic string, handler func(domain.NotificationMsg)) {
|
||||||
|
go func() {
|
||||||
|
for {
|
||||||
|
var notification domain.NotificationMsg
|
||||||
|
val, err := redis.RDB.BLPop(r.Ctx, 0, topic).Result()
|
||||||
|
if err != nil {
|
||||||
|
fmt.Print(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
if err := json.Unmarshal([]byte(val[1]), ¬ification); err != nil {
|
||||||
|
fmt.Print(err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
go handler(notification)
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
|
||||||
|
}
|
||||||
@@ -2,14 +2,12 @@ package consumer
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"os"
|
||||||
|
|
||||||
|
"github.com/JscorpTech/notification/internal/broker"
|
||||||
"github.com/JscorpTech/notification/internal/domain"
|
"github.com/JscorpTech/notification/internal/domain"
|
||||||
"github.com/JscorpTech/notification/internal/notifier"
|
"github.com/JscorpTech/notification/internal/notifier"
|
||||||
"github.com/JscorpTech/notification/internal/rabbitmq"
|
|
||||||
"github.com/streadway/amqp"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
type notificationConsumer struct {
|
type notificationConsumer struct {
|
||||||
@@ -23,39 +21,26 @@ func NewNotificationConsumer(ctx context.Context) domain.NotificationConsumerPor
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (n *notificationConsumer) Start() {
|
func (n *notificationConsumer) Start() {
|
||||||
conn, ch, err := rabbitmq.Connect()
|
|
||||||
if err != nil {
|
brokerName := os.Getenv("BROKER")
|
||||||
log.Fatal(err)
|
if brokerName == "" {
|
||||||
|
brokerName = "redis"
|
||||||
}
|
}
|
||||||
defer conn.Close()
|
var brokerService domain.BrokerPort
|
||||||
defer ch.Close()
|
switch brokerName {
|
||||||
|
case "redis":
|
||||||
exchangeName := "notification"
|
brokerService = broker.NewRedisBroker(n.Ctx)
|
||||||
queueName := "notification"
|
case "rabbitmq":
|
||||||
routingKey := "notification"
|
brokerService = broker.NewRabbitMQBroker(n.Ctx)
|
||||||
|
default:
|
||||||
ch.ExchangeDeclare(exchangeName, "direct", true, false, false, false, nil)
|
brokerService = broker.NewRedisBroker(n.Ctx)
|
||||||
q, _ := ch.QueueDeclare(queueName, true, false, false, false, nil)
|
}
|
||||||
ch.QueueBind(q.Name, routingKey, exchangeName, false, nil)
|
brokerService.Subscribe(os.Getenv("TOPIC"), n.Handler)
|
||||||
|
|
||||||
msgs, _ := ch.Consume(q.Name, "", true, false, false, false, nil)
|
|
||||||
|
|
||||||
go func() {
|
|
||||||
for msg := range msgs {
|
|
||||||
go n.Handler(msg)
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
|
|
||||||
fmt.Println("🚀 Server started. Ctrl+C to quit.")
|
fmt.Println("🚀 Server started. Ctrl+C to quit.")
|
||||||
select {}
|
select {}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *notificationConsumer) Handler(msg amqp.Delivery) {
|
func (n *notificationConsumer) Handler(notification domain.NotificationMsg) {
|
||||||
var notification domain.NotificationMsg
|
|
||||||
err := json.Unmarshal(msg.Body, ¬ification)
|
|
||||||
if err != nil {
|
|
||||||
fmt.Print(err.Error())
|
|
||||||
}
|
|
||||||
var ntf domain.NotifierPort
|
var ntf domain.NotifierPort
|
||||||
switch notification.Type {
|
switch notification.Type {
|
||||||
case "sms":
|
case "sms":
|
||||||
@@ -63,5 +48,6 @@ func (n *notificationConsumer) Handler(msg amqp.Delivery) {
|
|||||||
case "email":
|
case "email":
|
||||||
ntf = notifier.NewEmailNotifier()
|
ntf = notifier.NewEmailNotifier()
|
||||||
}
|
}
|
||||||
|
fmt.Println(notification.Message)
|
||||||
ntf.SendMessage(notification.To, notification.Message)
|
ntf.SendMessage(notification.To, notification.Message)
|
||||||
}
|
}
|
||||||
|
|||||||
6
internal/domain/broker.go
Normal file
6
internal/domain/broker.go
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
type BrokerPort interface {
|
||||||
|
Subscribe(string, func(NotificationMsg))
|
||||||
|
// Publish()
|
||||||
|
}
|
||||||
5
internal/domain/email.go
Normal file
5
internal/domain/email.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
type EmailServicePort interface {
|
||||||
|
SendMail([]string, []byte)
|
||||||
|
}
|
||||||
@@ -1,10 +1,8 @@
|
|||||||
package domain
|
package domain
|
||||||
|
|
||||||
import "github.com/streadway/amqp"
|
|
||||||
|
|
||||||
type NotificationConsumerPort interface {
|
type NotificationConsumerPort interface {
|
||||||
Start()
|
Start()
|
||||||
Handler(amqp.Delivery)
|
Handler(NotificationMsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
type SMSServicePort interface {
|
type SMSServicePort interface {
|
||||||
|
|||||||
@@ -2,15 +2,19 @@ package notifier
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"github.com/JscorpTech/notification/internal/domain"
|
"github.com/JscorpTech/notification/internal/domain"
|
||||||
"github.com/k0kubun/pp/v3"
|
"github.com/JscorpTech/notification/internal/services"
|
||||||
)
|
)
|
||||||
|
|
||||||
type emailNotifier struct{}
|
type emailNotifier struct {
|
||||||
|
EmailService domain.EmailServicePort
|
||||||
|
}
|
||||||
|
|
||||||
func NewEmailNotifier() domain.NotifierPort {
|
func NewEmailNotifier() domain.NotifierPort {
|
||||||
return &emailNotifier{}
|
return &emailNotifier{
|
||||||
|
EmailService: services.NewEmailService(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *emailNotifier) SendMessage(to []string, body string) {
|
func (n *emailNotifier) SendMessage(to []string, body string) {
|
||||||
pp.Print(to, body)
|
n.EmailService.SendMail(to, []byte(body))
|
||||||
}
|
}
|
||||||
|
|||||||
33
internal/services/email.go
Normal file
33
internal/services/email.go
Normal file
@@ -0,0 +1,33 @@
|
|||||||
|
package services
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"net/smtp"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/JscorpTech/notification/internal/domain"
|
||||||
|
)
|
||||||
|
|
||||||
|
type emailService struct{}
|
||||||
|
|
||||||
|
func NewEmailService() domain.EmailServicePort {
|
||||||
|
return &emailService{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *emailService) SendMail(to []string, body []byte) {
|
||||||
|
// Gmail konfiguratsiyasi
|
||||||
|
from := os.Getenv("MAIL_USER")
|
||||||
|
password := os.Getenv("MAIL_PASSWORD")
|
||||||
|
smtpHost := os.Getenv("MAIL_DOMAIN")
|
||||||
|
smtpPort := os.Getenv("MAIL_PORT")
|
||||||
|
|
||||||
|
auth := smtp.PlainAuth("", from, password, smtpHost)
|
||||||
|
|
||||||
|
err := smtp.SendMail(smtpHost+":"+smtpPort, auth, from, to, body)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Println("Xatolik:", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Println("Email yuborildi!")
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import (
|
|||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
|
"log"
|
||||||
"net/http"
|
"net/http"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
@@ -35,7 +36,7 @@ func (e *eskizSMSService) Request(payload any, path string, isAuth bool, retry b
|
|||||||
req, err := http.NewRequest("POST", e.BaseURL+path, &buf)
|
req, err := http.NewRequest("POST", e.BaseURL+path, &buf)
|
||||||
req.Header.Add("Content-Type", "application/json")
|
req.Header.Add("Content-Type", "application/json")
|
||||||
if isAuth {
|
if isAuth {
|
||||||
req.Header.Add("Authorization", "Bearer "+e.GetToken(true))
|
req.Header.Add("Authorization", "Bearer "+e.GetToken(true, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -43,24 +44,32 @@ func (e *eskizSMSService) Request(payload any, path string, isAuth bool, retry b
|
|||||||
}
|
}
|
||||||
res, err := client.Do(req)
|
res, err := client.Do(req)
|
||||||
if res.StatusCode == http.StatusUnauthorized && retry {
|
if res.StatusCode == http.StatusUnauthorized && retry {
|
||||||
|
time.Sleep(time.Second * 5)
|
||||||
pp.Print("Qayta urunish")
|
pp.Print("Qayta urunish")
|
||||||
e.GetToken(false)
|
e.GetToken(false, false)
|
||||||
return e.Request(payload, path, isAuth, false)
|
return e.Request(payload, path, isAuth, false)
|
||||||
}
|
}
|
||||||
return res, err
|
return res, err
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e *eskizSMSService) GetToken(cache bool) string {
|
func (e *eskizSMSService) GetToken(cache bool, retry bool) string {
|
||||||
|
email := os.Getenv("ESKIZ_USER")
|
||||||
|
password := os.Getenv("ESKIZ_PASSWORD")
|
||||||
|
|
||||||
|
if email == "" || password == "" {
|
||||||
|
log.Fatal("password or fmail not found")
|
||||||
|
}
|
||||||
|
|
||||||
token, err := redis.RDB.Get(e.Ctx, "eskiz_token").Result()
|
token, err := redis.RDB.Get(e.Ctx, "eskiz_token").Result()
|
||||||
if err == nil && cache {
|
if err == nil && cache {
|
||||||
pp.Print("Eskiz token topildi 😁")
|
pp.Print("Eskiz token topildi 😁")
|
||||||
return token
|
return token
|
||||||
}
|
}
|
||||||
payload := domain.EskizLogin{
|
payload := domain.EskizLogin{
|
||||||
Email: os.Getenv("ESKIZ_USER"),
|
Email: email,
|
||||||
Password: os.Getenv("ESKIZ_PASSWORD"),
|
Password: password,
|
||||||
}
|
}
|
||||||
res, err := e.Request(payload, "/auth/login", false, true)
|
res, err := e.Request(payload, "/auth/login", false, retry)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
pp.Print(err.Error())
|
pp.Print(err.Error())
|
||||||
}
|
}
|
||||||
|
|||||||
15
notify.service
Normal file
15
notify.service
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
[Unit]
|
||||||
|
Description="Notification service"
|
||||||
|
After=network.target
|
||||||
|
|
||||||
|
[Service]
|
||||||
|
User=root
|
||||||
|
Group=root
|
||||||
|
Type=simple
|
||||||
|
Restart=on-failure
|
||||||
|
RestartSec=5s
|
||||||
|
ExecStart=/home/user/projects/notification/main
|
||||||
|
WorkingDirectory=/home/user/projects/notification
|
||||||
|
|
||||||
|
[Install]
|
||||||
|
WantedBy=multi-user.target
|
||||||
35
stack.yaml
Normal file
35
stack.yaml
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
version: "3.8"
|
||||||
|
|
||||||
|
services:
|
||||||
|
notification:
|
||||||
|
image: jscorptech/taxi-notification:3
|
||||||
|
env_file:
|
||||||
|
- /opt/env/.notification
|
||||||
|
networks:
|
||||||
|
- taxi
|
||||||
|
deploy:
|
||||||
|
mode: replicated
|
||||||
|
restart_policy:
|
||||||
|
condition: any
|
||||||
|
update_config:
|
||||||
|
parallelism: 1
|
||||||
|
order: start-first
|
||||||
|
failure_action: rollback
|
||||||
|
monitor: 10s
|
||||||
|
delay: 10s
|
||||||
|
max_failure_ratio: 0.2
|
||||||
|
resources:
|
||||||
|
limits:
|
||||||
|
cpus: "2"
|
||||||
|
memory: 1024M
|
||||||
|
logging:
|
||||||
|
driver: json-file
|
||||||
|
options:
|
||||||
|
max-size: "10m"
|
||||||
|
max-file: 10
|
||||||
|
|
||||||
|
networks:
|
||||||
|
taxi:
|
||||||
|
driver: overlay
|
||||||
|
external: true
|
||||||
|
attachable: true
|
||||||
36
test.py
Normal file
36
test.py
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
# from kombu import Connection, Exchange, Producer
|
||||||
|
|
||||||
|
# # RabbitMQ ulanishi
|
||||||
|
# rabbit_url = 'amqp://guest:guest@127.0.0.1:5672/'
|
||||||
|
# connection = Connection(rabbit_url)
|
||||||
|
# channel = connection.channel()
|
||||||
|
|
||||||
|
# exchange = Exchange('notification', type='direct')
|
||||||
|
|
||||||
|
# # Producer yaratish
|
||||||
|
# producer = Producer(channel, exchange=exchange, routing_key="notification")
|
||||||
|
|
||||||
|
# # Xabar yuborish
|
||||||
|
# message = {'type': 'email', 'message': "Subject: test\r\n\r\nclasscom.uz sayti va mobil ilovasiga ro'yxatdan o'tishingingiz uchun tasdiqlash kodi: 1234", "to": ["JscorpTech@gmail.com", "admin@jscorp.uz"]}
|
||||||
|
# producer.publish(message)
|
||||||
|
|
||||||
|
# print("Message sent to all workers!")
|
||||||
|
|
||||||
|
|
||||||
|
import redis
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Redis ulanishi
|
||||||
|
r = redis.StrictRedis(host='127.0.0.1', port=6379, db=0)
|
||||||
|
# Subject: tasdiqlash ko'di\r\n\r\n
|
||||||
|
# Xabar tayyorlash
|
||||||
|
message = {
|
||||||
|
'type': 'sms',
|
||||||
|
'message': "Assalomu alaykum samandar sizni https://classcom.uz o’qituvchining virtual kаbinetida muallif sifatida tasdiqlanganingiz bilan tabriklaymiz!!!",
|
||||||
|
'to': ["+998888112309"]
|
||||||
|
}
|
||||||
|
|
||||||
|
# Xabarni JSON formatga o‘tkazib, Redis listga push qilish
|
||||||
|
r.rpush('notification', json.dumps(message))
|
||||||
|
|
||||||
|
print("Message pushed to Redis list!")
|
||||||
2
tmp/.gitignore
vendored
Normal file
2
tmp/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
||||||
@@ -1 +0,0 @@
|
|||||||
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
|
|
||||||
Reference in New Issue
Block a user