Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 16 additions & 1 deletion backend/config/environment.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,18 @@ def unsplash_access_key(self) -> Optional[str]:
@property
def unsplash_secret_key(self) -> Optional[str]:
return os.getenv('UNSPLASH_SECRET_KEY')

@property
def google_places_api_key(self) -> Optional[str]:
return os.getenv('GOOGLE_PLACES_API_KEY')

@property
def meetup_api_key(self) -> Optional[str]:
return os.getenv('MEETUP_API_KEY')

@property
def yelp_api_key(self) -> Optional[str]:
return os.getenv('YELP_API_KEY')

@property
def backend_url(self) -> str:
Expand All @@ -91,7 +103,10 @@ def get_api_keys(self) -> dict:
'eventbrite': self.eventbrite_api_key,
'ticketmaster': self.ticketmaster_api_key,
'unsplash': self.unsplash_access_key,
'google_places': self.google_places_api_key,
'meetup': self.meetup_api_key,
'yelp': self.yelp_api_key,
}

# Global configuration instance
env_config = EnvironmentConfig()
env_config = EnvironmentConfig()
55 changes: 42 additions & 13 deletions backend/core/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -410,6 +410,8 @@ async def get_events(lat: float, lng: float, category: str = "adventure", radius
# Convert GlobalEvent objects to dictionaries for JSON serialization
event_dicts = []
for event in events:
# Tickets flag: Eventbrite/Ticketmaster with external_url implies ticket purchase
tickets_required = True if (event.external_url and (event.source and event.source.value in ["eventbrite", "ticketmaster"])) else False
event_dict = {
'id': event.id,
'name': event.name,
Expand All @@ -432,7 +434,8 @@ async def get_events(lat: float, lng: float, category: str = "adventure", radius
'max_attendees': event.max_attendees,
'is_free': event.is_free,
'is_featured': event.is_featured,
'metadata': event.metadata
'metadata': event.metadata,
'tickets_required': tickets_required
}
event_dicts.append(event_dict)

Expand Down Expand Up @@ -606,6 +609,18 @@ def create_events_for_plan(plan_id: str, events: List[dict]):
created_count = 0
for event in events:
event_id = str(uuid.uuid4())
# Map incoming source to DB constraint values
raw_source = (event.get("source") or event.get("source_type") or "").lower()
if raw_source in ("eventbrite", "ticketmaster", "yelp", "custom", "google", "local"):
source_type = raw_source
elif raw_source in ("google_places", "openstreetmap", "osm", "places"):
source_type = "google"
elif raw_source in ("meetup",):
source_type = "local"
elif not raw_source:
source_type = "local"
else:
source_type = "local"
conn.execute(
text("""
INSERT INTO events (id, plan_id, name, image, hours, source_type, votes_count, metadata)
Expand All @@ -617,7 +632,7 @@ def create_events_for_plan(plan_id: str, events: List[dict]):
"name": event.get("name", "Event"),
"image": event.get("image_url") or event.get("image"), # Support both field names
"hours": event.get("hours"),
"source_type": event.get("source_type", "google"), # Default to google instead of external
"source_type": source_type,
"metadata": json.dumps(event.get("metadata", {}))
}
)
Expand Down Expand Up @@ -747,6 +762,8 @@ def get_events_for_plan(plan_id: str):
"description": metadata.get('description', '') or f"Experience the best {metadata.get('category', 'local')} vibes at {row[1]}. Perfect for {metadata.get('category', 'fun')} activities and memorable moments.",
"organizer": metadata.get('organizer', ''),
"external_url": metadata.get('external_url'),
"tickets_required": bool(metadata.get('tickets_required')),
"reservations_accepted": bool(metadata.get('reservations_accepted')),
"reviews": {
"count": metadata.get('review_count', metadata.get('user_ratings_total', 42)),
"stars": metadata.get('rating', metadata.get('stars', 4.2))
Expand Down Expand Up @@ -968,33 +985,33 @@ def get_plan_results(plan_id: str):

events_result = conn.execute(
text("""
SELECT e.id, e.name, e.image, e.metadata,
SELECT e.id, e.name, e.image, e.source_type, e.metadata,
COUNT(v.id) as total_votes,
COUNT(CASE WHEN v.vote_type = 'like' THEN 1 END) as likes,
COUNT(CASE WHEN v.vote_type = 'dislike' THEN 1 END) as dislikes
FROM events e
LEFT JOIN votes v ON e.id = v.event_id
WHERE e.plan_id = :plan_id
GROUP BY e.id, e.name, e.image, e.metadata
GROUP BY e.id, e.name, e.image, e.source_type, e.metadata
ORDER BY likes DESC, e.name ASC
"""),
{"plan_id": plan_id}
)
events = []
for row in events_result:
total_votes = row[4] or 0
likes = row[5] or 0
dislikes = row[6] or 0
total_votes = row[5] or 0
likes = row[6] or 0
dislikes = row[7] or 0
percentage = (likes / total_votes * 100) if total_votes > 0 else 0

# Parse metadata for additional info
metadata = {}
if row[3]:
if row[4]:
try:
if isinstance(row[3], str):
metadata = json.loads(row[3])
if isinstance(row[4], str):
metadata = json.loads(row[4])
else:
metadata = row[3]
metadata = row[4]
except:
metadata = {}

Expand All @@ -1021,13 +1038,25 @@ def get_plan_results(plan_id: str):
image_category = topic_image_map.get(plan[0] if plan else 'nightlife', 'nightlife')
topic_specific_image = f"https://picsum.photos/600/400?random={str(row[0])[-6:]}&category={image_category}"

# Extract ticketing/reservation info from metadata when present
tickets_required = bool(metadata.get('tickets_required'))
reservations_accepted = bool(metadata.get('reservations_accepted'))
external_url = metadata.get('external_url') or metadata.get('purchase_url')
seatmap_url = metadata.get('seatmap_url')

event_data = {
"id": str(row[0]),
"name": row[1],
"image_url": row[2] or metadata.get('image_url') or topic_specific_image, # Changed to image_url
"source_type": row[3],
"votes": likes,
"total_votes": total_votes,
"percentage": round(percentage, 1)
"percentage": round(percentage, 1),
"tickets_required": tickets_required,
"reservations_accepted": reservations_accepted,
"external_url": external_url,
"seatmap_url": seatmap_url,
"metadata": metadata
}
events.append(event_data)
print(f"📊 Event: {row[1]}, Votes: {likes}, Total: {total_votes}")
Expand Down Expand Up @@ -1768,4 +1797,4 @@ async def refresh_token(refresh_token: str):

if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
uvicorn.run(app, host="0.0.0.0", port=8000)
14 changes: 13 additions & 1 deletion backend/main.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,18 @@ TICKETMASTER_API_SECRET=
# Get key: https://www.meetup.com/api/
MEETUP_API_KEY=

# Google Places API (Venue details, ratings, hours, price)
# Billing required after free credit: https://developers.google.com/maps/documentation/places/web-service/overview
GOOGLE_PLACES_API_KEY=

# Foursquare Places API (Global POI, ratings, hours)
# Docs: https://location.foursquare.com/developer/reference/place-search
FOURSQUARE_API_KEY=

# Yelp Fusion API (Business ratings, price, reservations flag)
# Docs: https://docs.developer.yelp.com/reference/v3_business_search
YELP_API_KEY=

# =============================================================================
# SECURITY (Auto-generated if not provided)
# =============================================================================
Expand All @@ -47,4 +59,4 @@ ALGORITHM=HS256
# =============================================================================
DEBUG=false
ENVIRONMENT=development
ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
ALLOWED_ORIGINS=http://localhost:3000,http://127.0.0.1:3000
Loading