Merge commit '164340f86dabb71bef83bfa774e0b45702be270a' as 'notification'

This commit is contained in:
A'zamov Samandar
2025-04-21 20:51:30 +05:00
408 changed files with 615 additions and 41799 deletions

View File

@@ -1,2 +1,15 @@
USER_COMMAND=sh ./resources/scripts/entrypoint.sh
JWT_KEY=key
RABBITMQ_URL=amqp://guest:guest@127.0.0.1:5672/
REDIS_ADDRESS=127.0.0.1:6379
REDIS_PASSWORD=
REDIS_DB=0
ESKIZ_DOMAIN="https://notify.eskiz.uz/api"
ESKIZ_USER="admin@gmail.com"
ESKIZ_PASSWORD="password"
ESKIZ_FROM="4546"
PMB_DOMAIN=""
PMB_USER=""
PMB_PASSWORD=""

View File

@@ -1,3 +0,0 @@
[flake8]
max-line-length = 120
ignore = E701, E704, W503

View File

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

2
.gitignore vendored
View File

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

20
Dockerfile Normal file
View 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"]

View File

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

@@ -1,2 +1,133 @@
# JST-DJANGO
[Docs](https://docs.jscorp.uz)
# Notification Service
A microservice for handling and delivering notifications through various channels like SMS and email using RabbitMQ as a message broker.
## Overview
This notification service is designed as a standalone microservice that consumes notification requests from a RabbitMQ queue and routes them to the appropriate notification provider based on the notification type. Currently, it supports SMS and email notifications.
## Features
- Message consumption from RabbitMQ
- Support for multiple notification channels (SMS, email)
- Extensible architecture for adding new notification types
- Asynchronous notification handling
## Architecture
The notification service follows a clean architecture approach:
- **Domain Layer**: Contains core business logic and port interfaces
- **Infrastructure Layer**: Implements the ports with concrete adapters
- **RabbitMQ**: Used as a message broker for consuming notification requests
## Installation
### Prerequisites
- Go 1.x+
- RabbitMQ server
### Setup
1. Clone the repository:
```bash
git clone https://github.com/JscorpTech/notification.git
cd notification
```
2. Install dependencies:
```bash
go mod download
```
3. Build the application:
```bash
go build -o notification-service
```
## Configuration
Configure your RabbitMQ connection and other settings in the appropriate configuration files.
## Usage
### Running the service
```bash
./notification-service
```
This will start the notification consumer that listens for incoming notification requests.
### Sending a notification
Notifications should be published to the RabbitMQ exchange with the following JSON format:
```json
{
"type": "email",
"message": "Hello, this is a test notification.",
"to": ["user@example.com"]
}
```
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
View 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()
}

View File

@@ -1,44 +1,12 @@
networks:
lamenu:
external: true
name: lamenu
driver: bridge
volumes:
pg_data: null
pycache: null
rabbitmq-data: null
services:
traefik:
image: traefik:v2.10
command:
- "--api.insecure=true"
- "--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"
notification:
build:
context: .
dockerfile: Dockerfile
networks:
- 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
View 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
View 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
View 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
}

View 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 &notificationConsumer{
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, &notification)
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
View File

@@ -0,0 +1,27 @@
package domain
type EskizLogin struct {
Email string `json:"email"`
Password string `json:"password"`
}
type EskizLoginRes struct {
Message string
TokenType string
Data struct {
Token string
}
}
type EskizMessage struct {
Phone string `json:"mobile_phone"`
Message string `json:"message"`
From string `json:"from"`
CallbackURL string `json:"callback_url"`
}
type EskizMessageRes struct {
ID string `json:"id"`
Message string `json:"message"`
Status string `json:"status"`
}

View File

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

View File

@@ -0,0 +1,20 @@
package domain
type PmbContent struct {
Text string `json:"text"`
}
type PmbSMS struct {
Originator string `json:"originator"`
Content PmbContent `json:"content"`
}
type PmbMessage struct {
Recipient string `json:"recipient"`
MessageID string `json:"message-id"`
Sms PmbSMS `json:"sms"`
}
type PmbPayload struct {
Messages []PmbMessage
}

1
internal/models/fcm.go Normal file
View File

@@ -0,0 +1 @@
package models

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

View 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
View File

@@ -0,0 +1,27 @@
package redis
import (
"context"
"log"
"os"
"strconv"
"github.com/redis/go-redis/v9"
)
var RDB *redis.Client
func InitRedis() {
DB, _ := strconv.Atoi(os.Getenv("REDIS_DB"))
RDB = redis.NewClient(&redis.Options{
Addr: os.Getenv("REDIS_ADDRESS"),
Password: os.Getenv("REDIS_PASSWORD"),
DB: DB,
})
// Test connection
_, err := RDB.Ping(context.Background()).Result()
if err != nil {
log.Fatalf("Redisga ulanib bo'lmadi: %v", err)
}
}

View File

@@ -0,0 +1,89 @@
package services
import (
"bytes"
"context"
"encoding/json"
"net/http"
"os"
"time"
"github.com/JscorpTech/notification/internal/domain"
"github.com/JscorpTech/notification/internal/redis"
"github.com/k0kubun/pp/v3"
)
type eskizSMSService struct {
BaseURL string
Ctx context.Context
}
// /broker-api/send
func NewEskizSMSService(ctx context.Context) domain.SMSServicePort {
return &eskizSMSService{
Ctx: ctx,
BaseURL: os.Getenv("ESKIZ_DOMAIN"),
}
}
func (e *eskizSMSService) Request(payload any, path string, isAuth bool, retry bool) (*http.Response, error) {
var buf bytes.Buffer
_ = json.NewEncoder(&buf).Encode(payload)
client := http.Client{
Timeout: 60 * time.Second,
}
req, err := http.NewRequest("POST", e.BaseURL+path, &buf)
req.Header.Add("Content-Type", "application/json")
if isAuth {
req.Header.Add("Authorization", "Bearer "+e.GetToken(true))
}
if err != nil {
return nil, err
}
res, err := client.Do(req)
if res.StatusCode == http.StatusUnauthorized && retry {
pp.Print("Qayta urunish")
e.GetToken(false)
return e.Request(payload, path, isAuth, false)
}
return res, err
}
func (e *eskizSMSService) GetToken(cache bool) string {
token, err := redis.RDB.Get(e.Ctx, "eskiz_token").Result()
if err == nil && cache {
pp.Print("Eskiz token topildi 😁")
return token
}
payload := domain.EskizLogin{
Email: os.Getenv("ESKIZ_USER"),
Password: os.Getenv("ESKIZ_PASSWORD"),
}
res, err := e.Request(payload, "/auth/login", false, true)
if err != nil {
pp.Print(err.Error())
}
var data domain.EskizLoginRes
_ = json.NewDecoder(res.Body).Decode(&data)
token = data.Data.Token
redis.RDB.Set(e.Ctx, "eskiz_token", token, 30*24*time.Hour)
pp.Print("Eskiz yangi token olindi 😔")
return token
}
func (e *eskizSMSService) SendSMS(to, body string) error {
payload := domain.EskizMessage{
Phone: to,
Message: body,
From: os.Getenv("ESKIZ_FROM"),
}
res, err := e.Request(payload, "/message/sms/send", true, true)
if err != nil {
return err
}
var data domain.EskizMessageRes
json.NewDecoder(res.Body).Decode(&data)
pp.Print(data)
return nil
}

View File

@@ -0,0 +1,53 @@
package services
import (
"bytes"
"encoding/json"
"fmt"
"net/http"
"time"
"github.com/JscorpTech/notification/internal/domain"
)
type pmbSMSService struct {
BaseURL string
}
// /broker-api/send
func NewPmbSMSService() domain.SMSServicePort {
return &pmbSMSService{
BaseURL: "https://send.smsxabar.uz",
}
}
func (e *pmbSMSService) SendSMS(to, body string) error {
client := http.Client{
Timeout: 60 * time.Second,
}
payload := domain.PmbPayload{
Messages: []domain.PmbMessage{
{
Recipient: "+998888112309",
MessageID: "salomsdfs",
Sms: domain.PmbSMS{
Originator: "3600",
Content: domain.PmbContent{
Text: "salom",
},
},
},
},
}
var buf bytes.Buffer
_ = json.NewEncoder(&buf).Encode(payload)
req, _ := http.NewRequest("POST", e.BaseURL+"/broker-api/send", &buf)
res, err := client.Do(req)
if err != nil {
return err
}
var data map[string]interface{}
json.NewDecoder(res.Body).Decode(&data)
fmt.Print(data)
return nil
}

View File

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

View File

@@ -1,2 +0,0 @@
venv/
resources/staticfiles/

View File

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

View File

@@ -1,3 +0,0 @@
[flake8]
max-line-length = 120
ignore = E701, E704, W503

158
order/.gitignore vendored
View File

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

View File

@@ -1,2 +0,0 @@
# JST-DJANGO
[Docs](https://docs.jscorp.uz)

View File

@@ -1,3 +0,0 @@
from .celery import app
__all__ = ["app"]

View File

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

View File

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

View File

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

View File

@@ -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",
]

View File

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

View File

@@ -1,7 +0,0 @@
CELERY_BEAT_SCHEDULE = {
# "test": {
# "task": "core.apps.home.tasks.demo.add",
# "schedule": 5.0,
# "args": (1, 2)
# },
}

View File

@@ -1,8 +0,0 @@
CHANNEL_LAYERS = {
"default": {
"BACKEND": "channels_redis.core.RedisChannelLayer",
"CONFIG": {
"hosts": [("redis", 6379)],
},
},
}

View File

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

View File

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

View File

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

View File

@@ -1 +0,0 @@
MODULES = []

View File

@@ -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"),
},
],
},
]

View File

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

View File

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

View File

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

View File

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

View File

@@ -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"),
)

View File

@@ -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",
}
]

View File

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

View File

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

View File

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

View File

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

View File

View File

@@ -1,3 +0,0 @@
from .cache import * # noqa
from .console import * # noqa
from .core import * # noqa

View File

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

View File

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

View File

@@ -1,6 +0,0 @@
class Helper:
"""
Helper class to handle index
"""
pass

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,3 +0,0 @@
FROM nginx:alpine
COPY ./resources/layout/nginx.conf /etc/nginx/nginx.conf

View File

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

View File

@@ -1,8 +0,0 @@
{
"dirs": {
"apps": "./core/apps/",
"locale": "./resources/locale/"
},
"stubs": {},
"apps": "core.apps."
}

View File

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

View File

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

View File

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

View File

@@ -1 +0,0 @@
staticfiles/

View File

@@ -1,3 +0,0 @@
[flake8]
max-line-length = 120
ignore = E701, E704, W503

View File

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

View File

@@ -1,3 +0,0 @@
FROM nginx:alpine
COPY ./resources/layout/nginx.conf /etc/nginx/nginx.conf

View File

@@ -1,5 +0,0 @@
[mypy]
check_untyped_defs = True
[mypy-requests.*]
ignore_missing_imports = True

View File

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

View File

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

View File

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

View File

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

View File

@@ -1,2 +0,0 @@
*
!.gitignore

View File

@@ -1,2 +0,0 @@
*
!.gitignore

View File

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

View 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 $?

View File

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

View File

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

View File

@@ -1,3 +0,0 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

View File

@@ -1,5 +0,0 @@
.login-logo img {
border-radius: 100%;
width: 100px;
height: 100px;
}

View File

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

View File

@@ -1,9 +0,0 @@
import Alpine from 'alpinejs'
import counter from "./counter";
window.Alpine = Alpine
Alpine.data("vars", counter)
Alpine.start()

View File

@@ -1 +0,0 @@
import "./alpine"

View File

@@ -1,3 +0,0 @@
export default () => ({
count: 2309
})

View File

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

View File

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