You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

165 lines
6.6 KiB

import hashlib
import logging
from django.contrib.auth import get_user_model
from django.db import transaction
from rest_framework.authtoken.models import Token
from utils.ip_helper import get_client_ip
logger = logging.getLogger(__name__)
User = get_user_model()
def _extract_token_from_auth_header(request) -> str:
auth_header = (request.META.get('HTTP_AUTHORIZATION') or '').strip()
if not auth_header:
return ''
parts = auth_header.split()
if len(parts) == 2 and parts[0].lower() in ('token', 'bearer'):
return parts[1].strip()
return ''
def compute_web_guest_device_id(request) -> str:
x_forwarded_for = request.META.get('HTTP_X_FORWARDED_FOR')
if x_forwarded_for:
client_ip = x_forwarded_for.split(',')[0]
else:
client_ip = request.META.get('REMOTE_ADDR') or get_client_ip(request) or ''
user_agent = request.META.get('HTTP_USER_AGENT', '')
ua_hash = hashlib.md5(user_agent.encode('utf-8')).hexdigest()[:8]
return f"{client_ip}_{ua_hash}"
def resolve_guest_users_from_request(request, device_id: str = None) -> list:
"""
Finds any Guest User(s) (`email__isnull=True`) associated with the current request
via X-Guest-Token header, Authorization token header, explicit device_id, or Web IP+UA device_id.
Returns a deduplicated list of guest User instances in priority order.
"""
candidates = []
seen_ids = set()
def _add(u):
if u and getattr(u, 'email', None) is None and u.id not in seen_ids:
seen_ids.add(u.id)
candidates.append(u)
# 1. Explicit X-Guest-Token header
x_guest_key = (request.headers.get('X-Guest-Token') or '').strip()
if x_guest_key:
token_obj = Token.objects.select_related('user').filter(key=x_guest_key).first()
if token_obj:
_add(token_obj.user)
# 2. Authorization: token <guest_token> header (sent automatically by frontend before auth_token is set)
auth_key = _extract_token_from_auth_header(request)
if auth_key and auth_key != x_guest_key:
token_obj = Token.objects.select_related('user').filter(key=auth_key).first()
if token_obj:
_add(token_obj.user)
# 3. Explicit device_id parameter
if device_id:
guest_by_device = User.objects.filter(device_id=device_id, email__isnull=True).first()
_add(guest_by_device)
# 4. Web IP + User-Agent hash fallback (matches WebUserGuestView)
try:
web_device_id = compute_web_guest_device_id(request)
if web_device_id and web_device_id != device_id:
guest_by_web_id = User.objects.filter(device_id=web_device_id, email__isnull=True).first()
_add(guest_by_web_id)
except Exception as exc:
logger.debug("Could not compute web guest device_id fallback: %s", exc)
return candidates
@transaction.atomic
def merge_guest_user_data(guest_user, target_user) -> None:
"""
Merges all guest activity (SupportRoom, SupportMessage, Bookmark, Rate)
from `guest_user` into `target_user`.
Safe to call multiple times (idempotent).
"""
if not guest_user or not target_user or guest_user.id == target_user.id:
return
# 1. Merge Support Chat (SupportRoom & SupportMessage)
try:
from apps.support.models import SupportRoom, SupportMessage
guest_room = SupportRoom.objects.select_for_update().filter(user=guest_user).first()
if guest_room:
target_room = SupportRoom.objects.select_for_update().filter(user=target_user).first()
if not target_room:
guest_room.user = target_user
guest_room.save(update_fields=['user', 'updated_at'])
SupportMessage.objects.filter(room=guest_room, sender=guest_user).update(sender=target_user)
else:
# Both guest and target user already have support rooms: merge messages into target_room
SupportMessage.objects.filter(room=guest_room).update(room=target_room)
SupportMessage.objects.filter(room=target_room, sender=guest_user).update(sender=target_user)
target_room.unread_by_admin = (target_room.unread_by_admin or 0) + (guest_room.unread_by_admin or 0)
target_room.unread_by_user = (target_room.unread_by_user or 0) + (guest_room.unread_by_user or 0)
last_msg = target_room.messages.order_by('-id').first()
if last_msg:
target_room.last_message_at = last_msg.created_at
if last_msg.sender_type == SupportMessage.SenderType.USER:
target_room.status = SupportRoom.StatusChoices.PENDING
else:
target_room.status = SupportRoom.StatusChoices.ANSWERED
target_room.save(
update_fields=[
'unread_by_admin',
'unread_by_user',
'last_message_at',
'status',
'updated_at',
]
)
guest_room.delete()
except Exception as exc:
logger.error("Error merging support chat from guest %s to user %s: %s", guest_user.id, target_user.id, exc)
# 2. Merge Bookmarks
try:
from apps.bookmark.models import Bookmark
for bm in Bookmark.objects.filter(user=guest_user):
existing = Bookmark.objects.filter(
user=target_user,
service=bm.service,
content_id=bm.content_id,
).first()
if existing:
if bm.status and not existing.status:
existing.status = True
existing.save(update_fields=['status', 'updated_at'])
bm.delete()
else:
bm.user = target_user
bm.save(update_fields=['user', 'updated_at'])
except Exception as exc:
logger.error("Error merging bookmarks from guest %s to user %s: %s", guest_user.id, target_user.id, exc)
# 3. Merge Rates
try:
from apps.bookmark.models import Rate
for rt in Rate.objects.filter(user=guest_user):
existing = Rate.objects.filter(
user=target_user,
service=rt.service,
content_id=rt.content_id,
).first()
if existing:
rt.delete()
else:
rt.user = target_user
rt.save(update_fields=['user', 'updated_at'])
except Exception as exc:
logger.error("Error merging rates from guest %s to user %s: %s", guest_user.id, target_user.id, exc)