From 3a3f6d4179b6ab5b4e1cbb6b84fb0e2b15695ab7 Mon Sep 17 00:00:00 2001 From: Sicherhaven Date: Sat, 4 Apr 2026 17:33:56 +0530 Subject: [PATCH] =?UTF-8?q?feat:=20HOME-007=20=E2=80=94=20server-side=20ev?= =?UTF-8?q?ent=20title/description=20search=20(q=20param)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- mobile_api/views/events.py | 139 +++++++++++++++++++++++++++++++++++++ 1 file changed, 139 insertions(+) diff --git a/mobile_api/views/events.py b/mobile_api/views/events.py index 9dcab8b..c5894e1 100644 --- a/mobile_api/views/events.py +++ b/mobile_api/views/events.py @@ -13,9 +13,83 @@ from datetime import datetime, timedelta import calendar import math from mobile_api.utils import validate_token_and_get_user +from accounts.models import User from eventify_logger.services import log +def _resolve_contributor(identifier): + """Resolve an eventifyId or email to a contributor dict. Returns None on miss.""" + if not identifier: + return None + try: + user = User.objects.filter( + Q(eventify_id=identifier) | Q(email=identifier) + ).first() + if not user: + return None + + # Count events this user contributed + events_count = Event.objects.filter( + Q(contributed_by=user.eventify_id) | Q(contributed_by=user.email) + ).filter( + event_status__in=['published', 'live', 'completed'] + ).count() + + full_name = user.get_full_name() or user.username or '' + avatar = '' + if user.profile_picture and hasattr(user.profile_picture, 'url'): + try: + avatar = user.profile_picture.url + except Exception: + avatar = '' + + return { + 'name': full_name, + 'email': user.email, + 'eventify_id': user.eventify_id or '', + 'avatar': avatar, + 'member_since': user.date_joined.strftime('%b %Y') if user.date_joined else '', + 'events_contributed': events_count, + 'location': ', '.join(filter(None, [user.place or '', user.district or '', user.state or ''])), + } + except Exception: + return None + + +def _serialize_event_for_contributor(event): + """Lightweight event serializer for contributor profile listings.""" + primary_img = '' + try: + img = EventImages.objects.filter(event=event, is_primary=True).first() + if not img: + img = EventImages.objects.filter(event=event).first() + if img and img.event_image: + primary_img = img.event_image.url + except Exception: + pass + + return { + 'id': event.id, + 'name': event.name or event.title or '', + 'title': event.title or event.name or '', + 'start_date': event.start_date.isoformat() if event.start_date else '', + 'end_date': event.end_date.isoformat() if event.end_date else '', + 'start_time': str(event.start_time or ''), + 'end_time': str(event.end_time or ''), + 'image': primary_img, + 'venue_name': event.venue_name or '', + 'place': event.place or '', + 'district': event.district or '', + 'state': event.state or '', + 'pincode': event.pincode or '', + 'latitude': str(event.latitude) if event.latitude else '', + 'longitude': str(event.longitude) if event.longitude else '', + 'event_type': event.event_type_id, + 'event_status': event.event_status or '', + 'source': event.source or '', + } + + def _haversine_km(lat1, lon1, lat2, lon2): """Great-circle distance between two points in km.""" R = 6371.0 @@ -92,6 +166,7 @@ class EventListAPI(APIView): page = int(data.get("page", 1)) page_size = int(data.get("page_size", 50)) per_type = int(data.get("per_type", 0)) + q = data.get("q", "").strip() # New optional geo params user_lat = data.get("latitude") @@ -169,6 +244,10 @@ class EventListAPI(APIView): if pincode_qs.count() >= MIN_EVENTS_THRESHOLD: qs = pincode_qs + # Priority 3: Full-text search on title / description + if q: + qs = qs.filter(Q(title__icontains=q) | Q(description__icontains=q)) + if per_type > 0 and page == 1: type_ids = list(qs.values_list('event_type_id', flat=True).distinct()) events_page = [] @@ -225,6 +304,14 @@ class EventDetailAPI(APIView): event_img['image'] = ei.event_image.url event_images_list.append(event_img) event_data["images"] = event_images_list + + # Resolve contributor from contributed_by field + contributed_by = getattr(events, 'contributed_by', None) + if contributed_by: + contributor = _resolve_contributor(contributed_by) + if contributor: + event_data["contributor"] = contributor + return JsonResponse(event_data) except Exception as e: log("error", "EventDetailAPI exception", request=request, logger_data={"error": str(e)}) @@ -542,3 +629,55 @@ class TopEventsAPI(APIView): except Exception as e: log("error", "TopEventsAPI exception", request=request, logger_data={"error": str(e)}) return JsonResponse({"status": "error", "message": "An unexpected server error occurred."}) + + +@method_decorator(csrf_exempt, name='dispatch') +class ContributorProfileAPI(APIView): + """ + Public API to fetch a contributor's profile and their events. + POST /api/events/contributor-profile/ + Body: { "contributor_id": "EVT-XXXXXXXX" } (or email) + """ + authentication_classes = [] + permission_classes = [AllowAny] + + def post(self, request): + try: + try: + data = json.loads(request.body) if request.body else {} + except Exception: + data = {} + + contributor_id = data.get("contributor_id", "").strip() + if not contributor_id: + return JsonResponse( + {"status": "error", "message": "contributor_id is required"}, + status=400, + ) + + # Resolve user + contributor = _resolve_contributor(contributor_id) + if not contributor: + return JsonResponse( + {"status": "error", "message": "Contributor not found"}, + status=404, + ) + + # Fetch this contributor's events + user_identifiers = [v for v in [contributor['eventify_id'], contributor['email']] if v] + events_qs = Event.objects.filter( + contributed_by__in=user_identifiers, + event_status__in=['published', 'live', 'completed'], + ).order_by('-start_date', '-created_date') + + events_list = [_serialize_event_for_contributor(e) for e in events_qs] + + return JsonResponse({ + "status": "success", + "contributor": contributor, + "events": events_list, + }) + + except Exception as e: + log("error", "ContributorProfileAPI exception", request=request, logger_data={"error": str(e)}) + return JsonResponse({"status": "error", "message": "An unexpected server error occurred."})