diff --git a/api/urls.py b/api/urls.py index d48eebb..fda9946 100644 --- a/api/urls.py +++ b/api/urls.py @@ -5,7 +5,8 @@ from .views import ( RegisterView, LoginView, LogoutView, VerifyOtpView, ProfileView, ProductViewSet, BulkUploadView, BulkUploadLogsView, BulkUploadErrorReportView, OrderViewSet, ReturnRequestViewSet, WalletView, WithdrawView, SubmitGstinView, PresignedUrlView, MockUploadView, MockDownloadView, - UploadEAadhaarView, VerifyPANView, VerifyBankView, SendWhatsAppOtpView, SendEmailOtpView, ProductSearchView, DashboardStatsView + UploadEAadhaarView, VerifyPANView, VerifyBankView, SendWhatsAppOtpView, SendEmailOtpView, ProductSearchView, DashboardStatsView, + StorefrontDetailView ) from .logs_views import LogsView, LogStatsView, LogClearView @@ -64,5 +65,9 @@ urlpatterns = [ path('shipping/reverse/label/', ReverseLabelView.as_view(), name='shipping-reverse-label'), path('shipping/reverse/update-qc/', ReverseUpdateQCView.as_view(), name='shipping-reverse-update-qc'), + # Public Storefront Endpoints + path('store//', StorefrontDetailView.as_view(), name='store-detail'), + path('storefront//', StorefrontDetailView.as_view(), name='storefront-detail'), + path('', include(router.urls)), ] diff --git a/api/views.py b/api/views.py index 3f3615d..65b7dc6 100644 --- a/api/views.py +++ b/api/views.py @@ -2641,3 +2641,69 @@ class ProductSearchView(APIView): except Exception as err: logger.error(f"Fallback Product Search Error: {err}", exc_info=True) return Response({'error': str(err), 'results': [], 'count': 0}, status=status.HTTP_500_INTERNAL_SERVER_ERROR) + + +class StorefrontDetailView(APIView): + """ + Public API endpoint to retrieve a seller's storefront details and products by store_slug. + Accessible by customers opening tradhox.com/store// + GET /api/store// + GET /api/storefront// + """ + permission_classes = [AllowAny] + + def get(self, request, slug): + clean_slug = str(slug or '').strip() + if not clean_slug: + return Response({'error': 'Store slug is required'}, status=status.HTTP_400_BAD_REQUEST) + + # Look up profile by store_slug, or fallback to username or store_name + profile = ( + SupplierProfile.objects.filter(store_slug__iexact=clean_slug).first() + or SupplierProfile.objects.filter(user__username__iexact=clean_slug).first() + or SupplierProfile.objects.filter(store_name__iexact=clean_slug).first() + ) + + if not profile: + return Response({'error': 'Storefront not found', 'slug': clean_slug}, status=status.HTTP_404_NOT_FOUND) + + # Build S3 / CDN URLs for banner and logo + banner_url = get_document_url(profile.banner_s3_key, request) if profile.banner_s3_key else (profile.banner_url or '') + logo_url = get_document_url(profile.logo_s3_key, request) if profile.logo_s3_key else (profile.store_logo or profile.artisan_avatar or '') + + location = ", ".join([part for part in [profile.city, profile.state] if part]).strip() + if not location: + location = profile.workshop_location or "India" + + # Fetch active products for this seller + products_qs = Product.objects.filter(supplier=profile.user, is_active=True).order_by('-id') + products_data = ProductSerializer(products_qs, many=True).data + + # Ensure product image URLs are properly resolved + for p in products_data: + img = p.get('image') + if img and not str(img).startswith(('http://', 'https://', 'data:')): + p['image'] = get_document_url(img, request) + + categories = [c.name for c in profile.categories.all()] + + store_data = { + 'id': profile.user.id, + 'name': profile.store_name or profile.user.username, + 'slug': profile.store_slug or clean_slug, + 'tagline': profile.featured_tagline or profile.business_bio or "Authentic Handcrafted Treasures from Master Artisans", + 'bio': profile.business_bio or "Generational master artisanal workshop preserving authentic Indian heritage crafts.", + 'banner_url': banner_url, + 'logo_url': logo_url, + 'location': location, + 'craft_lineage_years': profile.craft_lineage_years, + 'is_verified': profile.is_verified or profile.status == 'approved', + 'categories': categories, + 'total_products': len(products_data), + } + + return Response({ + 'status': 'success', + 'store': store_data, + 'products': products_data + }, status=status.HTTP_200_OK)