- 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>
202 lines
5.4 KiB
Python
202 lines
5.4 KiB
Python
from typing import List, Optional
|
|
from fastapi import APIRouter, Depends, HTTPException, status
|
|
from sqlalchemy.orm import Session
|
|
from sqlalchemy.sql import func
|
|
from pydantic import BaseModel
|
|
|
|
from database import get_db
|
|
from auth import get_current_active_user
|
|
from models import User, SavedSearch
|
|
|
|
router = APIRouter(prefix="/searches", tags=["Saved Searches"])
|
|
|
|
|
|
class SavedSearchCreate(BaseModel):
|
|
name: str
|
|
query: str
|
|
filters: Optional[dict] = None
|
|
results_count: Optional[int] = 0
|
|
|
|
|
|
class SavedSearchUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
filters: Optional[dict] = None
|
|
results_count: Optional[int] = None
|
|
|
|
|
|
class SavedSearchResponse(BaseModel):
|
|
id: int
|
|
name: str
|
|
query: str
|
|
filters: Optional[dict]
|
|
results_count: int
|
|
last_used: str
|
|
|
|
class Config:
|
|
from_attributes = True
|
|
|
|
|
|
@router.get("/", response_model=List[SavedSearchResponse])
|
|
def get_saved_searches(
|
|
current_user: User = Depends(get_current_active_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Get all saved searches for the current user"""
|
|
searches = db.query(SavedSearch).filter(
|
|
SavedSearch.user_id == current_user.id
|
|
).order_by(SavedSearch.last_used.desc()).all()
|
|
|
|
return [
|
|
SavedSearchResponse(
|
|
id=s.id,
|
|
name=s.name,
|
|
query=s.query,
|
|
filters=s.filters,
|
|
results_count=s.results_count,
|
|
last_used=s.last_used.isoformat() if s.last_used else ""
|
|
)
|
|
for s in searches
|
|
]
|
|
|
|
|
|
@router.post("/", response_model=SavedSearchResponse, status_code=status.HTTP_201_CREATED)
|
|
def create_saved_search(
|
|
search_data: SavedSearchCreate,
|
|
current_user: User = Depends(get_current_active_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Create a new saved search"""
|
|
search = SavedSearch(
|
|
user_id=current_user.id,
|
|
name=search_data.name,
|
|
query=search_data.query,
|
|
filters=search_data.filters,
|
|
results_count=search_data.results_count
|
|
)
|
|
db.add(search)
|
|
db.commit()
|
|
db.refresh(search)
|
|
|
|
return SavedSearchResponse(
|
|
id=search.id,
|
|
name=search.name,
|
|
query=search.query,
|
|
filters=search.filters,
|
|
results_count=search.results_count,
|
|
last_used=search.last_used.isoformat() if search.last_used else ""
|
|
)
|
|
|
|
|
|
@router.get("/{search_id}", response_model=SavedSearchResponse)
|
|
def get_saved_search(
|
|
search_id: int,
|
|
current_user: User = Depends(get_current_active_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Get a specific saved search"""
|
|
search = db.query(SavedSearch).filter(
|
|
SavedSearch.id == search_id,
|
|
SavedSearch.user_id == current_user.id
|
|
).first()
|
|
|
|
if not search:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Saved search not found"
|
|
)
|
|
|
|
return SavedSearchResponse(
|
|
id=search.id,
|
|
name=search.name,
|
|
query=search.query,
|
|
filters=search.filters,
|
|
results_count=search.results_count,
|
|
last_used=search.last_used.isoformat() if search.last_used else ""
|
|
)
|
|
|
|
|
|
@router.put("/{search_id}", response_model=SavedSearchResponse)
|
|
def update_saved_search(
|
|
search_id: int,
|
|
search_update: SavedSearchUpdate,
|
|
current_user: User = Depends(get_current_active_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Update a saved search"""
|
|
search = db.query(SavedSearch).filter(
|
|
SavedSearch.id == search_id,
|
|
SavedSearch.user_id == current_user.id
|
|
).first()
|
|
|
|
if not search:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Saved search not found"
|
|
)
|
|
|
|
update_data = search_update.model_dump(exclude_unset=True)
|
|
for field, value in update_data.items():
|
|
setattr(search, field, value)
|
|
|
|
db.commit()
|
|
db.refresh(search)
|
|
|
|
return SavedSearchResponse(
|
|
id=search.id,
|
|
name=search.name,
|
|
query=search.query,
|
|
filters=search.filters,
|
|
results_count=search.results_count,
|
|
last_used=search.last_used.isoformat() if search.last_used else ""
|
|
)
|
|
|
|
|
|
@router.post("/{search_id}/use")
|
|
def mark_search_used(
|
|
search_id: int,
|
|
results_count: Optional[int] = None,
|
|
current_user: User = Depends(get_current_active_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Mark a saved search as used (updates last_used timestamp)"""
|
|
search = db.query(SavedSearch).filter(
|
|
SavedSearch.id == search_id,
|
|
SavedSearch.user_id == current_user.id
|
|
).first()
|
|
|
|
if not search:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Saved search not found"
|
|
)
|
|
|
|
search.last_used = func.now()
|
|
if results_count is not None:
|
|
search.results_count = results_count
|
|
|
|
db.commit()
|
|
return {"message": "Search marked as used"}
|
|
|
|
|
|
@router.delete("/{search_id}")
|
|
def delete_saved_search(
|
|
search_id: int,
|
|
current_user: User = Depends(get_current_active_user),
|
|
db: Session = Depends(get_db)
|
|
):
|
|
"""Delete a saved search"""
|
|
search = db.query(SavedSearch).filter(
|
|
SavedSearch.id == search_id,
|
|
SavedSearch.user_id == current_user.id
|
|
).first()
|
|
|
|
if not search:
|
|
raise HTTPException(
|
|
status_code=status.HTTP_404_NOT_FOUND,
|
|
detail="Saved search not found"
|
|
)
|
|
|
|
db.delete(search)
|
|
db.commit()
|
|
return {"message": "Saved search deleted"}
|