diff --git a/api/serializers.py b/api/serializers.py index fda9bdb..94d3543 100644 --- a/api/serializers.py +++ b/api/serializers.py @@ -14,11 +14,29 @@ class CategoryListField(serializers.RelatedField): class SupplierProfileSerializer(serializers.ModelSerializer): onboarding_step = serializers.SerializerMethodField() categories = CategoryListField(many=True, queryset=Category.objects.all(), required=False) + aadhar_url = serializers.SerializerMethodField() + pan_url = serializers.SerializerMethodField() + logo_url = serializers.SerializerMethodField() class Meta: model = SupplierProfile exclude = ('user',) + def get_aadhar_url(self, obj): + from .views import get_document_url + request = self.context.get('request') + return get_document_url(obj.aadhar_s3_key, request) + + def get_pan_url(self, obj): + from .views import get_document_url + request = self.context.get('request') + return get_document_url(obj.pan_s3_key, request) + + def get_logo_url(self, obj): + from .views import get_document_url + request = self.context.get('request') + return get_document_url(obj.logo_s3_key, request) + def get_onboarding_step(self, obj): if obj.is_profile_complete: return 7 diff --git a/api/tests.py b/api/tests.py index 76aeb8a..558c9ac 100644 --- a/api/tests.py +++ b/api/tests.py @@ -190,3 +190,52 @@ def test_wallet_withdrawal(api_client, create_user): # Invalid amount (exceeds outstanding) response = api_client.post(url, {'amount': '1500.00'}, format='json') assert response.status_code == status.HTTP_400_BAD_REQUEST + +@pytest.mark.django_db +def test_document_cloudfront_url_generation(api_client, create_user): + from django.conf import settings + from api.views import get_document_url + + profile = create_user.profile + profile.aadhar_s3_key = 'suppliers/123/aadhar.pdf' + profile.save() + + # Case 1: CloudFront Domain set + settings.AWS_CLOUDFRONT_DOMAIN = 'd12345.cloudfront.net' + url = get_document_url(profile.aadhar_s3_key) + assert url == 'https://d12345.cloudfront.net/suppliers/123/aadhar.pdf' + + # Case 2: CloudFront Domain set with protocol prefix + settings.AWS_CLOUDFRONT_DOMAIN = 'https://d98765.cloudfront.net/' + url = get_document_url(profile.aadhar_s3_key) + assert url == 'https://d98765.cloudfront.net/suppliers/123/aadhar.pdf' + + # Case 3: CloudFront Domain empty, fallback to Local Mock + settings.AWS_CLOUDFRONT_DOMAIN = '' + url = get_document_url(profile.aadhar_s3_key) + assert 'mock-download' in url + + # Case 4: API Response includes aadhar_url, pan_url, logo_url + url = reverse('profile') + api_client.force_authenticate(user=create_user) + response = api_client.get(url) + assert response.status_code == status.HTTP_200_OK + assert 'aadhar_url' in response.data['profile'] + assert 'pan_url' in response.data['profile'] + assert 'logo_url' in response.data['profile'] + +@pytest.mark.django_db +def test_mock_download_view(api_client, create_user): + api_client.force_authenticate(user=create_user) + + # 1. Upload mock file first using PUT request + upload_url = reverse('mock-upload') + '?key=test_key.pdf' + file_content = b'PDF content' + upload_resp = api_client.put(upload_url, data=file_content, content_type='application/octet-stream') + assert upload_resp.status_code == status.HTTP_200_OK + + # 2. Download it + download_url = reverse('mock-download') + '?key=test_key.pdf' + download_resp = api_client.get(download_url) + assert download_resp.status_code == status.HTTP_200_OK + assert b"".join(download_resp.streaming_content) == file_content diff --git a/api/urls.py b/api/urls.py index 37d1609..cffcf39 100644 --- a/api/urls.py +++ b/api/urls.py @@ -4,7 +4,7 @@ from rest_framework_simplejwt.views import TokenRefreshView from .views import ( RegisterView, LoginView, LogoutView, VerifyOtpView, ProfileView, ProductViewSet, BulkUploadView, OrderViewSet, ReturnRequestViewSet, - WalletView, WithdrawView, SubmitGstinView, PresignedUrlView, MockUploadView + WalletView, WithdrawView, SubmitGstinView, PresignedUrlView, MockUploadView, MockDownloadView ) router = DefaultRouter() @@ -22,6 +22,7 @@ urlpatterns = [ path('profile/submit-gstin/', SubmitGstinView.as_view(), name='submit-gstin'), path('profile/presigned-url/', PresignedUrlView.as_view(), name='presigned-url'), path('profile/mock-upload/', MockUploadView.as_view(), name='mock-upload'), + path('profile/mock-download/', MockDownloadView.as_view(), name='mock-download'), path('products/bulk-upload/', BulkUploadView.as_view(), name='bulk-upload'), path('wallet/', WalletView.as_view(), name='wallet'), path('wallet/withdraw/', WithdrawView.as_view(), name='withdraw'), diff --git a/api/views.py b/api/views.py index 1aeb0f0..99d3b37 100644 --- a/api/views.py +++ b/api/views.py @@ -7,6 +7,7 @@ import io import os import zipfile from django.conf import settings +from django.http import FileResponse, Http404 from datetime import date, datetime, timedelta from django.utils import timezone from django.contrib.auth import authenticate @@ -286,6 +287,48 @@ class MockUploadView(APIView): except Exception as e: return Response({"error": f"Failed to save file locally: {str(e)}"}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) +def get_document_url(s3_key, request=None): + if not s3_key: + return None + + # 1. CloudFront distribution + cloudfront_domain = getattr(settings, 'AWS_CLOUDFRONT_DOMAIN', None) + if cloudfront_domain: + domain = cloudfront_domain.replace('https://', '').replace('http://', '').strip('/') + return f"https://{domain}/{s3_key}" + + # 2. AWS S3 fallback + aws_access = os.environ.get('AWS_ACCESS_KEY_ID') or os.environ.get('AWS_ACCESS_KEY') + aws_secret = os.environ.get('AWS_SECRET_ACCESS_KEY') or os.environ.get('AWS_SECRET_KEY') + bucket_name = os.environ.get('AWS_STORAGE_BUCKET_NAME') or os.environ.get('AWS_BUCKET_NAME') or 'betasupplierdocument-764709663363-ap-south-2-an' + region_name = os.environ.get('AWS_REGION', 'ap-south-2') + + if aws_access and aws_secret: + return f"https://{bucket_name}.s3.{region_name}.amazonaws.com/{s3_key}" + + # 3. Local Mock fallback + path = f"/api/profile/mock-download/?key={s3_key}" + if request: + return request.build_absolute_uri(path) + return path + +class MockDownloadView(APIView): + permission_classes = [AllowAny] + + def get(self, request): + s3_key = request.query_params.get('key') + if not s3_key: + return Response({"error": "key query param is required"}, status=status.HTTP_400_BAD_REQUEST) + + base_dir = os.environ.get('UPLOAD_MOUNT_DIR', '/home/ubuntu/mnt/s3files/supplierdocuments') + if not os.path.exists('/home/ubuntu/mnt/s3files/supplierdocuments') and not os.environ.get('UPLOAD_MOUNT_DIR'): + base_dir = os.path.join(settings.BASE_DIR, 'mnt/s3files/supplierdocuments') + + file_path = os.path.join(base_dir, s3_key) + if os.path.exists(file_path): + return FileResponse(open(file_path, 'rb')) + raise Http404("File not found") + class PresignedUrlView(APIView): permission_classes = [IsAuthenticated] diff --git a/config/settings.py b/config/settings.py index f2e0d14..de7339f 100644 --- a/config/settings.py +++ b/config/settings.py @@ -314,3 +314,7 @@ LOGGING = { # Media files (uploads) MEDIA_URL = '/media/' MEDIA_ROOT = os.path.join(BASE_DIR, 'media') + +# CloudFront Domain Configuration +AWS_CLOUDFRONT_DOMAIN = os.environ.get('AWS_CLOUDFRONT_DOMAIN', '') + diff --git a/mnt/s3files/supplierdocuments/test_key.pdf b/mnt/s3files/supplierdocuments/test_key.pdf new file mode 100644 index 0000000..12352f4 --- /dev/null +++ b/mnt/s3files/supplierdocuments/test_key.pdf @@ -0,0 +1 @@ +PDF content \ No newline at end of file