chat.py 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. from fastapi import APIRouter, WebSocket, WebSocketDisconnect, Depends, Query, HTTPException, File, UploadFile, Form
  2. from services.chat_manager import manager
  3. from services.global_manager import global_manager
  4. import db
  5. import auth_utils
  6. import datetime
  7. import schemas
  8. import locales
  9. from dependencies import get_current_user
  10. import os
  11. import uuid
  12. import config
  13. import json
  14. from typing import Optional
  15. router = APIRouter(tags=["chat"])
  16. # In-memory storage for flood control: {user_id: timestamp}
  17. last_message_times = {}
  18. @router.get("/orders/{order_id}/messages")
  19. async def get_order_messages(order_id: int, user: dict = Depends(get_current_user)):
  20. role = user.get("role")
  21. user_id = user.get("id")
  22. # Fetch user chat status
  23. user_info = db.execute_query("SELECT can_chat FROM users WHERE id = %s", (user_id,))
  24. can_chat = user_info[0]['can_chat'] if user_info else False
  25. order = db.execute_query("SELECT user_id FROM orders WHERE id = %s", (order_id,))
  26. if not order: raise HTTPException(status_code=404, detail="Order not found")
  27. if role != 'admin':
  28. if order[0]['user_id'] != user_id: raise HTTPException(status_code=403, detail="Not authorized")
  29. if not can_chat: raise HTTPException(status_code=403, detail="Chat access disabled for your account")
  30. query = """
  31. SELECT m.id, m.is_from_admin, m.message, m.created_at, u.first_name, u.email
  32. FROM order_messages m
  33. LEFT JOIN users u ON m.user_id = u.id
  34. WHERE m.order_id = %s AND m.deleted = FALSE
  35. ORDER BY m.created_at ASC
  36. """
  37. messages = db.execute_query(query, (order_id,))
  38. for msg in messages:
  39. if msg.get('created_at'): msg['created_at'] = msg['created_at'].isoformat()
  40. msg['is_from_admin'] = bool(msg['is_from_admin'])
  41. # Mark messages as read
  42. if role == 'admin':
  43. db.execute_commit("UPDATE order_messages SET is_read = TRUE WHERE order_id = %s AND is_from_admin = FALSE AND is_read = FALSE", (order_id,))
  44. await global_manager.notify_admins()
  45. await global_manager.notify_order_read(order_id)
  46. else:
  47. db.execute_commit("UPDATE order_messages SET is_read = TRUE WHERE order_id = %s AND is_from_admin = TRUE AND is_read = FALSE", (order_id,))
  48. await global_manager.notify_user(user_id)
  49. return messages
  50. @router.post("/orders/{order_id}/messages")
  51. async def post_order_message(
  52. order_id: int,
  53. message: Optional[str] = Form(None),
  54. file: Optional[UploadFile] = File(None),
  55. user: dict = Depends(get_current_user),
  56. lang: str = "en"
  57. ):
  58. role = user.get("role")
  59. user_id = user.get("id")
  60. # Flood control for non-admin users
  61. if role != 'admin':
  62. now = datetime.datetime.utcnow().timestamp()
  63. last_time = last_message_times.get(user_id, 0)
  64. if now - last_time < 10:
  65. raise HTTPException(status_code=429, detail=locales.translate_error("flood_control", lang))
  66. last_message_times[user_id] = now
  67. if not message and not file:
  68. raise HTTPException(status_code=400, detail="Empty message")
  69. if message:
  70. message = message.strip()
  71. is_admin = (role == 'admin')
  72. if not is_admin:
  73. user_info = db.execute_query("SELECT can_chat FROM users WHERE id = %s", (user_id,))
  74. if not user_info or not user_info[0]['can_chat']:
  75. raise HTTPException(status_code=403, detail="Chat access disabled")
  76. order = db.execute_query("SELECT user_id FROM orders WHERE id = %s", (order_id,))
  77. if not order: raise HTTPException(status_code=404, detail="Order not found")
  78. if not is_admin and order[0]['user_id'] != user_id: raise HTTPException(status_code=403, detail="Not authorized")
  79. final_message = ""
  80. db_file_path = None
  81. if file and file.filename:
  82. file_ext = os.path.splitext(file.filename)[1]
  83. unique_filename = f"{uuid.uuid4()}{file_ext}"
  84. file_path = os.path.join(config.CHAT_UPLOADS_DIR, unique_filename)
  85. db_file_path = f"uploads/chats/{unique_filename}"
  86. with open(file_path, "wb") as buffer:
  87. while chunk := file.file.read(8192):
  88. buffer.write(chunk)
  89. msg_obj = {}
  90. if db_file_path:
  91. msg_obj["image_url"] = db_file_path
  92. if message:
  93. msg_obj["text"] = message
  94. final_message = json.dumps(msg_obj)
  95. query = "INSERT INTO order_messages (order_id, user_id, is_from_admin, message) VALUES (%s, %s, %s, %s)"
  96. msg_id = db.execute_commit(query, (order_id, user_id, is_admin, final_message))
  97. now = datetime.datetime.utcnow().isoformat()
  98. user_info = db.execute_query("SELECT first_name, email FROM users WHERE id = %s", (user_id,))
  99. first_name = user_info[0]['first_name'] if user_info else None
  100. email = user_info[0]['email'] if user_info else None
  101. await manager.broadcast_to_order(order_id, {
  102. "id": msg_id,
  103. "is_from_admin": is_admin,
  104. "message": final_message,
  105. "created_at": now,
  106. "first_name": first_name,
  107. "email": email
  108. })
  109. if is_admin:
  110. await global_manager.notify_user(order[0]['user_id'])
  111. else:
  112. from services import event_hooks
  113. await global_manager.notify_admins()
  114. await global_manager.notify_admins_new_message(order_id, message or "Image uploaded")
  115. event_hooks.on_message_received(order_id, user_id, message or "Image uploaded", db_file_path)
  116. return {
  117. "id": msg_id,
  118. "status": "sent",
  119. "message": final_message,
  120. "first_name": first_name,
  121. "email": email
  122. }
  123. @router.delete("/orders/{order_id}/messages/{msg_id}")
  124. async def delete_order_message(order_id: int, msg_id: int, user: dict = Depends(get_current_user)):
  125. user_id = user.get("id")
  126. role = user.get("role")
  127. is_admin = (role == 'admin')
  128. # Check order access
  129. order = db.execute_query("SELECT user_id FROM orders WHERE id = %s", (order_id,))
  130. if not order:
  131. raise HTTPException(status_code=404, detail="Order not found")
  132. if not is_admin and order[0]['user_id'] != user_id:
  133. raise HTTPException(status_code=403, detail="Not authorized")
  134. # Fetch the message
  135. msg = db.execute_query("SELECT * FROM order_messages WHERE id = %s AND order_id = %s", (msg_id, order_id))
  136. if not msg:
  137. raise HTTPException(status_code=404, detail="Message not found")
  138. # Only admin or the sender can delete the message
  139. if not is_admin and msg[0]['user_id'] != user_id:
  140. raise HTTPException(status_code=403, detail="Not authorized to delete this message")
  141. # Soft delete message from database
  142. db.execute_commit("UPDATE order_messages SET deleted = TRUE WHERE id = %s", (msg_id,))
  143. # Broadcast deletion
  144. await manager.broadcast_to_order(order_id, {
  145. "id": msg_id,
  146. "type": "message_deleted"
  147. })
  148. return {"message": "Deleted successfully"}