Merge commit '164340f86dabb71bef83bfa774e0b45702be270a' as 'notification'
This commit is contained in:
17
.env.example
17
.env.example
@@ -1,2 +1,15 @@
|
|||||||
USER_COMMAND=sh ./resources/scripts/entrypoint.sh
|
RABBITMQ_URL=amqp://guest:guest@127.0.0.1:5672/
|
||||||
JWT_KEY=key
|
|
||||||
|
|
||||||
|
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=""
|
||||||
25
.github/workflows/deploy.yml
vendored
25
.github/workflows/deploy.yml
vendored
@@ -1,25 +0,0 @@
|
|||||||
name: Auto Deploy to Self-Hosted
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- prod
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: self-hosted
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: 🔄 Kodni olish
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: 📥 Secret orqali .env fayl yaratish
|
|
||||||
run: |
|
|
||||||
echo "${{ secrets.BASE_ENV_FILE }}" > .env
|
|
||||||
echo "${{ secrets.USER_ENV_FILE }}" > ./user/.env
|
|
||||||
|
|
||||||
- name: 🛠 Docker Compose bilan build & deploy
|
|
||||||
run: |
|
|
||||||
docker compose --profile user --profile payment build
|
|
||||||
docker compose down
|
|
||||||
docker compose --profile user --profile payment up -d
|
|
||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -1 +1,3 @@
|
|||||||
.env
|
.env
|
||||||
|
./bin
|
||||||
|
./main
|
||||||
20
Dockerfile
Normal file
20
Dockerfile
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
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 .
|
||||||
|
COPY ./.env /app/
|
||||||
|
|
||||||
|
CMD ["./notification"]
|
||||||
25
Makefile
25
Makefile
@@ -1,25 +0,0 @@
|
|||||||
SERVICES ?= -f order/docker-compose.yml -f user/docker-compose.yml -f payment/docker-compose.yml -f notification/docker-compose.yml
|
|
||||||
NAME ?= lamenu
|
|
||||||
|
|
||||||
|
|
||||||
logs:
|
|
||||||
docker compose -f $(app)/docker-compose.yml logs -f $(cn)
|
|
||||||
|
|
||||||
sh:
|
|
||||||
docker compose -f $(app)/docker-compose.yml exec $(cn) sh
|
|
||||||
|
|
||||||
up:
|
|
||||||
docker compose -f docker-compose.yml up -d
|
|
||||||
docker compose -f $(app)/docker-compose.yml up -d
|
|
||||||
|
|
||||||
build:
|
|
||||||
docker compose -f $(app)/docker-compose.yml build
|
|
||||||
|
|
||||||
down:
|
|
||||||
docker compose -f $(app)/docker-compose.yml down
|
|
||||||
|
|
||||||
up-all:
|
|
||||||
docker compose -p $(NAME) $(SERVICES) up -d
|
|
||||||
|
|
||||||
down-all:
|
|
||||||
docker compose -$(NAME) $(SERVICES) down
|
|
||||||
135
README.MD
135
README.MD
@@ -1,2 +1,133 @@
|
|||||||
# JST-DJANGO
|
# Notification Service
|
||||||
[Docs](https://docs.jscorp.uz)
|
|
||||||
|
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"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Python example
|
||||||
|
```python
|
||||||
|
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': 'sms', 'message': "classcom.uz sayti va mobil ilovasiga ro'yxatdan o'tishingingiz uchun tasdiqlash kodi: 1234", "to": ["+998888112309", "+998943990509"]}
|
||||||
|
producer.publish(message)
|
||||||
|
|
||||||
|
print("Message sent to all workers!")
|
||||||
|
```
|
||||||
|
|
||||||
|
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)
|
||||||
|
|||||||
20
cmd/main.go
Normal file
20
cmd/main.go
Normal file
@@ -0,0 +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() {
|
||||||
|
if err := godotenv.Load(); err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
redis.InitRedis()
|
||||||
|
notification := consumer.NewNotificationConsumer(ctx)
|
||||||
|
notification.Start()
|
||||||
|
}
|
||||||
@@ -1,44 +1,12 @@
|
|||||||
networks:
|
networks:
|
||||||
lamenu:
|
lamenu:
|
||||||
|
external: true
|
||||||
name: lamenu
|
name: lamenu
|
||||||
driver: bridge
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
pg_data: null
|
|
||||||
pycache: null
|
|
||||||
rabbitmq-data: null
|
|
||||||
|
|
||||||
services:
|
services:
|
||||||
traefik:
|
notification:
|
||||||
image: traefik:v2.10
|
build:
|
||||||
command:
|
context: .
|
||||||
- "--api.insecure=true"
|
dockerfile: Dockerfile
|
||||||
- "--providers.docker=true"
|
|
||||||
- "--entrypoints.web.address=:80"
|
|
||||||
- "--providers.docker.exposedbydefault=false"
|
|
||||||
ports:
|
|
||||||
- "${PORT-84}:80"
|
|
||||||
- "8080:8080" # Dashboard uchun
|
|
||||||
volumes:
|
|
||||||
- "/var/run/docker.sock:/var/run/docker.sock:ro"
|
|
||||||
networks:
|
networks:
|
||||||
- lamenu
|
- lamenu
|
||||||
redis:
|
|
||||||
networks:
|
|
||||||
- lamenu
|
|
||||||
restart: always
|
|
||||||
image: redis
|
|
||||||
ports:
|
|
||||||
- 6379:6379
|
|
||||||
rabbitmq:
|
|
||||||
image: rabbitmq:management
|
|
||||||
networks:
|
|
||||||
- lamenu
|
|
||||||
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
|
|
||||||
|
|||||||
16
go.mod
Normal file
16
go.mod
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
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
|
||||||
|
)
|
||||||
21
go.sum
Normal file
21
go.sum
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
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=
|
||||||
|
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
internal/config/config.go
Normal file
13
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
|
||||||
|
}
|
||||||
67
internal/consumer/notification.go
Normal file
67
internal/consumer/notification.go
Normal file
@@ -0,0 +1,67 @@
|
|||||||
|
package consumer
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"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 {
|
||||||
|
Ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewNotificationConsumer(ctx context.Context) domain.NotificationConsumerPort {
|
||||||
|
return ¬ificationConsumer{
|
||||||
|
Ctx: ctx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(n.Ctx)
|
||||||
|
case "email":
|
||||||
|
ntf = notifier.NewEmailNotifier()
|
||||||
|
}
|
||||||
|
ntf.SendMessage(notification.To, notification.Message)
|
||||||
|
}
|
||||||
27
internal/domain/eskiz.go
Normal file
27
internal/domain/eskiz.go
Normal 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"`
|
||||||
|
}
|
||||||
21
internal/domain/notification.go
Normal file
21
internal/domain/notification.go
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
package domain
|
||||||
|
|
||||||
|
import "github.com/streadway/amqp"
|
||||||
|
|
||||||
|
type NotificationConsumerPort interface {
|
||||||
|
Start()
|
||||||
|
Handler(amqp.Delivery)
|
||||||
|
}
|
||||||
|
|
||||||
|
type SMSServicePort interface {
|
||||||
|
SendSMS(string, string) error
|
||||||
|
}
|
||||||
|
type NotifierPort interface {
|
||||||
|
SendMessage([]string, string)
|
||||||
|
}
|
||||||
|
|
||||||
|
type NotificationMsg struct {
|
||||||
|
Type string `json:"type"`
|
||||||
|
Message string `json:"message"`
|
||||||
|
To []string `json:"to"`
|
||||||
|
}
|
||||||
20
internal/domain/playmobile.go
Normal file
20
internal/domain/playmobile.go
Normal 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
|
||||||
|
}
|
||||||
1
internal/models/fcm.go
Normal file
1
internal/models/fcm.go
Normal file
@@ -0,0 +1 @@
|
|||||||
|
package models
|
||||||
16
internal/notifier/email.go
Normal file
16
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)
|
||||||
|
}
|
||||||
25
internal/notifier/sms.go
Normal file
25
internal/notifier/sms.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package notifier
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
|
||||||
|
"github.com/JscorpTech/notification/internal/domain"
|
||||||
|
"github.com/JscorpTech/notification/internal/services"
|
||||||
|
)
|
||||||
|
|
||||||
|
type smsNotifier struct {
|
||||||
|
SMSServie domain.SMSServicePort
|
||||||
|
Ctx context.Context
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSmsNotifier(ctx context.Context) domain.NotifierPort {
|
||||||
|
return &smsNotifier{
|
||||||
|
SMSServie: services.NewEskizSMSService(ctx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *smsNotifier) SendMessage(to []string, body string) {
|
||||||
|
for _, user := range to {
|
||||||
|
n.SMSServie.SendSMS(user, body)
|
||||||
|
}
|
||||||
|
}
|
||||||
22
internal/rabbitmq/connection.go
Normal file
22
internal/rabbitmq/connection.go
Normal file
@@ -0,0 +1,22 @@
|
|||||||
|
package rabbitmq
|
||||||
|
|
||||||
|
import (
|
||||||
|
"log"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/streadway/amqp"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Connect() (*amqp.Connection, *amqp.Channel, error) {
|
||||||
|
conn, err := amqp.Dial(os.Getenv("RABBITMQ_URL"))
|
||||||
|
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
|
||||||
|
}
|
||||||
27
internal/redis/client.go
Normal file
27
internal/redis/client.go
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
||||||
89
internal/services/eskiz.go
Normal file
89
internal/services/eskiz.go
Normal 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
|
||||||
|
}
|
||||||
53
internal/services/playmobile.go
Normal file
53
internal/services/playmobile.go
Normal 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,28 +0,0 @@
|
|||||||
{
|
|
||||||
"template": "https://github.com/JscorpTech/django",
|
|
||||||
"commit": "d8682a7685597c2cf088da2f240b78a7a71e91ec",
|
|
||||||
"checkout": null,
|
|
||||||
"context": {
|
|
||||||
"cookiecutter": {
|
|
||||||
"cacheops": true,
|
|
||||||
"silk": true,
|
|
||||||
"storage": true,
|
|
||||||
"rosetta": false,
|
|
||||||
"channels": true,
|
|
||||||
"ckeditor": true,
|
|
||||||
"modeltranslation": true,
|
|
||||||
"parler": false,
|
|
||||||
"project_name": "order",
|
|
||||||
"settings_module": "config.settings.local",
|
|
||||||
"runner": "wsgi",
|
|
||||||
"script": "entrypoint.sh",
|
|
||||||
"key": "key",
|
|
||||||
"port": "8081",
|
|
||||||
"phone": "998888112309",
|
|
||||||
"password": "2309",
|
|
||||||
"max_line_length": "120",
|
|
||||||
"project_slug": "order"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"directory": null
|
|
||||||
}
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
venv/
|
|
||||||
resources/staticfiles/
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
# Django configs
|
|
||||||
DJANGO_SECRET_KEY=key
|
|
||||||
DEBUG=True
|
|
||||||
DJANGO_SETTINGS_MODULE=config.settings.local
|
|
||||||
COMMAND=sh ./resources/scripts/entrypoint.sh
|
|
||||||
PORT=8081
|
|
||||||
#! debug | prod
|
|
||||||
PROJECT_ENV=debug
|
|
||||||
PROTOCOL_HTTPS=False
|
|
||||||
|
|
||||||
# Databse configs
|
|
||||||
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
|
|
||||||
DB_ENGINE=django.db.backends.postgresql_psycopg2
|
|
||||||
DB_NAME=django
|
|
||||||
DB_USER=postgres
|
|
||||||
DB_PASSWORD=2309
|
|
||||||
DB_HOST=db
|
|
||||||
DB_PORT=5432
|
|
||||||
|
|
||||||
# Cache
|
|
||||||
CACHE_BACKEND=django.core.cache.backends.redis.RedisCache
|
|
||||||
REDIS_URL=redis://redis:6379
|
|
||||||
|
|
||||||
|
|
||||||
CACHE_ENABLED=False
|
|
||||||
|
|
||||||
CACHE_TIMEOUT=120
|
|
||||||
|
|
||||||
# Vite settings
|
|
||||||
VITE_LIVE=False
|
|
||||||
VITE_PORT=5173
|
|
||||||
VITE_HOST=127.0.0.1
|
|
||||||
|
|
||||||
# Sms service
|
|
||||||
SMS_API_URL=https://notify.eskiz.uz/api
|
|
||||||
SMS_LOGIN=admin@gmail.com
|
|
||||||
SMS_PASSWORD=key
|
|
||||||
|
|
||||||
# Addition
|
|
||||||
|
|
||||||
ALLOWED_HOSTS=127.0.0.1,web
|
|
||||||
CSRF_TRUSTED_ORIGINS=http://127.0.0.1:8081
|
|
||||||
|
|
||||||
|
|
||||||
OTP_MODULE=core.services.otp
|
|
||||||
OTP_SERVICE=EskizService
|
|
||||||
|
|
||||||
|
|
||||||
# Storage
|
|
||||||
STORAGE_ID=id
|
|
||||||
STORAGE_KEY=key
|
|
||||||
STORAGE_URL=example.com
|
|
||||||
|
|
||||||
#! MINIO | AWS | FILE
|
|
||||||
STORAGE_DEFAULT=FILE
|
|
||||||
|
|
||||||
#! MINIO | AWS | STATIC
|
|
||||||
STORAGE_STATIC=STATIC
|
|
||||||
|
|
||||||
STORAGE_BUCKET_MEDIA=name
|
|
||||||
STORAGE_BUCKET_STATIC=name
|
|
||||||
STORAGE_PATH=127.0.0.1:8081/bucket/
|
|
||||||
STORAGE_PROTOCOL=http:
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
[flake8]
|
|
||||||
max-line-length = 120
|
|
||||||
ignore = E701, E704, W503
|
|
||||||
158
order/.gitignore
vendored
158
order/.gitignore
vendored
@@ -1,158 +0,0 @@
|
|||||||
node_modules
|
|
||||||
|
|
||||||
# OS ignores
|
|
||||||
*.DS_Store
|
|
||||||
|
|
||||||
# Byte-compiled / optimized / DLL files
|
|
||||||
__pycache__/
|
|
||||||
*.py[cod]
|
|
||||||
*$py.class
|
|
||||||
|
|
||||||
poetry.lock
|
|
||||||
|
|
||||||
# C extensions
|
|
||||||
*.so
|
|
||||||
|
|
||||||
# Distribution / packaging
|
|
||||||
.Python
|
|
||||||
build/
|
|
||||||
develop-eggs/
|
|
||||||
dist/
|
|
||||||
downloads/
|
|
||||||
eggs/
|
|
||||||
.eggs/
|
|
||||||
lib/
|
|
||||||
lib64/
|
|
||||||
parts/
|
|
||||||
sdist/
|
|
||||||
var/
|
|
||||||
wheels/
|
|
||||||
share/python-wheels/
|
|
||||||
*.egg-info/
|
|
||||||
.installed.cfg
|
|
||||||
*.egg
|
|
||||||
MANIFEST
|
|
||||||
|
|
||||||
# PyInstaller
|
|
||||||
# Usually these files are written by a python script from a template
|
|
||||||
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
||||||
*.manifest
|
|
||||||
*.spec
|
|
||||||
|
|
||||||
# Installer logs
|
|
||||||
pip-log.txt
|
|
||||||
pip-delete-this-directory.txt
|
|
||||||
|
|
||||||
# Unit test / coverage reports
|
|
||||||
htmlcov/
|
|
||||||
.tox/
|
|
||||||
.nox/
|
|
||||||
.coverage
|
|
||||||
.coverage.*
|
|
||||||
.cache
|
|
||||||
nosetests.xml
|
|
||||||
coverage.xml
|
|
||||||
*.cover
|
|
||||||
*.py,cover
|
|
||||||
.hypothesis/
|
|
||||||
.pytest_cache/
|
|
||||||
cover/
|
|
||||||
|
|
||||||
# Translations
|
|
||||||
*.mo
|
|
||||||
*.pot
|
|
||||||
|
|
||||||
# Django stuff:
|
|
||||||
*.log
|
|
||||||
local_settings.py
|
|
||||||
db.sqlite3
|
|
||||||
db.sqlite3-journal
|
|
||||||
|
|
||||||
# Flask stuff:
|
|
||||||
instance/
|
|
||||||
.webassets-cache
|
|
||||||
|
|
||||||
# Scrapy stuff:
|
|
||||||
.scrapy
|
|
||||||
|
|
||||||
# Sphinx documentation
|
|
||||||
docs/_build/
|
|
||||||
|
|
||||||
# PyBuilder
|
|
||||||
.pybuilder/
|
|
||||||
target/
|
|
||||||
|
|
||||||
# Jupyter Notebook
|
|
||||||
.ipynb_checkpoints
|
|
||||||
|
|
||||||
# IPython
|
|
||||||
profile_default/
|
|
||||||
ipython_config.py
|
|
||||||
|
|
||||||
# pyenv
|
|
||||||
# For a library or package, you might want to ignore these files since the code is
|
|
||||||
# intended to run in multiple environments; otherwise, check them in:
|
|
||||||
# .python-version
|
|
||||||
|
|
||||||
# pipenv
|
|
||||||
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
||||||
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
||||||
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
||||||
# install all needed dependencies.
|
|
||||||
#Pipfile.lock
|
|
||||||
|
|
||||||
# poetry
|
|
||||||
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
||||||
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
||||||
# commonly ignored for libraries.
|
|
||||||
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
||||||
#poetry.lock
|
|
||||||
|
|
||||||
# PEP 582; used by e.g. github.com/David-OConnor/pyflow
|
|
||||||
__pypackages__/
|
|
||||||
|
|
||||||
# Celery stuff
|
|
||||||
celerybeat-schedule
|
|
||||||
celerybeat.pid
|
|
||||||
|
|
||||||
# SageMath parsed files
|
|
||||||
*.sage.py
|
|
||||||
|
|
||||||
# Environments
|
|
||||||
.env
|
|
||||||
.venv
|
|
||||||
env/
|
|
||||||
venv/
|
|
||||||
ENV/
|
|
||||||
env.bak/
|
|
||||||
venv.bak/
|
|
||||||
|
|
||||||
# Spyder project settings
|
|
||||||
.spyderproject
|
|
||||||
.spyproject
|
|
||||||
|
|
||||||
# Rope project settings
|
|
||||||
.ropeproject
|
|
||||||
|
|
||||||
# mkdocs documentation
|
|
||||||
/site
|
|
||||||
|
|
||||||
# mypy
|
|
||||||
.mypy_cache/
|
|
||||||
.dmypy.json
|
|
||||||
dmypy.json
|
|
||||||
|
|
||||||
# Pyre type checker
|
|
||||||
.pyre/
|
|
||||||
|
|
||||||
# pytype static type analyzer
|
|
||||||
.pytype/
|
|
||||||
|
|
||||||
# Cython debug symbols
|
|
||||||
cython_debug/
|
|
||||||
|
|
||||||
# PyCharm
|
|
||||||
.idea/
|
|
||||||
|
|
||||||
# Visual Studio Code
|
|
||||||
.vscode
|
|
||||||
@@ -1,2 +0,0 @@
|
|||||||
# JST-DJANGO
|
|
||||||
[Docs](https://docs.jscorp.uz)
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from .celery import app
|
|
||||||
|
|
||||||
__all__ = ["app"]
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
from django.core.asgi import get_asgi_application
|
|
||||||
|
|
||||||
asgi_application = get_asgi_application()
|
|
||||||
from config.env import env # noqa
|
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", env("DJANGO_SETTINGS_MODULE"))
|
|
||||||
|
|
||||||
|
|
||||||
from channels.routing import ProtocolTypeRouter # noqa
|
|
||||||
from channels.routing import URLRouter # noqa
|
|
||||||
|
|
||||||
# from core.apps.websocket.urls import websocket_urlpatterns # noqa
|
|
||||||
# from core.apps.websocket.middlewares import JWTAuthMiddlewareStack # noqa
|
|
||||||
|
|
||||||
application = ProtocolTypeRouter(
|
|
||||||
{
|
|
||||||
"http": asgi_application,
|
|
||||||
# "websocket": JWTAuthMiddlewareStack(URLRouter(websocket_urlpatterns)),
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
"""
|
|
||||||
Celery configurations
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import celery
|
|
||||||
from config.env import env
|
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", env("DJANGO_SETTINGS_MODULE"))
|
|
||||||
|
|
||||||
app = celery.Celery("config")
|
|
||||||
|
|
||||||
app.config_from_object("django.conf:settings", namespace="CELERY")
|
|
||||||
|
|
||||||
app.autodiscover_tasks()
|
|
||||||
@@ -1,12 +0,0 @@
|
|||||||
from .cache import * # noqa
|
|
||||||
from .celery import * # noqa
|
|
||||||
from .cron import * # noqa
|
|
||||||
from .jwt import * # noqa
|
|
||||||
from .logs import * # noqa
|
|
||||||
from .rest_framework import * # noqa
|
|
||||||
from .unfold import * # noqa
|
|
||||||
from .spectacular import * # noqa
|
|
||||||
|
|
||||||
from .ckeditor import * # noqa
|
|
||||||
from .storage import * # noqa
|
|
||||||
from .channels import * # noqa
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
from config.env import env
|
|
||||||
|
|
||||||
APPS = [
|
|
||||||
"channels",
|
|
||||||
"cacheops",
|
|
||||||
"django_ckeditor_5",
|
|
||||||
"drf_spectacular",
|
|
||||||
"rest_framework",
|
|
||||||
"corsheaders",
|
|
||||||
"django_filters",
|
|
||||||
"django_redis",
|
|
||||||
"rest_framework_simplejwt",
|
|
||||||
"django_core",
|
|
||||||
]
|
|
||||||
|
|
||||||
if env.str("PROJECT_ENV") == "debug":
|
|
||||||
APPS += [
|
|
||||||
"silk",
|
|
||||||
]
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
from config.env import env
|
|
||||||
|
|
||||||
CACHES = {
|
|
||||||
"default": {
|
|
||||||
"BACKEND": env.str("CACHE_BACKEND"),
|
|
||||||
"LOCATION": env.str("REDIS_URL"),
|
|
||||||
"TIMEOUT": env.str("CACHE_TIMEOUT"),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
CACHE_MIDDLEWARE_SECONDS = env("CACHE_TIMEOUT")
|
|
||||||
|
|
||||||
|
|
||||||
CACHEOPS_REDIS = env.str("REDIS_URL")
|
|
||||||
CACHEOPS_DEFAULTS = {
|
|
||||||
"timeout": env.str("CACHE_TIMEOUT"),
|
|
||||||
}
|
|
||||||
CACHEOPS = {
|
|
||||||
# !NOTE: api => "you app name"
|
|
||||||
# "api.*": {
|
|
||||||
# "ops": "all", # Barcha turdagi so'rovlarni keshga olish
|
|
||||||
# "timeout": 60 * 5, # 5 daqiqa davomida saqlash
|
|
||||||
# },
|
|
||||||
}
|
|
||||||
CACHEOPS_DEGRADE_ON_FAILURE = True
|
|
||||||
CACHEOPS_ENABLED = env.bool("CACHE_ENABLED", False)
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
CELERY_BEAT_SCHEDULE = {
|
|
||||||
# "test": {
|
|
||||||
# "task": "core.apps.home.tasks.demo.add",
|
|
||||||
# "schedule": 5.0,
|
|
||||||
# "args": (1, 2)
|
|
||||||
# },
|
|
||||||
}
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
CHANNEL_LAYERS = {
|
|
||||||
"default": {
|
|
||||||
"BACKEND": "channels_redis.core.RedisChannelLayer",
|
|
||||||
"CONFIG": {
|
|
||||||
"hosts": [("redis", 6379)],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,147 +0,0 @@
|
|||||||
import os
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
STATIC_URL = "/resources/static/"
|
|
||||||
MEDIA_URL = "/resources/media/"
|
|
||||||
MEDIA_ROOT = os.path.join(Path().parent.parent, "media")
|
|
||||||
|
|
||||||
customColorPalette = [
|
|
||||||
{"color": "hsl(4, 90%, 58%)", "label": "Red"},
|
|
||||||
{"color": "hsl(340, 82%, 52%)", "label": "Pink"},
|
|
||||||
{"color": "hsl(291, 64%, 42%)", "label": "Purple"},
|
|
||||||
{"color": "hsl(262, 52%, 47%)", "label": "Deep Purple"},
|
|
||||||
{"color": "hsl(231, 48%, 48%)", "label": "Indigo"},
|
|
||||||
{"color": "hsl(207, 90%, 54%)", "label": "Blue"},
|
|
||||||
]
|
|
||||||
|
|
||||||
CKEDITOR_5_CONFIGS = {
|
|
||||||
"default": {
|
|
||||||
"toolbar": [
|
|
||||||
"heading",
|
|
||||||
"|",
|
|
||||||
"bold",
|
|
||||||
"italic",
|
|
||||||
"link",
|
|
||||||
"bulletedList",
|
|
||||||
"numberedList",
|
|
||||||
"blockQuote",
|
|
||||||
"imageUpload",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"extends": {
|
|
||||||
"blockToolbar": [
|
|
||||||
"paragraph",
|
|
||||||
"heading1",
|
|
||||||
"heading2",
|
|
||||||
"heading3",
|
|
||||||
"|",
|
|
||||||
"bulletedList",
|
|
||||||
"numberedList",
|
|
||||||
"|",
|
|
||||||
"blockQuote",
|
|
||||||
],
|
|
||||||
"toolbar": [
|
|
||||||
"heading",
|
|
||||||
"|",
|
|
||||||
"outdent",
|
|
||||||
"indent",
|
|
||||||
"|",
|
|
||||||
"bold",
|
|
||||||
"italic",
|
|
||||||
"link",
|
|
||||||
"underline",
|
|
||||||
"strikethrough",
|
|
||||||
"code",
|
|
||||||
"subscript",
|
|
||||||
"superscript",
|
|
||||||
"highlight",
|
|
||||||
"|",
|
|
||||||
"codeBlock",
|
|
||||||
"sourceEditing",
|
|
||||||
"insertImage",
|
|
||||||
"bulletedList",
|
|
||||||
"numberedList",
|
|
||||||
"todoList",
|
|
||||||
"|",
|
|
||||||
"blockQuote",
|
|
||||||
"imageUpload",
|
|
||||||
"|",
|
|
||||||
"fontSize",
|
|
||||||
"fontFamily",
|
|
||||||
"fontColor",
|
|
||||||
"fontBackgroundColor",
|
|
||||||
"mediaEmbed",
|
|
||||||
"removeFormat",
|
|
||||||
"insertTable",
|
|
||||||
],
|
|
||||||
"image": {
|
|
||||||
"toolbar": [
|
|
||||||
"imageTextAlternative",
|
|
||||||
"|",
|
|
||||||
"imageStyle:alignLeft",
|
|
||||||
"imageStyle:alignRight",
|
|
||||||
"imageStyle:alignCenter",
|
|
||||||
"imageStyle:side",
|
|
||||||
"|",
|
|
||||||
],
|
|
||||||
"styles": [
|
|
||||||
"full",
|
|
||||||
"side",
|
|
||||||
"alignLeft",
|
|
||||||
"alignRight",
|
|
||||||
"alignCenter",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
"table": {
|
|
||||||
"contentToolbar": [
|
|
||||||
"tableColumn",
|
|
||||||
"tableRow",
|
|
||||||
"mergeTableCells",
|
|
||||||
"tableProperties",
|
|
||||||
"tableCellProperties",
|
|
||||||
],
|
|
||||||
"tableProperties": {
|
|
||||||
"borderColors": customColorPalette,
|
|
||||||
"backgroundColors": customColorPalette,
|
|
||||||
},
|
|
||||||
"tableCellProperties": {
|
|
||||||
"borderColors": customColorPalette,
|
|
||||||
"backgroundColors": customColorPalette,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"heading": {
|
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"model": "paragraph",
|
|
||||||
"title": "Paragraph",
|
|
||||||
"class": "ck-heading_paragraph",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "heading1",
|
|
||||||
"view": "h1",
|
|
||||||
"title": "Heading 1",
|
|
||||||
"class": "ck-heading_heading1",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "heading2",
|
|
||||||
"view": "h2",
|
|
||||||
"title": "Heading 2",
|
|
||||||
"class": "ck-heading_heading2",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"model": "heading3",
|
|
||||||
"view": "h3",
|
|
||||||
"title": "Heading 3",
|
|
||||||
"class": "ck-heading_heading3",
|
|
||||||
},
|
|
||||||
]
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"list": {
|
|
||||||
"properties": {
|
|
||||||
"styles": "true",
|
|
||||||
"startIndex": "true",
|
|
||||||
"reversed": "true",
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
from datetime import timedelta
|
|
||||||
|
|
||||||
from config.env import env
|
|
||||||
|
|
||||||
SIMPLE_JWT = {
|
|
||||||
"ACCESS_TOKEN_LIFETIME": timedelta(minutes=60),
|
|
||||||
"REFRESH_TOKEN_LIFETIME": timedelta(days=30),
|
|
||||||
"ROTATE_REFRESH_TOKENS": False,
|
|
||||||
"BLACKLIST_AFTER_ROTATION": False,
|
|
||||||
"UPDATE_LAST_LOGIN": False,
|
|
||||||
"ALGORITHM": "HS256",
|
|
||||||
"SIGNING_KEY": env("DJANGO_SECRET_KEY"),
|
|
||||||
"VERIFYING_KEY": "",
|
|
||||||
"AUDIENCE": None,
|
|
||||||
"ISSUER": None,
|
|
||||||
"JSON_ENCODER": None,
|
|
||||||
"JWK_URL": None,
|
|
||||||
"LEEWAY": 0,
|
|
||||||
"AUTH_HEADER_TYPES": ("Bearer",),
|
|
||||||
"AUTH_HEADER_NAME": "HTTP_AUTHORIZATION",
|
|
||||||
"USER_ID_FIELD": "id",
|
|
||||||
"USER_ID_CLAIM": "user_id",
|
|
||||||
"AUTH_TOKEN_CLASSES": ("rest_framework_simplejwt.tokens.AccessToken",),
|
|
||||||
"TOKEN_TYPE_CLAIM": "token_type",
|
|
||||||
"TOKEN_USER_CLASS": "rest_framework_simplejwt.models.TokenUser",
|
|
||||||
"JTI_CLAIM": "jti",
|
|
||||||
"SLIDING_TOKEN_REFRESH_EXP_CLAIM": "refresh_exp",
|
|
||||||
"SLIDING_TOKEN_LIFETIME": timedelta(minutes=60),
|
|
||||||
"SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=30),
|
|
||||||
"TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainPairSerializer",
|
|
||||||
"TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSerializer",
|
|
||||||
"TOKEN_VERIFY_SERIALIZER": "rest_framework_simplejwt.serializers.TokenVerifySerializer",
|
|
||||||
"TOKEN_BLACKLIST_SERIALIZER": "rest_framework_simplejwt.serializers.TokenBlacklistSerializer",
|
|
||||||
"SLIDING_TOKEN_OBTAIN_SERIALIZER": "rest_framework_simplejwt.serializers.TokenObtainSlidingSerializer",
|
|
||||||
"SLIDING_TOKEN_REFRESH_SERIALIZER": "rest_framework_simplejwt.serializers.TokenRefreshSlidingSerializer",
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
# settings.py faylida
|
|
||||||
|
|
||||||
LOGGING = {
|
|
||||||
"version": 1,
|
|
||||||
"disable_existing_loggers": False,
|
|
||||||
"formatters": {
|
|
||||||
"verbose": {
|
|
||||||
"format": "{levelname} {asctime} {module} {message}",
|
|
||||||
"style": "{",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"handlers": {
|
|
||||||
"daily_rotating_file": {
|
|
||||||
"level": "INFO",
|
|
||||||
"class": "logging.handlers.TimedRotatingFileHandler",
|
|
||||||
"filename": "resources/logs/django.log", # Fayl nomi (kunlik fayllar uchun avtomatik yoziladi)
|
|
||||||
"when": "midnight", # Har kecha log fayli yangilanadi
|
|
||||||
"backupCount": 30, # 30 kunlik loglar saqlanadi, 1 oydan keyin eski fayllar o'chiriladi
|
|
||||||
"formatter": "verbose",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"loggers": {
|
|
||||||
"django": {
|
|
||||||
"handlers": ["daily_rotating_file"],
|
|
||||||
"level": "INFO",
|
|
||||||
"propagate": True,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
MODULES = []
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
from django.urls import reverse_lazy
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
|
|
||||||
PAGES = [
|
|
||||||
{
|
|
||||||
"seperator": False,
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"title": _("Home page"),
|
|
||||||
"icon": "home",
|
|
||||||
"link": reverse_lazy("admin:index"),
|
|
||||||
}
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": _("Auth"),
|
|
||||||
"separator": True, # Top border
|
|
||||||
"items": [
|
|
||||||
{
|
|
||||||
"title": _("Users"),
|
|
||||||
"icon": "group",
|
|
||||||
"link": reverse_lazy("admin:http_user_changelist"),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"title": _("Group"),
|
|
||||||
"icon": "group",
|
|
||||||
"link": reverse_lazy("admin:auth_group_changelist"),
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
from typing import Any, Union
|
|
||||||
|
|
||||||
REST_FRAMEWORK: Union[Any] = {
|
|
||||||
"DEFAULT_AUTHENTICATION_CLASSES": ("rest_framework_simplejwt.authentication.JWTAuthentication",),
|
|
||||||
"DEFAULT_SCHEMA_CLASS": "drf_spectacular.openapi.AutoSchema",
|
|
||||||
"DEFAULT_PERMISSION_CLASSES": ["rest_framework.permissions.IsAuthenticated"],
|
|
||||||
"DEFAULT_PAGINATION_CLASS": "django_core.paginations.CustomPagination",
|
|
||||||
"PAGE_SIZE": 10,
|
|
||||||
}
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
SPECTACULAR_SETTINGS = {
|
|
||||||
"TITLE": "Your Project API",
|
|
||||||
"DESCRIPTION": "Your project description",
|
|
||||||
"VERSION": "1.0.0",
|
|
||||||
"SERVE_INCLUDE_SCHEMA": False,
|
|
||||||
"CAMELIZE_NAMES": True,
|
|
||||||
"POSTPROCESSING_HOOKS": ["config.conf.spectacular.custom_postprocessing_hook"],
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
def custom_postprocessing_hook(result, generator, request, public):
|
|
||||||
"""
|
|
||||||
Customizes the API schema to wrap all responses in a standard format.
|
|
||||||
"""
|
|
||||||
for path, methods in result.get("paths", {}).items():
|
|
||||||
for method, operation in methods.items():
|
|
||||||
if "responses" in operation:
|
|
||||||
for status_code, response in operation["responses"].items():
|
|
||||||
if "content" in response:
|
|
||||||
for content_type, content in response["content"].items():
|
|
||||||
# Wrap original schema
|
|
||||||
original_schema = content.get("schema", {})
|
|
||||||
response["content"][content_type]["schema"] = {
|
|
||||||
"type": "object",
|
|
||||||
"properties": {
|
|
||||||
"status": {"type": "boolean", "example": True},
|
|
||||||
"data": original_schema,
|
|
||||||
},
|
|
||||||
"required": ["status", "data"],
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
@@ -1,23 +0,0 @@
|
|||||||
from config.env import env
|
|
||||||
from core.utils.storage import Storage
|
|
||||||
|
|
||||||
AWS_ACCESS_KEY_ID = env.str("STORAGE_ID")
|
|
||||||
AWS_SECRET_ACCESS_KEY = env.str("STORAGE_KEY")
|
|
||||||
AWS_S3_ENDPOINT_URL = env.str("STORAGE_URL")
|
|
||||||
AWS_S3_CUSTOM_DOMAIN = env.str("STORAGE_PATH")
|
|
||||||
AWS_S3_URL_PROTOCOL = env.str("STORAGE_PROTOCOL", "https:")
|
|
||||||
AWS_S3_FILE_OVERWRITE = False
|
|
||||||
|
|
||||||
default_storage = Storage(env.str("STORAGE_DEFAULT"), "default")
|
|
||||||
static_storage = Storage(env.str("STORAGE_STATIC"), "static")
|
|
||||||
|
|
||||||
STORAGES = {
|
|
||||||
"default": {
|
|
||||||
"BACKEND": default_storage.get_backend(),
|
|
||||||
"OPTIONS": default_storage.get_options(),
|
|
||||||
},
|
|
||||||
"staticfiles": {
|
|
||||||
"BACKEND": static_storage.get_backend(),
|
|
||||||
"OPTIONS": static_storage.get_options(),
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
from . import navigation
|
|
||||||
|
|
||||||
UNFOLD = {
|
|
||||||
"DASHBOARD_CALLBACK": "django_core.views.dashboard_callback",
|
|
||||||
"SITE_TITLE": None,
|
|
||||||
"SHOW_LANGUAGES": True,
|
|
||||||
"SITE_HEADER": None,
|
|
||||||
"SITE_URL": "/",
|
|
||||||
"SITE_SYMBOL": "speed", # symbol from icon set
|
|
||||||
"SHOW_HISTORY": True, # show/hide "History" button, default: True
|
|
||||||
"SHOW_VIEW_ON_SITE": True,
|
|
||||||
"COLORS": {
|
|
||||||
"primary": {
|
|
||||||
"50": "220 255 230",
|
|
||||||
"100": "190 255 200",
|
|
||||||
"200": "160 255 170",
|
|
||||||
"300": "130 255 140",
|
|
||||||
"400": "100 255 110",
|
|
||||||
"500": "70 255 80",
|
|
||||||
"600": "50 225 70",
|
|
||||||
"700": "40 195 60",
|
|
||||||
"800": "30 165 50",
|
|
||||||
"900": "20 135 40",
|
|
||||||
"950": "10 105 30",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"EXTENSIONS": {
|
|
||||||
"modeltranslation": {
|
|
||||||
"flags": {
|
|
||||||
"en": "🇬🇧",
|
|
||||||
"uz": "🇺🇿",
|
|
||||||
"ru": "🇷🇺",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
"SIDEBAR": {
|
|
||||||
"show_search": True, # Search in applications and models names
|
|
||||||
"show_all_applications": True,
|
|
||||||
# "navigation": navigation.PAGES, # Pagelarni qo'lda qo'shish
|
|
||||||
},
|
|
||||||
}
|
|
||||||
@@ -1,28 +0,0 @@
|
|||||||
"""
|
|
||||||
Default value for environ variable
|
|
||||||
"""
|
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
import environ
|
|
||||||
|
|
||||||
environ.Env.read_env(os.path.join(".env"))
|
|
||||||
|
|
||||||
env = environ.Env(
|
|
||||||
DEBUG=(bool, False),
|
|
||||||
CACHE_TIME=(int, 180),
|
|
||||||
OTP_EXPIRE_TIME=(int, 2),
|
|
||||||
VITE_LIVE=(bool, False),
|
|
||||||
ALLOWED_HOSTS=(str, "localhost"),
|
|
||||||
CSRF_TRUSTED_ORIGINS=(str, "localhost"),
|
|
||||||
DJANGO_SETTINGS_MODULE=(str, "config.settings.local"),
|
|
||||||
CACHE_TIMEOUT=(int, 120),
|
|
||||||
CACHE_ENABLED=(bool, False),
|
|
||||||
VITE_PORT=(int, 5173),
|
|
||||||
VITE_HOST=(str, "vite"),
|
|
||||||
NGROK_AUTHTOKEN=(str, "TOKEN"),
|
|
||||||
BOT_TOKEN=(str, "TOKEN"),
|
|
||||||
OTP_MODULE="core.services.otp",
|
|
||||||
OTP_SERVICE="EskizService",
|
|
||||||
PROJECT_ENV=(str, "prod"),
|
|
||||||
)
|
|
||||||
@@ -1,172 +0,0 @@
|
|||||||
import os
|
|
||||||
import pathlib
|
|
||||||
from typing import List, Union
|
|
||||||
|
|
||||||
from config.conf import * # noqa
|
|
||||||
from config.conf.apps import APPS
|
|
||||||
from config.conf.modules import MODULES
|
|
||||||
from config.env import env
|
|
||||||
from django.utils.translation import gettext_lazy as _
|
|
||||||
from rich.traceback import install
|
|
||||||
|
|
||||||
install(show_locals=True)
|
|
||||||
BASE_DIR = pathlib.Path(__file__).resolve().parent.parent.parent
|
|
||||||
|
|
||||||
SECRET_KEY = env.str("DJANGO_SECRET_KEY")
|
|
||||||
DEBUG = env.bool("DEBUG")
|
|
||||||
|
|
||||||
ALLOWED_HOSTS: Union[List[str]] = ["*"]
|
|
||||||
|
|
||||||
if env.bool("PROTOCOL_HTTPS", False):
|
|
||||||
SECURE_PROXY_SSL_HEADER = ("HTTP_X_FORWARDED_PROTO", "https")
|
|
||||||
|
|
||||||
DATABASES = {
|
|
||||||
"default": {
|
|
||||||
"ENGINE": env.str("DB_ENGINE"),
|
|
||||||
"NAME": env.str("DB_NAME"),
|
|
||||||
"USER": env.str("DB_USER"),
|
|
||||||
"PASSWORD": env.str("DB_PASSWORD"),
|
|
||||||
"HOST": env.str("DB_HOST"),
|
|
||||||
"PORT": env.str("DB_PORT"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PASSWORD_HASHERS = [
|
|
||||||
"django.contrib.auth.hashers.BCryptPasswordHasher",
|
|
||||||
]
|
|
||||||
|
|
||||||
INSTALLED_APPS = [
|
|
||||||
"modeltranslation",
|
|
||||||
"unfold",
|
|
||||||
"unfold.contrib.filters",
|
|
||||||
"unfold.contrib.forms",
|
|
||||||
"unfold.contrib.guardian",
|
|
||||||
"unfold.contrib.simple_history",
|
|
||||||
"django.contrib.admin",
|
|
||||||
"django.contrib.auth",
|
|
||||||
"django.contrib.contenttypes",
|
|
||||||
"django.contrib.sessions",
|
|
||||||
"django.contrib.messages",
|
|
||||||
"django.contrib.staticfiles",
|
|
||||||
] + APPS
|
|
||||||
|
|
||||||
MODULES = [app for app in MODULES if isinstance(app, str)]
|
|
||||||
|
|
||||||
for module_path in MODULES:
|
|
||||||
INSTALLED_APPS.append("{}.apps.ModuleConfig".format(module_path))
|
|
||||||
|
|
||||||
MIDDLEWARE = [
|
|
||||||
"django.middleware.security.SecurityMiddleware",
|
|
||||||
"django.contrib.sessions.middleware.SessionMiddleware",
|
|
||||||
"corsheaders.middleware.CorsMiddleware", # Cors middleware
|
|
||||||
"django.middleware.locale.LocaleMiddleware", # Locale middleware
|
|
||||||
"django.middleware.common.CommonMiddleware",
|
|
||||||
"django.middleware.csrf.CsrfViewMiddleware",
|
|
||||||
"django.contrib.auth.middleware.AuthenticationMiddleware",
|
|
||||||
"django.contrib.messages.middleware.MessageMiddleware",
|
|
||||||
"django.middleware.clickjacking.XFrameOptionsMiddleware",
|
|
||||||
]
|
|
||||||
if env.str("PROJECT_ENV") == "debug":
|
|
||||||
MIDDLEWARE += [
|
|
||||||
"silk.middleware.SilkyMiddleware",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
ROOT_URLCONF = "config.urls"
|
|
||||||
|
|
||||||
TEMPLATES = [
|
|
||||||
{
|
|
||||||
"BACKEND": "django.template.backends.django.DjangoTemplates",
|
|
||||||
"DIRS": [os.path.join(BASE_DIR, "resources/templates")],
|
|
||||||
"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",
|
|
||||||
],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
]
|
|
||||||
# fmt: off
|
|
||||||
|
|
||||||
WSGI_APPLICATION = "config.wsgi.application"
|
|
||||||
|
|
||||||
# fmt: on
|
|
||||||
|
|
||||||
AUTH_PASSWORD_VALIDATORS = [
|
|
||||||
{
|
|
||||||
"NAME": "django.contrib.auth.password_validation.{}".format(validator)
|
|
||||||
} for validator in [
|
|
||||||
"UserAttributeSimilarityValidator",
|
|
||||||
"MinimumLengthValidator",
|
|
||||||
"CommonPasswordValidator",
|
|
||||||
"NumericPasswordValidator"
|
|
||||||
]
|
|
||||||
]
|
|
||||||
|
|
||||||
TIME_ZONE = "Asia/Tashkent"
|
|
||||||
USE_I18N = True
|
|
||||||
USE_TZ = True
|
|
||||||
STATIC_URL = "resources/static/"
|
|
||||||
DEFAULT_AUTO_FIELD = "django.db.models.BigAutoField"
|
|
||||||
|
|
||||||
# Date formats
|
|
||||||
##
|
|
||||||
DATE_FORMAT = "d.m.y"
|
|
||||||
TIME_FORMAT = "H:i:s"
|
|
||||||
DATE_INPUT_FORMATS = ["%d.%m.%Y", "%Y.%d.%m", "%Y.%d.%m"]
|
|
||||||
|
|
||||||
|
|
||||||
SEEDERS = ["core.apps.accounts.seeder.UserSeeder"]
|
|
||||||
|
|
||||||
STATICFILES_DIRS = [
|
|
||||||
os.path.join(BASE_DIR, "resources/static"),
|
|
||||||
]
|
|
||||||
|
|
||||||
CORS_ORIGIN_ALLOW_ALL = True
|
|
||||||
|
|
||||||
STATIC_ROOT = os.path.join(BASE_DIR, "resources/staticfiles")
|
|
||||||
VITE_APP_DIR = os.path.join(BASE_DIR, "resources/static/vite")
|
|
||||||
|
|
||||||
LANGUAGES = (
|
|
||||||
("ru", _("Russia")),
|
|
||||||
("en", _("English")),
|
|
||||||
("uz", _("Uzbek")),
|
|
||||||
)
|
|
||||||
LOCALE_PATHS = [os.path.join(BASE_DIR, "resources/locale")]
|
|
||||||
|
|
||||||
LANGUAGE_CODE = "uz"
|
|
||||||
|
|
||||||
MEDIA_ROOT = os.path.join(BASE_DIR, "resources/media") # Media files
|
|
||||||
MEDIA_URL = "/resources/media/"
|
|
||||||
|
|
||||||
CELERY_BROKER_URL = env("REDIS_URL")
|
|
||||||
CELERY_RESULT_BACKEND = env("REDIS_URL")
|
|
||||||
|
|
||||||
ALLOWED_HOSTS += env("ALLOWED_HOSTS").split(",")
|
|
||||||
CSRF_TRUSTED_ORIGINS = env("CSRF_TRUSTED_ORIGINS").split(",")
|
|
||||||
SILKY_AUTHORISATION = True
|
|
||||||
SILKY_PYTHON_PROFILER = True
|
|
||||||
|
|
||||||
MODELTRANSLATION_LANGUAGES = ("uz", "ru", "en")
|
|
||||||
MODELTRANSLATION_DEFAULT_LANGUAGE = "uz"
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
JST_LANGUAGES = [
|
|
||||||
{
|
|
||||||
"code": "uz",
|
|
||||||
"name": "Uzbek",
|
|
||||||
"is_default": True,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "en",
|
|
||||||
"name": "English",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"code": "ru",
|
|
||||||
"name": "Russia",
|
|
||||||
}
|
|
||||||
]
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
from config.settings.common import * # noqa
|
|
||||||
from config.settings.common import (ALLOWED_HOSTS, INSTALLED_APPS,
|
|
||||||
REST_FRAMEWORK)
|
|
||||||
|
|
||||||
INSTALLED_APPS += ["django_extensions"]
|
|
||||||
|
|
||||||
ALLOWED_HOSTS += ["127.0.0.1", "192.168.100.26"]
|
|
||||||
|
|
||||||
REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = {
|
|
||||||
"user": "60/min",
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
from config.settings.common import * # noqa
|
|
||||||
from config.settings.common import ALLOWED_HOSTS, REST_FRAMEWORK
|
|
||||||
|
|
||||||
ALLOWED_HOSTS += ["192.168.100.26", "80.90.178.156"]
|
|
||||||
|
|
||||||
REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = {"user": "60/min"}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
"""
|
|
||||||
All urls configurations tree
|
|
||||||
"""
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from config.env import env
|
|
||||||
from django.contrib import admin
|
|
||||||
from django.urls import include, path, re_path
|
|
||||||
from django.views.static import serve
|
|
||||||
from drf_spectacular.views import (SpectacularAPIView, SpectacularRedocView,
|
|
||||||
SpectacularSwaggerView)
|
|
||||||
|
|
||||||
################
|
|
||||||
# My apps url
|
|
||||||
################
|
|
||||||
urlpatterns = [
|
|
||||||
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
################
|
|
||||||
# Library urls
|
|
||||||
################
|
|
||||||
urlpatterns += [
|
|
||||||
path("admin/", admin.site.urls),
|
|
||||||
path("accounts/", include("django.contrib.auth.urls")),
|
|
||||||
path("i18n/", include("django.conf.urls.i18n")),
|
|
||||||
|
|
||||||
path("ckeditor5/", include("django_ckeditor_5.urls"), name="ck_editor_5_upload_file"),
|
|
||||||
]
|
|
||||||
|
|
||||||
################
|
|
||||||
# Project env debug mode
|
|
||||||
################
|
|
||||||
if env.str("PROJECT_ENV") == "debug":
|
|
||||||
urlpatterns += [
|
|
||||||
path('silk/', include('silk.urls', namespace='silk'))
|
|
||||||
]
|
|
||||||
|
|
||||||
################
|
|
||||||
# Swagger urls
|
|
||||||
################
|
|
||||||
urlpatterns += [
|
|
||||||
path("schema/", SpectacularAPIView.as_view(), name="schema"),
|
|
||||||
path("swagger/", SpectacularSwaggerView.as_view(url_name="schema"), name="swagger-ui"),
|
|
||||||
path("redoc/", SpectacularRedocView.as_view(url_name="schema"), name="redoc"),
|
|
||||||
]
|
|
||||||
|
|
||||||
################
|
|
||||||
# Media urls
|
|
||||||
################
|
|
||||||
urlpatterns += [
|
|
||||||
re_path(r"static/(?P<path>.*)", serve, {"document_root": settings.STATIC_ROOT}),
|
|
||||||
re_path(r"media/(?P<path>.*)", serve, {"document_root": settings.MEDIA_ROOT}),
|
|
||||||
]
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
import os
|
|
||||||
|
|
||||||
from config.env import env
|
|
||||||
from django.core.wsgi import get_wsgi_application
|
|
||||||
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", env("DJANGO_SETTINGS_MODULE"))
|
|
||||||
|
|
||||||
application = get_wsgi_application()
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
from .cache import * # noqa
|
|
||||||
from .console import * # noqa
|
|
||||||
from .core import * # noqa
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
import hashlib
|
|
||||||
|
|
||||||
from django.core.cache import cache
|
|
||||||
|
|
||||||
from config.env import env
|
|
||||||
|
|
||||||
|
|
||||||
class Cache:
|
|
||||||
def remember(self, func, key: str, timeout=None, *args, **kwargs):
|
|
||||||
cache_enabled = env.bool("CACHE_ENABLED")
|
|
||||||
key = hashlib.md5(key.encode("utf-8")).hexdigest()
|
|
||||||
response = cache.get(key)
|
|
||||||
if not cache_enabled:
|
|
||||||
return func(*args, **kwargs)
|
|
||||||
elif response is None:
|
|
||||||
response = func(*args, **kwargs)
|
|
||||||
cache.set(key, response, env.int("CACHE_TIME") if timeout is None else timeout)
|
|
||||||
return response
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
import logging
|
|
||||||
import os
|
|
||||||
from typing import Any, Union
|
|
||||||
|
|
||||||
from django.conf import settings
|
|
||||||
from django.core import management
|
|
||||||
|
|
||||||
|
|
||||||
class Console(management.BaseCommand):
|
|
||||||
"""
|
|
||||||
Console logging class
|
|
||||||
"""
|
|
||||||
|
|
||||||
def get_stdout(self):
|
|
||||||
base_command = management.BaseCommand()
|
|
||||||
return base_command.stdout
|
|
||||||
|
|
||||||
def get_style(self):
|
|
||||||
base_command = management.BaseCommand()
|
|
||||||
return base_command.style
|
|
||||||
|
|
||||||
def success(self, message):
|
|
||||||
logging.debug(message)
|
|
||||||
self.get_stdout().write(self.get_style().SUCCESS(message))
|
|
||||||
|
|
||||||
def error(self, message):
|
|
||||||
logging.error(message)
|
|
||||||
self.get_stdout().write(self.get_style().ERROR(message))
|
|
||||||
|
|
||||||
def log(self, message):
|
|
||||||
self.get_stdout().write(
|
|
||||||
self.get_style().ERROR(
|
|
||||||
"\n{line}\n{message}\n{line}\n".format(
|
|
||||||
message=message, line="=" * len(message)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
class BaseMake(management.BaseCommand):
|
|
||||||
path: str
|
|
||||||
|
|
||||||
def __init__(self, *args, **options):
|
|
||||||
super().__init__(*args, **options)
|
|
||||||
self.console = Console()
|
|
||||||
|
|
||||||
def add_arguments(self, parser):
|
|
||||||
parser.add_argument("name")
|
|
||||||
|
|
||||||
def handle(self, *args, **options):
|
|
||||||
name = options.get("name")
|
|
||||||
if name is None:
|
|
||||||
name = ""
|
|
||||||
|
|
||||||
stub = open(os.path.join(settings.BASE_DIR, f"resources/stub/{self.path}.stub"))
|
|
||||||
data: Union[Any] = stub.read()
|
|
||||||
stub.close()
|
|
||||||
|
|
||||||
stub = data.replace("{{name}}", name or "")
|
|
||||||
|
|
||||||
|
|
||||||
core_http_path = os.path.join(settings.BASE_DIR, "core/http")
|
|
||||||
if os.path.exists(
|
|
||||||
os.path.join(core_http_path, f"{self.path}/{name.lower()}.py")
|
|
||||||
): # noqa
|
|
||||||
self.console.error(f"{self.name} already exists")
|
|
||||||
return
|
|
||||||
|
|
||||||
if not os.path.exists(os.path.join(core_http_path, self.path)):
|
|
||||||
os.makedirs(os.path.join(core_http_path, self.path))
|
|
||||||
|
|
||||||
file = open(
|
|
||||||
os.path.join(core_http_path, f"{self.path}/{name.lower()}.py"),
|
|
||||||
"w+",
|
|
||||||
)
|
|
||||||
file.write(stub) # type: ignore
|
|
||||||
file.close()
|
|
||||||
|
|
||||||
self.console.success(f"{self.name} created")
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
class Helper:
|
|
||||||
"""
|
|
||||||
Helper class to handle index
|
|
||||||
"""
|
|
||||||
|
|
||||||
pass
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
# rabbitmq.py
|
|
||||||
|
|
||||||
from kombu import Exchange, Producer
|
|
||||||
from .rabbitmq import get_connection
|
|
||||||
|
|
||||||
_channel = None
|
|
||||||
_exchange = Exchange('notification', type='direct')
|
|
||||||
_producer = None
|
|
||||||
|
|
||||||
def get_producer():
|
|
||||||
global _producer, _channel
|
|
||||||
if _producer is None:
|
|
||||||
conn = get_connection()
|
|
||||||
_channel = conn.channel()
|
|
||||||
_producer = Producer(_channel, exchange=_exchange, routing_key="notification")
|
|
||||||
return _producer
|
|
||||||
|
|
||||||
|
|
||||||
def send_notification(message: dict):
|
|
||||||
producer = get_producer()
|
|
||||||
producer.publish(message, serializer='json')
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
# rabbitmq.py
|
|
||||||
|
|
||||||
from kombu import Connection
|
|
||||||
|
|
||||||
rabbit_url = 'amqp://guest:guest@rabbitmq:5672/'
|
|
||||||
_connection = None
|
|
||||||
|
|
||||||
|
|
||||||
def get_connection():
|
|
||||||
global _connection
|
|
||||||
if _connection is None or not _connection.connected:
|
|
||||||
_connection = Connection(rabbit_url)
|
|
||||||
_connection.ensure_connection(max_retries=3)
|
|
||||||
return _connection
|
|
||||||
|
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
from typing import Optional, Union
|
|
||||||
|
|
||||||
from config.env import env
|
|
||||||
|
|
||||||
|
|
||||||
class Storage:
|
|
||||||
|
|
||||||
storages = ["AWS", "MINIO", "FILE", "STATIC"]
|
|
||||||
|
|
||||||
def __init__(self, storage: Union[str], storage_type: Union[str] = "default") -> None:
|
|
||||||
self.storage = storage
|
|
||||||
self.sorage_type = storage_type
|
|
||||||
if storage not in self.storages:
|
|
||||||
raise ValueError(f"Invalid storage type: {storage}")
|
|
||||||
|
|
||||||
def get_backend(self) -> Optional[str]:
|
|
||||||
match self.storage:
|
|
||||||
case "AWS" | "MINIO":
|
|
||||||
return "storages.backends.s3boto3.S3Boto3Storage"
|
|
||||||
case "FILE":
|
|
||||||
return "django.core.files.storage.FileSystemStorage"
|
|
||||||
case "STATIC":
|
|
||||||
return "django.contrib.staticfiles.storage.StaticFilesStorage"
|
|
||||||
|
|
||||||
def get_options(self) -> Optional[str]:
|
|
||||||
match self.storage:
|
|
||||||
case "AWS" | "MINIO":
|
|
||||||
if self.sorage_type == "default":
|
|
||||||
return {"bucket_name": env.str("STORAGE_BUCKET_MEDIA")}
|
|
||||||
elif self.sorage_type == "static":
|
|
||||||
return {"bucket_name": env.str("STORAGE_BUCKET_STATIC")}
|
|
||||||
case _:
|
|
||||||
return {}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
networks:
|
|
||||||
lamenu:
|
|
||||||
external: true
|
|
||||||
|
|
||||||
volumes:
|
|
||||||
pg_data: null
|
|
||||||
pycache: null
|
|
||||||
|
|
||||||
services:
|
|
||||||
order-nginx:
|
|
||||||
labels:
|
|
||||||
- "traefik.enable=true"
|
|
||||||
- "traefik.http.routers.order.rule=PathPrefix(`/order`)"
|
|
||||||
- "traefik.http.routers.order.entrypoints=web"
|
|
||||||
- "traefik.http.services.order.loadbalancer.server.port=80"
|
|
||||||
# - "traefik.http.middlewares.order-strip-prefix.stripPrefix.prefixes=/order"
|
|
||||||
# - "traefik.http.routers.order.middlewares=order-strip-prefix"
|
|
||||||
networks:
|
|
||||||
- lamenu
|
|
||||||
volumes:
|
|
||||||
- ./resources/layout/nginx.conf:/etc/nginx/nginx.conf
|
|
||||||
- ./resources/:/usr/share/nginx/html/resources/
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: ./docker/Dockerfile.nginx
|
|
||||||
depends_on:
|
|
||||||
- order
|
|
||||||
order:
|
|
||||||
networks:
|
|
||||||
- lamenu
|
|
||||||
build:
|
|
||||||
context: .
|
|
||||||
dockerfile: ./docker/Dockerfile.web
|
|
||||||
restart: always
|
|
||||||
command: ${COMMAND:-sh ./entrypoint.sh}
|
|
||||||
environment:
|
|
||||||
- PYTHONPYCACHEPREFIX=/var/cache/pycache
|
|
||||||
volumes:
|
|
||||||
- .:/code
|
|
||||||
- pycache:/var/cache/pycache
|
|
||||||
depends_on:
|
|
||||||
- order-db
|
|
||||||
order-db:
|
|
||||||
networks:
|
|
||||||
- lamenu
|
|
||||||
image: postgres:16
|
|
||||||
restart: always
|
|
||||||
environment:
|
|
||||||
POSTGRES_DB: django
|
|
||||||
POSTGRES_USER: postgres
|
|
||||||
POSTGRES_PASSWORD: '2309'
|
|
||||||
volumes:
|
|
||||||
- pg_data:/var/lib/postgresql/data
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
FROM nginx:alpine
|
|
||||||
|
|
||||||
COPY ./resources/layout/nginx.conf /etc/nginx/nginx.conf
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
FROM jscorptech/django:v0.5
|
|
||||||
|
|
||||||
WORKDIR /code
|
|
||||||
|
|
||||||
COPY ./ /code
|
|
||||||
|
|
||||||
RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt
|
|
||||||
|
|
||||||
CMD ["sh", "./resources/scripts/entrypoint.sh"]
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
{
|
|
||||||
"dirs": {
|
|
||||||
"apps": "./core/apps/",
|
|
||||||
"locale": "./resources/locale/"
|
|
||||||
},
|
|
||||||
"stubs": {},
|
|
||||||
"apps": "core.apps."
|
|
||||||
}
|
|
||||||
@@ -1,24 +0,0 @@
|
|||||||
#!/usr/bin/env python
|
|
||||||
"""Django's command-line utility for administrative tasks."""
|
|
||||||
import os
|
|
||||||
import sys
|
|
||||||
|
|
||||||
from config.env import env
|
|
||||||
|
|
||||||
|
|
||||||
def main():
|
|
||||||
"""Run administrative tasks."""
|
|
||||||
os.environ.setdefault("DJANGO_SETTINGS_MODULE", env("DJANGO_SETTINGS_MODULE"))
|
|
||||||
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()
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
[tool.black]
|
|
||||||
line-length = 120
|
|
||||||
|
|
||||||
[tool.isort]
|
|
||||||
profile = "black"
|
|
||||||
line_length = 120
|
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
|
||||||
DJANGO_SETTINGS_MODULE = "config.settings.local"
|
|
||||||
python_files = "tests.py test_*.py *_tests.py"
|
|
||||||
filterwarnings = [
|
|
||||||
"ignore::DeprecationWarning",
|
|
||||||
"ignore::PendingDeprecationWarning",
|
|
||||||
"ignore::ResourceWarning",
|
|
||||||
"ignore::Warning" # This line will ignore all warnings
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
[tool.flake8]
|
|
||||||
max-line-length = 120
|
|
||||||
ignore = ["E701", "E704", "W503"]
|
|
||||||
@@ -1,45 +0,0 @@
|
|||||||
backports.tarfile==1.2.0
|
|
||||||
celery==5.4.0
|
|
||||||
django-cors-headers==4.6.0
|
|
||||||
django-environ==0.11.2
|
|
||||||
django-extensions==3.2.3
|
|
||||||
django-filter==24.3
|
|
||||||
django-redis==5.4.0
|
|
||||||
django-unfold==0.42.0
|
|
||||||
djangorestframework-simplejwt==5.3.1
|
|
||||||
drf-spectacular==0.28.0
|
|
||||||
importlib-metadata==8.5.0
|
|
||||||
importlib-resources==6.4.5
|
|
||||||
inflect==7.3.1
|
|
||||||
jaraco.collections==5.1.0
|
|
||||||
packaging==24.2
|
|
||||||
pip-chill==1.0.3
|
|
||||||
platformdirs==4.3.6
|
|
||||||
psycopg2-binary==2.9.10
|
|
||||||
tomli==2.2.1
|
|
||||||
uvicorn==0.32.1
|
|
||||||
jst-django-core~=1.1.8
|
|
||||||
rich
|
|
||||||
pydantic
|
|
||||||
bcrypt
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
django-modeltranslation~=0.19.11
|
|
||||||
django-ckeditor-5==0.2.15
|
|
||||||
channels==4.2.0
|
|
||||||
|
|
||||||
django-cacheops~=7.1
|
|
||||||
django-silk
|
|
||||||
|
|
||||||
# !NOTE: on-server
|
|
||||||
# gunicorn
|
|
||||||
|
|
||||||
|
|
||||||
django-storages
|
|
||||||
boto3
|
|
||||||
|
|
||||||
|
|
||||||
# !NOTE: on-websocket
|
|
||||||
# websockets
|
|
||||||
# channels-redis
|
|
||||||
1
order/resources/.gitignore
vendored
1
order/resources/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
staticfiles/
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
[flake8]
|
|
||||||
max-line-length = 120
|
|
||||||
ignore = E701, E704, W503
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
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"]
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
FROM nginx:alpine
|
|
||||||
|
|
||||||
COPY ./resources/layout/nginx.conf /etc/nginx/nginx.conf
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
[mypy]
|
|
||||||
check_untyped_defs = True
|
|
||||||
|
|
||||||
[mypy-requests.*]
|
|
||||||
ignore_missing_imports = True
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
# Main configuration block
|
|
||||||
worker_processes 1;
|
|
||||||
|
|
||||||
events {
|
|
||||||
worker_connections 1024;
|
|
||||||
}
|
|
||||||
|
|
||||||
http {
|
|
||||||
include mime.types;
|
|
||||||
default_type application/octet-stream;
|
|
||||||
|
|
||||||
# Logging settings (optional)
|
|
||||||
log_format main '$remote_addr - $remote_user [$time_local] "$request" '
|
|
||||||
'$status $body_bytes_sent "$http_referer" '
|
|
||||||
'"$http_user_agent" "$http_x_forwarded_for"';
|
|
||||||
access_log /var/log/nginx/access.log main;
|
|
||||||
|
|
||||||
sendfile on;
|
|
||||||
tcp_nopush on;
|
|
||||||
tcp_nodelay on;
|
|
||||||
keepalive_timeout 65;
|
|
||||||
types_hash_max_size 2048;
|
|
||||||
client_max_body_size 1024M;
|
|
||||||
|
|
||||||
# Server block for handling requests
|
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
|
|
||||||
server_name _;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://order:8000;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto https;
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
}
|
|
||||||
location /ws/ {
|
|
||||||
proxy_pass http://order:8000; # Uvicorn serveri ishga tushadigan port
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Upgrade $http_upgrade;
|
|
||||||
proxy_set_header Connection 'upgrade';
|
|
||||||
proxy_set_header Host $http_host;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /resources/static/ {
|
|
||||||
alias /usr/share/nginx/html/resources/staticfiles/;
|
|
||||||
}
|
|
||||||
|
|
||||||
location /resources/media/ {
|
|
||||||
alias /usr/share/nginx/html/resources/media/;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
||||||
#
|
|
||||||
#, fuzzy
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2024-02-15 16:40+0500\n"
|
|
||||||
"PO-Revision-Date: 2024-02-09 15:09+0500\n"
|
|
||||||
"Last-Translator: <admin@gmail.com>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"Language: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Plural-Forms: nplurals=2; plural=(n != 1);\n"
|
|
||||||
"X-Translated-Using: django-rosetta 0.10.0\n"
|
|
||||||
|
|
||||||
#: config/settings/common.py:148
|
|
||||||
msgid "Russia"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: config/settings/common.py:149
|
|
||||||
msgid "English"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: config/settings/common.py:150
|
|
||||||
msgid "Uzbek"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: core/http/admin/index.py:20
|
|
||||||
msgid "Custom Field"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: core/http/tasks/index.py:13
|
|
||||||
#, python-format
|
|
||||||
msgid "Sizning Tasdiqlash ko'dingiz: %(code)s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: resources/templates/user/home.html:18
|
|
||||||
msgid "Django"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: resources/templates/user/home.html:19
|
|
||||||
msgid "Assalomu aleykum"
|
|
||||||
msgstr ""
|
|
||||||
@@ -1,51 +0,0 @@
|
|||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
||||||
#
|
|
||||||
#, fuzzy
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2024-02-15 16:40+0500\n"
|
|
||||||
"PO-Revision-Date: 2024-02-09 15:09+0500\n"
|
|
||||||
"Last-Translator: <admin@gmail.com>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"Language: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Plural-Forms: nplurals=4; plural=(n%10==1 && n%100!=11 ? 0 : n%10>=2 && "
|
|
||||||
"n%10<=4 && (n%100<12 || n%100>14) ? 1 : n%10==0 || (n%10>=5 && n%10<=9) || "
|
|
||||||
"(n%100>=11 && n%100<=14)? 2 : 3);\n"
|
|
||||||
"X-Translated-Using: django-rosetta 0.10.0\n"
|
|
||||||
|
|
||||||
#: config/settings/common.py:148
|
|
||||||
msgid "Russia"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: config/settings/common.py:149
|
|
||||||
msgid "English"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: config/settings/common.py:150
|
|
||||||
msgid "Uzbek"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: core/http/admin/index.py:20
|
|
||||||
msgid "Custom Field"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: core/http/tasks/index.py:13
|
|
||||||
#, python-format
|
|
||||||
msgid "Sizning Tasdiqlash ko'dingiz: %(code)s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: resources/templates/user/home.html:18
|
|
||||||
msgid "Django"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: resources/templates/user/home.html:19
|
|
||||||
msgid "Assalomu aleykum"
|
|
||||||
msgstr ""
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
# SOME DESCRIPTIVE TITLE.
|
|
||||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
|
||||||
# This file is distributed under the same license as the PACKAGE package.
|
|
||||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
|
||||||
#
|
|
||||||
#, fuzzy
|
|
||||||
msgid ""
|
|
||||||
msgstr ""
|
|
||||||
"Project-Id-Version: PACKAGE VERSION\n"
|
|
||||||
"Report-Msgid-Bugs-To: \n"
|
|
||||||
"POT-Creation-Date: 2024-02-15 16:40+0500\n"
|
|
||||||
"PO-Revision-Date: 2024-02-10 22:46+0500\n"
|
|
||||||
"Last-Translator: <admin@gmail.com>\n"
|
|
||||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
|
||||||
"Language: \n"
|
|
||||||
"MIME-Version: 1.0\n"
|
|
||||||
"Content-Type: text/plain; charset=UTF-8\n"
|
|
||||||
"Content-Transfer-Encoding: 8bit\n"
|
|
||||||
"Plural-Forms: nplurals=1; plural=0;\n"
|
|
||||||
"X-Translated-Using: django-rosetta 0.10.0\n"
|
|
||||||
|
|
||||||
#: config/settings/common.py:148
|
|
||||||
msgid "Russia"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: config/settings/common.py:149
|
|
||||||
msgid "English"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: config/settings/common.py:150
|
|
||||||
msgid "Uzbek"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: core/http/admin/index.py:20
|
|
||||||
msgid "Custom Field"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: core/http/tasks/index.py:13
|
|
||||||
#, python-format
|
|
||||||
msgid "Sizning Tasdiqlash ko'dingiz: %(code)s"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: resources/templates/user/home.html:18
|
|
||||||
msgid "Django"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#: resources/templates/user/home.html:19
|
|
||||||
msgid "Assalomu aleykum"
|
|
||||||
msgstr ""
|
|
||||||
|
|
||||||
#~ msgid "Home"
|
|
||||||
#~ msgstr "Bosh sahifa"
|
|
||||||
|
|
||||||
#~ msgid "Homes"
|
|
||||||
#~ msgstr "Bosh sahifa"
|
|
||||||
2
order/resources/logs/.gitignore
vendored
2
order/resources/logs/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
*
|
|
||||||
!.gitignore
|
|
||||||
2
order/resources/media/.gitignore
vendored
2
order/resources/media/.gitignore
vendored
@@ -1,2 +0,0 @@
|
|||||||
*
|
|
||||||
!.gitignore
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
file=/tmp/db-$(/usr/bin/date +\%Y-%m-%d-%H:%M:%S).sql
|
|
||||||
container=postgres
|
|
||||||
/usr/bin/docker container exec $container pg_dump -U postgres django > $file
|
|
||||||
mc cp $file b2/buket-name
|
|
||||||
rm $file
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
python3 manage.py collectstatic --noinput
|
|
||||||
python3 manage.py migrate --noinput
|
|
||||||
|
|
||||||
gunicorn config.wsgi:application -b 0.0.0.0:8000 --workers $(($(nproc) * 2 + 1))
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
exit $?
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
python3 manage.py collectstatic --noinput
|
|
||||||
python3 manage.py migrate --noinput
|
|
||||||
|
|
||||||
uvicorn config.asgi:application --host 0.0.0.0 --port 8000 --reload --reload-dir core --reload-dir config
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
exit $?
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,109 +0,0 @@
|
|||||||
* {
|
|
||||||
-webkit-box-sizing: border-box;
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
#notfound {
|
|
||||||
position: relative;
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
#notfound .notfound {
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
-webkit-transform: translate(-50%, -50%);
|
|
||||||
-ms-transform: translate(-50%, -50%);
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notfound {
|
|
||||||
max-width: 710px;
|
|
||||||
width: 100%;
|
|
||||||
padding-left: 190px;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notfound .notfound-404 {
|
|
||||||
position: absolute;
|
|
||||||
left: 0;
|
|
||||||
top: 0;
|
|
||||||
width: 150px;
|
|
||||||
height: 150px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notfound .notfound-404 h1 {
|
|
||||||
font-family: 'Passion One', cursive;
|
|
||||||
color: #00b5c3;
|
|
||||||
font-size: 150px;
|
|
||||||
letter-spacing: 15.5px;
|
|
||||||
margin: 0px;
|
|
||||||
font-weight: 900;
|
|
||||||
position: absolute;
|
|
||||||
left: 50%;
|
|
||||||
top: 50%;
|
|
||||||
-webkit-transform: translate(-50%, -50%);
|
|
||||||
-ms-transform: translate(-50%, -50%);
|
|
||||||
transform: translate(-50%, -50%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.notfound h2 {
|
|
||||||
font-family: 'Raleway', sans-serif;
|
|
||||||
color: #292929;
|
|
||||||
font-size: 28px;
|
|
||||||
font-weight: 700;
|
|
||||||
text-transform: uppercase;
|
|
||||||
letter-spacing: 2.5px;
|
|
||||||
margin-top: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notfound p {
|
|
||||||
font-family: 'Raleway', sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
font-weight: 400;
|
|
||||||
margin-top: 0;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notfound a {
|
|
||||||
font-family: 'Raleway', sans-serif;
|
|
||||||
font-size: 14px;
|
|
||||||
text-decoration: none;
|
|
||||||
text-transform: uppercase;
|
|
||||||
background: #fff;
|
|
||||||
display: inline-block;
|
|
||||||
padding: 15px 30px;
|
|
||||||
border-radius: 40px;
|
|
||||||
color: #292929;
|
|
||||||
font-weight: 700;
|
|
||||||
-webkit-box-shadow: 0px 4px 15px -5px rgba(0, 0, 0, 0.3);
|
|
||||||
box-shadow: 0px 4px 15px -5px rgba(0, 0, 0, 0.3);
|
|
||||||
-webkit-transition: 0.2s all;
|
|
||||||
transition: 0.2s all;
|
|
||||||
}
|
|
||||||
|
|
||||||
.notfound a:hover {
|
|
||||||
color: #fff;
|
|
||||||
background-color: #00b5c3;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media only screen and (max-width: 480px) {
|
|
||||||
.notfound {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
.notfound .notfound-404 {
|
|
||||||
position: relative;
|
|
||||||
width: 100%;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
.notfound {
|
|
||||||
padding-left: 15px;
|
|
||||||
padding-right: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
@tailwind base;
|
|
||||||
@tailwind components;
|
|
||||||
@tailwind utilities;
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
.login-logo img {
|
|
||||||
border-radius: 100%;
|
|
||||||
width: 100px;
|
|
||||||
height: 100px;
|
|
||||||
}
|
|
||||||
@@ -1,772 +0,0 @@
|
|||||||
*, ::before, ::after {
|
|
||||||
--tw-border-spacing-x: 0;
|
|
||||||
--tw-border-spacing-y: 0;
|
|
||||||
--tw-translate-x: 0;
|
|
||||||
--tw-translate-y: 0;
|
|
||||||
--tw-rotate: 0;
|
|
||||||
--tw-skew-x: 0;
|
|
||||||
--tw-skew-y: 0;
|
|
||||||
--tw-scale-x: 1;
|
|
||||||
--tw-scale-y: 1;
|
|
||||||
--tw-pan-x: ;
|
|
||||||
--tw-pan-y: ;
|
|
||||||
--tw-pinch-zoom: ;
|
|
||||||
--tw-scroll-snap-strictness: proximity;
|
|
||||||
--tw-gradient-from-position: ;
|
|
||||||
--tw-gradient-via-position: ;
|
|
||||||
--tw-gradient-to-position: ;
|
|
||||||
--tw-ordinal: ;
|
|
||||||
--tw-slashed-zero: ;
|
|
||||||
--tw-numeric-figure: ;
|
|
||||||
--tw-numeric-spacing: ;
|
|
||||||
--tw-numeric-fraction: ;
|
|
||||||
--tw-ring-inset: ;
|
|
||||||
--tw-ring-offset-width: 0px;
|
|
||||||
--tw-ring-offset-color: #fff;
|
|
||||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
|
||||||
--tw-ring-offset-shadow: 0 0 #0000;
|
|
||||||
--tw-ring-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow-colored: 0 0 #0000;
|
|
||||||
--tw-blur: ;
|
|
||||||
--tw-brightness: ;
|
|
||||||
--tw-contrast: ;
|
|
||||||
--tw-grayscale: ;
|
|
||||||
--tw-hue-rotate: ;
|
|
||||||
--tw-invert: ;
|
|
||||||
--tw-saturate: ;
|
|
||||||
--tw-sepia: ;
|
|
||||||
--tw-drop-shadow: ;
|
|
||||||
--tw-backdrop-blur: ;
|
|
||||||
--tw-backdrop-brightness: ;
|
|
||||||
--tw-backdrop-contrast: ;
|
|
||||||
--tw-backdrop-grayscale: ;
|
|
||||||
--tw-backdrop-hue-rotate: ;
|
|
||||||
--tw-backdrop-invert: ;
|
|
||||||
--tw-backdrop-opacity: ;
|
|
||||||
--tw-backdrop-saturate: ;
|
|
||||||
--tw-backdrop-sepia: ;
|
|
||||||
--tw-contain-size: ;
|
|
||||||
--tw-contain-layout: ;
|
|
||||||
--tw-contain-paint: ;
|
|
||||||
--tw-contain-style: ;
|
|
||||||
}
|
|
||||||
|
|
||||||
::backdrop {
|
|
||||||
--tw-border-spacing-x: 0;
|
|
||||||
--tw-border-spacing-y: 0;
|
|
||||||
--tw-translate-x: 0;
|
|
||||||
--tw-translate-y: 0;
|
|
||||||
--tw-rotate: 0;
|
|
||||||
--tw-skew-x: 0;
|
|
||||||
--tw-skew-y: 0;
|
|
||||||
--tw-scale-x: 1;
|
|
||||||
--tw-scale-y: 1;
|
|
||||||
--tw-pan-x: ;
|
|
||||||
--tw-pan-y: ;
|
|
||||||
--tw-pinch-zoom: ;
|
|
||||||
--tw-scroll-snap-strictness: proximity;
|
|
||||||
--tw-gradient-from-position: ;
|
|
||||||
--tw-gradient-via-position: ;
|
|
||||||
--tw-gradient-to-position: ;
|
|
||||||
--tw-ordinal: ;
|
|
||||||
--tw-slashed-zero: ;
|
|
||||||
--tw-numeric-figure: ;
|
|
||||||
--tw-numeric-spacing: ;
|
|
||||||
--tw-numeric-fraction: ;
|
|
||||||
--tw-ring-inset: ;
|
|
||||||
--tw-ring-offset-width: 0px;
|
|
||||||
--tw-ring-offset-color: #fff;
|
|
||||||
--tw-ring-color: rgb(59 130 246 / 0.5);
|
|
||||||
--tw-ring-offset-shadow: 0 0 #0000;
|
|
||||||
--tw-ring-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow: 0 0 #0000;
|
|
||||||
--tw-shadow-colored: 0 0 #0000;
|
|
||||||
--tw-blur: ;
|
|
||||||
--tw-brightness: ;
|
|
||||||
--tw-contrast: ;
|
|
||||||
--tw-grayscale: ;
|
|
||||||
--tw-hue-rotate: ;
|
|
||||||
--tw-invert: ;
|
|
||||||
--tw-saturate: ;
|
|
||||||
--tw-sepia: ;
|
|
||||||
--tw-drop-shadow: ;
|
|
||||||
--tw-backdrop-blur: ;
|
|
||||||
--tw-backdrop-brightness: ;
|
|
||||||
--tw-backdrop-contrast: ;
|
|
||||||
--tw-backdrop-grayscale: ;
|
|
||||||
--tw-backdrop-hue-rotate: ;
|
|
||||||
--tw-backdrop-invert: ;
|
|
||||||
--tw-backdrop-opacity: ;
|
|
||||||
--tw-backdrop-saturate: ;
|
|
||||||
--tw-backdrop-sepia: ;
|
|
||||||
--tw-contain-size: ;
|
|
||||||
--tw-contain-layout: ;
|
|
||||||
--tw-contain-paint: ;
|
|
||||||
--tw-contain-style: ;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
! tailwindcss v3.4.14 | MIT License | https://tailwindcss.com
|
|
||||||
*/
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Prevent padding and border from affecting element width. (https://github.com/mozdevs/cssremedy/issues/4)
|
|
||||||
2. Allow adding a border to an element by just adding a border-width. (https://github.com/tailwindcss/tailwindcss/pull/116)
|
|
||||||
*/
|
|
||||||
|
|
||||||
*,
|
|
||||||
::before,
|
|
||||||
::after {
|
|
||||||
box-sizing: border-box;
|
|
||||||
/* 1 */
|
|
||||||
border-width: 0;
|
|
||||||
/* 2 */
|
|
||||||
border-style: solid;
|
|
||||||
/* 2 */
|
|
||||||
border-color: #e5e7eb;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
::before,
|
|
||||||
::after {
|
|
||||||
--tw-content: '';
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Use a consistent sensible line-height in all browsers.
|
|
||||||
2. Prevent adjustments of font size after orientation changes in iOS.
|
|
||||||
3. Use a more readable tab size.
|
|
||||||
4. Use the user's configured `sans` font-family by default.
|
|
||||||
5. Use the user's configured `sans` font-feature-settings by default.
|
|
||||||
6. Use the user's configured `sans` font-variation-settings by default.
|
|
||||||
7. Disable tap highlights on iOS
|
|
||||||
*/
|
|
||||||
|
|
||||||
html,
|
|
||||||
:host {
|
|
||||||
line-height: 1.5;
|
|
||||||
/* 1 */
|
|
||||||
-webkit-text-size-adjust: 100%;
|
|
||||||
/* 2 */
|
|
||||||
-moz-tab-size: 4;
|
|
||||||
/* 3 */
|
|
||||||
-o-tab-size: 4;
|
|
||||||
tab-size: 4;
|
|
||||||
/* 3 */
|
|
||||||
font-family: ui-sans-serif, system-ui, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
|
|
||||||
/* 4 */
|
|
||||||
font-feature-settings: normal;
|
|
||||||
/* 5 */
|
|
||||||
font-variation-settings: normal;
|
|
||||||
/* 6 */
|
|
||||||
-webkit-tap-highlight-color: transparent;
|
|
||||||
/* 7 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Remove the margin in all browsers.
|
|
||||||
2. Inherit line-height from `html` so users can set them as a class directly on the `html` element.
|
|
||||||
*/
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
/* 1 */
|
|
||||||
line-height: inherit;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Add the correct height in Firefox.
|
|
||||||
2. Correct the inheritance of border color in Firefox. (https://bugzilla.mozilla.org/show_bug.cgi?id=190655)
|
|
||||||
3. Ensure horizontal rules are visible by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
hr {
|
|
||||||
height: 0;
|
|
||||||
/* 1 */
|
|
||||||
color: inherit;
|
|
||||||
/* 2 */
|
|
||||||
border-top-width: 1px;
|
|
||||||
/* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct text decoration in Chrome, Edge, and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
abbr:where([title]) {
|
|
||||||
-webkit-text-decoration: underline dotted;
|
|
||||||
text-decoration: underline dotted;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the default font size and weight for headings.
|
|
||||||
*/
|
|
||||||
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6 {
|
|
||||||
font-size: inherit;
|
|
||||||
font-weight: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Reset links to optimize for opt-in styling instead of opt-out.
|
|
||||||
*/
|
|
||||||
|
|
||||||
a {
|
|
||||||
color: inherit;
|
|
||||||
text-decoration: inherit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct font weight in Edge and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
b,
|
|
||||||
strong {
|
|
||||||
font-weight: bolder;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Use the user's configured `mono` font-family by default.
|
|
||||||
2. Use the user's configured `mono` font-feature-settings by default.
|
|
||||||
3. Use the user's configured `mono` font-variation-settings by default.
|
|
||||||
4. Correct the odd `em` font sizing in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
code,
|
|
||||||
kbd,
|
|
||||||
samp,
|
|
||||||
pre {
|
|
||||||
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
|
|
||||||
/* 1 */
|
|
||||||
font-feature-settings: normal;
|
|
||||||
/* 2 */
|
|
||||||
font-variation-settings: normal;
|
|
||||||
/* 3 */
|
|
||||||
font-size: 1em;
|
|
||||||
/* 4 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct font size in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
small {
|
|
||||||
font-size: 80%;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prevent `sub` and `sup` elements from affecting the line height in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
sub,
|
|
||||||
sup {
|
|
||||||
font-size: 75%;
|
|
||||||
line-height: 0;
|
|
||||||
position: relative;
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
sub {
|
|
||||||
bottom: -0.25em;
|
|
||||||
}
|
|
||||||
|
|
||||||
sup {
|
|
||||||
top: -0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Remove text indentation from table contents in Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=999088, https://bugs.webkit.org/show_bug.cgi?id=201297)
|
|
||||||
2. Correct table border color inheritance in all Chrome and Safari. (https://bugs.chromium.org/p/chromium/issues/detail?id=935729, https://bugs.webkit.org/show_bug.cgi?id=195016)
|
|
||||||
3. Remove gaps between table borders by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
table {
|
|
||||||
text-indent: 0;
|
|
||||||
/* 1 */
|
|
||||||
border-color: inherit;
|
|
||||||
/* 2 */
|
|
||||||
border-collapse: collapse;
|
|
||||||
/* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Change the font styles in all browsers.
|
|
||||||
2. Remove the margin in Firefox and Safari.
|
|
||||||
3. Remove default padding in all browsers.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
input,
|
|
||||||
optgroup,
|
|
||||||
select,
|
|
||||||
textarea {
|
|
||||||
font-family: inherit;
|
|
||||||
/* 1 */
|
|
||||||
font-feature-settings: inherit;
|
|
||||||
/* 1 */
|
|
||||||
font-variation-settings: inherit;
|
|
||||||
/* 1 */
|
|
||||||
font-size: 100%;
|
|
||||||
/* 1 */
|
|
||||||
font-weight: inherit;
|
|
||||||
/* 1 */
|
|
||||||
line-height: inherit;
|
|
||||||
/* 1 */
|
|
||||||
letter-spacing: inherit;
|
|
||||||
/* 1 */
|
|
||||||
color: inherit;
|
|
||||||
/* 1 */
|
|
||||||
margin: 0;
|
|
||||||
/* 2 */
|
|
||||||
padding: 0;
|
|
||||||
/* 3 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the inheritance of text transform in Edge and Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
select {
|
|
||||||
text-transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the inability to style clickable types in iOS and Safari.
|
|
||||||
2. Remove default button styles.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
input:where([type='button']),
|
|
||||||
input:where([type='reset']),
|
|
||||||
input:where([type='submit']) {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
/* 1 */
|
|
||||||
background-color: transparent;
|
|
||||||
/* 2 */
|
|
||||||
background-image: none;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Use the modern Firefox focus style for all focusable elements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
:-moz-focusring {
|
|
||||||
outline: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the additional `:invalid` styles in Firefox. (https://github.com/mozilla/gecko-dev/blob/2f9eacd9d3d995c937b4251a5557d95d494c9be1/layout/style/res/forms.css#L728-L737)
|
|
||||||
*/
|
|
||||||
|
|
||||||
:-moz-ui-invalid {
|
|
||||||
box-shadow: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct vertical alignment in Chrome and Firefox.
|
|
||||||
*/
|
|
||||||
|
|
||||||
progress {
|
|
||||||
vertical-align: baseline;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Correct the cursor style of increment and decrement buttons in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-inner-spin-button,
|
|
||||||
::-webkit-outer-spin-button {
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the odd appearance in Chrome and Safari.
|
|
||||||
2. Correct the outline style in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
[type='search'] {
|
|
||||||
-webkit-appearance: textfield;
|
|
||||||
/* 1 */
|
|
||||||
outline-offset: -2px;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Remove the inner padding in Chrome and Safari on macOS.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-search-decoration {
|
|
||||||
-webkit-appearance: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Correct the inability to style clickable types in iOS and Safari.
|
|
||||||
2. Change font properties to `inherit` in Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
::-webkit-file-upload-button {
|
|
||||||
-webkit-appearance: button;
|
|
||||||
/* 1 */
|
|
||||||
font: inherit;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Add the correct display in Chrome and Safari.
|
|
||||||
*/
|
|
||||||
|
|
||||||
summary {
|
|
||||||
display: list-item;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Removes the default spacing and border for appropriate elements.
|
|
||||||
*/
|
|
||||||
|
|
||||||
blockquote,
|
|
||||||
dl,
|
|
||||||
dd,
|
|
||||||
h1,
|
|
||||||
h2,
|
|
||||||
h3,
|
|
||||||
h4,
|
|
||||||
h5,
|
|
||||||
h6,
|
|
||||||
hr,
|
|
||||||
figure,
|
|
||||||
p,
|
|
||||||
pre {
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
fieldset {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
legend {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
ol,
|
|
||||||
ul,
|
|
||||||
menu {
|
|
||||||
list-style: none;
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Reset default styling for dialogs.
|
|
||||||
*/
|
|
||||||
|
|
||||||
dialog {
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Prevent resizing textareas horizontally by default.
|
|
||||||
*/
|
|
||||||
|
|
||||||
textarea {
|
|
||||||
resize: vertical;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Reset the default placeholder opacity in Firefox. (https://github.com/tailwindlabs/tailwindcss/issues/3300)
|
|
||||||
2. Set the default placeholder color to the user's configured gray 400 color.
|
|
||||||
*/
|
|
||||||
|
|
||||||
input::-moz-placeholder, textarea::-moz-placeholder {
|
|
||||||
opacity: 1;
|
|
||||||
/* 1 */
|
|
||||||
color: #9ca3af;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
input::placeholder,
|
|
||||||
textarea::placeholder {
|
|
||||||
opacity: 1;
|
|
||||||
/* 1 */
|
|
||||||
color: #9ca3af;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Set the default cursor for buttons.
|
|
||||||
*/
|
|
||||||
|
|
||||||
button,
|
|
||||||
[role="button"] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Make sure disabled buttons don't get the pointer cursor.
|
|
||||||
*/
|
|
||||||
|
|
||||||
:disabled {
|
|
||||||
cursor: default;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
1. Make replaced elements `display: block` by default. (https://github.com/mozdevs/cssremedy/issues/14)
|
|
||||||
2. Add `vertical-align: middle` to align replaced elements more sensibly by default. (https://github.com/jensimmons/cssremedy/issues/14#issuecomment-634934210)
|
|
||||||
This can trigger a poorly considered lint error in some tools but is included by design.
|
|
||||||
*/
|
|
||||||
|
|
||||||
img,
|
|
||||||
svg,
|
|
||||||
video,
|
|
||||||
canvas,
|
|
||||||
audio,
|
|
||||||
iframe,
|
|
||||||
embed,
|
|
||||||
object {
|
|
||||||
display: block;
|
|
||||||
/* 1 */
|
|
||||||
vertical-align: middle;
|
|
||||||
/* 2 */
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
Constrain images and videos to the parent width and preserve their intrinsic aspect ratio. (https://github.com/mozdevs/cssremedy/issues/14)
|
|
||||||
*/
|
|
||||||
|
|
||||||
img,
|
|
||||||
video {
|
|
||||||
max-width: 100%;
|
|
||||||
height: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Make elements with the HTML hidden attribute stay hidden by default */
|
|
||||||
|
|
||||||
[hidden]:where(:not([hidden="until-found"])) {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.static {
|
|
||||||
position: static;
|
|
||||||
}
|
|
||||||
|
|
||||||
.m-2 {
|
|
||||||
margin: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mx-4 {
|
|
||||||
margin-left: 1rem;
|
|
||||||
margin-right: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb-3 {
|
|
||||||
margin-bottom: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mb-5 {
|
|
||||||
margin-bottom: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.ml-1 {
|
|
||||||
margin-left: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-3 {
|
|
||||||
margin-top: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-4 {
|
|
||||||
margin-top: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.mt-5 {
|
|
||||||
margin-top: 1.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.block {
|
|
||||||
display: block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inline-block {
|
|
||||||
display: inline-block;
|
|
||||||
}
|
|
||||||
|
|
||||||
.flex {
|
|
||||||
display: flex;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid {
|
|
||||||
display: grid;
|
|
||||||
}
|
|
||||||
|
|
||||||
.h-\[100vh\] {
|
|
||||||
height: 100vh;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-\[100vw\] {
|
|
||||||
width: 100vw;
|
|
||||||
}
|
|
||||||
|
|
||||||
.w-full {
|
|
||||||
width: 100%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.cursor-pointer {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.grid-cols-1 {
|
|
||||||
grid-template-columns: repeat(1, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
|
|
||||||
.items-center {
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.justify-end {
|
|
||||||
justify-content: flex-end;
|
|
||||||
}
|
|
||||||
|
|
||||||
.justify-center {
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gap-8 {
|
|
||||||
gap: 2rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gap-x-3 {
|
|
||||||
-moz-column-gap: 0.75rem;
|
|
||||||
column-gap: 0.75rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.whitespace-nowrap {
|
|
||||||
white-space: nowrap;
|
|
||||||
}
|
|
||||||
|
|
||||||
.rounded {
|
|
||||||
border-radius: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.border {
|
|
||||||
border-width: 1px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.border-black {
|
|
||||||
--tw-border-opacity: 1;
|
|
||||||
border-color: rgb(0 0 0 / var(--tw-border-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-blue-100 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(219 234 254 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.bg-gray-200 {
|
|
||||||
--tw-bg-opacity: 1;
|
|
||||||
background-color: rgb(229 231 235 / var(--tw-bg-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.p-4 {
|
|
||||||
padding: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.px-2 {
|
|
||||||
padding-left: 0.5rem;
|
|
||||||
padding-right: 0.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.px-\[40px\] {
|
|
||||||
padding-left: 40px;
|
|
||||||
padding-right: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.py-1 {
|
|
||||||
padding-top: 0.25rem;
|
|
||||||
padding-bottom: 0.25rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-center {
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-\[20px\] {
|
|
||||||
font-size: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-\[25px\] {
|
|
||||||
font-size: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-\[30px\] {
|
|
||||||
font-size: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-\[40px\] {
|
|
||||||
font-size: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-\[400\] {
|
|
||||||
font-weight: 400;
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-\[600\] {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-bold {
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.font-semibold {
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.uppercase {
|
|
||||||
text-transform: uppercase;
|
|
||||||
}
|
|
||||||
|
|
||||||
.leading-normal {
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-black {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(0 0 0 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-blue-500 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(59 130 246 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
.text-red-500 {
|
|
||||||
--tw-text-opacity: 1;
|
|
||||||
color: rgb(239 68 68 / var(--tw-text-opacity));
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 640px) {
|
|
||||||
.sm\:grid-cols-2 {
|
|
||||||
grid-template-columns: repeat(2, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 768px) {
|
|
||||||
.md\:grid-cols-3 {
|
|
||||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (min-width: 1024px) {
|
|
||||||
.lg\:grid-cols-4 {
|
|
||||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 38 KiB |
@@ -1,9 +0,0 @@
|
|||||||
import Alpine from 'alpinejs'
|
|
||||||
import counter from "./counter";
|
|
||||||
|
|
||||||
window.Alpine = Alpine
|
|
||||||
|
|
||||||
Alpine.data("vars", counter)
|
|
||||||
|
|
||||||
Alpine.start()
|
|
||||||
|
|
||||||
@@ -1 +0,0 @@
|
|||||||
import "./alpine"
|
|
||||||
@@ -1,3 +0,0 @@
|
|||||||
export default () => ({
|
|
||||||
count: 2309
|
|
||||||
})
|
|
||||||
@@ -1,49 +0,0 @@
|
|||||||
|
|
||||||
class Changer {
|
|
||||||
constructor() {
|
|
||||||
this.inputs = [];
|
|
||||||
|
|
||||||
this.legal = [
|
|
||||||
"bank_mfo",
|
|
||||||
"bank_name",
|
|
||||||
"bank_account",
|
|
||||||
"name",
|
|
||||||
"director_name",
|
|
||||||
"responsible_person",
|
|
||||||
"inn"
|
|
||||||
];
|
|
||||||
|
|
||||||
this.physical = [
|
|
||||||
"passport_series",
|
|
||||||
"jshir",
|
|
||||||
"first_name",
|
|
||||||
"last_name",
|
|
||||||
]
|
|
||||||
this.legal.concat(this.physical).forEach((item) => {
|
|
||||||
this.inputs[item] = document.querySelector(`#id_${item}`).closest(".form-row");
|
|
||||||
})
|
|
||||||
}
|
|
||||||
toggleDisplay(showItems, hideItems) {
|
|
||||||
showItems.forEach(item => {
|
|
||||||
this.inputs[item].style.display = "block";
|
|
||||||
});
|
|
||||||
hideItems.forEach(item => {
|
|
||||||
this.inputs[item].style.display = "none";
|
|
||||||
});
|
|
||||||
};
|
|
||||||
|
|
||||||
change(e) {
|
|
||||||
if (e == "PHYSICAL") {
|
|
||||||
this.toggleDisplay(this.physical, this.legal);
|
|
||||||
} else if (e == "LEGAL") {
|
|
||||||
this.toggleDisplay(this.legal, this.physical);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
document.addEventListener("DOMContentLoaded", () => {
|
|
||||||
let obj = new Changer();
|
|
||||||
let select = document.querySelector("#id_person_type");
|
|
||||||
select.addEventListener("change", (e) => obj.change(e.target.value));
|
|
||||||
obj.change(select.value);
|
|
||||||
})
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
import RefreshRuntime from 'http://localhost:5173/@react-refresh'
|
|
||||||
|
|
||||||
if (RefreshRuntime) {
|
|
||||||
RefreshRuntime.injectIntoGlobalHook(window)
|
|
||||||
window.$RefreshReg$ = () => {
|
|
||||||
}
|
|
||||||
window.$RefreshSig$ = () => (type) => type
|
|
||||||
window.__vite_plugin_react_preamble_installed__ = true
|
|
||||||
}
|
|
||||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user