audit_service.py 4.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107
  1. import db
  2. import json
  3. import redis
  4. import config
  5. from fastapi import Request
  6. from typing import Optional, Any
  7. class AuditService:
  8. def __init__(self):
  9. # Redis connection for telegram queue
  10. try:
  11. self.redis_client = redis.Redis(host='localhost', port=6379, decode_responses=True)
  12. except:
  13. self.redis_client = None
  14. def _format_details(self, details: Any) -> str:
  15. if not details:
  16. return ""
  17. if isinstance(details, dict):
  18. lines = []
  19. for k, v in details.items():
  20. lines.append(f"• <b>{k}</b>: {v}")
  21. return "\n" + "\n".join(lines)
  22. return str(details)
  23. def _get_readable_action(self, action: str) -> str:
  24. # Simple mapping for better readability
  25. mapping = {
  26. "warehouse_add_item": "📦 Добавление на склад",
  27. "warehouse_update_item": "📝 Обновление склада",
  28. "warehouse_delete_item": "🗑 Удаление со склада",
  29. "order_status_update": "🔔 Статус заказа изменен",
  30. "user_role_update": "👤 Смена роли пользователя",
  31. "user_register": "🆕 Новая регистрация",
  32. "user_login": "🔑 Вход в систему",
  33. "order_created": "🛒 Новый заказ оформлен",
  34. "order_review": "⭐ Оставлен отзыв",
  35. "update_order": "✏️ Заказ отредактирован",
  36. "delete_order_entirely": "🔥 Заказ полностью удален",
  37. "admin_create_user": "👤 Пользователь создан админом",
  38. "admin_update_user": "✏️ Пользователь отредактирован админом",
  39. "password_reset_success": "🔐 Пароль успешно сброшен",
  40. "upload_order_photo": "📸 Загружено фото заказа",
  41. "update_photo_visibility": "👁️ Изменена видимость фото",
  42. "delete_photo": "🗑️ Удалено фото из портфолио",
  43. "create_material": "🏗️ Создан новый материал",
  44. "update_material": "📝 Изменен материал",
  45. "delete_material": "❌ Удален материал",
  46. "create_service": "🛠️ Создана новая услуга",
  47. "update_service": "📝 Изменена услуга",
  48. "delete_service": "❌ Удалена услуга"
  49. }
  50. return mapping.get(action, action)
  51. async def log(
  52. self,
  53. user_id: int,
  54. action: str,
  55. target_type: Optional[str] = None,
  56. target_id: Optional[int] = None,
  57. details: Optional[Any] = None,
  58. request: Optional[Request] = None
  59. ):
  60. ip_address = None
  61. if request:
  62. ip_address = request.headers.get("X-Forwarded-For", request.client.host if request.client else None)
  63. details_str = None
  64. if details:
  65. if isinstance(details, (dict, list)):
  66. def decimal_default(obj):
  67. from decimal import Decimal
  68. if isinstance(obj, Decimal):
  69. return float(obj)
  70. return str(obj)
  71. details_str = json.dumps(details, ensure_ascii=False, default=decimal_default)
  72. else:
  73. details_str = str(details)
  74. # 1. Save to DB
  75. query = """
  76. INSERT INTO audit_logs (user_id, action, target_type, target_id, details, ip_address)
  77. VALUES (%s, %s, %s, %s, %s, %s)
  78. """
  79. db.execute_commit(query, (user_id, action, target_type, target_id, details_str, ip_address))
  80. # 2. Push to Telegram Queue if CHAT_ID is configured
  81. if config.TELEGRAM_CHAT_ID and self.redis_client:
  82. try:
  83. readable_action = self._get_readable_action(action)
  84. formatted_details = self._format_details(details)
  85. message = f"<b>Audit Log:</b> {readable_action}\n"
  86. if target_type:
  87. message += f"<b>Target:</b> {target_type} (ID: {target_id})\n"
  88. if formatted_details:
  89. message += f"<b>Details:</b>{formatted_details}"
  90. payload = {
  91. "id": config.TELEGRAM_CHAT_ID,
  92. "message": message
  93. }
  94. self.redis_client.rpush("messages_queue", json.dumps(payload, ensure_ascii=False))
  95. except Exception as e:
  96. print(f"FAILED TO PUSH TO TELEGRAM QUEUE: {e}")
  97. audit_service = AuditService()