diff --git a/.deploy/deploy-dev.yaml b/.deploy/deploy-dev.yaml index 4c9209f..b7893f6 100644 --- a/.deploy/deploy-dev.yaml +++ b/.deploy/deploy-dev.yaml @@ -356,6 +356,50 @@ services: parallelism: 1 order: start-first + telegram_sender: + image: mathwave/sprint-repo:sprint + networks: + - net + command: ./manage.py telegram_sender + environment: + DB_HOST: "postgres" + FS_HOST: "storage" + RABBIT_HOST: "rabbitmq" + REDIS_HOST: "redis" + DB_PASSWORD: $DB_PASSWORD + DEBUG: $DEBUG + TELEGRAM_TOKEN: $TELEGRAM_TOKEN + deploy: + mode: replicated + replicas: 1 + restart_policy: + condition: any + update_config: + parallelism: 1 + order: start-first + + email_sender: + image: mathwave/sprint-repo:sprint + networks: + - net + command: ./manage.py email_sender + environment: + DB_HOST: "postgres" + FS_HOST: "storage" + RABBIT_HOST: "rabbitmq" + REDIS_HOST: "redis" + DB_PASSWORD: $DB_PASSWORD + DEBUG: $DEBUG + EMAIL_PASSWORD: $EMAIL_PASSWORD + deploy: + mode: replicated + replicas: 1 + restart_policy: + condition: any + update_config: + parallelism: 1 + order: start-first + apply-languages: image: mathwave/sprint-repo:sprint networks: diff --git a/.deploy/deploy-prod.yaml b/.deploy/deploy-prod.yaml index acc060f..ffca88a 100644 --- a/.deploy/deploy-prod.yaml +++ b/.deploy/deploy-prod.yaml @@ -360,6 +360,50 @@ services: parallelism: 1 order: start-first + telegram_sender: + image: mathwave/sprint-repo:sprint + networks: + - net + command: ./manage.py telegram_sender + environment: + DB_HOST: "postgres" + FS_HOST: "storage" + RABBIT_HOST: "rabbitmq" + REDIS_HOST: "redis" + DB_PASSWORD: $DB_PASSWORD + DEBUG: $DEBUG + TELEGRAM_TOKEN: $TELEGRAM_TOKEN + deploy: + mode: replicated + replicas: 1 + restart_policy: + condition: any + update_config: + parallelism: 1 + order: start-first + + email_sender: + image: mathwave/sprint-repo:sprint + networks: + - net + command: ./manage.py email_sender + environment: + DB_HOST: "postgres" + FS_HOST: "storage" + RABBIT_HOST: "rabbitmq" + REDIS_HOST: "redis" + DB_PASSWORD: $DB_PASSWORD + DEBUG: $DEBUG + EMAIL_PASSWORD: $EMAIL_PASSWORD + deploy: + mode: replicated + replicas: 2 + restart_policy: + condition: any + update_config: + parallelism: 1 + order: start-first + apply-languages: image: mathwave/sprint-repo:sprint networks: diff --git a/Checker/views.py b/Checker/views.py index 9cfaa71..b0dfa6b 100644 --- a/Checker/views.py +++ b/Checker/views.py @@ -10,8 +10,8 @@ from django.utils import timezone from Checker.models import Checker from FileStorage.sync import synchronized_method from Main.models import Solution, SolutionFile, ExtraFile, Progress +from SprintLib.queue import send_to_queue from SprintLib.utils import generate_token -from SprintLib.queue import notify as notification def get_dynamic(request): @@ -92,13 +92,10 @@ def notify(request): solution = Solution.objects.get(id=request.GET['solution_id']) if checker.set != solution.set: return JsonResponse({"status": "incorrect solution"}, status=403) - notification( - solution.user, - "solution_result", - f"Задача: {solution.task.name}\n" - f"Результат: {solution.result}\n" - f"Очки решения: {Progress.by_solution(solution).score}\n" - f"Текущий рейтинг: {solution.user.userinfo.rating}") + send_to_queue("notification", { + "type": "solution", + "solution_id": solution.id + }) return JsonResponse({"status": True}) except ObjectDoesNotExist: return JsonResponse({"status": "incorrect token"}, status=403) diff --git a/Main/views/AccountView.py b/Main/views/AccountView.py index c535612..6bf5e4b 100644 --- a/Main/views/AccountView.py +++ b/Main/views/AccountView.py @@ -1,10 +1,10 @@ from django.contrib.auth.models import User from django.db.models import Q -from SprintLib.queue import notify from Main.models import Friendship from SprintLib.BaseView import BaseView from SprintLib.language import languages +from SprintLib.queue import send_to_queue from SprintLib.utils import delete_file, write_bytes @@ -46,17 +46,26 @@ class AccountView(BaseView): ).first() if friendship is None: Friendship.objects.create(from_user=self.request.user, to_user=self.context["account"]) - notify(self.context['account'], 'friends', f"Пользователь {self.request.user.username} хочет добавить тебя в друзья") + send_to_queue("notification", { + "type": "friends_add", + "from_user": self.request.user.id, + "to_user": self.context["account"].id + }) elif friendship.verified or friendship.from_user == self.request.user: friendship.delete() else: if self.request.POST["to_do"] == "yes": friendship.verified = True friendship.save() - notify(self.context['account'], 'friends', f"Пользователь {self.request.user.username} добавил тебя в друзья") else: friendship.delete() - notify(self.context['account'], 'friends', f"Пользователь {self.request.user.username} отклонил твою заявку") + send_to_queue("notification", { + "type": "friends_accept", + "from_user": self.request.user.id, + "to_user": self.context['account'].id, + "accepted": self.request.POST['to_do'] == 'yes' + }) + return "/account?username=" + self.request.GET["username"] def post_upload_photo(self): diff --git a/Main/views/SendCodeView.py b/Main/views/SendCodeView.py index 5365b5e..3a6c3b6 100644 --- a/Main/views/SendCodeView.py +++ b/Main/views/SendCodeView.py @@ -5,7 +5,7 @@ from django.contrib.auth.models import User from Sprint import settings from SprintLib.BaseView import BaseView -from SprintLib.queue import notify +from SprintLib.queue import send_to_queue class SendCodeView(BaseView): @@ -23,7 +23,10 @@ class SendCodeView(BaseView): print(f"Отправлен код для {username}", code) user.userinfo.code = code user.userinfo.save() - notify(user, "any", "Код для входа в сервис: " + str(code)) + send_to_queue("telegram", { + "chat_id": user.userinfo.telegram_chat_id, + "text": "Код для входа в сервис: " + str(code) + }) return {"success": True, "message": "Код отправлен"} def post_check(self): diff --git a/Sprint/settings.py b/Sprint/settings.py index be2671c..24bdc99 100644 --- a/Sprint/settings.py +++ b/Sprint/settings.py @@ -147,6 +147,14 @@ RABBIT_PORT = 5672 FS_HOST = "http://" + os.getenv("FS_HOST", "127.0.0.1") FS_PORT = 5555 +EMAIL_BACKEND = "django.core.mail.backends.smtp.EmailBackend" +EMAIL_HOST = "smtp.yandex.ru" +EMAIL_HOST_USER = "sprint.no-reply@yandex.ru" +EMAIL_HOST_PASSWORD = os.getenv("EMAIL_PASSWORD") +EMAIL_PORT = 587 +EMAIL_USE_TLS = True +EMAIL_USE_SSL = False + # Authentication backends AUTHENTICATION_BACKENDS = ("django.contrib.auth.backends.ModelBackend",) diff --git a/SprintLib/queue.py b/SprintLib/queue.py index 65a08dc..31911a2 100644 --- a/SprintLib/queue.py +++ b/SprintLib/queue.py @@ -56,11 +56,3 @@ class MessagingSupport(BaseCommand): channel.start_consuming() except AMQPConnectorException: print("connection to rabbit failed: reconnecting") - - -def notify(user: User, notification_type: str, text: str): - send_to_queue("notifications", { - 'user_id': user.id, - 'type': notification_type, - 'text': text, - }) diff --git a/SprintLib/testers/BaseTester.py b/SprintLib/testers/BaseTester.py index 320c9c3..baf3b66 100644 --- a/SprintLib/testers/BaseTester.py +++ b/SprintLib/testers/BaseTester.py @@ -5,7 +5,7 @@ from tempfile import TemporaryDirectory from Main.models.progress import Progress from Sprint.settings import CONSTS -from SprintLib.queue import notify, send_to_queue +from SprintLib.queue import send_to_queue from SprintLib.utils import Timer @@ -111,14 +111,10 @@ class BaseTester: def notify(self): self.solution.user.userinfo.refresh_from_db() - notify( - self.solution.user, - "solution_result", - f"Задача: {self.solution.task.name}\n" - f"Результат: {self.solution.result}\n" - f"Очки решения: {Progress.by_solution(self.solution).score}\n" - f"Текущий рейтинг: {self.solution.user.userinfo.rating}", - ) + send_to_queue("notification", { + "type": "solution", + "solution_id": self.solution.id + }) def cleanup(self): self.solution.save() diff --git a/daemons/management/commands/email_sender.py b/daemons/management/commands/email_sender.py new file mode 100644 index 0000000..7d922cf --- /dev/null +++ b/daemons/management/commands/email_sender.py @@ -0,0 +1,20 @@ +from django.core.mail import send_mail + +from Sprint.settings import EMAIL_HOST_USER +from SprintLib.queue import MessagingSupport + + +class Command(MessagingSupport): + help = "starts file email sender" + queue_name = "email" + + def process(self, payload: dict): + subject = payload['subject'] + message = payload['message'] + email = payload['email'] + send_mail( + subject, + message, + EMAIL_HOST_USER, + [email] + ) diff --git a/daemons/management/commands/notification_manager.py b/daemons/management/commands/notification_manager.py index 7f227ca..b5844af 100644 --- a/daemons/management/commands/notification_manager.py +++ b/daemons/management/commands/notification_manager.py @@ -1,20 +1,53 @@ from django.contrib.auth.models import User -from SprintLib.queue import MessagingSupport -from daemons.management.commands.bot import bot +from Main.models import Solution, Progress +from SprintLib.queue import MessagingSupport, send_to_queue class Command(MessagingSupport): help = "starts file notification manager" - queue_name = "notifications" + queue_name = "notification" + + def handle_solution(self, payload): + solution = Solution.objects.get(id=payload["solution_id"]) + user = solution.user + if user.userinfo.notification_solution_result: + message = f"Задача: {solution.task.name}\n" \ + f"Результат: {solution.result}\n" \ + f"Очки решения: {Progress.by_solution(solution).score}\n" \ + f"Текущий рейтинг: {solution.user.userinfo.rating}" + if user.userinfo.telegram_chat_id is not None: + yield "telegram", {"chat_id": user.userinfo.telegram_chat_id, "text": message} + if user.email: + yield "email", {"subject": "Тестирование завершено", "message": message, "email": user.email} + + def handle_friends_add(self, payload): + user = User.objects.get(id=payload['to_user_id']) + from_user = User.objects.get(id=payload['from_user_id']) + if user.userinfo.notification_friends: + message = f"Пользователь {from_user.username} хочет добавить тебя в друзья" + if user.userinfo.telegram_chat_id: + yield "telegram", {"chat_id": user.userinfo.telegram_chat_id, "text": message} + if user.email: + yield "email", {"subject": "Новая заявка в друзья", "message": message, "email": user.email} + + def handle_friends_accept(self, payload): + user = User.objects.get(id=payload['to_user_id']) + from_user = User.objects.get(id=payload['from_user_id']) + if user.userinfo.notification_friends: + if payload['accepted']: + message = f"Пользователь {from_user} одобрил заявку в друзья" + else: + message = f"Пользователь {from_user} отклонил заявку в друзья" + if user.userinfo.telegram_chat_id: + yield "telegram", {"chat_id": user.userinfo.telegram_chat_id, "text": message} + if user.email: + yield "email", {"subject": "Новая заявка в друзья", "message": message, "email": user.email} def process(self, payload: dict): - user = User.objects.get(id=payload['user_id']) - notification_type = payload['type'] - text = payload['text'] - if notification_type == "any" or getattr(user.userinfo, "notification_" + notification_type): - bot.send_message( - user.userinfo.telegram_chat_id, - text, - parse_mode="html", - ) + notification_type = payload["type"] + handler = getattr(self, "handle_" + notification_type, None) + if handler is None: + raise ValueError(f"Unknown type: {notification_type}") + for queue, payload in handler(payload): + send_to_queue(queue, payload) diff --git a/daemons/management/commands/telegram_sender.py b/daemons/management/commands/telegram_sender.py new file mode 100644 index 0000000..8064cd8 --- /dev/null +++ b/daemons/management/commands/telegram_sender.py @@ -0,0 +1,16 @@ +from SprintLib.queue import MessagingSupport +from daemons.management.commands.bot import bot + + +class Command(MessagingSupport): + help = "starts file telegram sender" + queue_name = "telegram" + + def process(self, payload: dict): + chat_id = payload['chat_id'] + text = payload['text'] + bot.send_message( + int(chat_id), + text, + parse_mode="html", + )