add init commit

This commit is contained in:
harold 2025-08-28 11:34:22 +05:00
parent 330d39373f
commit 294c0ebb2d
17 changed files with 513 additions and 59 deletions

213
.gitignore vendored
View File

@ -1 +1,212 @@
.env # 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

37
Dockerfile Normal file
View File

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

18
docker-compose.yml Normal file
View File

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

94
poetry.lock generated
View File

@ -124,6 +124,25 @@ files = [
{file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, {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]] [[package]]
name = "fastapi" name = "fastapi"
version = "0.116.1" version = "0.116.1"
@ -242,6 +261,18 @@ files = [
[package.extras] [package.extras]
all = ["flake8 (>=7.1.1)", "mypy (>=1.11.2)", "pytest (>=8.3.2)", "ruff (>=0.6.2)"] 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]] [[package]]
name = "jinja2" name = "jinja2"
version = "3.1.6" version = "3.1.6"
@ -331,6 +362,18 @@ files = [
{file = "markupsafe-3.0.2.tar.gz", hash = "sha256:ee55d3edf80167e48ea11a923c7386f4669df67d7994554387f84e7d8b0a2bf0"}, {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]] [[package]]
name = "pydantic" name = "pydantic"
version = "2.11.7" version = "2.11.7"
@ -480,6 +523,29 @@ files = [
[package.extras] [package.extras]
cli = ["click (>=5.0)"] 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]] [[package]]
name = "python-multipart" name = "python-multipart"
version = "0.0.20" version = "0.0.20"
@ -492,6 +558,32 @@ files = [
{file = "python_multipart-0.0.20.tar.gz", hash = "sha256:8dd0cab45b8e23064ae09147625994d090fa46f5b0d1e13af944c331a7fa9d13"}, {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]] [[package]]
name = "sniffio" name = "sniffio"
version = "1.3.1" version = "1.3.1"
@ -708,4 +800,4 @@ email = ["email-validator"]
[metadata] [metadata]
lock-version = "2.1" lock-version = "2.1"
python-versions = ">=3.11" python-versions = ">=3.11"
content-hash = "21a35caa29ba269d21d8b22f161f5828f99ab497c5d687f013eda16461478541" content-hash = "00e000faa33bdaefef12840d09b78569135abf3a3a37996816ac4866bce65714"

View File

@ -13,7 +13,9 @@ dependencies = [
"fastapi (>=0.116.1,<0.117.0)", "fastapi (>=0.116.1,<0.117.0)",
"uvicorn (>=0.35.0,<0.36.0)", "uvicorn (>=0.35.0,<0.36.0)",
"python-dotenv (>=1.1.1,<2.0.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)"
] ]

Binary file not shown.

View File

@ -1,10 +1,11 @@
from sqladmin import ModelView from sqladmin import ModelView
from src.admin.base_admin import BaseAdmin
from src.database.engines import accounts_engine from src.database.engines import accounts_engine
from src.database.streamers import Account from src.database.streamers import Account
class AccountAdmin(ModelView, model=Account): class AccountAdmin(BaseAdmin, model=Account):
engine = accounts_engine engine = accounts_engine
name = "Account" name = "Account"

View File

@ -1,45 +1,115 @@
from fastapi import Request import os
from jose import JWTError 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 sqladmin.authentication import AuthenticationBackend
from starlette import status
from starlette.responses import RedirectResponse from starlette.responses import RedirectResponse
from src.api_v1.auth.jwt import create_access_token, parse_jwt_token from src.database.engines import accounts_session_maker
from src.api_v1.auth.security import check_password from src.database.streamers import Account
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 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): def parse_jwt_token(
async def login(self, request: Request) -> bool: token: str,
form = await request.form() ) -> dict:
username, password = form['username'], form['password'] try:
async with db_helper.get_async_session_not_closed() as session: payload = jwt.decode(
user_model = await auth_crud.get_user_by_login( token=token,
login=username, key=SECRET_KEY,
session=session, algorithms=[ALGORITHM]
)
if payload["email"] != ADMIN_EMAIL:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
) )
except JWTError:
if not check_password( raise HTTPException(
plain_password=password, status_code=status.HTTP_401_UNAUTHORIZED,
hashed_password=user_model.hashed_password, # type:ignore detail="Could not validate credentials",
):
raise NotAuthenticated()
if user_model.role != 'Админ':
raise PermissionDenied()
jwt_token = create_access_token(
user=user_model
) )
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: async def logout(self, request: Request) -> bool:
"""
Обрабатывает выход администратора.
"""
request.session.clear() request.session.clear()
return True return True
@ -63,4 +133,5 @@ class AdminAuth(AuthenticationBackend):
return True return True
authentication_backend = AdminAuth(secret_key=settings.JWT.SECRET) # Создаем экземпляр бэкенда
authentication_backend = AdminAuth(secret_key=SECRET_KEY)

6
src/admin/base_admin.py Normal file
View File

@ -0,0 +1,6 @@
from sqladmin import ModelView
class BaseAdmin(ModelView):
can_delete = False
page_size = 100

View File

@ -1,19 +1,25 @@
from sqladmin import ModelView from sqladmin import ModelView
from src.admin.base_admin import BaseAdmin
from src.database.payments import Payment, Deal, Balance, TinkoffWithdraw, TinkoffWithdrawMethod, StreamersCommission 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" category = "Payments DB"
name = "Payment" name = "Payment"
name_plural = "Payments" name_plural = "Payments"
icon = "fa-solid fa-money-bill" icon = "fa-solid fa-money-bill"
column_list = [Payment.order_id, Payment.streamer_id, Payment.amount, Payment.created_at] 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) column_default_sort = ("created_at", True)
class DealAdmin(ModelView, model=Deal): class DealAdmin(BaseAdmin, model=Deal):
category = "Payments DB" category = "Payments DB"
name = "Deal" name = "Deal"
name_plural = "Deals" name_plural = "Deals"
@ -22,16 +28,18 @@ class DealAdmin(ModelView, model=Deal):
column_searchable_list = [Deal.deal_id, Deal.streamer_id] column_searchable_list = [Deal.deal_id, Deal.streamer_id]
class BalanceAdmin(ModelView, model=Balance): class BalanceAdmin(BaseAdmin, model=Balance):
category = "Payments DB" category = "Payments DB"
name = "Balance Op" name = "Balance Op"
name_plural = "Balance Ops" name_plural = "Balance Ops"
icon = "fa-solid fa-scale-balanced" 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_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" category = "Payments DB"
name = "Tinkoff Withdraw" name = "Tinkoff Withdraw"
name_plural = "Tinkoff Withdraws" name_plural = "Tinkoff Withdraws"
@ -42,7 +50,7 @@ class TinkoffWithdrawAdmin(ModelView, model=TinkoffWithdraw):
column_default_sort = ("created_at", True) column_default_sort = ("created_at", True)
class TinkoffWithdrawMethodAdmin(ModelView, model=TinkoffWithdrawMethod): class TinkoffWithdrawMethodAdmin(BaseAdmin, model=TinkoffWithdrawMethod):
category = "Payments DB" category = "Payments DB"
name = "Withdraw Method" name = "Withdraw Method"
name_plural = "Withdraw Methods" name_plural = "Withdraw Methods"
@ -51,7 +59,7 @@ class TinkoffWithdrawMethodAdmin(ModelView, model=TinkoffWithdrawMethod):
TinkoffWithdrawMethod.is_main] TinkoffWithdrawMethod.is_main]
class StreamersCommissionAdmin(ModelView, model=StreamersCommission): class StreamersCommissionAdmin(BaseAdmin, model=StreamersCommission):
category = "Payments DB" category = "Payments DB"
name = "Commission" name = "Commission"
name_plural = "Commissions" name_plural = "Commissions"

View File

@ -1,11 +1,12 @@
from sqladmin import ModelView from sqladmin import ModelView
from src.admin.base_admin import BaseAdmin
from src.database.engines import widgets_engine from src.database.engines import widgets_engine
from src.database.widgets import Widget, File, Donat, Target, DonatePage, Moderation, Filter, FilterWord, VoiceSetting, \ from src.database.widgets import Widget, File, Donat, Target, DonatePage, Moderation, Filter, FilterWord, VoiceSetting, \
Language, VoiceLanguage, StreamerWidgetPage, StreamerOnline Language, VoiceLanguage, StreamerWidgetPage, StreamerOnline
class FileAdmin(ModelView, model=File): class FileAdmin(BaseAdmin, model=File):
engine = widgets_engine engine = widgets_engine
category = "Widgets DB" category = "Widgets DB"
name = "File" 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_list = [File.id, File.file_name, File.file_type, File.streamer_id, File.created_at]
column_searchable_list = [File.file_name] column_searchable_list = [File.file_name]
class WidgetAdmin(ModelView, model=Widget): class WidgetAdmin(BaseAdmin, model=Widget):
engine = widgets_engine engine = widgets_engine
category = "Widgets DB" category = "Widgets DB"
name = "Widget" 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_list = [Widget.id, Widget.name, Widget.streamer_id, Widget.is_active, Widget.min_amount]
column_searchable_list = [Widget.name] column_searchable_list = [Widget.name]
class DonatAdmin(ModelView, model=Donat): class DonatAdmin(BaseAdmin, model=Donat):
engine = widgets_engine engine = widgets_engine
category = "Widgets DB" category = "Widgets DB"
name = "Donat" name = "Donat"
@ -33,7 +34,7 @@ class DonatAdmin(ModelView, model=Donat):
column_searchable_list = [Donat.donat_user, Donat.text] column_searchable_list = [Donat.donat_user, Donat.text]
column_default_sort = ("created_at", True) column_default_sort = ("created_at", True)
class TargetAdmin(ModelView, model=Target): class TargetAdmin(BaseAdmin, model=Target):
engine = widgets_engine engine = widgets_engine
category = "Widgets DB" category = "Widgets DB"
name = "Target" name = "Target"
@ -41,7 +42,7 @@ class TargetAdmin(ModelView, model=Target):
icon = "fa-solid fa-bullseye" icon = "fa-solid fa-bullseye"
column_list = [Target.id, Target.text, Target.amount, Target.collected, Target.streamer_id] 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 engine = widgets_engine
category = "Widgets DB" category = "Widgets DB"
name = "Donate Page" name = "Donate Page"
@ -50,7 +51,7 @@ class DonatePageAdmin(ModelView, model=DonatePage):
column_list = [DonatePage.id, DonatePage.streamer_login, DonatePage.streamer_id] column_list = [DonatePage.id, DonatePage.streamer_login, DonatePage.streamer_id]
column_searchable_list = [DonatePage.streamer_login] column_searchable_list = [DonatePage.streamer_login]
class ModerationAdmin(ModelView, model=Moderation): class ModerationAdmin(BaseAdmin, model=Moderation):
engine = widgets_engine engine = widgets_engine
category = "Widgets DB" category = "Widgets DB"
name = "Moderation" name = "Moderation"
@ -58,7 +59,7 @@ class ModerationAdmin(ModelView, model=Moderation):
icon = "fa-solid fa-user-shield" icon = "fa-solid fa-user-shield"
column_list = [c.name for c in Moderation.__table__.c] column_list = [c.name for c in Moderation.__table__.c]
class FilterAdmin(ModelView, model=Filter): class FilterAdmin(BaseAdmin, model=Filter):
engine = widgets_engine engine = widgets_engine
category = "Widgets DB" category = "Widgets DB"
name = "Filter" name = "Filter"
@ -66,7 +67,7 @@ class FilterAdmin(ModelView, model=Filter):
icon = "fa-solid fa-filter" icon = "fa-solid fa-filter"
column_list = [c.name for c in Filter.__table__.c] column_list = [c.name for c in Filter.__table__.c]
class FilterWordAdmin(ModelView, model=FilterWord): class FilterWordAdmin(BaseAdmin, model=FilterWord):
engine = widgets_engine engine = widgets_engine
category = "Widgets DB" category = "Widgets DB"
name = "Filter Word" name = "Filter Word"
@ -75,7 +76,7 @@ class FilterWordAdmin(ModelView, model=FilterWord):
column_list = [FilterWord.id, FilterWord.word, FilterWord.donat_filter_id] column_list = [FilterWord.id, FilterWord.word, FilterWord.donat_filter_id]
column_searchable_list = [FilterWord.word] column_searchable_list = [FilterWord.word]
class VoiceSettingAdmin(ModelView, model=VoiceSetting): class VoiceSettingAdmin(BaseAdmin, model=VoiceSetting):
engine = widgets_engine engine = widgets_engine
category = "Widgets DB" category = "Widgets DB"
name = "Voice Setting" name = "Voice Setting"
@ -83,7 +84,7 @@ class VoiceSettingAdmin(ModelView, model=VoiceSetting):
icon = "fa-solid fa-microphone-lines" icon = "fa-solid fa-microphone-lines"
column_list = [VoiceSetting.id, VoiceSetting.streamer_id, VoiceSetting.enable, VoiceSetting.min_price] 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 engine = widgets_engine
category = "Widgets DB" category = "Widgets DB"
name = "Language" 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_list = [Language.id, Language.ru_name, Language.en_name, Language.iso_code]
column_searchable_list = [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 engine = widgets_engine
category = "Widgets DB" category = "Widgets DB"
name = "Voice Language" name = "Voice Language"
@ -100,7 +101,7 @@ class VoiceLanguageAdmin(ModelView, model=VoiceLanguage):
icon = "fa-solid fa-link" icon = "fa-solid fa-link"
column_list = [c.name for c in VoiceLanguage.__table__.c] column_list = [c.name for c in VoiceLanguage.__table__.c]
class StreamerWidgetPageAdmin(ModelView, model=StreamerWidgetPage): class StreamerWidgetPageAdmin(BaseAdmin, model=StreamerWidgetPage):
engine = widgets_engine engine = widgets_engine
category = "Widgets DB" category = "Widgets DB"
name = "Streamer Widget Page" name = "Streamer Widget Page"
@ -108,7 +109,7 @@ class StreamerWidgetPageAdmin(ModelView, model=StreamerWidgetPage):
icon = "fa-solid fa-desktop" icon = "fa-solid fa-desktop"
column_list = [c.name for c in StreamerWidgetPage.__table__.c] column_list = [c.name for c in StreamerWidgetPage.__table__.c]
class StreamerOnlineAdmin(ModelView, model=StreamerOnline): class StreamerOnlineAdmin(BaseAdmin, model=StreamerOnline):
engine = widgets_engine engine = widgets_engine
category = "Widgets DB" category = "Widgets DB"
name = "Streamer Online" name = "Streamer Online"

View File

@ -2,7 +2,7 @@ import os
from dotenv import load_dotenv 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() env = load_dotenv()
@ -15,6 +15,11 @@ WIDGETS_DB_URL = os.getenv("WIDGETS_DB_URL")
ACCOUNTS_DB_URL = os.getenv("ACCOUNTS_DB_URL") ACCOUNTS_DB_URL = os.getenv("ACCOUNTS_DB_URL")
payments_engine = create_async_engine(PAYMENTS_DB_URL) payments_engine = create_async_engine(PAYMENTS_DB_URL)
widgets_engine = create_async_engine(WIDGETS_DB_URL) widgets_engine = create_async_engine(WIDGETS_DB_URL)
accounts_engine = create_async_engine(ACCOUNTS_DB_URL) accounts_engine = create_async_engine(ACCOUNTS_DB_URL)
accounts_session_maker = async_sessionmaker(
bind=accounts_engine,
)

View File

@ -2,6 +2,7 @@ from fastapi import FastAPI
from sqladmin import Admin from sqladmin import Admin
from src.admin.account import AccountAdmin from src.admin.account import AccountAdmin
from src.admin.auth import authentication_backend
from src.admin.multi_engine import AdminSessionMaker from src.admin.multi_engine import AdminSessionMaker
from src.admin.payment import DealAdmin, BalanceAdmin, PaymentAdmin, TinkoffWithdrawMethodAdmin, TinkoffWithdrawAdmin, \ from src.admin.payment import DealAdmin, BalanceAdmin, PaymentAdmin, TinkoffWithdrawMethodAdmin, TinkoffWithdrawAdmin, \
StreamersCommissionAdmin StreamersCommissionAdmin
@ -14,6 +15,7 @@ app = FastAPI()
admin = Admin( admin = Admin(
app=app, app=app,
session_maker=AdminSessionMaker, session_maker=AdminSessionMaker,
authentication_backend=authentication_backend
) )
admin.add_view(AccountAdmin) admin.add_view(AccountAdmin)