diff --git a/app.py b/app.py
index 288c205..1cca8b6 100644
--- a/app.py
+++ b/app.py
@@ -1,30 +1,23 @@
-from app import MongoDB, aggregate_salaries, Settings
+from app import configs, configure_logger, router
import asyncio
-import json
+from aiogram import Bot, Dispatcher
+from aiogram.enums.parse_mode import ParseMode
+from aiogram.fsm.storage.memory import MemoryStorage
+from loguru import logger
+def register_logger():
+ configure_logger(capture_exceptions=True)
+ logger.info("Success logger register")
-settings = Settings()
-client = MongoDB(str(settings.DB_URI))
-db = client.client[settings.DATABASE_NAME]
-collection = db[settings.COLLECTION_NAME]
-
-json_str = '''
-{
- "dt_from": "2022-09-01T00:00:00",
- "dt_upto": "2022-12-31T23:59:00",
- "group_type": "month"
-}
-'''
-
-async def main(json_str):
- data = json.loads(json_str)
- dt_from = data["dt_from"]
- dt_upto = data["dt_upto"]
- group_type = data["group_type"]
-
- result = await aggregate_salaries(collection, dt_from, dt_upto, group_type)
-
- print(result['dataset'], len(result['dataset']))
- print(result['labels'], len(result['labels']))
-
-asyncio.run(main(json_str))
\ No newline at end of file
+async def start_app():
+ bot = Bot(token=configs.API_TOKEN_TG)
+ dp = Dispatcher(storage=MemoryStorage())
+ dp.include_router(router)
+ logger.info("Starting bot..")
+ await dp.start_polling(bot, allowed_updates=dp.resolve_used_update_types())
+
+
+if __name__ == "__main__":
+ loop = asyncio.get_event_loop()
+ loop.run_until_complete(start_app())
+ loop.run_forever()
\ No newline at end of file
diff --git a/app/__init__.py b/app/__init__.py
index 920a29a..24238b6 100644
--- a/app/__init__.py
+++ b/app/__init__.py
@@ -1,2 +1,3 @@
-from app.config import Settings
-from app.database import MongoDB, aggregate_salaries
\ No newline at end of file
+from app.config import configs
+from app.logger import configure_logger
+from app.handlers import router
\ No newline at end of file
diff --git a/app/config.py b/app/config.py
index b6ff5a7..e7c7981 100644
--- a/app/config.py
+++ b/app/config.py
@@ -3,7 +3,7 @@ from pydantic import computed_field, MongoDsn
from pydantic_core import Url
-class Settings(BaseSettings):
+class Configs(BaseSettings):
API_TOKEN_TG: str
HOST_MONGODB: str
DATABASE_NAME: str
@@ -17,4 +17,6 @@ class Settings(BaseSettings):
f"mongodb+srv://{self.USERNAME_MONGO}:{self.PASSWORD_MONGO}@{self.HOST_MONGODB}"
)
- model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8')
\ No newline at end of file
+ model_config = SettingsConfigDict(env_file='.env', env_file_encoding='utf-8')
+
+configs = Configs()
\ No newline at end of file
diff --git a/app/database/MongoDBConfig.py b/app/database/MongoDBConfig.py
new file mode 100644
index 0000000..9cba3f8
--- /dev/null
+++ b/app/database/MongoDBConfig.py
@@ -0,0 +1,7 @@
+from pydantic import BaseModel
+
+
+class MongoDBConfig(BaseModel):
+ url: str
+ db_name: str = None
+ collection: str = None
\ No newline at end of file
diff --git a/app/database/mongodb.py b/app/database/mongodb.py
index 4c3bf9b..2ab8fd0 100644
--- a/app/database/mongodb.py
+++ b/app/database/mongodb.py
@@ -1,5 +1,19 @@
from motor.motor_asyncio import AsyncIOMotorClient
+from app.database.MongoDBConfig import MongoDBConfig
+
class MongoDB:
- def __init__(self, url: str):
- self.client = AsyncIOMotorClient(url)
+ def __init__(self, config: MongoDBConfig):
+ self.client = AsyncIOMotorClient(config.url)
+ self.db = self.get_db(config.db_name)
+ self.collection = self.get_collection(config.collection)
+
+ def get_db(self, db_name: str):
+ if db_name is None:
+ return None
+ return self.client[db_name]
+
+ def get_collection(self, collection_name: str):
+ if collection_name is None or self.db is None:
+ return None
+ return self.db[collection_name]
\ No newline at end of file
diff --git a/app/handlers.py b/app/handlers.py
new file mode 100644
index 0000000..1dce343
--- /dev/null
+++ b/app/handlers.py
@@ -0,0 +1,30 @@
+import json
+from app.database import MongoDB, aggregate_salaries
+from app.config import configs
+from aiogram import Router, types
+from aiogram.filters import Command
+from app.texts import invalid, greetings
+from app.query import Query
+from app.database.MongoDBConfig import MongoDBConfig
+from loguru import logger
+
+
+router = Router()
+client_config = MongoDBConfig(url=str(configs.DB_URI), db_name=configs.DATABASE_NAME, collection=configs.COLLECTION_NAME)
+client = MongoDB(config=client_config)
+
+@router.message(Command("start"))
+async def start_handler(message: types.Message):
+ await message.answer(greetings.format(name=message.from_user.first_name))
+
+
+@router.message()
+async def query_handler(message: types.Message):
+ try:
+ query = Query(**json.loads(message.text))
+ logger.info("Aggregate query starting")
+ result = await aggregate_salaries(client.collection, query.dt_from, query.dt_upto, query.group_type)
+ logger.info("Aggregate query complete")
+ await message.answer(str(result))
+ except ValueError as e:
+ await message.answer(invalid)
\ No newline at end of file
diff --git a/app/logger.py b/app/logger.py
new file mode 100644
index 0000000..634cb99
--- /dev/null
+++ b/app/logger.py
@@ -0,0 +1,52 @@
+import logging
+import sys
+
+from loguru import logger
+
+
+class InterceptHandler(logging.Handler):
+ LEVELS_MAP = {
+ logging.CRITICAL: "CRITICAL",
+ logging.ERROR: "ERROR",
+ logging.WARNING: "WARNING",
+ logging.INFO: "INFO",
+ logging.DEBUG: "DEBUG",
+ }
+
+ def _get_level(self, record):
+ return self.LEVELS_MAP.get(record.levelno, record.levelno)
+
+ def emit(self, record):
+ logger_opt = logger.opt(depth=6, exception=record.exc_info)
+ logger_opt.log(self._get_level(record), record.getMessage())
+
+
+def configure_logger(capture_exceptions: bool = False) -> None:
+ logger.remove()
+ level = "INFO"
+ logger.add(
+ "logs/log_{time:YYYY-MM-DD}.log",
+ rotation="12:00",
+ format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {file}:{line} | {message}",
+ level="INFO",
+ encoding="utf-8",
+ compression="zip",
+ )
+ logger.add(
+ sys.stdout,
+ colorize=True,
+ format="{time:YYYY-MM-DD at HH:mm:ss} | {level} | {file}:{line} | "
+ "{message}",
+ level=level,
+ )
+ if capture_exceptions:
+ logger.add(
+ "logs/error_log_{time:YYYY-MM-DD}.log",
+ rotation="12:00",
+ format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {file}:{line} | {message}",
+ level="ERROR",
+ encoding="utf-8",
+ compression="zip",
+ )
+
+ logging.basicConfig(handlers=[InterceptHandler()], level=logging.INFO)
diff --git a/app/query.py b/app/query.py
new file mode 100644
index 0000000..eb7cc74
--- /dev/null
+++ b/app/query.py
@@ -0,0 +1,32 @@
+from datetime import datetime
+from pydantic import BaseModel, validator
+
+
+class Query(BaseModel):
+ dt_from: str
+ dt_upto: str
+ group_type: str
+
+ @validator('group_type', pre=True, always=True)
+ def validate_group_type(cls, v):
+ if v not in ['month', 'day', 'hour']:
+ raise ValueError('Invalid group_type')
+ return v
+
+ @validator('dt_from', pre=True, always=True)
+ def validate_dt_from(cls, v):
+ try:
+ datetime.strptime(v, '%Y-%m-%dT%H:%M:%S')
+ except ValueError:
+ raise ValueError('Invalid dt_from')
+ return v
+
+ @validator('dt_upto', pre=True, always=True)
+ def validate_dt_upto(cls, v, values):
+ try:
+ datetime.strptime(v, '%Y-%m-%dT%H:%M:%S')
+ except ValueError:
+ raise ValueError('Invalid dt_upto')
+ if values['dt_from'] > v:
+ raise ValueError('dt_upto should be later than dt_from')
+ return v
\ No newline at end of file
diff --git a/app/texts.py b/app/texts.py
new file mode 100644
index 0000000..59de8c8
--- /dev/null
+++ b/app/texts.py
@@ -0,0 +1,2 @@
+invalid = 'Невалидный запрос. Пример запроса: {"dt_from": "2022-09-01T00:00:00", "dt_upto": "2022-12-31T23:59:00", "group_type": "month"}'
+greetings = 'Привет, {name}! Отправьте запрос в формате {{"dt_from": "2022-09-01T00:00:00", "dt_upto": "2022-12-31T23:59:00", "group_type": "month" || "day" || "hour"}}'