Telezab/backend_flask.py

349 lines
19 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import logging
import sqlite3
import telebot
from flask import Flask, request, jsonify, render_template
import backend_bot
import bot_database
import telezab
import utils
from backend_locks import db_lock
from config import BASE_URL, DB_PATH
from utils 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)
# Настройка уровня логирования для 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 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} (hash={event_hash}, delivered={False})")
cursor.execute(query, (event_hash, 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.user_state_manager.set_state(chat_id, "MAIN_MENU")
# DEBUG: Показ основного меню пользователю
app.logger.debug(f"Отображение основного меню для пользователя с chat_id {chat_id}")
utils.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 + '/users', methods=['GET'])
def view_users():
return render_template('users.html')
@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