Compare commits
	
		
			3 Commits
		
	
	
		
			5937058899
			...
			951bc15957
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 951bc15957 | |||
| 
						 | 
					258ed970f1 | ||
| 
						 | 
					a3a6509933 | 
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							@@ -148,6 +148,7 @@ env.bak/
 | 
			
		||||
venv.bak/
 | 
			
		||||
/logger_helper/loggers
 | 
			
		||||
/app/bybit/logger_bybit/loggers
 | 
			
		||||
*.db
 | 
			
		||||
# Spyder project settings
 | 
			
		||||
.spyderproject
 | 
			
		||||
.spyproject
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										147
									
								
								alembic.ini
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										147
									
								
								alembic.ini
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,147 @@
 | 
			
		||||
# A generic, single database configuration.
 | 
			
		||||
 | 
			
		||||
[alembic]
 | 
			
		||||
# path to migration scripts.
 | 
			
		||||
# this is typically a path given in POSIX (e.g. forward slashes)
 | 
			
		||||
# format, relative to the token %(here)s which refers to the location of this
 | 
			
		||||
# ini file
 | 
			
		||||
script_location = %(here)s/alembic
 | 
			
		||||
 | 
			
		||||
# template used to generate migration file names; The default value is %%(rev)s_%%(slug)s
 | 
			
		||||
# Uncomment the line below if you want the files to be prepended with date and time
 | 
			
		||||
# see https://alembic.sqlalchemy.org/en/latest/tutorial.html#editing-the-ini-file
 | 
			
		||||
# for all available tokens
 | 
			
		||||
# file_template = %%(year)d_%%(month).2d_%%(day).2d_%%(hour).2d%%(minute).2d-%%(rev)s_%%(slug)s
 | 
			
		||||
 | 
			
		||||
# sys.path path, will be prepended to sys.path if present.
 | 
			
		||||
# defaults to the current working directory.  for multiple paths, the path separator
 | 
			
		||||
# is defined by "path_separator" below.
 | 
			
		||||
prepend_sys_path = .
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
# timezone to use when rendering the date within the migration file
 | 
			
		||||
# as well as the filename.
 | 
			
		||||
# If specified, requires the python>=3.9 or backports.zoneinfo library and tzdata library.
 | 
			
		||||
# Any required deps can installed by adding `alembic[tz]` to the pip requirements
 | 
			
		||||
# string value is passed to ZoneInfo()
 | 
			
		||||
# leave blank for localtime
 | 
			
		||||
# timezone =
 | 
			
		||||
 | 
			
		||||
# max length of characters to apply to the "slug" field
 | 
			
		||||
# truncate_slug_length = 40
 | 
			
		||||
 | 
			
		||||
# set to 'true' to run the environment during
 | 
			
		||||
# the 'revision' command, regardless of autogenerate
 | 
			
		||||
# revision_environment = false
 | 
			
		||||
 | 
			
		||||
# set to 'true' to allow .pyc and .pyo files without
 | 
			
		||||
# a source .py file to be detected as revisions in the
 | 
			
		||||
# versions/ directory
 | 
			
		||||
# sourceless = false
 | 
			
		||||
 | 
			
		||||
# version location specification; This defaults
 | 
			
		||||
# to <script_location>/versions.  When using multiple version
 | 
			
		||||
# directories, initial revisions must be specified with --version-path.
 | 
			
		||||
# The path separator used here should be the separator specified by "path_separator"
 | 
			
		||||
# below.
 | 
			
		||||
# version_locations = %(here)s/bar:%(here)s/bat:%(here)s/alembic/versions
 | 
			
		||||
 | 
			
		||||
# path_separator; This indicates what character is used to split lists of file
 | 
			
		||||
# paths, including version_locations and prepend_sys_path within configparser
 | 
			
		||||
# files such as alembic.ini.
 | 
			
		||||
# The default rendered in new alembic.ini files is "os", which uses os.pathsep
 | 
			
		||||
# to provide os-dependent path splitting.
 | 
			
		||||
#
 | 
			
		||||
# Note that in order to support legacy alembic.ini files, this default does NOT
 | 
			
		||||
# take place if path_separator is not present in alembic.ini.  If this
 | 
			
		||||
# option is omitted entirely, fallback logic is as follows:
 | 
			
		||||
#
 | 
			
		||||
# 1. Parsing of the version_locations option falls back to using the legacy
 | 
			
		||||
#    "version_path_separator" key, which if absent then falls back to the legacy
 | 
			
		||||
#    behavior of splitting on spaces and/or commas.
 | 
			
		||||
# 2. Parsing of the prepend_sys_path option falls back to the legacy
 | 
			
		||||
#    behavior of splitting on spaces, commas, or colons.
 | 
			
		||||
#
 | 
			
		||||
# Valid values for path_separator are:
 | 
			
		||||
#
 | 
			
		||||
# path_separator = :
 | 
			
		||||
# path_separator = ;
 | 
			
		||||
# path_separator = space
 | 
			
		||||
# path_separator = newline
 | 
			
		||||
#
 | 
			
		||||
# Use os.pathsep. Default configuration used for new projects.
 | 
			
		||||
path_separator = os
 | 
			
		||||
 | 
			
		||||
# set to 'true' to search source files recursively
 | 
			
		||||
# in each "version_locations" directory
 | 
			
		||||
# new in Alembic version 1.10
 | 
			
		||||
# recursive_version_locations = false
 | 
			
		||||
 | 
			
		||||
# the output encoding used when revision files
 | 
			
		||||
# are written from script.py.mako
 | 
			
		||||
# output_encoding = utf-8
 | 
			
		||||
 | 
			
		||||
# database URL.  This is consumed by the user-maintained env.py script only.
 | 
			
		||||
# other means of configuring database URLs may be customized within the env.py
 | 
			
		||||
# file.
 | 
			
		||||
sqlalchemy.url = sqlite+aiosqlite:///./database/db/stcs.db
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
[post_write_hooks]
 | 
			
		||||
# post_write_hooks defines scripts or Python functions that are run
 | 
			
		||||
# on newly generated revision scripts.  See the documentation for further
 | 
			
		||||
# detail and examples
 | 
			
		||||
 | 
			
		||||
# format using "black" - use the console_scripts runner, against the "black" entrypoint
 | 
			
		||||
# hooks = black
 | 
			
		||||
# black.type = console_scripts
 | 
			
		||||
# black.entrypoint = black
 | 
			
		||||
# black.options = -l 79 REVISION_SCRIPT_FILENAME
 | 
			
		||||
 | 
			
		||||
# lint with attempts to fix using "ruff" - use the module runner, against the "ruff" module
 | 
			
		||||
# hooks = ruff
 | 
			
		||||
# ruff.type = module
 | 
			
		||||
# ruff.module = ruff
 | 
			
		||||
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
 | 
			
		||||
 | 
			
		||||
# Alternatively, use the exec runner to execute a binary found on your PATH
 | 
			
		||||
# hooks = ruff
 | 
			
		||||
# ruff.type = exec
 | 
			
		||||
# ruff.executable = ruff
 | 
			
		||||
# ruff.options = check --fix REVISION_SCRIPT_FILENAME
 | 
			
		||||
 | 
			
		||||
# Logging configuration.  This is also consumed by the user-maintained
 | 
			
		||||
# env.py script only.
 | 
			
		||||
[loggers]
 | 
			
		||||
keys = root,sqlalchemy,alembic
 | 
			
		||||
 | 
			
		||||
[handlers]
 | 
			
		||||
keys = console
 | 
			
		||||
 | 
			
		||||
[formatters]
 | 
			
		||||
keys = generic
 | 
			
		||||
 | 
			
		||||
[logger_root]
 | 
			
		||||
level = WARNING
 | 
			
		||||
handlers = console
 | 
			
		||||
qualname =
 | 
			
		||||
 | 
			
		||||
[logger_sqlalchemy]
 | 
			
		||||
level = WARNING
 | 
			
		||||
handlers =
 | 
			
		||||
qualname = sqlalchemy.engine
 | 
			
		||||
 | 
			
		||||
[logger_alembic]
 | 
			
		||||
level = INFO
 | 
			
		||||
handlers =
 | 
			
		||||
qualname = alembic
 | 
			
		||||
 | 
			
		||||
[handler_console]
 | 
			
		||||
class = StreamHandler
 | 
			
		||||
args = (sys.stderr,)
 | 
			
		||||
level = NOTSET
 | 
			
		||||
formatter = generic
 | 
			
		||||
 | 
			
		||||
[formatter_generic]
 | 
			
		||||
format = %(levelname)-5.5s [%(name)s] %(message)s
 | 
			
		||||
datefmt = %H:%M:%S
 | 
			
		||||
							
								
								
									
										1
									
								
								alembic/README
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1
									
								
								alembic/README
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1 @@
 | 
			
		||||
Generic single-database configuration.
 | 
			
		||||
							
								
								
									
										53
									
								
								alembic/env.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										53
									
								
								alembic/env.py
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,53 @@
 | 
			
		||||
import asyncio
 | 
			
		||||
from logging.config import fileConfig
 | 
			
		||||
from sqlalchemy import pool
 | 
			
		||||
from sqlalchemy.ext.asyncio import async_engine_from_config
 | 
			
		||||
from alembic import context
 | 
			
		||||
 | 
			
		||||
config = context.config
 | 
			
		||||
 | 
			
		||||
if config.config_file_name is not None:
 | 
			
		||||
    fileConfig(config.config_file_name)
 | 
			
		||||
 | 
			
		||||
from database.models import Base
 | 
			
		||||
target_metadata = Base.metadata
 | 
			
		||||
 | 
			
		||||
def do_run_migrations(connection):
 | 
			
		||||
    context.configure(
 | 
			
		||||
        connection=connection,
 | 
			
		||||
        target_metadata=target_metadata,
 | 
			
		||||
        compare_type=True,
 | 
			
		||||
    )
 | 
			
		||||
    with context.begin_transaction():
 | 
			
		||||
        context.run_migrations()
 | 
			
		||||
 | 
			
		||||
async def run_async_migrations():
 | 
			
		||||
    connectable = async_engine_from_config(
 | 
			
		||||
        config.get_section(config.config_ini_section),
 | 
			
		||||
        prefix="sqlalchemy.",
 | 
			
		||||
        poolclass=pool.NullPool,
 | 
			
		||||
    )
 | 
			
		||||
 | 
			
		||||
    async with connectable.connect() as connection:
 | 
			
		||||
        await connection.run_sync(do_run_migrations)
 | 
			
		||||
 | 
			
		||||
    await connectable.dispose()
 | 
			
		||||
 | 
			
		||||
def run_migrations_offline():
 | 
			
		||||
    url = config.get_main_option("sqlalchemy.url")
 | 
			
		||||
    context.configure(
 | 
			
		||||
        url=url,
 | 
			
		||||
        target_metadata=target_metadata,
 | 
			
		||||
        literal_binds=True,
 | 
			
		||||
        dialect_opts={"paramstyle": "named"},
 | 
			
		||||
    )
 | 
			
		||||
    with context.begin_transaction():
 | 
			
		||||
        context.run_migrations()
 | 
			
		||||
 | 
			
		||||
def run_migrations_online():
 | 
			
		||||
    asyncio.run(run_async_migrations())
 | 
			
		||||
 | 
			
		||||
if context.is_offline_mode():
 | 
			
		||||
    run_migrations_offline()
 | 
			
		||||
else:
 | 
			
		||||
    run_migrations_online()
 | 
			
		||||
							
								
								
									
										28
									
								
								alembic/script.py.mako
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								alembic/script.py.mako
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,28 @@
 | 
			
		||||
"""${message}
 | 
			
		||||
 | 
			
		||||
Revision ID: ${up_revision}
 | 
			
		||||
Revises: ${down_revision | comma,n}
 | 
			
		||||
Create Date: ${create_date}
 | 
			
		||||
 | 
			
		||||
"""
 | 
			
		||||
from typing import Sequence, Union
 | 
			
		||||
 | 
			
		||||
from alembic import op
 | 
			
		||||
import sqlalchemy as sa
 | 
			
		||||
${imports if imports else ""}
 | 
			
		||||
 | 
			
		||||
# revision identifiers, used by Alembic.
 | 
			
		||||
revision: str = ${repr(up_revision)}
 | 
			
		||||
down_revision: Union[str, Sequence[str], None] = ${repr(down_revision)}
 | 
			
		||||
branch_labels: Union[str, Sequence[str], None] = ${repr(branch_labels)}
 | 
			
		||||
depends_on: Union[str, Sequence[str], None] = ${repr(depends_on)}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def upgrade() -> None:
 | 
			
		||||
    """Upgrade schema."""
 | 
			
		||||
    ${upgrades if upgrades else "pass"}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
def downgrade() -> None:
 | 
			
		||||
    """Downgrade schema."""
 | 
			
		||||
    ${downgrades if downgrades else "pass"}
 | 
			
		||||
@@ -21,16 +21,20 @@ async def user_profile_bybit(tg_id: int, message: Message, state: FSMContext) ->
 | 
			
		||||
        if wallet:
 | 
			
		||||
            balance = wallet.get("totalWalletBalance", "0")
 | 
			
		||||
            symbol = await rq.get_user_symbol(tg_id=tg_id)
 | 
			
		||||
            await message.answer(
 | 
			
		||||
                text=f"💎Ваш профиль:\n\n"
 | 
			
		||||
                     f"⚖️ Баланс: {float(balance):,.2f} USD\n"
 | 
			
		||||
                     f"📊Торговая пара: {symbol}\n\n"
 | 
			
		||||
                     f"Краткая инструкция:\n"
 | 
			
		||||
                     f"1. Укажите торговую пару (например: BTCUSDT).\n"
 | 
			
		||||
                     f"2. В настройках выставьте все необходимые параметры.\n"
 | 
			
		||||
                     f"3. Нажмите кнопку 'Начать торговлю'.\n",
 | 
			
		||||
                reply_markup=kbi.main_menu,
 | 
			
		||||
            )
 | 
			
		||||
            if symbol is None:
 | 
			
		||||
                await rq.set_user_symbol(tg_id=tg_id, symbol="BTCUSDT")
 | 
			
		||||
                await user_profile_bybit(tg_id=tg_id, message=message, state=state)
 | 
			
		||||
            else:
 | 
			
		||||
                await message.answer(
 | 
			
		||||
                    text=f"💎Ваш профиль:\n\n"
 | 
			
		||||
                         f"⚖️ Баланс: {float(balance):,.2f} USD\n"
 | 
			
		||||
                         f"📊Торговая пара: {symbol}\n\n"
 | 
			
		||||
                         f"Краткая инструкция:\n"
 | 
			
		||||
                         f"1. Укажите торговую пару (например: BTCUSDT).\n"
 | 
			
		||||
                         f"2. В настройках выставьте все необходимые параметры.\n"
 | 
			
		||||
                         f"3. Нажмите кнопку 'Начать торговлю'.\n",
 | 
			
		||||
                    reply_markup=kbi.main_menu,
 | 
			
		||||
                )
 | 
			
		||||
        else:
 | 
			
		||||
            await message.answer(
 | 
			
		||||
                text="Ошибка при подключении, повторите попытку",
 | 
			
		||||
 
 | 
			
		||||
@@ -6,5 +6,3 @@ if env_path:
 | 
			
		||||
    load_dotenv(env_path)
 | 
			
		||||
 | 
			
		||||
BOT_TOKEN = os.getenv("BOT_TOKEN")
 | 
			
		||||
 | 
			
		||||
DATABASE_URL = f"sqlite+aiosqlite:///database/data/sqlite.db"
 | 
			
		||||
 
 | 
			
		||||
@@ -1,14 +1,19 @@
 | 
			
		||||
from database.models import Base
 | 
			
		||||
from database.models import Base, User, UserAdditionalSettings, UserApi, UserConditionalSettings, UserDeals, \
 | 
			
		||||
    UserRiskManagement, UserSymbol
 | 
			
		||||
import logging.config
 | 
			
		||||
from sqlalchemy.ext.asyncio import create_async_engine, async_sessionmaker, AsyncSession
 | 
			
		||||
from sqlalchemy import event
 | 
			
		||||
from config import DATABASE_URL
 | 
			
		||||
 | 
			
		||||
from pathlib import Path
 | 
			
		||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
			
		||||
 | 
			
		||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
			
		||||
logger = logging.getLogger("database")
 | 
			
		||||
 | 
			
		||||
BASE_DIR = Path(__file__).parent.resolve()
 | 
			
		||||
DATA_DIR = BASE_DIR / "db"
 | 
			
		||||
DATA_DIR.mkdir(parents=True, exist_ok=True)
 | 
			
		||||
 | 
			
		||||
DATABASE_URL = f"sqlite+aiosqlite:///{DATA_DIR / 'stcs.db'}"
 | 
			
		||||
 | 
			
		||||
async_engine = create_async_engine(
 | 
			
		||||
    DATABASE_URL,
 | 
			
		||||
@@ -16,22 +21,25 @@ async_engine = create_async_engine(
 | 
			
		||||
    connect_args={"check_same_thread": False}
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@event.listens_for(async_engine.sync_engine, "connect")
 | 
			
		||||
def _enable_foreign_keys(dbapi_connection, connection_record):
 | 
			
		||||
    cursor = dbapi_connection.cursor()
 | 
			
		||||
    cursor.execute("PRAGMA foreign_keys=ON")
 | 
			
		||||
    cursor.close()
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async_session = async_sessionmaker(
 | 
			
		||||
    async_engine,
 | 
			
		||||
    class_=AsyncSession,
 | 
			
		||||
    expire_on_commit=False
 | 
			
		||||
)
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
async def init_db():
 | 
			
		||||
    try:
 | 
			
		||||
        async with async_engine.begin() as conn:
 | 
			
		||||
            await conn.run_sync(Base.metadata.create_all)
 | 
			
		||||
        logger.info("Database initialized.")
 | 
			
		||||
    except Exception as e:
 | 
			
		||||
        logger.error("Database initialization failed: %s", e, exc_info=True)
 | 
			
		||||
        logger.error("Database initialization failed: %s", e)
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								run.py
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								run.py
									
									
									
									
									
								
							@@ -5,10 +5,10 @@ import logging.config
 | 
			
		||||
from aiogram import Bot, Dispatcher
 | 
			
		||||
from aiogram.fsm.storage.redis import RedisStorage
 | 
			
		||||
 | 
			
		||||
from database import init_db
 | 
			
		||||
from app.bybit.web_socket import WebSocketBot
 | 
			
		||||
from app.telegram.handlers import router
 | 
			
		||||
from config import BOT_TOKEN
 | 
			
		||||
from database import init_db
 | 
			
		||||
from logger_helper.logger_helper import LOGGING_CONFIG
 | 
			
		||||
 | 
			
		||||
logging.config.dictConfig(LOGGING_CONFIG)
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user