@@ -1,2 +1,2 @@
|
|||||||
AUTH_COMMAND=sh ./resources/scripts/entrypoint.sh
|
USER_COMMAND=sh ./resources/scripts/entrypoint.sh
|
||||||
JWT_KEY=key
|
JWT_KEY=key
|
||||||
5
.github/workflows/deploy.yml
vendored
5
.github/workflows/deploy.yml
vendored
@@ -16,9 +16,10 @@ jobs:
|
|||||||
- name: 📥 Secret orqali .env fayl yaratish
|
- name: 📥 Secret orqali .env fayl yaratish
|
||||||
run: |
|
run: |
|
||||||
echo "${{ secrets.BASE_ENV_FILE }}" > .env
|
echo "${{ secrets.BASE_ENV_FILE }}" > .env
|
||||||
echo "${{ secrets.AUTH_ENV_FILE }}" > ./auth/.env
|
echo "${{ secrets.USER_ENV_FILE }}" > ./user/.env
|
||||||
|
|
||||||
- name: 🛠 Docker Compose bilan build & deploy
|
- name: 🛠 Docker Compose bilan build & deploy
|
||||||
run: |
|
run: |
|
||||||
|
docker compose --profile user --profile payment build
|
||||||
docker compose down
|
docker compose down
|
||||||
docker compose --profile auth --profile payment up -d --build
|
docker compose --profile user --profile payment up -d
|
||||||
14
Makefile
14
Makefile
@@ -1,14 +1,10 @@
|
|||||||
up-auth:
|
up-user:
|
||||||
docker compose --profile auth up -d
|
docker compose --profile user up -d
|
||||||
|
down-user:
|
||||||
down-auth:
|
docker compose --profile user down
|
||||||
docker compose --profile auth down
|
|
||||||
|
|
||||||
up-payment:
|
up-payment:
|
||||||
docker compose --profile payment up -d
|
docker compose --profile payment up -d
|
||||||
|
|
||||||
down-payment:
|
down-payment:
|
||||||
docker compose --profile payment down
|
docker compose --profile payment down
|
||||||
|
|
||||||
down-all:
|
down-all:
|
||||||
docker compose --profile payment --profile auth down
|
docker compose --profile payment --profile user --profile notification down
|
||||||
@@ -5,6 +5,7 @@ networks:
|
|||||||
volumes:
|
volumes:
|
||||||
pg_data: null
|
pg_data: null
|
||||||
pycache: null
|
pycache: null
|
||||||
|
rabbitmq-data: null
|
||||||
|
|
||||||
services:
|
services:
|
||||||
traefik:
|
traefik:
|
||||||
@@ -21,46 +22,46 @@ services:
|
|||||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
||||||
networks:
|
networks:
|
||||||
- lamenu
|
- lamenu
|
||||||
auth-nginx:
|
user-nginx:
|
||||||
labels:
|
labels:
|
||||||
- "traefik.enable=true"
|
- "traefik.enable=true"
|
||||||
- "traefik.http.routers.auth.rule=PathPrefix(`/auth`)"
|
- "traefik.http.routers.user.rule=PathPrefix(`/user`)"
|
||||||
- "traefik.http.routers.auth.entrypoints=web"
|
- "traefik.http.routers.user.entrypoints=web"
|
||||||
- "traefik.http.services.auth.loadbalancer.server.port=80"
|
- "traefik.http.services.user.loadbalancer.server.port=80"
|
||||||
- "traefik.http.middlewares.auth-strip-prefix.stripPrefix.prefixes=/auth"
|
- "traefik.http.middlewares.user-strip-prefix.stripPrefix.prefixes=/user"
|
||||||
- "traefik.http.routers.auth.middlewares=auth-strip-prefix"
|
- "traefik.http.routers.user.middlewares=user-strip-prefix"
|
||||||
networks:
|
networks:
|
||||||
- lamenu
|
- lamenu
|
||||||
volumes:
|
volumes:
|
||||||
- ./auth/resources/layout/nginx.conf:/etc/nginx/nginx.conf
|
- ./user/resources/layout/nginx.conf:/etc/nginx/nginx.conf
|
||||||
- ./auth/resources/:/usr/share/nginx/html/resources/
|
- ./user/resources/:/usr/share/nginx/html/resources/
|
||||||
build:
|
build:
|
||||||
context: ./auth
|
context: ./user
|
||||||
dockerfile: ./docker/Dockerfile.nginx
|
dockerfile: ./docker/Dockerfile.nginx
|
||||||
depends_on:
|
depends_on:
|
||||||
- auth
|
- user
|
||||||
profiles:
|
profiles:
|
||||||
- auth
|
- user
|
||||||
auth:
|
user:
|
||||||
networks:
|
networks:
|
||||||
- lamenu
|
- lamenu
|
||||||
build:
|
build:
|
||||||
context: ./auth
|
context: ./user
|
||||||
dockerfile: ./docker/Dockerfile.web
|
dockerfile: ./docker/Dockerfile.web
|
||||||
restart: always
|
restart: always
|
||||||
command: ${AUTH_COMMAND:-sh ./resources/scripts/entrypoint.sh}
|
command: ${user_COMMAND:-sh ./resources/scripts/entrypoint.sh}
|
||||||
environment:
|
environment:
|
||||||
- PYTHONPYCACHEPREFIX=/var/cache/pycache
|
- PYTHONPYCACHEPREFIX=/var/cache/pycache
|
||||||
- JWT_KEY=${JWT_KEY}
|
- JWT_KEY=${JWT_KEY}
|
||||||
volumes:
|
volumes:
|
||||||
- ./auth:/code
|
- ./user:/code
|
||||||
- pycache:/var/cache/pycache
|
- pycache:/var/cache/pycache
|
||||||
depends_on:
|
depends_on:
|
||||||
- auth-db
|
- user-db
|
||||||
- redis
|
- redis
|
||||||
profiles:
|
profiles:
|
||||||
- auth
|
- user
|
||||||
auth-db:
|
user-db:
|
||||||
networks:
|
networks:
|
||||||
- lamenu
|
- lamenu
|
||||||
image: postgres:16
|
image: postgres:16
|
||||||
@@ -72,9 +73,14 @@ services:
|
|||||||
volumes:
|
volumes:
|
||||||
- pg_data:/var/lib/postgresql/data
|
- pg_data:/var/lib/postgresql/data
|
||||||
profiles:
|
profiles:
|
||||||
- auth
|
- user
|
||||||
|
|
||||||
payment:
|
payment:
|
||||||
|
labels:
|
||||||
|
- "traefik.enable=true"
|
||||||
|
- "traefik.http.routers.payment.rule=PathPrefix(`/payment`)"
|
||||||
|
- "traefik.http.routers.payment.entrypoints=web"
|
||||||
|
- "traefik.http.services.payment.loadbalancer.server.port=8000"
|
||||||
networks:
|
networks:
|
||||||
- lamenu
|
- lamenu
|
||||||
build:
|
build:
|
||||||
@@ -89,8 +95,29 @@ services:
|
|||||||
profiles:
|
profiles:
|
||||||
- payment
|
- payment
|
||||||
|
|
||||||
|
notification:
|
||||||
|
build:
|
||||||
|
context: ./notification
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
networks:
|
||||||
|
- lamenu
|
||||||
|
profiles:
|
||||||
|
- notification
|
||||||
|
|
||||||
redis:
|
redis:
|
||||||
networks:
|
networks:
|
||||||
- lamenu
|
- lamenu
|
||||||
restart: always
|
restart: always
|
||||||
image: redis
|
image: redis
|
||||||
|
|
||||||
|
rabbitmq:
|
||||||
|
image: rabbitmq:management
|
||||||
|
container_name: rabbitmq
|
||||||
|
ports:
|
||||||
|
- "5672:5672" # RabbitMQ porti
|
||||||
|
- "15672:15672" # Web konsol porti
|
||||||
|
environment:
|
||||||
|
- RABBITMQ_DEFAULT_USER=guest # Foydalanuvchi nomi
|
||||||
|
- RABBITMQ_DEFAULT_PASS=guest # Parol
|
||||||
|
volumes:
|
||||||
|
- rabbitmq-data:/var/lib/rabbitmq # Ma'lumotlarni saqlash
|
||||||
|
|||||||
19
notification/Dockerfile
Normal file
19
notification/Dockerfile
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
FROM golang:alpine as build
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY go.mod go.sum ./
|
||||||
|
|
||||||
|
RUN go mod download
|
||||||
|
|
||||||
|
COPY . .
|
||||||
|
|
||||||
|
RUN go build -o notification ./cmd/main.go
|
||||||
|
|
||||||
|
FROM alpine
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
|
||||||
|
COPY --from=build /app/notification .
|
||||||
|
|
||||||
|
CMD ["./notification"]
|
||||||
112
notification/README.MD
Normal file
112
notification/README.MD
Normal file
@@ -0,0 +1,112 @@
|
|||||||
|
# Notification Service
|
||||||
|
|
||||||
|
A microservice for handling and delivering notifications through various channels like SMS and email using RabbitMQ as a message broker.
|
||||||
|
|
||||||
|
## 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
|
||||||
|
|
||||||
|
- Message consumption from RabbitMQ
|
||||||
|
- Support for multiple notification channels (SMS, email)
|
||||||
|
- Extensible architecture for adding new notification types
|
||||||
|
- Asynchronous notification handling
|
||||||
|
|
||||||
|
## Architecture
|
||||||
|
|
||||||
|
The notification service follows a clean architecture approach:
|
||||||
|
|
||||||
|
- **Domain Layer**: Contains core business logic and port interfaces
|
||||||
|
- **Infrastructure Layer**: Implements the ports with concrete adapters
|
||||||
|
- **RabbitMQ**: Used as a message broker for consuming notification requests
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
### Prerequisites
|
||||||
|
|
||||||
|
- Go 1.x+
|
||||||
|
- RabbitMQ server
|
||||||
|
|
||||||
|
### 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
|
||||||
|
|
||||||
|
Configure your RabbitMQ connection and other settings in the appropriate configuration files.
|
||||||
|
|
||||||
|
## Usage
|
||||||
|
|
||||||
|
### Running the service
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./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
|
||||||
|
{
|
||||||
|
"type": "email",
|
||||||
|
"message": "Hello, this is a test notification.",
|
||||||
|
"to": ["user@example.com"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Available notification types:
|
||||||
|
- `email`: For email notifications
|
||||||
|
- `sms`: For SMS notifications
|
||||||
|
|
||||||
|
## Project Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
notification/
|
||||||
|
├── cmd/
|
||||||
|
│ └── 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
|
||||||
|
|
||||||
|
1. Fork the repository
|
||||||
|
2. Create your feature branch: `git checkout -b feature/my-new-feature`
|
||||||
|
3. Commit your changes: `git commit -am 'Add some feature'`
|
||||||
|
4. Push to the branch: `git push origin feature/my-new-feature`
|
||||||
|
5. Submit a pull request
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
[Add your license here]
|
||||||
|
|
||||||
|
## Contact
|
||||||
|
|
||||||
|
JscorpTech - [GitHub](https://github.com/JscorpTech)
|
||||||
10
notification/cmd/main.go
Normal file
10
notification/cmd/main.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/JscorpTech/notification/internal/consumer"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
notification := consumer.NewNotificationConsumer()
|
||||||
|
notification.Start()
|
||||||
|
}
|
||||||
12
notification/go.mod
Normal file
12
notification/go.mod
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
module github.com/JscorpTech/notification
|
||||||
|
|
||||||
|
go 1.24.0
|
||||||
|
|
||||||
|
require (
|
||||||
|
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/streadway/amqp v1.1.0 // indirect
|
||||||
|
golang.org/x/sys v0.5.0 // indirect
|
||||||
|
golang.org/x/text v0.19.0 // indirect
|
||||||
|
)
|
||||||
13
notification/go.sum
Normal file
13
notification/go.sum
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
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/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=
|
||||||
|
golang.org/x/sys v0.5.0 h1:MUK/U/4lj1t1oPg0HfuXDN/Z1wv31ZJ/YcPiGccS4DU=
|
||||||
|
golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/text v0.19.0 h1:kTxAhCbGbxhK0IwgSKiMO5awPoDQ0RpfiVYBfK860YM=
|
||||||
|
golang.org/x/text v0.19.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
13
notification/internal/config/config.go
Normal file
13
notification/internal/config/config.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func GetEnv(key, fallback string) string {
|
||||||
|
val := os.Getenv(key)
|
||||||
|
if val == "" {
|
||||||
|
return fallback
|
||||||
|
}
|
||||||
|
return val
|
||||||
|
}
|
||||||
62
notification/internal/consumer/notification.go
Normal file
62
notification/internal/consumer/notification.go
Normal file
@@ -0,0 +1,62 @@
|
|||||||
|
package consumer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/JscorpTech/notification/internal/domain"
|
||||||
|
"github.com/JscorpTech/notification/internal/notifier"
|
||||||
|
"github.com/JscorpTech/notification/internal/rabbitmq"
|
||||||
|
"github.com/streadway/amqp"
|
||||||
|
)
|
||||||
|
|
||||||
|
type notificationConsumer struct{}
|
||||||
|
|
||||||
|
func NewNotificationConsumer() domain.NotificationConsumerPort {
|
||||||
|
return ¬ificationConsumer{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *notificationConsumer) Start() {
|
||||||
|
conn, ch, err := rabbitmq.Connect()
|
||||||
|
if err != nil {
|
||||||
|
log.Fatal(err)
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
defer ch.Close()
|
||||||
|
|
||||||
|
exchangeName := "notification"
|
||||||
|
queueName := "notification"
|
||||||
|
routingKey := "notification"
|
||||||
|
|
||||||
|
ch.ExchangeDeclare(exchangeName, "direct", true, false, false, false, nil)
|
||||||
|
q, _ := ch.QueueDeclare(queueName, true, false, false, false, nil)
|
||||||
|
ch.QueueBind(q.Name, routingKey, exchangeName, false, nil)
|
||||||
|
|
||||||
|
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.")
|
||||||
|
select {}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *notificationConsumer) Handler(msg amqp.Delivery) {
|
||||||
|
var notification domain.NotificationMsg
|
||||||
|
err := json.Unmarshal(msg.Body, ¬ification)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Print(err.Error())
|
||||||
|
}
|
||||||
|
var ntf domain.NotifierPort
|
||||||
|
switch notification.Type {
|
||||||
|
case "sms":
|
||||||
|
ntf = notifier.NewSmsNotifier()
|
||||||
|
case "email":
|
||||||
|
ntf = notifier.NewEmailNotifier()
|
||||||
|
}
|
||||||
|
ntf.SendMessage(notification.To, notification.Message)
|
||||||
|
}
|
||||||
18
notification/internal/domain/notification.go
Normal file
18
notification/internal/domain/notification.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import "github.com/streadway/amqp"
|
||||||
|
|
||||||
|
type NotificationConsumerPort interface {
|
||||||
|
Start()
|
||||||
|
Handler(amqp.Delivery)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotifierPort interface {
|
||||||
|
SendMessage([]string, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotificationMsg struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
To []string `json:"to"`
|
||||||
|
}
|
||||||
16
notification/internal/notifier/email.go
Normal file
16
notification/internal/notifier/email.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package notifier
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/JscorpTech/notification/internal/domain"
|
||||||
|
"github.com/k0kubun/pp/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type emailNotifier struct{}
|
||||||
|
|
||||||
|
func NewEmailNotifier() domain.NotifierPort {
|
||||||
|
return &emailNotifier{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *emailNotifier) SendMessage(to []string, body string) {
|
||||||
|
pp.Print(to, body)
|
||||||
|
}
|
||||||
16
notification/internal/notifier/sms.go
Normal file
16
notification/internal/notifier/sms.go
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
package notifier
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/JscorpTech/notification/internal/domain"
|
||||||
|
"github.com/k0kubun/pp/v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type smsNotifier struct{}
|
||||||
|
|
||||||
|
func NewSmsNotifier() domain.NotifierPort {
|
||||||
|
return &smsNotifier{}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *smsNotifier) SendMessage(to []string, body string) {
|
||||||
|
pp.Print(to, body)
|
||||||
|
}
|
||||||
21
notification/internal/rabbitmq/connection.go
Normal file
21
notification/internal/rabbitmq/connection.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package rabbitmq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"github.com/streadway/amqp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Connect() (*amqp.Connection, *amqp.Channel, error) {
|
||||||
|
conn, err := amqp.Dial("amqp://guest:guest@127.0.0.1:5672/")
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
ch, err := conn.Channel()
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
log.Println("🐇 Connected to RabbitMQ")
|
||||||
|
|
||||||
|
return conn, ch, nil
|
||||||
|
}
|
||||||
@@ -1,3 +1,13 @@
|
|||||||
FROM alpine:latest
|
FROM python:3.13-alpine
|
||||||
|
|
||||||
CMD ["sleep", "60"]
|
ENV PYTHONPYCACHEPREFIX=/dev/null
|
||||||
|
|
||||||
|
RUN apk update && apk add git gettext
|
||||||
|
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
COPY requirements.txt /code/requirements.txt
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
|
||||||
|
|
||||||
|
CMD ["sh", "./entrypoint.sh"]
|
||||||
3
payment/api/admin.py
Normal file
3
payment/api/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
payment/api/apps.py
Normal file
6
payment/api/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ApiConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "api"
|
||||||
3
payment/api/models.py
Normal file
3
payment/api/models.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
3
payment/api/tests.py
Normal file
3
payment/api/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
6
payment/api/urls.py
Normal file
6
payment/api/urls.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from .views import HealthView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("health/", HealthView.as_view())
|
||||||
|
]
|
||||||
7
payment/api/views.py
Normal file
7
payment/api/views.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
|
||||||
|
class HealthView(APIView):
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
return Response(data={"detail": "OK"})
|
||||||
16
payment/config/asgi.py
Normal file
16
payment/config/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for config project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
126
payment/config/settings.py
Normal file
126
payment/config/settings.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
"""
|
||||||
|
Django settings for config project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 5.1.3.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = "django-insecure-iv-iwjd(4d%g5&fyo*+xybkjhaik+r@3j0$h91u0^$u4fwuh53"
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
"api",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = "config.urls"
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [],
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = "config.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": BASE_DIR / "db.sqlite3",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/5.1/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
|
TIME_ZONE = "UTC"
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = "static/"
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
7
payment/config/urls.py
Normal file
7
payment/config/urls.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("payment/api/", include("api.urls")),
|
||||||
|
path("payment/admin/", admin.site.urls),
|
||||||
|
]
|
||||||
16
payment/config/wsgi.py
Normal file
16
payment/config/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for config project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
BIN
payment/db.sqlite3
Normal file
BIN
payment/db.sqlite3
Normal file
Binary file not shown.
5
payment/entrypoint.sh
Normal file
5
payment/entrypoint.sh
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
python3 manage.py collectstatus --no-input
|
||||||
|
python3 manage.py migrate --no-input
|
||||||
|
python3 manage.py runserver 0.0.0.0:8000
|
||||||
22
payment/manage.py
Executable file
22
payment/manage.py
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
2
payment/requirements.txt
Normal file
2
payment/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
django==5.1.3
|
||||||
|
djangorestframework==3.15.2
|
||||||
13
template/Dockerfile
Normal file
13
template/Dockerfile
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
FROM python:3.13-alpine
|
||||||
|
|
||||||
|
ENV PYTHONPYCACHEPREFIX=/dev/null
|
||||||
|
|
||||||
|
RUN apk update && apk add git gettext
|
||||||
|
|
||||||
|
WORKDIR /code
|
||||||
|
|
||||||
|
COPY requirements.txt /code/requirements.txt
|
||||||
|
|
||||||
|
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
|
||||||
|
|
||||||
|
CMD ["sh", "./entrypoint.sh"]
|
||||||
3
template/api/admin.py
Normal file
3
template/api/admin.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
|
||||||
|
# Register your models here.
|
||||||
6
template/api/apps.py
Normal file
6
template/api/apps.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.apps import AppConfig
|
||||||
|
|
||||||
|
|
||||||
|
class ApiConfig(AppConfig):
|
||||||
|
default_auto_field = "django.db.models.BigAutoField"
|
||||||
|
name = "api"
|
||||||
3
template/api/models.py
Normal file
3
template/api/models.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.db import models
|
||||||
|
|
||||||
|
# Create your models here.
|
||||||
3
template/api/tests.py
Normal file
3
template/api/tests.py
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
from django.test import TestCase
|
||||||
|
|
||||||
|
# Create your tests here.
|
||||||
6
template/api/urls.py
Normal file
6
template/api/urls.py
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
from django.urls import path
|
||||||
|
from .views import HealthView
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("health/", HealthView.as_view())
|
||||||
|
]
|
||||||
7
template/api/views.py
Normal file
7
template/api/views.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from rest_framework.views import APIView
|
||||||
|
from rest_framework.response import Response
|
||||||
|
|
||||||
|
|
||||||
|
class HealthView(APIView):
|
||||||
|
def get(self, *args, **kwargs):
|
||||||
|
return Response(data={"detail": "OK"})
|
||||||
16
template/config/asgi.py
Normal file
16
template/config/asgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
ASGI config for config project.
|
||||||
|
|
||||||
|
It exposes the ASGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/howto/deployment/asgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.asgi import get_asgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
||||||
|
|
||||||
|
application = get_asgi_application()
|
||||||
126
template/config/settings.py
Normal file
126
template/config/settings.py
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
"""
|
||||||
|
Django settings for config project.
|
||||||
|
|
||||||
|
Generated by 'django-admin startproject' using Django 5.1.3.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/topics/settings/
|
||||||
|
|
||||||
|
For the full list of settings and their values, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/ref/settings/
|
||||||
|
"""
|
||||||
|
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
# Build paths inside the project like this: BASE_DIR / 'subdir'.
|
||||||
|
BASE_DIR = Path(__file__).resolve().parent.parent
|
||||||
|
|
||||||
|
|
||||||
|
# Quick-start development settings - unsuitable for production
|
||||||
|
# See https://docs.djangoproject.com/en/5.1/howto/deployment/checklist/
|
||||||
|
|
||||||
|
# SECURITY WARNING: keep the secret key used in production secret!
|
||||||
|
SECRET_KEY = "django-insecure-iv-iwjd(4d%g5&fyo*+xybkjhaik+r@3j0$h91u0^$u4fwuh53"
|
||||||
|
|
||||||
|
# SECURITY WARNING: don't run with debug turned on in production!
|
||||||
|
DEBUG = True
|
||||||
|
|
||||||
|
ALLOWED_HOSTS = [
|
||||||
|
"*"
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Application definition
|
||||||
|
|
||||||
|
INSTALLED_APPS = [
|
||||||
|
"django.contrib.admin",
|
||||||
|
"django.contrib.auth",
|
||||||
|
"django.contrib.contenttypes",
|
||||||
|
"django.contrib.sessions",
|
||||||
|
"django.contrib.messages",
|
||||||
|
"django.contrib.staticfiles",
|
||||||
|
"api",
|
||||||
|
]
|
||||||
|
|
||||||
|
MIDDLEWARE = [
|
||||||
|
"django.middleware.security.SecurityMiddleware",
|
||||||
|
"django.contrib.sessions.middleware.SessionMiddleware",
|
||||||
|
"django.middleware.common.CommonMiddleware",
|
||||||
|
"django.middleware.csrf.CsrfViewMiddleware",
|
||||||
|
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
||||||
|
"django.contrib.messages.middleware.MessageMiddleware",
|
||||||
|
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
||||||
|
]
|
||||||
|
|
||||||
|
ROOT_URLCONF = "config.urls"
|
||||||
|
|
||||||
|
TEMPLATES = [
|
||||||
|
{
|
||||||
|
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
||||||
|
"DIRS": [],
|
||||||
|
"APP_DIRS": True,
|
||||||
|
"OPTIONS": {
|
||||||
|
"context_processors": [
|
||||||
|
"django.template.context_processors.debug",
|
||||||
|
"django.template.context_processors.request",
|
||||||
|
"django.contrib.auth.context_processors.auth",
|
||||||
|
"django.contrib.messages.context_processors.messages",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
WSGI_APPLICATION = "config.wsgi.application"
|
||||||
|
|
||||||
|
|
||||||
|
# Database
|
||||||
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#databases
|
||||||
|
|
||||||
|
DATABASES = {
|
||||||
|
"default": {
|
||||||
|
"ENGINE": "django.db.backends.sqlite3",
|
||||||
|
"NAME": BASE_DIR / "db.sqlite3",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# Password validation
|
||||||
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#auth-password-validators
|
||||||
|
|
||||||
|
AUTH_PASSWORD_VALIDATORS = [
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.UserAttributeSimilarityValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator",
|
||||||
|
},
|
||||||
|
]
|
||||||
|
|
||||||
|
|
||||||
|
# Internationalization
|
||||||
|
# https://docs.djangoproject.com/en/5.1/topics/i18n/
|
||||||
|
|
||||||
|
LANGUAGE_CODE = "en-us"
|
||||||
|
|
||||||
|
TIME_ZONE = "UTC"
|
||||||
|
|
||||||
|
USE_I18N = True
|
||||||
|
|
||||||
|
USE_TZ = True
|
||||||
|
|
||||||
|
|
||||||
|
# Static files (CSS, JavaScript, Images)
|
||||||
|
# https://docs.djangoproject.com/en/5.1/howto/static-files/
|
||||||
|
|
||||||
|
STATIC_URL = "static/"
|
||||||
|
|
||||||
|
# Default primary key field type
|
||||||
|
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
|
||||||
|
|
||||||
|
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
||||||
7
template/config/urls.py
Normal file
7
template/config/urls.py
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
from django.contrib import admin
|
||||||
|
from django.urls import path, include
|
||||||
|
|
||||||
|
urlpatterns = [
|
||||||
|
path("payment/api/", include("api.urls")),
|
||||||
|
path("payment/admin/", admin.site.urls),
|
||||||
|
]
|
||||||
16
template/config/wsgi.py
Normal file
16
template/config/wsgi.py
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
"""
|
||||||
|
WSGI config for config project.
|
||||||
|
|
||||||
|
It exposes the WSGI callable as a module-level variable named ``application``.
|
||||||
|
|
||||||
|
For more information on this file, see
|
||||||
|
https://docs.djangoproject.com/en/5.1/howto/deployment/wsgi/
|
||||||
|
"""
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
from django.core.wsgi import get_wsgi_application
|
||||||
|
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
||||||
|
|
||||||
|
application = get_wsgi_application()
|
||||||
BIN
template/db.sqlite3
Normal file
BIN
template/db.sqlite3
Normal file
Binary file not shown.
5
template/entrypoint.sh
Normal file
5
template/entrypoint.sh
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
python3 manage.py collectstatus --no-input
|
||||||
|
python3 manage.py migrate --no-input
|
||||||
|
python3 manage.py runserver 0.0.0.0:8000
|
||||||
22
template/manage.py
Executable file
22
template/manage.py
Executable file
@@ -0,0 +1,22 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
"""Django's command-line utility for administrative tasks."""
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
"""Run administrative tasks."""
|
||||||
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "config.settings")
|
||||||
|
try:
|
||||||
|
from django.core.management import execute_from_command_line
|
||||||
|
except ImportError as exc:
|
||||||
|
raise ImportError(
|
||||||
|
"Couldn't import Django. Are you sure it's installed and "
|
||||||
|
"available on your PYTHONPATH environment variable? Did you "
|
||||||
|
"forget to activate a virtual environment?"
|
||||||
|
) from exc
|
||||||
|
execute_from_command_line(sys.argv)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
2
template/requirements.txt
Normal file
2
template/requirements.txt
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
django==5.1.3
|
||||||
|
djangorestframework==3.15.2
|
||||||
17
test.py
Normal file
17
test.py
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
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': 'Hello, Workers!', "to": ["+998888112309", "+998943990509"]}
|
||||||
|
producer.publish(message)
|
||||||
|
|
||||||
|
print("Message sent to all workers!")
|
||||||
0
auth/.gitignore → user/.gitignore
vendored
0
auth/.gitignore → user/.gitignore
vendored
@@ -1,11 +1,7 @@
|
|||||||
from config.env import env
|
from config.env import env
|
||||||
|
|
||||||
APPS = [
|
APPS = [
|
||||||
|
|
||||||
"cacheops",
|
"cacheops",
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
"drf_spectacular",
|
"drf_spectacular",
|
||||||
"rest_framework",
|
"rest_framework",
|
||||||
"corsheaders",
|
"corsheaders",
|
||||||
0
user/core/apps/accounts/__init__.py
Normal file
0
user/core/apps/accounts/__init__.py
Normal file
0
user/core/apps/accounts/migrations/__init__.py
Normal file
0
user/core/apps/accounts/migrations/__init__.py
Normal file
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user