""" Background tasks for user management and notifications. """ import logging from celery import shared_task from django.core.mail import send_mail from django.template.loader import render_to_string from django.conf import settings from django.utils import timezone from datetime import timedelta logger = logging.getLogger(__name__) @shared_task(bind=True, max_retries=3, default_retry_delay=60) def send_welcome_email(self, user_id): """ Send a welcome email to a newly registered user. Args: user_id: ID of the User Returns: str: Email send result """ from apps.users.models import User try: user = User.objects.get(id=user_id) context = { 'user': user, 'site_url': getattr(settings, 'SITE_URL', 'https://thrillwiki.com'), } html_message = render_to_string('emails/welcome.html', context) send_mail( subject='Welcome to ThrillWiki! 🎢', message='', html_message=html_message, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[user.email], fail_silently=False, ) logger.info(f"Welcome email sent to {user.email}") return f"Welcome email sent to {user.email}" except User.DoesNotExist: logger.error(f"User {user_id} not found") raise except Exception as exc: logger.error(f"Error sending welcome email to user {user_id}: {str(exc)}") raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries)) @shared_task(bind=True, max_retries=3, default_retry_delay=60) def send_password_reset_email(self, user_id, token, reset_url): """ Send a password reset email with a secure token. Args: user_id: ID of the User token: Password reset token reset_url: Full URL for password reset Returns: str: Email send result """ from apps.users.models import User try: user = User.objects.get(id=user_id) context = { 'user': user, 'reset_url': reset_url, 'request_time': timezone.now(), 'expiry_hours': 24, # Configurable 'site_url': getattr(settings, 'SITE_URL', 'https://thrillwiki.com'), } html_message = render_to_string('emails/password_reset.html', context) send_mail( subject='Reset Your ThrillWiki Password', message='', html_message=html_message, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[user.email], fail_silently=False, ) logger.info(f"Password reset email sent to {user.email}") return f"Password reset email sent to {user.email}" except User.DoesNotExist: logger.error(f"User {user_id} not found") raise except Exception as exc: logger.error(f"Error sending password reset email: {str(exc)}") raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries)) @shared_task(bind=True, max_retries=2) def cleanup_expired_tokens(self): """ Clean up expired JWT tokens and password reset tokens. This task runs daily to remove old tokens from the database. Returns: dict: Cleanup statistics """ from rest_framework_simplejwt.token_blacklist.models import OutstandingToken from django.contrib.auth.tokens import default_token_generator try: # Clean up blacklisted JWT tokens older than 7 days cutoff = timezone.now() - timedelta(days=7) # Note: Actual implementation depends on token storage strategy # This is a placeholder for the concept logger.info("Token cleanup completed") return { 'jwt_tokens_cleaned': 0, 'reset_tokens_cleaned': 0, } except Exception as exc: logger.error(f"Error cleaning up tokens: {str(exc)}") raise self.retry(exc=exc, countdown=300) @shared_task(bind=True, max_retries=3) def send_account_notification(self, user_id, notification_type, context_data=None): """ Send a generic account notification email. Args: user_id: ID of the User notification_type: Type of notification (e.g., 'security_alert', 'profile_update') context_data: Additional context data for the email Returns: str: Email send result """ from apps.users.models import User try: user = User.objects.get(id=user_id) context = { 'user': user, 'notification_type': notification_type, 'site_url': getattr(settings, 'SITE_URL', 'https://thrillwiki.com'), } if context_data: context.update(context_data) # For now, just log (would need specific templates for each type) logger.info(f"Account notification ({notification_type}) for user {user.email}") return f"Notification sent to {user.email}" except User.DoesNotExist: logger.error(f"User {user_id} not found") raise except Exception as exc: logger.error(f"Error sending account notification: {str(exc)}") raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries)) @shared_task(bind=True, max_retries=2) def cleanup_inactive_users(self, days_inactive=365): """ Clean up or flag users who haven't logged in for a long time. Args: days_inactive: Number of days of inactivity before flagging (default: 365) Returns: dict: Cleanup statistics """ from apps.users.models import User try: cutoff = timezone.now() - timedelta(days=days_inactive) inactive_users = User.objects.filter( last_login__lt=cutoff, is_active=True ) count = inactive_users.count() # For now, just log inactive users # In production, you might want to send reactivation emails # or mark accounts for deletion logger.info(f"Found {count} inactive users (last login before {cutoff})") return { 'inactive_count': count, 'cutoff_date': cutoff.isoformat(), } except Exception as exc: logger.error(f"Error cleaning up inactive users: {str(exc)}") raise self.retry(exc=exc, countdown=300) @shared_task def update_user_statistics(): """ Update user-related statistics across the database. Returns: dict: Updated statistics """ from apps.users.models import User from django.db.models import Count from datetime import timedelta try: now = timezone.now() week_ago = now - timedelta(days=7) month_ago = now - timedelta(days=30) stats = { 'total_users': User.objects.count(), 'active_users': User.objects.filter(is_active=True).count(), 'new_this_week': User.objects.filter(date_joined__gte=week_ago).count(), 'new_this_month': User.objects.filter(date_joined__gte=month_ago).count(), 'verified_users': User.objects.filter(email_verified=True).count(), 'by_role': dict( User.objects.values('role__name') .annotate(count=Count('id')) .values_list('role__name', 'count') ), } logger.info(f"User statistics updated: {stats}") return stats except Exception as e: logger.error(f"Error updating user statistics: {str(e)}") raise @shared_task(bind=True, max_retries=3) def send_bulk_notification(self, user_ids, subject, message, html_message=None): """ Send bulk email notifications to multiple users. This is useful for announcements, feature updates, etc. Args: user_ids: List of User IDs subject: Email subject message: Plain text message html_message: HTML version of message (optional) Returns: dict: Send statistics """ from apps.users.models import User try: users = User.objects.filter(id__in=user_ids, is_active=True) sent_count = 0 failed_count = 0 for user in users: try: send_mail( subject=subject, message=message, html_message=html_message, from_email=settings.DEFAULT_FROM_EMAIL, recipient_list=[user.email], fail_silently=False, ) sent_count += 1 except Exception as e: logger.error(f"Failed to send to {user.email}: {str(e)}") failed_count += 1 continue result = { 'total': len(user_ids), 'sent': sent_count, 'failed': failed_count, } logger.info(f"Bulk notification sent: {result}") return result except Exception as exc: logger.error(f"Error sending bulk notification: {str(exc)}") raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries)) @shared_task(bind=True, max_retries=2) def send_email_verification_reminder(self, user_id): """ Send a reminder to users who haven't verified their email. Args: user_id: ID of the User Returns: str: Reminder result """ from apps.users.models import User try: user = User.objects.get(id=user_id) if user.email_verified: logger.info(f"User {user.email} already verified, skipping reminder") return "User already verified" # Send verification reminder logger.info(f"Sending email verification reminder to {user.email}") # In production, generate new verification token and send email # For now, just log return f"Verification reminder sent to {user.email}" except User.DoesNotExist: logger.error(f"User {user_id} not found") raise except Exception as exc: logger.error(f"Error sending verification reminder: {str(exc)}") raise self.retry(exc=exc, countdown=60 * (2 ** self.request.retries))