commit ab73d05ecc5533e2bd36ae5e9c63a1d648753ee2 Author: husanjon Date: Wed Apr 15 08:59:36 2026 +0200 gold eggs backend diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..c489446 --- /dev/null +++ b/.env.example @@ -0,0 +1,42 @@ +# Django configs +DJANGO_SECRET_KEY=key +DEBUG=True +DJANGO_SETTINGS_MODULE=config.settings.local +COMMAND=sh ./scripts/entrypoint.sh + +# 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,https://one-sheep-happily.ngrok-free.app,web +CSRF_TRUSTED_ORIGINS=https://one-sheep-happily.ngrok-free.app,http://127.0.0.1:88 + +# ngrok service +NGROK_AUTHTOKEN=token +NGROK_DOMAIN= +NGROK_ADMIN_PORT=4040 + +# Celery +CELERY_PORT=5555 diff --git a/.flake8 b/.flake8 new file mode 100644 index 0000000..bfead2c --- /dev/null +++ b/.flake8 @@ -0,0 +1,2 @@ +[flake8] +max-line-length = 79 diff --git a/.github/workflows/deploy.yaml b/.github/workflows/deploy.yaml new file mode 100644 index 0000000..ff38a5d --- /dev/null +++ b/.github/workflows/deploy.yaml @@ -0,0 +1,125 @@ +name: Build and Push to Docker Hub + +on: + push: + branches: + - main + +env: + PROJECT_NAME: eggs + +permissions: + contents: write + +jobs: + build-test-push: + runs-on: ubuntu-latest + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Copy env + run: | + cp .env.example .env + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./Dockerfile + push: false + load: true + tags: ${{ env.PROJECT_NAME }}:test + no-cache: true + + + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + + - name: Tag and push to Docker Hub + run: | + docker tag ${{ env.PROJECT_NAME }}:test ${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:latest + docker tag ${{ env.PROJECT_NAME }}:test ${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:${{ github.run_number }} + docker push ${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:latest + docker push ${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:${{ github.run_number }} + echo "SUCCESS TAGS: latest, ${{ github.run_number }}" + + - name: stack.yaml updated + run: | + sed -i 's|image: ${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:.*|image: ${{ secrets.DOCKER_USERNAME }}/${{ env.PROJECT_NAME }}:${{ github.run_number }}|' stack.yaml + sed -i 's/return HttpResponse("OK.*"/return HttpResponse("OK: #${{ github.sha }}"/' routes/common.py + + - name: Commit and push updated version + run: | + git config user.name "github-actions[bot]" + git config user.email "github-actions[bot]@users.noreply.github.com" + git add . + git commit -m "🔄 image to ${{ github.run_number }} [CI SKIP]" || echo "No changes" + git pull origin main --rebase + git push origin main + + - name: Execute remote SSH commands using SSH key + uses: appleboy/ssh-action@v1.2.2 + with: + host: ${{ secrets.HOST }} + username: ${{ secrets.USERNAME }} + # key: ${{ secrets.KEY }} + password: ${{ secrets.PASSWORD }} + port: ${{ secrets.PORT }} + script: | + sudo su + PROJECTS=/opt/projects/ + DIR=/opt/projects/${{ env.PROJECT_NAME }}/ + + if [ -d "$PROJECTS" ]; then + echo "projects papkasi mavjud" + else + mkdir -p $PROJECTS + echo "projects papkasi yaratildi" + fi + + if [ -d "$DIR" ]; then + echo "loyiha mavjud" + else + cd $PROJECTS + echo ${{ github.repository }} + sudo git clone git@github.com:${{ github.repository }}.git ${{ env.PROJECT_NAME }} + echo "Clone qilindi"; + fi + + cd $DIR + sudo git fetch origin main + sudo git reset --hard origin/main + sudo cp .env.example .env + + update_env() { + local env_file=".env" + sudo cp .env.example "$env_file" + + # argumentlar orqali key=value olish + for kv in "$@"; do + local key="${kv%%=*}" + local value="${kv#*=}" + sudo sed -i "s|^$key=.*|$key=$value|" "$env_file" + done + } + + # Funksiyani chaqirish misoli + update_env \ + "DJANGO_SECRET_KEY=${{ secrets.KEY }}" \ + "CACHE_ENABLED=True" \ + "ALLOWED_HOSTS=127.0.0.1,web,api.gold-eggs.uz" \ + "CSRF_TRUSTED_ORIGINS=http://127.0.0.1:8081,https://api.gold-eggs.uz" \ + "SMS_API_URL=${{ secrets.SMS_API_URL }}" \ + "SMS_LOGIN=${{ secrets.SMS_LOGIN }}" \ + "SMS_PASSWORD=${{ secrets.SMS_PASSWORD }}" + + sudo docker stack deploy -c stack.yaml ${{ env.PROJECT_NAME }} + diff --git a/.gitignore b/.gitignore new file mode 100755 index 0000000..dc571b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,156 @@ +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/ +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/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..9832edd --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,22 @@ +stages: + - deploy + +deploy_django_app: + stage: deploy + image: ubuntu:latest # Ubuntu tasvirini ishlatish + only: + - main + before_script: + - apt-get update && apt-get install -y openssh-client # Ubuntu/Debian uchun to'g'ri paket o'rnatish buyruqlari + - eval $(ssh-agent -s) + - echo "$SSH_PRIVATE_KEY" | tr -d '\r' | ssh-add - + - mkdir -p ~/.ssh + - echo "$SSH_SERVER_HOSTKEYS" > ~/.ssh/known_hosts + script: + - ssh $SSH_USER@$SSH_HOST -p $SSH_PORT " + cd $DEPLOY_PATH && + git pull origin main && + docker compose up --build -d && + docker image prune -f && + echo 'Deploy jarayoni muvaffaqiyatli yakunlandi!'" + diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..e69de29 diff --git a/Dockerfile b/Dockerfile new file mode 100755 index 0000000..d0e9ada --- /dev/null +++ b/Dockerfile @@ -0,0 +1,23 @@ +FROM jscorptech/django:latest + +ARG SCRIPT="entrypoint.sh" +ENV SCRIPT=$SCRIPT +ENV PROMETHEUS_MULTIPROC_DIR=/tmp/prometheus_metrics + +WORKDIR /code + +COPY requirements.txt /code/requirements.txt + +RUN uv pip install -r requirements.txt + +COPY ./ /code + +COPY ./scripts/$SCRIPT /code/$SCRIPT + +RUN chmod +x /code/scripts/$SCRIPT + +RUN mkdir -p /tmp/prometheus_metrics + +CMD sh /code/scripts/$SCRIPT + + diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..5a183d0 --- /dev/null +++ b/Makefile @@ -0,0 +1,60 @@ +app ?= web +shell ?= bash + +up: + docker compose up + +build: + docker compose build + +up-b: + docker compose up -b + +db: + docker compose exec db sh + +restart: + docker compose restart $(app) + +collect: + docker compose exec web poetry run python manage.py collectstatic + +makemigrations: + docker compose exec web poetry run python manage.py makemigrations + +up-d: + docker compose up -d + +logging: + docker compose logs -f web + +down: + docker compose down + +migrate: + docker compose exec web poetry run python manage.py migrate + +superuser: + docker compose exec web poetry run python manage.py createsuperuser + +shell: + @echo "Following logs for: $(app) shell: $(shell)" + docker compose exec $(app) $(shell) + +test: + docker compose exec web poetry run python manage.py test + +chown: + sudo chown -R user:user ./* + +connect: + @echo "Following logs for: $(app)" + docker compose logs -f $(app) + +pull: + git pull + +push: + git add . && git commit -m "$(comment)" && git push + +.PHONY: up diff --git a/README.md b/README.md new file mode 100644 index 0000000..e69de29 diff --git a/ViteDockerfile b/ViteDockerfile new file mode 100644 index 0000000..e0414c9 --- /dev/null +++ b/ViteDockerfile @@ -0,0 +1,11 @@ +FROM node:20-alpine + +WORKDIR /code + +COPY package*.json ./ + +RUN npm install + +COPY . . + +CMD ["sh","./scripts/node.sh"] diff --git a/celerybeat-schedule-shm b/celerybeat-schedule-shm new file mode 100644 index 0000000..e7c0135 Binary files /dev/null and b/celerybeat-schedule-shm differ diff --git a/celerybeat-schedule-wal b/celerybeat-schedule-wal new file mode 100644 index 0000000..5955bb5 Binary files /dev/null and b/celerybeat-schedule-wal differ diff --git a/common/env.py b/common/env.py new file mode 100755 index 0000000..dc3bb3c --- /dev/null +++ b/common/env.py @@ -0,0 +1,23 @@ +""" +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"), +) diff --git a/config/__init__.py b/config/__init__.py new file mode 100755 index 0000000..be38ddf --- /dev/null +++ b/config/__init__.py @@ -0,0 +1,3 @@ +from .celery import app + +__all__ = ["app"] diff --git a/config/asgi.py b/config/asgi.py new file mode 100755 index 0000000..5f2bee7 --- /dev/null +++ b/config/asgi.py @@ -0,0 +1,9 @@ +import os + +from django.core.asgi import get_asgi_application + +from common.env import env + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", env("DJANGO_SETTINGS_MODULE")) + +application = get_asgi_application() diff --git a/config/celery.py b/config/celery.py new file mode 100755 index 0000000..288fc96 --- /dev/null +++ b/config/celery.py @@ -0,0 +1,20 @@ +""" +Celery configurations +""" + +from __future__ import absolute_import +from __future__ import unicode_literals + +import os +import celery +from django.conf import settings + +from common.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(lambda: settings.INSTALLED_APPS) diff --git a/config/conf/__init__.py b/config/conf/__init__.py new file mode 100755 index 0000000..a4bec35 --- /dev/null +++ b/config/conf/__init__.py @@ -0,0 +1,8 @@ +from . import apps # noqa +from .cache import * # noqa +from .ckeditor import * # noqa +from .cron import * # noqa +from .jazzmin import * # noqa +from .jwt import * # noqa +from .logs import * # noqa +from .rest_framework import * # noqa diff --git a/config/conf/apps.py b/config/conf/apps.py new file mode 100644 index 0000000..e707b6e --- /dev/null +++ b/config/conf/apps.py @@ -0,0 +1,24 @@ +##################### +# My Settings +##################### +INSTALLED_APPS = [ + "rest_framework", + "corsheaders", + "django_filters", + "rosetta", + "django_redis", + "rest_framework_simplejwt", + "drf_yasg", + "crispy_forms", + "import_export", + "django_ckeditor_5", + "polymorphic", + ##################### + # My apps + ##################### + "core.apps.home.apps.HomeConfig", + "core.http.HttpConfig", + "core.apps.accounts.apps.AccountsConfig", + "core.console.ConsoleConfig", + "core.apps.eggs.apps.EggsConfig", +] diff --git a/config/conf/cache.py b/config/conf/cache.py new file mode 100755 index 0000000..d528aa0 --- /dev/null +++ b/config/conf/cache.py @@ -0,0 +1,11 @@ +from common.env import env + +CACHES = { + "default": { + "BACKEND": env("CACHE_BACKEND"), + "LOCATION": env("REDIS_URL"), + "TIMEOUT": env("CACHE_TIMEOUT"), + }, +} + +# CACHE_MIDDLEWARE_SECONDS = env("CACHE_TIMEOUT") diff --git a/config/conf/ckeditor.py b/config/conf/ckeditor.py new file mode 100755 index 0000000..8c1d0b2 --- /dev/null +++ b/config/conf/ckeditor.py @@ -0,0 +1,147 @@ +import os +from pathlib import Path + +STATIC_URL = "/static/" +MEDIA_URL = "/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/config/conf/cron.py b/config/conf/cron.py new file mode 100755 index 0000000..e69de29 diff --git a/config/conf/jazzmin.py b/config/conf/jazzmin.py new file mode 100755 index 0000000..1d2ab15 --- /dev/null +++ b/config/conf/jazzmin.py @@ -0,0 +1,127 @@ +from typing import Any + +JAZZMIN_SETTINGS: dict[str | Any, str | None | Any] = { + "site_title": "Felix IT Solution", + "site_header": "Felix IT Solution", + "site_brand": "Felix IT Solution", + "site_logo": "/images/logo.png", + "login_logo": None, + "login_logo_dark": None, + "site_logo_classes": "img-circle", + "site_icon": None, + "welcome_sign": "Felix IT Solution", + "copyright": "Felix IT Solution LLC", + "search_model": ["auth.User"], + "user_avatar": None, + "topmenu_links": [ + { + "name": "Home", + "url": "admin:index", + "permissions": ["auth.view_user"], + }, + { + "name": "Support", + "url": "https://github.com/farridav/django-jazzmin/issues", + "new_window": True, + }, + {"model": "auth.User"}, + {"app": "books"}, + ], + "usermenu_links": [ + { + "name": "Support", + "url": "https://github.com/farridav/django-jazzmin/issues", + "new_window": True, + }, + {"model": "auth.user"}, + ], + "show_sidebar": True, + "navigation_expanded": True, + "hide_apps": [], + "hide_models": [], + "order_with_respect_to": ["auth", "books", "books.author", "books.book"], + "custom_links": { + "books": [ + { + "name": "Make Messages", + "url": "make_messages", + "icon": "fas fa-comments", + "permissions": ["books.view_book"], + } + ] + }, + "icons": { + "http.Comment": "fas fa-comments", + "http.FrontendTranslation": "fas fa-globe", + "http.Post": "fas fa-bars", + "http.User": "fas fa-user", + "http.Tags": "fas fa-tag", + "http.SmsConfirm": "fas fa-comments", + "auth.Group": "fas fa-users", + "eggs.Courier": "fas fa-user-tie", + "eggs.CourierHistory": "fas fa-history", + "eggs.CourierProduct": "fas fa-envelope-open-text", + "eggs.Group": "fas fa-users", + "eggs.Party": "fas fa-person-booth", + "eggs.Product": "fas fa-egg", + "eggs.Invoice": "fas fa-envelope-open-text", + "eggs.Location": "fas fa-location-arrow", + "eggs.Market": "fas fa-store", + "eggs.Order": "fas fa-envelope-open-text", + "eggs.OrderItems": "fas fa-object-group", + "eggs.Broken": "fas fa-trash-alt", + "eggs.Debt": "fas fa-hand-holding-usd", + "eggs.History": "fas fa-history", + "eggs.AllHistory": "fas fa-history", + "eggs.AdditionalCost": "fas fa-money-bill-wave", + "eggs.Sklad": "fas fa-warehouse", + "eggs.Monitoring": "fas fa-chart-line", + "eggs.Notification": "fas fa-bell", + }, + "default_icon_parents": "fas fa-chevron-circle-right", + "default_icon_children": "fas fa-circle", + "related_modal_active": False, + "custom_css": "css/jazzmin.css", + "custom_js": None, + "use_google_fonts_cdn": True, + "show_ui_builder": False, + "changeform_format": "horizontal_tabs", + "changeform_format_overrides": { + "auth.user": "collapsible", + "auth.group": "vertical_tabs", + }, + "language_chooser": True, +} + +JAZZMIN_UI_TWEAKS = { + "navbar_small_text": False, + "footer_small_text": False, + "body_small_text": True, + "brand_small_text": False, + "brand_colour": "navbar-navy", + "accent": "accent-primary", + "navbar": "navbar-navy navbar-dark", + "no_navbar_border": False, + "navbar_fixed": True, + "layout_boxed": False, + "footer_fixed": False, + "sidebar_fixed": True, + "sidebar": "sidebar-dark-navy", + "sidebar_nav_small_text": False, + "sidebar_disable_expand": False, + "sidebar_nav_child_indent": False, + "sidebar_nav_compact_style": True, + "sidebar_nav_legacy_style": False, + "sidebar_nav_flat_style": False, + "theme": "minty", + "dark_mode_theme": None, + "button_classes": { + "primary": "btn-outline-primary", + "secondary": "btn-outline-secondary", + "info": "btn-outline-info", + "warning": "btn-warning", + "danger": "btn-danger", + "success": "btn-success", + }, + "actions_sticky_top": False, +} diff --git a/config/conf/jwt.py b/config/conf/jwt.py new file mode 100755 index 0000000..838816b --- /dev/null +++ b/config/conf/jwt.py @@ -0,0 +1,37 @@ +from datetime import timedelta + +from common.env import env + +SIMPLE_JWT = { + "ACCESS_TOKEN_LIFETIME": timedelta(days=30), + "REFRESH_TOKEN_LIFETIME": timedelta(days=365), + "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", + "USER_AUTHENTICATION_RULE": "rest_framework_simplejwt.authentication.default_user_authentication_rule", + "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(days=30), + "SLIDING_TOKEN_REFRESH_LIFETIME": timedelta(days=365), + "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/config/conf/logs.py b/config/conf/logs.py new file mode 100755 index 0000000..b188b43 --- /dev/null +++ b/config/conf/logs.py @@ -0,0 +1,7 @@ +import logging + +logging.basicConfig( + filename=f"./logs/django.log", + level=logging.DEBUG, + format="%(asctime)s - %(levelname)s - %(message)s", +) diff --git a/config/conf/rest_framework.py b/config/conf/rest_framework.py new file mode 100755 index 0000000..64606a4 --- /dev/null +++ b/config/conf/rest_framework.py @@ -0,0 +1,5 @@ +REST_FRAMEWORK = { + "DEFAULT_AUTHENTICATION_CLASSES": ( + "rest_framework_simplejwt.authentication.JWTAuthentication", + ), +} diff --git a/config/settings/__init__.py b/config/settings/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/config/settings/common.py b/config/settings/common.py new file mode 100755 index 0000000..1f7a24d --- /dev/null +++ b/config/settings/common.py @@ -0,0 +1,146 @@ +import os # noqa +import pathlib + +import firebase_admin +from django.utils.translation import gettext_lazy as _ +from firebase_admin import credentials + +from config.conf import * # noqa + +BASE_DIR = pathlib.Path(__file__).resolve().parent.parent.parent + +SECRET_KEY = env.str("DJANGO_SECRET_KEY") +DEBUG = env.str("DEBUG") + +ALLOWED_HOSTS = ["*"] + +INSTALLED_APPS = [ + # Design admin panel + "jazzmin", + "django_select2", + "modeltranslation", + # Default apps + "django.contrib.admin", + "django.contrib.auth", + "django.contrib.contenttypes", + "django.contrib.sessions", + "django.contrib.messages", + "django.contrib.staticfiles", +] + +INSTALLED_APPS += apps.INSTALLED_APPS # noqa + +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", + "core.middlewares.fcm_token.JWTFCMMiddleware", # FCM Token middleware + "django.contrib.auth.middleware.AuthenticationMiddleware", + "django.contrib.messages.middleware.MessageMiddleware", + "django.middleware.clickjacking.XFrameOptionsMiddleware", +] + +ROOT_URLCONF = "routes" + +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", + ], + }, + }, +] + +WSGI_APPLICATION = "config.wsgi.application" + +AUTH_PASSWORD_VALIDATORS = [ + { + "NAME": "django.contrib.auth.password_validation" + ".UserAttributeSimilarityValidator", + }, + { + "NAME": "django.contrib.auth.password_validation" + ".MinimumLengthValidator", + }, + { + "NAME": "django.contrib.auth.password_validation" + ".CommonPasswordValidator", + }, + { + "NAME": "django.contrib.auth.password_validation" + ".NumericPasswordValidator", + }, +] + +TIME_ZONE = "Asia/Tashkent" +USE_I18N = True +USE_TZ = True +STATIC_URL = "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"] + +FACTORYS = [ + ("core.http.database.factory.PostFactory", 100000), + # ("core.http.database.factory.UserFactory", 1), +] + +SEEDERS = ["core.http.database.seeder.UserSeeder"] + +STATICFILES_DIRS = [ + os.path.join(BASE_DIR, "resources/static"), +] + +CORS_ORIGIN_ALLOW_ALL = True + +PAYCOM_SETTINGS = { + "KASSA_ID": "1111", + "ACCOUNTS": { + "KEY": "1234", + }, + "TOKEN": "1111", +} + +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, "locale")] + +MODELTRANSLATION_LANGUAGES = ("uz", "ru", "en") +MODELTRANSLATION_DEFAULT_LANGUAGE = "uz" +LANGUAGE_CODE = "uz" + +MEDIA_ROOT = os.path.join(BASE_DIR, "media") # Media files +MEDIA_URL = "/media/" + +AUTH_USER_MODEL = "http.User" + +CELERY_BROKER_URL = env("REDIS_URL") +CELERY_RESULT_BACKEND = env("REDIS_URL") + +CRISPY_TEMPLATE_PACK = "tailwind" + +ALLOWED_HOSTS += env("ALLOWED_HOSTS").split(",") +CSRF_TRUSTED_ORIGINS = env("CSRF_TRUSTED_ORIGINS").split(",") + +cred = credentials.Certificate("resources/firebase_golden.json") +firebase_admin.initialize_app(cred) diff --git a/config/settings/local.py b/config/settings/local.py new file mode 100755 index 0000000..2b87e8d --- /dev/null +++ b/config/settings/local.py @@ -0,0 +1,47 @@ +import core.apps.accounts.apps +from common.env import env +from config.conf import rest_framework +from config.settings.common import * # noqa + +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"), + } +} + +MIDDLEWARE += [ + "debug_toolbar.middleware.DebugToolbarMiddleware", + "core.middlewares.ExceptionMiddleware", +] + +# Debug toolbar middleware +DEBUG_TOOLBAR_PANELS = [ + "debug_toolbar.panels.versions.VersionsPanel", + "debug_toolbar.panels.timer.TimerPanel", + "debug_toolbar.panels.settings.SettingsPanel", + "debug_toolbar.panels.headers.HeadersPanel", + "debug_toolbar.panels.request.RequestPanel", + "debug_toolbar.panels.sql.SQLPanel", + "debug_toolbar.panels.staticfiles.StaticFilesPanel", + "debug_toolbar.panels.templates.TemplatesPanel", + "debug_toolbar.panels.cache.CachePanel", + "debug_toolbar.panels.signals.SignalsPanel", + "debug_toolbar.panels.logging.LoggingPanel", + "debug_toolbar.panels.redirects.RedirectsPanel", +] + +INTERNAL_IPS = ("127.0.0.1",) + +INSTALLED_APPS += ["debug_toolbar", "django_extensions"] + +# Allowed Hosts +ALLOWED_HOSTS += ["127.0.0.1", "192.168.100.26"] + +rest_framework.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = { + "user": "10/min", +} diff --git a/config/settings/production.py b/config/settings/production.py new file mode 100755 index 0000000..5d2cf9a --- /dev/null +++ b/config/settings/production.py @@ -0,0 +1,22 @@ +from common.env import env +from config.conf import rest_framework +from config.settings.common import * # noqa + +DATABASES = { + "default": { + "ENGINE": env("DB_ENGINE"), + "NAME": env("DB_NAME"), + "USER": env("DB_USER"), + "PASSWORD": env("DB_PASSWORD"), + "HOST": env("DB_HOST"), + "PORT": env("DB_PORT"), + } +} + +MIDDLEWARE += [] + +ALLOWED_HOSTS += ["192.168.100.26", "80.90.178.156"] + +rest_framework.REST_FRAMEWORK["DEFAULT_THROTTLE_RATES"] = { + "user": "10/min", +} diff --git a/config/wsgi.py b/config/wsgi.py new file mode 100755 index 0000000..2639331 --- /dev/null +++ b/config/wsgi.py @@ -0,0 +1,9 @@ +import os + +from django.core.wsgi import get_wsgi_application + +from common.env import env + +os.environ.setdefault("DJANGO_SETTINGS_MODULE", env("DJANGO_SETTINGS_MODULE")) + +application = get_wsgi_application() diff --git a/core/apps/accounts/__init__.py b/core/apps/accounts/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/accounts/admin/__init__.py b/core/apps/accounts/admin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/accounts/apps.py b/core/apps/accounts/apps.py new file mode 100644 index 0000000..a778dbd --- /dev/null +++ b/core/apps/accounts/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class AccountsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "core.apps.accounts" diff --git a/core/apps/accounts/migrations/__init__.py b/core/apps/accounts/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/accounts/models/__init__.py b/core/apps/accounts/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/accounts/serializers/__init__.py b/core/apps/accounts/serializers/__init__.py new file mode 100644 index 0000000..df371ea --- /dev/null +++ b/core/apps/accounts/serializers/__init__.py @@ -0,0 +1 @@ +from .check_password import * # noqa diff --git a/core/apps/accounts/serializers/check_password.py b/core/apps/accounts/serializers/check_password.py new file mode 100644 index 0000000..2a778f9 --- /dev/null +++ b/core/apps/accounts/serializers/check_password.py @@ -0,0 +1,6 @@ +from rest_framework import serializers + + +class ChangePasswordSerializer(serializers.Serializer): + old_password = serializers.CharField(required=True) + new_password = serializers.CharField(required=True) diff --git a/core/apps/accounts/test/__init__.py b/core/apps/accounts/test/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/accounts/urls.py b/core/apps/accounts/urls.py new file mode 100755 index 0000000..a64cd15 --- /dev/null +++ b/core/apps/accounts/urls.py @@ -0,0 +1,64 @@ +""" +Accounts app urls +""" + +from django.urls import path +from rest_framework_simplejwt import views as jwt_views + +from core.apps.accounts import views +from core.http.views.user import UserAvatarUpdateView, UserUpdateView + +urlpatterns = [ + path( + "auth/token/", + jwt_views.TokenObtainPairView.as_view(), + name="token_obtain_pair", + ), # Login view # noqa + path( + "auth/token/refresh/", + jwt_views.TokenRefreshView.as_view(), + name="token_refresh", + ), # Refresh token view # noqa + path( + "auth/token/verify/", + jwt_views.TokenVerifyView.as_view(), + name="token_verify", + ), # Verify token # noqa + # path( + # "auth/register/", views.RegisterView.as_view(), name="register" + # ), # Register # noqa + path( + "auth/confirm/", views.ConfirmView.as_view(), name="confirm" + ), # Confirm Otp code view # noqa + path( + "auth/reset/password/", + views.ResetPasswordView.as_view(), + name="reset-password", + ), # Reset password step 1 # noqa + path( + "auth/confirm/reset/", + views.ResetConfirmationCodeView.as_view(), + name="reset-confirmation-code", + ), # noqa + # Reset password step 2 + path( + "auth/resend/", views.ResendView.as_view(), name="resend" + ), # resend otp code # noqa + path( + "auth/me/", views.MeView.as_view(), name="me" + ), # get user information # noqa + path( + "change/password/", + views.ChangePasswordView.as_view(), + name="avatar-update", + ), # chamge user password # noqa + # Update user information + path( + "auth/update/", UserUpdateView.as_view(), name="update" + ), # update user information # noqa + path( + "auth/avatar/update/", + UserAvatarUpdateView.as_view(), + name="avatar-update", + ), # update user avatar # noqa +] diff --git a/core/apps/accounts/views/__init__.py b/core/apps/accounts/views/__init__.py new file mode 100644 index 0000000..a829163 --- /dev/null +++ b/core/apps/accounts/views/__init__.py @@ -0,0 +1,2 @@ +from .check_passeord import * # noqa +from .sms import * # noqa diff --git a/core/apps/accounts/views/check_passeord.py b/core/apps/accounts/views/check_passeord.py new file mode 100644 index 0000000..79eb53f --- /dev/null +++ b/core/apps/accounts/views/check_passeord.py @@ -0,0 +1,35 @@ +from django.contrib.auth.hashers import make_password +from rest_framework import exceptions, permissions, status, views +from rest_framework.generics import get_object_or_404 + +from core.http import views as http_views +from core.http.models import User + +from ..serializers import ChangePasswordSerializer + + +class ChangePasswordView(views.APIView, http_views.ApiResponse): + permission_classes = (permissions.IsAuthenticated,) + + def post(self, request, *args, **kwargs): + user = request.user + + if user is None: + raise exceptions.ValidationError( + {"status": False, "message": "Foydalanuvchi topilmadi"} + ) + serializer = ChangePasswordSerializer(data=request.data) + if serializer.is_valid(): + if user.check_password(request.data["old_password"]): + user.password = make_password(serializer.data["new_password"]) + user.save() + return http_views.ApiResponse().success( + "Parol muvaffaqiyatli o'zgartirildi", + status_code=status.HTTP_200_OK, + ) + return http_views.ApiResponse().error( + "Noto'g'ri eski parol", status_code=status.HTTP_400_BAD_REQUEST + ) + return http_views.ApiResponse().error( + serializer.errors, status_code=status.HTTP_400_BAD_REQUEST + ) diff --git a/core/apps/accounts/views/sms.py b/core/apps/accounts/views/sms.py new file mode 100644 index 0000000..fb829e7 --- /dev/null +++ b/core/apps/accounts/views/sms.py @@ -0,0 +1,146 @@ +""" +SMS configuration (eskiz.uz) +""" + +import typing + +from django.utils.translation import gettext_lazy as _ +from rest_framework import permissions +from rest_framework import request as rest_request +from rest_framework import throttling, views + +from core import enums, exceptions, services, utils +from core.http import serializers +from core.http import views as http_views +from core.http.models import User + + +class RegisterView( + views.APIView, services.UserService, http_views.ApiResponse +): + """ + Register new user + """ + + serializer_class = serializers.RegisterSerializer + throttle_classes = [throttling.UserRateThrottle] + + def post(self, request: rest_request.Request): + ser = self.serializer_class(data=request.data) + ser.is_valid(raise_exception=True) + data = ser.data + phone = data.get("phone") + + # Create pending user + self.create_user( + phone, + data.get("first_name"), + data.get("last_name"), + data.get("password"), + ) + + # Send confirmation code for sms eskiz.uz + self.send_confirmation(phone) + return self.success(_(enums.Messages.SEND_MESSAGE) % {"phone": phone}) + + +class ConfirmView(views.APIView, services.UserService, http_views.ApiResponse): + """Confirm otp code""" + + serializer_class = serializers.ConfirmSerializer + + def post(self, request: rest_request.Request): + ser = self.serializer_class(data=request.data) + ser.is_valid(raise_exception=True) + + data = ser.data + phone, code = data.get("phone"), data.get("code") + + try: + # Check Sms confirmation otp code + if services.SmsService.check_confirm(phone, code=code): + # Create user + token = self.validate_user( + User.objects.filter(phone=phone).first() + ) + return self.success( + _(enums.Messages.OTP_CONFIRMED), token=token + ) + except exceptions.SmsException as e: + return utils.ResponseException(e) + except Exception as e: + return self.error(e) + + +class ResetConfirmationCodeView( + views.APIView, http_views.ApiResponse, services.UserService +): # noqa + """ + Reset confirm otp code + """ + + serializer_class = serializers.ResetConfirmationSerializer + + def post(self, request: rest_request.Request): + ser = self.serializer_class(data=request.data) + ser.is_valid(raise_exception=True) + + data = ser.data + code, phone, password = ( + data.get("code"), + data.get("phone"), + data.get("password"), + ) # noqa + + try: + res = services.SmsService.check_confirm(phone, code) + if res: + self.change_password(phone, password) + return self.success(_(enums.Messages.CHANGED_PASSWORD)) + return self.error(_(enums.Messages.INVALID_OTP)) + except exceptions.SmsException as e: + return self.error(e, error_code=enums.Codes.INVALID_OTP_ERROR) + except Exception as e: + return self.error(e) + + +class ResendView(http_views.AbstractSendSms): + """ + Resend Otp Code + """ + + serializer_class = serializers.ResendSerializer + + +class ResetPasswordView(http_views.AbstractSendSms): + """ + Reset user password + """ + + serializer_class: typing.Type[serializers.ResetPasswordSerializer] = ( + serializers.ResetPasswordSerializer + ) # noqa + + +# class MeView(views.APIView, http_views.ApiResponse): +# """ +# Get user information +# """ +# permission_classes = [permissions.IsAuthenticated] +# +# def get(self, request: rest_request.Request): +# user = request.user +# serializer = serializers.UserSerializer(user, context={'request': request}) +# return self.success(data=serializer.data) + + +class MeView(views.APIView, http_views.ApiResponse): + """ + Get user information + """ + + permission_classes = [permissions.IsAuthenticated] + + def get(self, request: rest_request.Request): + user = request.user + return self.success(data=serializers.UserSerializer(user).data) diff --git a/core/apps/eggs/__init__.py b/core/apps/eggs/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/eggs/admin.py b/core/apps/eggs/admin.py new file mode 100644 index 0000000..a9662e5 --- /dev/null +++ b/core/apps/eggs/admin.py @@ -0,0 +1,211 @@ +""" +Dajngo admin panel models register +""" + +from django.contrib import admin + +from core.apps.eggs import models +from core.apps.eggs.models.notification import Notification + + +@admin.register(models.Courier) +class CourierAdmin(admin.ModelAdmin): + list_display = ["id", "user_id", "courier_name"] + + def courier_name(self, obj): + return f"{obj.user_id.first_name} {obj.user_id.last_name}" + + +@admin.register(models.CourierProduct) +class CourierProductAdmin(admin.ModelAdmin): + list_display = [ + "id", + "courier_id", + "group_id", + "count", + "return_eggs", + "created_at", + ] + + +@admin.register(models.CourierHistory) +class CourierHistoryAdmin(admin.ModelAdmin): + list_display = [ + "id", + "courier_id", + "group_id", + "get_eggs", + "return_eggs", + "broken_eggs", + "date", + "courier_product_id", + ] + + +@admin.register(models.Group) +class GroupAdmin(admin.ModelAdmin): + list_display = [ + "id", + "product_id", + "party_id", + "entry_price", + "unit_price", + "wholesale_price", + "quantity", + "broken_eggs", + ] + + +@admin.register(models.Invoice) +class InvoiceAdmin(admin.ModelAdmin): + list_display = ["id", "name", "price", "party_id"] + + +@admin.register(models.Location) +class LocationAdmin(admin.ModelAdmin): + list_display = ["id", "long", "lat", "label"] + + +@admin.register(models.Market) +class MarketAdmin(admin.ModelAdmin): + list_display = [ + "id", + "name", + "company_name", + "user_id", + "phone", + "debt_paid", + "debt_unpaid", + ] + + +@admin.register(models.Order) +class OrderAdmin(admin.ModelAdmin): + list_display = [ + "id", + "courier_id", + "market_id", + "data", + "status", + "price", + "price_paid", + "debt", + ] + + +@admin.register(models.OrderItems) +class OrderItemsAdmin(admin.ModelAdmin): + list_display = [ + "id", + "group_id", + "count", + "discount", + "courier_product_id", + "order_id", + "sale_type", + ] + + +@admin.register(models.Party) +class PartAdmin(admin.ModelAdmin): + list_display = [ + "id", + "user_id", + "count", + "sold_count", + "remaining_count", + "benefit", + "cost", + "total_cost", + ] + + +@admin.register(models.Product) +class ProductAdmin(admin.ModelAdmin): + list_display = ["id", "name"] + + +@admin.register(models.Broken) +class BrokenAdmin(admin.ModelAdmin): + list_display = ["id", "group", "comment", "quantity", "user"] + + def user(self, obj): + return f"{obj.user_id.first_name} {obj.user_id.last_name}" + + +@admin.register(models.Debt) +class DebtAdmin(admin.ModelAdmin): + list_display = ["id", "market", "debt_price"] + + +@admin.register(models.History) +class HistoryAdmin(admin.ModelAdmin): + list_display = [ + "id", + "content_type", + "object_id", + "action", + "timestamp", + "created_by", + "created_who", + "reason", + "comment", + ] + + +# @admin.register(models.AllHistory) +# class AllHistoryAdmin(admin.ModelAdmin): +# list_display = [ +# "id", +# "content_type", +# "object_id", +# "action", +# "timestamp", +# "created_by", +# "created_who", +# "reason", +# ] + + +@admin.register(models.AdditionalCost) +class AdditionalCostAdmin(admin.ModelAdmin): + list_display = ["id", "user", "reason", "price", "created_at"] + readonly_fields = ["created_at"] + + def user(self, obj): + return f"{obj.user.first_name} {obj.user.last_name}" + + +@admin.register(models.Sklad) +class SkladAdmin(admin.ModelAdmin): + list_display = ["id", "user_id"] + + def user_id(self, obj): + return f"{obj.user_id.first_name} {obj.user_id.last_name}" + + +@admin.register(models.Monitoring) +class MonitoringAdmin(admin.ModelAdmin): + list_display = [ + "id", + "content_type", + "object_id", + "action", + "timestamp", + "created_by", + "created_who", + "reason", + "comment", + "price", + ] + + +@admin.register(Notification) +class NotificationAdmin(admin.ModelAdmin): + list_display = ("title", "body", "user", "is_read", "is_sending") + + +@admin.register(models.DailyCost) +class DailyCostAdmin(admin.ModelAdmin): + list_display = ["id", "first_cost", "second_cost", "third_cost", "created_at"] + readonly_fields = ["created_at"] diff --git a/core/apps/eggs/apps.py b/core/apps/eggs/apps.py new file mode 100644 index 0000000..8537c9c --- /dev/null +++ b/core/apps/eggs/apps.py @@ -0,0 +1,9 @@ +from django.apps import AppConfig + + +class EggsConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "core.apps.eggs" + + def ready(self) -> None: + from core.apps.eggs import utils # noqa diff --git a/core/apps/eggs/migrations/0001_initial.py b/core/apps/eggs/migrations/0001_initial.py new file mode 100644 index 0000000..9116a69 --- /dev/null +++ b/core/apps/eggs/migrations/0001_initial.py @@ -0,0 +1,435 @@ +# Generated by Django 5.0.4 on 2024-04-23 08:54 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Group", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "entry_price", + models.DecimalField(decimal_places=2, max_digits=10), + ), + ( + "unit_price", + models.DecimalField(decimal_places=2, max_digits=10), + ), + ( + "wholesale_price", + models.DecimalField(decimal_places=2, max_digits=10), + ), + ("quantity", models.IntegerField()), + ("broken_eggs", models.IntegerField()), + ], + options={ + "verbose_name": "Group", + "verbose_name_plural": "Groups", + "db_table": "group", + }, + ), + migrations.CreateModel( + name="Location", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("long", models.BigIntegerField()), + ("lat", models.BigIntegerField()), + ("label", models.TextField()), + ], + options={ + "verbose_name": "Location", + "verbose_name_plural": "Locations", + "db_table": "location", + }, + ), + migrations.CreateModel( + name="Product", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("name", models.CharField(max_length=255)), + ], + options={ + "verbose_name": "Product", + "verbose_name_plural": "Products", + "db_table": "product", + }, + ), + migrations.CreateModel( + name="Courier", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "user_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="couriers", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "verbose_name": "Courier", + "verbose_name_plural": "Couriers", + "db_table": "courier", + }, + ), + migrations.CreateModel( + name="CourierProduct", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("count", models.IntegerField()), + ( + "courier_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="courier_products", + to="eggs.courier", + ), + ), + ( + "group_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="courier_groups", + to="eggs.group", + ), + ), + ], + options={ + "verbose_name": "CourierProduct", + "verbose_name_plural": "CourierProducts", + "db_table": "courier_product", + }, + ), + migrations.CreateModel( + name="Market", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("name", models.CharField(max_length=255)), + ( + "avatar", + models.ImageField( + blank=True, null=True, upload_to="market_avatar/" + ), + ), + ("phone", models.CharField(max_length=20)), + ( + "location", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="market_location", + to="eggs.location", + ), + ), + ( + "user_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "verbose_name": "Market", + "verbose_name_plural": "Markets", + "db_table": "market", + }, + ), + migrations.CreateModel( + name="Order", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("data", models.DateField(auto_now=True)), + ( + "status", + models.CharField( + choices=[ + ("delivery", "Delivery"), + ("pending", "Pending"), + ("success", "Success"), + ("cancel", "Cancel"), + ("done", "Done"), + ], + default="pending", + max_length=255, + ), + ), + ("comment", models.TextField(blank=True, null=True)), + ( + "price", + models.DecimalField(decimal_places=2, max_digits=10), + ), + ( + "price_paid", + models.DecimalField(decimal_places=2, max_digits=10), + ), + ( + "courier_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="orders", + to="eggs.courier", + ), + ), + ( + "location_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="eggs.location", + ), + ), + ( + "market_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="orders", + to="eggs.market", + ), + ), + ], + options={ + "verbose_name": "Order", + "verbose_name_plural": "Orders", + "db_table": "order", + }, + ), + migrations.CreateModel( + name="Party", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "price", + models.DecimalField(decimal_places=2, max_digits=10), + ), + ( + "user_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="parties", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "verbose_name": "Party", + "verbose_name_plural": "Parties", + "db_table": "party", + }, + ), + migrations.CreateModel( + name="Invoice", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=256)), + ( + "price", + models.DecimalField(decimal_places=2, max_digits=10), + ), + ( + "party_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="invoices", + to="eggs.party", + ), + ), + ], + options={ + "verbose_name": "Invoice", + "verbose_name_plural": "Invoices", + "db_table": "invoice", + }, + ), + migrations.AddField( + model_name="group", + name="party_id", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, to="eggs.party" + ), + ), + migrations.CreateModel( + name="CourierHistory", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("get_eggs", models.IntegerField()), + ("return_eggs", models.IntegerField(blank=True, null=True)), + ("broken_eggs", models.IntegerField(blank=True, null=True)), + ("date", models.DateField(auto_now=True, null=True)), + ( + "group_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="eggs.group", + ), + ), + ( + "courier_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="courier_histories", + to="eggs.party", + ), + ), + ], + options={ + "verbose_name": "CourierHistory", + "verbose_name_plural": "CourierHistories", + "db_table": "courier_history", + }, + ), + migrations.CreateModel( + name="OrderItems", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("count", models.IntegerField()), + ("discount", models.IntegerField()), + ( + "courier_product_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="eggs.courierproduct", + ), + ), + ( + "product_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="eggs.product", + ), + ), + ], + options={ + "verbose_name": "OrderItem", + "verbose_name_plural": "OrderItems", + "db_table": "order_item", + }, + ), + migrations.AddField( + model_name="group", + name="product_id", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="groups", + to="eggs.product", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0002_rename_location_market_location_id.py b/core/apps/eggs/migrations/0002_rename_location_market_location_id.py new file mode 100644 index 0000000..3bda96f --- /dev/null +++ b/core/apps/eggs/migrations/0002_rename_location_market_location_id.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.4 on 2024-04-23 10:28 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0001_initial"), + ] + + operations = [ + migrations.RenameField( + model_name="market", + old_name="location", + new_name="location_id", + ), + ] diff --git a/core/apps/eggs/migrations/0003_remove_group_party_id_group_party.py b/core/apps/eggs/migrations/0003_remove_group_party_id_group_party.py new file mode 100644 index 0000000..40072a9 --- /dev/null +++ b/core/apps/eggs/migrations/0003_remove_group_party_id_group_party.py @@ -0,0 +1,29 @@ +# Generated by Django 5.0.4 on 2024-04-23 11:39 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0002_rename_location_market_location_id"), + ] + + operations = [ + migrations.RemoveField( + model_name="group", + name="party_id", + ), + migrations.AddField( + model_name="group", + name="party", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + related_name="groups", + to="eggs.party", + ), + preserve_default=False, + ), + ] diff --git a/core/apps/eggs/migrations/0004_rename_product_id_group_product.py b/core/apps/eggs/migrations/0004_rename_product_id_group_product.py new file mode 100644 index 0000000..439182b --- /dev/null +++ b/core/apps/eggs/migrations/0004_rename_product_id_group_product.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.4 on 2024-04-23 11:39 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0003_remove_group_party_id_group_party"), + ] + + operations = [ + migrations.RenameField( + model_name="group", + old_name="product_id", + new_name="product", + ), + ] diff --git a/core/apps/eggs/migrations/0005_rename_product_group_product_id_remove_group_party_and_more.py b/core/apps/eggs/migrations/0005_rename_product_group_product_id_remove_group_party_and_more.py new file mode 100644 index 0000000..62ca82e --- /dev/null +++ b/core/apps/eggs/migrations/0005_rename_product_group_product_id_remove_group_party_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.0.4 on 2024-04-23 11:55 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0004_rename_product_id_group_product"), + ] + + operations = [ + migrations.RenameField( + model_name="group", + old_name="product", + new_name="product_id", + ), + migrations.RemoveField( + model_name="group", + name="party", + ), + migrations.AddField( + model_name="group", + name="party_id", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + to="eggs.party", + ), + preserve_default=False, + ), + ] diff --git a/core/apps/eggs/migrations/0006_party_count_party_sold_party_sold_price_and_more.py b/core/apps/eggs/migrations/0006_party_count_party_sold_party_sold_price_and_more.py new file mode 100644 index 0000000..83f33ef --- /dev/null +++ b/core/apps/eggs/migrations/0006_party_count_party_sold_party_sold_price_and_more.py @@ -0,0 +1,48 @@ +# Generated by Django 5.0.4 on 2024-04-24 07:17 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "eggs", + "0005_rename_product_group_product_id_remove_group_party_and_more", + ), + ] + + operations = [ + migrations.AddField( + model_name="party", + name="count", + field=models.IntegerField(default=1), + preserve_default=False, + ), + migrations.AddField( + model_name="party", + name="sold", + field=models.DecimalField( + decimal_places=2, default=1, max_digits=10 + ), + preserve_default=False, + ), + migrations.AddField( + model_name="party", + name="sold_price", + field=models.DecimalField( + decimal_places=2, default=1, max_digits=10 + ), + preserve_default=False, + ), + migrations.AlterField( + model_name="group", + name="party_id", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="groups", + to="eggs.party", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0007_alter_courierhistory_courier_id.py b/core/apps/eggs/migrations/0007_alter_courierhistory_courier_id.py new file mode 100644 index 0000000..773d816 --- /dev/null +++ b/core/apps/eggs/migrations/0007_alter_courierhistory_courier_id.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.4 on 2024-04-24 12:46 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0006_party_count_party_sold_party_sold_price_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="courierhistory", + name="courier_id", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="courier_histories", + to="eggs.courier", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0007_orderitems_order_id.py b/core/apps/eggs/migrations/0007_orderitems_order_id.py new file mode 100644 index 0000000..44197a8 --- /dev/null +++ b/core/apps/eggs/migrations/0007_orderitems_order_id.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.4 on 2024-04-24 10:51 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0006_party_count_party_sold_party_sold_price_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="orderitems", + name="order_id", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + related_name="order_items", + to="eggs.order", + ), + preserve_default=False, + ), + ] diff --git a/core/apps/eggs/migrations/0008_merge_20240424_1752.py b/core/apps/eggs/migrations/0008_merge_20240424_1752.py new file mode 100644 index 0000000..1657932 --- /dev/null +++ b/core/apps/eggs/migrations/0008_merge_20240424_1752.py @@ -0,0 +1,13 @@ +# Generated by Django 5.0.4 on 2024-04-24 12:52 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0007_alter_courierhistory_courier_id"), + ("eggs", "0007_orderitems_order_id"), + ] + + operations = [] diff --git a/core/apps/eggs/migrations/0009_alter_party_sold.py b/core/apps/eggs/migrations/0009_alter_party_sold.py new file mode 100644 index 0000000..fb4cdee --- /dev/null +++ b/core/apps/eggs/migrations/0009_alter_party_sold.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.4 on 2024-05-01 06:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0008_merge_20240424_1752"), + ] + + operations = [ + migrations.AlterField( + model_name="party", + name="sold", + field=models.IntegerField(), + ), + ] diff --git a/core/apps/eggs/migrations/0010_orderitems_sale_type_alter_order_price_and_more.py b/core/apps/eggs/migrations/0010_orderitems_sale_type_alter_order_price_and_more.py new file mode 100644 index 0000000..75dc2fa --- /dev/null +++ b/core/apps/eggs/migrations/0010_orderitems_sale_type_alter_order_price_and_more.py @@ -0,0 +1,61 @@ +# Generated by Django 5.0.4 on 2024-05-02 11:42 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0009_alter_party_sold"), + ] + + operations = [ + migrations.AddField( + model_name="orderitems", + name="sale_type", + field=models.CharField( + choices=[("optom", "Optom"), ("dona", "Dona")], + default=1, + max_length=10, + ), + preserve_default=False, + ), + migrations.AlterField( + model_name="order", + name="price", + field=models.DecimalField( + decimal_places=2, default=0.0, max_digits=10 + ), + ), + migrations.AlterField( + model_name="order", + name="price_paid", + field=models.DecimalField( + decimal_places=2, default=0.0, max_digits=10 + ), + ), + migrations.AlterField( + model_name="party", + name="count", + field=models.IntegerField(default=0), + ), + migrations.AlterField( + model_name="party", + name="price", + field=models.DecimalField( + decimal_places=2, default=0, max_digits=10 + ), + ), + migrations.AlterField( + model_name="party", + name="sold", + field=models.IntegerField(default=0), + ), + migrations.AlterField( + model_name="party", + name="sold_price", + field=models.DecimalField( + decimal_places=2, default=0, max_digits=10 + ), + ), + ] diff --git a/core/apps/eggs/migrations/0011_alter_order_courier_id.py b/core/apps/eggs/migrations/0011_alter_order_courier_id.py new file mode 100644 index 0000000..1082e0d --- /dev/null +++ b/core/apps/eggs/migrations/0011_alter_order_courier_id.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.4 on 2024-05-02 12:08 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0010_orderitems_sale_type_alter_order_price_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="order", + name="courier_id", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="orders", + to="eggs.courier", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0012_alter_order_data.py b/core/apps/eggs/migrations/0012_alter_order_data.py new file mode 100644 index 0000000..85b8c8e --- /dev/null +++ b/core/apps/eggs/migrations/0012_alter_order_data.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.4 on 2024-05-03 07:21 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0011_alter_order_courier_id"), + ] + + operations = [ + migrations.AlterField( + model_name="order", + name="data", + field=models.DateField(), + ), + ] diff --git a/core/apps/eggs/migrations/0013_alter_order_data.py b/core/apps/eggs/migrations/0013_alter_order_data.py new file mode 100644 index 0000000..be73351 --- /dev/null +++ b/core/apps/eggs/migrations/0013_alter_order_data.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.4 on 2024-05-03 07:22 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0012_alter_order_data"), + ] + + operations = [ + migrations.AlterField( + model_name="order", + name="data", + field=models.DateTimeField(auto_now_add=True), + ), + ] diff --git a/core/apps/eggs/migrations/0014_group_date_group_name.py b/core/apps/eggs/migrations/0014_group_date_group_name.py new file mode 100644 index 0000000..449b781 --- /dev/null +++ b/core/apps/eggs/migrations/0014_group_date_group_name.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.4 on 2024-05-03 10:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0013_alter_order_data"), + ] + + operations = [ + migrations.AddField( + model_name="group", + name="date", + field=models.DateTimeField(auto_now=True), + ), + migrations.AddField( + model_name="group", + name="name", + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/core/apps/eggs/migrations/0015_alter_group_party_id.py b/core/apps/eggs/migrations/0015_alter_group_party_id.py new file mode 100644 index 0000000..c050e17 --- /dev/null +++ b/core/apps/eggs/migrations/0015_alter_group_party_id.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.4 on 2024-05-03 12:27 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0014_group_date_group_name"), + ] + + operations = [ + migrations.AlterField( + model_name="group", + name="party_id", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="groups", + to="eggs.party", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0016_market_company_name.py b/core/apps/eggs/migrations/0016_market_company_name.py new file mode 100644 index 0000000..3eb92e6 --- /dev/null +++ b/core/apps/eggs/migrations/0016_market_company_name.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0.4 on 2024-05-07 12:28 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0015_alter_group_party_id"), + ] + + operations = [ + migrations.AddField( + model_name="market", + name="company_name", + field=models.CharField(default=1, max_length=255), + preserve_default=False, + ), + ] diff --git a/core/apps/eggs/migrations/0017_alter_location_lat_alter_location_long.py b/core/apps/eggs/migrations/0017_alter_location_lat_alter_location_long.py new file mode 100644 index 0000000..d1d6947 --- /dev/null +++ b/core/apps/eggs/migrations/0017_alter_location_lat_alter_location_long.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.4 on 2024-05-07 12:56 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0016_market_company_name"), + ] + + operations = [ + migrations.AlterField( + model_name="location", + name="lat", + field=models.BigIntegerField(blank=True, null=True), + ), + migrations.AlterField( + model_name="location", + name="long", + field=models.BigIntegerField(blank=True, null=True), + ), + ] diff --git a/core/apps/eggs/migrations/0018_alter_order_market_id.py b/core/apps/eggs/migrations/0018_alter_order_market_id.py new file mode 100644 index 0000000..6423168 --- /dev/null +++ b/core/apps/eggs/migrations/0018_alter_order_market_id.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.4 on 2024-05-08 09:24 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0017_alter_location_lat_alter_location_long"), + ] + + operations = [ + migrations.AlterField( + model_name="order", + name="market_id", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="orders", + to="eggs.market", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0019_alter_orderitems_courier_product_id.py b/core/apps/eggs/migrations/0019_alter_orderitems_courier_product_id.py new file mode 100644 index 0000000..2fb6274 --- /dev/null +++ b/core/apps/eggs/migrations/0019_alter_orderitems_courier_product_id.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.6 on 2024-05-08 12:14 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0018_alter_order_market_id"), + ] + + operations = [ + migrations.AlterField( + model_name="orderitems", + name="courier_product_id", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="eggs.courierproduct", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0020_alter_location_lat_alter_location_long.py b/core/apps/eggs/migrations/0020_alter_location_lat_alter_location_long.py new file mode 100644 index 0000000..e765419 --- /dev/null +++ b/core/apps/eggs/migrations/0020_alter_location_lat_alter_location_long.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.6 on 2024-05-08 12:32 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0019_alter_orderitems_courier_product_id"), + ] + + operations = [ + migrations.AlterField( + model_name="location", + name="lat", + field=models.DecimalField( + blank=True, decimal_places=25, max_digits=35, null=True + ), + ), + migrations.AlterField( + model_name="location", + name="long", + field=models.DecimalField( + blank=True, decimal_places=25, max_digits=35, null=True + ), + ), + ] diff --git a/core/apps/eggs/migrations/0021_alter_location_lat_alter_location_long.py b/core/apps/eggs/migrations/0021_alter_location_lat_alter_location_long.py new file mode 100644 index 0000000..cf26af6 --- /dev/null +++ b/core/apps/eggs/migrations/0021_alter_location_lat_alter_location_long.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.6 on 2024-05-08 12:33 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0020_alter_location_lat_alter_location_long"), + ] + + operations = [ + migrations.AlterField( + model_name="location", + name="lat", + field=models.BigIntegerField(blank=True, null=True), + ), + migrations.AlterField( + model_name="location", + name="long", + field=models.BigIntegerField(blank=True, null=True), + ), + ] diff --git a/core/apps/eggs/migrations/0022_alter_location_lat_alter_location_long.py b/core/apps/eggs/migrations/0022_alter_location_lat_alter_location_long.py new file mode 100644 index 0000000..b110a7e --- /dev/null +++ b/core/apps/eggs/migrations/0022_alter_location_lat_alter_location_long.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.6 on 2024-05-08 12:35 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0021_alter_location_lat_alter_location_long"), + ] + + operations = [ + migrations.AlterField( + model_name="location", + name="lat", + field=models.FloatField(blank=True, null=True), + ), + migrations.AlterField( + model_name="location", + name="long", + field=models.FloatField(blank=True, null=True), + ), + ] diff --git a/core/apps/eggs/migrations/0023_broken.py b/core/apps/eggs/migrations/0023_broken.py new file mode 100644 index 0000000..6bfaedf --- /dev/null +++ b/core/apps/eggs/migrations/0023_broken.py @@ -0,0 +1,42 @@ +# Generated by Django 5.0.6 on 2024-05-08 13:58 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0022_alter_location_lat_alter_location_long"), + ] + + operations = [ + migrations.CreateModel( + name="Broken", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("comment", models.TextField(verbose_name="Comment")), + ("quantity", models.IntegerField(verbose_name="Quantity")), + ( + "product", + models.ManyToManyField( + related_name="brokens", to="eggs.product" + ), + ), + ], + options={ + "verbose_name": "Broken", + "verbose_name_plural": "Brokens", + "db_table": "broken", + }, + ), + ] diff --git a/core/apps/eggs/migrations/0024_broken_group.py b/core/apps/eggs/migrations/0024_broken_group.py new file mode 100644 index 0000000..151c8eb --- /dev/null +++ b/core/apps/eggs/migrations/0024_broken_group.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.6 on 2024-05-08 14:28 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0023_broken"), + ] + + operations = [ + migrations.AddField( + model_name="broken", + name="group", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + related_name="brokens", + to="eggs.group", + ), + preserve_default=False, + ), + ] diff --git a/core/apps/eggs/migrations/0025_remove_broken_product.py b/core/apps/eggs/migrations/0025_remove_broken_product.py new file mode 100644 index 0000000..9f616dd --- /dev/null +++ b/core/apps/eggs/migrations/0025_remove_broken_product.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.6 on 2024-05-08 14:50 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0024_broken_group"), + ] + + operations = [ + migrations.RemoveField( + model_name="broken", + name="product", + ), + ] diff --git a/core/apps/eggs/migrations/0026_market_debt_paid_market_debt_unpaid.py b/core/apps/eggs/migrations/0026_market_debt_paid_market_debt_unpaid.py new file mode 100644 index 0000000..4aa6c2f --- /dev/null +++ b/core/apps/eggs/migrations/0026_market_debt_paid_market_debt_unpaid.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.6 on 2024-05-10 11:43 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0025_remove_broken_product"), + ] + + operations = [ + migrations.AddField( + model_name="market", + name="debt_paid", + field=models.DecimalField( + decimal_places=2, default=0, max_digits=10 + ), + ), + migrations.AddField( + model_name="market", + name="debt_unpaid", + field=models.DecimalField( + decimal_places=2, default=0, max_digits=10 + ), + ), + ] diff --git a/core/apps/eggs/migrations/0027_remove_orderitems_product_id_orderitems_group_id_and_more.py b/core/apps/eggs/migrations/0027_remove_orderitems_product_id_orderitems_group_id_and_more.py new file mode 100644 index 0000000..dbc6251 --- /dev/null +++ b/core/apps/eggs/migrations/0027_remove_orderitems_product_id_orderitems_group_id_and_more.py @@ -0,0 +1,53 @@ +# Generated by Django 5.0.6 on 2024-05-10 12:27 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0026_market_debt_paid_market_debt_unpaid"), + ] + + operations = [ + migrations.RemoveField( + model_name="orderitems", + name="product_id", + ), + migrations.AddField( + model_name="orderitems", + name="group_id", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + to="eggs.group", + ), + preserve_default=False, + ), + migrations.CreateModel( + name="Debt", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "debt_price", + models.DecimalField(decimal_places=2, max_digits=10), + ), + ( + "market", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="eggs.market", + ), + ), + ], + ), + ] diff --git a/core/apps/eggs/migrations/0028_alter_orderitems_discount.py b/core/apps/eggs/migrations/0028_alter_orderitems_discount.py new file mode 100644 index 0000000..0cb9058 --- /dev/null +++ b/core/apps/eggs/migrations/0028_alter_orderitems_discount.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.6 on 2024-05-10 13:47 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "eggs", + "0027_remove_orderitems_product_id_orderitems_group_id_and_more", + ), + ] + + operations = [ + migrations.AlterField( + model_name="orderitems", + name="discount", + field=models.IntegerField(blank=True, default=0, null=True), + ), + ] diff --git a/core/apps/eggs/migrations/0029_order_debt.py b/core/apps/eggs/migrations/0029_order_debt.py new file mode 100644 index 0000000..6fd268b --- /dev/null +++ b/core/apps/eggs/migrations/0029_order_debt.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.6 on 2024-05-10 14:37 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0028_alter_orderitems_discount"), + ] + + operations = [ + migrations.AddField( + model_name="order", + name="debt", + field=models.DecimalField( + decimal_places=2, default=0.0, max_digits=10 + ), + ), + ] diff --git a/core/apps/eggs/migrations/0030_alter_courierproduct_count_alter_group_broken_eggs_and_more.py b/core/apps/eggs/migrations/0030_alter_courierproduct_count_alter_group_broken_eggs_and_more.py new file mode 100644 index 0000000..c812282 --- /dev/null +++ b/core/apps/eggs/migrations/0030_alter_courierproduct_count_alter_group_broken_eggs_and_more.py @@ -0,0 +1,94 @@ +# Generated by Django 5.0.6 on 2024-05-11 07:58 + +import django.core.validators +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("contenttypes", "0002_remove_content_type_name"), + ("eggs", "0029_order_debt"), + ] + + operations = [ + migrations.AlterField( + model_name="courierproduct", + name="count", + field=models.PositiveIntegerField(), + ), + migrations.AlterField( + model_name="group", + name="broken_eggs", + field=models.IntegerField( + validators=[django.core.validators.MinValueValidator(0)] + ), + ), + migrations.AlterField( + model_name="group", + name="quantity", + field=models.IntegerField( + validators=[django.core.validators.MinValueValidator(0)] + ), + ), + migrations.AlterField( + model_name="market", + name="debt_paid", + field=models.DecimalField( + decimal_places=2, + default=0, + max_digits=10, + validators=[django.core.validators.MinValueValidator(0)], + ), + ), + migrations.AlterField( + model_name="market", + name="debt_unpaid", + field=models.DecimalField( + decimal_places=2, + default=0, + max_digits=10, + validators=[django.core.validators.MinValueValidator(0)], + ), + ), + migrations.CreateModel( + name="History", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("object_id", models.PositiveIntegerField()), + ("action", models.CharField(max_length=255)), + ("timestamp", models.DateTimeField(auto_now_add=True)), + ( + "created_by", + models.CharField(blank=True, max_length=255, null=True), + ), + ( + "created_who", + models.CharField(blank=True, max_length=255, null=True), + ), + ( + "reason", + models.CharField(blank=True, max_length=255, null=True), + ), + ( + "content_type", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + ], + options={ + "ordering": ["-timestamp"], + }, + ), + ] diff --git a/core/apps/eggs/migrations/0031_broken_user_id.py b/core/apps/eggs/migrations/0031_broken_user_id.py new file mode 100644 index 0000000..e7ee56a --- /dev/null +++ b/core/apps/eggs/migrations/0031_broken_user_id.py @@ -0,0 +1,30 @@ +# Generated by Django 5.0.6 on 2024-05-11 09:51 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "eggs", + "0030_alter_courierproduct_count_alter_group_broken_eggs_and_more", + ), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name="broken", + name="user_id", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + related_name="broken_eggs", + to=settings.AUTH_USER_MODEL, + ), + preserve_default=False, + ), + ] diff --git a/core/apps/eggs/migrations/0032_history_avatar.py b/core/apps/eggs/migrations/0032_history_avatar.py new file mode 100644 index 0000000..87bf03a --- /dev/null +++ b/core/apps/eggs/migrations/0032_history_avatar.py @@ -0,0 +1,20 @@ +# Generated by Django 5.0.6 on 2024-05-11 10:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0031_broken_user_id"), + ] + + operations = [ + migrations.AddField( + model_name="history", + name="avatar", + field=models.ImageField( + blank=True, null=True, upload_to="history_avatars/" + ), + ), + ] diff --git a/core/apps/eggs/migrations/0033_alter_broken_user_id.py b/core/apps/eggs/migrations/0033_alter_broken_user_id.py new file mode 100644 index 0000000..8c66f64 --- /dev/null +++ b/core/apps/eggs/migrations/0033_alter_broken_user_id.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.6 on 2024-05-11 11:23 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0032_history_avatar"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name="broken", + name="user_id", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="broken_eggs", + to=settings.AUTH_USER_MODEL, + ), + ), + ] diff --git a/core/apps/eggs/migrations/0034_allhistory.py b/core/apps/eggs/migrations/0034_allhistory.py new file mode 100644 index 0000000..3962565 --- /dev/null +++ b/core/apps/eggs/migrations/0034_allhistory.py @@ -0,0 +1,54 @@ +# Generated by Django 5.0.6 on 2024-05-11 13:01 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("contenttypes", "0002_remove_content_type_name"), + ("eggs", "0033_alter_broken_user_id"), + ] + + operations = [ + migrations.CreateModel( + name="AllHistory", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("object_id", models.PositiveIntegerField()), + ( + "action", + models.CharField(blank=True, max_length=255, null=True), + ), + ("timestamp", models.DateTimeField(auto_now_add=True)), + ( + "created_by", + models.CharField(blank=True, max_length=255, null=True), + ), + ( + "created_who", + models.CharField(blank=True, max_length=255, null=True), + ), + ("reason", models.TextField(blank=True, null=True)), + ( + "content_type", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + ], + options={ + "ordering": ["-timestamp"], + }, + ), + ] diff --git a/core/apps/eggs/migrations/0035_history_comment.py b/core/apps/eggs/migrations/0035_history_comment.py new file mode 100644 index 0000000..d2284cc --- /dev/null +++ b/core/apps/eggs/migrations/0035_history_comment.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-05-11 14:18 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0034_allhistory"), + ] + + operations = [ + migrations.AddField( + model_name="history", + name="comment", + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/core/apps/eggs/migrations/0036_courierproduct_return_eggs_and_more.py b/core/apps/eggs/migrations/0036_courierproduct_return_eggs_and_more.py new file mode 100644 index 0000000..9046efa --- /dev/null +++ b/core/apps/eggs/migrations/0036_courierproduct_return_eggs_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.0.6 on 2024-05-15 10:43 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0035_history_comment"), + ] + + operations = [ + migrations.AddField( + model_name="courierproduct", + name="return_eggs", + field=models.IntegerField( + blank=True, + default=0, + null=True, + validators=[django.core.validators.MinValueValidator(0)], + verbose_name="Return eggs", + ), + ), + migrations.AlterField( + model_name="courierproduct", + name="count", + field=models.IntegerField( + validators=[django.core.validators.MinValueValidator(0)], + verbose_name="Count of eggs", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0037_courierhistory_courier_product_id.py b/core/apps/eggs/migrations/0037_courierhistory_courier_product_id.py new file mode 100644 index 0000000..057a157 --- /dev/null +++ b/core/apps/eggs/migrations/0037_courierhistory_courier_product_id.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.6 on 2024-05-16 06:32 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0036_courierproduct_return_eggs_and_more"), + ] + + operations = [ + migrations.AddField( + model_name="courierhistory", + name="courier_product_id", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="courier_histories", + to="eggs.courierproduct", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0038_alter_courierhistory_courier_product_id.py b/core/apps/eggs/migrations/0038_alter_courierhistory_courier_product_id.py new file mode 100644 index 0000000..b05286d --- /dev/null +++ b/core/apps/eggs/migrations/0038_alter_courierhistory_courier_product_id.py @@ -0,0 +1,25 @@ +# Generated by Django 5.0.6 on 2024-05-16 13:29 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0037_courierhistory_courier_product_id"), + ] + + operations = [ + migrations.AlterField( + model_name="courierhistory", + name="courier_product_id", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.DO_NOTHING, + related_name="courier_histories", + to="eggs.courierproduct", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0039_alter_courierhistory_courier_product_id_and_more.py b/core/apps/eggs/migrations/0039_alter_courierhistory_courier_product_id_and_more.py new file mode 100644 index 0000000..eb8febd --- /dev/null +++ b/core/apps/eggs/migrations/0039_alter_courierhistory_courier_product_id_and_more.py @@ -0,0 +1,33 @@ +# Generated by Django 5.0.6 on 2024-05-16 13:37 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0038_alter_courierhistory_courier_product_id"), + ] + + operations = [ + migrations.AlterField( + model_name="courierhistory", + name="courier_product_id", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="courier_histories", + to="eggs.courierproduct", + ), + ), + migrations.AlterField( + model_name="courierhistory", + name="group_id", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.SET_NULL, + to="eggs.group", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0040_alter_orderitems_discount.py b/core/apps/eggs/migrations/0040_alter_orderitems_discount.py new file mode 100644 index 0000000..d2cbec0 --- /dev/null +++ b/core/apps/eggs/migrations/0040_alter_orderitems_discount.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.6 on 2024-06-19 10:40 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0039_alter_courierhistory_courier_product_id_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="orderitems", + name="discount", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=5, + null=True, + ), + ), + ] diff --git a/core/apps/eggs/migrations/0041_alter_orderitems_discount.py b/core/apps/eggs/migrations/0041_alter_orderitems_discount.py new file mode 100644 index 0000000..bbcc84b --- /dev/null +++ b/core/apps/eggs/migrations/0041_alter_orderitems_discount.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-06-20 06:52 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0040_alter_orderitems_discount"), + ] + + operations = [ + migrations.AlterField( + model_name="orderitems", + name="discount", + field=models.IntegerField(blank=True, default=0, null=True), + ), + ] diff --git a/core/apps/eggs/migrations/0042_alter_order_location_id.py b/core/apps/eggs/migrations/0042_alter_order_location_id.py new file mode 100644 index 0000000..a0a92f4 --- /dev/null +++ b/core/apps/eggs/migrations/0042_alter_order_location_id.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.6 on 2024-06-21 10:53 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0041_alter_orderitems_discount"), + ] + + operations = [ + migrations.AlterField( + model_name="order", + name="location_id", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="eggs.location", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0043_alter_orderitems_group_id.py b/core/apps/eggs/migrations/0043_alter_orderitems_group_id.py new file mode 100644 index 0000000..b74b055 --- /dev/null +++ b/core/apps/eggs/migrations/0043_alter_orderitems_group_id.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.6 on 2024-06-21 10:58 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0042_alter_order_location_id"), + ] + + operations = [ + migrations.AlterField( + model_name="orderitems", + name="group_id", + field=models.ForeignKey( + null=True, + on_delete=django.db.models.deletion.CASCADE, + to="eggs.group", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0044_additionalcost.py b/core/apps/eggs/migrations/0044_additionalcost.py new file mode 100644 index 0000000..a4ea332 --- /dev/null +++ b/core/apps/eggs/migrations/0044_additionalcost.py @@ -0,0 +1,64 @@ +# Generated by Django 5.0.6 on 2024-06-26 09:41 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0043_alter_orderitems_group_id"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="AdditionalCost", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "name", + models.CharField(max_length=255, verbose_name="Name"), + ), + ( + "price", + models.DecimalField( + decimal_places=2, max_digits=10, verbose_name="Price" + ), + ), + ( + "created_at", + models.DateTimeField( + auto_now_add=True, verbose_name="Created at" + ), + ), + ( + "updated_at", + models.DateTimeField( + auto_now=True, verbose_name="Updated at" + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="User", + ), + ), + ], + options={ + "verbose_name": "Additional cost", + "verbose_name_plural": "Additional costs", + }, + ), + ] diff --git a/core/apps/eggs/migrations/0045_alter_additionalcost_user.py b/core/apps/eggs/migrations/0045_alter_additionalcost_user.py new file mode 100644 index 0000000..145eb72 --- /dev/null +++ b/core/apps/eggs/migrations/0045_alter_additionalcost_user.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.6 on 2024-06-26 09:57 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0044_additionalcost"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name="additionalcost", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + to=settings.AUTH_USER_MODEL, + verbose_name="User", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0046_remove_additionalcost_name_additionalcost_reason.py b/core/apps/eggs/migrations/0046_remove_additionalcost_name_additionalcost_reason.py new file mode 100644 index 0000000..6d45314 --- /dev/null +++ b/core/apps/eggs/migrations/0046_remove_additionalcost_name_additionalcost_reason.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.6 on 2024-06-26 10:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0045_alter_additionalcost_user"), + ] + + operations = [ + migrations.RemoveField( + model_name="additionalcost", + name="name", + ), + migrations.AddField( + model_name="additionalcost", + name="reason", + field=models.TextField(default=1, verbose_name="Name"), + preserve_default=False, + ), + ] diff --git a/core/apps/eggs/migrations/0047_alter_orderitems_discount.py b/core/apps/eggs/migrations/0047_alter_orderitems_discount.py new file mode 100644 index 0000000..fb3cfc3 --- /dev/null +++ b/core/apps/eggs/migrations/0047_alter_orderitems_discount.py @@ -0,0 +1,24 @@ +# Generated by Django 5.0.6 on 2024-06-26 12:00 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0046_remove_additionalcost_name_additionalcost_reason"), + ] + + operations = [ + migrations.AlterField( + model_name="orderitems", + name="discount", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0.0, + max_digits=10, + null=True, + ), + ), + ] diff --git a/core/apps/eggs/migrations/0048_order_count.py b/core/apps/eggs/migrations/0048_order_count.py new file mode 100644 index 0000000..a6c9d80 --- /dev/null +++ b/core/apps/eggs/migrations/0048_order_count.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.6 on 2024-06-27 06:50 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0047_alter_orderitems_discount"), + ] + + operations = [ + migrations.AddField( + model_name="order", + name="count", + field=models.IntegerField(blank=True, null=True), + ), + ] diff --git a/core/apps/eggs/migrations/0049_sklad.py b/core/apps/eggs/migrations/0049_sklad.py new file mode 100644 index 0000000..93b9e9b --- /dev/null +++ b/core/apps/eggs/migrations/0049_sklad.py @@ -0,0 +1,44 @@ +# Generated by Django 5.0.6 on 2024-06-27 12:49 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0048_order_count"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Sklad", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "user_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="sklad", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "verbose_name_plural": "Sklad", + "db_table": "sklad", + }, + ), + ] diff --git a/core/apps/eggs/migrations/0050_alter_order_debt_alter_order_price_paid_and_more.py b/core/apps/eggs/migrations/0050_alter_order_debt_alter_order_price_paid_and_more.py new file mode 100644 index 0000000..0f520b3 --- /dev/null +++ b/core/apps/eggs/migrations/0050_alter_order_debt_alter_order_price_paid_and_more.py @@ -0,0 +1,34 @@ +# Generated by Django 5.0.6 on 2024-06-28 05:16 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0049_sklad"), + ] + + operations = [ + migrations.AlterField( + model_name="order", + name="debt", + field=models.DecimalField( + blank=True, decimal_places=2, max_digits=10, null=True + ), + ), + migrations.AlterField( + model_name="order", + name="price_paid", + field=models.DecimalField( + blank=True, decimal_places=2, max_digits=10, null=True + ), + ), + migrations.AlterField( + model_name="orderitems", + name="discount", + field=models.DecimalField( + blank=True, decimal_places=2, max_digits=10, null=True + ), + ), + ] diff --git a/core/apps/eggs/migrations/0051_alter_group_broken_eggs_alter_group_quantity.py b/core/apps/eggs/migrations/0051_alter_group_broken_eggs_alter_group_quantity.py new file mode 100644 index 0000000..ec03bec --- /dev/null +++ b/core/apps/eggs/migrations/0051_alter_group_broken_eggs_alter_group_quantity.py @@ -0,0 +1,30 @@ +# Generated by Django 5.0.6 on 2024-06-28 05:36 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0050_alter_order_debt_alter_order_price_paid_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="group", + name="broken_eggs", + field=models.IntegerField( + default=0, + validators=[django.core.validators.MinValueValidator(0)], + ), + ), + migrations.AlterField( + model_name="group", + name="quantity", + field=models.IntegerField( + default=0, + validators=[django.core.validators.MinValueValidator(0)], + ), + ), + ] diff --git a/core/apps/eggs/migrations/0052_alter_order_status.py b/core/apps/eggs/migrations/0052_alter_order_status.py new file mode 100644 index 0000000..6727c75 --- /dev/null +++ b/core/apps/eggs/migrations/0052_alter_order_status.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.6 on 2024-06-28 09:44 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0051_alter_group_broken_eggs_alter_group_quantity"), + ] + + operations = [ + migrations.AlterField( + model_name="order", + name="status", + field=models.CharField( + choices=[ + ("pending", "Pending"), + ("delivery", "Delivery"), + ("success", "Success"), + ("done", "Done"), + ("cancel", "Cancel"), + ], + default="pending", + max_length=255, + ), + ), + ] diff --git a/core/apps/eggs/migrations/0053_order_status_was.py b/core/apps/eggs/migrations/0053_order_status_was.py new file mode 100644 index 0000000..0e8dfd8 --- /dev/null +++ b/core/apps/eggs/migrations/0053_order_status_was.py @@ -0,0 +1,29 @@ +# Generated by Django 5.0.6 on 2024-06-28 10:10 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0052_alter_order_status"), + ] + + operations = [ + migrations.AddField( + model_name="order", + name="status_was", + field=models.CharField( + blank=True, + choices=[ + ("pending", "Pending"), + ("delivery", "Delivery"), + ("success", "Success"), + ("done", "Done"), + ("cancel", "Cancel"), + ], + max_length=255, + null=True, + ), + ), + ] diff --git a/core/apps/eggs/migrations/0054_remove_order_status_was.py b/core/apps/eggs/migrations/0054_remove_order_status_was.py new file mode 100644 index 0000000..d6688e4 --- /dev/null +++ b/core/apps/eggs/migrations/0054_remove_order_status_was.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.6 on 2024-06-28 10:13 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0053_order_status_was"), + ] + + operations = [ + migrations.RemoveField( + model_name="order", + name="status_was", + ), + ] diff --git a/core/apps/eggs/migrations/0055_history_user_id.py b/core/apps/eggs/migrations/0055_history_user_id.py new file mode 100644 index 0000000..b7e2686 --- /dev/null +++ b/core/apps/eggs/migrations/0055_history_user_id.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.6 on 2024-06-28 11:47 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0054_remove_order_status_was"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name="history", + name="user_id", + field=models.ForeignKey( + default=1, + on_delete=django.db.models.deletion.CASCADE, + related_name="history", + to=settings.AUTH_USER_MODEL, + ), + preserve_default=False, + ), + ] diff --git a/core/apps/eggs/migrations/0056_alter_order_count_alter_order_debt_and_more.py b/core/apps/eggs/migrations/0056_alter_order_count_alter_order_debt_and_more.py new file mode 100644 index 0000000..713e128 --- /dev/null +++ b/core/apps/eggs/migrations/0056_alter_order_count_alter_order_debt_and_more.py @@ -0,0 +1,40 @@ +# Generated by Django 5.0.6 on 2024-07-01 07:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0055_history_user_id"), + ] + + operations = [ + migrations.AlterField( + model_name="order", + name="count", + field=models.IntegerField(blank=True, default=0, null=True), + ), + migrations.AlterField( + model_name="order", + name="debt", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=10, + null=True, + ), + ), + migrations.AlterField( + model_name="order", + name="price_paid", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=10, + null=True, + ), + ), + ] diff --git a/core/apps/eggs/migrations/0057_rename_price_party_benefit_and_more.py b/core/apps/eggs/migrations/0057_rename_price_party_benefit_and_more.py new file mode 100644 index 0000000..e884830 --- /dev/null +++ b/core/apps/eggs/migrations/0057_rename_price_party_benefit_and_more.py @@ -0,0 +1,59 @@ +# Generated by Django 5.0.6 on 2024-07-04 05:35 + +import django.core.validators +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0056_alter_order_count_alter_order_debt_and_more"), + ] + + operations = [ + migrations.RenameField( + model_name="party", + old_name="price", + new_name="benefit", + ), + migrations.RenameField( + model_name="party", + old_name="sold_price", + new_name="cost", + ), + migrations.RemoveField( + model_name="party", + name="sold", + ), + migrations.AddField( + model_name="party", + name="remaining_count", + field=models.IntegerField( + default=0, + verbose_name=django.core.validators.MinValueValidator(0), + ), + ), + migrations.AddField( + model_name="party", + name="sold_count", + field=models.IntegerField( + default=0, + verbose_name=django.core.validators.MinValueValidator(0), + ), + ), + migrations.AddField( + model_name="party", + name="total_cost", + field=models.DecimalField( + decimal_places=2, default=0, max_digits=10 + ), + ), + migrations.AlterField( + model_name="party", + name="count", + field=models.IntegerField( + default=0, + verbose_name=django.core.validators.MinValueValidator(0), + ), + ), + ] diff --git a/core/apps/eggs/migrations/0058_alter_party_count_alter_party_remaining_count_and_more.py b/core/apps/eggs/migrations/0058_alter_party_count_alter_party_remaining_count_and_more.py new file mode 100644 index 0000000..9a0571f --- /dev/null +++ b/core/apps/eggs/migrations/0058_alter_party_count_alter_party_remaining_count_and_more.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.6 on 2024-07-06 05:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0057_rename_price_party_benefit_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="party", + name="count", + field=models.IntegerField(default=0), + ), + migrations.AlterField( + model_name="party", + name="remaining_count", + field=models.IntegerField(default=0), + ), + migrations.AlterField( + model_name="party", + name="sold_count", + field=models.IntegerField(default=0), + ), + ] diff --git a/core/apps/eggs/migrations/0059_party_profit.py b/core/apps/eggs/migrations/0059_party_profit.py new file mode 100644 index 0000000..591d6af --- /dev/null +++ b/core/apps/eggs/migrations/0059_party_profit.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.6 on 2024-07-06 07:31 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "eggs", + "0058_alter_party_count_alter_party_remaining_count_and_more", + ), + ] + + operations = [ + migrations.AddField( + model_name="party", + name="profit", + field=models.DecimalField( + decimal_places=2, default=0, max_digits=10 + ), + ), + ] diff --git a/core/apps/eggs/migrations/0060_alter_party_benefit_alter_party_cost_and_more.py b/core/apps/eggs/migrations/0060_alter_party_benefit_alter_party_cost_and_more.py new file mode 100644 index 0000000..e93bcfe --- /dev/null +++ b/core/apps/eggs/migrations/0060_alter_party_benefit_alter_party_cost_and_more.py @@ -0,0 +1,72 @@ +# Generated by Django 5.0.6 on 2024-07-06 07:45 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0059_party_profit"), + ] + + operations = [ + migrations.AlterField( + model_name="party", + name="benefit", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=10, + null=True, + ), + ), + migrations.AlterField( + model_name="party", + name="cost", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=10, + null=True, + ), + ), + migrations.AlterField( + model_name="party", + name="count", + field=models.IntegerField(blank=True, default=0, null=True), + ), + migrations.AlterField( + model_name="party", + name="profit", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=10, + null=True, + ), + ), + migrations.AlterField( + model_name="party", + name="remaining_count", + field=models.IntegerField(blank=True, default=0, null=True), + ), + migrations.AlterField( + model_name="party", + name="sold_count", + field=models.IntegerField(blank=True, default=0, null=True), + ), + migrations.AlterField( + model_name="party", + name="total_cost", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=10, + null=True, + ), + ), + ] diff --git a/core/apps/eggs/migrations/0061_alter_debt_debt_price_alter_group_entry_price_and_more.py b/core/apps/eggs/migrations/0061_alter_debt_debt_price_alter_group_entry_price_and_more.py new file mode 100644 index 0000000..b718671 --- /dev/null +++ b/core/apps/eggs/migrations/0061_alter_debt_debt_price_alter_group_entry_price_and_more.py @@ -0,0 +1,67 @@ +# Generated by Django 5.0.6 on 2024-07-06 13:15 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0060_alter_party_benefit_alter_party_cost_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="debt", + name="debt_price", + field=models.DecimalField(decimal_places=2, max_digits=20), + ), + migrations.AlterField( + model_name="group", + name="entry_price", + field=models.DecimalField(decimal_places=2, max_digits=20), + ), + migrations.AlterField( + model_name="group", + name="unit_price", + field=models.DecimalField(decimal_places=2, max_digits=20), + ), + migrations.AlterField( + model_name="group", + name="wholesale_price", + field=models.DecimalField(decimal_places=2, max_digits=20), + ), + migrations.AlterField( + model_name="invoice", + name="price", + field=models.DecimalField(decimal_places=2, max_digits=20), + ), + migrations.AlterField( + model_name="order", + name="debt", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=20, + null=True, + ), + ), + migrations.AlterField( + model_name="order", + name="price", + field=models.DecimalField( + decimal_places=2, default=0, max_digits=20 + ), + ), + migrations.AlterField( + model_name="order", + name="price_paid", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=20, + null=True, + ), + ), + ] diff --git a/core/apps/eggs/migrations/0062_alter_party_benefit_alter_party_cost_and_more.py b/core/apps/eggs/migrations/0062_alter_party_benefit_alter_party_cost_and_more.py new file mode 100644 index 0000000..9ae40a9 --- /dev/null +++ b/core/apps/eggs/migrations/0062_alter_party_benefit_alter_party_cost_and_more.py @@ -0,0 +1,60 @@ +# Generated by Django 5.0.6 on 2024-07-06 13:17 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "eggs", + "0061_alter_debt_debt_price_alter_group_entry_price_and_more", + ), + ] + + operations = [ + migrations.AlterField( + model_name="party", + name="benefit", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=20, + null=True, + ), + ), + migrations.AlterField( + model_name="party", + name="cost", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=20, + null=True, + ), + ), + migrations.AlterField( + model_name="party", + name="profit", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=20, + null=True, + ), + ), + migrations.AlterField( + model_name="party", + name="total_cost", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=20, + null=True, + ), + ), + ] diff --git a/core/apps/eggs/migrations/0063_alter_debt_debt_price_alter_group_entry_price_and_more.py b/core/apps/eggs/migrations/0063_alter_debt_debt_price_alter_group_entry_price_and_more.py new file mode 100644 index 0000000..4f73b2d --- /dev/null +++ b/core/apps/eggs/migrations/0063_alter_debt_debt_price_alter_group_entry_price_and_more.py @@ -0,0 +1,111 @@ +# Generated by Django 5.0.6 on 2024-07-06 13:19 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0062_alter_party_benefit_alter_party_cost_and_more"), + ] + + operations = [ + migrations.AlterField( + model_name="debt", + name="debt_price", + field=models.DecimalField(decimal_places=2, max_digits=30), + ), + migrations.AlterField( + model_name="group", + name="entry_price", + field=models.DecimalField(decimal_places=2, max_digits=30), + ), + migrations.AlterField( + model_name="group", + name="unit_price", + field=models.DecimalField(decimal_places=2, max_digits=30), + ), + migrations.AlterField( + model_name="group", + name="wholesale_price", + field=models.DecimalField(decimal_places=2, max_digits=30), + ), + migrations.AlterField( + model_name="invoice", + name="price", + field=models.DecimalField(decimal_places=2, max_digits=30), + ), + migrations.AlterField( + model_name="order", + name="debt", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=30, + null=True, + ), + ), + migrations.AlterField( + model_name="order", + name="price", + field=models.DecimalField( + decimal_places=2, default=0, max_digits=30 + ), + ), + migrations.AlterField( + model_name="order", + name="price_paid", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=30, + null=True, + ), + ), + migrations.AlterField( + model_name="party", + name="benefit", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=30, + null=True, + ), + ), + migrations.AlterField( + model_name="party", + name="cost", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=30, + null=True, + ), + ), + migrations.AlterField( + model_name="party", + name="profit", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=30, + null=True, + ), + ), + migrations.AlterField( + model_name="party", + name="total_cost", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=30, + null=True, + ), + ), + ] diff --git a/core/apps/eggs/migrations/0064_order_stock_updated.py b/core/apps/eggs/migrations/0064_order_stock_updated.py new file mode 100644 index 0000000..75975cb --- /dev/null +++ b/core/apps/eggs/migrations/0064_order_stock_updated.py @@ -0,0 +1,21 @@ +# Generated by Django 5.0.6 on 2024-07-08 11:13 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ( + "eggs", + "0063_alter_debt_debt_price_alter_group_entry_price_and_more", + ), + ] + + operations = [ + migrations.AddField( + model_name="order", + name="stock_updated", + field=models.BooleanField(default=False), + ), + ] diff --git a/core/apps/eggs/migrations/0065_remove_order_stock_updated.py b/core/apps/eggs/migrations/0065_remove_order_stock_updated.py new file mode 100644 index 0000000..7e10760 --- /dev/null +++ b/core/apps/eggs/migrations/0065_remove_order_stock_updated.py @@ -0,0 +1,17 @@ +# Generated by Django 5.0.6 on 2024-07-08 11:17 + +from django.db import migrations + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0064_order_stock_updated"), + ] + + operations = [ + migrations.RemoveField( + model_name="order", + name="stock_updated", + ), + ] diff --git a/core/apps/eggs/migrations/0066_debt_created_at_debt_updated_at.py b/core/apps/eggs/migrations/0066_debt_created_at_debt_updated_at.py new file mode 100644 index 0000000..018e721 --- /dev/null +++ b/core/apps/eggs/migrations/0066_debt_created_at_debt_updated_at.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.7 on 2024-07-10 11:25 + +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0065_remove_order_stock_updated"), + ] + + operations = [ + migrations.AddField( + model_name="debt", + name="created_at", + field=models.DateTimeField( + auto_now_add=True, default=django.utils.timezone.now + ), + preserve_default=False, + ), + migrations.AddField( + model_name="debt", + name="updated_at", + field=models.DateTimeField(auto_now=True), + ), + ] diff --git a/core/apps/eggs/migrations/0067_alter_order_debt_alter_order_price_paid.py b/core/apps/eggs/migrations/0067_alter_order_debt_alter_order_price_paid.py new file mode 100644 index 0000000..0386cae --- /dev/null +++ b/core/apps/eggs/migrations/0067_alter_order_debt_alter_order_price_paid.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.7 on 2024-07-20 05:38 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0066_debt_created_at_debt_updated_at"), + ] + + operations = [ + migrations.AlterField( + model_name="order", + name="debt", + field=models.DecimalField( + decimal_places=2, default=0, max_digits=30 + ), + ), + migrations.AlterField( + model_name="order", + name="price_paid", + field=models.DecimalField( + decimal_places=2, default=0, max_digits=30 + ), + ), + ] diff --git a/core/apps/eggs/migrations/0068_alter_market_avatar.py b/core/apps/eggs/migrations/0068_alter_market_avatar.py new file mode 100644 index 0000000..160016b --- /dev/null +++ b/core/apps/eggs/migrations/0068_alter_market_avatar.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.7 on 2024-07-22 13:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0067_alter_order_debt_alter_order_price_paid"), + ] + + operations = [ + migrations.AlterField( + model_name="market", + name="avatar", + field=models.ImageField( + blank=True, + default="market_avatar/default.png", + null=True, + upload_to="market_avatar/", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0069_broken_price_monitoring.py b/core/apps/eggs/migrations/0069_broken_price_monitoring.py new file mode 100644 index 0000000..5c6bc28 --- /dev/null +++ b/core/apps/eggs/migrations/0069_broken_price_monitoring.py @@ -0,0 +1,89 @@ +# Generated by Django 5.0.7 on 2024-07-23 11:35 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("contenttypes", "0002_remove_content_type_name"), + ("eggs", "0068_alter_market_avatar"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AddField( + model_name="broken", + name="price", + field=models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=30, + null=True, + ), + ), + migrations.CreateModel( + name="Monitoring", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("object_id", models.PositiveIntegerField()), + ("action", models.CharField(max_length=255)), + ("timestamp", models.DateTimeField(auto_now_add=True)), + ( + "created_by", + models.CharField(blank=True, max_length=255, null=True), + ), + ( + "created_who", + models.CharField(blank=True, max_length=255, null=True), + ), + ( + "comment", + models.CharField(blank=True, max_length=255, null=True), + ), + ( + "reason", + models.CharField(blank=True, max_length=255, null=True), + ), + ( + "price", + models.DecimalField( + blank=True, + decimal_places=2, + default=0, + max_digits=30, + null=True, + ), + ), + ( + "content_type", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + to="contenttypes.contenttype", + ), + ), + ( + "user_id", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="monitoring", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "ordering": ["-timestamp"], + }, + ), + ] diff --git a/core/apps/eggs/migrations/0070_party_broken_eggs_party_courier_eggs.py b/core/apps/eggs/migrations/0070_party_broken_eggs_party_courier_eggs.py new file mode 100644 index 0000000..5689a3d --- /dev/null +++ b/core/apps/eggs/migrations/0070_party_broken_eggs_party_courier_eggs.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.7 on 2024-07-25 02:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0069_broken_price_monitoring"), + ] + + operations = [ + migrations.AddField( + model_name="party", + name="broken_eggs", + field=models.IntegerField(blank=True, default=0, null=True), + ), + migrations.AddField( + model_name="party", + name="courier_eggs", + field=models.IntegerField(blank=True, default=0, null=True), + ), + ] diff --git a/core/apps/eggs/migrations/0071_notification.py b/core/apps/eggs/migrations/0071_notification.py new file mode 100644 index 0000000..61c97c7 --- /dev/null +++ b/core/apps/eggs/migrations/0071_notification.py @@ -0,0 +1,60 @@ +# Generated by Django 5.1 on 2024-08-29 06:42 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0070_party_broken_eggs_party_courier_eggs"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Notification", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ( + "title", + models.CharField(max_length=255, verbose_name="Title"), + ), + ("body", models.TextField(verbose_name="Body")), + ( + "is_read", + models.BooleanField(default=False, verbose_name="Is read"), + ), + ( + "is_sending", + models.BooleanField( + default=False, verbose_name="Is sending" + ), + ), + ( + "user", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="notifications", + to=settings.AUTH_USER_MODEL, + verbose_name="User", + ), + ), + ], + options={ + "verbose_name": "Notification", + "verbose_name_plural": "Notifications", + }, + ), + ] diff --git a/core/apps/eggs/migrations/0072_alter_notification_user.py b/core/apps/eggs/migrations/0072_alter_notification_user.py new file mode 100644 index 0000000..151f0cd --- /dev/null +++ b/core/apps/eggs/migrations/0072_alter_notification_user.py @@ -0,0 +1,28 @@ +# Generated by Django 5.0.8 on 2024-08-29 08:56 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("eggs", "0071_notification"), + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.AlterField( + model_name="notification", + name="user", + field=models.ForeignKey( + blank=True, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="notifications", + to=settings.AUTH_USER_MODEL, + verbose_name="User", + ), + ), + ] diff --git a/core/apps/eggs/migrations/0073_dailycost.py b/core/apps/eggs/migrations/0073_dailycost.py new file mode 100644 index 0000000..dba4b22 --- /dev/null +++ b/core/apps/eggs/migrations/0073_dailycost.py @@ -0,0 +1,29 @@ +# Generated by Django 5.0.8 on 2024-09-11 04:36 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eggs', '0072_alter_notification_user'), + ] + + operations = [ + migrations.CreateModel( + name='DailyCost', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('created_at', models.DateTimeField(auto_now_add=True)), + ('updated_at', models.DateTimeField(auto_now=True)), + ('first_cost', models.DecimalField(decimal_places=2, max_digits=40)), + ('second_cost', models.DecimalField(decimal_places=2, max_digits=40)), + ('third_cost', models.DecimalField(decimal_places=2, max_digits=40)), + ], + options={ + 'verbose_name': 'Daily Cost', + 'verbose_name_plural': 'Daily Costs', + 'db_table': 'daily_costs', + }, + ), + ] diff --git a/core/apps/eggs/migrations/0074_alter_debt_market.py b/core/apps/eggs/migrations/0074_alter_debt_market.py new file mode 100644 index 0000000..235b0a4 --- /dev/null +++ b/core/apps/eggs/migrations/0074_alter_debt_market.py @@ -0,0 +1,19 @@ +# Generated by Django 5.0.8 on 2025-11-04 09:49 + +import django.db.models.deletion +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eggs', '0073_dailycost'), + ] + + operations = [ + migrations.AlterField( + model_name='debt', + name='market', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='debts', to='eggs.market'), + ), + ] diff --git a/core/apps/eggs/migrations/0075_debt_debt_type.py b/core/apps/eggs/migrations/0075_debt_debt_type.py new file mode 100644 index 0000000..8b0fb12 --- /dev/null +++ b/core/apps/eggs/migrations/0075_debt_debt_type.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.8 on 2025-11-21 11:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eggs', '0074_alter_debt_market'), + ] + + operations = [ + migrations.AddField( + model_name='debt', + name='debt_type', + field=models.CharField(choices=[('added', 'Added'), ('lost', 'Lost')], default='lost', max_length=255), + ), + ] diff --git a/core/apps/eggs/migrations/__init__.py b/core/apps/eggs/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/eggs/models/__init__.py b/core/apps/eggs/models/__init__.py new file mode 100644 index 0000000..6d13ec4 --- /dev/null +++ b/core/apps/eggs/models/__init__.py @@ -0,0 +1,21 @@ +from .additional_cost import * # noqa +from .all_history import * # noqa +from .broken import * # noqa +from .courier import * # noqa +from .courier_history import * # noqa +from .courier_product import * # noqa +from .debt import * # noqa +from .group import * # noqa +from .history import * # noqa +from .invoice import * # noqa +from .location import * # noqa +from .market import * # noqa +from .monitoring import * # noqa +from .order import * # noqa +from .order_item import * # noqa +from .order_item import * # noqa +from .party import * # noqa +from .product import * # noqa +from .sklad import * # noqa +from .notification import * # noqa +from .daily_cost import * # noqa \ No newline at end of file diff --git a/core/apps/eggs/models/additional_cost.py b/core/apps/eggs/models/additional_cost.py new file mode 100644 index 0000000..29840df --- /dev/null +++ b/core/apps/eggs/models/additional_cost.py @@ -0,0 +1,25 @@ +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from core.http.models import User + + +class AdditionalCost(models.Model): + user = models.ForeignKey( + to=User, + verbose_name=_("User"), + on_delete=models.CASCADE, + null=True, + blank=True, + ) + reason = models.TextField(_("Name")) + price = models.DecimalField(_("Price"), max_digits=10, decimal_places=2) + created_at = models.DateTimeField(_("Created at"), auto_now_add=True) + updated_at = models.DateTimeField(_("Updated at"), auto_now=True) + + class Meta: + verbose_name = _("Additional cost") + verbose_name_plural = _("Additional costs") + + def __str__(self): + return self.reason diff --git a/core/apps/eggs/models/all_history.py b/core/apps/eggs/models/all_history.py new file mode 100644 index 0000000..fae4fe6 --- /dev/null +++ b/core/apps/eggs/models/all_history.py @@ -0,0 +1,17 @@ +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType +from django.db import models + + +class AllHistory(models.Model): + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey("content_type", "object_id") + action = models.CharField(max_length=255, null=True, blank=True) + timestamp = models.DateTimeField(auto_now_add=True) + created_by = models.CharField(max_length=255, null=True, blank=True) + created_who = models.CharField(max_length=255, null=True, blank=True) + reason = models.TextField(null=True, blank=True) + + class Meta: + ordering = ["-timestamp"] diff --git a/core/apps/eggs/models/broken.py b/core/apps/eggs/models/broken.py new file mode 100644 index 0000000..5aa489d --- /dev/null +++ b/core/apps/eggs/models/broken.py @@ -0,0 +1,50 @@ +from django.db import models + +from core.apps.eggs.models import group +from core.http.models import AbstractBaseModel +from core.http.models.user import User + + +class Broken(AbstractBaseModel): + user_id = models.ForeignKey( + to=User, + on_delete=models.CASCADE, + related_name="broken_eggs", + null=True, + blank=True, + ) + group = models.ForeignKey( + group.Group, on_delete=models.CASCADE, related_name="brokens" + ) + comment = models.TextField(verbose_name="Comment") + quantity = models.IntegerField(verbose_name="Quantity") + price = models.DecimalField( + max_digits=30, decimal_places=2, default=0, null=True, blank=True + ) + + def save(self, *args, **kwargs): + is_new = self.pk is None + if is_new: + if self.group.quantity < self.quantity: + raise ValueError( + "Quantity cannot be greater than group quantity" + ) + self.group.quantity -= self.quantity + self.group.party_id.remaining_count -= self.quantity + self.group.broken_eggs += self.quantity + self.group.party_id.profit -= ( + self.group.entry_price * self.quantity + ) + self.group.party_id.broken_eggs += self.quantity + self.price = self.group.entry_price * self.quantity + self.group.save() + self.group.party_id.save() + super().save(*args, **kwargs) + + def __str__(self): + return self.comment + + class Meta: + verbose_name = "Broken" + verbose_name_plural = "Brokens" + db_table = "broken" diff --git a/core/apps/eggs/models/courier.py b/core/apps/eggs/models/courier.py new file mode 100644 index 0000000..5a501fa --- /dev/null +++ b/core/apps/eggs/models/courier.py @@ -0,0 +1,21 @@ +""" +Courier model +""" + +from django.db import models +from core.http.models import base +from core.http.models.user import User + + +class Courier(base.AbstractBaseModel): + user_id = models.ForeignKey( + to=User, on_delete=models.CASCADE, related_name="couriers" + ) + + def __str__(self): + return f"Courier - {self.user_id}" + + class Meta: + verbose_name = "Courier" + verbose_name_plural = "Couriers" + db_table = "courier" diff --git a/core/apps/eggs/models/courier_history.py b/core/apps/eggs/models/courier_history.py new file mode 100644 index 0000000..dbad2ec --- /dev/null +++ b/core/apps/eggs/models/courier_history.py @@ -0,0 +1,39 @@ +""" +Couirier History model +""" + +from django.db import models + +from core.apps.eggs.models import courier, courier_product, group +from core.http.models import base + + +class CourierHistory(base.AbstractBaseModel): + courier_id = models.ForeignKey( + to=courier.Courier, + on_delete=models.CASCADE, + related_name="courier_histories", + ) + courier_product_id = models.ForeignKey( + to=courier_product.CourierProduct, + on_delete=models.SET_NULL, + related_name="courier_histories", + null=True, # Allow NULL values + ) + group_id = models.ForeignKey( + to=group.Group, + on_delete=models.SET_NULL, + null=True, # Allow NULL values + ) + get_eggs = models.IntegerField() + return_eggs = models.IntegerField(null=True, blank=True) + broken_eggs = models.IntegerField(null=True, blank=True) + date = models.DateField(auto_now=True, null=True, blank=True) + + def __str__(self) -> str: + return f"{self.courier_id}, {self.group_id}" + + class Meta: + verbose_name = "CourierHistory" + verbose_name_plural = "CourierHistories" + db_table = "courier_history" diff --git a/core/apps/eggs/models/courier_product.py b/core/apps/eggs/models/courier_product.py new file mode 100644 index 0000000..307a798 --- /dev/null +++ b/core/apps/eggs/models/courier_product.py @@ -0,0 +1,42 @@ +""" +Courier Product model +""" + +from django.core.validators import MinValueValidator +from django.db import models + +from core.apps.eggs.models import courier, group +from core.http.models import base + + +class CourierProduct(base.AbstractBaseModel): + group_id = models.ForeignKey( + to=group.Group, on_delete=models.CASCADE, related_name="courier_groups" + ) + count = models.IntegerField( + validators=[MinValueValidator(0)], verbose_name="Count of eggs" + ) + courier_id = models.ForeignKey( + to=courier.Courier, + on_delete=models.CASCADE, + related_name="courier_products", + ) + return_eggs = models.IntegerField( + default=0, + verbose_name="Return eggs", + validators=[MinValueValidator(0)], + null=True, + blank=True, + ) + + def clean(self): + if self.count < 0: + raise ValueError("Count must be greater than 0") + + def __str__(self): + return f"Courier - {self.courier_id}, group_id - {self.group_id}" + + class Meta: + verbose_name = "CourierProduct" + verbose_name_plural = "CourierProducts" + db_table = "courier_product" diff --git a/core/apps/eggs/models/daily_cost.py b/core/apps/eggs/models/daily_cost.py new file mode 100644 index 0000000..8341fb2 --- /dev/null +++ b/core/apps/eggs/models/daily_cost.py @@ -0,0 +1,14 @@ +from django.db import models + +from core.http.models import AbstractBaseModel + + +class DailyCost(AbstractBaseModel): + first_cost = models.DecimalField(max_digits=40, decimal_places=2) + second_cost = models.DecimalField(max_digits=40, decimal_places=2) + third_cost = models.DecimalField(max_digits=40, decimal_places=2) + + class Meta: + verbose_name = "Daily Cost" + verbose_name_plural = "Daily Costs" + db_table = "daily_costs" diff --git a/core/apps/eggs/models/debt.py b/core/apps/eggs/models/debt.py new file mode 100644 index 0000000..f2dadfb --- /dev/null +++ b/core/apps/eggs/models/debt.py @@ -0,0 +1,78 @@ +from django.db import models +from rest_framework.exceptions import ValidationError + +from core.http.models import AbstractBaseModel + + +class DebtTypeEnum(models.TextChoices): + ADDED = "added" + LOST = "lost" + + +class Debt(AbstractBaseModel): + market = models.ForeignKey( + to="Market", on_delete=models.CASCADE, related_name="debts" + ) + debt_price = models.DecimalField(max_digits=30, decimal_places=2) + debt_type = models.CharField( + max_length=255, + choices=DebtTypeEnum.choices, + default=DebtTypeEnum.LOST.value, + ) + + def save(self, *args, **kwargs): + if self.pk is None: + if self.debt_type == DebtTypeEnum.ADDED.value: + # self.market.debt_paid -= self.debt_price + self.market.debt_unpaid += self.debt_price + elif self.debt_type == DebtTypeEnum.LOST.value: + if self.debt_price > self.market.debt_unpaid: + raise ValueError( + "Debt price cannot be greater than unpaid debt." + ) + self.market.debt_paid += self.debt_price + self.market.debt_unpaid -= self.debt_price + else: + raise ValidationError( + detail={"debt_type": "invlaid debt_type"} + ) + self.market.save() + super().save(*args, **kwargs) + + def __str__(self): + return f"{self.debt_price} - {self.market.name}" + + +# from django.db import models +# from django.core.exceptions import ValidationError +# +# +# class Debt(AbstractBaseModel): +# market = models.ForeignKey(to="Market", on_delete=models.CASCADE) +# order = models.ForeignKey(to="Order", on_delete=models.CASCADE) +# debt_price = models.DecimalField(max_digits=30, decimal_places=2) +# +# def save(self, *args, **kwargs): +# if self.pk is None: +# if self.debt_price > self.market.debt_unpaid: +# raise ValidationError("Debt price cannot be greater than unpaid debt.") +# +# self.market.debt_paid += self.debt_price +# self.market.debt_unpaid -= self.debt_price +# self.market.save() +# +# self.order.price_paid += self.debt_price +# self.order.debt -= self.debt_price +# self.order.save() +# +# order_items = OrderItems.objects.filter(order_id=self.order.id) +# for item in order_items: +# party = item.courier_product_id.group_id.party_id +# if party: +# party.benefit += self.debt_price +# party.save() +# +# super().save(*args, **kwargs) +# +# def __str__(self): +# return f"{self.debt_price} - {self.market.name} - {self.order.id}" diff --git a/core/apps/eggs/models/group.py b/core/apps/eggs/models/group.py new file mode 100644 index 0000000..51ea2a2 --- /dev/null +++ b/core/apps/eggs/models/group.py @@ -0,0 +1,52 @@ +""" +Groups model +""" + +from django.core.validators import MinValueValidator +from django.db import models + +from core.apps.eggs.models import party +from core.apps.eggs.models import product +from core.http.models import base + + +class Group(base.AbstractBaseModel): + product_id = models.ForeignKey( + to=product.Product, on_delete=models.CASCADE, related_name="groups" + ) + party_id = models.ForeignKey( + to=party.Party, + on_delete=models.CASCADE, + related_name="groups", + blank=True, + null=True, + ) + entry_price = models.DecimalField(max_digits=30, decimal_places=2) + unit_price = models.DecimalField(max_digits=30, decimal_places=2) + wholesale_price = models.DecimalField(max_digits=30, decimal_places=2) + quantity = models.IntegerField( + validators=[MinValueValidator(0)], default=0 + ) + broken_eggs = models.IntegerField( + validators=[MinValueValidator(0)], default=0 + ) + date = models.DateTimeField(auto_now=True) + name = models.CharField(max_length=255, blank=True, null=True) + + def clean(self): + if self.quantity < 0: + raise ValueError("Quantity can't be negative") + + # def save(self, *args, **kwargs): + # self.party_id.count += self.quantity + # self.party_id.price += self.entry_price * self.quantity + # self.party_id.save() + # super().save(*args, **kwargs) + + def __str__(self) -> str: + return f"{self.product_id}, Quantity - {self.quantity}" + + class Meta: + verbose_name = "Group" + verbose_name_plural = "Groups" + db_table = "group" diff --git a/core/apps/eggs/models/history.py b/core/apps/eggs/models/history.py new file mode 100644 index 0000000..156fb55 --- /dev/null +++ b/core/apps/eggs/models/history.py @@ -0,0 +1,26 @@ +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType +from django.db import models + +from core.http.models import User + + +class History(models.Model): + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey("content_type", "object_id") + action = models.CharField(max_length=255) + user_id = models.ForeignKey( + to=User, on_delete=models.CASCADE, related_name="history" + ) + avatar = models.ImageField( + upload_to="history_avatars/", null=True, blank=True + ) + timestamp = models.DateTimeField(auto_now_add=True) + created_by = models.CharField(max_length=255, null=True, blank=True) + created_who = models.CharField(max_length=255, null=True, blank=True) + comment = models.CharField(max_length=255, null=True, blank=True) + reason = models.CharField(max_length=255, null=True, blank=True) + + class Meta: + ordering = ["-timestamp"] diff --git a/core/apps/eggs/models/invoice.py b/core/apps/eggs/models/invoice.py new file mode 100644 index 0000000..eed7a72 --- /dev/null +++ b/core/apps/eggs/models/invoice.py @@ -0,0 +1,23 @@ +""" +Invoice model +""" + +from django.db import models + +from core.apps.eggs.models import party + + +class Invoice(models.Model): + name = models.CharField(max_length=256) + price = models.DecimalField(max_digits=30, decimal_places=2) + party_id = models.ForeignKey( + to=party.Party, on_delete=models.CASCADE, related_name="invoices" + ) + + def __str__(self) -> str: + return f"{self.name}, {self.price}" + + class Meta: + verbose_name = "Invoice" + verbose_name_plural = "Invoices" + db_table = "invoice" diff --git a/core/apps/eggs/models/location.py b/core/apps/eggs/models/location.py new file mode 100644 index 0000000..58fc555 --- /dev/null +++ b/core/apps/eggs/models/location.py @@ -0,0 +1,21 @@ +""" +Location model +""" + +from django.db import models + +from core.http.models import base + + +class Location(base.AbstractBaseModel): + long = models.FloatField(null=True, blank=True) + lat = models.FloatField(null=True, blank=True) + label = models.TextField() + + def __str__(self) -> str: + return f"{self.long}, {self.lat}" + + class Meta: + verbose_name = "Location" + verbose_name_plural = "Locations" + db_table = "location" diff --git a/core/apps/eggs/models/market.py b/core/apps/eggs/models/market.py new file mode 100644 index 0000000..877e93b --- /dev/null +++ b/core/apps/eggs/models/market.py @@ -0,0 +1,48 @@ +""" +Market model +""" + +from django.core.validators import MinValueValidator +from django.db import models + +from core.apps.eggs.models import location +from core.http.models import base +from core.http.models.user import User + + +class Market(base.AbstractBaseModel): + name = models.CharField(max_length=255) + company_name = models.CharField(max_length=255) + user_id = models.ForeignKey(to=User, on_delete=models.CASCADE) + avatar = models.ImageField( + upload_to="market_avatar/", + blank=True, + null=True, + default="market_avatar/default.png", + ) + phone = models.CharField(max_length=20) + location_id = models.ForeignKey( + to=location.Location, + on_delete=models.CASCADE, + related_name="market_location", + ) + debt_paid = models.DecimalField( + max_digits=10, + decimal_places=2, + default=0, + validators=[MinValueValidator(0)], + ) + debt_unpaid = models.DecimalField( + max_digits=10, + decimal_places=2, + default=0, + validators=[MinValueValidator(0)], + ) + + def __str__(self): + return f"{self.name} - {self.phone}" + + class Meta: + verbose_name = "Market" + verbose_name_plural = "Markets" + db_table = "market" diff --git a/core/apps/eggs/models/monitoring.py b/core/apps/eggs/models/monitoring.py new file mode 100644 index 0000000..d54ce2f --- /dev/null +++ b/core/apps/eggs/models/monitoring.py @@ -0,0 +1,26 @@ +from django.contrib.contenttypes.fields import GenericForeignKey +from django.contrib.contenttypes.models import ContentType +from django.db import models + +from core.http.models import User + + +class Monitoring(models.Model): + content_type = models.ForeignKey(ContentType, on_delete=models.CASCADE) + object_id = models.PositiveIntegerField() + content_object = GenericForeignKey("content_type", "object_id") + action = models.CharField(max_length=255) + user_id = models.ForeignKey( + to=User, on_delete=models.CASCADE, related_name="monitoring" + ) + timestamp = models.DateTimeField(auto_now_add=True) + created_by = models.CharField(max_length=255, null=True, blank=True) + created_who = models.CharField(max_length=255, null=True, blank=True) + comment = models.CharField(max_length=255, null=True, blank=True) + reason = models.CharField(max_length=255, null=True, blank=True) + price = models.DecimalField( + max_digits=30, decimal_places=2, default=0, null=True, blank=True + ) + + class Meta: + ordering = ["-timestamp"] diff --git a/core/apps/eggs/models/notification.py b/core/apps/eggs/models/notification.py new file mode 100644 index 0000000..b4cf833 --- /dev/null +++ b/core/apps/eggs/models/notification.py @@ -0,0 +1,25 @@ +from django.db import models + +from core.http.models import AbstractBaseModel + + +class Notification(AbstractBaseModel): + title = models.CharField(max_length=255, verbose_name="Title") + body = models.TextField(verbose_name="Body") + user = models.ForeignKey( + "http.User", + on_delete=models.CASCADE, + related_name="notifications", + verbose_name="User", + null=True, + blank=True, + ) + is_read = models.BooleanField(default=False, verbose_name="Is read") + is_sending = models.BooleanField(default=False, verbose_name="Is sending") + + def __str__(self) -> str: + return self.title + + class Meta: + verbose_name = "Notification" + verbose_name_plural = "Notifications" diff --git a/core/apps/eggs/models/order.py b/core/apps/eggs/models/order.py new file mode 100644 index 0000000..15cbd3e --- /dev/null +++ b/core/apps/eggs/models/order.py @@ -0,0 +1,55 @@ +""" +Order model +""" + +from django.db import models, transaction +from django.db.models import F +from core.apps.eggs.models import Location, courier, location, market +from core.http.models import base + + +class Order(base.AbstractBaseModel): + STATUS_CHOICES = ( + ("pending", "Pending"), + ("delivery", "Delivery"), + ("success", "Success"), + ("done", "Done"), + ("cancel", "Cancel"), + ) + + courier_id = models.ForeignKey( + to=courier.Courier, + on_delete=models.CASCADE, + related_name="orders", + null=True, + blank=True, + ) + market_id = models.ForeignKey( + to=market.Market, + on_delete=models.CASCADE, + related_name="orders", + null=True, + blank=True, + ) + data = models.DateTimeField(auto_now_add=True) + status = models.CharField( + max_length=255, choices=STATUS_CHOICES, default="pending" + ) + comment = models.TextField(blank=True, null=True) + price = models.DecimalField(max_digits=30, decimal_places=2, default=0) + price_paid = models.DecimalField( + max_digits=30, decimal_places=2, default=0 + ) + debt = models.DecimalField(max_digits=30, decimal_places=2, default=0) + location_id = models.ForeignKey( + Location, on_delete=models.CASCADE, null=True + ) + count = models.IntegerField(null=True, blank=True, default=0) + + def __str__(self): + return f"Order - {self.id}, status - {self.status}" + + class Meta: + verbose_name = "Order" + verbose_name_plural = "Orders" + db_table = "order" diff --git a/core/apps/eggs/models/order_item.py b/core/apps/eggs/models/order_item.py new file mode 100644 index 0000000..f7db918 --- /dev/null +++ b/core/apps/eggs/models/order_item.py @@ -0,0 +1,43 @@ +""" +Order Items model +""" + +import decimal + +from django.db import models + +from core.apps.eggs.models import courier_product, group, order +from core.http.models import base + + +class OrderItems(base.AbstractBaseModel): + SALE_TYPE_CHOICES = ( + ("optom", "Optom"), + ("dona", "Dona"), + ) + + group_id = models.ForeignKey( + group.Group, on_delete=models.CASCADE, null=True + ) + count = models.IntegerField() + discount = models.DecimalField( + max_digits=10, decimal_places=2, null=True, blank=True + ) + courier_product_id = models.ForeignKey( + to=courier_product.CourierProduct, + on_delete=models.CASCADE, + null=True, + blank=True, + ) + order_id = models.ForeignKey( + order.Order, on_delete=models.CASCADE, related_name="order_items" + ) + sale_type = models.CharField(max_length=10, choices=SALE_TYPE_CHOICES) + + def __str__(self): + return f"Product - {self.group_id}, count - {self.count}" + + class Meta: + verbose_name = "OrderItem" + verbose_name_plural = "OrderItems" + db_table = "order_item" diff --git a/core/apps/eggs/models/party.py b/core/apps/eggs/models/party.py new file mode 100644 index 0000000..63f06d4 --- /dev/null +++ b/core/apps/eggs/models/party.py @@ -0,0 +1,85 @@ +""" +Party model +""" + +from django.db import models +from django.db.models import Sum + +from core.http.models import base +from core.http.models.user import User + + +class Party(base.AbstractBaseModel): + user_id = models.ForeignKey( + to=User, on_delete=models.CASCADE, related_name="parties" + ) + # umumiy soni + count = models.IntegerField(default=0, null=True, blank=True) + # sotilgan soni + sold_count = models.IntegerField(default=0, null=True, blank=True) + # qolgan soni + remaining_count = models.IntegerField(default=0, null=True, blank=True) + # tushum + benefit = models.DecimalField( + max_digits=30, decimal_places=2, default=0, null=True, blank=True + ) + # foyda + profit = models.DecimalField( + max_digits=30, decimal_places=2, default=0, null=True, blank=True + ) + # partiya narxi + cost = models.DecimalField( + max_digits=30, decimal_places=2, default=0, null=True, blank=True + ) + # umumiy narxi + total_cost = models.DecimalField( + max_digits=30, decimal_places=2, default=0, null=True, blank=True + ) + broken_eggs = models.IntegerField(default=0, null=True, blank=True) + courier_eggs = models.IntegerField(default=0, null=True, blank=True) + + @staticmethod + def get_total_benefit(): + return Party.objects.aggregate( + total_count=Sum("count"), # umumiy mahsulot soni + total_sold_count=Sum( + "sold_count" + ), # umumiy sotilgan mahsulot soni + total_remaining_count=Sum( + "remaining_count" + ), # umumiy qolgan mahsulot soni + total_broken_eggs=Sum("broken_eggs"), # umumiy singan soni + total_courier_eggs=Sum( + "courier_eggs" + ), # umumiy kuryerlar tomonidan olingan soni + ) + + def clean(self): + if self.count < 0: + raise ValueError("Count can't be negative") + if self.sold_count < 0: + raise ValueError("Sold count can't be negative") + if self.remaining_count < 0: + raise ValueError("Remaining count can't be negative") + if self.benefit < 0: + raise ValueError("Benefit can't be negative") + if self.cost < 0: + raise ValueError("Cost can't be negative") + if self.total_cost < 0: + raise ValueError("Total cost can't be negative") + + def update_total_cost(self): + total_invoice_price = ( + self.invoices.aggregate(Sum("price"))["price__sum"] or 0 + ) + self.total_cost = self.cost + total_invoice_price + self.profit -= total_invoice_price + self.save() + + def __str__(self): + return f"User - {self.user_id}, price - {self.benefit}" + + class Meta: + verbose_name = "Party" + verbose_name_plural = "Parties" + db_table = "party" diff --git a/core/apps/eggs/models/product.py b/core/apps/eggs/models/product.py new file mode 100644 index 0000000..992aa2c --- /dev/null +++ b/core/apps/eggs/models/product.py @@ -0,0 +1,19 @@ +""" +Product model +""" + +from django.db import models + +from core.http.models import base + + +class Product(base.AbstractBaseModel): + name = models.CharField(max_length=255) + + def __str__(self): + return f"Product - {self.name}" + + class Meta: + verbose_name = "Product" + verbose_name_plural = "Products" + db_table = "product" diff --git a/core/apps/eggs/models/sklad.py b/core/apps/eggs/models/sklad.py new file mode 100644 index 0000000..a2b96aa --- /dev/null +++ b/core/apps/eggs/models/sklad.py @@ -0,0 +1,17 @@ +from django.db import models + +from core.http.models import base +from core.http.models.user import User + + +class Sklad(base.AbstractBaseModel): + user_id = models.ForeignKey( + to=User, on_delete=models.CASCADE, related_name="sklad" + ) + + def __str__(self): + return f"Sklad - {self.user_id}" + + class Meta: + verbose_name_plural = "Sklad" + db_table = "sklad" diff --git a/core/apps/eggs/serializers/__init__.py b/core/apps/eggs/serializers/__init__.py new file mode 100644 index 0000000..841f227 --- /dev/null +++ b/core/apps/eggs/serializers/__init__.py @@ -0,0 +1,19 @@ +from .all_history import * # noqa +from .broken import * # noqa +from .courier_history import * # noqa +from .courier_product import * # noqa +from .courier import * # noqa +from .debt import * # noqa +from .group import * # noqa +from .history import * # noqa +from .invoice import * # noqa +from .location import * # noqa +from .order import * # noqa +from .order_item import * # noqa +from .party import * # noqa +from .product import * # noqa +from .market import * # noqa +from .additional_cost import * # noqa +from .sklad import * # noqa +from .delete_account import * # noqa +from .daily_cost import * # noqa \ No newline at end of file diff --git a/core/apps/eggs/serializers/add_courier.py b/core/apps/eggs/serializers/add_courier.py new file mode 100644 index 0000000..55b4ee0 --- /dev/null +++ b/core/apps/eggs/serializers/add_courier.py @@ -0,0 +1,83 @@ +from django.core import exceptions +from rest_framework import serializers + +from core.http.models import User +from core.apps.eggs import models +from core.apps.eggs.serializers import CourierHistorySerializer + + +class AddCourierSerializer(serializers.ModelSerializer): + courier_id = serializers.SerializerMethodField() + + class Meta: + model = User + fields = ( + "courier_id", + "id", + "first_name", + "last_name", + "phone", + "username", + "role", + "avatar", + "password", + ) + + def create(self, validated_data): + validated_data["role"] = "courier" + user = User.objects.create_user(**validated_data) + courier = models.Courier.objects.create(user_id=user) + + request_user = self.context["request"].user + created_who = f"{request_user.first_name} {request_user.last_name}" + + models.History.objects.create( + content_object=courier, + action="courier_created", + created_who=created_who, + user_id=user, + created_by=f"{user.first_name} {user.last_name}", + reason="Kuryer qo'shildi", + avatar=user.avatar if user.avatar else None, + ) + + return user + + def get_courier_id(self, obj): + courier = models.Courier.objects.filter(user_id=obj.id).first() + return courier.id if courier else None + + +class CouriersListSerializer(serializers.ModelSerializer): + courier_history = serializers.SerializerMethodField() + avatar = serializers.SerializerMethodField() + + class Meta: + model = User + fields = ( + "id", + "first_name", + "last_name", + "phone", + "username", + "avatar", + "role", + "courier_history", + "avatar", + ) + + def get_courier_history(self, obj): + try: + user = User.objects.get(id=obj.id, role="courier") + courier = models.Courier.objects.get(user_id=user.id) + courier_history = models.CourierHistory.objects.filter( + courier_id=courier.id + ) # noqa + return CourierHistorySerializer(courier_history, many=True).data + except exceptions.ObjectDoesNotExist: + return [] + + def get_avatar(self, obj): + if obj.avatar: + return obj.avatar.url + return None diff --git a/core/apps/eggs/serializers/add_market.py b/core/apps/eggs/serializers/add_market.py new file mode 100644 index 0000000..b75de6a --- /dev/null +++ b/core/apps/eggs/serializers/add_market.py @@ -0,0 +1,72 @@ +from rest_framework import serializers + +from core.apps.eggs import models +from core.http.models import User + + +class MarketLocationSerializer(serializers.ModelSerializer): + class Meta: + model = models.Location + fields = ("label", "long", "lat") + + +class MarketUserSerializer(serializers.ModelSerializer): + class Meta: + model = User + fields = ( + "id", + "first_name", + "last_name", + "phone", + "password", + "role", + "avatar", + ) + + +class AddMarketSerializer(serializers.ModelSerializer): + user_id = MarketUserSerializer() + location_id = MarketLocationSerializer() + + class Meta: + model = models.Market + fields = ( + "name", + "company_name", + "avatar", + "phone", + "user_id", + "location_id", + ) + + def create(self, validated_data): + user_data = validated_data.pop("user_id") + location_data = validated_data.pop("location_id") + password = user_data.pop("password", None) + user_data.pop("role", None) # remove 'role' from user_data + + user = User.objects.create(role="market", **user_data) + if password: + user.set_password(password) + user.save() + + location = models.Location.objects.create(**location_data) + + market = models.Market.objects.create( + user_id=user, location_id=location, **validated_data + ) + + request_user = self.context["request"].user + created_who = f"{request_user.first_name} {request_user.last_name}" + + models.History.objects.create( + content_object=market, + action="market_created", + user_id=user, + created_who=created_who, + created_by=market.company_name, + reason="Market qo'shildi", + avatar=market.avatar if market.avatar else None, + ) + + return market diff --git a/core/apps/eggs/serializers/additional_cost.py b/core/apps/eggs/serializers/additional_cost.py new file mode 100644 index 0000000..354c8ac --- /dev/null +++ b/core/apps/eggs/serializers/additional_cost.py @@ -0,0 +1,54 @@ +from django.db.models import Sum +from rest_framework import serializers + +from core.apps.eggs.models import AdditionalCost, History +from core.apps.eggs.models.monitoring import Monitoring +from core.http.serializers import UserSerializer + + +class AdditionalCostSerializer(serializers.ModelSerializer): + user = UserSerializer(read_only=True) + total_price = serializers.SerializerMethodField() + + class Meta: + model = AdditionalCost + fields = ["id", "user", "reason", "price", "created_at", "total_price"] + read_only_fields = ["created_at", "total_price"] + extra_kwargs = { + "user": {"required": False}, + } + + def create(self, validated_data): + additional_cost = AdditionalCost.objects.create(**validated_data) + user = self.context["request"].user + if user is not None: + additional_cost.user = user + additional_cost.save() + History.objects.create( + content_object=additional_cost, + action="additional_cost_created", + user_id=user, + created_who=f"{user.first_name} {user.last_name}", + created_by=f"{user.first_name} {user.last_name}", + reason=f"Qo'shimcha xarajat qo'shildi {additional_cost.price} so'm", + comment=additional_cost.reason, + avatar=user.avatar if user.avatar else None, + ) + Monitoring.objects.create( + content_object=additional_cost, + action="additional_cost_created", + user_id=user, + created_who=f"{user.first_name} {user.last_name}", + created_by=f"{user.first_name} {user.last_name}", + reason=f"Qo'shimcha xarajat qo'shildi {additional_cost.price} so'm", + comment="Chiqim", + price=additional_cost.price, + ) + return additional_cost + else: + raise serializers.ValidationError("User is not authenticated") + + def get_total_price(self, obj): + return AdditionalCost.objects.aggregate(total_price=Sum("price"))[ + "total_price" + ] diff --git a/core/apps/eggs/serializers/all_history.py b/core/apps/eggs/serializers/all_history.py new file mode 100644 index 0000000..52d311f --- /dev/null +++ b/core/apps/eggs/serializers/all_history.py @@ -0,0 +1,27 @@ +from rest_framework import serializers +from core.apps.eggs.models.all_history import AllHistory + + +class AllHistorySerializer(serializers.ModelSerializer): + action = serializers.SerializerMethodField() + + class Meta: + model = AllHistory + fields = [ + "id", + "object_id", + "action", + "timestamp", + "created_by", + "created_who", + "reason", + ] + + def get_action(self, obj): + return obj.action + + def to_representation(self, instance): + representation = super().to_representation(instance) + if instance.action != "created": + return {} + return representation diff --git a/core/apps/eggs/serializers/broken.py b/core/apps/eggs/serializers/broken.py new file mode 100644 index 0000000..78527b9 --- /dev/null +++ b/core/apps/eggs/serializers/broken.py @@ -0,0 +1,50 @@ +from rest_framework import serializers + +from core.apps.eggs.models import Broken, Group, History +from core.apps.eggs.models.monitoring import Monitoring +from core.http.models.user import User + + +class BrokenSerializer(serializers.ModelSerializer): + class Meta: + model = Broken + fields = ["comment", "quantity", "group", "price"] + + def validate(self, data): + group = Group.objects.get(id=data["group"].id) + if data["quantity"] > group.quantity: + raise serializers.ValidationError( + "Miqdori guruh miqdoridan ko'p bo'lishi mumkin emas" + ) + return data + + def create(self, validated_data): + request = self.context.get("request") + if request and hasattr(request, "user"): + user = User.objects.get(id=request.user.id) + validated_data["user_id"] = user + instance = Broken.objects.create(**validated_data) + + History.objects.create( + content_object=instance, + action="broken_created", + user_id=instance.user_id, + created_who=f"{instance.user_id.first_name} {instance.user_id.last_name}", + created_by=instance.group.name, + reason=f"Singan tuxum qo'shildi: {instance.quantity} ta", + avatar=( + instance.user_id.avatar if instance.user_id.avatar else None + ), + ) + Monitoring.objects.create( + content_object=instance, + action="broken_created", + user_id=instance.user_id, + created_who=f"{instance.user_id.first_name} {instance.user_id.last_name}", + created_by=instance.group.name, + reason=f"Singan tuxum qo'shildi: {instance.quantity} ta", + comment="Chiqim", + price=instance.price, + ) + + return instance diff --git a/core/apps/eggs/serializers/courier.py b/core/apps/eggs/serializers/courier.py new file mode 100644 index 0000000..3f105be --- /dev/null +++ b/core/apps/eggs/serializers/courier.py @@ -0,0 +1,24 @@ +""" +Courier serializer +""" + +from rest_framework import serializers + +from core.apps.eggs import models +from core.http.serializers import user + + +class CourierSerializer(serializers.ModelSerializer): + user = user.UserSerializer(read_only=True, source="user_id") + + class Meta: + model = models.Courier + fields = ( + "id", + "user_id", + "user", + ) + + extra_kwargs = { + "user_id": {"write_only": True}, + } diff --git a/core/apps/eggs/serializers/courier_history.py b/core/apps/eggs/serializers/courier_history.py new file mode 100644 index 0000000..5704bdb --- /dev/null +++ b/core/apps/eggs/serializers/courier_history.py @@ -0,0 +1,31 @@ +""" +Courier History serializer +""" + +from rest_framework import serializers + +from core.apps.eggs import models + + +class CourierHistorySerializer(serializers.ModelSerializer): + class Meta: + model = models.CourierHistory + fields = ( + "id", + "courier_id", + "group_id", + "get_eggs", + "return_eggs", + "broken_eggs", + "date", + ) + + def update(self, instance, validated_data): + return_eggs = validated_data.get("return_eggs", 0) + broken_eggs = validated_data.get("broken_eggs", 0) + + instance.return_eggs += return_eggs + instance.broken_eggs += broken_eggs + instance.save() + + return instance diff --git a/core/apps/eggs/serializers/courier_product.py b/core/apps/eggs/serializers/courier_product.py new file mode 100644 index 0000000..ca9adcc --- /dev/null +++ b/core/apps/eggs/serializers/courier_product.py @@ -0,0 +1,106 @@ +""" +Courier Product serializer +""" + +from rest_framework import serializers + +from core.apps.eggs import models +from core.apps.eggs.serializers import group +from core.apps.eggs.serializers import courier +from core.apps.eggs.models import History +from django.utils.translation import gettext as _ +from django.contrib.auth import get_user_model + + +class CourierProductSerializer(serializers.ModelSerializer): + courier = courier.CourierSerializer(read_only=True, source="courier_id") + group = group.GroupSerializer(read_only=True, source="group_id") + + class Meta: + model = models.CourierProduct + fields = ("id", "group", "count", "courier") + + def list(self, request): + user_id = request.user.id + courier_product = models.CourierProduct.objects.filter( + courier_id=user_id, count__gt=0 + ) + + return courier_product + + +User = get_user_model() + + +class CourierProductCreateSerializer(serializers.ModelSerializer): + class Meta: + model = models.CourierProduct + fields = ("count", "group_id") + + def validate(self, data): + group = models.Group.objects.get(id=data["group_id"].id) + + if data["count"] > group.quantity: + raise serializers.ValidationError( + "Guruh sonidan ko'p bo'lishi mumkin emas." + ) + + return data + + def create(self, validated_data): + user_id = self.context["request"].user.id + courier = models.Courier.objects.get(user_id=user_id) + courier_product = models.CourierProduct.objects.create( + courier_id=courier, **validated_data + ) + + user = User.objects.get(id=user_id) + + History.objects.create( + user_id=user, + action="courier_product_created", + created_who=f"{courier.user_id.first_name} {courier.user_id.last_name}", + created_by=courier_product.group_id.name, + object_id=courier_product.id, + content_object=courier_product, + reason=_(f"Kuryer mahsulot oldi {courier_product.count} ta"), + avatar=user.avatar if user.avatar else None, + ) + + return courier_product + + +class CourierProductReturnSerializer(serializers.ModelSerializer): + data = CourierProductCreateSerializer(many=True) + + class Meta: + model = models.CourierProduct + fields = ("data",) + + def create(self, validated_data): + data = validated_data.pop("data", []) + courier_products = [] + for item in data: + courier_product = CourierProductCreateSerializer( + context=self.context + ).create(item) + courier_product.save() + courier_products.append(courier_product) + + return {"data": courier_products} + + def to_representation(self, instance): + data = instance.get("data", []) + return { + "data": [ + { + "id": product.id, + "group": group.GroupSerializer(product.group_id).data, + "count": product.count, + "courier": courier.CourierSerializer( + product.courier_id + ).data, # noqa + } + for product in data + ] + } diff --git a/core/apps/eggs/serializers/daily_cost.py b/core/apps/eggs/serializers/daily_cost.py new file mode 100644 index 0000000..6440c2a --- /dev/null +++ b/core/apps/eggs/serializers/daily_cost.py @@ -0,0 +1,9 @@ +from rest_framework import serializers + +from core.apps.eggs.models import DailyCost + + +class DailyCostSerializer(serializers.ModelSerializer): + class Meta: + model = DailyCost + fields = ["id", "first_cost", "second_cost", "third_cost", "created_at"] diff --git a/core/apps/eggs/serializers/debt.py b/core/apps/eggs/serializers/debt.py new file mode 100644 index 0000000..232da6b --- /dev/null +++ b/core/apps/eggs/serializers/debt.py @@ -0,0 +1,73 @@ +from rest_framework import serializers + +from core.apps.eggs.models import Debt, Market, History, Notification +from core.apps.eggs.models.debt import DebtTypeEnum +from core.apps.eggs.models.monitoring import Monitoring +from core.http.models import User +from core.apps.eggs.tasks.send_sms import send_sms_msg + + +class DebtSerializer(serializers.ModelSerializer): + class Meta: + model = Debt + fields = [ + "market", + "debt_price", + "debt_type", + "created_at", + "updated_at", + ] + + def validate(self, data): + market = Market.objects.get(id=data["market"].id) + if ( + data["debt_price"] > market.debt_unpaid + and data.get("debt_type", None) == DebtTypeEnum.LOST.value + ): + raise serializers.ValidationError( + { + "debt_price": "Qarz narxi to'lanmagan qarzdan yuqori bo'lishi mumkin emas." + } + ) + return data + + def create(self, validated_data): + instance = Debt.objects.create(**validated_data) + + request = self.context.get("request") + request_user = request.user + + full_name = f"{request_user.first_name} {request_user.last_name}" + + History.objects.create( + content_object=instance, + action="debt_created", + user_id=request_user, + created_who=full_name, + created_by=instance.market.name, + avatar=instance.market.avatar, + reason=f"Qarz yechildi {instance.debt_price}", + comment=f"Qarz yechildi {instance.debt_price}", + ) + Monitoring.objects.create( + content_object=instance, + action="debt_created", + user_id=request_user, + created_who=full_name, + created_by=instance.market.name, + reason=f"Qarz yechildi {instance.debt_price}", + comment="Kirim", + price=instance.debt_price, + ) + notification_users = User.objects.filter(role="admin") + for user in notification_users: + Notification.objects.create( + user=user, + title=f"Qarz muvaffaqiyatli yechildi {instance.debt_price} so'm", + body=f"Qarz muvaffaqiyatli yechildi {instance.market.name} dan {instance.debt_price} so'm", + ) + + message = f"Hurmatli {instance.market.user_id.first_name} {instance.market.user_id.last_name} , Sizning Gold-eggs.uz tuxum yetkazib berish xizmati ilovasidagi xaridingizga {instance.debt_price} so'm qarz yechildi Umumiy balans: {instance.market.debt_unpaid} so'm Batafsil: +998914249515" + + send_sms_msg.delay(instance.market.user_id.phone, message) + return instance diff --git a/core/apps/eggs/serializers/delete_account.py b/core/apps/eggs/serializers/delete_account.py new file mode 100644 index 0000000..ceecce1 --- /dev/null +++ b/core/apps/eggs/serializers/delete_account.py @@ -0,0 +1,27 @@ +from django.contrib.auth import authenticate +from rest_framework import serializers + + +class DeleteAccountSerializer(serializers.Serializer): + phone = serializers.CharField() + password = serializers.CharField(write_only=True) + + def validate(self, data): + phone = data.get("phone") + password = data.get("password") + user = authenticate(phone=phone, password=password) + + if user is None: + raise serializers.ValidationError( + "Telefon raqam yoki parol noto'g'ri." + ) + + if user != self.context["request"].user: + raise serializers.ValidationError("Telefon raqam noto'g'ri.") + + return data + + def delete(self): + user = self.context["request"].user + user.delete() + return user diff --git a/core/apps/eggs/serializers/group.py b/core/apps/eggs/serializers/group.py new file mode 100644 index 0000000..5549da7 --- /dev/null +++ b/core/apps/eggs/serializers/group.py @@ -0,0 +1,77 @@ +""" +Group serializer +""" + +from rest_framework import serializers + +from core.apps.eggs import models +from core.apps.eggs.serializers import product + + +class GroupUserSerializer(serializers.ModelSerializer): + class Meta: + model = models.User + fields = ( + "id", + "first_name", + "last_name", + "phone", + ) + + +class GroupPartySerializer(serializers.ModelSerializer): + user = GroupUserSerializer(read_only=True, many=False, source="user_id") + + class Meta: + model = models.Party + fields = ( + "id", + "user", + "count", + "sold_count", + "remaining_count", + "benefit", + "cost", + "total_cost", + ) + + +class GroupSerializer(serializers.ModelSerializer): + party = GroupPartySerializer(read_only=True, many=False, source="party_id") + product = product.ProductSerializer( + read_only=True, many=False, source="product_id" + ) + + class Meta: + model = models.Group + fields = ( + "id", + "name", + "date", + "entry_price", + "unit_price", + "wholesale_price", + "quantity", + "broken_eggs", + "product", + "party", + ) + + def update(self, instance, validated_data): + instance.name = validated_data.get("name", instance.name) + instance.date = validated_data.get("date", instance.date) + instance.entry_price = validated_data.get( + "entry_price", instance.entry_price + ) + instance.unit_price = validated_data.get( + "unit_price", instance.unit_price + ) + instance.wholesale_price = validated_data.get( + "wholesale_price", instance.wholesale_price + ) + instance.quantity = validated_data.get("quantity", instance.quantity) + instance.broken_eggs = validated_data.get( + "broken_eggs", instance.broken_eggs + ) + instance.save() + return instance diff --git a/core/apps/eggs/serializers/history.py b/core/apps/eggs/serializers/history.py new file mode 100644 index 0000000..0ad1328 --- /dev/null +++ b/core/apps/eggs/serializers/history.py @@ -0,0 +1,26 @@ +from rest_framework import serializers +from core.apps.eggs.models import History + + +class HistorySerializer(serializers.ModelSerializer): + avatar = serializers.SerializerMethodField() + + class Meta: + model = History + fields = [ + "id", + "content_type", + "object_id", + "action", + "avatar", + "timestamp", + "created_by", + "created_who", + "comment", + "reason", + ] + + def get_avatar(self, obj): + if obj.avatar and hasattr(obj.avatar, "url"): + return obj.avatar.url + return None diff --git a/core/apps/eggs/serializers/invoice.py b/core/apps/eggs/serializers/invoice.py new file mode 100644 index 0000000..7d3d173 --- /dev/null +++ b/core/apps/eggs/serializers/invoice.py @@ -0,0 +1,13 @@ +""" +Invoice serializer +""" + +from rest_framework import serializers + +from core.apps.eggs import models + + +class InvoiceSerializer(serializers.ModelSerializer): + class Meta: + model = models.Invoice + fields = ("name", "price") diff --git a/core/apps/eggs/serializers/location.py b/core/apps/eggs/serializers/location.py new file mode 100644 index 0000000..2f2cec2 --- /dev/null +++ b/core/apps/eggs/serializers/location.py @@ -0,0 +1,13 @@ +""" +Location serializer +""" + +from rest_framework import serializers + +from core.apps.eggs import models + + +class LocationSerializer(serializers.ModelSerializer): + class Meta: + model = models.Location + fields = ("id", "long", "lat", "label") diff --git a/core/apps/eggs/serializers/market.py b/core/apps/eggs/serializers/market.py new file mode 100644 index 0000000..a9cea39 --- /dev/null +++ b/core/apps/eggs/serializers/market.py @@ -0,0 +1,87 @@ +""" +Market serializer +""" + +from rest_framework import serializers + +from core.apps.eggs import models +from core.apps.eggs.serializers import location, order +from core.http.serializers import user + + +class MarketSerializer(serializers.ModelSerializer): + user = user.UserSerializer(source="user_id", read_only=True) + location = location.LocationSerializer(source="location_id") + + class Meta: + model = models.Market + fields = ( + "id", + "name", + "company_name", + "user_id", + "avatar", + "user", + "phone", + "location", + "debt_paid", + "debt_unpaid", + ) + extra_kwargs = { + "user_id": {"write_only": True}, + } + + def create(self, validated_data): + user_id = validated_data.pop("user_id") + location_data = validated_data.pop("location_id") + + location = models.Location.objects.create(**location_data) + + market = models.Market.objects.create( + user_id=user_id, location_id=location, **validated_data + ) + + return market + + def update(self, instance, validated_data): + location_data = validated_data.pop("location_id", None) + + if location_data: + for attr, value in location_data.items(): + setattr(instance.location_id, attr, value) + instance.location_id.save() + + for attr, value in validated_data.items(): + setattr(instance, attr, value) + + instance.save() + return instance + + +class MarketHistorySerializer(serializers.ModelSerializer): + user = user.UserSerializer(read_only=True, source="user_id") + location = location.LocationSerializer( + read_only=True, source="location_id" + ) + orders = serializers.SerializerMethodField() + + class Meta: + model = models.Market + fields = ( + "id", + "name", + "company_name", + "user_id", + "avatar", + "user", + "phone", + "location", + "orders", + "debt_paid", + "debt_unpaid", + ) + + def get_orders(self, obj): + orders = models.Order.objects.filter(market_id=obj.id) + serializer = order.OrderSerializer(orders, many=True) + return serializer.data diff --git a/core/apps/eggs/serializers/order.py b/core/apps/eggs/serializers/order.py new file mode 100644 index 0000000..2b4e53e --- /dev/null +++ b/core/apps/eggs/serializers/order.py @@ -0,0 +1,205 @@ +""" +Order serializer +""" + +# import decimal + +from django.db.models import Q +from django.utils.translation import gettext as _ +from rest_framework import serializers +from rest_framework.exceptions import PermissionDenied + +from core.apps.eggs import models +from core.apps.eggs.models import Market, History, Notification +from core.apps.eggs.serializers import courier, location, market +from core.apps.eggs.serializers.order_item import OrderItemsSerializer +from core.http.models import User +from core.http.serializers import user + + +class OrderCourierSerializer(serializers.ModelSerializer): + user_id = user.UserSerializer() + + class Meta: + model = models.Courier + fields = ( + "id", + "user_id", + ) + + +class OrderSerializer(serializers.ModelSerializer): + market = market.MarketSerializer(read_only=True, source="market_id") + location = location.LocationSerializer( + read_only=True, source="location_id" + ) + courier = courier.CourierSerializer(read_only=True, source="courier_id") + order_items = OrderItemsSerializer(many=True, read_only=True) + + class Meta: + model = models.Order + fields = ( + "id", + "courier", + "market", + "location", + "price", + "price_paid", + "debt", + "status", + "comment", + "data", + "count", + "order_items", + ) + + def to_representation(self, instance): + representation = super().to_representation(instance) + for field in ["price", "price_paid", "debt"]: + try: + representation[field] = str( + round(float(representation[field]), 2) + ) + except (TypeError, ValueError): + representation[field] = "Invalid value" + return representation + + +class OrderCreateSerializer(serializers.ModelSerializer): + location = location.LocationSerializer(source="location_id", required=True) + market = market.MarketSerializer(source="market_id", read_only=True) + order_items = OrderItemsSerializer(many=True, required=False) + + class Meta: + model = models.Order + fields = ( + "id", + "market_id", + "market", + "comment", + "price", + "price_paid", + "debt", + "location", + "status", + "count", + "order_items", + ) + + def validate_price(self, value): + try: + return round(float(value), 2) + except (TypeError, ValueError): + raise serializers.ValidationError("Invalid price value.") + + def create(self, validated_data): + order_items_data = validated_data.pop("order_items", []) + location_data = validated_data.pop("location_id", None) + request = self.context.get("request") + if request and request.user.role == "market": + try: + market_instance = Market.objects.get(user_id=request.user.id) + validated_data["market_id"] = market_instance + except Market.DoesNotExist: + raise PermissionDenied( + "Bu foydalanuvchi uchun Market mavjud emas" + ) + if request and request.user.role == "courier": + courier_instance = models.Courier.objects.get( + user_id=request.user.id + ) + validated_data["courier_id"] = courier_instance + + if location_data: + location = models.Location.objects.create(**location_data) + validated_data["location_id"] = location + + # validated_data["price"] = self.validate_price( + # validated_data.get("price", 0.00) + # ) + # validated_data["price_paid"] = self.validate_price( + # validated_data.get("price_paid", 0.00) + # ) + # validated_data["debt"] = self.validate_price( + # validated_data.get("debt", 0.00) + # ) + + order_pk = super().create(validated_data).pk + order = models.Order.objects.get(pk=order_pk) + + for order_item_data in order_items_data: + models.OrderItems.objects.create(order_id=order, **order_item_data) + + created_who = "" + if request: + if request.user.role == "market" and order.market_id is not None: + created_who = f"{order.market_id.user_id.first_name} {order.market_id.user_id.last_name}" + elif ( + request.user.role == "courier" and order.courier_id is not None + ): + created_who = f"{order.courier_id.user_id.first_name} {order.courier_id.user_id.last_name}" + elif request.user.role == "admin": + created_who = ( + f"{request.user.first_name} {request.user.last_name}" + ) + + History.objects.create( + content_object=order, + action="order_created", + created_who=created_who, + user_id=request.user, + created_by=order.market_id.name, + comment=order.comment, + reason=_(f"Buyurtma: {order.price}"), + avatar=( + order.market_id.avatar if order.market_id.avatar else None + ), + ) + + notification_user = User.objects.filter( + Q(role="admin") | Q(role="courier") + ) + for user in notification_user: + Notification.objects.create( + user=user, + title=f"Yangi buyurtma: {order.price}", + body=f"Yangi buyurtma: {order.market_id.name} {order.price}", + ) + + return order + + def to_representation(self, instance): + serializer = OrderSerializer(instance) + return serializer.data + + def update(self, instance, validated_data): + order_items_data = validated_data.pop("order_items", []) + location_data = validated_data.pop("location_id", None) + request = self.context.get("request") + + if request and request.user.role == "courier": + courier_instance = models.Courier.objects.get( + user_id=request.user.id + ) + instance.courier_id = courier_instance + + for attr, value in validated_data.items(): + if attr in ["price", "price_paid", "debt"]: + value = self.validate_price(value) + setattr(instance, attr, value) + instance.save() + + for order_item_data in order_items_data: + models.OrderItems.objects.update_or_create( + order_id=instance, defaults=order_item_data + ) + + if location_data: + location, created = models.Location.objects.update_or_create( + id=instance.location_id.id if instance.location_id else None, + defaults=location_data, + ) + instance.location_id = location + instance.save() + + return instance diff --git a/core/apps/eggs/serializers/order_item.py b/core/apps/eggs/serializers/order_item.py new file mode 100644 index 0000000..ed9313b --- /dev/null +++ b/core/apps/eggs/serializers/order_item.py @@ -0,0 +1,33 @@ +""" +Order Items serializer +""" + +from rest_framework import serializers + +from core.apps.eggs import models +from core.apps.eggs.models import Group +from core.apps.eggs.serializers import courier_product, GroupSerializer + + +class OrderItemsSerializer(serializers.ModelSerializer): + courier_product = courier_product.CourierProductSerializer( + read_only=True, source="courier_product_id" + ) + group = GroupSerializer(read_only=True, source="group_id") + group_id = serializers.PrimaryKeyRelatedField( + queryset=Group.objects.all(), required=True + ) + + class Meta: + model = models.OrderItems + fields = ( + "id", + "group", + "group_id", + "count", + "discount", + "courier_product", + "courier_product_id", + "sale_type", + ) + extra_kwargs = {"courier_product_id": {"write_only": True}} diff --git a/core/apps/eggs/serializers/party.py b/core/apps/eggs/serializers/party.py new file mode 100644 index 0000000..33c896e --- /dev/null +++ b/core/apps/eggs/serializers/party.py @@ -0,0 +1,130 @@ +""" +Party serializer +""" + +from django.utils.translation import gettext as _ +from rest_framework import serializers + +from core.apps.eggs import models +from core.apps.eggs.serializers import group +from core.apps.eggs.serializers import group as group_serializer +from core.apps.eggs.serializers import invoice +from core.http.serializers import user + + +class PartySerializer(serializers.ModelSerializer): + invoices = invoice.InvoiceSerializer(many=True) + groups = group.GroupSerializer(many=True) + user = user.UserSerializer(read_only=True, source="user_id") + + class Meta: + model = models.Party + fields = ( + "id", + "user", + "count", + "sold_count", + "remaining_count", + "profit", + "benefit", + "cost", + "total_cost", + "created_at", + "invoices", + "groups", + "broken_eggs", + "courier_eggs", + ) + + def create(self, validated_data): + invoices_data = validated_data.pop("invoices", []) + groups_data = validated_data.pop("groups", []) + party = models.Party.objects.create(**validated_data) + + for invoice_data in invoices_data: + invoice_data["party_id"] = party + models.Invoice.objects.create(**invoice_data) + + for group_data in groups_data: + group_data["party_id"] = party + models.Group.objects.create(**group_data) + + return party + + +class PartyGroupSerializer(serializers.ModelSerializer): + class Meta: + model = models.Group + fields = ( + "id", + "name", + "entry_price", + "unit_price", + "wholesale_price", + "quantity", + "broken_eggs", + "product_id", + ) + + +class PartyCreateSerializer(serializers.ModelSerializer): + invoices = invoice.InvoiceSerializer(many=True) + groups = PartyGroupSerializer(many=True) + + class Meta: + model = models.Party + fields = ( + "invoices", + "groups", + ) + + def create(self, validated_data): + user = self.context["request"].user + invoices_data = validated_data.pop("invoices", []) + groups_data = validated_data.pop("groups", []) + party = models.Party.objects.create(user_id=user, **validated_data) + + for invoice_data in invoices_data: + invoice_data["party_id"] = party + models.Invoice.objects.create(**invoice_data) + + created_groups = [] + for group_data in groups_data: + group_data["party_id"] = party + created_group = models.Group.objects.create(**group_data) + created_groups.append(created_group) + + party.groups.set(created_groups) + + full_name = f"{user.first_name} {user.last_name}" + + models.History.objects.create( + content_object=party, + action="party_created", + user_id=user, + created_who=full_name, + created_by=party.groups.name, + reason=_(f"Partiya yaratildi: {party.total_cost}"), + comment=_(f"Partiya yaratildi: {party.total_cost}"), + avatar=user.avatar if user.avatar else None, + ) + models.Monitoring.objects.create( + content_object=party, + action="party_created", + user_id=user, + created_who=full_name, + created_by=party.groups.name, + reason=_(f"Partiya yaratildi: {party.total_cost}"), + comment="Chiqim", + price=party.total_cost, + ) + + return party + + def to_representation(self, instance): + representation = super().to_representation(instance) + representation["groups"] = group_serializer.GroupSerializer( + instance.groups.all(), many=True + ).data + + return representation diff --git a/core/apps/eggs/serializers/product.py b/core/apps/eggs/serializers/product.py new file mode 100644 index 0000000..2fab6b9 --- /dev/null +++ b/core/apps/eggs/serializers/product.py @@ -0,0 +1,13 @@ +""" +Product serializer +""" + +from rest_framework import serializers + +from core.apps.eggs import models + + +class ProductSerializer(serializers.ModelSerializer): + class Meta: + model = models.Product + fields = ("id", "name") diff --git a/core/apps/eggs/serializers/sklad.py b/core/apps/eggs/serializers/sklad.py new file mode 100644 index 0000000..3786d88 --- /dev/null +++ b/core/apps/eggs/serializers/sklad.py @@ -0,0 +1,66 @@ +from rest_framework import serializers + +from core.apps.eggs import models +from core.http.models import User + + +class AddSkladSerializer(serializers.ModelSerializer): + sklad_id = serializers.SerializerMethodField() + + class Meta: + model = User + fields = ( + "sklad_id", + "id", + "first_name", + "last_name", + "phone", + "username", + "role", + "avatar", + "password", + ) + + def create(self, validated_data): + validated_data["role"] = "sklad" + user = User.objects.create_user(**validated_data) + sklad = models.Sklad.objects.create(user_id=user) + + request_user = self.context["request"].user + created_who = f"{request_user.first_name} {request_user.last_name}" + + models.History.objects.create( + content_object=sklad, + action="sklad_created", + user_id=user, + created_who=created_who, + created_by=f"{user.first_name} {user.last_name}", + reason="Omborxona qo'shildi", + avatar=user.avatar if user.avatar else None, + ) + + return user + + def get_sklad_id(self, obj): + sklad = models.Sklad.objects.filter(user_id=obj.id).first() + return sklad.id if sklad else None + + +class SkladListSerializer(serializers.ModelSerializer): + avatar = serializers.SerializerMethodField() + + class Meta: + model = User + fields = ( + "id", + "first_name", + "last_name", + "phone", + "username", + "avatar", + "role", + "avatar", + ) + + def get_avatar(self, obj): + return obj.avatar.url if obj.avatar else None diff --git a/core/apps/eggs/tasks/__init__.py b/core/apps/eggs/tasks/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/eggs/tasks/send_sms.py b/core/apps/eggs/tasks/send_sms.py new file mode 100644 index 0000000..8236e55 --- /dev/null +++ b/core/apps/eggs/tasks/send_sms.py @@ -0,0 +1,13 @@ +from celery import shared_task + +from core.services.sms_service import SendService + +sms_service = SendService() + +@shared_task +def send_sms_msg(phone_number, message): + phone_number = phone_number.lstrip("+") + if not phone_number.startswith("998"): + phone_number = "998" + phone_number + res = sms_service.send_sms(phone_number, message) + return res \ No newline at end of file diff --git a/core/apps/eggs/tests.py b/core/apps/eggs/tests.py new file mode 100644 index 0000000..7ce503c --- /dev/null +++ b/core/apps/eggs/tests.py @@ -0,0 +1,3 @@ +from django.test import TestCase + +# Create your tests here. diff --git a/core/apps/eggs/urls.py b/core/apps/eggs/urls.py new file mode 100644 index 0000000..6f2c8a2 --- /dev/null +++ b/core/apps/eggs/urls.py @@ -0,0 +1,91 @@ +""" +Urs registrations for eggs app +""" + +from django import urls +from rest_framework import routers + +from core.apps.eggs import views +from core.apps.eggs.views import MonthlyBenefitView, DeleteAccountView + +router = routers.DefaultRouter() + +router.register(r"debt", views.DebtViewSet, basename="debt") +router.register(r"party", views.PartViewSet, basename="party") +router.register(r"group", views.GroupViewSet, basename="group") +router.register(r"order", views.OrderViewSet, basename="order") +router.register(r"broken", views.BrokenViewSet, basename="broken") +router.register(r"market", views.MarketViewSet, basename="market") +router.register(r"history", views.HistoryViewSet, basename="history") +router.register(r"courier", views.CourierViewSet, basename="courier") +router.register(r"product", views.ProductViewSet, basename="product") +router.register(r"add-market", views.AddMarketView, basename="add-market") +router.register(r"daily-cost", views.DailyCostViewSet, basename="daily-cost") +router.register( + r"all-history", views.AllHistoryViewSet, basename="all-history" +) # noqa +router.register( + r"add-courier", views.AddCourierViewSet, basename="add-courier" +) # noqa +router.register( + r"add-sklad", views.AddSkladViewSet, basename="add-sklad" +) # noqa +router.register( + r"market-history", views.MarketHistoryViewSet, basename="market-history" +) # noqa +router.register( + r"courier-product", views.CourierProductViewSet, basename="courier-product" +) # noqa +router.register( + r"courier-history", views.CourierHistoryViewSet, basename="courier-history" +) # noqa + +urlpatterns = [ + urls.path("", urls.include(router.urls)), + urls.path( + "top_couriers/", views.TopCourierView.as_view(), name="top_couriers" + ), + urls.path( + "top_products/", views.TopProductsView.as_view(), name="top_products" + ), + urls.path( + route="return_eggs/", + view=views.ReturnEggsViewSet.as_view({"put": "update"}), + name="return_eggs", + ), + urls.path( + "additional_cost/", + views.AdditionalCostView.as_view(), + name="add_additional_cost", + ), + urls.path( + "add_debt/", + views.AddDebtView.as_view(), + name="add_debt", + ), + urls.path( + "monthly-benefit/", + MonthlyBenefitView.as_view(), + name="monthly-benefit", + ), + urls.path( + "debt-list//", + views.UserDebtsAPIView.as_view(), + name="debt-history", + ), + # urls.path( + # 'history///', + # views.HistoryDetailView.as_view(), + # name='history-detail' + # ), + urls.path( + "monitoring/summary/", + views.MonitoringSummaryView.as_view(), + name="monitoring-summary", + ), + urls.path( + "auth/delete-account/", + DeleteAccountView.as_view(), + name="delete-account", + ), +] diff --git a/core/apps/eggs/utils/__init__.py b/core/apps/eggs/utils/__init__.py new file mode 100644 index 0000000..774a6bc --- /dev/null +++ b/core/apps/eggs/utils/__init__.py @@ -0,0 +1,5 @@ +from .calculate import * # noqa +from .history import * # noqa +from .signals import * # noqa +from .status import * # noqa +from .notification import * # noqa diff --git a/core/apps/eggs/utils/calculate.py b/core/apps/eggs/utils/calculate.py new file mode 100644 index 0000000..5a49e32 --- /dev/null +++ b/core/apps/eggs/utils/calculate.py @@ -0,0 +1,214 @@ +from decimal import Decimal + +from django.db.models.signals import post_save +from django.db.models.signals import ( + pre_delete, + post_delete, + pre_save, +) +from django.dispatch import receiver + +from core.apps.eggs.models import ( + CourierProduct, + Group, + Invoice, + Party, + Monitoring, +) +from core.apps.eggs.models import OrderItems + + +@receiver(post_save, sender=CourierProduct) +def update_group_quantity(sender, instance, created, **kwargs): + if created: + group = Group.objects.get(id=instance.group_id.id) + if instance.count > group.quantity: + raise ValueError("Count cannot be greater than group quantity") + else: + group.quantity -= instance.count + group.party_id.remaining_count -= instance.count + group.party_id.courier_eggs += instance.count + group.party_id.save() + group.save() + + +@receiver(post_save, sender=OrderItems) +def update_order_price(sender, instance, created, **kwargs): + if created: + group_instance = instance.group_id + order_instance = instance.order_id + + if instance.sale_type == "optom": + order_instance.price += ( + group_instance.wholesale_price * instance.count + ) + elif instance.sale_type == "dona": + order_instance.price += group_instance.unit_price * instance.count + + order_instance.save() + # narxni chegirma bilan kamaytirish + order_instance.price -= instance.discount if instance.discount else 0 + order_instance.save() + # foydani hisoblash + group_instance.party_id.benefit += order_instance.price + group_instance.party_id.save() + + # sotilgan mahsulotlar sonini + group_instance.party_id.sold_count += instance.count + # group_instance.party_id.sold_count = ( + # group_instance.party_id.count + # - group_instance.party_id.remaining_count + # ) + group_instance.party_id.save() + # sof foydani hisoblash + group_instance.party_id.profit = ( + group_instance.party_id.benefit + - group_instance.party_id.sold_count * group_instance.entry_price + ) + group_instance.party_id.save() + + price_paid = ( + Decimal(order_instance.price_paid) + if order_instance.price_paid + else Decimal("0") + ) + + Monitoring.objects.create( + content_object=instance, + action="order_created", + user_id=instance.order_id.courier_id.user_id, + created_who=f"{instance.order_id.courier_id.user_id.first_name} {instance.order_id.courier_id.user_id.last_name}", + created_by=instance.order_id.market_id.name, + reason=f"Buyurtma: {instance.order_id.price}", + comment="Kirim", + price=( + instance.order_id.price_paid + if instance.order_id.price_paid + else 0 + ), + ) + + # qarzni hisoblash + order_instance.debt = order_instance.price - price_paid + order_instance.save() + # to'langan narxni hisoblash + order_instance.price_paid = order_instance.price - Decimal( + order_instance.debt + ) + order_instance.save() + + if order_instance.market_id: + # bozor qarzini hisoblash + market_instance = order_instance.market_id + market_instance.debt_unpaid += Decimal(order_instance.debt) + market_instance.save() + # bozor to'langan narxni hisoblash + market_instance.debt_paid += price_paid + market_instance.save() + + +# @receiver(post_save, sender=OrderItems) +# def update_order_price(sender, instance, created, **kwargs): +# if created: +# group_instance = instance.group_id +# order_instance = instance.order_id +# +# # Ensure all numeric values are Decimal +# count = Decimal(instance.count) +# wholesale_price = Decimal(group_instance.wholesale_price) +# unit_price = Decimal(group_instance.unit_price) +# discount = Decimal(instance.discount) if instance.discount else Decimal('0') +# +# if instance.sale_type == "optom": +# order_instance.price += wholesale_price * count +# elif instance.sale_type == "dona": +# order_instance.price += unit_price * count +# +# order_instance.price -= discount +# order_instance.save() +# +# # Update party benefit +# group_instance.party_id.benefit += order_instance.price +# group_instance.party_id.save() +# +# # Ensure price_paid and debt are Decimals +# price_paid = Decimal(order_instance.price_paid) if order_instance.price_paid else Decimal('0') +# debt = Decimal(order_instance.debt) if order_instance.debt else Decimal('0') +# +# order_instance.debt = order_instance.price - price_paid +# order_instance.save() +# order_instance.price_paid = order_instance.price - debt +# order_instance.save() +# +# # Update market debt +# if order_instance.market_id: +# market_instance = order_instance.market_id +# market_instance.debt_unpaid += order_instance.debt +# market_instance.save() +# market_instance.debt_paid += order_instance.price_paid +# market_instance.save() + + +@receiver(post_save, sender=Group) +def update_party_on_group_change(sender, instance, created, **kwargs): + if created: + quantity_change = instance.quantity + + party_instance = instance.party_id + + party_instance.count += instance.quantity + party_instance.cost += instance.entry_price * instance.quantity + party_instance.save() + party_instance.remaining_count += instance.quantity + party_instance.save() + else: + pass + # old_group = Group.objects.get(pk=instance.pk) + # quantity_change = instance.quantity - old_group.quantity + # + # party_instance = instance.party_id + # if party_instance: + # party_instance.remaining_count += quantity_change + # party_instance.save() + # party_instance.sold_count = ( + # party_instance.count - party_instance.remaining_count + # ) + # party_instance.save() + # party_instance.profit = party_instance.benefit - ( + # party_instance.sold_count * instance.entry_price + # ) + # party_instance.save() + + +@receiver(pre_delete, sender=Group) +def update_party_on_group_delete(sender, instance, **kwargs): + party_instance = instance.party_id + if party_instance: + quantity_change = instance.quantity + party_instance.count -= quantity_change + party_instance.remaining_count += quantity_change + party_instance.sold_count = ( + party_instance.count - party_instance.remaining_count + ) + party_instance.save() + + +@receiver(post_save, sender=Invoice) +@receiver(post_delete, sender=Invoice) +def invoice_changed(sender, instance, **kwargs): + if instance.party_id: + instance.party_id.update_total_cost() + + +@receiver(pre_save, sender=Party) +def party_cost_changed(sender, instance, **kwargs): + if instance.pk: + old_party = Party.objects.get(pk=instance.pk) + if old_party.cost != instance.cost: + if ( + not hasattr(instance, "_updating_total_cost") + or not instance._updating_total_cost + ): + instance._updating_total_cost = True + instance.update_total_cost() + instance._updating_total_cost = False diff --git a/core/apps/eggs/utils/history.py b/core/apps/eggs/utils/history.py new file mode 100644 index 0000000..0a03e43 --- /dev/null +++ b/core/apps/eggs/utils/history.py @@ -0,0 +1,25 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver + +from core.apps.eggs import models + + +@receiver(post_save, sender=models.CourierProduct) +def create_courier_history(sender, instance, created, **kwargs): + if created: + models.CourierHistory.objects.create( + courier_id=instance.courier_id, + group_id=instance.group_id, + get_eggs=instance.count, + courier_product_id=instance, + return_eggs=instance.return_eggs, + ) + # + + else: + histories = models.CourierHistory.objects.filter( + courier_product_id=instance, group_id=instance.group_id + ) + for history in histories: + history.return_eggs = instance.return_eggs + history.save() diff --git a/core/apps/eggs/utils/notification.py b/core/apps/eggs/utils/notification.py new file mode 100644 index 0000000..7c48e3a --- /dev/null +++ b/core/apps/eggs/utils/notification.py @@ -0,0 +1,49 @@ +import logging + +from django.db.models.signals import post_save +from django.dispatch import receiver +from firebase_admin import messaging, exceptions + +from core.apps.eggs.models.notification import Notification +from core.http.models import User + +logger = logging.getLogger(__name__) + + +@receiver(post_save, sender=Notification) +def send_notification(sender, instance, created, **kwargs): # noqa + if created: + try: + if instance.user: + # Sending notification to a specific user + fcm_token = instance.user.fcm_token + if fcm_token: + message = messaging.Message( + notification=messaging.Notification( + title=instance.title, + body=instance.body, + ), + token=fcm_token, + ) + messaging.send(message) + logger.info(f"Notification sent to user {instance.user.id}") + else: + # Sending notification to all users + users = User.objects.exclude(fcm_token__isnull=True).exclude(fcm_token='') + if users.exists(): + messages = [ + messaging.Message( + notification=messaging.Notification( + title=instance.title, + body=instance.body, + ), + token=user.fcm_token, + ) + for user in users + ] + response = messaging.send_all(messages) + logger.info(f"{response.success_count} notifications sent successfully") + else: + logger.warning("No users with valid FCM tokens found") + except exceptions.FirebaseError as e: + logger.error(f"Failed to send notification: {e}") diff --git a/core/apps/eggs/utils/signals.py b/core/apps/eggs/utils/signals.py new file mode 100644 index 0000000..478a461 --- /dev/null +++ b/core/apps/eggs/utils/signals.py @@ -0,0 +1,34 @@ +from django.db.models.signals import post_save +from django.dispatch import receiver + +from core.apps.eggs.models.order import Order + + +@receiver(post_save, sender=Order) +def update_order_status(sender, instance, created, **kwargs): + if created: + if instance.courier_id is None: + Order.objects.filter(id=instance.id).update(status="pending") + elif instance.courier_id is not None and instance.status == "pending": + Order.objects.filter(id=instance.id).update(status="delivery") + else: + if instance.courier_id is not None and instance.status == "pending": + Order.objects.filter(id=instance.id).update(status="delivery") + if instance.status == "success": + order_items = instance.order_items.all() + for item in order_items: + if item.courier_product_id: + if item.courier_product_id.count < item.count: + print(item.courier_product_id.count, item.count) + raise ValueError( + "Count cannot be greater than courier product count" + ) + item.courier_product_id.count -= item.count + print(item.courier_product_id.count, item.count) + item.courier_product_id.save() + + if ( + instance.price == instance.price_paid + and instance.status == "success" + ): + Order.objects.filter(id=instance.id).update(status="done") diff --git a/core/apps/eggs/utils/status.py b/core/apps/eggs/utils/status.py new file mode 100644 index 0000000..60cae3d --- /dev/null +++ b/core/apps/eggs/utils/status.py @@ -0,0 +1,111 @@ +# from django.db.models import F +# from django.db.models.signals import post_save +# from django.dispatch import receiver +# +# from core.apps.eggs.models import Order +# +# +# @receiver(post_save, sender=Order) +# def update_order_status(sender, instance, **kwargs): +# if instance.courier_id is not None and instance.status == "pending": +# instance.status = "delivery" +# instance.save() +# if ( +# instance.debt == 0 +# and instance.price == instance.price_paid +# and instance.status in ["success"] +# ): +# instance.status = "done" +# instance.save() +# if instance.status == "success": +# order_items = instance.order_items.all() +# for item in order_items: +# if item.courier_product_id: +# item.courier_product_id.count = F("count") - item.count +# item.courier_product_id.save() +# +# if instance.market_id: +# market_instance = instance.market_id +# market_instance.refresh_from_db() +# market_instance.debt_paid = F("debt_paid") + instance.price_paid +# market_instance.debt_unpaid = ( +# F("debt_unpaid") + instance.debt - instance.price_paid +# ) +# market_instance.save(update_fields=["debt_paid", "debt_unpaid"]) +# +# Order.objects.filter(pk=instance.pk).update( +# debt=instance.price - instance.price_paid +# ) +# +# if instance.status == "cancel": +# if instance.market_id: +# market_instance = instance.market_id +# market_instance.refresh_from_db() +# market_instance.debt_paid = F("debt_paid") - instance.price_paid +# market_instance.debt_unpaid = ( +# F("debt_unpaid") - instance.debt + instance.price_paid +# ) +# market_instance.save(update_fields=["debt_paid", "debt_unpaid"]) +# Order.objects.filter(pk=instance.pk).update(debt=0) +# order_items = instance.order_items.all() +# for item in order_items: +# if item.courier_product_id: +# item.courier_product_id.count = F("count") + item.count +# item.courier_product_id.save() +# if instance.status == "cancel" and instance.debt > 0: +# Order.objects.filter(pk=instance.pk).update(price_paid=0) +# if instance.market_id: +# market_instance = instance.market_id +# market_instance.refresh_from_db() +# market_instance.debt_paid = F("debt_paid") - instance.price_paid +# market_instance.debt_unpaid = ( +# F("debt_unpaid") - instance.debt + instance.price_paid +# ) +# market_instance.save(update_fields=["debt_paid", "debt_unpaid"]) +# Order.objects.filter(pk=instance.pk).update(debt=0) +# order_items = instance.order_items.all() +# for item in order_items: +# if item.courier_product_id: +# item.courier_product_id.count = F("count") + item.count +# item.courier_product_id.save() +# +# if instance.status == "cancel" and instance.debt == 0: +# Order.objects.filter(pk=instance.pk).update(price_paid=0) +# if instance.market_id: +# market_instance = instance.market_id +# market_instance.refresh_from_db() +# market_instance.debt_paid = F("debt_paid") - instance.price_paid +# market_instance.debt_unpaid = ( +# F("debt_unpaid") - instance.debt + instance.price_paid +# ) +# market_instance.save(update_fields=["debt_paid", "debt_unpaid"]) +# order_items = instance.order_items.all() +# for item in order_items: +# if item.courier_product_id: +# item.courier_product_id.count = F("count") + item.count +# item.courier_product_id.save() + + +# @receiver(post_save, sender=Order) +# def update_order_status(sender, instance, created, **kwargs): +# if created: +# if instance.courier_id is None: +# instance.status = "pending" +# elif instance.courier_id is not None and instance.status == "pending": +# instance.status = "delivery" +# instance.save() +# else: +# if instance.status == "success": +# order_items = instance.order_items.all() +# for item in order_items: +# if item.courier_product_id: +# if item.courier_product_id.count < item.count: +# raise ValueError( +# "Count cannot be greater than courier product count" +# ) +# item.courier_product_id.count -= item.count +# item.courier_product_id.save() +# +# if instance.price == instance.price_paid and instance.status == "success": +# instance.status = "done" +# instance.save() diff --git a/core/apps/eggs/views/__init__.py b/core/apps/eggs/views/__init__.py new file mode 100644 index 0000000..7aca465 --- /dev/null +++ b/core/apps/eggs/views/__init__.py @@ -0,0 +1,24 @@ +from .add_courier import * # noqa +from .add_debt import * # noqa +from .add_market import * # noqa +from .additional_cost import * # noqa +from .all_history import * # noqa +from .benefit import * # noqa +from .broken import * # noqa +from .courier import * # noqa +from .courier_history import * # noqa +from .courier_product import * # noqa +from .debt import * # noqa +from .debt_list import * # noqa +from .delete_account import * # noqa +from .group import * # noqa +from .history import * # noqa +from .market import * # noqa +from .monitoring import * # noqa +from .order import * # noqa +from .party import * # noqa +from .product import * # noqa +from .sklad import * # noqa +from .top_couriers import * # noqa +from .top_products import * # noqa +from .daily_cost import * # noqa \ No newline at end of file diff --git a/core/apps/eggs/views/add_courier.py b/core/apps/eggs/views/add_courier.py new file mode 100644 index 0000000..486e169 --- /dev/null +++ b/core/apps/eggs/views/add_courier.py @@ -0,0 +1,20 @@ +from rest_framework import viewsets +from rest_framework.permissions import IsAuthenticated + +from core.apps.eggs.models import User +from core.apps.eggs.serializers.add_courier import ( + AddCourierSerializer, + CouriersListSerializer, +) +from core.http.permissions import IsRole + + +class AddCourierViewSet(viewsets.ModelViewSet): + permission_classes = [IsAuthenticated, IsRole(["admin"])] + queryset = User.objects.filter(role="courier") + + def get_serializer_class(self): + if self.request.method == "GET": + return CouriersListSerializer + else: + return AddCourierSerializer diff --git a/core/apps/eggs/views/add_debt.py b/core/apps/eggs/views/add_debt.py new file mode 100644 index 0000000..7caa014 --- /dev/null +++ b/core/apps/eggs/views/add_debt.py @@ -0,0 +1,113 @@ +from decimal import Decimal + +from core.apps.eggs.models.debt import Debt, DebtTypeEnum +from drf_yasg import openapi +from drf_yasg.utils import swagger_auto_schema +from rest_framework import status +from rest_framework.response import Response +from rest_framework.views import APIView + +from core.apps.eggs.models import History, Monitoring, Notification +from core.apps.eggs.models.market import Market +from core.http.models import User +from core.apps.eggs.tasks.send_sms import send_sms_msg + + +class AddDebtView(APIView): + @swagger_auto_schema( + request_body=openapi.Schema( + type=openapi.TYPE_OBJECT, + properties={ + "market_id": openapi.Schema( + type=openapi.TYPE_INTEGER, + description="Market ID", + ), + "price": openapi.Schema( + type=openapi.TYPE_NUMBER, + description="Price", + ), + }, + ), + responses={ + 200: openapi.Schema( + type=openapi.TYPE_OBJECT, + properties={ + "message": openapi.Schema( + type=openapi.TYPE_STRING, + description="Debt added successfully.", + ), + }, + ), + 400: openapi.Schema( + type=openapi.TYPE_OBJECT, + properties={ + "error": openapi.Schema( + type=openapi.TYPE_STRING, + description="Detailed error message when the request fails with a 400 status code.", + ), + }, + ), + 404: openapi.Schema( + type=openapi.TYPE_OBJECT, + properties={ + "error": openapi.Schema( + type=openapi.TYPE_STRING, + description="Detailed error message when the request fails with a 404 status code.", + ), + }, + ), + }, + ) + def post(self, request, *args, **kwargs): + market_id = request.data.get("market_id") + price = Decimal(request.data.get("price")) + + if not all([market_id, price]): + return Response( + {"error": "Both market_id and price are required."}, + status=status.HTTP_400_BAD_REQUEST, + ) + + try: + market = Market.objects.get(pk=market_id) + except Market.DoesNotExist: + return Response( + {"error": "Market with given id does not exist."}, + status=status.HTTP_404_NOT_FOUND, + ) + + Debt.objects.create( + market=market, debt_price=price, debt_type=DebtTypeEnum.ADDED.value + ) + + History.objects.create( + content_object=market, + action="add_debt_created", + user_id=request.user, + comment=f"Qarz qo'shildi: {price}", + avatar=market.avatar, + reason=f"Qarz qo'shildi: {price}", + created_by=market.name, + created_who=f"{request.user.first_name} {request.user.last_name}", + ) + notification_users = User.objects.filter(role="admin") + for user in notification_users: + Notification.objects.create( + user=user, + title=f"Qarz muvaffaqiyatli qo'shildi {price} so'm", + body=f"Qarz muvaffaqiyatli qo'shildi {market.name} ga {price} so'm", + ) + + message = f"Hurmatli {market.user_id.first_name} {market.user_id.last_name}, Sizning Gold-eggs.uz tuxum yetkazib berish xizmati ilovasidagi xaridingizga {price} so'm qarz qo'shib qo'yildi Umumiy balans: {market.debt_unpaid} so'm Batafsil: +998914249515" + + send_sms_msg.delay(market.user_id.phone, message) + + return Response( + { + "message": "Qarz muvaffaqiyatli qo'shildi.", + "debt_unpaid": f"{market.debt_unpaid}", + "market_name": f"{market.name}", + "market_company": f"{market.company_name}", + }, + status=status.HTTP_200_OK, + ) diff --git a/core/apps/eggs/views/add_market.py b/core/apps/eggs/views/add_market.py new file mode 100644 index 0000000..d8ede2e --- /dev/null +++ b/core/apps/eggs/views/add_market.py @@ -0,0 +1,13 @@ +from rest_framework import viewsets +from core.apps.eggs.models import Market +from core.apps.eggs.serializers.add_market import AddMarketSerializer +from core.http.permissions import IsRole + + +class AddMarketView(viewsets.ModelViewSet): + queryset = Market.objects.all() + serializer_class = AddMarketSerializer + permission_classes = [IsRole(["admin"])] + + def perform_create(self, serializer): + serializer.save() diff --git a/core/apps/eggs/views/additional_cost.py b/core/apps/eggs/views/additional_cost.py new file mode 100644 index 0000000..6deca02 --- /dev/null +++ b/core/apps/eggs/views/additional_cost.py @@ -0,0 +1,76 @@ +from rest_framework import generics, status, permissions +from rest_framework.response import Response + +from core.apps.eggs.models import AdditionalCost +from core.apps.eggs.serializers import AdditionalCostSerializer + + +class AdditionalCostView(generics.GenericAPIView): + queryset = AdditionalCost.objects.all() + serializer_class = AdditionalCostSerializer + permission_classes = [permissions.IsAuthenticated] + + def get(self, request, *args, **kwargs): + queryset = self.get_queryset() + serializer = self.get_serializer(queryset, many=True) + return Response(serializer.data) + + def post(self, request, *args, **kwargs): + serializer = self.get_serializer(data=request.data) + serializer.is_valid(raise_exception=True) + serializer.save() + return Response( + { + "message": "Qo'shimcha xarajat muvaffaqiyatli kiritildi.", + "data": serializer.data, + }, + status=status.HTTP_201_CREATED, + ) + + +# from collections import defaultdict +# from datetime import datetime +# from rest_framework import generics, status, permissions +# from rest_framework.response import Response +# +# from core.apps.eggs.models import AdditionalCost +# from core.apps.eggs.serializers import AdditionalCostSerializer +# +# +# class AdditionalCostView(generics.GenericAPIView): +# queryset = AdditionalCost.objects.all() +# serializer_class = AdditionalCostSerializer +# permission_classes = [permissions.IsAuthenticated] +# +# def get(self, request, *args, **kwargs): +# queryset = self.get_queryset() +# grouped_data = defaultdict(lambda: {"total_price": 0, "additional_costs": []}) +# +# for cost in queryset: +# month_year = cost.created_at.strftime("%m-%Y") +# grouped_data[month_year]["total_price"] += cost.price +# grouped_data[month_year]["additional_costs"].append(cost) +# +# response_data = [] +# for month_year, data in grouped_data.items(): +# month, year = month_year.split("-") +# response_data.append({ +# "month": month, +# "year": year, +# "total_price": data["total_price"], +# "additional_costs": AdditionalCostSerializer(data["additional_costs"], many=True).data +# }) +# +# return Response(response_data) +# +# def post(self, request, *args, **kwargs): +# serializer = self.get_serializer(data=request.data) +# serializer.is_valid(raise_exception=True) +# serializer.save() +# return Response( +# { +# "message": "Qo'shimcha xarajat muvaffaqiyatli kiritildi.", +# "data": serializer.data, +# }, +# status=status.HTTP_201_CREATED, +# ) diff --git a/core/apps/eggs/views/all_history.py b/core/apps/eggs/views/all_history.py new file mode 100644 index 0000000..d105f21 --- /dev/null +++ b/core/apps/eggs/views/all_history.py @@ -0,0 +1,11 @@ +from rest_framework import viewsets +from core.apps.eggs.models.all_history import AllHistory +from core.apps.eggs.serializers.all_history import AllHistorySerializer +from core.http.permissions import IsRole + + +class AllHistoryViewSet(viewsets.ModelViewSet): + queryset = AllHistory.objects.all() + serializer_class = AllHistorySerializer + permission_classes = [IsRole(["admin"])] + http_method_names = ["get"] diff --git a/core/apps/eggs/views/benefit.py b/core/apps/eggs/views/benefit.py new file mode 100644 index 0000000..ec3c434 --- /dev/null +++ b/core/apps/eggs/views/benefit.py @@ -0,0 +1,11 @@ +from django.http import JsonResponse +from rest_framework.views import APIView + +from core.apps.eggs.models import Party + + +class MonthlyBenefitView(APIView): + + def get(self, request, *args, **kwargs): + monthly_benefit = Party.get_total_benefit() + return JsonResponse(list(monthly_benefit), safe=False) diff --git a/core/apps/eggs/views/broken.py b/core/apps/eggs/views/broken.py new file mode 100644 index 0000000..f48f1eb --- /dev/null +++ b/core/apps/eggs/views/broken.py @@ -0,0 +1,11 @@ +from rest_framework import viewsets +from core.apps.eggs.models import Broken +from core.apps.eggs.serializers import BrokenSerializer +from core.http.permissions import IsRole + + +class BrokenViewSet(viewsets.ModelViewSet): + queryset = Broken.objects.all() + serializer_class = BrokenSerializer + http_method_names = ["get", "post", "put", "patch", "delete"] + permission_classes = [IsRole(["admin", "sklad", "courier"])] diff --git a/core/apps/eggs/views/courier.py b/core/apps/eggs/views/courier.py new file mode 100644 index 0000000..eb32895 --- /dev/null +++ b/core/apps/eggs/views/courier.py @@ -0,0 +1,16 @@ +""" +Courier view +""" + +from rest_framework import viewsets + +from core.apps.eggs import models +from core.apps.eggs import serializers +from core.http.permissions import IsRole + + +class CourierViewSet(viewsets.ModelViewSet): + queryset = models.Courier.objects.all() + serializer_class = serializers.CourierSerializer + http_method_names = ["get", "post", "put", "patch", "delete"] + permission_classes = [IsRole(["courier", "admin"])] diff --git a/core/apps/eggs/views/courier_history.py b/core/apps/eggs/views/courier_history.py new file mode 100644 index 0000000..461261b --- /dev/null +++ b/core/apps/eggs/views/courier_history.py @@ -0,0 +1,25 @@ +""" +Courier History view +""" + +from rest_framework import viewsets + +from core.apps.eggs import models +from core.apps.eggs import serializers +from core.http.permissions import IsRole + + +class CourierHistoryViewSet(viewsets.ModelViewSet): + serializer_class = serializers.CourierHistorySerializer + http_method_names = ["get"] + permission_classes = [IsRole(["admin", "courier"])] + + def get_queryset(self): + """ + This view should return a list of all the courier history + for the currently authenticated user. + """ + user = self.request.user + if user.role == "courier": + return models.CourierHistory.objects.filter(courier_id=user.id) + return models.CourierHistory.objects.all() diff --git a/core/apps/eggs/views/courier_product.py b/core/apps/eggs/views/courier_product.py new file mode 100644 index 0000000..7da1c26 --- /dev/null +++ b/core/apps/eggs/views/courier_product.py @@ -0,0 +1,58 @@ +""" +Courier Product view +""" + +from rest_framework import response, status, viewsets + +from core.apps.eggs import models, serializers +from core.http.permissions import IsRole + + +class CourierProductViewSet(viewsets.ModelViewSet): + http_method_names = ["get", "post", "put", "patch", "delete"] + permission_classes = [IsRole(["courier"])] + + def get_queryset(self): + user = self.request.user + return models.CourierProduct.objects.filter( + courier_id__user_id=user.id + ) + + def get_serializer_class(self): + if self.request.method == "GET": + return serializers.CourierProductSerializer + return serializers.CourierProductReturnSerializer + + +class ReturnEggsViewSet(viewsets.ViewSet): + permission_classes = [IsRole(["courier"])] + + def update(self, request, pk=None): + courier_products = models.CourierProduct.objects.filter( + courier_id__user_id=request.user.id + ) + + if not courier_products.exists(): + return response.Response( + {"error": "No products found for this courier."}, + status=status.HTTP_404_NOT_FOUND, + ) + + for courier_product in courier_products: + courier_product.group_id.quantity += courier_product.count + courier_product.group_id.party_id.courier_eggs -= ( + courier_product.count + ) + courier_product.group_id.party_id.remaining_count += ( + courier_product.count + ) + courier_product.return_eggs = courier_product.count + courier_product.count = 0 + courier_product.save() + courier_product.group_id.save() + courier_product.group_id.party_id.save() + courier_product.delete() + + return response.Response( + data={"status": "success"}, status=status.HTTP_200_OK + ) diff --git a/core/apps/eggs/views/daily_cost.py b/core/apps/eggs/views/daily_cost.py new file mode 100644 index 0000000..09897e9 --- /dev/null +++ b/core/apps/eggs/views/daily_cost.py @@ -0,0 +1,13 @@ +from rest_framework import viewsets +from rest_framework.response import Response + +from core.apps.eggs.models import DailyCost +from core.apps.eggs.serializers import DailyCostSerializer + + +class DailyCostViewSet(viewsets.ModelViewSet): + queryset = DailyCost.objects.all() + serializer_class = DailyCostSerializer + + def get_queryset(self): + return DailyCost.objects.all().order_by("-id")[:1] \ No newline at end of file diff --git a/core/apps/eggs/views/debt.py b/core/apps/eggs/views/debt.py new file mode 100644 index 0000000..72a1a50 --- /dev/null +++ b/core/apps/eggs/views/debt.py @@ -0,0 +1,11 @@ +from rest_framework import viewsets +from core.apps.eggs.models import Debt +from core.apps.eggs.serializers import DebtSerializer +from core.http.permissions import IsRole + + +class DebtViewSet(viewsets.ModelViewSet): + queryset = Debt.objects.all() + serializer_class = DebtSerializer + permission_classes = [IsRole(["admin", "courier"])] + http_method_names = ["post", "get"] diff --git a/core/apps/eggs/views/debt_list.py b/core/apps/eggs/views/debt_list.py new file mode 100644 index 0000000..538d181 --- /dev/null +++ b/core/apps/eggs/views/debt_list.py @@ -0,0 +1,20 @@ +from rest_framework import status +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView + +from core.apps.eggs.models import Debt +from core.apps.eggs.serializers import DebtSerializer + + +class UserDebtsAPIView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, id=None): + if id: + user_debts = Debt.objects.filter(market_id=id).order_by('-created_at') + else: + user_debts = Debt.objects.none() + + serializer = DebtSerializer(user_debts, many=True) + return Response(serializer.data, status=status.HTTP_200_OK) diff --git a/core/apps/eggs/views/delete_account.py b/core/apps/eggs/views/delete_account.py new file mode 100644 index 0000000..fdc80db --- /dev/null +++ b/core/apps/eggs/views/delete_account.py @@ -0,0 +1,26 @@ +from rest_framework import status +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView + +from core.apps.eggs.serializers import DeleteAccountSerializer + + +class DeleteAccountView(APIView): + permission_classes = [IsAuthenticated] + + def delete(self, request, *args, **kwargs): + serializer = DeleteAccountSerializer( + data=request.data, context={"request": request} + ) + if serializer.is_valid(): + user = request.user + user.delete() + return Response( + { + "status": "success", + "message": "Akkaunt muvaffaqiyatli o'chirildi.", + }, + status=status.HTTP_204_NO_CONTENT, + ) + return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST) diff --git a/core/apps/eggs/views/group.py b/core/apps/eggs/views/group.py new file mode 100644 index 0000000..bb2ea98 --- /dev/null +++ b/core/apps/eggs/views/group.py @@ -0,0 +1,16 @@ +""" +Group view +""" + +from rest_framework import viewsets + +from core.apps.eggs import models +from core.apps.eggs import serializers +from core.http.permissions import IsRole + + +class GroupViewSet(viewsets.ModelViewSet): + queryset = models.Group.objects.all() + serializer_class = serializers.GroupSerializer + http_method_names = ["get", "post", "put", "patch", "delete"] + permission_classes = [IsRole(["admin", "sklad", "courier", "market"])] diff --git a/core/apps/eggs/views/history.py b/core/apps/eggs/views/history.py new file mode 100644 index 0000000..a024fb5 --- /dev/null +++ b/core/apps/eggs/views/history.py @@ -0,0 +1,25 @@ +from rest_framework import viewsets + +from core.apps.eggs.models import History +from core.apps.eggs.serializers import HistorySerializer +from core.http.permissions import IsRole + + +class HistoryViewSet(viewsets.ModelViewSet): + serializer_class = HistorySerializer + http_method_names = ["get"] + permission_classes = [IsRole(["admin", "sklad", "courier", "market"])] + + def get_queryset(self): + user = self.request.user + action_param = self.request.query_params.get("action", None) + + if user.role == "admin": + queryset = History.objects.all() + else: + queryset = History.objects.filter(user_id=user.id) + + if action_param: + queryset = queryset.filter(action=action_param) + + return queryset diff --git a/core/apps/eggs/views/market.py b/core/apps/eggs/views/market.py new file mode 100644 index 0000000..f8385e1 --- /dev/null +++ b/core/apps/eggs/views/market.py @@ -0,0 +1,40 @@ +""" +Market view +""" + +from rest_framework import viewsets + +from core.apps.eggs import models +from core.apps.eggs import serializers +from core.http.permissions import IsRole + + +class MarketViewSet(viewsets.ModelViewSet): + queryset = models.Market.objects.all() + serializer_class = serializers.MarketSerializer + http_method_names = ["get", "post", "put", "patch", "delete"] + permission_classes = [IsRole(["sklad", "admin", "courier", "market"])] + + def get_queryset(self): + user = self.request.user + if user.role in ["admin", "sklad", "courier"]: + return models.Market.objects.all() + elif user.role == "market": + return models.Market.objects.filter(user_id=user.id) + return None + + +class MarketHistoryViewSet(viewsets.ModelViewSet): + queryset = models.Market.objects.all() + serializer_class = serializers.MarketHistorySerializer + http_method_names = ["get"] + permission_classes = [IsRole(["sklad", "admin", "courier", "market"])] + + def get_queryset(self): + user = self.request.user + if user.role in ["admin", "sklad", "courier"]: + return models.Market.objects.all().order_by("-debts__created_at") + elif user.role == "market": + return models.Market.objects.filter(user_id=user.id).order_by("-debts__created_at") + return None + diff --git a/core/apps/eggs/views/monitoring.py b/core/apps/eggs/views/monitoring.py new file mode 100644 index 0000000..77e8d82 --- /dev/null +++ b/core/apps/eggs/views/monitoring.py @@ -0,0 +1,112 @@ +from datetime import datetime +from datetime import timedelta + +from django.db.models import Sum +from rest_framework import serializers +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView + +from core.apps.eggs.models import Monitoring + + +class MonitoringSerializer(serializers.ModelSerializer): + class Meta: + model = Monitoring + fields = [ + "id", + "content_type", + "object_id", + "action", + "timestamp", + "created_by", + "created_who", + "reason", + "comment", + "price", + ] + + +class MonitoringSummarySerializer(serializers.Serializer): + from_date = serializers.CharField() + end_date = serializers.CharField() + enter_price = serializers.DecimalField(max_digits=30, decimal_places=2) + exit_price = serializers.DecimalField(max_digits=30, decimal_places=2) + profit = serializers.DecimalField(max_digits=30, decimal_places=2) + results = MonitoringSerializer(many=True) + + +class MonitoringSummaryView(APIView): + permission_classes = [IsAuthenticated] + + def get(self, request, *args, **kwargs): + from_date = request.query_params.get("from_date") + end_date = request.query_params.get("end_date") + + if from_date and end_date: + start_date = datetime.strptime(from_date, "%Y-%m-%d") + end_date = datetime.strptime(end_date, "%Y-%m-%d") + timedelta( + days=1 + ) + else: + start_date = None + end_date = None + + enter_actions = ["Kirim", "debt_created", "order_created"] + exit_actions = [ + "Chiqim", + "additional_cost_created", + "broken_created", + "party_created", + ] + + if start_date and end_date: + enter_price = ( + Monitoring.objects.filter( + action__in=enter_actions, + timestamp__range=[start_date, end_date], + ).aggregate(total=Sum("price"))["total"] + or 0 + ) + + exit_price = ( + Monitoring.objects.filter( + action__in=exit_actions, + timestamp__range=[start_date, end_date], + ).aggregate(total=Sum("price"))["total"] + or 0 + ) + + results = Monitoring.objects.filter( + timestamp__range=[start_date, end_date] + ) + else: + enter_price = ( + Monitoring.objects.filter(action__in=enter_actions).aggregate( + total=Sum("price") + )["total"] + or 0 + ) + + exit_price = ( + Monitoring.objects.filter(action__in=exit_actions).aggregate( + total=Sum("price") + )["total"] + or 0 + ) + + results = Monitoring.objects.all() + + profit = enter_price - exit_price + + data = { + "from_date": from_date, + "end_date": end_date, + "enter_price": enter_price, + "exit_price": exit_price, + "profit": profit, + "results": results, + } + + serializer = MonitoringSummarySerializer(data) + return Response(serializer.data) diff --git a/core/apps/eggs/views/order.py b/core/apps/eggs/views/order.py new file mode 100644 index 0000000..72cc067 --- /dev/null +++ b/core/apps/eggs/views/order.py @@ -0,0 +1,32 @@ +""" +Order view +""" + +from django.core.exceptions import PermissionDenied +from rest_framework import viewsets + +from core.apps.eggs import models, serializers +from core.http.permissions import IsRole + + +class OrderViewSet(viewsets.ModelViewSet): + http_method_names = ["get", "post", "put", "patch", "delete"] + permission_classes = [IsRole(["courier", "admin", "sklad", "market"])] + + def get_queryset(self): + user = self.request.user + if user.role == "market": + try: + market_instance = models.Market.objects.get(user_id=user.id) + return models.Order.objects.filter( + market_id=market_instance.id + ) + except models.Market.DoesNotExist: + raise PermissionDenied("Bunday Market mavjud emas.") + return models.Order.objects.all() + + def get_serializer_class(self): + if self.request.method == "GET": + return serializers.OrderSerializer + else: + return serializers.OrderCreateSerializer diff --git a/core/apps/eggs/views/party.py b/core/apps/eggs/views/party.py new file mode 100644 index 0000000..3033026 --- /dev/null +++ b/core/apps/eggs/views/party.py @@ -0,0 +1,45 @@ +""" +Party view +""" + +from django.db.models import Q +from rest_framework import viewsets +from rest_framework.decorators import action +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response + +from core.apps.eggs import models +from core.apps.eggs import serializers +from core.http.permissions import IsRole + + +class PartViewSet(viewsets.ModelViewSet): + queryset = models.Party.objects.all() + http_method_names = ["get", "post", "put", "patch", "delete"] + permission_classes = [IsAuthenticated, IsRole(["admin", "sklad", "courier"])] + + def get_serializer_class(self): + if self.request.method == "GET": + return serializers.PartySerializer + else: + return serializers.PartyCreateSerializer + + @action(detail=False, methods=["get"]) + def search(self, request): + search_query = request.query_params.get("query", "") + results = self.queryset.filter( + Q(user_id__username__icontains=search_query) + | Q(count__icontains=search_query) + | Q(price__icontains=search_query) + | Q(sold__icontains=search_query) + | Q(sold_price__icontains=search_query) + | Q(invoices__name__icontains=search_query) + | Q(groups__product_id__name__icontains=search_query) + | Q(groups__entry_price__icontains=search_query) + | Q(groups__unit_price__icontains=search_query) + | Q(groups__wholesale_price__icontains=search_query) + | Q(groups__quantity__icontains=search_query) + | Q(groups__broken_eggs__icontains=search_query) + ).distinct() + serializer = self.get_serializer(results, many=True) + return Response() diff --git a/core/apps/eggs/views/product.py b/core/apps/eggs/views/product.py new file mode 100644 index 0000000..610d79a --- /dev/null +++ b/core/apps/eggs/views/product.py @@ -0,0 +1,16 @@ +""" +Product view +""" + +from rest_framework import viewsets + +from core.apps.eggs import models +from core.apps.eggs import serializers +from core.http.permissions import IsRole + + +class ProductViewSet(viewsets.ModelViewSet): + queryset = models.Product.objects.all() + serializer_class = serializers.ProductSerializer + http_method_names = ["get", "post", "put", "patch", "delete"] + permission_classes = [IsRole(["admin", "sklad", "market", "courier"])] diff --git a/core/apps/eggs/views/sklad.py b/core/apps/eggs/views/sklad.py new file mode 100644 index 0000000..71575d4 --- /dev/null +++ b/core/apps/eggs/views/sklad.py @@ -0,0 +1,21 @@ +from rest_framework import viewsets +from rest_framework.permissions import IsAuthenticated + +from core.apps.eggs.models import User +from core.apps.eggs.serializers import ( + AddSkladSerializer, + SkladListSerializer, +) +from core.http.permissions import IsRole + + +class AddSkladViewSet(viewsets.ModelViewSet): + permission_classes = [IsAuthenticated, IsRole(["admin"])] + queryset = User.objects.filter(role="sklad") + http_method_names = ["get", "post"] + + def get_serializer_class(self): + if self.request.method == "GET": + return SkladListSerializer + else: + return AddSkladSerializer diff --git a/core/apps/eggs/views/top_couriers.py b/core/apps/eggs/views/top_couriers.py new file mode 100644 index 0000000..ea07f2a --- /dev/null +++ b/core/apps/eggs/views/top_couriers.py @@ -0,0 +1,81 @@ +from itertools import groupby + +from django.db.models import F, Func, Sum, Value +from django.db.models.functions import Concat, TruncDay, TruncMonth, TruncYear +from rest_framework.response import Response +from rest_framework.views import APIView + +from core.apps.eggs.models import Order +from core.http.permissions import IsRole + + +class WeekOfMonth(Func): + function = "EXTRACT" + template = "(%(function)s('day' from %(expressions)s) - 1) / 7 + 1" + + def as_postgresql(self, compiler, connection): + self.template = ( + "ROUND((EXTRACT('day' from %(expressions)s) - 1) / 7 + 1)" + ) + return super().as_sql(compiler, connection) + + +class DayName(Func): + function = "to_char" + template = "%(function)s(%(expressions)s, 'Day')" + + +class TopCourierView(APIView): + permission_classes = [IsRole(["admin"])] + + def get(self, request): + top_couriers = ( + Order.objects.annotate( + year=TruncYear("created_at"), + month=TruncMonth("created_at"), + day=TruncDay("created_at"), + day_name=DayName("created_at"), + full_name=Concat( + F("courier_id__user_id__first_name"), + Value(" "), + F("courier_id__user_id__last_name"), + ), + ) + .values( + "year", + "month", + "day", + "day_name", + "full_name", + ) + .annotate(value=Sum("order_items__count")) + .order_by("year", "month", "day", "-value") + )[:3] + + data = [] + for year, year_data in groupby(top_couriers, key=lambda x: x["year"]): + year_dict = {"Year": year.year, "Months": []} + for month, month_data in groupby( + year_data, key=lambda x: x["month"] + ): + month_dict = {"month": month.strftime("%B"), "days": []} + for day, day_data in groupby( + month_data, key=lambda x: x["day"] + ): + day_data_list = list(day_data) + people_list = [ + { + "full_name": person["full_name"], + "value": person["value"], + } + for person in day_data_list + ] + day_dict = { + "day": day.day, + "day_name": day_data_list[0]["day_name"].strip(), + "people": people_list, + } + month_dict["days"].append(day_dict) + year_dict["Months"].append(month_dict) + data.append(year_dict) + return Response(data) diff --git a/core/apps/eggs/views/top_products.py b/core/apps/eggs/views/top_products.py new file mode 100644 index 0000000..54749f3 --- /dev/null +++ b/core/apps/eggs/views/top_products.py @@ -0,0 +1,32 @@ +from django.db.models import Sum +from django.shortcuts import get_object_or_404 +from rest_framework.permissions import IsAuthenticated +from rest_framework.response import Response +from rest_framework.views import APIView + +from core.apps.eggs.models import OrderItems, Product +from core.http.permissions import IsRole + + +class TopProductsView(APIView): + permission_classes = [IsRole(["admin"]), IsAuthenticated] + + def get(self, request): + top_products = ( + OrderItems.objects.filter(order_id__status__in=["done", "success"]) + .values("courier_product_id__group_id__product_id") + .annotate(product_count=Sum("count")) + .order_by("-product_count") + ) + response_data = [] + for item in top_products: + product = get_object_or_404( + Product, id=item["courier_product_id__group_id__product_id"] + ) + response_data.append( + { + "product_name": product.name, + "product_count": item["product_count"], + } + ) + return Response(response_data) diff --git a/core/apps/home/__init__.py b/core/apps/home/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/core/apps/home/admin/__init__.py b/core/apps/home/admin/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/home/apps.py b/core/apps/home/apps.py new file mode 100755 index 0000000..6896495 --- /dev/null +++ b/core/apps/home/apps.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class HomeConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "core.apps.home" diff --git a/core/apps/home/migrations/__init__.py b/core/apps/home/migrations/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/core/apps/home/models/__init__.py b/core/apps/home/models/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/home/tests/__init__.py b/core/apps/home/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/apps/home/urls.py b/core/apps/home/urls.py new file mode 100755 index 0000000..3d4bfbf --- /dev/null +++ b/core/apps/home/urls.py @@ -0,0 +1,23 @@ +""" +Home app urls +""" + +from django.urls import path +from django.urls import include + +from rest_framework import routers + +from core.apps.home import views + +router = routers.DefaultRouter() +router.register("", views.PostListView, basename="posts") + +urlpatterns = [ + path( + "messages/", + views.FrontendTranslationView.as_view(), + name="frontend-translation", + ), # noqa + path("posts/", include(router.urls), name="posts"), + path("", views.HomeView.as_view(), name="home"), +] diff --git a/core/apps/home/views/__init__.py b/core/apps/home/views/__init__.py new file mode 100644 index 0000000..0be7deb --- /dev/null +++ b/core/apps/home/views/__init__.py @@ -0,0 +1,3 @@ +from .frontend import * # noqa +from .home import * # noqa +from .post import * # noqa diff --git a/core/apps/home/views/frontend.py b/core/apps/home/views/frontend.py new file mode 100644 index 0000000..e149daa --- /dev/null +++ b/core/apps/home/views/frontend.py @@ -0,0 +1,32 @@ +""" +Admin panel UI view +""" + +from rest_framework import status +from rest_framework import generics + +from core.http import views +from core.http import models +from core.http import serializers + + +class FrontendTranslationView(generics.ListAPIView, views.ApiResponse): + queryset = models.FrontendTranslation.objects.all() + serializer_class = serializers.FrontendTransactionSerializer + + def get(self, request, **kwargs): + serializer = self.get_serializer(self.get_queryset(), many=True) + data = {} + + for obj in serializer.data: + data[obj["key"]] = obj["value"] + return self.success(data=data, status=status.HTTP_200_OK) + + def get_queryset(self): + queryset = self.queryset.all() + key = self.request.GET.get("key") + + if key: + queryset = queryset.filter(key__icontains=key) + + return queryset diff --git a/core/apps/home/views/home.py b/core/apps/home/views/home.py new file mode 100644 index 0000000..1749e61 --- /dev/null +++ b/core/apps/home/views/home.py @@ -0,0 +1,7 @@ +from django import views +from django import shortcuts + + +class HomeView(views.View): + def get(self, request): + return shortcuts.render(request, "user/home.html") diff --git a/core/apps/home/views/post.py b/core/apps/home/views/post.py new file mode 100644 index 0000000..61c74fb --- /dev/null +++ b/core/apps/home/views/post.py @@ -0,0 +1,12 @@ +from rest_framework import viewsets + +from core.http import models +from core.http import serializers + + +class PostListView(viewsets.ModelViewSet): + queryset = models.Post.objects.all() + serializer_class = serializers.PostSerializer + + def dispatch(self, request, *args, **kwargs): + return super().dispatch(request, *args, **kwargs) diff --git a/core/apps/logs/.gitignore b/core/apps/logs/.gitignore new file mode 100755 index 0000000..c96a04f --- /dev/null +++ b/core/apps/logs/.gitignore @@ -0,0 +1,2 @@ +* +!.gitignore \ No newline at end of file diff --git a/core/console/__init__.py b/core/console/__init__.py new file mode 100755 index 0000000..3d7d504 --- /dev/null +++ b/core/console/__init__.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class ConsoleConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "core.console" diff --git a/core/console/management/__init__.py b/core/console/management/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/core/console/management/commands/__init__.py b/core/console/management/commands/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/core/console/management/commands/factory.py b/core/console/management/commands/factory.py new file mode 100755 index 0000000..cb88cd4 --- /dev/null +++ b/core/console/management/commands/factory.py @@ -0,0 +1,59 @@ +import importlib +import typing + +from django.conf import settings +from django.core import management +from tqdm import tqdm + + +class Command(management.BaseCommand): + help = "factory database with" + + def print(self, message, is_type="success"): + if is_type == "success": + self.stdout.write(self.style.SUCCESS(message)) + else: + self.stdout.write(self.style.ERROR(message)) + + def handle(self, *args, **options): + FACTORYS: list[typing.Any] | typing.Any = ( + settings.FACTORYS if hasattr(settings, "FACTORYS") else [] + ) + + if len(FACTORYS) == 0: + self.print( + "FACTORYS not defined:\n\nsettings file add FACTORYS variable", + "error", + ) + return + + for factory in FACTORYS: + count = factory[1] + factory = factory[0] + + class_name = str(factory).split(".")[-1] + module_path = ".".join(str(factory).split(".")[:-1]) + module = importlib.import_module(module_path) + my_class = getattr(module, class_name)() + + if not hasattr(my_class, "handle"): + self.print("Handle function not found", "error") + return + if not hasattr(my_class, "model") or my_class.model is None: + self.print("Model not found", "error") + return + try: + self.print(f"Start factory: {factory}") + progress_bar = tqdm(total=count) + for i in range(count): + progress_bar.update(1) + try: + data = my_class.handle() + model = my_class.model + model.objects.create(**data) + except Exception as e: + self.print(e, "error") + except Exception as e: + self.print(f"ERROR: {class_name} {e}", "error") + return + self.print(f"SUCCESS: {class_name}") diff --git a/core/console/management/commands/makeapp.py b/core/console/management/commands/makeapp.py new file mode 100755 index 0000000..a94e282 --- /dev/null +++ b/core/console/management/commands/makeapp.py @@ -0,0 +1,31 @@ +""" +Create a new app in django project command +""" + +import os + +from django.conf import settings +from django.core.management import base + + +class Command(base.BaseCommand): + help = "Generate new app" + + def add_arguments(self, parser): + parser.add_argument("name") + + def handle(self, *args, **options): + name = options.get("name") + + if os.path.exists( + os.path.join(settings.BASE_DIR, f"core/apps/{name}") + ): + self.stdout.write(self.style.ERROR(f"App {name} already")) + return + try: + os.system( + f"cd ./core/apps && python3 ./../../manage.py startapp {name}" + ) + self.stdout.write(self.style.SUCCESS(f"Make app {name} created")) + except Exception as e: + self.stdout.write(self.style.ERROR(f"Error: {e}")) diff --git a/core/console/management/commands/makefactory.py b/core/console/management/commands/makefactory.py new file mode 100755 index 0000000..62e649b --- /dev/null +++ b/core/console/management/commands/makefactory.py @@ -0,0 +1,7 @@ +from core.utils import console + + +class Command(console.BaseMake): + help = "Factory seeder" + path = "database/factory" + name = "Factory" diff --git a/core/console/management/commands/makeseeder.py b/core/console/management/commands/makeseeder.py new file mode 100755 index 0000000..44f85da --- /dev/null +++ b/core/console/management/commands/makeseeder.py @@ -0,0 +1,7 @@ +from core.utils import console + + +class Command(console.BaseMake): + help = "Create seeder" + path = "database/seeder" + name = "Seeder" diff --git a/core/console/management/commands/seed.py b/core/console/management/commands/seed.py new file mode 100755 index 0000000..035dee5 --- /dev/null +++ b/core/console/management/commands/seed.py @@ -0,0 +1,40 @@ +import importlib + +from django.conf import settings +from django.core import management + + +class Command(management.BaseCommand): + help = "seeder database with" + + def print(self, message, is_type="success"): + if is_type == "success": + self.stdout.write(self.style.SUCCESS(message)) + else: + self.stdout.write(self.style.ERROR(message)) + + def handle(self, *args, **options): + SEEDERS = settings.SEEDERS if hasattr(settings, "SEEDERS") else [] + + if len(SEEDERS) == 0: + self.print( + "SEEDERS not defined:\n\nsettings file add SEEDERS variable", + "error", + ) + return + + for seeder in SEEDERS: + class_name = str(seeder).split(".")[-1] + module_path = ".".join(str(seeder).split(".")[:-1]) + module = importlib.import_module(module_path) + my_class = getattr(module, class_name)() + + if not hasattr(my_class, "run"): + self.print("run function not found", "error") + return + try: + my_class.run() + except Exception as e: + self.print(f"ERROR: {class_name} {e}", "error") + return + self.print(f"SUCCESS: {class_name}") diff --git a/core/enums/__init__.py b/core/enums/__init__.py new file mode 100755 index 0000000..bb67a43 --- /dev/null +++ b/core/enums/__init__.py @@ -0,0 +1 @@ +from .core import * diff --git a/core/enums/core.py b/core/enums/core.py new file mode 100755 index 0000000..e9cccad --- /dev/null +++ b/core/enums/core.py @@ -0,0 +1,42 @@ +class Codes: + # Database errors + DB_CONNECTION_ERROR = 1001 + DB_SQL_EXECUTION_ERROR = 1002 + DB_READ_ERROR = 1003 + DB_WRITE_ERROR = 1004 + + # Filesystem errors + FILE_OPEN_ERROR = 2001 + FILE_WRITE_ERROR = 2002 + FILE_READ_ERROR = 2003 + FILE_DELETE_ERROR = 2004 + + # Connection errors + NETWORK_CONNECTION_ERROR = 3001 + NETWORK_HTTP_REQUEST_ERROR = 3002 + NETWORK_WEBSOCKET_ERROR = 3003 + + # Programming errors + INVALID_PARAMETER_VALUE = 4001 + REQUIRED_PARAMETER_MISSING = 4002 + CONSTRAINT_VIOLATION = 4003 + INVALID_PARAMETERS = 4006 + NOT_FOUND_ERROR = 4004 + USER_ALREADY_EXISTS_ERROR = 4005 + USER_NOT_FOUND_ERROR = 4006 + BOCKED_ERROR = 4007 + INVALID_OTP_ERROR = 4008 + + # Security errors + AUTHENTICATION_ERROR = 5001 + UNAUTHORIZED_ACCESS = 5002 + CSRF_DETECTED = 5003 + INVALID_CREDENTIALS = 5005 + + +class Messages: + SEND_MESSAGE = "Sms %(phone)s raqamiga yuborildi" + USER_ALREADY_EXISTS = "User already exists" + OTP_CONFIRMED = "Tasdiqlash ko'di qabul qilindi" + INVALID_OTP = "Tasdiqlash ko'di xato" + CHANGED_PASSWORD = "Parol o'zgartirildi" diff --git a/core/exceptions/__init__.py b/core/exceptions/__init__.py new file mode 100755 index 0000000..151ee18 --- /dev/null +++ b/core/exceptions/__init__.py @@ -0,0 +1 @@ +from .core import * # noqa diff --git a/core/exceptions/core.py b/core/exceptions/core.py new file mode 100755 index 0000000..c07a3fd --- /dev/null +++ b/core/exceptions/core.py @@ -0,0 +1,40 @@ +""" +Raise exception +""" + +from rest_framework import exceptions + + +class SmsException(Exception): + """ + Sms exception + """ + + def __init__(self, message, **kwargs): + super().__init__(message) + self.kwargs = kwargs + + +class BreakException(Exception): + """ + Break exception + """ + + def __init__(self, *args, message: str = None, data=None): + if data is None: + data = [] + self.args = args + self.message = message + self.data = data + + +class MyApiException(exceptions.APIException): + """ + My API Exception for API exceptions status code edit + """ + + status_code = 400 + + def __init__(self, message, status_code): + super().__init__(message) + self.status_code = status_code diff --git a/core/http/__init__.py b/core/http/__init__.py new file mode 100755 index 0000000..b956b82 --- /dev/null +++ b/core/http/__init__.py @@ -0,0 +1,6 @@ +from django.apps import AppConfig + + +class HttpConfig(AppConfig): + default_auto_field = "django.db.models.BigAutoField" + name = "core.http" diff --git a/core/http/admin/__init__.py b/core/http/admin/__init__.py new file mode 100644 index 0000000..35f15f1 --- /dev/null +++ b/core/http/admin/__init__.py @@ -0,0 +1,3 @@ +from .core import * # noqa +from .another import * # noqa +from .user import * # noqa diff --git a/core/http/admin/another.py b/core/http/admin/another.py new file mode 100644 index 0000000..2f72abc --- /dev/null +++ b/core/http/admin/another.py @@ -0,0 +1,66 @@ +from django.contrib import admin +from django.db import models as db_model +from django_select2 import forms as django_select2 +from import_export import admin as import_export +from modeltranslation import admin as modeltranslation + +from core.http import forms, models + + +class PostInline(admin.TabularInline): + model = models.Post.comments.through + fields = ["comment"] + extra = 1 + + +class TagsInline(admin.TabularInline): + model = models.Post.tags.through + extra = 1 + + +class PostAdmin( + modeltranslation.TabbedTranslationAdmin, + import_export.ImportExportModelAdmin, +): # noqa + fields: tuple = ("title", "desc", "image", "tags") + search_fields: list = ["title", "desc"] + list_filter = ["title"] + required_languages: tuple = ("uz",) + form = forms.PostAdminForm + inlines = [PostInline] + formfield_overrides = { + db_model.ManyToManyField: { + "widget": django_select2.Select2MultipleWidget + } + } + + +class TagsAdmin(import_export.ImportExportModelAdmin): + fields: tuple = ("name",) + search_fields: list = ["name"] + + +class FrontendInline(admin.TabularInline): + model = models.FrontendTranslation.comments.through + fields = ["comment"] + extra = 1 + + +class FrontendTranslationAdmin( + modeltranslation.TabbedTranslationAdmin, + import_export.ImportExportModelAdmin, +): # noqa + fields: tuple = ("key", "value") + required_languages: tuple = ("uz",) + list_display = ["key", "value"] + inlines = [FrontendInline] + + +class SmsConfirmAdmin(admin.ModelAdmin): + list_display = ["phone", "code", "resend_count", "try_count"] + search_fields = ["phone", "code"] + + +class CommentAdmin(import_export.ImportExportModelAdmin): + list_display = ["text"] + search_fields = ["text"] diff --git a/core/http/admin/core.py b/core/http/admin/core.py new file mode 100644 index 0000000..f0b663f --- /dev/null +++ b/core/http/admin/core.py @@ -0,0 +1,22 @@ +""" +Admin panel register +""" + +from django.contrib import admin +from django.contrib.auth import models as db_models + +from core.http import models +from core.http.admin import user +from core.http.admin import another + +admin.site.unregister(db_models.Group) +admin.site.register(db_models.Group, user.GroupAdmin) + +admin.site.register(models.Tags, another.TagsAdmin) +admin.site.register(models.Post, another.PostAdmin) +admin.site.register(models.User, user.CustomUserAdmin) +admin.site.register(models.Comment, another.CommentAdmin) +admin.site.register(models.SmsConfirm, another.SmsConfirmAdmin) +admin.site.register( + models.FrontendTranslation, another.FrontendTranslationAdmin +) # noqa diff --git a/core/http/admin/user.py b/core/http/admin/user.py new file mode 100644 index 0000000..9a9edcc --- /dev/null +++ b/core/http/admin/user.py @@ -0,0 +1,47 @@ +from django.contrib.auth import admin +from import_export import admin as import_export +from import_export.admin import ImportExportModelAdmin + +from core.http.forms import CustomUserCreationForm + + +class CustomUserAdmin(admin.UserAdmin, ImportExportModelAdmin): + add_form = CustomUserCreationForm + list_display = ["id", "phone", "first_name", "last_name", "role"] + search_fields = ["phone", "first_name", "last_name"] + list_filter = ["role"] + fieldsets = ( + (None, {"fields": ("username", "phone", "password")}), + ( + "Personal info", + {"fields": ("first_name", "last_name", "email", "role", "avatar", "fcm_token")}, + ), + ( + "Permissions", + { + "fields": ( + "is_active", + "is_staff", + "is_superuser", + "groups", + "user_permissions", + ) + }, + ), + ("Important dates", {"fields": ("last_login", "date_joined")}), + ) + add_fieldsets = ( + ( + None, + { + "classes": ("wide",), + "fields": ("phone", "password1", "password2"), + }, + ), + ) + + +class GroupAdmin(import_export.ImportExportModelAdmin): + list_display = ["name"] + search_fields = ["name"] + filter_horizontal = ("permissions",) diff --git a/core/http/database/__init__.py b/core/http/database/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/core/http/database/factory/__init__.py b/core/http/database/factory/__init__.py new file mode 100755 index 0000000..151ee18 --- /dev/null +++ b/core/http/database/factory/__init__.py @@ -0,0 +1 @@ +from .core import * # noqa diff --git a/core/http/database/factory/core.py b/core/http/database/factory/core.py new file mode 100755 index 0000000..79221f8 --- /dev/null +++ b/core/http/database/factory/core.py @@ -0,0 +1,31 @@ +""" +Create a new fake User/Post +""" + +from core.http import models +from core.utils import factory + + +class UserFactory(factory.BaseFaker): + model = models.User + + def handle(self): + """ + Factory method + """ + return { + "first_name": self.faker.first_name(), + "username": self.faker.user_name(), + "phone": self.faker.phone_number(), + } + + +class PostFactory(factory.BaseFaker): + model = models.Post + + def handle(self): + return { + "title": self.faker.name(), + "desc": self.faker.text(), + "image": self.faker.image_url(), + } diff --git a/core/http/database/seeder/__init__.py b/core/http/database/seeder/__init__.py new file mode 100755 index 0000000..151ee18 --- /dev/null +++ b/core/http/database/seeder/__init__.py @@ -0,0 +1 @@ +from .core import * # noqa diff --git a/core/http/database/seeder/core.py b/core/http/database/seeder/core.py new file mode 100755 index 0000000..297fb84 --- /dev/null +++ b/core/http/database/seeder/core.py @@ -0,0 +1,11 @@ +""" +Create a new user/superuser +""" + +from core.http import models + + +class UserSeeder: + def run(self): + models.User.objects.create_user("998943990509", "2309") + models.User.objects.create_superuser("998888112309", "2309") diff --git a/core/http/forms/__init__.py b/core/http/forms/__init__.py new file mode 100644 index 0000000..d6b994e --- /dev/null +++ b/core/http/forms/__init__.py @@ -0,0 +1,2 @@ +from .another import * # noqa +from .user import * # noqa diff --git a/core/http/forms/another.py b/core/http/forms/another.py new file mode 100644 index 0000000..75a044c --- /dev/null +++ b/core/http/forms/another.py @@ -0,0 +1,17 @@ +""" +Project base forms +""" + +from django import forms +from django_ckeditor_5 import widgets + +from core.http import models + + +class PostAdminForm(forms.ModelForm): + class Meta: + model = models.Post + widgets = { + "desc": widgets.CKEditor5Widget(), + } + fields = "__all__" diff --git a/core/http/forms/user.py b/core/http/forms/user.py new file mode 100644 index 0000000..3eb3d1c --- /dev/null +++ b/core/http/forms/user.py @@ -0,0 +1,8 @@ +from django.contrib.auth.forms import UserCreationForm +from core.http.models import User + + +class CustomUserCreationForm(UserCreationForm): + class Meta(UserCreationForm.Meta): + model = User + fields = ("id", "phone") diff --git a/core/http/management/__init__.py b/core/http/management/__init__.py new file mode 100755 index 0000000..151ee18 --- /dev/null +++ b/core/http/management/__init__.py @@ -0,0 +1 @@ +from .core import * # noqa diff --git a/core/http/management/commands/__init__.py b/core/http/management/commands/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/core/http/management/commands/clearcache.py b/core/http/management/commands/clearcache.py new file mode 100755 index 0000000..fe5e48a --- /dev/null +++ b/core/http/management/commands/clearcache.py @@ -0,0 +1,16 @@ +""" +Clear cache command +""" + +from django.core.cache import cache +from django.core.management import BaseCommand + +from core.utils import console + + +class Command(BaseCommand): + help = "Clear all caches" + + def handle(self, *args, **options): + cache.clear() + console.Console.success("Cache cleared successfully") diff --git a/core/http/management/core.py b/core/http/management/core.py new file mode 100755 index 0000000..a03ce0f --- /dev/null +++ b/core/http/management/core.py @@ -0,0 +1,10 @@ +from django.db.models.manager import BaseManager as BManager +from django.db.models.query import QuerySet + + +class BaseQuerySet(QuerySet): + pass + + +class BaseManager(BManager): + pass diff --git a/core/http/managers/__init__.py b/core/http/managers/__init__.py new file mode 100644 index 0000000..1000b27 --- /dev/null +++ b/core/http/managers/__init__.py @@ -0,0 +1 @@ +from .user import * # noqa diff --git a/core/http/managers/user.py b/core/http/managers/user.py new file mode 100644 index 0000000..3d5fc87 --- /dev/null +++ b/core/http/managers/user.py @@ -0,0 +1,24 @@ +from django.contrib.auth import base_user + + +class UserManager(base_user.BaseUserManager): + + def create_user(self, phone, password=None, **extra_fields): + if not phone: + raise ValueError("The phone number must be set") + + user = self.model(phone=phone, **extra_fields) + user.set_password(password) + user.save(using=self._db) + return user + + def create_superuser(self, phone, password=None, **extra_fields): + extra_fields.setdefault("is_staff", True) + extra_fields.setdefault("is_superuser", True) + + if extra_fields.get("is_staff") is not True: + raise ValueError("Superuser must have is_staff=True.") + if extra_fields.get("is_superuser") is not True: + raise ValueError("Superuser must have is_superuser=True.") + + return self.create_user(phone, password, **extra_fields) diff --git a/core/http/migrations/0001_initial.py b/core/http/migrations/0001_initial.py new file mode 100644 index 0000000..85e4869 --- /dev/null +++ b/core/http/migrations/0001_initial.py @@ -0,0 +1,276 @@ +# Generated by Django 5.0.4 on 2024-04-22 11:17 + +import django.db.models.deletion +import django.utils.timezone +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + ("auth", "0012_alter_user_first_name_max_length"), + ("contenttypes", "0002_remove_content_type_name"), + ] + + operations = [ + migrations.CreateModel( + name="BaseComment", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "polymorphic_ctype", + models.ForeignKey( + editable=False, + null=True, + on_delete=django.db.models.deletion.CASCADE, + related_name="polymorphic_%(app_label)s.%(class)s_set+", + to="contenttypes.contenttype", + ), + ), + ], + options={ + "abstract": False, + "base_manager_name": "objects", + }, + ), + migrations.CreateModel( + name="Comment", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("text", models.CharField(max_length=255)), + ], + ), + migrations.CreateModel( + name="SmsConfirm", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("code", models.IntegerField()), + ("try_count", models.IntegerField(default=0)), + ("resend_count", models.IntegerField(default=0)), + ("phone", models.CharField(max_length=20)), + ("expire_time", models.DateTimeField(blank=True, null=True)), + ("unlock_time", models.DateTimeField(blank=True, null=True)), + ( + "resend_unlock_time", + models.DateTimeField(blank=True, null=True), + ), + ], + ), + migrations.CreateModel( + name="Tags", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("name", models.CharField(max_length=255)), + ], + options={ + "verbose_name": "Tag", + "verbose_name_plural": "Tags", + }, + ), + migrations.CreateModel( + name="User", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "password", + models.CharField(max_length=128, verbose_name="password"), + ), + ( + "last_login", + models.DateTimeField( + blank=True, null=True, verbose_name="last login" + ), + ), + ( + "is_superuser", + models.BooleanField( + default=False, + help_text="Designates that this user has all permissions without explicitly assigning them.", + verbose_name="superuser status", + ), + ), + ( + "first_name", + models.CharField( + blank=True, max_length=150, verbose_name="first name" + ), + ), + ( + "last_name", + models.CharField( + blank=True, max_length=150, verbose_name="last name" + ), + ), + ( + "email", + models.EmailField( + blank=True, + max_length=254, + verbose_name="email address", + ), + ), + ( + "is_staff", + models.BooleanField( + default=False, + help_text="Designates whether the user can log into this admin site.", + verbose_name="staff status", + ), + ), + ( + "is_active", + models.BooleanField( + default=True, + help_text="Designates whether this user should be treated as active. Unselect this instead of deleting accounts.", + verbose_name="active", + ), + ), + ( + "date_joined", + models.DateTimeField( + default=django.utils.timezone.now, + verbose_name="date joined", + ), + ), + ("phone", models.CharField(max_length=255, unique=True)), + ( + "username", + models.CharField(blank=True, max_length=255, null=True), + ), + ("created_at", models.DateTimeField(auto_now_add=True)), + ("updated_at", models.DateTimeField(auto_now=True)), + ("validated_at", models.DateTimeField(blank=True, null=True)), + ( + "groups", + models.ManyToManyField( + blank=True, + help_text="The groups this user belongs to. A user will get all permissions granted to each of their groups.", + related_name="user_set", + related_query_name="user", + to="auth.group", + verbose_name="groups", + ), + ), + ( + "user_permissions", + models.ManyToManyField( + blank=True, + help_text="Specific permissions for this user.", + related_name="user_set", + related_query_name="user", + to="auth.permission", + verbose_name="user permissions", + ), + ), + ], + options={ + "verbose_name": "user", + "verbose_name_plural": "users", + "abstract": False, + }, + ), + migrations.CreateModel( + name="FrontendTranslation", + fields=[ + ( + "basecomment_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="http.basecomment", + ), + ), + ("key", models.CharField(max_length=255, unique=True)), + ("value", models.TextField()), + ("value_uz", models.TextField(null=True)), + ("value_ru", models.TextField(null=True)), + ("value_en", models.TextField(null=True)), + ], + options={ + "verbose_name": "Frontend Translation", + "verbose_name_plural": "Frontend Translations", + }, + bases=("http.basecomment",), + ), + migrations.AddField( + model_name="basecomment", + name="comments", + field=models.ManyToManyField(to="http.comment"), + ), + migrations.CreateModel( + name="Post", + fields=[ + ( + "basecomment_ptr", + models.OneToOneField( + auto_created=True, + on_delete=django.db.models.deletion.CASCADE, + parent_link=True, + primary_key=True, + serialize=False, + to="http.basecomment", + ), + ), + ("title", models.CharField(max_length=255)), + ("title_uz", models.CharField(max_length=255, null=True)), + ("title_ru", models.CharField(max_length=255, null=True)), + ("title_en", models.CharField(max_length=255, null=True)), + ("desc", models.TextField()), + ("desc_uz", models.TextField(null=True)), + ("desc_ru", models.TextField(null=True)), + ("desc_en", models.TextField(null=True)), + ("image", models.ImageField(blank=True, upload_to="posts/")), + ("tags", models.ManyToManyField(to="http.tags")), + ], + options={ + "abstract": False, + "base_manager_name": "objects", + }, + bases=("http.basecomment",), + ), + ] diff --git a/core/http/migrations/0002_user_avatar_user_role.py b/core/http/migrations/0002_user_avatar_user_role.py new file mode 100644 index 0000000..1cc14ad --- /dev/null +++ b/core/http/migrations/0002_user_avatar_user_role.py @@ -0,0 +1,34 @@ +# Generated by Django 5.0.4 on 2024-04-23 10:55 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("http", "0001_initial"), + ] + + operations = [ + migrations.AddField( + model_name="user", + name="avatar", + field=models.ImageField( + blank=True, null=True, upload_to="avatars/" + ), + ), + migrations.AddField( + model_name="user", + name="role", + field=models.CharField( + choices=[ + ("admin", "Admin"), + ("market", "Market"), + ("courier", "Courier"), + ("sklad", "Sklad"), + ], + default="market", + max_length=10, + ), + ), + ] diff --git a/core/http/migrations/0003_alter_user_role.py b/core/http/migrations/0003_alter_user_role.py new file mode 100644 index 0000000..530d672 --- /dev/null +++ b/core/http/migrations/0003_alter_user_role.py @@ -0,0 +1,27 @@ +# Generated by Django 5.0.4 on 2024-04-23 11:03 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("http", "0002_user_avatar_user_role"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="role", + field=models.CharField( + choices=[ + ("admin", "Admin"), + ("market", "Market"), + ("courier", "Courier"), + ("sklad", "Sklad"), + ], + default="admin", + max_length=10, + ), + ), + ] diff --git a/core/http/migrations/0004_alter_user_avatar.py b/core/http/migrations/0004_alter_user_avatar.py new file mode 100644 index 0000000..330792c --- /dev/null +++ b/core/http/migrations/0004_alter_user_avatar.py @@ -0,0 +1,23 @@ +# Generated by Django 5.0.7 on 2024-07-22 13:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("http", "0003_alter_user_role"), + ] + + operations = [ + migrations.AlterField( + model_name="user", + name="avatar", + field=models.ImageField( + blank=True, + default="avatars/golden_eggs.png", + null=True, + upload_to="avatars/", + ), + ), + ] diff --git a/core/http/migrations/0005_user_fcm_token.py b/core/http/migrations/0005_user_fcm_token.py new file mode 100644 index 0000000..2e8a803 --- /dev/null +++ b/core/http/migrations/0005_user_fcm_token.py @@ -0,0 +1,18 @@ +# Generated by Django 5.0.8 on 2024-08-29 08:57 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ("http", "0004_alter_user_avatar"), + ] + + operations = [ + migrations.AddField( + model_name="user", + name="fcm_token", + field=models.CharField(blank=True, max_length=255, null=True), + ), + ] diff --git a/core/http/migrations/__init__.py b/core/http/migrations/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/core/http/models/__init__.py b/core/http/models/__init__.py new file mode 100644 index 0000000..97a5c5f --- /dev/null +++ b/core/http/models/__init__.py @@ -0,0 +1,3 @@ +from .another import * # noqa +from .base import * # noqa +from .user import * # noqa diff --git a/core/http/models/another.py b/core/http/models/another.py new file mode 100644 index 0000000..988df31 --- /dev/null +++ b/core/http/models/another.py @@ -0,0 +1,47 @@ +from django.db import models +from polymorphic import models as polymorphic +from django.utils.translation import gettext as _ + + +class Tags(models.Model): + name = models.CharField(max_length=255) + + def __str__(self): + return self.name + + class Meta: + verbose_name = _("Tag") + verbose_name_plural = _("Tags") + + +class Comment(models.Model): + text = models.CharField(max_length=255) + + def __str__(self) -> str: + return self.text + + +class BaseComment(polymorphic.PolymorphicModel): + comments = models.ManyToManyField(Comment) + + +class Post(BaseComment): + title = models.CharField(max_length=255) + desc = models.TextField() + image = models.ImageField(upload_to="posts/", blank=True) + tags = models.ManyToManyField(Tags) + + def __str__(self): + return self.title + + +class FrontendTranslation(BaseComment): + key = models.CharField(max_length=255, unique=True) + value = models.TextField() + + def __str__(self): + return self.key + + class Meta: + verbose_name = _("Frontend Translation") + verbose_name_plural = _("Frontend Translations") diff --git a/core/http/models/base.py b/core/http/models/base.py new file mode 100644 index 0000000..6266b2f --- /dev/null +++ b/core/http/models/base.py @@ -0,0 +1,9 @@ +from django.db import models + + +class AbstractBaseModel(models.Model): + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + + class Meta: + abstract = True diff --git a/core/http/models/user.py b/core/http/models/user.py new file mode 100644 index 0000000..3e35f5f --- /dev/null +++ b/core/http/models/user.py @@ -0,0 +1,110 @@ +import math +from datetime import datetime, timedelta + +from django.contrib.auth import models as auth_models +from django.db import models +from django.utils.translation import gettext_lazy as _ + +from core.http import managers + + +class User(auth_models.AbstractUser): + ROLE_CHOICES = ( + ("admin", _("Admin")), + ("market", _("Market")), + ("courier", _("Courier")), + ("sklad", _("Sklad")), + ) + + phone = models.CharField(max_length=255, unique=True) + username = models.CharField(max_length=255, null=True, blank=True) + created_at = models.DateTimeField(auto_now_add=True) + updated_at = models.DateTimeField(auto_now=True) + validated_at = models.DateTimeField(null=True, blank=True) + USERNAME_FIELD = "phone" + objects = managers.UserManager() + + avatar = models.ImageField( + upload_to="avatars/", + null=True, + blank=True, + default="avatars/golden_eggs.png", + ) + role = models.CharField( + max_length=10, + choices=ROLE_CHOICES, + default="admin", + ) + fcm_token = models.CharField(max_length=255, null=True, blank=True) + + def __str__(self): + return f"{self.phone} | {self.id}" + + +class SmsConfirm(models.Model): + SMS_EXPIRY_SECONDS = 120 + RESEND_BLOCK_MINUTES = 10 + TRY_BLOCK_MINUTES = 2 + RESEND_COUNT = 5 + TRY_COUNT = 10 + + code = models.IntegerField() + try_count = models.IntegerField(default=0) + resend_count = models.IntegerField(default=0) + phone = models.CharField(max_length=20) + expire_time = models.DateTimeField(null=True, blank=True) + unlock_time = models.DateTimeField(null=True, blank=True) + resend_unlock_time = models.DateTimeField(null=True, blank=True) + + def sync_limits(self): + if self.resend_count >= self.RESEND_COUNT: + self.try_count = 0 + self.resend_count = 0 + self.resend_unlock_time = datetime.now() + timedelta( + minutes=self.RESEND_BLOCK_MINUTES + ) + elif self.try_count >= self.TRY_COUNT: + self.try_count = 0 + self.unlock_time = datetime.now() + timedelta( + minutes=self.TRY_BLOCK_MINUTES + ) + + if ( + self.resend_unlock_time is not None + and self.resend_unlock_time.timestamp() + < datetime.now().timestamp() + ): + self.resend_unlock_time = None + + if ( + self.unlock_time is not None + and self.unlock_time.timestamp() < datetime.now().timestamp() + ): + self.unlock_time = None + self.save() + + def is_expired(self): + return ( + self.expire_time.timestamp() < datetime.now().timestamp() + if hasattr(self.expire_time, "timestamp") + else None + ) + + def is_block(self): + return self.unlock_time is not None + + def reset_limits(self): + self.try_count = 0 + self.resend_count = 0 + self.unlock_time = None + + def interval(self, time): + expire = time.timestamp() - datetime.now().timestamp() + minutes = math.floor(expire / 60) + expire -= minutes * 60 + expire = math.floor(expire) + + return f"{minutes:02d}:{expire:02d}" + + def __str__(self) -> str: + return f"{self.phone} | {self.code}" diff --git a/core/http/permissions/__init__.py b/core/http/permissions/__init__.py new file mode 100644 index 0000000..f59e4cc --- /dev/null +++ b/core/http/permissions/__init__.py @@ -0,0 +1 @@ +from .base_permissions import * # noqa diff --git a/core/http/permissions/base_permissions.py b/core/http/permissions/base_permissions.py new file mode 100644 index 0000000..449671b --- /dev/null +++ b/core/http/permissions/base_permissions.py @@ -0,0 +1,16 @@ +from rest_framework.permissions import BasePermission +from django.utils.translation import gettext_lazy as _ + + +class IsRole(BasePermission): + message = _("You must be an admin to perform this action.") + + def __init__(self, role): + super().__init__() + self.role = role + + def __call__(self): + return self + + def has_permission(self, request, view): + return request.user.role in self.role diff --git a/core/http/serializers/__init__.py b/core/http/serializers/__init__.py new file mode 100644 index 0000000..042a063 --- /dev/null +++ b/core/http/serializers/__init__.py @@ -0,0 +1,4 @@ +from .another import * # noqa +from .auth import * # noqa +from .generics import * # noqa +from .user import * # noqa diff --git a/core/http/serializers/another.py b/core/http/serializers/another.py new file mode 100644 index 0000000..c7e1f78 --- /dev/null +++ b/core/http/serializers/another.py @@ -0,0 +1,15 @@ +from rest_framework import serializers + +from core.http import models + + +class PostSerializer(serializers.ModelSerializer): + class Meta: + model = models.Post + fields = ["title", "desc", "image"] + + +class FrontendTransactionSerializer(serializers.ModelSerializer): + class Meta: + model = models.FrontendTranslation + fields = ["key", "value"] diff --git a/core/http/serializers/auth.py b/core/http/serializers/auth.py new file mode 100644 index 0000000..8b12b16 --- /dev/null +++ b/core/http/serializers/auth.py @@ -0,0 +1,66 @@ +from django.utils.translation import gettext as _ +from rest_framework import exceptions, serializers + +from core.http import models + + +class LoginSerializer(serializers.Serializer): + username = serializers.CharField(max_length=255) + password = serializers.CharField(max_length=255) + + +class RegisterSerializer(serializers.ModelSerializer): + phone = serializers.CharField(max_length=255) + + def validate_phone(self, value): + user = models.User.objects.filter( + phone=value, validated_at__isnull=False + ) + if user.exists(): + return exceptions.ValidationError( + _("Telefon raqami allaqachon ro'yxatdan o'tgan."), + code="unique", + ) + return value + + class Meta: + model = models.User + fields = ["first_name", "last_name", "phone", "password"] + extra_kwargs = { + "first_name": { + "required": True, + }, + "last_name": {"required": True}, + } + + +class ConfirmSerializer(serializers.Serializer): + code = serializers.IntegerField(min_value=1000, max_value=9999) + phone = serializers.CharField(max_length=255) + + +class ResetPasswordSerializer(serializers.Serializer): + phone = serializers.CharField(max_length=255) + + def validate_phone(self, value): + user = models.User.objects.filter(phone=value) + if user.exists(): + return value + + raise serializers.ValidationError(_("Foydalanuvchi mavjud emas")) + + +class ResetConfirmationSerializer(serializers.Serializer): + code = serializers.IntegerField(min_value=1000, max_value=9999) + phone = serializers.CharField(max_length=255) + password = serializers.CharField(max_length=255) + + def validate_phone(self, value): + user = models.User.objects.filter(phone=value) + if user.exists(): + return value + raise serializers.ValidationError(_("User does not exist")) + + +class ResendSerializer(serializers.Serializer): + phone = serializers.CharField(max_length=255) diff --git a/core/http/serializers/generics.py b/core/http/serializers/generics.py new file mode 100644 index 0000000..7d93064 --- /dev/null +++ b/core/http/serializers/generics.py @@ -0,0 +1,17 @@ +from rest_framework import exceptions +from rest_framework import serializers + + +from core import enums +from core.utils import exception + + +class GenericSerializer(serializers.Serializer): + def to_internal_value(self, data): + try: + return super().to_internal_value(data) + except exceptions.ValidationError as e: + key, value = next(iter(e.detail.items())) + exception.ResponseException( + value[0], error_code=enums.Codes.INVALID_PARAMETER_VALUE + ) diff --git a/core/http/serializers/user.py b/core/http/serializers/user.py new file mode 100644 index 0000000..c528dd5 --- /dev/null +++ b/core/http/serializers/user.py @@ -0,0 +1,35 @@ +from rest_framework import serializers + +from core.http import models + + +class UserSerializer(serializers.ModelSerializer): + class Meta: + model = models.User + fields = ( + "id", + "first_name", + "last_name", + "phone", + "email", + "role", + "username", + "avatar", + ) + + +class UserUpdateSerializer(serializers.ModelSerializer): + class Meta: + model = models.User + fields = ( + "first_name", + "last_name", + "username", + "phone", + ) + + +class UserAvatarUpdateSerializer(serializers.ModelSerializer): + class Meta: + model = models.User + fields = ("avatar",) diff --git a/core/http/tasks/__init__.py b/core/http/tasks/__init__.py new file mode 100644 index 0000000..23f58fb --- /dev/null +++ b/core/http/tasks/__init__.py @@ -0,0 +1 @@ +from .sms import * # noqa diff --git a/core/http/tasks/sms.py b/core/http/tasks/sms.py new file mode 100644 index 0000000..b7639dd --- /dev/null +++ b/core/http/tasks/sms.py @@ -0,0 +1,25 @@ +""" +Base celery tasks +""" + +from celery import shared_task +from django.utils.translation import gettext as _ + +from core.services import sms_service +from core.utils import console + + +@shared_task +def SendConfirm(phone, code): + try: + service: sms_service.SendService = sms_service.SendService() + service.send_sms( + phone, _("Sizning Tasdiqlash ko'dingiz: %(code)s") % {"code": code} + ) + console.Console().success(f"Success: {phone}-{code}") + except Exception as e: + console.Console().error( + "Error: {phone}-{code}\n\n{error}".format( + phone=phone, code=code, error=e + ) + ) # noqa diff --git a/core/http/templatetags/__init__.py b/core/http/templatetags/__init__.py new file mode 100755 index 0000000..e69de29 diff --git a/core/http/templatetags/vite.py b/core/http/templatetags/vite.py new file mode 100755 index 0000000..8904eeb --- /dev/null +++ b/core/http/templatetags/vite.py @@ -0,0 +1,54 @@ +import json + +from django import template +from django.conf import settings +from django.templatetags import static +from django.utils import safestring + +from common.env import env + +register = template.Library() + + +def getScript(url: object) -> str: + ext: str = str(url).split(".")[-1] + + if env("VITE_LIVE"): + url = f"http://{env('VITE_HOST')}:{env('VITE_PORT')}/{url}" + else: + url: str = static.static(f"vite/{url}") + + if ext == "css": + script: str = f"" + else: + script: str = ( + "" + ).format(url) + return script + + +@register.simple_tag +def vite_load(*args): + try: + fd = open(f"{settings.VITE_APP_DIR}/manifest.json") + manifest = json.load(fd) + except Exception: + raise Exception( + f"Vite manifest file not found or invalid. Maybe your" + f" {settings.VITE_APP_DIR}/manifest.json file is empty?" + ) + if not env("VITE_LIVE"): + imports_files = "".join( + [getScript(file["file"]) for file in manifest.values()] + ) + + else: + imports_files = "".join([getScript(file) for file in args]) + imports_files += f""" + """ + + return safestring.mark_safe(imports_files) diff --git a/core/http/translation/__init__.py b/core/http/translation/__init__.py new file mode 100644 index 0000000..ded45fa --- /dev/null +++ b/core/http/translation/__init__.py @@ -0,0 +1,2 @@ +from .core import * # noqa +from .another import * # noqa diff --git a/core/http/translation/another.py b/core/http/translation/another.py new file mode 100644 index 0000000..99519b3 --- /dev/null +++ b/core/http/translation/another.py @@ -0,0 +1,16 @@ +""" +Django model translation resources +""" + +from modeltranslation import translator + + +class PostTranslationOption(translator.TranslationOptions): + fields = ( + "title", + "desc", + ) + + +class FrontendTranslationOption(translator.TranslationOptions): + fields = ("value",) diff --git a/core/http/translation/core.py b/core/http/translation/core.py new file mode 100644 index 0000000..5535f53 --- /dev/null +++ b/core/http/translation/core.py @@ -0,0 +1,14 @@ +""" +Register models +""" + +from modeltranslation.translator import translator + +from core.http import models +from core.http.translation import another + + +translator.register(models.Post, another.PostTranslationOption) +translator.register( + models.FrontendTranslation, another.FrontendTranslationOption +) # noqa diff --git a/core/http/views/__init__.py b/core/http/views/__init__.py new file mode 100644 index 0000000..33fdbb6 --- /dev/null +++ b/core/http/views/__init__.py @@ -0,0 +1,2 @@ +from .auth import * # noqa +from .generics import * # noqa diff --git a/core/http/views/auth.py b/core/http/views/auth.py new file mode 100644 index 0000000..f31fab7 --- /dev/null +++ b/core/http/views/auth.py @@ -0,0 +1,25 @@ +from django.utils.translation import gettext as _ +from rest_framework import views +from rest_framework import request +from rest_framework import throttling + +from core import enums +from core import services +from core.http import serializers +from core.http.views import generics as http_views + + +class AbstractSendSms(views.APIView, http_views.ApiResponse): + serializer_class = serializers.ResendSerializer + throttle_classes = [throttling.UserRateThrottle] + + def __init__(self, *args, **kwargs): + super().__init__(*args, **kwargs) + self.service = services.UserService() + + def post(self, request: request.Request): + ser = self.serializer_class(data=request.data) + ser.is_valid(raise_exception=True) + phone = ser.data.get("phone") + self.service.send_confirmation(phone) + return self.success(_(enums.Messages.SEND_MESSAGE) % {"phone": phone}) diff --git a/core/http/views/generics.py b/core/http/views/generics.py new file mode 100644 index 0000000..0ef8241 --- /dev/null +++ b/core/http/views/generics.py @@ -0,0 +1,67 @@ +from rest_framework import generics, response, status + +from core.exceptions import BreakException + + +class ApiResponse: + def response( + self, + success=True, + message="", + data=None, + status_code=status.HTTP_200_OK, + **kwargs + ): + if data is None: + data = {} + response_data = { + "success": success, + "message": message, + "data": data, + **kwargs, + } + + return response.Response(data=response_data, status=status_code) + + def success( + self, message="", data=None, status_code=status.HTTP_200_OK, **kwargs + ): + return self.response(True, message, data, status_code, **kwargs) + + def error( + self, + message="", + data=None, + error_code=0, + status_code=status.HTTP_400_BAD_REQUEST, + exception=None, + **kwargs + ): + if isinstance(exception, BreakException): + raise exception + return self.response( + False, message, data, status_code, error_code=error_code, **kwargs + ) # noqa + + +class ListApiView(generics.ListAPIView, ApiResponse): + def list(self, request, *args, **kwargs): + queryset = self.filter_queryset(self.get_queryset()) + + page = self.paginate_queryset(queryset) + if page is not None: + serializer = self.get_serializer(page, many=True) + return self.get_paginated_response(serializer.data) + + serializer = self.get_serializer(queryset, many=True) + return self.success(data=serializer.data) + + +class CreateApiView(generics.CreateAPIView, ApiResponse): + def create(self, request, *args, **kwargs): + super().create(request, *args, **kwargs) + return self.success( + self.message + if hasattr(self, "message") + else "Muvaffaqiyatli yaratildi" + ) # noqa diff --git a/core/http/views/user.py b/core/http/views/user.py new file mode 100644 index 0000000..0c5a78d --- /dev/null +++ b/core/http/views/user.py @@ -0,0 +1,29 @@ +from rest_framework.generics import UpdateAPIView + +from core.http.models import User +from core.http.serializers.user import ( + UserAvatarUpdateSerializer, + UserUpdateSerializer, +) + + +class UserUpdateView(UpdateAPIView): + serializer_class = UserUpdateSerializer + queryset = User.objects.all() + + def get_object(self): + return self.request.user + + def perform_update(self, serializer): + serializer.save(user=self.request.user) + + +class UserAvatarUpdateView(UpdateAPIView): + serializer_class = UserAvatarUpdateSerializer + queryset = User.objects.all() + + def get_object(self): + return self.request.user + + def perform_update(self, serializer): + serializer.save(user=self.request.user) diff --git a/core/middlewares/__init__.py b/core/middlewares/__init__.py new file mode 100755 index 0000000..5df8c8f --- /dev/null +++ b/core/middlewares/__init__.py @@ -0,0 +1,4 @@ +from .core import ExceptionMiddleware # noqa +from .cache_middleware import * # noqa +from .logging_middleware import * # noqa +from .fcm_token import * # noqa diff --git a/core/middlewares/cache_middleware.py b/core/middlewares/cache_middleware.py new file mode 100755 index 0000000..c39c808 --- /dev/null +++ b/core/middlewares/cache_middleware.py @@ -0,0 +1,13 @@ +class CacheMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + response = self.get_response(request) + vary_headers = set( + response.get("Vary", "").replace(" ", "").split(",") + ) + vary_headers.update(["Accept-Language"]) + # Authorization + response["Vary"] = ", ".join(vary_headers) + return response diff --git a/core/middlewares/core.py b/core/middlewares/core.py new file mode 100755 index 0000000..1eb0458 --- /dev/null +++ b/core/middlewares/core.py @@ -0,0 +1,62 @@ +from django.http import response + +from core import exceptions + + +class ExceptionMiddleware: + """ + This class is used to handle exceptions that occur during the request/response cycle. + It is a middleware that is added to the Django middleware pipeline. + """ + + def __init__(self, get_response): + """ + Initialize the middleware. + + Args: + get_response: The next middleware in the pipeline. + """ + self.get_response = get_response + + def __call__(self, request): + """ + This method is called for each request. + It retrieves the response from the next middleware in the pipeline, + and handles any exceptions that occur. + + Args: + request: The incoming request. + + Returns: + The response from the next middleware in the pipeline. + """ + try: + response = self.get_response(request) + except exceptions.BreakException as e: + return self.process_exception(request, e) + return response + + def process_exception(self, request, e): + """ + Process an exception that occurred during the request/response cycle. + + Args: + request: The incoming request. + e: The exception that occurred. + + Returns: + A JSON response containing information about the exception. + """ + if isinstance(e, exceptions.BreakException): + """ + If the exception is a BreakException, construct a JSON response containing the error message, data, and + any additional arguments passed to the BreakException. + """ + error_data = { + "message": e.message, + "data": e.data, + "errors": [ + e.args.__str__(), + ], + } + return response.JsonResponse(error_data) diff --git a/core/middlewares/fcm_token.py b/core/middlewares/fcm_token.py new file mode 100644 index 0000000..05f8dd3 --- /dev/null +++ b/core/middlewares/fcm_token.py @@ -0,0 +1,27 @@ +import jwt +from django.conf import settings +from django.utils.deprecation import MiddlewareMixin + +from core.http.models import User + + +class JWTFCMMiddleware(MiddlewareMixin): + def process_request(self, request): + auth_header = request.headers.get("Authorization") + if auth_header and auth_header.startswith("Bearer "): + token = auth_header.split(" ")[1] + try: + payload = jwt.decode( + token, settings.SECRET_KEY, algorithms=["HS256"] + ) + user_id = payload.get("user_id") + fcm_token = request.headers.get("FCM-Token") + if user_id: + user = User.objects.get(id=user_id) + if fcm_token: + user.fcm_token = fcm_token + user.save() + except jwt.ExpiredSignatureError: + pass + except jwt.InvalidTokenError: + pass diff --git a/core/middlewares/logging_middleware.py b/core/middlewares/logging_middleware.py new file mode 100755 index 0000000..40ad0c7 --- /dev/null +++ b/core/middlewares/logging_middleware.py @@ -0,0 +1,14 @@ +class LoggingMiddleware: + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + print( + f"Request: {request.method} {request.path} from {request.META['REMOTE_ADDR']}" + ) + + response = self.get_response(request) + + print(f"Response: {response.status_code}") + + return response diff --git a/core/services/__init__.py b/core/services/__init__.py new file mode 100644 index 0000000..bfce8d7 --- /dev/null +++ b/core/services/__init__.py @@ -0,0 +1,4 @@ +from .sms import * # noqa +from .sms_service import * # noqa +from .base_service import * # noqa +from .user import * # noqa diff --git a/core/services/base_service.py b/core/services/base_service.py new file mode 100755 index 0000000..d088195 --- /dev/null +++ b/core/services/base_service.py @@ -0,0 +1,6 @@ +class BaseService: + """ + Test Service Base + """ + + pass diff --git a/core/services/sms.py b/core/services/sms.py new file mode 100755 index 0000000..22b568e --- /dev/null +++ b/core/services/sms.py @@ -0,0 +1,66 @@ +from datetime import datetime +from datetime import timedelta + +from core import exceptions +from core.http import models +from core.http import tasks + + +class SmsService: + @staticmethod + def send_confirm(phone): + # TODO: Deploy this change when deploying -> code = random.randint(1000, 9999) # noqa + code = 1111 + + sms_confirm, status = models.SmsConfirm.objects.get_or_create( + phone=phone, defaults={"code": code} + ) + + sms_confirm.sync_limits() + + if sms_confirm.resend_unlock_time is not None: + expired = sms_confirm.interval(sms_confirm.resend_unlock_time) + exception = exceptions.SmsException( + f"Resend blocked, try again in {expired}", expired=expired + ) + raise exception + + sms_confirm.code = code + sms_confirm.try_count = 0 + sms_confirm.resend_count += 1 + sms_confirm.phone = phone + sms_confirm.expired_time = datetime.now() + timedelta( + seconds=models.SmsConfirm.SMS_EXPIRY_SECONDS + ) # noqa + sms_confirm.resend_unlock_time = datetime.now() + timedelta( + seconds=models.SmsConfirm.SMS_EXPIRY_SECONDS + ) # noqa + sms_confirm.save() + + tasks.SendConfirm.delay(phone, code) + return True + + @staticmethod + def check_confirm(phone, code): + sms_confirm = models.SmsConfirm.objects.filter(phone=phone).first() + + if sms_confirm is None: + raise exceptions.SmsException("Invalid confirmation code") + + sms_confirm.sync_limits() + + if sms_confirm.is_expired(): + raise exceptions.SmsException("Time for confirmation has expired") + + if sms_confirm.is_block(): + expired = sms_confirm.interval(sms_confirm.unlock_time) + raise exceptions.SmsException(f"Try again in {expired}") + + if sms_confirm.code == code: + sms_confirm.delete() + return True + + sms_confirm.try_count += 1 + sms_confirm.save() + + raise exceptions.SmsException("Invalid confirmation code") diff --git a/core/services/sms_service.py b/core/services/sms_service.py new file mode 100755 index 0000000..554b798 --- /dev/null +++ b/core/services/sms_service.py @@ -0,0 +1,135 @@ +import requests + +from common.env import env + + +class SendService: + GET = "GET" + POST = "POST" + PATCH = "PATCH" + CONTACT = "contact" + + def __init__( + self, api_url=None, email=None, password=None, callback_url=None + ): + self.api_url = api_url or env("SMS_API_URL") + self.email = email or env("SMS_LOGIN") + self.password = password or env("SMS_PASSWORD") + self.callback_url = callback_url + self.headers = {} + + self.methods = { + "auth_user": "auth/user", + "auth_login": "auth/login", + "auth_refresh": "auth/refresh", + "send_message": "message/sms/send", + } + + def request(self, api_path, data=None, method=None, headers=None): + incoming_data = {"status": "error"} + + try: + response = requests.request( + method, + f"{self.api_url}/{api_path}", + data=data, + headers=headers, + ) + + if api_path == self.methods["auth_refresh"]: + if response.status_code == 200: + incoming_data["status"] = "success" + else: + incoming_data = response.json() + except requests.RequestException as error: + raise Exception(str(error)) + + return incoming_data + + def auth(self): + data = {"email": self.email, "password": self.password} + + return self.request( + self.methods["auth_login"], data=data, method=self.POST + ) + + def refresh_token(self): + token = self.auth()["data"]["token"] + self.headers["Authorization"] = "Bearer " + token + + context = { + "headers": self.headers, + "method": self.PATCH, + "api_path": self.methods["auth_refresh"], + } + + return self.request( + context["api_path"], + method=context["method"], + headers=context["headers"], + ) + + def get_my_user_info(self): + token = self.auth()["data"]["token"] + self.headers["Authorization"] = "Bearer " + token + + data = { + "headers": self.headers, + "method": self.GET, + "api_path": self.methods["auth_user"], + } + + return self.request( + data["api_path"], method=data["method"], headers=data["headers"] + ) + + def add_sms_contact(self, first_name, phone_number, group): + token = self.auth()["data"]["token"] + self.headers["Authorization"] = "Bearer " + token + + data = { + "name": first_name, + "email": self.email, + "group": group, + "mobile_phone": phone_number, + } + + context = { + "headers": self.headers, + "method": self.POST, + "api_path": self.CONTACT, + "data": data, + } + + return self.request( + context["api_path"], + data=context["data"], + method=context["method"], + headers=context["headers"], + ) + + def send_sms(self, phone_number, message): + token = self.auth()["data"]["token"] + self.headers["Authorization"] = "Bearer " + token + + data = { + "from": 4546, + "mobile_phone": phone_number, + "callback_url": self.callback_url, + "message": message, + } + + context = { + "headers": self.headers, + "method": self.POST, + "api_path": self.methods["send_message"], + "data": data, + } + + res = self.request( + context["api_path"], + data=context["data"], + method=context["method"], + headers=context["headers"], + ) + return res \ No newline at end of file diff --git a/core/services/user.py b/core/services/user.py new file mode 100755 index 0000000..70da2e8 --- /dev/null +++ b/core/services/user.py @@ -0,0 +1,71 @@ +import typing +from datetime import datetime + +from django.contrib.auth import hashers +from rest_framework_simplejwt import tokens + + +from core import exceptions +from core.http import models +from core.utils import exception + +from core.services import sms +from core.services import base_service + + +class UserService(base_service.BaseService, sms.SmsService): + + def get_token(self, user): + refresh = tokens.RefreshToken.for_user(user) + + return { + "refresh": str(refresh), + "access": str(refresh.access_token), + } + + def create_user(self, phone, first_name, last_name, password): + models.User.objects.update_or_create( + phone=phone, + defaults={ + "phone": phone, + "first_name": first_name, + "last_name": last_name, + "password": hashers.make_password(password), + }, + ) + + def send_confirmation(self, phone) -> bool: + try: + self.send_confirm(phone) + return True + except exceptions.SmsException as e: + exception.ResponseException( + e, data={"expired": e.kwargs.get("expired")} + ) # noqa + except Exception as e: + exception.ResponseException(e) + + def validate_user(self, user: typing.Union[models.User]) -> dict: + """ + Create user if user not found + """ + user.validated_at = datetime.now() + user.save() + token = self.get_token(user) + return token + + def is_validated(self, user: typing.Union[models.User]) -> bool: + """ + User is validated check + """ + if user.validated_at is not None: + return True + return False + + def change_password(self, phone, password): + """ + Change password + """ + user = models.User.objects.filter(phone=phone).first() + user.set_password(password) + user.save() diff --git a/core/utils/__init__.py b/core/utils/__init__.py new file mode 100755 index 0000000..075c751 --- /dev/null +++ b/core/utils/__init__.py @@ -0,0 +1,6 @@ +from .cache import * # noqa +from .console import * # noqa +from .core import * # noqa +from .dd import * # noqa +from .exception import * # noqa +from .factory import * # noqa diff --git a/core/utils/cache.py b/core/utils/cache.py new file mode 100755 index 0000000..910251d --- /dev/null +++ b/core/utils/cache.py @@ -0,0 +1,17 @@ +import hashlib +from django.core.cache import cache + +from common.env import env + + +class Cache: + def remember(self, func, key: str): + cache_enabled = env("CACHE_ENABLED") + key = hashlib.md5(key.encode("utf-8")).hexdigest() + response = cache.get(key) + + if (response is None) or cache_enabled: + response = func() + cache.set(key, response, env("CACHE_TIME")) + + return response diff --git a/core/utils/console.py b/core/utils/console.py new file mode 100755 index 0000000..4d47bca --- /dev/null +++ b/core/utils/console.py @@ -0,0 +1,65 @@ +import os + +from django.conf import settings +from django.core import management + + +class Console(management.BaseCommand): + 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): + self.get_stdout().write(self.get_style().SUCCESS(message)) + + def error(self, message): + self.get_stdout().write(self.get_style().ERROR(message)) + + def log(self, message): + self.get_stdout().write( + self.get_style().ERROR( + "\n====================\n{}\n====================\n".format( + message + ) + ) + ) + + +class BaseMake(management.BaseCommand): + 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") + with open( + os.path.join(settings.BASE_DIR, f"stub/{self.path}.stub") + ) as stub: # noqa + data = stub.read() + stub.close() + stub = data.replace("{{name}}", name) + + 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)) + + with open( + os.path.join(core_http_path, f"{self.path}/{name.lower()}.py"), + "w+", + ) as file: # noqa + file.write(stub) + + self.console.success(f"{self.name} created") diff --git a/core/utils/core.py b/core/utils/core.py new file mode 100755 index 0000000..8614847 --- /dev/null +++ b/core/utils/core.py @@ -0,0 +1,6 @@ +class Helper: + """ + Helper class to handle index + """ + + pass diff --git a/core/utils/dd.py b/core/utils/dd.py new file mode 100755 index 0000000..b9f95b7 --- /dev/null +++ b/core/utils/dd.py @@ -0,0 +1,12 @@ +from core import exceptions + + +def dd( + *args, message: str | None = None, data: dict | list | None = None +) -> None: + """ + Dump the given variables and then raise a SystemExit exception + to stop execution of the script. + """ + + raise exceptions.BreakException(args, message=message, data=data) diff --git a/core/utils/exception.py b/core/utils/exception.py new file mode 100755 index 0000000..9440cf8 --- /dev/null +++ b/core/utils/exception.py @@ -0,0 +1,29 @@ +from rest_framework import status + +from core import exceptions + + +class ResponseException: + + def __init__( + self, + message="", + data=None, + error_code=0, + status_code=status.HTTP_400_BAD_REQUEST, + exception=None, + **kwargs + ): + if isinstance(exception, exceptions.BreakException): + raise exception + + if data is None: + data = [] + response = { + "success": False, + "message": message, + "data": data, + "error_code": error_code, + **kwargs, + } + raise exceptions.MyApiException(response, status_code) diff --git a/core/utils/factory.py b/core/utils/factory.py new file mode 100755 index 0000000..5945d45 --- /dev/null +++ b/core/utils/factory.py @@ -0,0 +1,7 @@ +import abc +import faker + + +class BaseFaker(abc.ABC): + def __init__(self): + self.faker = faker.Faker() diff --git a/deploy.yml b/deploy.yml new file mode 100755 index 0000000..f78b5c1 --- /dev/null +++ b/deploy.yml @@ -0,0 +1,52 @@ +version: '3.11' + +networks: + dokploy-network: + external: true +volumes: + pg_data: null + +services: + eggs-web: + networks: + - dokploy-network + build: . + restart: always + command: ${COMMAND:-sh ./scripts/entrypoint.sh} + volumes: + - .:/code + - '../files/media:/code/media' + depends_on: + - eggs-db + - eggs-redis + eggs-db: + networks: + - dokploy-network + image: postgres:13 + restart: always + environment: + POSTGRES_DB: django + POSTGRES_USER: postgres + POSTGRES_PASSWORD: '2309' + volumes: + - pg_data:/var/lib/postgresql/data + eggs-redis: + networks: + - dokploy-network + restart: always + image: redis + + eggs-celery: + build: . + command: celery -A config worker --loglevel=info + restart: always + volumes: + - .:/code + depends_on: + - eggs-web + - eggs-redis + environment: + - CELERY_BROKER_URL=redis://eggs-redis:6379/ + - CELERY_RESULT_BACKEND=redis://eggs-redis:6379/ + networks: + - dokploy-network \ No newline at end of file diff --git a/deployments/django-project/Dockerfile b/deployments/django-project/Dockerfile new file mode 100755 index 0000000..e69de29 diff --git a/deployments/docker-compose.yml b/deployments/docker-compose.yml new file mode 100755 index 0000000..70e0543 --- /dev/null +++ b/deployments/docker-compose.yml @@ -0,0 +1,73 @@ +version: "3.11" + +services: + web: + build: . + restart: always + command: ${COMMAND:-python3 manage.py runserver 0.0.0.0:8000} + volumes: + - .:/code + ports: + - "8001:8000" + depends_on: + - db + - redis + db: + image: postgres:13 + restart: always + environment: + POSTGRES_DB: django + POSTGRES_USER: postgres + POSTGRES_PASSWORD: 2309 + volumes: + - pg_data:/var/lib/postgresql/data + redis: + restart: always + image: "redis" + ngrok: + image: ngrok/ngrok:latest + ports: + - ${NGROK_ADMIN_PORT}:4040 + environment: + NGROK_AUTHTOKEN: ${NGROK_AUTHTOKEN} + command: http http://web:8000 --domain=${NGROK_DOMAIN} + nginx: + build: ./nginx + ports: + - '81:80' + depends_on: + - web + +# vite: +# build: +# context: . +# dockerfile: ViteDockerfile +# ports: +# - ${VITE_PORT}:5173 +# volumes: +# - .:/code +# - /code/node_modules + + celery: + build: . + command: celery -A config worker --loglevel=info + restart: always + volumes: + - .:/code + depends_on: + - web + - redis + +# celery-beat: +# build: . +# command: celery -A config beat --loglevel=info +# restart: always +# volumes: +# - .:/code +# depends_on: +# - web +# - redis + +volumes: + pg_data: + diff --git a/deployments/nginx/Dockerfile b/deployments/nginx/Dockerfile new file mode 100755 index 0000000..e69de29 diff --git a/deployments/nginx/default.conf b/deployments/nginx/default.conf new file mode 100755 index 0000000..500b576 --- /dev/null +++ b/deployments/nginx/default.conf @@ -0,0 +1,22 @@ +upstream django { + server web:8000; +} + + +server { + listen 80; + server_name domain.com; + root /code/; + access_log /var/log/nginx/django.access.log; + error_log /var/log/nginx/django.error.log; + client_max_body_size 1024M; + try_files $uri @django; + + location @django { + proxy_pass http://django; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} \ No newline at end of file diff --git a/deployments/nginx/microservice.conf b/deployments/nginx/microservice.conf new file mode 100644 index 0000000..4045289 --- /dev/null +++ b/deployments/nginx/microservice.conf @@ -0,0 +1,29 @@ +server { + + listen 80; + charset utf-8; + + location /api/v1/products { + rewrite /api/v1/products(.*) /products$1 break; + proxy_redirect off; + proxy_set_header Host $host; + proxy_pass http://products_web:8001; + } + + + location /api/v1/emails { + rewrite /api/v1/emails(.*) /emails$1 break; + proxy_pass http://emails_web:8002; + proxy_redirect off; + proxy_set_header Host $host; + } + + + location /api/v1/orders { + rewrite /api/v1/orders(.*) /orders$1 break; + proxy_pass http://orders_web:8003; + proxy_redirect off; + proxy_set_header Host $host; + } + +} \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100755 index 0000000..a92e802 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,50 @@ +version: '3.11' +services: + web: + build: . + restart: always + command: ${COMMAND:-sh ./scripts/entrypoint.sh} + volumes: + - .:/code + ports: + - ${PORT:-8001}:8000 + depends_on: + - db + - redis + networks: + - eggs + db: + image: postgres:13 + restart: always + environment: + POSTGRES_DB: django + POSTGRES_USER: postgres + POSTGRES_PASSWORD: '2309' + volumes: + - pg_data:/var/lib/postgresql/data + networks: + - eggs + redis: + restart: always + image: redis + networks: + - eggs + + celery: + build: . + command: celery -A config worker --loglevel=info + restart: always + volumes: + - .:/code + depends_on: + - web + - redis + networks: + - eggs + +volumes: + pg_data: null + +networks: + eggs: + driver: bridge diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md new file mode 100755 index 0000000..e69de29 diff --git a/docs/CONTRIBUTING.md b/docs/CONTRIBUTING.md new file mode 100755 index 0000000..e69de29 diff --git a/docs/collections/insomnia.json b/docs/collections/insomnia.json new file mode 100644 index 0000000..c20303b --- /dev/null +++ b/docs/collections/insomnia.json @@ -0,0 +1 @@ +{"_type":"export","__export_format":4,"__export_date":"2024-04-23T12:16:54.786Z","__export_source":"insomnia.desktop.app:v8.6.1","resources":[{"_id":"req_c45c05af856c4f819d688b919852573a","parentId":"fld_2c622e0139854329bb77b015806bdc32","modified":1713869763507,"created":1713869763507,"url":"{{BASE_URL}}/v1/auth/me/","name":"me","description":"","method":"GET","body":{"mimeType":"","text":"{\n \"phone\":\"998888112307\",\n \"password\":\"Samandar001@\"\n}"},"parameters":[],"headers":[{"name":"Accept","value":"application/json"}],"authentication":{},"metaSortKey":-1713352734542,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_2c622e0139854329bb77b015806bdc32","parentId":"fld_90f7feb6ccab4773bf818b6301b39b5f","modified":1713869763506,"created":1713869763506,"name":"default","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1713352675934,"_type":"request_group"},{"_id":"fld_90f7feb6ccab4773bf818b6301b39b5f","parentId":"wrk_3535ce5326004d399f55741c92200a1b","modified":1713869763505,"created":1713869763505,"name":"auth","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1713352459924,"_type":"request_group"},{"_id":"wrk_3535ce5326004d399f55741c92200a1b","parentId":null,"modified":1713869763476,"created":1713869763476,"name":"eggs","description":"Internet magazine for flowers","scope":"collection","_type":"workspace"},{"_id":"req_9bf2e3c5a84843ee90099688e546163c","parentId":"fld_2c622e0139854329bb77b015806bdc32","modified":1713873399318,"created":1713869763506,"url":"http://localhost:8001/api/auth/register/","name":"register","description":"","method":"POST","body":{"mimeType":"","text":"{\n \"phone\":\"998888112309\",\n \"password\":\"2309\",\n \"first_name\":\"Samandar\",\n \"last_name\":\"Azamov\",\n \"jshir\":\"12345678\"\n}\n"},"parameters":[],"headers":[{"name":"Accept","value":"application/json"}],"authentication":{},"metaSortKey":-1713352734442,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_16572f7f17224d959cd826e7e4577d31","parentId":"fld_2c622e0139854329bb77b015806bdc32","modified":1713869763506,"created":1713869763506,"url":"{{BASE_URL}}/v1/auth/confirm/","name":"confirm","description":"","method":"POST","body":{"mimeType":"","text":"{\n \"phone\":\"998943990508\",\n \"code\":\"1111\"\n}"},"parameters":[],"headers":[{"name":"Accept","value":"application/json"}],"authentication":{},"metaSortKey":-1713352734417,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_cb18ba15ba354ade8a4c68c986e0e2fd","parentId":"fld_2c622e0139854329bb77b015806bdc32","modified":1713873399318,"created":1713869763507,"url":"{{BASE_URL}}/auth/token/","name":"login","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"phone\":\"1\",\n \"password\":\"1\"\n}\n"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"},{"name":"Accept","value":"application/json"}],"authentication":{},"metaSortKey":-1713352734392,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_8b9939212d0646bd82c06cf236f02764","parentId":"fld_2c622e0139854329bb77b015806bdc32","modified":1713869763506,"created":1713869763506,"url":"{{BASE_URL}}/v1/auth/confirm/reset/","name":"reset confirm","description":"","method":"POST","body":{"mimeType":"","text":"{\n \"phone\":\"998888112307\",\n \"code\":\"1111\",\n \"password\":\"Samandar001@\"\n}"},"parameters":[],"headers":[{"name":"Accept","value":"application/json"}],"authentication":{},"metaSortKey":-1713352734342,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_944e2383d4474760a50ba6ec579bf583","parentId":"fld_2c622e0139854329bb77b015806bdc32","modified":1713869763507,"created":1713869763507,"url":"{{BASE_URL}}/v1/auth/reset/password/","name":"reset password","description":"","method":"POST","body":{"mimeType":"","text":"{\n \"phone\":\"998888112307\"\n}"},"parameters":[],"headers":[{"name":"Accept","value":"application/json"}],"authentication":{},"metaSortKey":-1713352734292,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_3f94ca7abc394ba48449ad0fc7f74f26","parentId":"fld_2c622e0139854329bb77b015806bdc32","modified":1713869763507,"created":1713869763507,"url":"http://localhost:8080/api/v1/auth/token/refresh/","name":"refresh","description":"","method":"POST","body":{"mimeType":"","text":"{\n \"refresh\":\"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ0b2tlbl90eXBlIjoicmVmcmVzaCIsImV4cCI6MTcxMjY0NzQ1OCwiaWF0IjoxNzEyNTYxMDU4LCJqdGkiOiJjZGZiM2E0NDQwOTY0OGM0YmQ2Y2FhYTMwZjZlYWNjOCIsInVzZXJfaWQiOjEwfQ.lbmcktPAG5S5_NJoCMMThT7AgC2vkjhWb1SzxIuDvQ8\"\n}"},"parameters":[],"headers":[{"name":"Accept","value":"application/json"}],"authentication":{},"metaSortKey":-1713352734279.5,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_a279163bbc36442cb59e5fea42e03f2c","parentId":"fld_2c622e0139854329bb77b015806bdc32","modified":1713869763507,"created":1713869763507,"url":"{{BASE_URL}}/v1/auth/resend/","name":"Resend sms code","description":"","method":"POST","body":{"mimeType":"","text":"{\n \"phone\":\"998888112307\"\n}"},"parameters":[],"headers":[{"name":"Accept","value":"application/json"}],"authentication":{},"metaSortKey":-1713352734142,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_836d3e356d104435a7c65da02f4f0de6","parentId":"fld_dc469eca2f0b4154a1107f8891780e64","modified":1713873399318,"created":1713869763503,"url":"{{BASE_URL}}/courier-history","name":"GET ALL","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459845,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_dc469eca2f0b4154a1107f8891780e64","parentId":"fld_525e91f8f25a40a3923f48d98b8bd749","modified":1713873399318,"created":1713869763503,"name":"Courier History","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1713352459744,"_type":"request_group"},{"_id":"fld_525e91f8f25a40a3923f48d98b8bd749","parentId":"wrk_3535ce5326004d399f55741c92200a1b","modified":1713873399318,"created":1713869763481,"name":"eggs","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1713352459874,"_type":"request_group"},{"_id":"req_d932a6c1fc8d4df39baf3e77d3b8640a","parentId":"fld_dc469eca2f0b4154a1107f8891780e64","modified":1713873399318,"created":1713869763503,"url":"{{BASE_URL}}/courier-history/1","name":"GET ONE","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459795,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_b3968facb2ec4fcea5c2c464e2dcf00d","parentId":"fld_dc469eca2f0b4154a1107f8891780e64","modified":1713873399318,"created":1713869763503,"url":"{{BASE_URL}}/courier-history","name":"DELETE","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459770,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_efa908802cae4670949b6bcbc866e982","parentId":"fld_dc469eca2f0b4154a1107f8891780e64","modified":1713873399318,"created":1713869763504,"url":"{{BASE_URL}}/courier-history/1/","name":"PUT","description":"","method":"PUT","body":{"mimeType":"application/json","text":""},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1713352459745,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_ea09405a49b34820b56cc2eb86fd9585","parentId":"fld_dc469eca2f0b4154a1107f8891780e64","modified":1713873399318,"created":1713869763503,"url":"{{BASE_URL}}/courier-history/","name":"POST","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"courier_id\": 1,\n \"group_id\": 1,\n \"get_eggs\": 10\n}\n"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1713352459745,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_3fc27530956f492a98170a5cd58bdfde","parentId":"fld_25331b6d777444f8be314f215e33fe13","modified":1713873399318,"created":1713869763501,"url":"{{BASE_URL}}/courier","name":"GET ALL","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459843,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_25331b6d777444f8be314f215e33fe13","parentId":"fld_525e91f8f25a40a3923f48d98b8bd749","modified":1713873399318,"created":1713869763501,"name":"Courier","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1713352459742,"_type":"request_group"},{"_id":"req_793fd2d3e68a4a01977a478095619adc","parentId":"fld_25331b6d777444f8be314f215e33fe13","modified":1713873399318,"created":1713869763502,"url":"{{BASE_URL}}/courier/1","name":"GET ONE","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459793,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_b56a3c57cf1f44bfbe747ac1f3ec73e5","parentId":"fld_25331b6d777444f8be314f215e33fe13","modified":1713873399318,"created":1713869763502,"url":"{{BASE_URL}}/courier","name":"DELETE","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459743,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_6960e2936e7e496ab685e3914af60a94","parentId":"fld_25331b6d777444f8be314f215e33fe13","modified":1713873399318,"created":1713869763502,"url":"{{BASE_URL}}/courier","name":"PUT","description":"","method":"PUT","body":{"mimeType":"","text":"{\n \"name\": \"Umidjon\",\n \"phone\": \"998938857747\",\n \"delivery_date\": \"2024-04-11\",\n \"delivery_hour\": \"12:30\",\n \"ask_location\": \"True\"\n}\n"},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459743,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_2e8e5ac3d9784569a44ed73175be9d01","parentId":"fld_25331b6d777444f8be314f215e33fe13","modified":1713873399318,"created":1713869763502,"url":"{{BASE_URL}}/courier/","name":"POST","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"user_id\": 1\n}\n"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1713352459743,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_e7348fec62774e329063059539415202","parentId":"fld_d079481fb8424b64b66db5efa241b792","modified":1713873399318,"created":1713869763501,"url":"{{BASE_URL}}/order/","name":"GET ALL","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459842,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_d079481fb8424b64b66db5efa241b792","parentId":"fld_525e91f8f25a40a3923f48d98b8bd749","modified":1713873399318,"created":1713869763500,"name":"Order","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1713352459741,"_type":"request_group"},{"_id":"req_900a1d58499346edbb27154b3ed79bb9","parentId":"fld_d079481fb8424b64b66db5efa241b792","modified":1713873399318,"created":1713869763500,"url":"{{BASE_URL}}/order/1","name":"GET ONE","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459792,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_6912414c5f044de6aee30d4350db0464","parentId":"fld_d079481fb8424b64b66db5efa241b792","modified":1713873399318,"created":1713869763500,"url":"{{BASE_URL}}/order/","name":"DELETE","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459767,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_d9a7f451a94444c0ad6b0a1262d327e2","parentId":"fld_d079481fb8424b64b66db5efa241b792","modified":1713873399318,"created":1713869763501,"url":"{{BASE_URL}}/order/","name":"PUT","description":"","method":"PUT","body":{"mimeType":"application/json","text":"{\n \"price_paid\": 200000,\n \"market_id\": 1,\n \"courier_id\": 1,\n \"location_id\": 1,\n\t\t\"price\": 2000\n}\n"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1713352459742,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_55e129306d874b749a0d6bf5b7ebc211","parentId":"fld_d079481fb8424b64b66db5efa241b792","modified":1713873399318,"created":1713869763501,"url":"{{BASE_URL}}/order/","name":"POST","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"price_paid\": 200000,\n \"market_id\": 1,\n \"courier_id\": 1,\n \"location_id\": 1,\n\t\t\"price\": 2000\n}\n"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1713352459641,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_32603fbd3c834726b867657ca64bf838","parentId":"fld_173cd4f16a9f4e06828e042eef393973","modified":1713873399318,"created":1713869763499,"url":"{{BASE_URL}}/party","name":"GET ALL","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459840,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_173cd4f16a9f4e06828e042eef393973","parentId":"fld_525e91f8f25a40a3923f48d98b8bd749","modified":1713873399318,"created":1713869763498,"name":"Party","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1713352459739,"_type":"request_group"},{"_id":"req_3e42b7dd6a8546458f5d8c9946d44d0d","parentId":"fld_173cd4f16a9f4e06828e042eef393973","modified":1713873399318,"created":1713869763499,"url":"{{BASE_URL}}/party/1","name":"GET ONE","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459740,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_e8ad22c45f0345638ae44d6ed2cffe88","parentId":"fld_173cd4f16a9f4e06828e042eef393973","modified":1713873399318,"created":1713869763498,"url":"{{BASE_URL}}/party/1","name":"DELETE","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459690,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_2e705867361b49e6b25d2b734770da5f","parentId":"fld_173cd4f16a9f4e06828e042eef393973","modified":1713873399318,"created":1713869763499,"url":"{{BASE_URL}}/party/","name":"PUT","description":"","method":"PUT","body":{"mimeType":"application/json","text":"{\n \"user_id\": 1,\n \"postcard\": \"Salom\",\n \"receiver_id\": 1,\n \"address\": 1,\n\t\t\"price\": 2000\n}\n"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1713352459665,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_58c46932285c4a23b48936cfccdcabf5","parentId":"fld_173cd4f16a9f4e06828e042eef393973","modified":1713873399318,"created":1713869763499,"url":"{{BASE_URL}}/party/","name":"POST","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n \"user_id\": 1,\n \"postcard\": \"Salom\",\n \"receiver_id\": 1,\n \"address\": 1,\n\t\t\"price\": 2000\n}\n"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1713352459640,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_d0e6ad1671784c4cb697f1aa40fdda49","parentId":"fld_21837b6af6d040a19575e5d151ef7c37","modified":1713873399318,"created":1713869763497,"url":"{{BASE_URL}}/group","name":"GET ALL","description":"","method":"GET","body":{"mimeType":"application/json","text":""},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1713352459839,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_21837b6af6d040a19575e5d151ef7c37","parentId":"fld_525e91f8f25a40a3923f48d98b8bd749","modified":1713873399318,"created":1713869763496,"name":"Group","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1713352459738,"_type":"request_group"},{"_id":"req_d83b5ea119624de5a24f396f80bbc300","parentId":"fld_21837b6af6d040a19575e5d151ef7c37","modified":1713873399318,"created":1713869763496,"url":"{{BASE_URL}}/group/1","name":"GET ONE","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459789,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_711cc1a060ce4d40b205c0e95f7d2070","parentId":"fld_21837b6af6d040a19575e5d151ef7c37","modified":1713873399318,"created":1713869763497,"url":"{{BASE_URL}}/group","name":"DELETE","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459739,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_0df8f7842910477bbf2d2f86d4595102","parentId":"fld_21837b6af6d040a19575e5d151ef7c37","modified":1713873399318,"created":1713869763497,"url":"{{BASE_URL}}/group","name":"PUT","description":"","method":"PUT","body":{"mimeType":"application/json","text":"{\n \"name\": \"Jerry\",\n \"value\": \"Tom\",\n \"flower_id\": 1\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1713352459739,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_115aeb508dc54b2d834ddc028c196e03","parentId":"fld_21837b6af6d040a19575e5d151ef7c37","modified":1713873399318,"created":1713869763497,"url":"{{BASE_URL}}/group/","name":"POST","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"entry_price\": 1211,\n\t\"unit_price\": 121,\n\t\"wholesale_price\": 1,\n\t\"quantity\": 20,\n\t\"broken_eggs\": 2,\n\t\"product_id\": 1,\n\t\"party_id\": 1\n}"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1713352459639,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_db4f1ec4590249c1831ede24486ff0b9","parentId":"fld_47e0694218b74458a8b226c3fa0660ab","modified":1713873399318,"created":1713869763496,"url":"{{BASE_URL}}/product","name":"GET ALL","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459838,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_47e0694218b74458a8b226c3fa0660ab","parentId":"fld_525e91f8f25a40a3923f48d98b8bd749","modified":1713873399318,"created":1713869763494,"name":"Product","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1713352459736,"_type":"request_group"},{"_id":"req_6ee50f9295334f53a51636ce8a56aca0","parentId":"fld_47e0694218b74458a8b226c3fa0660ab","modified":1713873399318,"created":1713869763495,"url":"{{BASE_URL}}/product","name":"GET ONE","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459788,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_d2143eca656e49a19637dd95665cb05c","parentId":"fld_47e0694218b74458a8b226c3fa0660ab","modified":1713873399318,"created":1713869763494,"url":"{{BASE_URL}}/product","name":"DELETE","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459775.5,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_4c92ff7f556248be8bc0198fccf34277","parentId":"fld_47e0694218b74458a8b226c3fa0660ab","modified":1713873399318,"created":1713869763495,"url":"{{BASE_URL}}/product/2/","name":"PUT","description":"","method":"PUT","body":{"mimeType":"application/json","text":"{\n \"name\": \"White\"\n}\n"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1713352459763,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_9e148304317c4946aff66caeb229a6e1","parentId":"fld_47e0694218b74458a8b226c3fa0660ab","modified":1713873399318,"created":1713869763496,"url":"{{BASE_URL}}/product/","name":"POST","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"name\": \"Qora tuxum\"\n}\n"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1713352459637,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_4226b4df19544070aab6bc974182f263","parentId":"fld_597898a372474d6ea69f5408cc63fec6","modified":1713873399318,"created":1713872928793,"url":"{{BASE_URL}}/market","name":"GET ALL","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459838,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"fld_597898a372474d6ea69f5408cc63fec6","parentId":"fld_525e91f8f25a40a3923f48d98b8bd749","modified":1713873399318,"created":1713872928790,"name":"Market","description":"","environment":{},"environmentPropertyOrder":null,"metaSortKey":-1713352459735.5,"_type":"request_group"},{"_id":"req_327fb2e5cfc84fab868b8f52f6324cd8","parentId":"fld_597898a372474d6ea69f5408cc63fec6","modified":1713873399318,"created":1713872928792,"url":"{{BASE_URL}}/market/1","name":"GET ONE","description":"","method":"GET","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459788,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_616a4c5250934634b6df695e7f407ead","parentId":"fld_597898a372474d6ea69f5408cc63fec6","modified":1713873399318,"created":1713872928791,"url":"{{BASE_URL}}/market","name":"DELETE","description":"","method":"DELETE","body":{},"parameters":[],"headers":[],"authentication":{},"metaSortKey":-1713352459775.5,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_f92c268dceb5411da1953c215d2b5bbd","parentId":"fld_597898a372474d6ea69f5408cc63fec6","modified":1713873399318,"created":1713872928792,"url":"{{BASE_URL}}/market/2/","name":"PUT","description":"","method":"PUT","body":{"mimeType":"application/json","text":"{\n \"name\": \"White\"\n}\n"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1713352459763,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"req_b2ece3768e7b487196961e94e57a4fdf","parentId":"fld_597898a372474d6ea69f5408cc63fec6","modified":1713873399318,"created":1713872928793,"url":"{{BASE_URL}}/market/","name":"POST","description":"","method":"POST","body":{"mimeType":"application/json","text":"{\n\t\"name\": \"Korzinka\",\n\t\"phone\": \"+998938857747\",\n\t\"user_id\": 1,\n\t\"location_id\": 1\n}\n"},"parameters":[],"headers":[{"name":"Content-Type","value":"application/json"}],"authentication":{},"metaSortKey":-1713352459637,"isPrivate":false,"pathParameters":[],"settingStoreCookies":true,"settingSendCookies":true,"settingDisableRenderRequestBody":false,"settingEncodeUrl":true,"settingRebuildPath":true,"settingFollowRedirects":"global","_type":"request"},{"_id":"env_102f8913e3a24472966919234c49f4b9","parentId":"wrk_3535ce5326004d399f55741c92200a1b","modified":1713873399318,"created":1713869763478,"name":"Base Environment","data":{"BASE_URL":"http://127.0.0.1:8001/api"},"dataPropertyOrder":{"&":["BASE_URL"]},"color":null,"isPrivate":false,"metaSortKey":1713272680405,"_type":"environment"},{"_id":"jar_b02af93236c14f8bb014e8c3b1fa60d1","parentId":"wrk_3535ce5326004d399f55741c92200a1b","modified":1713869763480,"created":1713869763480,"name":"Default Jar","cookies":[],"_type":"cookie_jar"},{"_id":"spc_cbc26f7215064756b97488390acb75cf","parentId":"wrk_3535ce5326004d399f55741c92200a1b","modified":1713869763511,"created":1713869763511,"fileName":"eggs","contents":"","contentType":"yaml","_type":"api_spec"},{"_id":"env_e5e02b7a8d2a46b59467c0b6de4a9615","parentId":"env_102f8913e3a24472966919234c49f4b9","modified":1713869763479,"created":1713869763479,"name":"Production","data":{"BASE_URL":"http://127.0.0.1:8080/api"},"dataPropertyOrder":{"&":["BASE_URL"]},"color":null,"isPrivate":false,"metaSortKey":1713273570609,"_type":"environment"}]} \ No newline at end of file diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100755 index 0000000..e69de29 diff --git a/docs/swagger.yaml b/docs/swagger.yaml new file mode 100755 index 0000000..e69de29 diff --git a/locale/.gitkeep b/locale/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/locale/en/LC_MESSAGES/django.po b/locale/en/LC_MESSAGES/django.po new file mode 100755 index 0000000..0ea109f --- /dev/null +++ b/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 "Jscorp/django" +msgstr "" + +#: resources/templates/user/home.html:19 +msgid "Assalomu aleykum" +msgstr "" diff --git a/locale/ru/LC_MESSAGES/django.po b/locale/ru/LC_MESSAGES/django.po new file mode 100755 index 0000000..df2a1ef --- /dev/null +++ b/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 "Jscorp/django" +msgstr "" + +#: resources/templates/user/home.html:19 +msgid "Assalomu aleykum" +msgstr "" diff --git a/locale/uz/LC_MESSAGES/django.po b/locale/uz/LC_MESSAGES/django.po new file mode 100755 index 0000000..55033a8 --- /dev/null +++ b/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 "Jscorp/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/logs/.gitignore b/logs/.gitignore new file mode 100755 index 0000000..5da1072 --- /dev/null +++ b/logs/.gitignore @@ -0,0 +1 @@ +django.log \ No newline at end of file diff --git a/logs/.gitkeep b/logs/.gitkeep new file mode 100755 index 0000000..e69de29 diff --git a/manage.py b/manage.py new file mode 100755 index 0000000..eba792a --- /dev/null +++ b/manage.py @@ -0,0 +1,26 @@ +#!/usr/bin/env python +"""Django's command-line utility for administrative tasks.""" +import os +import sys + +from common.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/media/avatars/golden_eggs.png b/media/avatars/golden_eggs.png new file mode 100644 index 0000000..3349f65 Binary files /dev/null and b/media/avatars/golden_eggs.png differ diff --git a/media/market_avatar/default.png b/media/market_avatar/default.png new file mode 100644 index 0000000..de1f361 Binary files /dev/null and b/media/market_avatar/default.png differ diff --git a/nginx/Dockerfile b/nginx/Dockerfile new file mode 100644 index 0000000..31a4396 --- /dev/null +++ b/nginx/Dockerfile @@ -0,0 +1,7 @@ +FROM nginx + +RUN rm /etc/nginx/conf.d/default.conf + +COPY default.conf /etc/nginx/conf.d/ + +RUN ls /etc/nginx/conf.d/ diff --git a/nginx/default.conf b/nginx/default.conf new file mode 100644 index 0000000..021960a --- /dev/null +++ b/nginx/default.conf @@ -0,0 +1,22 @@ +upstream django { + server web:8000; +} + + +server { + listen 80; + server_name domain.com; + root /code/; + access_log /var/log/nginx/django.access.log; + error_log /var/log/nginx/django.error.log; + client_max_body_size 1024M; + try_files $uri @django; + + location @django { + proxy_pass http://django; + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } +} diff --git a/package-lock.json b/package-lock.json new file mode 100755 index 0000000..c6d8b88 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,2673 @@ +{ + "name": "django-vite-starter", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "django-vite-starter", + "version": "1.0.0", + "dependencies": { + "@vitejs/plugin-react": "^4.2.1", + "alpinejs": "^3.13.5", + "path": "^0.12.7", + "tailwindcss": "^3.4.3", + "vite": "^5.1.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@ampproject/remapping": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.1.tgz", + "integrity": "sha512-lFMjJTrFL3j7L9yBxwYfCq2k6qqwHyzuUl/XBnif78PWTJYyL/dfowQHWE3sp6U6ZzqWiiIZnpTMO96zhkjwtg==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.0", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz", + "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==", + "dependencies": { + "@babel/highlight": "^7.23.4", + "chalk": "^2.4.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/compat-data": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.23.5.tgz", + "integrity": "sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/core": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.23.9.tgz", + "integrity": "sha512-5q0175NOjddqpvvzU+kDiSOAk4PfdO6FvwCWoQ6RO7rTzEe8vlo+4HVfcnAREhD4npMs0e9uZypjTwzZPCf/cw==", + "dependencies": { + "@ampproject/remapping": "^2.2.0", + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-compilation-targets": "^7.23.6", + "@babel/helper-module-transforms": "^7.23.3", + "@babel/helpers": "^7.23.9", + "@babel/parser": "^7.23.9", + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@babel/generator": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.6.tgz", + "integrity": "sha512-qrSfCYxYQB5owCmGLbl8XRpX1ytXlpueOb0N0UmQwA073KZxejgQTzAmJezxvpwQD9uGtK2shHdi55QT+MbjIw==", + "dependencies": { + "@babel/types": "^7.23.6", + "@jridgewell/gen-mapping": "^0.3.2", + "@jridgewell/trace-mapping": "^0.3.17", + "jsesc": "^2.5.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-compilation-targets": { + "version": "7.23.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.23.6.tgz", + "integrity": "sha512-9JB548GZoQVmzrFgp8o7KxdgkTGm6xs9DW0o/Pim72UDjzr5ObUQ6ZzYPqA+g9OTS2bBQoctLJrky0RDCAWRgQ==", + "dependencies": { + "@babel/compat-data": "^7.23.5", + "@babel/helper-validator-option": "^7.23.5", + "browserslist": "^4.22.2", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-environment-visitor": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz", + "integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-function-name": { + "version": "7.23.0", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz", + "integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==", + "dependencies": { + "@babel/template": "^7.22.15", + "@babel/types": "^7.23.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-hoist-variables": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.22.5.tgz", + "integrity": "sha512-wGjk9QZVzvknA6yKIUURb8zY3grXCcOZt+/7Wcy8O2uctxhplmUPkOdlgoNhmdVee2c92JXbf1xpMtVNbfoxRw==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-imports": { + "version": "7.22.15", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.22.15.tgz", + "integrity": "sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==", + "dependencies": { + "@babel/types": "^7.22.15" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-module-transforms": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.23.3.tgz", + "integrity": "sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==", + "dependencies": { + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-module-imports": "^7.22.15", + "@babel/helper-simple-access": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/helper-validator-identifier": "^7.22.20" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@babel/helper-plugin-utils": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.22.5.tgz", + "integrity": "sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-simple-access": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.22.5.tgz", + "integrity": "sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-split-export-declaration": { + "version": "7.22.6", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz", + "integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==", + "dependencies": { + "@babel/types": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.23.4.tgz", + "integrity": "sha512-803gmbQdqwdf4olxrX4AJyFBV/RTr3rSmOj0rKwesmzlfhYNDEs+/iOcznzpNWlJlIlTJC2QfPFcHB6DlzdVLQ==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.20", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz", + "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-option": { + "version": "7.23.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.23.5.tgz", + "integrity": "sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helpers": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.23.9.tgz", + "integrity": "sha512-87ICKgU5t5SzOT7sBMfCOZQ2rHjRU+Pcb9BoILMYz600W6DkVRLFBPwQ18gwUVvggqXivaUakpnxWQGbpywbBQ==", + "dependencies": { + "@babel/template": "^7.23.9", + "@babel/traverse": "^7.23.9", + "@babel/types": "^7.23.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.23.4", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz", + "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==", + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.20", + "chalk": "^2.4.2", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.9.tgz", + "integrity": "sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-self": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.23.3.tgz", + "integrity": "sha512-qXRvbeKDSfwnlJnanVRp0SfuWE5DQhwQr5xtLBzp56Wabyo+4CMosF6Kfp+eOD/4FYpql64XVJ2W0pVLlJZxOQ==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/plugin-transform-react-jsx-source": { + "version": "7.23.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.23.3.tgz", + "integrity": "sha512-91RS0MDnAWDNvGC6Wio5XYkyWI39FMFO+JK9+4AlgaTH+yWwVTsw7/sn6LK0lH7c5F+TFkpv/3LfCJ1Ydwof/g==", + "dependencies": { + "@babel/helper-plugin-utils": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0-0" + } + }, + "node_modules/@babel/template": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.23.9.tgz", + "integrity": "sha512-+xrD2BWLpvHKNmX2QbpdpsBaWnRxahMwJjO+KZk2JOElj5nSmKezyS1B4u+QbHMTX69t4ukm6hh9lsYQ7GHCKA==", + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/traverse": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.9.tgz", + "integrity": "sha512-I/4UJ9vs90OkBtY6iiiTORVMyIhJ4kAVmsKo9KFc8UOxMeUfi2hvtIBsET5u9GizXE6/GFSuKCTNfgCswuEjRg==", + "dependencies": { + "@babel/code-frame": "^7.23.5", + "@babel/generator": "^7.23.6", + "@babel/helper-environment-visitor": "^7.22.20", + "@babel/helper-function-name": "^7.23.0", + "@babel/helper-hoist-variables": "^7.22.5", + "@babel/helper-split-export-declaration": "^7.22.6", + "@babel/parser": "^7.23.9", + "@babel/types": "^7.23.9", + "debug": "^4.3.1", + "globals": "^11.1.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/types": { + "version": "7.23.9", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.9.tgz", + "integrity": "sha512-dQjSq/7HaSjRM43FFGnv5keM2HsxpmyV1PfaSVm0nzzjwwTmjOe6J4bC8e3+pTEIgHaHj+1ZlLThRJ2auc/w1Q==", + "dependencies": { + "@babel/helper-string-parser": "^7.23.4", + "@babel/helper-validator-identifier": "^7.22.20", + "to-fast-properties": "^2.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.20.2.tgz", + "integrity": "sha512-D+EBOJHXdNZcLJRBkhENNG8Wji2kgc9AZ9KiPr1JuZjsNtyHzrsfLRrY0tk2H2aoFu6RANO1y1iPPUCDYWkb5g==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.20.2.tgz", + "integrity": "sha512-t98Ra6pw2VaDhqNWO2Oph2LXbz/EJcnLmKLGBJwEwXX/JAN83Fym1rU8l0JUWK6HkIbWONCSSatf4sf2NBRx/w==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.20.2.tgz", + "integrity": "sha512-mRzjLacRtl/tWU0SvD8lUEwb61yP9cqQo6noDZP/O8VkwafSYwZ4yWy24kan8jE/IMERpYncRt2dw438LP3Xmg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.20.2.tgz", + "integrity": "sha512-btzExgV+/lMGDDa194CcUQm53ncxzeBrWJcncOBxuC6ndBkKxnHdFJn86mCIgTELsooUmwUm9FkhSp5HYu00Rg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.20.2.tgz", + "integrity": "sha512-4J6IRT+10J3aJH3l1yzEg9y3wkTDgDk7TSDFX+wKFiWjqWp/iCfLIYzGyasx9l0SAFPT1HwSCR+0w/h1ES/MjA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.20.2.tgz", + "integrity": "sha512-tBcXp9KNphnNH0dfhv8KYkZhjc+H3XBkF5DKtswJblV7KlT9EI2+jeA8DgBjp908WEuYll6pF+UStUCfEpdysA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.20.2.tgz", + "integrity": "sha512-d3qI41G4SuLiCGCFGUrKsSeTXyWG6yem1KcGZVS+3FYlYhtNoNgYrWcvkOoaqMhwXSMrZRl69ArHsGJ9mYdbbw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.20.2.tgz", + "integrity": "sha512-d+DipyvHRuqEeM5zDivKV1KuXn9WeRX6vqSqIDgwIfPQtwMP4jaDsQsDncjTDDsExT4lR/91OLjRo8bmC1e+Cw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.20.2.tgz", + "integrity": "sha512-VhLPeR8HTMPccbuWWcEUD1Az68TqaTYyj6nfE4QByZIQEQVWBB8vup8PpR7y1QHL3CpcF6xd5WVBU/+SBEvGTg==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.20.2.tgz", + "integrity": "sha512-9pb6rBjGvTFNira2FLIWqDk/uaf42sSyLE8j1rnUpuzsODBq7FvpwHYZxQ/It/8b+QOS1RYfqgGFNLRI+qlq2A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.20.2.tgz", + "integrity": "sha512-o10utieEkNPFDZFQm9CoP7Tvb33UutoJqg3qKf1PWVeeJhJw0Q347PxMvBgVVFgouYLGIhFYG0UGdBumROyiig==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.20.2.tgz", + "integrity": "sha512-PR7sp6R/UC4CFVomVINKJ80pMFlfDfMQMYynX7t1tNTeivQ6XdX5r2XovMmha/VjR1YN/HgHWsVcTRIMkymrgQ==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.20.2.tgz", + "integrity": "sha512-4BlTqeutE/KnOiTG5Y6Sb/Hw6hsBOZapOVF6njAESHInhlQAghVVZL1ZpIctBOoTFbQyGW+LsVYZ8lSSB3wkjA==", + "cpu": [ + "mips64el" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.20.2.tgz", + "integrity": "sha512-rD3KsaDprDcfajSKdn25ooz5J5/fWBylaaXkuotBDGnMnDP1Uv5DLAN/45qfnf3JDYyJv/ytGHQaziHUdyzaAg==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.20.2.tgz", + "integrity": "sha512-snwmBKacKmwTMmhLlz/3aH1Q9T8v45bKYGE3j26TsaOVtjIag4wLfWSiZykXzXuE1kbCE+zJRmwp+ZbIHinnVg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.20.2.tgz", + "integrity": "sha512-wcWISOobRWNm3cezm5HOZcYz1sKoHLd8VL1dl309DiixxVFoFe/o8HnwuIwn6sXre88Nwj+VwZUvJf4AFxkyrQ==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.20.2.tgz", + "integrity": "sha512-1MdwI6OOTsfQfek8sLwgyjOXAu+wKhLEoaOLTjbijk6E2WONYpH9ZU2mNtR+lZ2B4uwr+usqGuVfFT9tMtGvGw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.20.2.tgz", + "integrity": "sha512-K8/DhBxcVQkzYc43yJXDSyjlFeHQJBiowJ0uVL6Tor3jGQfSGHNNJcWxNbOI8v5k82prYqzPuwkzHt3J1T1iZQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.20.2.tgz", + "integrity": "sha512-eMpKlV0SThJmmJgiVyN9jTPJ2VBPquf6Kt/nAoo6DgHAoN57K15ZghiHaMvqjCye/uU4X5u3YSMgVBI1h3vKrQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.20.2.tgz", + "integrity": "sha512-2UyFtRC6cXLyejf/YEld4Hajo7UHILetzE1vsRcGL3earZEW77JxrFjH4Ez2qaTiEfMgAXxfAZCm1fvM/G/o8w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.20.2.tgz", + "integrity": "sha512-GRibxoawM9ZCnDxnP3usoUDO9vUkpAxIIZ6GQI+IlVmr5kP3zUq+l17xELTHMWTWzjxa2guPNyrpq1GWmPvcGQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.20.2.tgz", + "integrity": "sha512-HfLOfn9YWmkSKRQqovpnITazdtquEW8/SoHW7pWpuEeguaZI4QnCRW6b+oZTztdBnZOS2hqJ6im/D5cPzBTTlQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.20.2.tgz", + "integrity": "sha512-N49X4lJX27+l9jbLKSqZ6bKNjzQvHaT8IIFUy+YIqmXQdjYCToGWwOItDrfby14c78aDd5NHQl29xingXfCdLQ==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@isaacs/cliui": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/@isaacs/cliui/-/cliui-8.0.2.tgz", + "integrity": "sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==", + "dependencies": { + "string-width": "^5.1.2", + "string-width-cjs": "npm:string-width@^4.2.0", + "strip-ansi": "^7.0.1", + "strip-ansi-cjs": "npm:strip-ansi@^6.0.1", + "wrap-ansi": "^8.1.0", + "wrap-ansi-cjs": "npm:wrap-ansi@^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.1.tgz", + "integrity": "sha512-dSYZh7HhCDtCKm4QakX0xFpsRDqjjtZf/kjI/v3T3Nwt5r8/qz/M19F9ySyOqU94SXBmeG9ttTul+YnR4LOxFA==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.22", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.22.tgz", + "integrity": "sha512-Wf963MzWtA2sjrNt+g18IAln9lKnlRp+K2eH4jjIoF1wYeq3aMREpG09xhlhdzS0EjwU7qmUJYangWa+151vZw==", + "dependencies": { + "@jridgewell/resolve-uri": "^3.1.0", + "@jridgewell/sourcemap-codec": "^1.4.14" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@pkgjs/parseargs": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/@pkgjs/parseargs/-/parseargs-0.11.0.tgz", + "integrity": "sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==", + "optional": true, + "engines": { + "node": ">=14" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.18.0.tgz", + "integrity": "sha512-Tya6xypR10giZV1XzxmH5wr25VcZSncG0pZIjfePT0OVBvqNEurzValetGNarVrGiq66EBVAFn15iYX4w6FKgQ==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.18.0.tgz", + "integrity": "sha512-avCea0RAP03lTsDhEyfy+hpfr85KfyTctMADqHVhLAF3MlIkq83CP8UfAHUssgXTYd+6er6PaAhx/QGv4L1EiA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.18.0.tgz", + "integrity": "sha512-IWfdwU7KDSm07Ty0PuA/W2JYoZ4iTj3TUQjkVsO/6U+4I1jN5lcR71ZEvRh52sDOERdnNhhHU57UITXz5jC1/w==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.18.0.tgz", + "integrity": "sha512-n2LMsUz7Ynu7DoQrSQkBf8iNrjOGyPLrdSg802vk6XT3FtsgX6JbE8IHRvposskFm9SNxzkLYGSq9QdpLYpRNA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.18.0.tgz", + "integrity": "sha512-C/zbRYRXFjWvz9Z4haRxcTdnkPt1BtCkz+7RtBSuNmKzMzp3ZxdM28Mpccn6pt28/UWUCTXa+b0Mx1k3g6NOMA==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.18.0.tgz", + "integrity": "sha512-l3m9ewPgjQSXrUMHg93vt0hYCGnrMOcUpTz6FLtbwljo2HluS4zTXFy2571YQbisTnfTKPZ01u/ukJdQTLGh9A==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.18.0.tgz", + "integrity": "sha512-rJ5D47d8WD7J+7STKdCUAgmQk49xuFrRi9pZkWoRD1UeSMakbcepWXPF8ycChBoAqs1pb2wzvbY6Q33WmN2ftw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.18.0.tgz", + "integrity": "sha512-be6Yx37b24ZwxQ+wOQXXLZqpq4jTckJhtGlWGZs68TgdKXJgw54lUUoFYrg6Zs/kjzAQwEwYbp8JxZVzZLRepQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-powerpc64le-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-powerpc64le-gnu/-/rollup-linux-powerpc64le-gnu-4.18.0.tgz", + "integrity": "sha512-hNVMQK+qrA9Todu9+wqrXOHxFiD5YmdEi3paj6vP02Kx1hjd2LLYR2eaN7DsEshg09+9uzWi2W18MJDlG0cxJA==", + "cpu": [ + "ppc64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.18.0.tgz", + "integrity": "sha512-ROCM7i+m1NfdrsmvwSzoxp9HFtmKGHEqu5NNDiZWQtXLA8S5HBCkVvKAxJ8U+CVctHwV2Gb5VUaK7UAkzhDjlg==", + "cpu": [ + "riscv64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.18.0.tgz", + "integrity": "sha512-0UyyRHyDN42QL+NbqevXIIUnKA47A+45WyasO+y2bGJ1mhQrfrtXUpTxCOrfxCR4esV3/RLYyucGVPiUsO8xjg==", + "cpu": [ + "s390x" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.18.0.tgz", + "integrity": "sha512-xuglR2rBVHA5UsI8h8UbX4VJ470PtGCf5Vpswh7p2ukaqBGFTnsfzxUBetoWBWymHMxbIG0Cmx7Y9qDZzr648w==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.18.0.tgz", + "integrity": "sha512-LKaqQL9osY/ir2geuLVvRRs+utWUNilzdE90TpyoX0eNqPzWjRm14oMEE+YLve4k/NAqCdPkGYDaDF5Sw+xBfg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.18.0.tgz", + "integrity": "sha512-7J6TkZQFGo9qBKH0pk2cEVSRhJbL6MtfWxth7Y5YmZs57Pi+4x6c2dStAUvaQkHQLnEQv1jzBUW43GvZW8OFqA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.18.0.tgz", + "integrity": "sha512-Txjh+IxBPbkUB9+SXZMpv+b/vnTEtFyfWZgJ6iyCmt2tdx0OF5WhFowLmnh8ENGNpfUlUZkdI//4IEmhwPieNg==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz", + "integrity": "sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/babel__core": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", + "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==", + "dependencies": { + "@babel/parser": "^7.20.7", + "@babel/types": "^7.20.7", + "@types/babel__generator": "*", + "@types/babel__template": "*", + "@types/babel__traverse": "*" + } + }, + "node_modules/@types/babel__generator": { + "version": "7.6.8", + "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.6.8.tgz", + "integrity": "sha512-ASsj+tpEDsEiFr1arWrlN6V3mdfjRMZt6LtK/Vp/kreFLnr5QH5+DhvD5nINYZXzwJvXeGq+05iUXcAzVrqWtw==", + "dependencies": { + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__template": { + "version": "7.4.4", + "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz", + "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==", + "dependencies": { + "@babel/parser": "^7.1.0", + "@babel/types": "^7.0.0" + } + }, + "node_modules/@types/babel__traverse": { + "version": "7.20.5", + "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.20.5.tgz", + "integrity": "sha512-WXCyOcRtH37HAUkpXhUduaxdm82b4GSlyTqajXviN4EfiuPgNYR109xMCKvpl6zPIpua0DGlMEDCq+g8EdoheQ==", + "dependencies": { + "@babel/types": "^7.20.7" + } + }, + "node_modules/@types/estree": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz", + "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.2.1.tgz", + "integrity": "sha512-oojO9IDc4nCUUi8qIR11KoQm0XFFLIwsRBwHRR4d/88IWghn1y6ckz/bJ8GHDCsYEJee8mDzqtJxh15/cisJNQ==", + "dependencies": { + "@babel/core": "^7.23.5", + "@babel/plugin-transform-react-jsx-self": "^7.23.3", + "@babel/plugin-transform-react-jsx-source": "^7.23.3", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.14.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.1.5.tgz", + "integrity": "sha512-1tdfLmNjWG6t/CsPldh+foumYFo3cpyCHgBYQ34ylaMsJ+SNHQ1kApMIa8jN+i593zQuaw3AdWH0nJTARzCFhg==", + "dependencies": { + "@vue/shared": "3.1.5" + } + }, + "node_modules/@vue/shared": { + "version": "3.1.5", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.1.5.tgz", + "integrity": "sha512-oJ4F3TnvpXaQwZJNF3ZK+kLPHKarDmJjJ6jyzVNDKH9md1dptjC7lWR//jrGuLdek/U6iltWxqAnYOu8gCiOvA==" + }, + "node_modules/alpinejs": { + "version": "3.13.5", + "resolved": "https://registry.npmjs.org/alpinejs/-/alpinejs-3.13.5.tgz", + "integrity": "sha512-1d2XeNGN+Zn7j4mUAKXtAgdc4/rLeadyTMWeJGXF5DzwawPBxwTiBhFFm6w/Ei8eJxUZeyNWWSD9zknfdz1kEw==", + "dependencies": { + "@vue/reactivity": "~3.1.1" + } + }, + "node_modules/ansi-regex": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.0.1.tgz", + "integrity": "sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-regex?sponsor=1" + } + }, + "node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==" + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==" + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/browserslist": { + "version": "4.22.3", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.22.3.tgz", + "integrity": "sha512-UAp55yfwNv0klWNapjs/ktHoguxuQNGnOzxYmfnXIS+8AsRDZkSDxg7R1AX3GKzn078SBI5dzwzj/Yx0Or0e3A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001580", + "electron-to-chromium": "^1.4.648", + "node-releases": "^2.0.14", + "update-browserslist-db": "^1.0.13" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001585", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001585.tgz", + "integrity": "sha512-yr2BWR1yLXQ8fMpdS/4ZZXpseBgE7o4g41x3a6AJOqZuOi+iE/WdJYAuZ6Y95i4Ohd2Y+9MzIWRR+uGABH4s3Q==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==" + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==" + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==" + }, + "node_modules/eastasianwidth": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", + "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==" + }, + "node_modules/electron-to-chromium": { + "version": "1.4.665", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.665.tgz", + "integrity": "sha512-UpyCWObBoD+nSZgOC2ToaIdZB0r9GhqT2WahPKiSki6ckkSuKhQNso8V2PrFcHBMleI/eqbKgVQgVC4Wni4ilw==" + }, + "node_modules/emoji-regex": { + "version": "9.2.2", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", + "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==" + }, + "node_modules/esbuild": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.20.2.tgz", + "integrity": "sha512-WdOOppmUNU+IbZ0PaDiTst80zjnrOkyJNHoKupIcVyU8Lvla3Ugx94VzkQ32Ijqd7UhHJy75gNWDMUekcrSJ6g==", + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.20.2", + "@esbuild/android-arm": "0.20.2", + "@esbuild/android-arm64": "0.20.2", + "@esbuild/android-x64": "0.20.2", + "@esbuild/darwin-arm64": "0.20.2", + "@esbuild/darwin-x64": "0.20.2", + "@esbuild/freebsd-arm64": "0.20.2", + "@esbuild/freebsd-x64": "0.20.2", + "@esbuild/linux-arm": "0.20.2", + "@esbuild/linux-arm64": "0.20.2", + "@esbuild/linux-ia32": "0.20.2", + "@esbuild/linux-loong64": "0.20.2", + "@esbuild/linux-mips64el": "0.20.2", + "@esbuild/linux-ppc64": "0.20.2", + "@esbuild/linux-riscv64": "0.20.2", + "@esbuild/linux-s390x": "0.20.2", + "@esbuild/linux-x64": "0.20.2", + "@esbuild/netbsd-x64": "0.20.2", + "@esbuild/openbsd-x64": "0.20.2", + "@esbuild/sunos-x64": "0.20.2", + "@esbuild/win32-arm64": "0.20.2", + "@esbuild/win32-ia32": "0.20.2", + "@esbuild/win32-x64": "0.20.2" + } + }, + "node_modules/escalade": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz", + "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/fast-glob": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz", + "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==", + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fastq": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz", + "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/foreground-child": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-3.1.1.tgz", + "integrity": "sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==", + "dependencies": { + "cross-spawn": "^7.0.0", + "signal-exit": "^4.0.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/glob": { + "version": "10.3.12", + "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.12.tgz", + "integrity": "sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==", + "dependencies": { + "foreground-child": "^3.1.0", + "jackspeak": "^2.3.6", + "minimatch": "^9.0.1", + "minipass": "^7.0.4", + "path-scurry": "^1.10.2" + }, + "bin": { + "glob": "dist/esm/bin.mjs" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "engines": { + "node": ">=4" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==" + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-core-module": { + "version": "2.13.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz", + "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==", + "dependencies": { + "hasown": "^2.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/jackspeak": { + "version": "2.3.6", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", + "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "dependencies": { + "@isaacs/cliui": "^8.0.2" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + }, + "optionalDependencies": { + "@pkgjs/parseargs": "^0.11.0" + } + }, + "node_modules/jiti": { + "version": "1.21.0", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.0.tgz", + "integrity": "sha512-gFqAIbuKyyso/3G2qhiO2OM6shY6EPP/R0+mkDbyspxKazh8BXDC5FiFsUjlczgdNz/vfra0da2y+aHrusLG/Q==", + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" + }, + "node_modules/jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "bin": { + "jsesc": "bin/jsesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==" + }, + "node_modules/lru-cache": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz", + "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==", + "dependencies": { + "yallist": "^3.0.2" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "9.0.4", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", + "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/minipass": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.0.4.tgz", + "integrity": "sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==", + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.7", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", + "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/node-releases": { + "version": "2.0.14", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.14.tgz", + "integrity": "sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/path": { + "version": "0.12.7", + "resolved": "https://registry.npmjs.org/path/-/path-0.12.7.tgz", + "integrity": "sha512-aXXC6s+1w7otVF9UletFkFcDsJeO7lSZBPUQhtb5O0xJe8LtYhj/GxldoL09bBj9+ZmE2hNoHqQSFMN5fikh4Q==", + "dependencies": { + "process": "^0.11.1", + "util": "^0.10.3" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==" + }, + "node_modules/path-scurry": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.10.2.tgz", + "integrity": "sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==", + "dependencies": { + "lru-cache": "^10.2.0", + "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/path-scurry/node_modules/lru-cache": { + "version": "10.2.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.0.tgz", + "integrity": "sha512-2bIM8x+VAf6JT4bKAljS1qUWgMsqZRPGJS6FSahIMPVvctcNhyVp7AJu7quxOW9jwkryBReKZY5tY5JYv2n/7Q==", + "engines": { + "node": "14 || >=16.14" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/postcss": { + "version": "8.4.38", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.38.tgz", + "integrity": "sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.7", + "picocolors": "^1.0.0", + "source-map-js": "^1.2.0" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.2.tgz", + "integrity": "sha512-bSVhyJGL00wMVoPUzAVAnbEoWyqRxkjv64tUl427SKnPrENtq6hJwUojroMz2VB+Q1edmi4IfrAPpami5VVgMQ==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "lilconfig": "^3.0.0", + "yaml": "^2.3.4" + }, + "engines": { + "node": ">= 14" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-load-config/node_modules/lilconfig": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.1.tgz", + "integrity": "sha512-O18pf7nyvHTckunPWCV1XUNXU1piu01y2b7ATJ0ppkUkk8ocqVWBrYjJBCwHDjD/ZWcfyrA0P4gKhzWGi5EINQ==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antonk52" + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.16", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.16.tgz", + "integrity": "sha512-A0RVJrX+IUkVZbW3ClroRWurercFhieevHB38sr2+l9eUClMqome3LmEmnhlNy+5Mr2EYN6B2Kaw9wYdd+VHiw==", + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==" + }, + "node_modules/process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/react-refresh": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.14.0.tgz", + "integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/resolve": { + "version": "1.22.8", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz", + "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==", + "dependencies": { + "is-core-module": "^2.13.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rollup": { + "version": "4.18.0", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.18.0.tgz", + "integrity": "sha512-QmJz14PX3rzbJCN1SG4Xe/bAAX2a6NpCP8ab2vfu2GiUr8AQcr2nCV/oEO3yneFarB67zk8ShlIyWb2LGTb3Sg==", + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.5" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.18.0", + "@rollup/rollup-android-arm64": "4.18.0", + "@rollup/rollup-darwin-arm64": "4.18.0", + "@rollup/rollup-darwin-x64": "4.18.0", + "@rollup/rollup-linux-arm-gnueabihf": "4.18.0", + "@rollup/rollup-linux-arm-musleabihf": "4.18.0", + "@rollup/rollup-linux-arm64-gnu": "4.18.0", + "@rollup/rollup-linux-arm64-musl": "4.18.0", + "@rollup/rollup-linux-powerpc64le-gnu": "4.18.0", + "@rollup/rollup-linux-riscv64-gnu": "4.18.0", + "@rollup/rollup-linux-s390x-gnu": "4.18.0", + "@rollup/rollup-linux-x64-gnu": "4.18.0", + "@rollup/rollup-linux-x64-musl": "4.18.0", + "@rollup/rollup-win32-arm64-msvc": "4.18.0", + "@rollup/rollup-win32-ia32-msvc": "4.18.0", + "@rollup/rollup-win32-x64-msvc": "4.18.0", + "fsevents": "~2.3.2" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", + "integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", + "integrity": "sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==", + "dependencies": { + "eastasianwidth": "^0.2.0", + "emoji-regex": "^9.2.2", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/string-width-cjs": { + "name": "string-width", + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/string-width-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/string-width-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", + "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", + "dependencies": { + "ansi-regex": "^6.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/strip-ansi?sponsor=1" + } + }, + "node_modules/strip-ansi-cjs": { + "name": "strip-ansi", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase": { + "version": "3.35.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.35.0.tgz", + "integrity": "sha512-8EbVDiu9iN/nESwxeSxDKe0dunta1GOlHufmSSXxMD2z2/tMZpDMpvXQGsc+ajGo8y2uYUmixaSRUc/QPoQ0GA==", + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "^10.3.10", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.4.3.tgz", + "integrity": "sha512-U7sxQk/n397Bmx4JHbJx/iSOOv5G+II3f1kpLpY2QeUv5DcPdcTsYLlusZfq1NthHS1c1cZoyFmmkex1rzke0A==", + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.3.0", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.21.0", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==", + "engines": { + "node": ">=4" + } + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==" + }, + "node_modules/update-browserslist-db": { + "version": "1.0.13", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.13.tgz", + "integrity": "sha512-xebP81SNcPuNpPP3uzeW1NYXxI3rxyJzF3pD6sH4jE7o/IX+WtSpwnVU+qIsDPyk0d3hmFQ7mjqc6AtV604hbg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/util": { + "version": "0.10.4", + "resolved": "https://registry.npmjs.org/util/-/util-0.10.4.tgz", + "integrity": "sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==", + "dependencies": { + "inherits": "2.0.3" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/vite": { + "version": "5.2.11", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.2.11.tgz", + "integrity": "sha512-HndV31LWW05i1BLPMUCE1B9E9GFbOu1MbenhS58FuK6owSO5qHm7GiCotrNY1YE5rMeQSFBGmT5ZaLEjFizgiQ==", + "license": "MIT", + "dependencies": { + "esbuild": "^0.20.1", + "postcss": "^8.4.38", + "rollup": "^4.13.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrap-ansi": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz", + "integrity": "sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==", + "dependencies": { + "ansi-styles": "^6.1.0", + "string-width": "^5.0.1", + "strip-ansi": "^7.0.1" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs": { + "name": "wrap-ansi", + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/wrap-ansi-cjs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, + "node_modules/wrap-ansi-cjs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi-cjs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/wrap-ansi/node_modules/ansi-styles": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", + "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==" + }, + "node_modules/yaml": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.4.1.tgz", + "integrity": "sha512-pIXzoImaqmfOrL7teGUBt/T7ZDnyeGBWyXQBvOVhLkWLN37GXv8NMLK406UY6dS51JfcQHsmcW5cJ441bHg6Lg==", + "bin": { + "yaml": "bin.mjs" + }, + "engines": { + "node": ">= 14" + } + } + } +} diff --git a/package.json b/package.json new file mode 100755 index 0000000..c2d2054 --- /dev/null +++ b/package.json @@ -0,0 +1,16 @@ +{ + "name": "django-vite-starter", + "version": "1.0.0", + "scripts": { + "dev": "vite --host=0.0.0.0", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@vitejs/plugin-react": "^4.2.1", + "alpinejs": "^3.13.5", + "path": "^0.12.7", + "tailwindcss": "^3.4.3", + "vite": "^5.1.0" + } +} diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..7cbc97a --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,61 @@ +[tool.poetry] +name = "django-blueprint" +version = "0.1.0" +description = "" +authors = ["A'zamov Samandar "] +readme = "README.md" + + +[tool.black] +line-length = 79 + +[tool.isort] +profile = "black" +line_length = 79 + + +[tool.poetry.dependencies] +python = "^3.11" +cookiecutter = "2.5.0" +crispy-tailwind = "1.0.1" +Django = "5.0.8" +django-ckeditor = "6.7.1" +django-ckeditor-5 = "0.2.11" +django-cors-headers = "4.3.1" +django-cron = "0.6.0" +django-debug-toolbar = "4.3.0" +django-environ = "0.11.2" +django-extensions = "3.2.3" +django-filter = "23.5" +django-import-export = "3.3.7" +django-jazzmin = "2.6.0" +django-modeltranslation = "0.18.11" +django-polymorphic = "3.1.0" +django-redis = "5.4.0" +django-rosetta = "0.10.0" +django-select2 = "8.1.2" +django-unicorn = "0.58.1" +djangorestframework-simplejwt = "5.3.1" +drf-yasg = "1.21.7" +faker = "23.1.0" +flower = "2.0.1" +ipython = "8.21.0" +markuppy = "1.14" +ngrok = "1.2.0" +odfpy = "1.4.1" +openpyxl = "3.1.2" +pip-chill = "1.0.3" +psycopg2-binary = "2.9.9" +pytest = "8.0.0" +shellingham = "1.5.4" +tqdm = "4.66.2" +twine = "4.0.2" +typer = "0.12.0" +xlrd = "2.0.1" +xlwt = "1.3.0" +firebase-admin = "6.5.0" + + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/pytest.ini b/pytest.ini new file mode 100755 index 0000000..461f069 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,3 @@ +[pytest] +DJANGO_SETTINGS_MODULE = config.settings.local +addopts = --reuse-db -p no:warnings --verbose \ No newline at end of file diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..37a3432 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,40 @@ +cookiecutter==2.5.0 +crispy-tailwind==1.0.1 +Django==5.0.8 +django-ckeditor==6.7.1 +django-ckeditor-5==0.2.11 +django-cors-headers==4.3.1 +django-cron==0.6.0 +django-debug-toolbar==4.3.0 +django-environ==0.11.2 +django-extensions==3.2.3 +django-filter==23.5 +django-import-export==3.3.7 +django-jazzmin==2.6.0 +django-modeltranslation==0.18.11 +django-polymorphic==3.1.0 +django-redis==5.4.0 +django-rosetta==0.10.0 +django-select2==8.1.2 +django-unicorn==0.58.1 +djangorestframework-simplejwt==5.3.1 +drf-yasg==1.21.7 +faker==23.1.0 +flower==2.0.1 +ipython==8.21.0 +markuppy==1.14 +ngrok==1.2.0 +odfpy==1.4.1 +openpyxl==3.1.2 +pip-chill==1.0.3 +psycopg2-binary +pytest==8.0.0 +shellingham==1.5.4 +tqdm==4.66.2 +twine==4.0.2 +typer==0.12.0 +xlrd==2.0.1 +xlwt==1.3.0 +firebase-admin==6.5.0 +gunicorn +setuptools diff --git a/requirements/common.txt b/requirements/common.txt new file mode 100755 index 0000000..e24409f --- /dev/null +++ b/requirements/common.txt @@ -0,0 +1,36 @@ +cookiecutter==2.5.0 +crispy-tailwind==1.0.1 +django-ckeditor==6.7.1 +django-ckeditor-5==0.2.11 +django-cors-headers==4.3.1 +django-cron==0.6.0 +django-debug-toolbar==4.3.0 +django-environ==0.11.2 +django-extensions==3.2.3 +django-filter==23.5 +django-import-export==3.3.7 +django-jazzmin==2.6.0 +django-modeltranslation==0.18.11 +django-polymorphic==3.1.0 +django-redis==5.4.0 +django-rosetta==0.10.0 +django-select2==8.1.2 +django-unicorn==0.58.1 +djangorestframework-simplejwt==5.3.1 +drf-yasg==1.21.7 +faker==23.1.0 +flower==2.0.1 +ipython==8.21.0 +markuppy==1.14 +ngrok==1.2.0 +odfpy==1.4.1 +openpyxl==3.1.2 +pip-chill==1.0.3 +psycopg2-binary==2.9.9 +pytest==8.0.0 +shellingham==1.5.4 +tqdm==4.66.2 +twine==4.0.2 +typer==0.12.0 +xlrd==2.0.1 +xlwt==1.3.0 diff --git a/requirements/local.txt b/requirements/local.txt new file mode 100755 index 0000000..b3d45ac --- /dev/null +++ b/requirements/local.txt @@ -0,0 +1,5 @@ +-r common.txt +django-extensions==3.2.3 +pytest==8.0.0 +ipython==8.21.0 +flake8 diff --git a/requirements/production.txt b/requirements/production.txt new file mode 100755 index 0000000..55c2442 --- /dev/null +++ b/requirements/production.txt @@ -0,0 +1,2 @@ +-r common.txt +gunicorn==21.2.0 \ No newline at end of file diff --git a/resources/.gitignore b/resources/.gitignore new file mode 100755 index 0000000..ef1d437 --- /dev/null +++ b/resources/.gitignore @@ -0,0 +1 @@ +staticfiles \ No newline at end of file diff --git a/resources/firebase-config.js b/resources/firebase-config.js new file mode 100644 index 0000000..c7e4a8a --- /dev/null +++ b/resources/firebase-config.js @@ -0,0 +1,15 @@ +// firebase-config.js +// Your web app's Firebase configuration +const firebaseConfig = { + apiKey: "AIzaSyDl76c_ga0BnWTmHyaE7Gd6vuA7Ix8V5xA", + authDomain: "golden-eggs-6a34b.firebaseapp.com", + projectId: "golden-eggs-6a34b", + storageBucket: "golden-eggs-6a34b.appspot.com", + messagingSenderId: "788587369063", + appId: "1:788587369063:web:3cfc9030975d2142193ac2", + measurementId: "G-E703RMJ2M0" +}; + +// Initialize Firebase +firebase.initializeApp(firebaseConfig); +const messaging = firebase.messaging(); diff --git a/resources/firebase.html b/resources/firebase.html new file mode 100644 index 0000000..9f06f67 --- /dev/null +++ b/resources/firebase.html @@ -0,0 +1,15 @@ + + + + + + Order Notification + + + + + + +

Order Notifications

+ + \ No newline at end of file diff --git a/resources/firebase.js b/resources/firebase.js new file mode 100644 index 0000000..a25d24d --- /dev/null +++ b/resources/firebase.js @@ -0,0 +1,31 @@ +// Request permission to receive notifications +messaging.requestPermission() + .then(() => messaging.getToken()) + .then(token => { + console.log('===================='); + console.log('FCM Token:', token); + console.log('===================='); + // Send the token to the server to subscribe to notifications + fetch('/save-fcm-token', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ token: token }) + }); + }) + .catch(error => { + console.error('Error getting permission or token', error); + }); + +// Handle incoming messages +messaging.onMessage(payload => { + console.log('Message received. ', payload); + // Customize notification here + const notificationTitle = payload.notification.title; + const notificationOptions = { + body: payload.notification.body, + }; + + new Notification(notificationTitle, notificationOptions); +}); \ No newline at end of file diff --git a/resources/firebase_golden.json b/resources/firebase_golden.json new file mode 100644 index 0000000..0f39855 --- /dev/null +++ b/resources/firebase_golden.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "golden-eggs-a578a", + "private_key_id": "2d42ab5a6df768fd45008df377e39c70c803ae5e", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCN4S2ipr/QMRsF\ndn1HwaghkrPbu1b2BlxLBnnovQdWvp2gAkmB/l9TF9YF6W4oUSkBQJJeY8ZZH2C0\nLRYlq7JhuKVLTKu4ZbKHZPtq4IBKhgcMHVoOkCqWwDqVyn6MJQAMMcHhX27LdHb9\nrEbc348hKVVjorzbHZeZMtNglw4Vb4LSyw+RDAa232u+Jr3AszYbJVaHr2hFnEXR\nWzXS/UvTcYQEtuQ5TrnwNKm+Ko8yYL9kYcYl9222syzmsaQ3Q4P2LUF0YSbpUkPg\n//EG6/zXNoTZ7TMViDedupR8tNohRgQzcdzpgIxqVEq4YCClkNEf9RMbg8oFsJ+h\nW/QHJqCxAgMBAAECggEALA6zvCFpyP2mtXCRALpbOmmy3lXzcByThlyxeJtSbxZs\nHib3qp1R/pyVQmq1YWGqiRADI7p5+mzq+6hQi0wg7YWIFrTtUiqKYM7z38FtNTcf\ncxyePY15HCz/REqIN3654A+HdDPNVPKKjMV1j9sAODz8nh8H5EbxL4p/5yuhHPh4\nW2tU73UNoTR+7MwH3nVbGJm2+euXBQNpBaasDlFLkBhOeQI/W/9CopJTD7rDzfum\nGUB6xHkroPuqx1gqkm8nAM4M9wVLVNTxmXMnc+EZ4w+RgCk2D21aqzC+fmvb01yX\ncHIyOJv4P9SdHf5M1uh+UfbpBnCcdp2xnyvZ3ojP3QKBgQC/LB298kVzT0/cdgCX\nwDBC1jcpvQ51C1KiAjj1eCuY7+RcM5uIn58CSfW0txWnG2TRy0Zd31g9gBiSBvvx\nnAx1JBPCOG+EGmV4TpxH7pIRW4LQOspHtSDh0/676Fe4JI0wgc4aiVZb8l1jd8/i\nITzafpP2gwtc9Q6zIYXW7EHnVwKBgQC9/efFI1K+v9PjciBg96C0rPAXo0lMe3r8\n3owVjDnYAVptzcBxbBdpopX8Il5DEOPZtihmxFboIeHYHAPqmfxbkTCohK+0KoLM\nEe7p9ActNSobWNjmg1GCSVhMRMh7GIEXQan3DB7bcrVD+orMrBY1hVMi5LSErF/B\nCjB+tw5bNwKBgGvTEiNkVk+nWD/cPpFY0N+huxNLkq/pZv4BiubIlK55/68RXJQ+\nk4zS8kavTQMnrEG4CURBCKAHc9RJJCkt/sjDRHGzKxKzYK/uhq77nF814h5YSmcP\nfjBhRbfuYCt4UjodvIWzGIZbuNi1ZwcAADasVgAgAXS4s0XNjpOeRb2ZAoGAI9Qp\nDBVAOnakd8UC9CZeqRPf5yVE4mZ66Arz8fBSzDhdHE3r17fhpmshy72TdyRNQ7ZF\nYjPui7IwC/gkiO5nF8GGopD7LYfCyMcs/fOmEtFu0l9r7/Sdv2sjeqaSBBy343Hf\nopYnj4zPQDWUTUq6mXc+GVt163syR44rXQoaa/sCgYBrYl83Nikiv9LQThB7Y5jn\nKPzAL43xYM5LIUcucdQmDly1zuQ//KNBzsmKGWtj/rTMxqg+L9YVpPWhI7795yQ1\n8ev6Ia+uVv8sPfrPivOR8cYFOr07YMGgSf0MoH+l46/ILm/KDvrQlPgE8jjmCjj3\nKCTK6XwyXAJLeRophzMcJQ==\n-----END PRIVATE KEY-----\n", + "client_email": "firebase-adminsdk-dgzy2@golden-eggs-a578a.iam.gserviceaccount.com", + "client_id": "101092326620621868178", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-dgzy2%40golden-eggs-a578a.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +} \ No newline at end of file diff --git a/resources/golden_eggs.png b/resources/golden_eggs.png new file mode 100644 index 0000000..3349f65 Binary files /dev/null and b/resources/golden_eggs.png differ diff --git a/resources/static/css/app.css b/resources/static/css/app.css new file mode 100755 index 0000000..e69de29 diff --git a/resources/static/css/input.css b/resources/static/css/input.css new file mode 100755 index 0000000..b5c61c9 --- /dev/null +++ b/resources/static/css/input.css @@ -0,0 +1,3 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; diff --git a/resources/static/css/jazzmin.css b/resources/static/css/jazzmin.css new file mode 100644 index 0000000..1805a3a --- /dev/null +++ b/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/resources/static/css/output.css b/resources/static/css/output.css new file mode 100755 index 0000000..09fd72a --- /dev/null +++ b/resources/static/css/output.css @@ -0,0 +1,666 @@ +/* +! tailwindcss v3.4.3 | 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] { + display: none; +} + +*, ::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: ; +} + +.static { + position: static; +} + +.m-2 { + margin: 0.5rem; +} + +.mx-4 { + margin-left: 1rem; + margin-right: 1rem; +} + +.mb-3 { + margin-bottom: 0.75rem; +} + +.mt-4 { + margin-top: 1rem; +} + +.mt-5 { + margin-top: 1.25rem; +} + +.flex { + display: flex; +} + +.h-\[100vh\] { + height: 100vh; +} + +.w-\[100vw\] { + width: 100vw; +} + +.items-center { + align-items: center; +} + +.justify-center { + justify-content: center; +} + +.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-gray-200 { + --tw-bg-opacity: 1; + background-color: rgb(229 231 235 / var(--tw-bg-opacity)); +} + +.p-4 { + padding: 1rem; +} + +.px-\[40px\] { + padding-left: 40px; + padding-right: 40px; +} + +.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-bold { + font-weight: 700; +} + +.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)); +} diff --git a/resources/static/images/logo.png b/resources/static/images/logo.png new file mode 100755 index 0000000..57ac67e Binary files /dev/null and b/resources/static/images/logo.png differ diff --git a/resources/static/js/alpine.js b/resources/static/js/alpine.js new file mode 100755 index 0000000..8ec2a90 --- /dev/null +++ b/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/resources/static/js/app.js b/resources/static/js/app.js new file mode 100755 index 0000000..0b7e57d --- /dev/null +++ b/resources/static/js/app.js @@ -0,0 +1 @@ +import "./alpine" diff --git a/resources/static/js/counter.js b/resources/static/js/counter.js new file mode 100755 index 0000000..293649a --- /dev/null +++ b/resources/static/js/counter.js @@ -0,0 +1,3 @@ +export default () => ({ + count: 2309 +}) \ No newline at end of file diff --git a/resources/static/js/vite-refresh.js b/resources/static/js/vite-refresh.js new file mode 100755 index 0000000..101cf65 --- /dev/null +++ b/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/resources/static/vite/assets/appCss-w40geAFS.js b/resources/static/vite/assets/appCss-w40geAFS.js new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/resources/static/vite/assets/appCss-w40geAFS.js @@ -0,0 +1 @@ + diff --git a/resources/static/vite/assets/appJs-YH6iAcjX.js b/resources/static/vite/assets/appJs-YH6iAcjX.js new file mode 100644 index 0000000..b8cd29f --- /dev/null +++ b/resources/static/vite/assets/appJs-YH6iAcjX.js @@ -0,0 +1,5 @@ +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 `