import logging import sqlite3 import telebot from flask import Flask, request, jsonify, render_template, flash, redirect, url_for from flask_ldap3_login.forms import LDAPLoginForm from flask_login import login_manager, login_user, logout_user, UserMixin from frontend.dashboard import bp_dashboard, bp_api import backend_bot import bot_database import telezab import utilities.telegram_utilities as telegram_util from backend_locks import db_lock from config import BASE_URL, DB_PATH from utilities.telegram_utilities import extract_region_number, format_message, validate_chat_id, validate_telegram_id, validate_email app = Flask(__name__, static_url_path='/telezab/static', template_folder='templates') # app.register_blueprint(webui) app.secret_key = "supersecretkey" app.register_blueprint(bp_dashboard) app.register_blueprint(bp_api) # # # Инициализация менеджеров # ldap_manager = LDAP3LoginManager(app) # login_manager = LoginManager(app) # login_manager.login_view = "login" # Пользовательский класс class User(UserMixin): def __init__(self, dn, username): self.id = dn self.username = username # Настройка уровня логирования для Flask app.logger.setLevel(logging.INFO) @app.route(BASE_URL + '/webhook', methods=['POST']) def webhook(): try: # Получаем данные и логируем data = request.get_json() app.logger.info(f"Получены данные: {data}") # # Генерация хеша события и логирование # event_hash = bot_database.hash_data(data) # app.logger.debug(f"Сгенерирован хеш для события: {event_hash}") # Работа с базой данных в блоке синхронизации with db_lock: conn = sqlite3.connect(DB_PATH) cursor = conn.cursor() # Проверяем количество записей в таблице событий cursor.execute('SELECT COUNT(*) FROM events') count = cursor.fetchone()[0] app.logger.debug(f"Текущее количество записей в таблице events: {count}") # Если записей >= 200, удаляем самое старое событие if count >= 200: query = 'DELETE FROM events WHERE id = (SELECT MIN(id) FROM events)' app.logger.debug(f"Удаление старого события: {query}") cursor.execute(query) # Извлечение номера региона из поля host region_id = extract_region_number(data.get("host")) if region_id is None: app.logger.error(f"Не удалось извлечь номер региона из host: {data.get('host')}") return jsonify({"status": "error", "message": "Invalid host format"}), 400 app.logger.debug(f"Извлечён номер региона: {region_id}") # Запрос подписчиков для отправки уведомления в зависимости от уровня критичности if data['severity'] == 'Disaster': # Авария query = 'SELECT chat_id, username FROM subscriptions WHERE region_id = ? AND active = TRUE' else: # Высокая критичность query = 'SELECT chat_id, username FROM subscriptions WHERE region_id = ? AND active = TRUE AND disaster_only = FALSE' app.logger.debug(f"Выполнение запроса: {query} для region_id={region_id}") cursor.execute(query, (region_id,)) results = cursor.fetchall() app.logger.debug(f"Найдено подписчиков: {len(results)} для региона {region_id}") # Проверка статуса региона (активен или нет) query = 'SELECT active FROM regions WHERE region_id = ?' cursor.execute(query, (region_id,)) region_row = cursor.fetchone() if region_row and region_row[0]: # Если регион активен app.logger.debug(f"Регион {region_id} активен. Начинаем рассылку сообщений.") message = format_message(data) undelivered = False # Отправляем сообщения подписчикам for chat_id, username in results: formatted_message = message.replace('\n', ' ').replace('\r', '') app.logger.info( f"Формирование сообщения для пользователя {username} (chat_id={chat_id}) [{formatted_message}]") try: from utilities.rabbitmq import send_to_queue send_to_queue({'chat_id': chat_id, 'username': username, 'message': message}) app.logger.debug(f"Сообщение поставлено в очередь для {chat_id} (@{username})") except Exception as e: app.logger.error(f"Ошибка при отправке сообщения для {chat_id} (@{username}): {e}") undelivered = True # Сохранение события, если были проблемы с доставкой if undelivered: query = 'INSERT OR IGNORE INTO events (hash, data, delivered) VALUES (?, ?, ?)' app.logger.debug( f"Сохранение события в базе данных: {query} (delivered={False})") cursor.execute(query, (str(data), False)) # Коммитим изменения в базе данных conn.commit() app.logger.debug("Изменения в базе данных успешно сохранены.") conn.close() # Возвращаем успешный ответ return jsonify({"status": "success"}), 200 except sqlite3.OperationalError as e: app.logger.error(f"Ошибка операции с базой данных: {e}") return jsonify({"status": "error", "message": "Ошибка работы с базой данных"}), 500 except ValueError as e: app.logger.error(f"Ошибка значения: {e}") return jsonify({"status": "error", "message": "Некорректные данные"}), 400 except Exception as e: app.logger.error(f"Неожиданная ошибка: {e}") return jsonify({"status": "error", "message": "Внутренняя ошибка сервера"}), 500 @app.route(BASE_URL + '/users/add', methods=['POST']) def add_user(): data = request.get_json() telegram_id = data.get('telegram_id') chat_id = data.get('chat_id') user_email = data.get('user_email') # DEBUG: Логирование полученных данных app.logger.debug(f"Получены данные для добавления пользователя: {data}") # Валидация данных if not validate_chat_id(chat_id): app.logger.warning(f"Ошибка валидации: некорректный chat_id: {chat_id}") return jsonify({"status": "failure", "reason": "Invalid data chat_id must be digit"}), 400 if not validate_telegram_id(telegram_id): app.logger.warning(f"Ошибка валидации: некорректный telegram_id: {telegram_id}") return jsonify({"status": "failure", "reason": "Invalid data telegram id must start from '@'"}), 400 if not validate_email(user_email): app.logger.warning(f"Ошибка валидации: некорректный email: {user_email}") return jsonify({"status": "failure", "reason": "Invalid data email address must be from rtmis"}), 400 if telegram_id and chat_id and user_email: try: # INFO: Попытка отправить сообщение пользователю app.logger.info(f"Отправка сообщения пользователю {telegram_id} с chat_id {chat_id}") backend_bot.bot.send_message(chat_id, "Регистрация пройдена успешно.") # DEBUG: Попытка добавления пользователя в whitelist app.logger.debug(f"Добавление пользователя {telegram_id} в whitelist") success = bot_database.rundeck_add_to_whitelist(chat_id, telegram_id, user_email) if success: # INFO: Пользователь успешно добавлен в whitelist app.logger.info(f"Пользователь {telegram_id} добавлен в whitelist.") telezab.state.set_state(chat_id, "MAIN_MENU") # DEBUG: Показ основного меню пользователю app.logger.debug(f"Отображение основного меню для пользователя с chat_id {chat_id}") telegram_util.show_main_menu(chat_id) return jsonify( {"status": "success", "msg": f"User {telegram_id} with {user_email} added successfully"}), 200 else: # INFO: Пользователь уже существует в системе app.logger.info(f"Пользователь с chat_id {chat_id} уже существует.") return jsonify({"status": "failure", "msg": "User already exists"}), 400 except telebot.apihelper.ApiTelegramException as e: if e.result.status_code == 403: # INFO: Пользователь заблокировал бота app.logger.info(f"Пользователь {telegram_id} заблокировал бота") return jsonify({"status": "failure", "msg": f"User {telegram_id} is blocked chat with bot"}) elif e.result.status_code == 400: # WARNING: Пользователь неизвестен боту, возможно не нажал /start app.logger.warning( f"Пользователь {telegram_id} с chat_id {chat_id} неизвестен боту, возможно, не нажал /start") return jsonify({"status": "failure", "msg": f"User {telegram_id} with {chat_id} is unknown to the bot, did the user press /start button?"}) else: # ERROR: Неизвестная ошибка при отправке сообщения app.logger.error(f"Ошибка при отправке сообщения пользователю {telegram_id}: {str(e)}") return jsonify({"status": "failure", "msg": f"{e}"}) else: # ERROR: Ошибка валидации — недостаточно данных app.logger.error("Получены некорректные данные для добавления пользователя.") return jsonify({"status": "failure", "reason": "Invalid data"}), 400 @app.route(BASE_URL + '/users/del', methods=['POST']) def delete_user(): data = request.get_json() user_email = data.get('email') conn = sqlite3.connect(DB_PATH) try: # DEBUG: Получен запрос и начинается обработка app.logger.debug(f"Получен запрос на удаление пользователя. Данные: {data}") if not user_email: # WARNING: Ошибка валидации данных, email отсутствует app.logger.warning(f"Ошибка валидации: отсутствует email") return jsonify({"status": "failure", "message": "Email is required"}), 400 cursor = conn.cursor() # DEBUG: Запрос на получение chat_id app.logger.debug(f"Выполняется запрос на получение chat_id для email: {user_email}") cursor.execute("SELECT chat_id FROM whitelist WHERE user_email = ?", (user_email,)) user = cursor.fetchone() if user is None: # WARNING: Пользователь с указанным email не найден app.logger.warning(f"Пользователь с email {user_email} не найден") return jsonify({"status": "failure", "message": "User not found"}), 404 chat_id = user[0] # INFO: Удаление пользователя и его подписок начато app.logger.info(f"Начато удаление пользователя с email {user_email} и всех его подписок") # DEBUG: Удаление пользователя из whitelist app.logger.debug(f"Удаление пользователя с email {user_email} из whitelist") cursor.execute("DELETE FROM whitelist WHERE user_email = ?", (user_email,)) # DEBUG: Удаление подписок пользователя app.logger.debug(f"Удаление подписок для пользователя с chat_id {chat_id}") cursor.execute("DELETE FROM subscriptions WHERE chat_id = ?", (chat_id,)) conn.commit() # INFO: Пользователь и подписки успешно удалены app.logger.info(f"Пользователь с email {user_email} и все его подписки успешно удалены") return jsonify( {"status": "success", "message": f"User with email {user_email} and all subscriptions deleted."}), 200 except Exception as e: conn.rollback() # ERROR: Ошибка при удалении данных app.logger.error(f"Ошибка при удалении пользователя с email {user_email}: {str(e)}") return jsonify({"status": "failure", "message": str(e)}), 500 finally: conn.close() # DEBUG: Соединение с базой данных закрыто app.logger.debug(f"Соединение с базой данных закрыто") # @app.route(BASE_URL + '/users/get', methods=['GET']) # def get_users(): # try: # # INFO: Запрос на получение списка пользователей # app.logger.info("Запрос на получение информации о пользователях получен") # # with db_lock: # conn = sqlite3.connect(DB_PATH) # cursor = conn.cursor() # # # DEBUG: Запрос данных из таблицы whitelist # app.logger.debug("Запрос данных пользователей из таблицы whitelist") # cursor.execute('SELECT * FROM whitelist') # users = cursor.fetchall() # app.logger.debug("Формирование словаря пользователей") # users_dict = {user_id: {'id': user_id, 'username': username, 'email': email, 'events': [], 'worker': '', # 'subscriptions': []} # for user_id, username, email in users} # # # DEBUG: Запрос данных событий пользователей # app.logger.debug("Запрос событий пользователей из таблицы user_events") # cursor.execute('SELECT chat_id, username, action, timestamp FROM user_events') # events = cursor.fetchall() # # # DEBUG: Обработка событий и добавление их в словарь пользователей # for chat_id, username, action, timestamp in events: # if chat_id in users_dict: # event = {'type': action, 'date': timestamp} # if "Subscribed to region" in action: # region = action.split(": ")[-1] # event['region'] = region # users_dict[chat_id]['events'].append(event) # # # DEBUG: Запрос данных подписок пользователей # app.logger.debug("Запрос активных подписок пользователей из таблицы subscriptions") # cursor.execute('SELECT chat_id, region_id FROM subscriptions WHERE active = 1') # subscriptions = cursor.fetchall() # # # DEBUG: Добавление подписок к пользователям # for chat_id, region_id in subscriptions: # if chat_id in users_dict: # users_dict[chat_id]['subscriptions'].append(str(region_id)) # # # INFO: Формирование результата # app.logger.info("Формирование результата для ответа") # result = [] # for user in users_dict.values(): # ordered_user = { # 'email': user['email'], # 'username': user['username'], # 'id': user['id'], # 'worker': user['worker'], # 'events': user['events'], # 'subscriptions': ', '.join(user['subscriptions']) # } # result.append(ordered_user) # # # INFO: Успешная отправка данных пользователей # app.logger.info("Информация о пользователях успешно отправлена") # return jsonify(result) # # except Exception as e: # # ERROR: Ошибка при получении информации о пользователях # app.logger.error(f"Ошибка при получении информации о пользователях: {str(e)}") # return jsonify({'status': 'error', 'message': str(e)}), 500 @app.route(BASE_URL + '/debug/flask', methods=['POST']) def toggle_flask_debug(): try: data = request.get_json() level = data.get('level').upper() if level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']: return jsonify({'status': 'error', 'message': 'Invalid log level'}), 400 log_level = getattr(logging, level, logging.DEBUG) app.logger.setLevel(log_level) for handler in app.logger.handlers: handler.setLevel(log_level) return jsonify({'status': 'success', 'level': level}) except Exception as e: return jsonify({'status': 'error', 'message': str(e)}), 500 @app.route(BASE_URL + '/debug/telebot', methods=['POST']) def toggle_telebot_debug(): try: data = request.get_json() level = data.get('level').upper() if level not in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']: return jsonify({'status': 'error', 'message': 'Invalid log level'}), 400 log_level = getattr(logging, level, logging.DEBUG) telebot.logger.setLevel(log_level) for handler in telebot.logger.handlers: handler.setLevel(log_level) return jsonify({'status': 'success', 'level': level}) except Exception as e: return jsonify({'status': 'error', 'message': str(e)}), 500