Files
gartenmanager/backend/app/api/v1/beds.py
Faultier314 834a3bf4d5 feat: Phase 1 complete – full working application
Backend (FastAPI):
- REST API: auth, plants, beds, plantings
- CRUD layer with CRUDBase
- Pydantic v2 schemas for all entities
- Alembic migration: complete schema + all enums
- Seed data: 28 global plants + 15 compatibilities

Frontend (Vue 3 + PrimeVue):
- Axios client with JWT interceptor + auto-refresh
- Pinia stores: auth, beds, plants
- Views: Login, Beds, BedDetail, PlantLibrary
- Components: AppLayout, BedForm, PlantingForm, PlantForm

Docker:
- docker-compose.yml (production)
- docker-compose.dev.yml (development with hot-reload)
- Nginx config with SPA fallback + API proxy
- Multi-stage frontend Dockerfile
- .env.example, .gitignore

Version: 1.0.0-alpha
2026-04-06 07:45:00 +02:00

79 lines
2.7 KiB
Python

from typing import Annotated
from uuid import UUID
from fastapi import APIRouter, Depends, HTTPException, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.deps import get_session, get_tenant_context, require_min_role
from app.crud.bed import crud_bed
from app.models.user import TenantRole
from app.schemas.bed import BedCreate, BedDetailRead, BedRead, BedUpdate
router = APIRouter(prefix="/beds", tags=["Beete"])
TenantCtx = Annotated[tuple, Depends(get_tenant_context)]
WriteCtx = Annotated[tuple, Depends(require_min_role(TenantRole.READ_WRITE))]
AdminCtx = Annotated[tuple, Depends(require_min_role(TenantRole.TENANT_ADMIN))]
@router.get("", response_model=list[BedRead])
async def list_beds(
db: Annotated[AsyncSession, Depends(get_session)],
ctx: TenantCtx,
) -> list[BedRead]:
_, tenant_id = ctx
beds = await crud_bed.get_multi_for_tenant(db, tenant_id=tenant_id)
return [BedRead.model_validate(b) for b in beds]
@router.get("/{bed_id}", response_model=BedDetailRead)
async def get_bed(
bed_id: UUID,
db: Annotated[AsyncSession, Depends(get_session)],
ctx: TenantCtx,
) -> BedDetailRead:
_, tenant_id = ctx
bed = await crud_bed.get_with_plantings(db, id=bed_id)
if not bed or bed.tenant_id != tenant_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Beet nicht gefunden.")
return BedDetailRead.model_validate(bed)
@router.post("", response_model=BedRead, status_code=status.HTTP_201_CREATED)
async def create_bed(
body: BedCreate,
db: Annotated[AsyncSession, Depends(get_session)],
ctx: WriteCtx,
) -> BedRead:
_, tenant_id, _ = ctx
bed = await crud_bed.create_for_tenant(db, obj_in=body, tenant_id=tenant_id)
return BedRead.model_validate(bed)
@router.put("/{bed_id}", response_model=BedRead)
async def update_bed(
bed_id: UUID,
body: BedUpdate,
db: Annotated[AsyncSession, Depends(get_session)],
ctx: WriteCtx,
) -> BedRead:
_, tenant_id, _ = ctx
bed = await crud_bed.get(db, id=bed_id)
if not bed or bed.tenant_id != tenant_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Beet nicht gefunden.")
updated = await crud_bed.update(db, db_obj=bed, obj_in=body)
return BedRead.model_validate(updated)
@router.delete("/{bed_id}", status_code=status.HTTP_204_NO_CONTENT)
async def delete_bed(
bed_id: UUID,
db: Annotated[AsyncSession, Depends(get_session)],
ctx: AdminCtx,
) -> None:
_, tenant_id, _ = ctx
bed = await crud_bed.get(db, id=bed_id)
if not bed or bed.tenant_id != tenant_id:
raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Beet nicht gefunden.")
await crud_bed.remove(db, id=bed_id)