Skip to content
Open
Show file tree
Hide file tree
Changes from 6 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
86 changes: 75 additions & 11 deletions backend/app/api/furniture.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,83 @@
Endpoints for the Furniture resource.
"""

from fastapi import APIRouter, Response, status
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.exc import IntegrityError

from ..config import get_settings
from ..database import get_db
from ..schemas import FurnitureCreate, FurnitureUpdate, Furniture as FurnitureSchema
from ..services import furniture_service

router = APIRouter()


@router.get("")
async def list_furniture():
"""List furniture items.
@router.get("", response_model=list[FurnitureSchema])
async def list_furniture(db: AsyncSession = Depends(get_db)):
"""List furniture items."""
return await furniture_service.list_furniture(db)


@router.post("", response_model=FurnitureSchema, status_code=status.HTTP_201_CREATED)
async def create_furniture(
furniture: FurnitureCreate, db: AsyncSession = Depends(get_db)
):
"""Create a new furniture item."""
try:
return await furniture_service.create_furniture(furniture, db)
except IntegrityError as e:
await db.rollback()
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail=(
"Invalid reference: donor_id, client_id, or dispatch_id must "
"reference existing rows."
),
) from e
except Exception as e:
await db.rollback()
if get_settings().DEBUG:
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, detail=str(e)
) from e
raise


@router.get("/{furniture_id}", response_model=FurnitureSchema)
async def get_furniture(furniture_id: str, db: AsyncSession = Depends(get_db)):
"""Get a single furniture item by ID."""
furniture = await furniture_service.get_furniture(furniture_id, db)
if not furniture:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Furniture not found",
)
return furniture


@router.put("/{furniture_id}", response_model=FurnitureSchema)
async def update_furniture(
furniture_id: str,
payload: FurnitureUpdate,
db: AsyncSession = Depends(get_db),
):
"""Update a furniture item."""
furniture = await furniture_service.update_furniture(furniture_id, payload, db)
if not furniture:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Furniture not found",
)
return furniture


Placeholder implementation. See `docs/STARTER_BACKEND_GUIDE.md`
for details on implementing this handler with the Furniture model and schemas.
"""
return Response(
content="Not implemented — see docs/STARTER_BACKEND_GUIDE.md",
status_code=status.HTTP_501_NOT_IMPLEMENTED,
)
@router.delete("/{furniture_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_furniture(furniture_id: str, db: AsyncSession = Depends(get_db)):
"""Delete a furniture item."""
deleted = await furniture_service.delete_furniture(furniture_id, db)
if not deleted:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Furniture not found",
)
8 changes: 8 additions & 0 deletions backend/app/services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
"""Services package.

Business logic layer; API routes depend on services, not the reverse.
"""

from . import furniture as furniture_service

__all__ = ["furniture_service"]
58 changes: 58 additions & 0 deletions backend/app/services/furniture.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
"""Furniture service.

CRUD and business logic for the Furniture resource.
"""

from sqlalchemy import select
from sqlalchemy.ext.asyncio import AsyncSession

from ..models import Furniture
from ..schemas import FurnitureCreate, FurnitureUpdate


async def list_furniture(db: AsyncSession) -> list[Furniture]:
"""Return furniture items ordered by name."""
result = await db.execute(select(Furniture).order_by(Furniture.name))
return list(result.scalars().all())


async def get_furniture(furniture_id: str, db: AsyncSession) -> Furniture | None:
"""Return a furniture item by id, or None if not found."""
result = await db.execute(select(Furniture).where(Furniture.id == furniture_id))
return result.scalar_one_or_none()


async def create_furniture(payload: FurnitureCreate, db: AsyncSession) -> Furniture:
"""Create a furniture item. Caller must commit; IntegrityError may be raised."""
db_furniture = Furniture(**payload.model_dump())
db.add(db_furniture)
await db.commit()
await db.refresh(db_furniture)
return db_furniture


async def update_furniture(
furniture_id: str,
payload: FurnitureUpdate,
db: AsyncSession,
) -> Furniture | None:
"""Update a furniture item by id. Returns None if not found."""
furniture = await get_furniture(furniture_id, db)
if not furniture:
return None
update_data = payload.model_dump(exclude_unset=True)
for key, value in update_data.items():
setattr(furniture, key, value)
await db.commit()
await db.refresh(furniture)
return furniture


async def delete_furniture(furniture_id: str, db: AsyncSession) -> bool:
"""Delete a furniture item by id. Returns True if deleted, False if not found."""
furniture = await get_furniture(furniture_id, db)
if not furniture:
return False
await db.delete(furniture)
await db.commit()
return True