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
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)
|