Add bottle size, bourbon list modal, and stat improvements
- Add bottle_size field to User model and UserResponse/UserUpdate schemas - Settings modal includes bottle size input (shots capacity) - Community bottles and My Bottle page show fill bar based on bottle size - Community bottle cards are clickable — opens searchable bourbon list modal - Add total_shots_added stat to replace duplicate net volume on dashboard - Reorder dashboard stats: Bourbons Added, Total Poured In, Shots Remaining, Est. Proof - Theme-matched custom scrollbar (amber on dark) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -14,6 +14,7 @@ class User(Base):
|
||||
password_hash: Mapped[str] = mapped_column(String(255), nullable=False)
|
||||
display_name: Mapped[Optional[str]] = mapped_column(String(100))
|
||||
timezone: Mapped[str] = mapped_column(String(50), default="UTC")
|
||||
bottle_size: Mapped[Optional[float]] = mapped_column(default=None)
|
||||
is_admin: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||||
is_disabled: Mapped[bool] = mapped_column(Boolean, default=False, nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime, server_default=func.now())
|
||||
|
||||
@@ -25,6 +25,7 @@ def _calc_stats(entries: list[Entry]) -> BottleStats:
|
||||
|
||||
return BottleStats(
|
||||
total_add_entries=len(adds),
|
||||
total_shots_added=round(total_add_shots, 2),
|
||||
current_total_shots=round(current_total, 2),
|
||||
estimated_proof=round(estimated_proof, 1) if estimated_proof is not None else None,
|
||||
)
|
||||
@@ -61,9 +62,8 @@ async def create_entry(
|
||||
amount_shots=body.amount_shots,
|
||||
notes=body.notes,
|
||||
)
|
||||
async with db.begin():
|
||||
db.add(entry)
|
||||
|
||||
db.add(entry)
|
||||
await db.commit()
|
||||
await db.refresh(entry)
|
||||
return entry
|
||||
|
||||
@@ -81,8 +81,8 @@ async def delete_entry(
|
||||
if not entry:
|
||||
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Entry not found")
|
||||
|
||||
async with db.begin():
|
||||
await db.delete(entry)
|
||||
await db.delete(entry)
|
||||
await db.commit()
|
||||
|
||||
|
||||
@router.get("/stats", response_model=BottleStats)
|
||||
|
||||
@@ -31,11 +31,15 @@ async def public_stats(db: AsyncSession = Depends(get_db)):
|
||||
proof_shot_total = sum(e.amount_shots for e in adds if e.proof is not None)
|
||||
estimated_proof = round(weighted_proof_sum / proof_shot_total, 1) if proof_shot_total > 0 else None
|
||||
|
||||
bourbons = sorted({e.bourbon_name for e in adds if e.bourbon_name}, key=str.casefold)
|
||||
|
||||
stats.append(PublicUserStats(
|
||||
display_name=user.display_name or user.email.split("@")[0],
|
||||
total_add_entries=len(adds),
|
||||
current_total_shots=round(current_total, 2),
|
||||
estimated_proof=estimated_proof,
|
||||
bottle_size=user.bottle_size,
|
||||
bourbons=bourbons,
|
||||
))
|
||||
|
||||
return stats
|
||||
|
||||
@@ -24,6 +24,7 @@ async def update_me(
|
||||
current_user.display_name = body.display_name
|
||||
if body.timezone is not None:
|
||||
current_user.timezone = body.timezone
|
||||
current_user.bottle_size = body.bottle_size
|
||||
|
||||
db.add(current_user)
|
||||
await db.commit()
|
||||
|
||||
@@ -29,6 +29,7 @@ class EntryResponse(BaseModel):
|
||||
|
||||
class BottleStats(BaseModel):
|
||||
total_add_entries: int
|
||||
total_shots_added: float
|
||||
current_total_shots: float
|
||||
estimated_proof: Optional[float]
|
||||
|
||||
@@ -38,3 +39,5 @@ class PublicUserStats(BaseModel):
|
||||
total_add_entries: int
|
||||
current_total_shots: float
|
||||
estimated_proof: Optional[float]
|
||||
bottle_size: Optional[float]
|
||||
bourbons: list[str]
|
||||
|
||||
@@ -12,6 +12,7 @@ class UserCreate(BaseModel):
|
||||
class UserUpdate(BaseModel):
|
||||
display_name: Optional[str] = None
|
||||
timezone: Optional[str] = None
|
||||
bottle_size: Optional[float] = None
|
||||
|
||||
|
||||
class PasswordChange(BaseModel):
|
||||
@@ -24,6 +25,7 @@ class UserResponse(BaseModel):
|
||||
email: str
|
||||
display_name: Optional[str]
|
||||
timezone: str
|
||||
bottle_size: Optional[float]
|
||||
is_admin: bool
|
||||
created_at: datetime
|
||||
|
||||
|
||||
Reference in New Issue
Block a user