Skip to content

Commit 48b2c7d

Browse files
amosttAygentic
andcommitted
feat(api): add Entity CRUD REST endpoints with auth and pagination [AYG-70]
Add 5 thin route handlers (POST/GET/GET/{id}/PATCH/{id}/DELETE/{id}) for the Entity resource at /api/v1/entities. All endpoints require Clerk JWT authentication via PrincipalDep and delegate business logic to entity_service. Pagination validated at route layer via Query constraints (offset>=0, limit 1-100). Includes 22 integration tests covering CRUD lifecycle, auth enforcement, ownership isolation (404 not 403), validation errors, and pagination boundary rejection. Files: - CREATE backend/app/api/routes/entities.py (5 route handlers) - MODIFY backend/app/api/main.py (register entities router) - CREATE backend/tests/integration/test_entities.py (22 tests) Fixes AYG-70 Related to AYG-64 🤖 Generated by Aygentic Co-Authored-By: Aygentic <noreply@aygentic.com>
1 parent abba475 commit 48b2c7d

File tree

3 files changed

+512
-1
lines changed

3 files changed

+512
-1
lines changed

backend/app/api/main.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,14 @@
11
from fastapi import APIRouter
22

3-
from app.api.routes import items, login, private, users, utils
3+
from app.api.routes import entities, items, login, private, users, utils
44
from app.core.config import settings
55

66
api_router = APIRouter()
77
api_router.include_router(login.router)
88
api_router.include_router(users.router)
99
api_router.include_router(utils.router)
1010
api_router.include_router(items.router)
11+
api_router.include_router(entities.router)
1112

1213

1314
if settings.ENVIRONMENT == "local":

backend/app/api/routes/entities.py

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
"""Entity CRUD route handlers.
2+
3+
Thin routes that inject authentication and database dependencies, then
4+
delegate all business logic to :mod:`app.services.entity_service`.
5+
"""
6+
7+
import uuid
8+
9+
from fastapi import APIRouter, Query, Response
10+
11+
from app.api.deps import PrincipalDep, SupabaseDep
12+
from app.models import EntitiesPublic, EntityCreate, EntityPublic, EntityUpdate
13+
from app.services import entity_service
14+
15+
router = APIRouter(prefix="/entities", tags=["entities"])
16+
17+
18+
@router.post("/", response_model=EntityPublic, status_code=201)
19+
def create_entity(
20+
supabase: SupabaseDep,
21+
principal: PrincipalDep,
22+
data: EntityCreate,
23+
) -> EntityPublic:
24+
"""Create a new entity owned by the authenticated user."""
25+
return entity_service.create_entity(supabase, data, principal.user_id)
26+
27+
28+
@router.get("/", response_model=EntitiesPublic)
29+
def list_entities(
30+
supabase: SupabaseDep,
31+
principal: PrincipalDep,
32+
offset: int = Query(default=0, ge=0),
33+
limit: int = Query(default=20, ge=1, le=100),
34+
) -> EntitiesPublic:
35+
"""List entities owned by the authenticated user with pagination."""
36+
return entity_service.list_entities(
37+
supabase, principal.user_id, offset=offset, limit=limit
38+
)
39+
40+
41+
@router.get("/{entity_id}", response_model=EntityPublic)
42+
def get_entity(
43+
supabase: SupabaseDep,
44+
principal: PrincipalDep,
45+
entity_id: uuid.UUID,
46+
) -> EntityPublic:
47+
"""Retrieve a single entity by ID."""
48+
return entity_service.get_entity(supabase, str(entity_id), principal.user_id)
49+
50+
51+
@router.patch("/{entity_id}", response_model=EntityPublic)
52+
def update_entity(
53+
supabase: SupabaseDep,
54+
principal: PrincipalDep,
55+
entity_id: uuid.UUID,
56+
data: EntityUpdate,
57+
) -> EntityPublic:
58+
"""Partially update an entity."""
59+
return entity_service.update_entity(
60+
supabase, str(entity_id), principal.user_id, data
61+
)
62+
63+
64+
@router.delete("/{entity_id}", status_code=204)
65+
def delete_entity(
66+
supabase: SupabaseDep,
67+
principal: PrincipalDep,
68+
entity_id: uuid.UUID,
69+
) -> Response:
70+
"""Delete an entity."""
71+
entity_service.delete_entity(supabase, str(entity_id), principal.user_id)
72+
return Response(status_code=204)

0 commit comments

Comments
 (0)