From f5d2051360a501b10d64e504a04a35ab3a95fe73 Mon Sep 17 00:00:00 2001 From: 0xbovs1 Date: Sat, 21 Dec 2024 12:38:57 +0000 Subject: [PATCH] massage 1 --- щхлджэ => .env | 0 .gitignore | 2 + Dockerfile | 14 +++ LICENSE | 7 ++ README.md | 36 ++++++ app/configdb1.py | 78 +++++++++++++ app/model1.py | 137 +++++++++++++++++++++++ app/mongodb1.py | 118 ++++++++++++++++++++ app/postgresql1.py | 191 ++++++++++++++++++++++++++++++++ app/routes/__init__.py | 0 app/routes/auth_routes.py | 0 app/routes/user_routes.py | 0 app/services/__init__.py | 0 app/services/payment_service.py | 0 app/services/user_service.py | 0 config.py | 0 docker-compose.yml | 0 instance/config.py | 0 requirements.txt | 40 +++++++ run.py | 0 tests/__init__.py | 0 tests/test_auth.py | 0 tests/test_users.py | 0 23 files changed, 623 insertions(+) rename щхлджэ => .env (100%) create mode 100644 .gitignore create mode 100644 Dockerfile create mode 100644 LICENSE create mode 100644 README.md create mode 100644 app/configdb1.py create mode 100644 app/model1.py create mode 100644 app/mongodb1.py create mode 100644 app/postgresql1.py create mode 100644 app/routes/__init__.py create mode 100644 app/routes/auth_routes.py create mode 100644 app/routes/user_routes.py create mode 100644 app/services/__init__.py create mode 100644 app/services/payment_service.py create mode 100644 app/services/user_service.py create mode 100644 config.py create mode 100644 docker-compose.yml create mode 100644 instance/config.py create mode 100644 requirements.txt create mode 100644 run.py create mode 100644 tests/__init__.py create mode 100644 tests/test_auth.py create mode 100644 tests/test_users.py diff --git a/щхлджэ b/.env similarity index 100% rename from щхлджэ rename to .env diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f96f1b6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +TBot/ +logs/* \ No newline at end of file diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..e4aa934 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,14 @@ +# Используем базовый Python-образ +FROM python:3.12-slim + +# Устанавливаем рабочую директорию +WORKDIR /app + +# Копируем файлы проекта +COPY . . + +# Устанавливаем зависимости +RUN pip install --no-cache-dir -r requirements.txt + +# Указываем команду запуска бота +CMD ["python", "main.py"] diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..c50c398 --- /dev/null +++ b/LICENSE @@ -0,0 +1,7 @@ +Copyright (c) 2024 disledg + +Данная лицензия разрешает лицам, получившим копию данного программного обеспечения и сопутствующей документации (в дальнейшем именуемыми «Программное Обеспечение»), безвозмездно использовать Программное Обеспечение без ограничений, включая неограниченное право на использование, копирование, изменение, слияние, публикацию, распространение, сублицензирование и/или продажу копий Программного Обеспечения, а также лицам, которым предоставляется данное Программное Обеспечение, при соблюдении следующих условий: + +Указанное выше уведомление об авторском праве и данные условия должны быть включены во все копии или значимые части данного Программного Обеспечения. + +ДАННОЕ ПРОГРАММНОЕ ОБЕСПЕЧЕНИЕ ПРЕДОСТАВЛЯЕТСЯ «КАК ЕСТЬ», БЕЗ КАКИХ-ЛИБО ГАРАНТИЙ, ЯВНО ВЫРАЖЕННЫХ ИЛИ ПОДРАЗУМЕВАЕМЫХ, ВКЛЮЧАЯ ГАРАНТИИ ТОВАРНОЙ ПРИГОДНОСТИ, СООТВЕТСТВИЯ ПО ЕГО КОНКРЕТНОМУ НАЗНАЧЕНИЮ И ОТСУТСТВИЯ НАРУШЕНИЙ, НО НЕ ОГРАНИЧИВАЯСЬ ИМИ. НИ В КАКОМ СЛУЧАЕ АВТОРЫ ИЛИ ПРАВООБЛАДАТЕЛИ НЕ НЕСУТ ОТВЕТСТВЕННОСТИ ПО КАКИМ-ЛИБО ИСКАМ, УЩЕРБАМ ИЛИ ИНЫМ ТРЕБОВАНИЯМ, В ТОМ ЧИСЛЕ, ПРИ ДЕЙСТВИИ КОНТРАКТА, ДЕЛИКТА ИЛИ ИНОЙ СИТУАЦИИ, ВОЗНИКШИМ ИЗ-ЗА ИСПОЛЬЗОВАНИЯ ПРОГРАММНОГО ОБЕСПЕЧЕНИЯ ИЛИ ИНЫХ ДЕЙСТВИЙ С ПРОГРАММНЫМ ОБЕСПЕЧЕНИЕМ. diff --git a/README.md b/README.md new file mode 100644 index 0000000..6e28450 --- /dev/null +++ b/README.md @@ -0,0 +1,36 @@ + + +# VPN Configuration Sales Bot + +Бот для Telegram, предназначенный для продажи VPN конфигураций. Проект создан с целью автоматизации процесса продажи VPN и управления пользователями через удобный интерфейс Telegram, с использованием баз данных PostgreSQL и MongoDB. + +## 📋 Описание + +Этот проект представляет собой Telegram-бота, который позволяет пользователям приобретать VPN настройки, а администраторам – управлять конфигурациями и отслеживать заказы. Бот поддерживает работу с двумя базами данных для обеспечения гибкого хранения и обработки данных. + +## 🛠 Функционал + +- Регистрация и авторизация пользователей +- Покупка VPN конфигураций + +### В стадии разработки + +- Автоматическая выдача VPN конфигураций +- Поддержка двух баз данных (PostgreSQL и MongoDB) для более гибкого и масштабируемого хранения данных +- Панель администратора для управления заказами и пользователями +- Саппорт система + +## 🚀 Технологии + +- **Python** +- **Telegram API** +- **PostgreSQL** +- **MongoDB** +- **SQLAlchemy** - для взаимодействия с PostgreSQL +- **PyMongo** - для работы с MongoDB + +## 📝 Лицензия + +Этот проект распространяется под лицензией MIT License. Подробности в файле [LICENSE](./LICENSE). + + diff --git a/app/configdb1.py b/app/configdb1.py new file mode 100644 index 0000000..842a143 --- /dev/null +++ b/app/configdb1.py @@ -0,0 +1,78 @@ +import os +from flask import Flask, g +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.orm import sessionmaker +from motor.motor_asyncio import AsyncIOMotorClient +from databases.model import Base +import asyncio + +# Инициализация Flask +app = Flask(__name__) + +# Настройки PostgreSQL из переменных окружения +POSTGRES_DSN = os.getenv("POSTGRES_URL") + +# Создание движка для PostgreSQL +postgres_engine = create_async_engine(POSTGRES_DSN, echo=False) +AsyncSessionLocal = sessionmaker(bind=postgres_engine, class_=AsyncSession, expire_on_commit=False) + +# Настройки MongoDB из переменных окружения +MONGO_URI = os.getenv("MONGO_URL") +DATABASE_NAME = os.getenv("DB_NAME") + +# Создание клиента MongoDB +mongo_client = AsyncIOMotorClient(MONGO_URI) +mongo_db = mongo_client[DATABASE_NAME] + +@app.before_first_request +async def init_databases(): + """ + Инициализация подключений к PostgreSQL и MongoDB перед первым запросом. + """ + try: + # Инициализация PostgreSQL + async with postgres_engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + print("PostgreSQL connected.") + + # Проверка подключения к MongoDB + await mongo_client.admin.command("ping") + print("MongoDB connected.") + except Exception as e: + print(f"Database initialization failed: {e}") + +@app.teardown_appcontext +def close_connections(exception=None): + """ + Закрытие соединений с базами данных после окончания работы приложения. + """ + asyncio.run(postgres_engine.dispose()) + mongo_client.close() + print("Database connections closed.") + +@app.route("/postgres_session") +async def get_postgres_session(): + """ + Пример использования сессии PostgreSQL в маршруте Flask. + """ + try: + async with AsyncSessionLocal() as session: + # Здесь можно выполнить запросы к базе данных PostgreSQL + result = await session.execute("SELECT 1") + return {"postgres_result": result.scalar()} + except Exception as e: + return {"error": str(e)}, 500 + +@app.route("/mongo_status") +async def get_mongo_status(): + """ + Пример проверки MongoDB в маршруте Flask. + """ + try: + await mongo_client.admin.command("ping") + return {"mongo_status": "connected"} + except Exception as e: + return {"error": str(e)}, 500 + +if __name__ == "__main__": + app.run(debug=True) diff --git a/app/model1.py b/app/model1.py new file mode 100644 index 0000000..cca8760 --- /dev/null +++ b/app/model1.py @@ -0,0 +1,137 @@ +import os +from flask import Flask, g +from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession +from sqlalchemy.orm import sessionmaker +from motor.motor_asyncio import AsyncIOMotorClient +from sqlalchemy import Column, String, Numeric, DateTime, Boolean, ForeignKey, Integer +from sqlalchemy.orm import declarative_base, relationship +from datetime import datetime +import uuid +import asyncio + +# Инициализация Flask +app = Flask(__name__) + +# Настройки PostgreSQL из переменных окружения +POSTGRES_DSN = os.getenv("POSTGRES_URL") + +# Создание движка для PostgreSQL +postgres_engine = create_async_engine(POSTGRES_DSN, echo=False) +AsyncSessionLocal = sessionmaker(bind=postgres_engine, class_=AsyncSession, expire_on_commit=False) + +# Настройки MongoDB из переменных окружения +MONGO_URI = os.getenv("MONGO_URL") +DATABASE_NAME = os.getenv("DB_NAME") + +# Создание клиента MongoDB +mongo_client = AsyncIOMotorClient(MONGO_URI) +mongo_db = mongo_client[DATABASE_NAME] + +# SQLAlchemy Base +Base = declarative_base() + +def generate_uuid(): + return str(uuid.uuid4()) + +"""Пользователи""" +class User(Base): + __tablename__ = 'users' + + id = Column(String, primary_key=True, default=generate_uuid) + telegram_id = Column(Integer, unique=True, nullable=False) + username = Column(String) + balance = Column(Numeric(10, 2), default=0.0) + created_at = Column(DateTime, default=datetime.utcnow) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + subscriptions = relationship("Subscription", back_populates="user") + transactions = relationship("Transaction", back_populates="user") + admins = relationship("Administrators", back_populates="user") + +"""Подписки""" +class Subscription(Base): + __tablename__ = 'subscriptions' + + id = Column(String, primary_key=True, default=generate_uuid) + user_id = Column(String, ForeignKey('users.id')) + vpn_server_id = Column(String) + plan = Column(String) + expiry_date = Column(DateTime) + created_at = Column(DateTime, default=datetime.utcnow) + updated_at = Column(DateTime, default=datetime.utcnow, onupdate=datetime.utcnow) + + user = relationship("User", back_populates="subscriptions") + +"""Транзакции""" +class Transaction(Base): + __tablename__ = 'transactions' + + id = Column(String, primary_key=True, default=generate_uuid) + user_id = Column(String, ForeignKey('users.id')) + amount = Column(Numeric(10, 2)) + transaction_type = Column(String) + created_at = Column(DateTime, default=datetime.utcnow) + + user = relationship("User", back_populates="transactions") + +"""Администраторы""" +class Administrators(Base): + __tablename__ = 'admins' + + id = Column(String, primary_key=True, default=generate_uuid) + user_id = Column(String, ForeignKey('users.id')) + + user = relationship("User", back_populates="admins") + +@app.before_first_request +async def init_databases(): + """ + Инициализация подключений к PostgreSQL и MongoDB перед первым запросом. + """ + try: + # Инициализация PostgreSQL + async with postgres_engine.begin() as conn: + await conn.run_sync(Base.metadata.create_all) + print("PostgreSQL connected.") + + # Проверка подключения к MongoDB + await mongo_client.admin.command("ping") + print("MongoDB connected.") + except Exception as e: + print(f"Database initialization failed: {e}") + +@app.teardown_appcontext +def close_connections(exception=None): + """ + Закрытие соединений с базами данных после окончания работы приложения. + """ + asyncio.run(postgres_engine.dispose()) + mongo_client.close() + print("Database connections closed.") + +@app.route("/postgres_session") +async def get_postgres_session(): + """ + Пример использования сессии PostgreSQL в маршруте Flask. + """ + try: + async with AsyncSessionLocal() as session: + # Здесь можно выполнить запросы к базе данных PostgreSQL + result = await session.execute("SELECT 1") + return {"postgres_result": result.scalar()} + except Exception as e: + return {"error": str(e)}, 500 + +@app.route("/mongo_status") +async def get_mongo_status(): + """ + Пример проверки MongoDB в маршруте Flask. + """ + try: + await mongo_client.admin.command("ping") + return {"mongo_status": "connected"} + except Exception as e: + return {"error": str(e)}, 500 + +if __name__ == "__main__": + app.run(debug=True) diff --git a/app/mongodb1.py b/app/mongodb1.py new file mode 100644 index 0000000..121e929 --- /dev/null +++ b/app/mongodb1.py @@ -0,0 +1,118 @@ +import os +import logging +from flask import Flask, jsonify, request +from motor.motor_asyncio import AsyncIOMotorClient +from bson import ObjectId +from flask.logging import default_handler +import asyncio + +app = Flask(__name__) + +# Настройки логирования +logger = logging.getLogger("MongoDBRepository") +logger.setLevel(logging.DEBUG) +logger.addHandler(default_handler) + +# Настройки MongoDB из переменных окружения +mongo_uri = os.getenv("MONGO_URL", "mongodb://localhost:27017") +database_name = os.getenv("DB_NAME", "mydatabase") +server_collection = os.getenv("SERVER_COLLECTION", "servers") +plan_collection = os.getenv("PLAN_COLLECTION", "plans") + +# Подключение к базе данных +client = AsyncIOMotorClient(mongo_uri) +db = client[database_name] +servers = db[server_collection] +plans = db[plan_collection] + +@app.route('/plans', methods=['POST']) +async def add_subscription_plan(): + plan_data = request.json + result = await plans.insert_one(plan_data) + logger.debug(f"Тарифный план добавлен с ID: {result.inserted_id}") + return jsonify({"inserted_id": str(result.inserted_id)}), 201 + +@app.route('/plans/', methods=['GET']) +async def get_subscription_plan(plan_id): + plan = await plans.find_one({"_id": ObjectId(plan_id)}) + if plan: + logger.debug(f"Найден тарифный план: {plan}") + plan["_id"] = str(plan["_id"]) + return jsonify(plan) + else: + logger.error(f"Тарифный план {plan_id} не найден.") + return jsonify({"error": "Plan not found"}), 404 + +@app.route('/servers', methods=['POST']) +async def add_server(): + server_data = request.json + result = await servers.insert_one(server_data) + logger.debug(f"VPN сервер добавлен с ID: {result.inserted_id}") + return jsonify({"inserted_id": str(result.inserted_id)}), 201 + +@app.route('/servers/', methods=['GET']) +async def get_server(server_name): + server = await servers.find_one({"server.name": server_name}) + if server: + logger.debug(f"Найден VPN сервер: {server}") + server["_id"] = str(server["_id"]) + return jsonify(server) + else: + logger.debug(f"VPN сервер с именем {server_name} не найден.") + return jsonify({"error": "Server not found"}), 404 + +@app.route('/servers/least_clients', methods=['GET']) +async def get_server_with_least_clients(): + pipeline = [ + {"$addFields": {"current_clients": {"$size": {"$ifNull": ["$clients", []]}}}}, + {"$sort": {"current_clients": 1}}, + {"$limit": 1} + ] + result = await servers.aggregate(pipeline).to_list(length=1) + if result: + server = result[0] + server["_id"] = str(server["_id"]) + logger.debug(f"Найден сервер с наименьшим количеством клиентов: {server}") + return jsonify(server) + else: + logger.debug("Не найдено серверов.") + return jsonify({"error": "No servers found"}), 404 + +@app.route('/servers/', methods=['PUT']) +async def update_server(server_id): + update_data = request.json + result = await servers.update_one({"_id": ObjectId(server_id)}, {"$set": update_data}) + if result.matched_count > 0: + logger.debug(f"VPN сервер с ID {server_id} обновлен.") + return jsonify({"updated": True}) + else: + logger.debug(f"VPN сервер с ID {server_id} не найден.") + return jsonify({"error": "Server not found"}), 404 + +@app.route('/servers/', methods=['DELETE']) +async def delete_server(server_id): + result = await servers.delete_one({"_id": ObjectId(server_id)}) + if result.deleted_count > 0: + logger.debug(f"VPN сервер с ID {server_id} удален.") + return jsonify({"deleted": True}) + else: + logger.debug(f"VPN сервер с ID {server_id} не найден.") + return jsonify({"error": "Server not found"}), 404 + +@app.route('/servers', methods=['GET']) +async def list_servers(): + server_list = await servers.find().to_list(length=1000) + for server in server_list: + server["_id"] = str(server["_id"]) + logger.debug(f"Найдено {len(server_list)} VPN серверов.") + return jsonify(server_list) + +@app.route('/shutdown', methods=['POST']) +async def close_connection(): + client.close() + logger.debug("Подключение к MongoDB закрыто.") + return jsonify({"message": "Connection closed"}), 200 + +if __name__ == '__main__': + loop = asyncio.get_event_loop() + loop.run_until_complete(app.run(debug=True)) diff --git a/app/postgresql1.py b/app/postgresql1.py new file mode 100644 index 0000000..bc851af --- /dev/null +++ b/app/postgresql1.py @@ -0,0 +1,191 @@ +from flask import Flask, request, jsonify +from databases.model import User, Subscription, Transaction +from sqlalchemy.future import select +from sqlalchemy.exc import SQLAlchemyError +from sqlalchemy import desc +from dateutil.relativedelta import relativedelta +from datetime import datetime +from utils.panel import PanelInteraction +from databases.mongodb import MongoDBRepository +import random +import string +import logging +import asyncio + +app = Flask(__name__) + +# Настройка логирования +logger = logging.getLogger(__name__) +mongo_repo = MongoDBRepository() + +# Генератор сессий передаётся извне (например, через dependency injection) +session_generator = None + +def generate_string(length): + """ + Генерирует случайную строку заданной длины. + """ + characters = string.ascii_lowercase + string.digits + return ''.join(random.choices(characters, k=length)) + +@app.route('/create_user', methods=['POST']) +def create_user(): + telegram_id = request.json.get('telegram_id') + if not telegram_id: + return jsonify({'error': 'Telegram ID is required'}), 400 + + async def process(): + async for session in session_generator(): + try: + username = generate_string(6) + result = await session.execute(select(User).where(User.telegram_id == int(telegram_id))) + user = result.scalars().first() + if not user: + new_user = User(telegram_id=int(telegram_id), username=username) + session.add(new_user) + await session.commit() + return jsonify({'user': new_user.id, 'username': new_user.username}), 201 + return jsonify({'user': user.id, 'username': user.username}), 200 + except SQLAlchemyError as e: + logger.error(f"Ошибка при создании пользователя {telegram_id}: {e}") + await session.rollback() + return jsonify({'error': 'Internal server error'}), 500 + + return asyncio.run(process()) + +@app.route('/get_user/', methods=['GET']) +def get_user_by_telegram_id(telegram_id): + async def process(): + async for session in session_generator(): + try: + result = await session.execute(select(User).where(User.telegram_id == telegram_id)) + user = result.scalars().first() + if user: + return jsonify({'id': user.id, 'username': user.username, 'balance': user.balance}), 200 + return jsonify({'error': 'User not found'}), 404 + except SQLAlchemyError as e: + logger.error(f"Ошибка при получении пользователя {telegram_id}: {e}") + return jsonify({'error': 'Internal server error'}), 500 + + return asyncio.run(process()) + +@app.route('/update_balance', methods=['POST']) +def update_balance(): + data = request.json + telegram_id = data.get('telegram_id') + amount = data.get('amount') + + if not telegram_id or amount is None: + return jsonify({'error': 'Telegram ID and amount are required'}), 400 + + async def process(): + async for session in session_generator(): + try: + result = await session.execute(select(User).where(User.telegram_id == telegram_id)) + user = result.scalars().first() + if user: + user.balance += int(amount) + transaction = Transaction(user_id=user.id, amount=amount) + session.add(transaction) + await session.commit() + return jsonify({'balance': user.balance}), 200 + return jsonify({'error': 'User not found'}), 404 + except SQLAlchemyError as e: + logger.error(f"Ошибка при обновлении баланса: {e}") + await session.rollback() + return jsonify({'error': 'Internal server error'}), 500 + + return asyncio.run(process()) + +@app.route('/buy_subscription', methods=['POST']) +def buy_subscription(): + data = request.json + telegram_id = data.get('telegram_id') + plan_id = data.get('plan_id') + + if not telegram_id or not plan_id: + return jsonify({'error': 'Telegram ID and Plan ID are required'}), 400 + + async def process(): + async for session in session_generator(): + try: + result = await session.execute(select(User).where(User.telegram_id == telegram_id)) + user = result.scalars().first() + if not user: + return jsonify({'error': 'User not found'}), 404 + + plan = await mongo_repo.get_subscription_plan(plan_id) + if not plan: + return jsonify({'error': 'Plan not found'}), 404 + + cost = int(plan['price']) + if user.balance >= cost: + user.balance -= cost + expiry_date = datetime.utcnow() + relativedelta(months=plan['duration_months']) + server = await mongo_repo.get_server_with_least_clients() + + new_subscription = Subscription(user_id=user.id, vpn_server_id=str(server['server']['name']), + plan=plan_id, expiry_date=expiry_date) + session.add(new_subscription) + await session.commit() + return jsonify({'message': 'Subscription purchased successfully'}), 200 + return jsonify({'error': 'Insufficient funds'}), 400 + except SQLAlchemyError as e: + logger.error(f"Ошибка при покупке подписки {plan_id} для пользователя {telegram_id}: {e}") + await session.rollback() + return jsonify({'error': 'Internal server error'}), 500 + + return asyncio.run(process()) + +@app.route('/add_to_server', methods=['POST']) +def add_to_server(): + data = request.json + telegram_id = data.get('telegram_id') + + if not telegram_id: + return jsonify({'error': 'Telegram ID is required'}), 400 + + async def process(): + async for session in session_generator(): + try: + result = await session.execute(select(Subscription).join(User).where(User.telegram_id == int(telegram_id))) + user_sub = result.scalars().first() + + if not user_sub: + logger.error(f"Не удалось найти подписку для пользователя с Telegram ID {telegram_id}.") + return jsonify({'error': 'Subscription not found'}), 404 + + user_result = await session.execute(select(User).where(User.telegram_id == telegram_id)) + user = user_result.scalars().first() + + server = await mongo_repo.get_server(user_sub.vpn_server_id) + if not server: + logger.error(f"Не удалось найти сервер с ID {user_sub.vpn_server_id}.") + return jsonify({'error': 'Server not found'}), 404 + + server_info = server['server'] + url_base = f"https://{server_info['ip']}:{server_info['port']}/{server_info['secretKey']}" + login_data = { + 'username': server_info['login'], + 'password': server_info['password'], + } + + panel = PanelInteraction(url_base, login_data, logger) + expiry_date_iso = user_sub.expiry_date.isoformat() + response = await panel.add_client(user.id, expiry_date_iso, user.username) + + if response == "OK": + logger.info(f"Клиент {telegram_id} успешно добавлен на сервер.") + return jsonify({'message': 'Client added successfully'}), 200 + else: + logger.error(f"Ошибка при добавлении клиента {telegram_id} на сервер: {response}") + return jsonify({'error': 'Failed to add client to server'}), 500 + + except Exception as e: + logger.error(f"Ошибка при установке на сервер для пользователя {telegram_id}: {e}") + return jsonify({'error': 'Internal server error'}), 500 + + return asyncio.run(process()) + +if __name__ == '__main__': + app.run(debug=True) diff --git a/app/routes/__init__.py b/app/routes/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/routes/auth_routes.py b/app/routes/auth_routes.py new file mode 100644 index 0000000..e69de29 diff --git a/app/routes/user_routes.py b/app/routes/user_routes.py new file mode 100644 index 0000000..e69de29 diff --git a/app/services/__init__.py b/app/services/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/services/payment_service.py b/app/services/payment_service.py new file mode 100644 index 0000000..e69de29 diff --git a/app/services/user_service.py b/app/services/user_service.py new file mode 100644 index 0000000..e69de29 diff --git a/config.py b/config.py new file mode 100644 index 0000000..e69de29 diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..e69de29 diff --git a/instance/config.py b/instance/config.py new file mode 100644 index 0000000..e69de29 diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..224c87a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,40 @@ +aiofiles==24.1.0 +aiogram==3.15.0 +aiohappyeyeballs==2.4.3 +aiohttp==3.10.11 +aiosignal==1.3.1 +annotated-types==0.7.0 +anyio==4.6.0 +asyncpg==0.30.0 +attrs==24.2.0 +certifi==2024.8.30 +charset-normalizer==3.4.0 +DateTime==5.5 +dnspython==2.7.0 +frozenlist==1.5.0 +greenlet==3.1.1 +h11==0.14.0 +httpcore==1.0.6 +httpx==0.27.2 +idna==3.10 +magic-filter==1.0.12 +motor==3.6.0 +multidict==6.1.0 +propcache==0.2.0 +psycopg2-binary==2.9.10 +pydantic==2.9.2 +pydantic_core==2.23.4 +pymongo==4.9.2 +python-dateutil==2.9.0.post0 +python-telegram-bot==21.6 +pytz==2024.2 +requests==2.32.3 +setuptools==75.1.0 +six==1.16.0 +sniffio==1.3.1 +SQLAlchemy==2.0.35 +telegram==0.0.1 +typing_extensions==4.12.2 +urllib3==2.2.3 +yarl==1.17.2 +zope.interface==7.1.0 \ No newline at end of file diff --git a/run.py b/run.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_auth.py b/tests/test_auth.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/test_users.py b/tests/test_users.py new file mode 100644 index 0000000..e69de29