diff --git a/Makefile b/Makefile index d71cf8d..20782c0 100644 --- a/Makefile +++ b/Makefile @@ -1,10 +1,25 @@ -up-user: - docker compose --profile user up -d -down-user: - docker compose --profile user down -up-payment: - docker compose --profile payment up -d -down-payment: - docker compose --profile payment down +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 --profile payment --profile user --profile notification down \ No newline at end of file + docker compose -$(NAME) $(SERVICES) down \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index fe7691e..2076cc1 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,5 +1,6 @@ networks: lamenu: + name: lamenu driver: bridge volumes: @@ -22,88 +23,6 @@ services: - "/var/run/docker.sock:/var/run/docker.sock:ro" networks: - lamenu - user-nginx: - labels: - - "traefik.enable=true" - - "traefik.http.routers.user.rule=PathPrefix(`/user`)" - - "traefik.http.routers.user.entrypoints=web" - - "traefik.http.services.user.loadbalancer.server.port=80" - - "traefik.http.middlewares.user-strip-prefix.stripPrefix.prefixes=/user" - - "traefik.http.routers.user.middlewares=user-strip-prefix" - networks: - - lamenu - volumes: - - ./user/resources/layout/nginx.conf:/etc/nginx/nginx.conf - - ./user/resources/:/usr/share/nginx/html/resources/ - build: - context: ./user - dockerfile: ./docker/Dockerfile.nginx - depends_on: - - user - profiles: - - user - user: - networks: - - lamenu - build: - context: ./user - dockerfile: ./docker/Dockerfile.web - restart: always - command: ${user_COMMAND:-sh ./resources/scripts/entrypoint.sh} - environment: - - PYTHONPYCACHEPREFIX=/var/cache/pycache - - JWT_KEY=${JWT_KEY} - volumes: - - ./user:/code - - pycache:/var/cache/pycache - depends_on: - - user-db - - redis - profiles: - - user - user-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 - profiles: - - user - - payment: - labels: - - "traefik.enable=true" - - "traefik.http.routers.payment.rule=PathPrefix(`/payment`)" - - "traefik.http.routers.payment.entrypoints=web" - - "traefik.http.services.payment.loadbalancer.server.port=8000" - networks: - - lamenu - build: - context: ./payment - dockerfile: ./Dockerfile - restart: always - environment: - - JWT_KEY=${JWT_KEY} - volumes: - - ./payment:/code - - pycache:/var/cache/pycache - profiles: - - payment - - notification: - build: - context: ./notification - dockerfile: Dockerfile - networks: - - lamenu - profiles: - - notification - redis: networks: - lamenu @@ -111,7 +30,6 @@ services: image: redis ports: - 6379:6379 - rabbitmq: image: rabbitmq:management networks: diff --git a/notification/docker-compose.yml b/notification/docker-compose.yml new file mode 100644 index 0000000..4ca2736 --- /dev/null +++ b/notification/docker-compose.yml @@ -0,0 +1,12 @@ +networks: + lamenu: + external: true + name: lamenu + +services: + notification: + build: + context: . + dockerfile: Dockerfile + networks: + - lamenu diff --git a/order/.cruft.json b/order/.cruft.json new file mode 100644 index 0000000..a52d028 --- /dev/null +++ b/order/.cruft.json @@ -0,0 +1,28 @@ +{ + "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 +} \ No newline at end of file diff --git a/order/.dockerignore b/order/.dockerignore new file mode 100644 index 0000000..fd148e5 --- /dev/null +++ b/order/.dockerignore @@ -0,0 +1,2 @@ +venv/ +resources/staticfiles/ \ No newline at end of file diff --git a/order/.env.example b/order/.env.example new file mode 100644 index 0000000..753f720 --- /dev/null +++ b/order/.env.example @@ -0,0 +1,63 @@ +# 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: diff --git a/order/.flake8 b/order/.flake8 new file mode 100644 index 0000000..95f57f4 --- /dev/null +++ b/order/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 120 +ignore = E701, E704, W503 diff --git a/order/.gitignore b/order/.gitignore new file mode 100644 index 0000000..e46f1af --- /dev/null +++ b/order/.gitignore @@ -0,0 +1,158 @@ +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 diff --git a/order/README.MD b/order/README.MD new file mode 100644 index 0000000..153f280 --- /dev/null +++ b/order/README.MD @@ -0,0 +1,2 @@ +# JST-DJANGO +[Docs](https://docs.jscorp.uz) diff --git a/order/config/__init__.py b/order/config/__init__.py new file mode 100644 index 0000000..801fff4 --- /dev/null +++ b/order/config/__init__.py @@ -0,0 +1,3 @@ +from .celery import app + +__all__ = ["app"] diff --git a/order/config/asgi.py b/order/config/asgi.py new file mode 100644 index 0000000..f715434 --- /dev/null +++ b/order/config/asgi.py @@ -0,0 +1,23 @@ +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)), + } +) + diff --git a/order/config/celery.py b/order/config/celery.py new file mode 100644 index 0000000..1578806 --- /dev/null +++ b/order/config/celery.py @@ -0,0 +1,16 @@ +""" +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() diff --git a/order/config/conf/__init__.py b/order/config/conf/__init__.py new file mode 100644 index 0000000..d8f1296 --- /dev/null +++ b/order/config/conf/__init__.py @@ -0,0 +1,12 @@ +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 \ No newline at end of file diff --git a/order/config/conf/apps.py b/order/config/conf/apps.py new file mode 100644 index 0000000..a34ee29 --- /dev/null +++ b/order/config/conf/apps.py @@ -0,0 +1,19 @@ +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", + ] diff --git a/order/config/conf/cache.py b/order/config/conf/cache.py new file mode 100644 index 0000000..cd724b5 --- /dev/null +++ b/order/config/conf/cache.py @@ -0,0 +1,26 @@ +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) diff --git a/order/config/conf/celery.py b/order/config/conf/celery.py new file mode 100644 index 0000000..5f46855 --- /dev/null +++ b/order/config/conf/celery.py @@ -0,0 +1,7 @@ +CELERY_BEAT_SCHEDULE = { + # "test": { + # "task": "core.apps.home.tasks.demo.add", + # "schedule": 5.0, + # "args": (1, 2) + # }, +} diff --git a/order/config/conf/channels.py b/order/config/conf/channels.py new file mode 100644 index 0000000..d9f0597 --- /dev/null +++ b/order/config/conf/channels.py @@ -0,0 +1,8 @@ +CHANNEL_LAYERS = { + "default": { + "BACKEND": "channels_redis.core.RedisChannelLayer", + "CONFIG": { + "hosts": [("redis", 6379)], + }, + }, +} \ No newline at end of file diff --git a/order/config/conf/ckeditor.py b/order/config/conf/ckeditor.py new file mode 100644 index 0000000..654019c --- /dev/null +++ b/order/config/conf/ckeditor.py @@ -0,0 +1,147 @@ +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", + } + }, +} diff --git a/order/config/conf/cron.py b/order/config/conf/cron.py new file mode 100644 index 0000000..e69de29 diff --git a/order/config/conf/jwt.py b/order/config/conf/jwt.py new file mode 100644 index 0000000..15d8970 --- /dev/null +++ b/order/config/conf/jwt.py @@ -0,0 +1,36 @@ +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", +} diff --git a/order/config/conf/logs.py b/order/config/conf/logs.py new file mode 100644 index 0000000..5245ac6 --- /dev/null +++ b/order/config/conf/logs.py @@ -0,0 +1,29 @@ +# 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, + }, + }, +} diff --git a/order/config/conf/modules.py b/order/config/conf/modules.py new file mode 100644 index 0000000..a429f38 --- /dev/null +++ b/order/config/conf/modules.py @@ -0,0 +1 @@ +MODULES = [] diff --git a/order/config/conf/navigation.py b/order/config/conf/navigation.py new file mode 100644 index 0000000..2083489 --- /dev/null +++ b/order/config/conf/navigation.py @@ -0,0 +1,31 @@ +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"), + }, + ], + }, +] diff --git a/order/config/conf/rest_framework.py b/order/config/conf/rest_framework.py new file mode 100644 index 0000000..fbec6fe --- /dev/null +++ b/order/config/conf/rest_framework.py @@ -0,0 +1,9 @@ +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, +} diff --git a/order/config/conf/spectacular.py b/order/config/conf/spectacular.py new file mode 100644 index 0000000..2be491b --- /dev/null +++ b/order/config/conf/spectacular.py @@ -0,0 +1,31 @@ +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 diff --git a/order/config/conf/storage.py b/order/config/conf/storage.py new file mode 100644 index 0000000..5d59a85 --- /dev/null +++ b/order/config/conf/storage.py @@ -0,0 +1,23 @@ +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(), + }, +} diff --git a/order/config/conf/unfold.py b/order/config/conf/unfold.py new file mode 100644 index 0000000..cfbd93a --- /dev/null +++ b/order/config/conf/unfold.py @@ -0,0 +1,41 @@ +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 + }, +} diff --git a/order/config/env.py b/order/config/env.py new file mode 100644 index 0000000..f37c164 --- /dev/null +++ b/order/config/env.py @@ -0,0 +1,28 @@ +""" +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"), +) diff --git a/order/config/settings/__init__.py b/order/config/settings/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/order/config/settings/common.py b/order/config/settings/common.py new file mode 100644 index 0000000..42ba66b --- /dev/null +++ b/order/config/settings/common.py @@ -0,0 +1,172 @@ +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", + } +] diff --git a/order/config/settings/local.py b/order/config/settings/local.py new file mode 100644 index 0000000..7394e18 --- /dev/null +++ b/order/config/settings/local.py @@ -0,0 +1,11 @@ +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", +} diff --git a/order/config/settings/production.py b/order/config/settings/production.py new file mode 100644 index 0000000..d802ed7 --- /dev/null +++ b/order/config/settings/production.py @@ -0,0 +1,6 @@ +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"} diff --git a/order/config/urls.py b/order/config/urls.py new file mode 100644 index 0000000..d555038 --- /dev/null +++ b/order/config/urls.py @@ -0,0 +1,55 @@ +""" +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.*)", serve, {"document_root": settings.STATIC_ROOT}), + re_path(r"media/(?P.*)", serve, {"document_root": settings.MEDIA_ROOT}), +] diff --git a/order/config/wsgi.py b/order/config/wsgi.py new file mode 100644 index 0000000..ab1662c --- /dev/null +++ b/order/config/wsgi.py @@ -0,0 +1,8 @@ +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() diff --git a/order/core/__init__.py b/order/core/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/order/core/apps/__init__.py b/order/core/apps/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/order/core/services/__init__.py b/order/core/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/order/core/utils/__init__.py b/order/core/utils/__init__.py new file mode 100644 index 0000000..f10075c --- /dev/null +++ b/order/core/utils/__init__.py @@ -0,0 +1,3 @@ +from .cache import * # noqa +from .console import * # noqa +from .core import * # noqa diff --git a/order/core/utils/cache.py b/order/core/utils/cache.py new file mode 100644 index 0000000..2c00899 --- /dev/null +++ b/order/core/utils/cache.py @@ -0,0 +1,18 @@ +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 diff --git a/order/core/utils/console.py b/order/core/utils/console.py new file mode 100644 index 0000000..a296d66 --- /dev/null +++ b/order/core/utils/console.py @@ -0,0 +1,79 @@ +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") diff --git a/order/core/utils/core.py b/order/core/utils/core.py new file mode 100644 index 0000000..04fdbed --- /dev/null +++ b/order/core/utils/core.py @@ -0,0 +1,6 @@ +class Helper: + """ + Helper class to handle index + """ + + pass diff --git a/order/core/utils/storage.py b/order/core/utils/storage.py new file mode 100644 index 0000000..50e6d33 --- /dev/null +++ b/order/core/utils/storage.py @@ -0,0 +1,33 @@ +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 {} diff --git a/order/docker-compose.yml b/order/docker-compose.yml new file mode 100644 index 0000000..945920e --- /dev/null +++ b/order/docker-compose.yml @@ -0,0 +1,53 @@ +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 diff --git a/order/docker/Dockerfile.nginx b/order/docker/Dockerfile.nginx new file mode 100644 index 0000000..22a1599 --- /dev/null +++ b/order/docker/Dockerfile.nginx @@ -0,0 +1,3 @@ +FROM nginx:alpine + +COPY ./resources/layout/nginx.conf /etc/nginx/nginx.conf diff --git a/order/docker/Dockerfile.web b/order/docker/Dockerfile.web new file mode 100644 index 0000000..1fcf699 --- /dev/null +++ b/order/docker/Dockerfile.web @@ -0,0 +1,9 @@ +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"] diff --git a/order/jst.json b/order/jst.json new file mode 100644 index 0000000..e87c706 --- /dev/null +++ b/order/jst.json @@ -0,0 +1,8 @@ +{ + "dirs": { + "apps": "./core/apps/", + "locale": "./resources/locale/" + }, + "stubs": {}, + "apps": "core.apps." +} \ No newline at end of file diff --git a/order/manage.py b/order/manage.py new file mode 100644 index 0000000..67c98de --- /dev/null +++ b/order/manage.py @@ -0,0 +1,24 @@ +#!/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() diff --git a/order/pyproject.toml b/order/pyproject.toml new file mode 100644 index 0000000..e7de07c --- /dev/null +++ b/order/pyproject.toml @@ -0,0 +1,21 @@ +[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"] diff --git a/order/requirements.txt b/order/requirements.txt new file mode 100644 index 0000000..7430bbb --- /dev/null +++ b/order/requirements.txt @@ -0,0 +1,45 @@ +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 diff --git a/order/resources/.gitignore b/order/resources/.gitignore new file mode 100644 index 0000000..7627088 --- /dev/null +++ b/order/resources/.gitignore @@ -0,0 +1 @@ +staticfiles/ \ No newline at end of file diff --git a/order/resources/layout/.flake8 b/order/resources/layout/.flake8 new file mode 100644 index 0000000..4088dec --- /dev/null +++ b/order/resources/layout/.flake8 @@ -0,0 +1,3 @@ +[flake8] +max-line-length = 120 +ignore = E701, E704, W503 diff --git a/order/resources/layout/Dockerfile.alpine b/order/resources/layout/Dockerfile.alpine new file mode 100644 index 0000000..f57c7bc --- /dev/null +++ b/order/resources/layout/Dockerfile.alpine @@ -0,0 +1,13 @@ +FROM python:3.13-alpine + +ENV PYTHONPYCACHEPREFIX=/dev/null + +RUN apk update && apk add git gettext + +WORKDIR /code + +COPY requirements.txt /code/requirements.txt + +RUN --mount=type=cache,target=/root/.cache/pip pip install -r requirements.txt + +CMD ["sh", "./entrypoint.sh"] diff --git a/order/resources/layout/Dockerfile.nginx b/order/resources/layout/Dockerfile.nginx new file mode 100644 index 0000000..22a1599 --- /dev/null +++ b/order/resources/layout/Dockerfile.nginx @@ -0,0 +1,3 @@ +FROM nginx:alpine + +COPY ./resources/layout/nginx.conf /etc/nginx/nginx.conf diff --git a/order/resources/layout/mypy.ini b/order/resources/layout/mypy.ini new file mode 100644 index 0000000..6c53a26 --- /dev/null +++ b/order/resources/layout/mypy.ini @@ -0,0 +1,5 @@ +[mypy] +check_untyped_defs = True + +[mypy-requests.*] +ignore_missing_imports = True \ No newline at end of file diff --git a/order/resources/layout/nginx.conf b/order/resources/layout/nginx.conf new file mode 100644 index 0000000..89280da --- /dev/null +++ b/order/resources/layout/nginx.conf @@ -0,0 +1,54 @@ +# 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/; + } + } +} diff --git a/order/resources/locale/.gitkeep b/order/resources/locale/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/order/resources/locale/en/LC_MESSAGES/django.po b/order/resources/locale/en/LC_MESSAGES/django.po new file mode 100644 index 0000000..acc28e8 --- /dev/null +++ b/order/resources/locale/en/LC_MESSAGES/django.po @@ -0,0 +1,49 @@ +# 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 , 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: \n" +"Language-Team: LANGUAGE \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 "" diff --git a/order/resources/locale/ru/LC_MESSAGES/django.po b/order/resources/locale/ru/LC_MESSAGES/django.po new file mode 100644 index 0000000..a039baa --- /dev/null +++ b/order/resources/locale/ru/LC_MESSAGES/django.po @@ -0,0 +1,51 @@ +# 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 , 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: \n" +"Language-Team: LANGUAGE \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 "" diff --git a/order/resources/locale/uz/LC_MESSAGES/django.po b/order/resources/locale/uz/LC_MESSAGES/django.po new file mode 100644 index 0000000..c3a7ef5 --- /dev/null +++ b/order/resources/locale/uz/LC_MESSAGES/django.po @@ -0,0 +1,55 @@ +# 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 , 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: \n" +"Language-Team: LANGUAGE \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" diff --git a/order/resources/logs/.gitignore b/order/resources/logs/.gitignore new file mode 100644 index 0000000..d6b7ef3 --- /dev/null +++ b/order/resources/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore diff --git a/order/resources/media/.gitignore b/order/resources/media/.gitignore new file mode 100644 index 0000000..a3a0c8b --- /dev/null +++ b/order/resources/media/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/order/resources/scripts/backup.sh b/order/resources/scripts/backup.sh new file mode 100644 index 0000000..5bac180 --- /dev/null +++ b/order/resources/scripts/backup.sh @@ -0,0 +1,5 @@ +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 \ No newline at end of file diff --git a/order/resources/scripts/entrypoint-server.sh b/order/resources/scripts/entrypoint-server.sh new file mode 100644 index 0000000..d0233fc --- /dev/null +++ b/order/resources/scripts/entrypoint-server.sh @@ -0,0 +1,11 @@ +#!/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 $? + + diff --git a/order/resources/scripts/entrypoint.sh b/order/resources/scripts/entrypoint.sh new file mode 100644 index 0000000..b5e6e01 --- /dev/null +++ b/order/resources/scripts/entrypoint.sh @@ -0,0 +1,11 @@ +#!/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 $? + + diff --git a/order/resources/static/css/app.css b/order/resources/static/css/app.css new file mode 100644 index 0000000..e69de29 diff --git a/order/resources/static/css/error.css b/order/resources/static/css/error.css new file mode 100644 index 0000000..11201a8 --- /dev/null +++ b/order/resources/static/css/error.css @@ -0,0 +1,109 @@ +* { + -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; + } +} diff --git a/order/resources/static/css/input.css b/order/resources/static/css/input.css new file mode 100644 index 0000000..04b35af --- /dev/null +++ b/order/resources/static/css/input.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/order/resources/static/css/jazzmin.css b/order/resources/static/css/jazzmin.css new file mode 100644 index 0000000..8b8be66 --- /dev/null +++ b/order/resources/static/css/jazzmin.css @@ -0,0 +1,5 @@ +.login-logo img { + border-radius: 100%; + width: 100px; + height: 100px; +} \ No newline at end of file diff --git a/order/resources/static/css/output.css b/order/resources/static/css/output.css new file mode 100644 index 0000000..99bc6dc --- /dev/null +++ b/order/resources/static/css/output.css @@ -0,0 +1,772 @@ +*, ::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)); + } +} diff --git a/order/resources/static/images/logo.png b/order/resources/static/images/logo.png new file mode 100644 index 0000000..57ac67e Binary files /dev/null and b/order/resources/static/images/logo.png differ diff --git a/order/resources/static/js/alpine.js b/order/resources/static/js/alpine.js new file mode 100644 index 0000000..88b64ab --- /dev/null +++ b/order/resources/static/js/alpine.js @@ -0,0 +1,9 @@ +import Alpine from 'alpinejs' +import counter from "./counter"; + +window.Alpine = Alpine + +Alpine.data("vars", counter) + +Alpine.start() + diff --git a/order/resources/static/js/app.js b/order/resources/static/js/app.js new file mode 100644 index 0000000..f936998 --- /dev/null +++ b/order/resources/static/js/app.js @@ -0,0 +1 @@ +import "./alpine" diff --git a/order/resources/static/js/counter.js b/order/resources/static/js/counter.js new file mode 100644 index 0000000..f6a1d2c --- /dev/null +++ b/order/resources/static/js/counter.js @@ -0,0 +1,3 @@ +export default () => ({ + count: 2309 +}) \ No newline at end of file diff --git a/order/resources/static/js/customer.js b/order/resources/static/js/customer.js new file mode 100644 index 0000000..06bacfb --- /dev/null +++ b/order/resources/static/js/customer.js @@ -0,0 +1,49 @@ + +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); +}) \ No newline at end of file diff --git a/order/resources/static/js/vite-refresh.js b/order/resources/static/js/vite-refresh.js new file mode 100644 index 0000000..019ed44 --- /dev/null +++ b/order/resources/static/js/vite-refresh.js @@ -0,0 +1,9 @@ +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 +} \ No newline at end of file diff --git a/order/resources/static/vite/assets/appCss-w40geAFS.js b/order/resources/static/vite/assets/appCss-w40geAFS.js new file mode 100644 index 0000000..e69de29 diff --git a/order/resources/static/vite/assets/appJs-YH6iAcjX.js b/order/resources/static/vite/assets/appJs-YH6iAcjX.js new file mode 100644 index 0000000..7274c06 --- /dev/null +++ b/order/resources/static/vite/assets/appJs-YH6iAcjX.js @@ -0,0 +1,6 @@ +var Ce=!1,Me=!1,L=[],Te=-1;function zn(e){Hn(e)}function Hn(e){L.includes(e)||L.push(e),qn()}function Mt(e){let t=L.indexOf(e);t!==-1&&t>Te&&L.splice(t,1)}function qn(){!Me&&!Ce&&(Ce=!0,queueMicrotask(Wn))}function Wn(){Ce=!1,Me=!0;for(let e=0;ee.effect(t,{scheduler:n=>{Ie?zn(n):n()}}),Tt=e.raw}function _t(e){K=e}function Vn(e){let t=()=>{};return[r=>{let i=K(r);return e._x_effects||(e._x_effects=new Set,e._x_runEffects=()=>{e._x_effects.forEach(o=>o())}),e._x_effects.add(i),t=()=>{i!==void 0&&(e._x_effects.delete(i),q(i))},i},()=>{t()}]}function It(e,t){let n=!0,r,i=K(()=>{let o=e();JSON.stringify(o),n?r=o:queueMicrotask(()=>{t(o,r),r=o}),n=!1});return()=>q(i)}function X(e,t,n={}){e.dispatchEvent(new CustomEvent(t,{detail:n,bubbles:!0,composed:!0,cancelable:!0}))}function I(e,t){if(typeof ShadowRoot=="function"&&e instanceof ShadowRoot){Array.from(e.children).forEach(i=>I(i,t));return}let n=!1;if(t(e,()=>n=!0),n)return;let r=e.firstElementChild;for(;r;)I(r,t),r=r.nextElementSibling}function O(e,...t){console.warn(`Alpine Warning: ${e}`,...t)}var ht=!1;function Yn(){ht&&O("Alpine has already been initialized on this page. Calling Alpine.start() more than once can cause problems."),ht=!0,document.body||O("Unable to initialize. Trying to load Alpine before `` is available. Did you forget to add `defer` in Alpine's `