""" User serializers for KeepItGoing API. """ from django.contrib.auth import get_user_model from django.contrib.auth.password_validation import validate_password from django.core.validators import FileExtensionValidator from rest_framework import serializers from .models import DeviceToken User = get_user_model() # Maximum file size for avatar uploads (5MB) MAX_AVATAR_SIZE = 5 * 1024 * 1024 class UserSerializer(serializers.ModelSerializer): """Serializer for user details.""" class Meta: model = User fields = [ 'id', 'email', 'username', 'first_name', 'last_name', 'timezone', 'avatar', 'default_reminder_minutes', 'email_notifications', 'push_notifications', 'created_at', 'updated_at', ] read_only_fields = ['id', 'email', 'created_at', 'updated_at'] def validate_avatar(self, value): """ Validate avatar file upload. Checks: - File extension (jpg, jpeg, png, gif only) - File size (max 5MB) - Content type """ if value: # Check file extension allowed_extensions = ['jpg', 'jpeg', 'png', 'gif'] ext = value.name.split('.')[-1].lower() if ext not in allowed_extensions: raise serializers.ValidationError( f"File type '.{ext}' is not supported. " f"Allowed types: {', '.join(allowed_extensions)}" ) # Check file size (5MB max) if value.size > MAX_AVATAR_SIZE: max_size_mb = MAX_AVATAR_SIZE / (1024 * 1024) actual_size_mb = value.size / (1024 * 1024) raise serializers.ValidationError( f"File size ({actual_size_mb:.1f}MB) exceeds maximum allowed size ({max_size_mb:.0f}MB)." ) # Check content type allowed_types = ['image/jpeg', 'image/png', 'image/gif'] if hasattr(value, 'content_type') and value.content_type not in allowed_types: raise serializers.ValidationError( f"Invalid image type. Allowed types: JPEG, PNG, GIF" ) return value class UserRegistrationSerializer(serializers.ModelSerializer): """Serializer for user registration.""" password = serializers.CharField( write_only=True, required=True, validators=[validate_password], style={'input_type': 'password'} ) password_confirm = serializers.CharField( write_only=True, required=True, style={'input_type': 'password'} ) class Meta: model = User fields = ['email', 'username', 'password', 'password_confirm', 'first_name', 'last_name', 'timezone'] def validate(self, attrs): if attrs['password'] != attrs['password_confirm']: raise serializers.ValidationError({ 'password_confirm': "Passwords do not match." }) return attrs def create(self, validated_data): validated_data.pop('password_confirm') user = User.objects.create_user(**validated_data) return user class ChangePasswordSerializer(serializers.Serializer): """Serializer for password change.""" old_password = serializers.CharField( required=True, style={'input_type': 'password'} ) new_password = serializers.CharField( required=True, validators=[validate_password], style={'input_type': 'password'} ) def validate_old_password(self, value): user = self.context['request'].user if not user.check_password(value): raise serializers.ValidationError("Current password is incorrect.") return value class DeviceTokenSerializer(serializers.ModelSerializer): """Serializer for device tokens.""" class Meta: model = DeviceToken fields = ['id', 'platform', 'token', 'device_name', 'is_active', 'created_at'] read_only_fields = ['id', 'created_at'] def create(self, validated_data): validated_data['user'] = self.context['request'].user # Update if token already exists, otherwise create device_token, created = DeviceToken.objects.update_or_create( user=validated_data['user'], token=validated_data['token'], defaults=validated_data ) return device_token