diff --git a/.gitignore b/.gitignore index 2eea525..9a143c2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,212 @@ -.env \ No newline at end of file +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[codz] +*$py.class +*__pycache + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py.cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.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 +#poetry.toml + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +# pdm recommends including project-wide configuration in pdm.toml, but excluding .pdm-python. +# https://pdm-project.org/en/latest/usage/project/#working-with-version-control +#pdm.lock +#pdm.toml +.pdm-python +.pdm-build/ + +# pixi +# Similar to Pipfile.lock, it is generally recommended to include pixi.lock in version control. +#pixi.lock +# Pixi creates a virtual environment in the .pixi directory, just like venv module creates one +# in the .venv directory. It is recommended not to include this directory in version control. +.pixi + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.envrc +.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 +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ + +# Abstra +# Abstra is an AI-powered process automation framework. +# Ignore directories containing user credentials, local state, and settings. +# Learn more at https://abstra.io/docs +.abstra/ + +# Visual Studio Code +# Visual Studio Code specific template is maintained in a separate VisualStudioCode.gitignore +# that can be found at https://github.com/github/gitignore/blob/main/Global/VisualStudioCode.gitignore +# and can be added to the global gitignore or merged into this file. However, if you prefer, +# you could uncomment the following to ignore the entire vscode folder +# .vscode/ + +# Ruff stuff: +.ruff_cache/ + +# PyPI configuration file +.pypirc + +# Marimo +marimo/_static/ +marimo/_lsp/ +__marimo__/ + +# Streamlit +.streamlit/secrets.toml + +.vscode + + + +.env +.idea/ +.venv diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..5245015 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,37 @@ +FROM python:3.13-alpine3.22 as base + +ENV VENV_PATH=/app/.venv +ENV PATH="$VENV_PATH/bin:$PATH" + +WORKDIR /app + + +FROM base as builder + +ENV POETRY_VERSION=2.0.1 +ENV POETRY_VIRTUALENVS_IN_PROJECT=1 +ENV POETRY_NO_INTERACTION=1 +ENV POETRY_VIRTUALENVS_CREATE=1 + +RUN pip install poetry=="$POETRY_VERSION" + +COPY pyproject.toml poetry.lock ./ + +RUN poetry install --no-root + + +FROM base as runtime + +ENV PYTHONDONTWRITEBYTECODE=1 +ENV PYTHONUNBUFFERED=1 +ENV PYTHONPATH=/app + +RUN mkdir -p /tmp/metrics + +ENV PROMETHEUS_MULTIPROC_DIR=/tmp/metrics + +COPY --from=builder "$VENV_PATH" "$VENV_PATH" + +EXPOSE 80 + +COPY src ./src \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e733e4c --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,18 @@ +version: "3.9" + +services: + ultrazord-api-dev: + build: + dockerfile: Dockerfile + target: runtime + entrypoint: > + uvicorn src.main:app + --reload + --port 80 + --host 0.0.0.0 + --forwarded-allow-ips='*' + --proxy-headers + + env_file: .env + ports: + - "31006:80" \ No newline at end of file diff --git a/poetry.lock b/poetry.lock index 36df6b3..af5dd7c 100644 --- a/poetry.lock +++ b/poetry.lock @@ -124,6 +124,25 @@ files = [ {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] +[[package]] +name = "ecdsa" +version = "0.19.1" +description = "ECDSA cryptographic signature library (pure python)" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,>=2.6" +groups = ["main"] +files = [ + {file = "ecdsa-0.19.1-py2.py3-none-any.whl", hash = "sha256:30638e27cf77b7e15c4c4cc1973720149e1033827cfd00661ca5c8cc0cdb24c3"}, + {file = "ecdsa-0.19.1.tar.gz", hash = "sha256:478cba7b62555866fcb3bb3fe985e06decbdb68ef55713c4e5ab98c57d508e61"}, +] + +[package.dependencies] +six = ">=1.9.0" + +[package.extras] +gmpy = ["gmpy"] +gmpy2 = ["gmpy2"] + [[package]] name = "fastapi" version = "0.116.1" @@ -242,6 +261,18 @@ files = [ [package.extras] all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] +[[package]] +name = "itsdangerous" +version = "2.2.0" +description = "Safely pass data to untrusted environments and back." +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "itsdangerous-2.2.0-py3-none-any.whl", hash = "sha256:c6242fc49e35958c8b15141343aa660db5fc54d4f13a1db01a3f5891b98700ef"}, + {file = "itsdangerous-2.2.0.tar.gz", hash = "sha256:e0050c0b7da1eea53ffaf149c0cfbb5c6e2e2b69c4bef22c81fa6eb73e5f6173"}, +] + [[package]] name = "jinja2" version = "3.1.6" @@ -331,6 +362,18 @@ files = [ {file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, ] +[[package]] +name = "pyasn1" +version = "0.6.1" +description = "Pure-Python implementation of ASN.1 types and DER/BER/CER codecs (X.208)" +optional = false +python-versions = ">=3.8" +groups = ["main"] +files = [ + {file = "pyasn1-0.6.1-py3-none-any.whl", hash = "sha256:0d632f46f2ba09143da3a8afe9e33fb6f92fa2320ab7e886e2d0f7672af84629"}, + {file = "pyasn1-0.6.1.tar.gz", hash = "sha256:6f580d2bdd84365380830acf45550f2511469f673cb4a5ae3857a3170128b034"}, +] + [[package]] name = "pydantic" version = "2.11.7" @@ -480,6 +523,29 @@ files = [ [package.extras] cli = ["click (>=5.0)"] +[[package]] +name = "python-jose" +version = "3.5.0" +description = "JOSE implementation in Python" +optional = false +python-versions = ">=3.9" +groups = ["main"] +files = [ + {file = "python_jose-3.5.0-py2.py3-none-any.whl", hash = "sha256:abd1202f23d34dfad2c3d28cb8617b90acf34132c7afd60abd0b0b7d3cb55771"}, + {file = "python_jose-3.5.0.tar.gz", hash = "sha256:fb4eaa44dbeb1c26dcc69e4bd7ec54a1cb8dd64d3b4d81ef08d90ff453f2b01b"}, +] + +[package.dependencies] +ecdsa = "!=0.15" +pyasn1 = ">=0.5.0" +rsa = ">=4.0,<4.1.1 || >4.1.1,<4.4 || >4.4,<5.0" + +[package.extras] +cryptography = ["cryptography (>=3.4.0)"] +pycrypto = ["pycrypto (>=2.6.0,<2.7.0)"] +pycryptodome = ["pycryptodome (>=3.3.1,<4.0.0)"] +test = ["pytest", "pytest-cov"] + [[package]] name = "python-multipart" version = "0.0.20" @@ -492,6 +558,32 @@ files = [ {file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, ] +[[package]] +name = "rsa" +version = "4.2" +description = "Pure-Python RSA implementation" +optional = false +python-versions = "*" +groups = ["main"] +files = [ + {file = "rsa-4.2.tar.gz", hash = "sha256:aaefa4b84752e3e99bd8333a2e1e3e7a7da64614042bd66f775573424370108a"}, +] + +[package.dependencies] +pyasn1 = ">=0.1.3" + +[[package]] +name = "six" +version = "1.17.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +groups = ["main"] +files = [ + {file = "six-1.17.0-py2.py3-none-any.whl", hash = "sha256:4721f391ed90541fddacab5acf947aa0d3dc7d27b2e1e8eda2be8970586c3274"}, + {file = "six-1.17.0.tar.gz", hash = "sha256:ff70335d468e7eb6ec65b95b99d3a2836546063f63acc5171de367e834932a81"}, +] + [[package]] name = "sniffio" version = "1.3.1" @@ -708,4 +800,4 @@ email = ["email-validator"] [metadata] lock-version = "2.1" python-versions = ">=3.11" -content-hash = "21a35caa29ba269d21d8b22f161f5828f99ab497c5d687f013eda16461478541" +content-hash = "00e000faa33bdaefef12840d09b78569135abf3a3a37996816ac4866bce65714" diff --git a/pyproject.toml b/pyproject.toml index b399a8c..e120e24 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -13,7 +13,9 @@ dependencies = [ "fastapi (>=0.116.1,<0.117.0)", "uvicorn (>=0.35.0,<0.36.0)", "python-dotenv (>=1.1.1,<2.0.0)", - "asyncpg (>=0.30.0,<0.31.0)" + "asyncpg (>=0.30.0,<0.31.0)", + "python-jose (>=3.5.0,<4.0.0)", + "itsdangerous (>=2.2.0,<3.0.0)" ] diff --git a/src/__pycache__/main.cpython-311.pyc b/src/__pycache__/main.cpython-311.pyc index c262534..1faea13 100644 Binary files a/src/__pycache__/main.cpython-311.pyc and b/src/__pycache__/main.cpython-311.pyc differ diff --git a/src/admin/__pycache__/account.cpython-311.pyc b/src/admin/__pycache__/account.cpython-311.pyc index 4e4bfe9..147bcb5 100644 Binary files a/src/admin/__pycache__/account.cpython-311.pyc and b/src/admin/__pycache__/account.cpython-311.pyc differ diff --git a/src/admin/__pycache__/payment.cpython-311.pyc b/src/admin/__pycache__/payment.cpython-311.pyc index cf456db..c46eeb5 100644 Binary files a/src/admin/__pycache__/payment.cpython-311.pyc and b/src/admin/__pycache__/payment.cpython-311.pyc differ diff --git a/src/admin/__pycache__/widget.cpython-311.pyc b/src/admin/__pycache__/widget.cpython-311.pyc index dc11e97..5eb1cac 100644 Binary files a/src/admin/__pycache__/widget.cpython-311.pyc and b/src/admin/__pycache__/widget.cpython-311.pyc differ diff --git a/src/admin/account.py b/src/admin/account.py index 8ea29f9..4faaa0a 100644 --- a/src/admin/account.py +++ b/src/admin/account.py @@ -1,10 +1,11 @@ from sqladmin import ModelView +from src.admin.base_admin import BaseAdmin from src.database.engines import accounts_engine from src.database.streamers import Account -class AccountAdmin(ModelView, model=Account): +class AccountAdmin(BaseAdmin, model=Account): engine = accounts_engine name = "Account" diff --git a/src/admin/auth.py b/src/admin/auth.py index 41f86be..bb0c218 100644 --- a/src/admin/auth.py +++ b/src/admin/auth.py @@ -1,45 +1,115 @@ -from fastapi import Request -from jose import JWTError +import os +from datetime import datetime, timedelta +from typing import Optional + +from dotenv import load_dotenv +from fastapi import Request, HTTPException +from jose import jwt, JWTError +from sqlalchemy import select from sqladmin.authentication import AuthenticationBackend +from starlette import status from starlette.responses import RedirectResponse -from src.api_v1.auth.jwt import create_access_token, parse_jwt_token -from src.api_v1.auth.security import check_password -from src.api_v1.auth import crud as auth_crud -from src.api_v1.users import crud as users_crud -from src.exceptions import NotAuthenticated, PermissionDenied -from src.database import db_helper -from src.settings.base import settings +from src.database.engines import accounts_session_maker +from src.database.streamers import Account + +# --- Конфигурация --- +# Лучше хранить все настройки в одном месте + +load_dotenv() + +# Ключ для подписи JWT-токенов +SECRET_KEY = os.getenv("SECRET_KEY", "your_default_secret_key") +# Алгоритм подписи +ALGORITHM = "HS256" + +ADMIN_EMAIL = "tihon414@gmail.com" +# Время жизни токена доступа +ACCESS_TOKEN_EXPIRE_MINUTES = 60 * 24 # 24 часа -class AdminAuth(AuthenticationBackend): - async def login(self, request: Request) -> bool: - form = await request.form() - username, password = form['username'], form['password'] - async with db_helper.get_async_session_not_closed() as session: - user_model = await auth_crud.get_user_by_login( - login=username, - session=session, +def parse_jwt_token( + token: str, +) -> dict: + try: + payload = jwt.decode( + token=token, + key=SECRET_KEY, + algorithms=[ALGORITHM] + ) + if payload["email"] != ADMIN_EMAIL: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", ) - - if not check_password( - plain_password=password, - hashed_password=user_model.hashed_password, # type:ignore - ): - raise NotAuthenticated() - - if user_model.role != 'Админ': - raise PermissionDenied() - - jwt_token = create_access_token( - user=user_model + except JWTError: + raise HTTPException( + status_code=status.HTTP_401_UNAUTHORIZED, + detail="Could not validate credentials", ) - request.session.update({'token': jwt_token.access_token}) + return payload - return True + +def create_access_token(user: Account) -> str: + """ + Создает JWT-токен для пользователя. + + :param user: Объект пользователя (модель Account). + :return: Строка с JWT-токеном. + """ + expires_delta = timedelta(minutes=ACCESS_TOKEN_EXPIRE_MINUTES) + expire = datetime.utcnow() + expires_delta + + jwt_data = { + "sub": user.login, # "sub" (subject) - стандартное поле для идентификатора пользователя + "email": user.email, + "id": user.id, + "exp": expire, # "exp" (expiration time) - время истечения срока действия токена + } + + token = jwt.encode(claims=jwt_data, key=SECRET_KEY, algorithm=ALGORITHM) + return token + + +# --- Класс аутентификации для SQLAdmin --- + +class AdminAuth(AuthenticationBackend): + """ + Кастомный бэкенд аутентификации для SQLAdmin. + Использует JWT-токены, хранящиеся в сессии. + """ + + async def login(self, request: Request) -> bool: + """ + Обрабатывает попытку входа администратора. + """ + form = await request.form() + username, password = form.get("username"), form.get("password") + + if not username or not password: + return False + + if username != ADMIN_EMAIL or password != os.getenv("ADMIN_PASS"): + return False + + async with accounts_session_maker() as session: + query = select(Account).where(Account.email == ADMIN_EMAIL) + result = await session.execute(query) + user: Optional[Account] = result.scalar_one_or_none() + + if user is None: + return False + + token = create_access_token(user=user) + request.session.update({"token": token}) + + return True async def logout(self, request: Request) -> bool: + """ + Обрабатывает выход администратора. + """ request.session.clear() return True @@ -63,4 +133,5 @@ class AdminAuth(AuthenticationBackend): return True -authentication_backend = AdminAuth(secret_key=settings.JWT.SECRET) \ No newline at end of file +# Создаем экземпляр бэкенда +authentication_backend = AdminAuth(secret_key=SECRET_KEY) \ No newline at end of file diff --git a/src/admin/base_admin.py b/src/admin/base_admin.py new file mode 100644 index 0000000..5376c32 --- /dev/null +++ b/src/admin/base_admin.py @@ -0,0 +1,6 @@ +from sqladmin import ModelView + + +class BaseAdmin(ModelView): + can_delete = False + page_size = 100 \ No newline at end of file diff --git a/src/admin/payment.py b/src/admin/payment.py index eefa571..f00896a 100644 --- a/src/admin/payment.py +++ b/src/admin/payment.py @@ -1,19 +1,25 @@ from sqladmin import ModelView +from src.admin.base_admin import BaseAdmin from src.database.payments import Payment, Deal, Balance, TinkoffWithdraw, TinkoffWithdrawMethod, StreamersCommission -class PaymentAdmin(ModelView, model=Payment): +class PaymentAdmin(BaseAdmin, model=Payment): + column_searchable_list = [ + Payment.order_id, + Payment.streamer_id, + Payment.deal_id, + Payment.balance_id, + ] category = "Payments DB" name = "Payment" name_plural = "Payments" icon = "fa-solid fa-money-bill" column_list = [Payment.order_id, Payment.streamer_id, Payment.amount, Payment.created_at] - column_searchable_list = [Payment.order_id, Payment.streamer_id] column_default_sort = ("created_at", True) -class DealAdmin(ModelView, model=Deal): +class DealAdmin(BaseAdmin, model=Deal): category = "Payments DB" name = "Deal" name_plural = "Deals" @@ -22,16 +28,18 @@ class DealAdmin(ModelView, model=Deal): column_searchable_list = [Deal.deal_id, Deal.streamer_id] -class BalanceAdmin(ModelView, model=Balance): +class BalanceAdmin(BaseAdmin, model=Balance): category = "Payments DB" name = "Balance Op" name_plural = "Balance Ops" icon = "fa-solid fa-scale-balanced" - column_list = [Balance.id, Balance.streamer_id, Balance.operation_type, Balance.balance_diff, Balance.created_at] + column_list = [Balance.balance_total, Balance.balance_diff, Balance.streamer_id, Balance.operation_type, Balance.created_at] column_default_sort = ("created_at", True) + column_sortable_list = [Balance.streamer_id, Balance.created_at, Balance.operation_type] + column_searchable_list = [Balance.streamer_id, Balance.operation_type] -class TinkoffWithdrawAdmin(ModelView, model=TinkoffWithdraw): +class TinkoffWithdrawAdmin(BaseAdmin, model=TinkoffWithdraw): category = "Payments DB" name = "Tinkoff Withdraw" name_plural = "Tinkoff Withdraws" @@ -42,7 +50,7 @@ class TinkoffWithdrawAdmin(ModelView, model=TinkoffWithdraw): column_default_sort = ("created_at", True) -class TinkoffWithdrawMethodAdmin(ModelView, model=TinkoffWithdrawMethod): +class TinkoffWithdrawMethodAdmin(BaseAdmin, model=TinkoffWithdrawMethod): category = "Payments DB" name = "Withdraw Method" name_plural = "Withdraw Methods" @@ -51,7 +59,7 @@ class TinkoffWithdrawMethodAdmin(ModelView, model=TinkoffWithdrawMethod): TinkoffWithdrawMethod.is_main] -class StreamersCommissionAdmin(ModelView, model=StreamersCommission): +class StreamersCommissionAdmin(BaseAdmin, model=StreamersCommission): category = "Payments DB" name = "Commission" name_plural = "Commissions" diff --git a/src/admin/widget.py b/src/admin/widget.py index 00a80bd..a712187 100644 --- a/src/admin/widget.py +++ b/src/admin/widget.py @@ -1,11 +1,12 @@ from sqladmin import ModelView +from src.admin.base_admin import BaseAdmin from src.database.engines import widgets_engine from src.database.widgets import Widget, File, Donat, Target, DonatePage, Moderation, Filter, FilterWord, VoiceSetting, \ Language, VoiceLanguage, StreamerWidgetPage, StreamerOnline -class FileAdmin(ModelView, model=File): +class FileAdmin(BaseAdmin, model=File): engine = widgets_engine category = "Widgets DB" name = "File" @@ -14,7 +15,7 @@ class FileAdmin(ModelView, model=File): column_list = [File.id, File.file_name, File.file_type, File.streamer_id, File.created_at] column_searchable_list = [File.file_name] -class WidgetAdmin(ModelView, model=Widget): +class WidgetAdmin(BaseAdmin, model=Widget): engine = widgets_engine category = "Widgets DB" name = "Widget" @@ -23,7 +24,7 @@ class WidgetAdmin(ModelView, model=Widget): column_list = [Widget.id, Widget.name, Widget.streamer_id, Widget.is_active, Widget.min_amount] column_searchable_list = [Widget.name] -class DonatAdmin(ModelView, model=Donat): +class DonatAdmin(BaseAdmin, model=Donat): engine = widgets_engine category = "Widgets DB" name = "Donat" @@ -33,7 +34,7 @@ class DonatAdmin(ModelView, model=Donat): column_searchable_list = [Donat.donat_user, Donat.text] column_default_sort = ("created_at", True) -class TargetAdmin(ModelView, model=Target): +class TargetAdmin(BaseAdmin, model=Target): engine = widgets_engine category = "Widgets DB" name = "Target" @@ -41,7 +42,7 @@ class TargetAdmin(ModelView, model=Target): icon = "fa-solid fa-bullseye" column_list = [Target.id, Target.text, Target.amount, Target.collected, Target.streamer_id] -class DonatePageAdmin(ModelView, model=DonatePage): +class DonatePageAdmin(BaseAdmin, model=DonatePage): engine = widgets_engine category = "Widgets DB" name = "Donate Page" @@ -50,7 +51,7 @@ class DonatePageAdmin(ModelView, model=DonatePage): column_list = [DonatePage.id, DonatePage.streamer_login, DonatePage.streamer_id] column_searchable_list = [DonatePage.streamer_login] -class ModerationAdmin(ModelView, model=Moderation): +class ModerationAdmin(BaseAdmin, model=Moderation): engine = widgets_engine category = "Widgets DB" name = "Moderation" @@ -58,7 +59,7 @@ class ModerationAdmin(ModelView, model=Moderation): icon = "fa-solid fa-user-shield" column_list = [c.name for c in Moderation.__table__.c] -class FilterAdmin(ModelView, model=Filter): +class FilterAdmin(BaseAdmin, model=Filter): engine = widgets_engine category = "Widgets DB" name = "Filter" @@ -66,7 +67,7 @@ class FilterAdmin(ModelView, model=Filter): icon = "fa-solid fa-filter" column_list = [c.name for c in Filter.__table__.c] -class FilterWordAdmin(ModelView, model=FilterWord): +class FilterWordAdmin(BaseAdmin, model=FilterWord): engine = widgets_engine category = "Widgets DB" name = "Filter Word" @@ -75,7 +76,7 @@ class FilterWordAdmin(ModelView, model=FilterWord): column_list = [FilterWord.id, FilterWord.word, FilterWord.donat_filter_id] column_searchable_list = [FilterWord.word] -class VoiceSettingAdmin(ModelView, model=VoiceSetting): +class VoiceSettingAdmin(BaseAdmin, model=VoiceSetting): engine = widgets_engine category = "Widgets DB" name = "Voice Setting" @@ -83,7 +84,7 @@ class VoiceSettingAdmin(ModelView, model=VoiceSetting): icon = "fa-solid fa-microphone-lines" column_list = [VoiceSetting.id, VoiceSetting.streamer_id, VoiceSetting.enable, VoiceSetting.min_price] -class LanguageAdmin(ModelView, model=Language): +class LanguageAdmin(BaseAdmin, model=Language): engine = widgets_engine category = "Widgets DB" name = "Language" @@ -92,7 +93,7 @@ class LanguageAdmin(ModelView, model=Language): column_list = [Language.id, Language.ru_name, Language.en_name, Language.iso_code] column_searchable_list = [Language.ru_name, Language.en_name, Language.iso_code] -class VoiceLanguageAdmin(ModelView, model=VoiceLanguage): +class VoiceLanguageAdmin(BaseAdmin, model=VoiceLanguage): engine = widgets_engine category = "Widgets DB" name = "Voice Language" @@ -100,7 +101,7 @@ class VoiceLanguageAdmin(ModelView, model=VoiceLanguage): icon = "fa-solid fa-link" column_list = [c.name for c in VoiceLanguage.__table__.c] -class StreamerWidgetPageAdmin(ModelView, model=StreamerWidgetPage): +class StreamerWidgetPageAdmin(BaseAdmin, model=StreamerWidgetPage): engine = widgets_engine category = "Widgets DB" name = "Streamer Widget Page" @@ -108,7 +109,7 @@ class StreamerWidgetPageAdmin(ModelView, model=StreamerWidgetPage): icon = "fa-solid fa-desktop" column_list = [c.name for c in StreamerWidgetPage.__table__.c] -class StreamerOnlineAdmin(ModelView, model=StreamerOnline): +class StreamerOnlineAdmin(BaseAdmin, model=StreamerOnline): engine = widgets_engine category = "Widgets DB" name = "Streamer Online" diff --git a/src/database/__pycache__/engines.cpython-311.pyc b/src/database/__pycache__/engines.cpython-311.pyc index 334a93c..81f2a1d 100644 Binary files a/src/database/__pycache__/engines.cpython-311.pyc and b/src/database/__pycache__/engines.cpython-311.pyc differ diff --git a/src/database/engines.py b/src/database/engines.py index 2ac8b59..a4536b1 100644 --- a/src/database/engines.py +++ b/src/database/engines.py @@ -2,7 +2,7 @@ import os from dotenv import load_dotenv -from sqlalchemy.ext.asyncio import create_async_engine +from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker env = load_dotenv() @@ -15,6 +15,11 @@ WIDGETS_DB_URL = os.getenv("WIDGETS_DB_URL") ACCOUNTS_DB_URL = os.getenv("ACCOUNTS_DB_URL") + payments_engine = create_async_engine(PAYMENTS_DB_URL) widgets_engine = create_async_engine(WIDGETS_DB_URL) -accounts_engine = create_async_engine(ACCOUNTS_DB_URL) \ No newline at end of file +accounts_engine = create_async_engine(ACCOUNTS_DB_URL) + +accounts_session_maker = async_sessionmaker( + bind=accounts_engine, +) \ No newline at end of file diff --git a/src/main.py b/src/main.py index 4240979..72d38f9 100644 --- a/src/main.py +++ b/src/main.py @@ -2,6 +2,7 @@ from fastapi import FastAPI from sqladmin import Admin from src.admin.account import AccountAdmin +from src.admin.auth import authentication_backend from src.admin.multi_engine import AdminSessionMaker from src.admin.payment import DealAdmin, BalanceAdmin, PaymentAdmin, TinkoffWithdrawMethodAdmin, TinkoffWithdrawAdmin, \ StreamersCommissionAdmin @@ -14,6 +15,7 @@ app = FastAPI() admin = Admin( app=app, session_maker=AdminSessionMaker, + authentication_backend=authentication_backend ) admin.add_view(AccountAdmin)