feat: Add Findagram and FindADispo consumer frontends
- Add findagram.co React frontend with product search, brands, categories - Add findadispo.com React frontend with dispensary locator - Wire findagram to backend /api/az/* endpoints - Update category/brand links to route to /products with filters - Add k8s manifests for both frontends - Add multi-domain user support migrations 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
164
findadispo/backend/routes/dispensary_routes.py
Normal file
164
findadispo/backend/routes/dispensary_routes.py
Normal file
@@ -0,0 +1,164 @@
|
||||
from typing import Optional
|
||||
from fastapi import APIRouter, HTTPException
|
||||
import httpx
|
||||
from pydantic import BaseModel
|
||||
|
||||
from config import get_settings
|
||||
|
||||
router = APIRouter(prefix="/dispensaries", tags=["Dispensaries"])
|
||||
settings = get_settings()
|
||||
|
||||
|
||||
class DispensaryQuery(BaseModel):
|
||||
lat: Optional[float] = None
|
||||
lng: Optional[float] = None
|
||||
city: Optional[str] = None
|
||||
state: Optional[str] = None
|
||||
radius: Optional[int] = 25
|
||||
limit: Optional[int] = 20
|
||||
offset: Optional[int] = 0
|
||||
|
||||
|
||||
async def fetch_from_api(endpoint: str, params: dict = None):
|
||||
"""Fetch data from the external dispensary API"""
|
||||
headers = {}
|
||||
if settings.dispensary_api_key:
|
||||
headers["X-API-Key"] = settings.dispensary_api_key
|
||||
|
||||
async with httpx.AsyncClient() as client:
|
||||
try:
|
||||
response = await client.get(
|
||||
f"{settings.dispensary_api_url}{endpoint}",
|
||||
params=params,
|
||||
headers=headers,
|
||||
timeout=30.0
|
||||
)
|
||||
response.raise_for_status()
|
||||
return response.json()
|
||||
except httpx.HTTPStatusError as e:
|
||||
raise HTTPException(
|
||||
status_code=e.response.status_code,
|
||||
detail=f"API error: {e.response.text}"
|
||||
)
|
||||
except httpx.RequestError as e:
|
||||
raise HTTPException(
|
||||
status_code=503,
|
||||
detail=f"Failed to connect to dispensary API: {str(e)}"
|
||||
)
|
||||
|
||||
|
||||
@router.get("/")
|
||||
async def search_dispensaries(
|
||||
lat: Optional[float] = None,
|
||||
lng: Optional[float] = None,
|
||||
city: Optional[str] = None,
|
||||
state: Optional[str] = "AZ",
|
||||
radius: int = 25,
|
||||
limit: int = 20,
|
||||
offset: int = 0,
|
||||
open_now: bool = False,
|
||||
min_rating: Optional[float] = None
|
||||
):
|
||||
"""Search for dispensaries by location"""
|
||||
params = {
|
||||
"limit": limit,
|
||||
"offset": offset,
|
||||
"state": state
|
||||
}
|
||||
|
||||
if lat and lng:
|
||||
params["lat"] = lat
|
||||
params["lng"] = lng
|
||||
params["radius"] = radius
|
||||
|
||||
if city:
|
||||
params["city"] = city
|
||||
|
||||
# Fetch from external API
|
||||
data = await fetch_from_api("/api/az/stores", params)
|
||||
|
||||
# Apply client-side filters if needed
|
||||
stores = data.get("stores", [])
|
||||
|
||||
if open_now:
|
||||
# Filter stores that are currently open
|
||||
# This would need actual business hours logic
|
||||
pass
|
||||
|
||||
if min_rating:
|
||||
stores = [s for s in stores if (s.get("rating") or 0) >= min_rating]
|
||||
|
||||
return {
|
||||
"dispensaries": stores,
|
||||
"total": len(stores),
|
||||
"limit": limit,
|
||||
"offset": offset
|
||||
}
|
||||
|
||||
|
||||
@router.get("/{dispensary_id}")
|
||||
async def get_dispensary(dispensary_id: int):
|
||||
"""Get details for a specific dispensary"""
|
||||
data = await fetch_from_api(f"/api/az/stores/{dispensary_id}")
|
||||
return data
|
||||
|
||||
|
||||
@router.get("/{dispensary_id}/products")
|
||||
async def get_dispensary_products(
|
||||
dispensary_id: int,
|
||||
category: Optional[str] = None,
|
||||
search: Optional[str] = None,
|
||||
limit: int = 50,
|
||||
offset: int = 0
|
||||
):
|
||||
"""Get products for a specific dispensary"""
|
||||
params = {
|
||||
"limit": limit,
|
||||
"offset": offset
|
||||
}
|
||||
|
||||
if category:
|
||||
params["category"] = category
|
||||
|
||||
if search:
|
||||
params["search"] = search
|
||||
|
||||
data = await fetch_from_api(f"/api/az/stores/{dispensary_id}/products", params)
|
||||
return data
|
||||
|
||||
|
||||
@router.get("/{dispensary_id}/categories")
|
||||
async def get_dispensary_categories(dispensary_id: int):
|
||||
"""Get product categories for a dispensary"""
|
||||
data = await fetch_from_api(f"/api/az/stores/{dispensary_id}/categories")
|
||||
return data
|
||||
|
||||
|
||||
@router.get("/nearby")
|
||||
async def get_nearby_dispensaries(
|
||||
lat: float,
|
||||
lng: float,
|
||||
radius: int = 10,
|
||||
limit: int = 10
|
||||
):
|
||||
"""Get nearby dispensaries by coordinates"""
|
||||
params = {
|
||||
"lat": lat,
|
||||
"lng": lng,
|
||||
"radius": radius,
|
||||
"limit": limit
|
||||
}
|
||||
data = await fetch_from_api("/api/az/stores", params)
|
||||
return data.get("stores", [])
|
||||
|
||||
|
||||
@router.get("/featured")
|
||||
async def get_featured_dispensaries(limit: int = 6):
|
||||
"""Get featured dispensaries for the homepage"""
|
||||
# For now, return top-rated dispensaries
|
||||
params = {
|
||||
"limit": limit,
|
||||
"sort": "rating"
|
||||
}
|
||||
data = await fetch_from_api("/api/az/stores", params)
|
||||
return data.get("stores", [])
|
||||
Reference in New Issue
Block a user