Files
yolkbook/backend/routers/eggs.py
derekc 60fed6d464 Implement performance improvements across backend and frontend
- models.py: add composite (user_id, date) indexes to flock_history,
  feed_purchases, and other_purchases for faster date-filtered queries
  (egg_collections already had one via its unique constraint)
- main.py: add v2.2 migration to create the three composite indexes on
  existing installs at startup
- stats.py: fix N+1 query in monthly_stats — flock history is now fetched
  once and looked up per month using bisect_right instead of one DB query
  per month row; also remove unnecessary Decimal(str(...)) round-trips
  since SQLAlchemy already returns Numeric columns as Decimal
- eggs.py: add limit parameter (default 500, max 1000) to list_eggs to
  cap unbounded fetches on large datasets
- dashboard.js: pass start= (30 days ago) when fetching eggs so the
  dashboard only loads the data it actually needs for the chart and
  recent collections list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-03-18 00:02:58 -07:00

93 lines
2.8 KiB
Python

from datetime import date
from typing import Optional
from fastapi import APIRouter, Depends, HTTPException
from sqlalchemy import select
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import Session
from database import get_db
from models import EggCollection, User
from schemas import EggCollectionCreate, EggCollectionUpdate, EggCollectionOut
from auth import get_current_user
router = APIRouter(prefix="/api/eggs", tags=["eggs"])
@router.get("", response_model=list[EggCollectionOut])
def list_eggs(
start: Optional[date] = None,
end: Optional[date] = None,
limit: int = 500,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
limit = min(limit, 1000)
q = (
select(EggCollection)
.where(EggCollection.user_id == current_user.id)
.order_by(EggCollection.date.desc())
.limit(limit)
)
if start:
q = q.where(EggCollection.date >= start)
if end:
q = q.where(EggCollection.date <= end)
return db.scalars(q).all()
@router.post("", response_model=EggCollectionOut, status_code=201)
def create_egg_collection(
body: EggCollectionCreate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
record = EggCollection(**body.model_dump(), user_id=current_user.id)
db.add(record)
try:
db.commit()
except IntegrityError:
db.rollback()
raise HTTPException(status_code=409, detail=f"An entry for {body.date} already exists. Edit it from the History page.")
db.refresh(record)
return record
@router.put("/{record_id}", response_model=EggCollectionOut)
def update_egg_collection(
record_id: int,
body: EggCollectionUpdate,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
record = db.scalars(
select(EggCollection)
.where(EggCollection.id == record_id, EggCollection.user_id == current_user.id)
).first()
if not record:
raise HTTPException(status_code=404, detail="Record not found")
for field, value in body.model_dump(exclude_none=True).items():
setattr(record, field, value)
try:
db.commit()
except IntegrityError:
db.rollback()
raise HTTPException(status_code=409, detail="An entry for that date already exists.")
db.refresh(record)
return record
@router.delete("/{record_id}", status_code=204)
def delete_egg_collection(
record_id: int,
db: Session = Depends(get_db),
current_user: User = Depends(get_current_user),
):
record = db.scalars(
select(EggCollection)
.where(EggCollection.id == record_id, EggCollection.user_id == current_user.id)
).first()
if not record:
raise HTTPException(status_code=404, detail="Record not found")
db.delete(record)
db.commit()