add init commit
This commit is contained in:
parent
330d39373f
commit
294c0ebb2d
213
.gitignore
vendored
213
.gitignore
vendored
@ -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
37
Dockerfile
Normal 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
18
docker-compose.yml
Normal 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
94
poetry.lock
generated
@ -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"
|
||||
|
@ -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)"
|
||||
]
|
||||
|
||||
|
||||
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -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"
|
||||
|
@ -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)
|
||||
# Создаем экземпляр бэкенда
|
||||
authentication_backend = AdminAuth(secret_key=SECRET_KEY)
|
6
src/admin/base_admin.py
Normal file
6
src/admin/base_admin.py
Normal file
@ -0,0 +1,6 @@
|
||||
from sqladmin import ModelView
|
||||
|
||||
|
||||
class BaseAdmin(ModelView):
|
||||
can_delete = False
|
||||
page_size = 100
|
@ -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"
|
||||
|
@ -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"
|
||||
|
Binary file not shown.
@ -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)
|
||||
accounts_engine = create_async_engine(ACCOUNTS_DB_URL)
|
||||
|
||||
accounts_session_maker = async_sessionmaker(
|
||||
bind=accounts_engine,
|
||||
)
|
@ -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)
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user