From 5d4a92703c9d9f12bebdd83019dd8f64ca81e2cb Mon Sep 17 00:00:00 2001 From: latan Date: Thu, 16 Nov 2023 01:26:17 +0200 Subject: [PATCH 1/2] Add tracking and logging new users and booking actions --- .../medsyncapp/tgbot/handlers/new_users.py | 15 ++++++++ .../medsyncapp/tgbot/services/log_new_user.py | 7 ++++ .../src/medsyncapp/webhook/routers/booking.py | 37 ++++++++++++++----- backend/src/medsyncapp/webhook/utils.py | 9 ++++- 4 files changed, 56 insertions(+), 12 deletions(-) create mode 100644 backend/src/medsyncapp/tgbot/handlers/new_users.py create mode 100644 backend/src/medsyncapp/tgbot/services/log_new_user.py diff --git a/backend/src/medsyncapp/tgbot/handlers/new_users.py b/backend/src/medsyncapp/tgbot/handlers/new_users.py new file mode 100644 index 0000000..9fed776 --- /dev/null +++ b/backend/src/medsyncapp/tgbot/handlers/new_users.py @@ -0,0 +1,15 @@ +from aiogram import Router, types, Bot +from aiogram.filters import JOIN_TRANSITION, ChatMemberUpdatedFilter + +from medsyncapp.tgbot.config import Config +from medsyncapp.tgbot.services.broadcaster import broadcast +from medsyncapp.tgbot.services.log_new_user import track_user_join_text + +new_users_router = Router() + + + + +@new_users_router.my_chat_member(ChatMemberUpdatedFilter(JOIN_TRANSITION)) +async def new_user(cmu: types.ChatMemberUpdated, config: Config, bot: Bot): + await broadcast(bot, config.tg_bot.admin_ids, track_user_join_text(cmu.from_user)) diff --git a/backend/src/medsyncapp/tgbot/services/log_new_user.py b/backend/src/medsyncapp/tgbot/services/log_new_user.py new file mode 100644 index 0000000..016a66b --- /dev/null +++ b/backend/src/medsyncapp/tgbot/services/log_new_user.py @@ -0,0 +1,7 @@ +from aiogram import types + + +def track_user_join_text(from_user: types.User): + user_username = f"@{from_user.username}" if from_user.username else "" + user_mention = from_user.mention_html(from_user.full_name) + return f"{user_mention} {user_username} joined the bot" diff --git a/backend/src/medsyncapp/webhook/routers/booking.py b/backend/src/medsyncapp/webhook/routers/booking.py index e3eccf3..d0bce96 100644 --- a/backend/src/medsyncapp/webhook/routers/booking.py +++ b/backend/src/medsyncapp/webhook/routers/booking.py @@ -1,5 +1,4 @@ import json -import logging from contextlib import suppress import sqlalchemy.exc @@ -13,6 +12,8 @@ validate_telegram_data, bot, parse_init_data, + track_user_booked_text, + config, ) from .diagnostics import diagnostics_router from .doctors import doctor_router @@ -44,16 +45,14 @@ async def book_slot_endpoint( ): book_repo = repo.doctors if item_type == "doctor" else repo.diagnostics - user_info = parsed_data.get('user') + user_info = parsed_data.get("user") if user_info: - user_id = json.loads(user_info).get('id') + user_id = json.loads(user_info).get("id") else: user_id = None try: - booking_id = await book_repo.book_slot( - payload, user_id=user_id - ) + booking_id = await book_repo.book_slot(payload, user_id=user_id) except sqlalchemy.exc.IntegrityError as e: await repo.session.rollback() if "bookings_user_id_fkey" in str(e) and payload.get("user_id"): @@ -63,7 +62,9 @@ async def book_slot_endpoint( + " " + payload.get("user_surname", ""), ) - booking_id = await book_repo.book_slot(payload, user_id=parsed_data.get("user_id")) + booking_id = await book_repo.book_slot( + payload, user_id=parsed_data.get("user_id") + ) else: raise HTTPException(status_code=400, detail="Invalid payload") from e @@ -83,16 +84,32 @@ async def book_slot(request: Request, repo: RequestsRepo = Depends(get_repo)): if init_data: user_info = parsed_data.get("user") if user_info: - user_id = json.loads(user_info).get("id") - with suppress(): - notification = await get_booking_notification_text( + user_info = json.loads(user_info) + user_id = user_info.get("id") + username = user_info.get("username") + first_name = user_info.get("first_name") + last_name = user_info.get("last_name") + notification = await get_booking_notification_text( repo, result["booking_id"] ) + with suppress(): await bot.send_message( chat_id=user_id, text=notification, parse_mode="HTML", ) + for admin_id in config.tg_bot.admin_ids: + with suppress(): + await bot.send_message( + chat_id=admin_id, + text=track_user_booked_text( + username=username, + user_id=user_id, + first_name=first_name, + last_name=last_name, + ), + parse_mode="HTML", + ) return result diff --git a/backend/src/medsyncapp/webhook/utils.py b/backend/src/medsyncapp/webhook/utils.py index dab2565..de1363f 100644 --- a/backend/src/medsyncapp/webhook/utils.py +++ b/backend/src/medsyncapp/webhook/utils.py @@ -4,7 +4,7 @@ import time from urllib.parse import unquote, parse_qsl -from aiogram import Bot +from aiogram import Bot, types from medsyncapp.infrastructure.database.repo.requests import RequestsRepo from medsyncapp.infrastructure.database.setup import create_session_pool, create_engine @@ -54,5 +54,10 @@ def validate_telegram_data(init_data: str) -> bool: if computed_hash != received_hash: return False - return True + + +def track_user_booked_text(username: str, user_id: int, first_name: str, last_name: str): + user_username = f"@{username}" if username else "" + user_mention = f"{first_name} {last_name}" + return f"{user_mention} {user_username} booked a slot" From 6f74fa41c2d8fd34d5de2f88cf4c9c68c1ee15aa Mon Sep 17 00:00:00 2001 From: latan Date: Thu, 16 Nov 2023 02:01:49 +0200 Subject: [PATCH 2/2] Refactor booking notification and new user handling code --- .../src/medsyncapp/tgbot/handlers/__init__.py | 2 + .../medsyncapp/tgbot/handlers/new_users.py | 2 - .../src/medsyncapp/webhook/routers/booking.py | 76 ++++++++++++------- 3 files changed, 49 insertions(+), 31 deletions(-) diff --git a/backend/src/medsyncapp/tgbot/handlers/__init__.py b/backend/src/medsyncapp/tgbot/handlers/__init__.py index bb0e0b4..1f17f88 100644 --- a/backend/src/medsyncapp/tgbot/handlers/__init__.py +++ b/backend/src/medsyncapp/tgbot/handlers/__init__.py @@ -1,5 +1,6 @@ """Import all routers and add them to routers_list.""" from .bookings import profile_router +from .new_users import new_users_router from .start import start_router from .test_results import test_results_router from .web_app import web_app_router @@ -8,6 +9,7 @@ start_router, profile_router, test_results_router, + new_users_router, web_app_router, ] diff --git a/backend/src/medsyncapp/tgbot/handlers/new_users.py b/backend/src/medsyncapp/tgbot/handlers/new_users.py index 9fed776..061b2d7 100644 --- a/backend/src/medsyncapp/tgbot/handlers/new_users.py +++ b/backend/src/medsyncapp/tgbot/handlers/new_users.py @@ -8,8 +8,6 @@ new_users_router = Router() - - @new_users_router.my_chat_member(ChatMemberUpdatedFilter(JOIN_TRANSITION)) async def new_user(cmu: types.ChatMemberUpdated, config: Config, bot: Bot): await broadcast(bot, config.tg_bot.admin_ids, track_user_join_text(cmu.from_user)) diff --git a/backend/src/medsyncapp/webhook/routers/booking.py b/backend/src/medsyncapp/webhook/routers/booking.py index d0bce96..98ab228 100644 --- a/backend/src/medsyncapp/webhook/routers/booking.py +++ b/backend/src/medsyncapp/webhook/routers/booking.py @@ -81,6 +81,39 @@ async def book_slot(request: Request, repo: RequestsRepo = Depends(get_repo)): parsed_data = parse_init_data(init_data) result = await book_slot_endpoint(data, "doctor", repo, parsed_data) + await notify_booking( + init_data=init_data, + repo=repo, + result=result, + parsed_data=parsed_data, + ) + + return result + + +async def log_notify_admins( + username: str, user_id: int, first_name: str, last_name: str +): + for admin_id in config.tg_bot.admin_ids: + with suppress(): + await bot.send_message( + chat_id=admin_id, + text=track_user_booked_text( + username=username, + user_id=user_id, + first_name=first_name, + last_name=last_name, + ), + parse_mode="HTML", + ) + + +async def notify_booking( + init_data: str, + repo: RequestsRepo, + result: dict, + parsed_data: dict = None, +): if init_data: user_info = parsed_data.get("user") if user_info: @@ -90,28 +123,20 @@ async def book_slot(request: Request, repo: RequestsRepo = Depends(get_repo)): first_name = user_info.get("first_name") last_name = user_info.get("last_name") notification = await get_booking_notification_text( - repo, result["booking_id"] - ) + repo, result["booking_id"] + ) + await log_notify_admins( + username=username, + user_id=user_id, + first_name=first_name, + last_name=last_name, + ) with suppress(): await bot.send_message( chat_id=user_id, text=notification, parse_mode="HTML", ) - for admin_id in config.tg_bot.admin_ids: - with suppress(): - await bot.send_message( - chat_id=admin_id, - text=track_user_booked_text( - username=username, - user_id=user_id, - first_name=first_name, - last_name=last_name, - ), - parse_mode="HTML", - ) - - return result @diagnostics_router.post("/book_slot") @@ -129,18 +154,11 @@ async def book_slot(request: Request, repo: RequestsRepo = Depends(get_repo)): result["booking_id"], diagnostic_id=data["diagnostic_id"] ) - if init_data: - user_info = parsed_data.get("user") - if user_info: - user_id = json.loads(user_info).get("id") - with suppress(): - notification = await get_booking_notification_text( - repo, result["booking_id"] - ) - await bot.send_message( - chat_id=user_id, - text=notification, - parse_mode="HTML", - ) + await notify_booking( + init_data=init_data, + repo=repo, + result=result, + parsed_data=parsed_data, + ) return result