Explorar o código

feat: Enrich audit logs and order chat with user details

unknown hai 2 meses
pai
achega
4c4e3dadba

+ 5 - 1
backend/routers/admin.py

@@ -17,9 +17,13 @@ async def get_audit_logs(
     offset = (page - 1) * size
     
     query = """
-        SELECT a.*, u.email as user_email 
+        SELECT a.*, u.email as user_email,
+               u2.email as target_user_email,
+               o.first_name as target_order_name
         FROM audit_logs a
         LEFT JOIN users u ON a.user_id = u.id
+        LEFT JOIN users u2 ON (a.target_type = 'user' AND a.target_id = u2.id)
+        LEFT JOIN orders o ON (a.target_type = 'order' AND a.target_id = o.id)
     """
     params = []
     

+ 27 - 3
backend/routers/chat.py

@@ -31,7 +31,13 @@ async def get_order_messages(order_id: int, user: dict = Depends(get_current_use
     if role != 'admin':
         if order[0]['user_id'] != user_id: raise HTTPException(status_code=403, detail="Not authorized")
         if not can_chat: raise HTTPException(status_code=403, detail="Chat access disabled for your account")
-    messages = db.execute_query("SELECT id, is_from_admin, message, created_at FROM order_messages WHERE order_id = %s ORDER BY created_at ASC", (order_id,))
+    query = """
+        SELECT m.id, m.is_from_admin, m.message, m.created_at, u.first_name, u.email 
+        FROM order_messages m
+        LEFT JOIN users u ON m.user_id = u.id
+        WHERE m.order_id = %s ORDER BY m.created_at ASC
+    """
+    messages = db.execute_query(query, (order_id,))
     for msg in messages:
         if msg.get('created_at'): msg['created_at'] = msg['created_at'].isoformat()
         msg['is_from_admin'] = bool(msg['is_from_admin'])
@@ -107,7 +113,19 @@ async def post_order_message(
     query = "INSERT INTO order_messages (order_id, user_id, is_from_admin, message) VALUES (%s, %s, %s, %s)"
     msg_id = db.execute_commit(query, (order_id, user_id, is_admin, final_message))
     now = datetime.datetime.utcnow().isoformat()
-    await manager.broadcast_to_order(order_id, {"id": msg_id, "is_from_admin": is_admin, "message": final_message, "created_at": now})
+    
+    user_info = db.execute_query("SELECT first_name, email FROM users WHERE id = %s", (user_id,))
+    first_name = user_info[0]['first_name'] if user_info else None
+    email = user_info[0]['email'] if user_info else None
+    
+    await manager.broadcast_to_order(order_id, {
+        "id": msg_id, 
+        "is_from_admin": is_admin, 
+        "message": final_message, 
+        "created_at": now,
+        "first_name": first_name,
+        "email": email
+    })
     
     if is_admin:
         await global_manager.notify_user(order[0]['user_id'])
@@ -117,4 +135,10 @@ async def post_order_message(
         await global_manager.notify_admins_new_message(order_id, message or "Image uploaded")
         event_hooks.on_message_received(order_id, user_id, message or "Image uploaded")
         
-    return {"id": msg_id, "status": "sent", "message": final_message}
+    return {
+        "id": msg_id, 
+        "status": "sent", 
+        "message": final_message,
+        "first_name": first_name,
+        "email": email
+    }

+ 8 - 1
backend/services/chat_manager.py

@@ -73,12 +73,19 @@ class ChatConnectionManager:
         msg_id = db.execute_commit(query, (order_id, user_id, is_admin, json_text))
         
         now = datetime.datetime.utcnow().isoformat()
+        
+        user_info = db.execute_query("SELECT first_name, email FROM users WHERE id = %s", (user_id,))
+        first_name = user_info[0]['first_name'] if user_info else None
+        email = user_info[0]['email'] if user_info else None
+
         message_payload = {
             "id": msg_id,
             "is_from_admin": is_admin,
             "message": json_text,
             "created_at": now,
-            "type": "message"
+            "type": "message",
+            "first_name": first_name,
+            "email": email
         }
         
         # Broadcast to all in this chat

+ 8 - 1
src/components/OrderChat.vue

@@ -51,7 +51,14 @@
           >
             <div v-if="msg.is_from_admin" class="flex items-center gap-1.5 mb-1">
               <ShieldCheck class="w-3 h-3 opacity-70" />
-              <span class="text-[10px] font-bold uppercase tracking-wider opacity-70">{{ t("chat.admin") }}</span>
+              <span class="text-[10px] font-bold uppercase tracking-wider opacity-70">
+                {{ t("chat.admin") }}<span v-if="msg.first_name || msg.email" class="lowercase font-mono ml-1 text-[9px] opacity-70">({{ msg.first_name || msg.email.split('@')[0] }})</span>
+              </span>
+            </div>
+            <div v-else class="flex items-center justify-end gap-1.5 mb-1">
+              <span class="text-[10px] font-bold uppercase tracking-wider opacity-70">
+                <span v-if="msg.first_name || msg.email" class="lowercase font-mono mr-1 text-[9px] opacity-70">{{ msg.first_name || msg.email.split('@')[0] }}</span>
+              </span>
             </div>
             <div v-if="msg.imageUrl" class="mt-1 mb-1">
               <img :src="getImageUrl(msg.imageUrl)" @click="openImageModal(msg.imageUrl)" class="max-w-[150px] max-h-[150px] rounded-lg cursor-pointer hover:opacity-90 transition-opacity shadow-sm border border-border/20 object-cover" />

+ 2 - 0
src/components/admin/AuditSection.vue

@@ -40,6 +40,8 @@
               </td>
               <td class="p-4 text-xs font-bold uppercase tracking-tighter opacity-70">
                 {{ log.target_type }} <span class="text-[9px] font-mono opacity-50 ml-1">#{{ log.target_id }}</span>
+                <span v-if="log.target_user_email" class="lowercase text-primary block mt-1 tracking-normal font-mono text-[10px]">{{ log.target_user_email }}</span>
+                <span v-else-if="log.target_order_name" class="capitalize text-primary block mt-1 tracking-normal font-sans text-xs">{{ log.target_order_name }}</span>
               </td>
               <td class="p-4">
                 <div class="text-[10px] text-muted-foreground leading-relaxed max-w-sm font-mono opacity-80 break-words">