Skip to content

feat(entity): add Entity models, service layer, and database migration [AYG-69]#2210

Closed
amostt wants to merge 8 commits intofastapi:masterfrom
Aygentic:amos/ayg-69-58-entity-models-service-layer-database-migration
Closed

feat(entity): add Entity models, service layer, and database migration [AYG-69]#2210
amostt wants to merge 8 commits intofastapi:masterfrom
Aygentic:amos/ayg-69-58-entity-models-service-layer-database-migration

Conversation

@amostt
Copy link

@amostt amostt commented Feb 28, 2026

Summary

  • Pure Pydantic entity models (EntityBase, EntityCreate, EntityUpdate, EntityPublic, EntitiesPublic) with field validation
  • Service layer with 5 CRUD operations via supabase-py table builder pattern, all enforcing ownership via owner_id
  • Supabase SQL migration: entities table with UUID PK, owner index, updated_at trigger, and 4 RLS policies (including WITH CHECK on UPDATE)
  • 34 unit tests (14 model + 20 service) covering happy paths, edge cases, and error handling

Changes

New Files

File Purpose
backend/app/models/entity.py Entity Pydantic models (Base, Create, Update, Public, collection)
backend/app/services/__init__.py Service layer package marker
backend/app/services/entity_service.py Entity CRUD operations via Supabase REST client
backend/tests/unit/test_entity_models.py 14 model validation tests
backend/tests/unit/test_entity_service.py 20 service layer tests (mocked Supabase)
supabase/migrations/20260227000000_create_entities.sql Database migration with RLS
supabase/config.toml Minimal Supabase project scaffold

Modified Files

File Change
backend/app/models/__init__.py Re-export entity models from package

Acceptance Criteria

  • AC-1: EntityCreate validates and serializes (title="Test Entity", description="A test")
  • AC-2: EntityPublic includes id, title, description, owner_id, created_at, updated_at
  • AC-3: entity_service.create_entity(supabase, data, owner_id) inserts into entities table, returns EntityPublic
  • AC-4: SQL migration creates entities table with correct columns, index, trigger, RLS
  • AC-5: EntityUpdate with no fields is a no-op (fetches and returns current entity)
  • AC-6: EntityCreate with empty title rejected by Pydantic validation
  • AC-7: get_entity for non-existent ID raises ServiceError(404, "ENTITY_NOT_FOUND")
  • AC-8: list_entities with limit > 100 capped at 100
  • AC-9: Supabase insert error caught and re-raised as ServiceError
  • AC-10: Infrastructure errors raise ServiceError(500); API errors (not-found) raise ServiceError(404)

Review Summary

  • Critical: 0 | High: 0 | Medium: 8 | Low: 7
  • All critical/high findings from 3-subagent review were auto-fixed:
    • SEC-001: Removed exception details from error messages (info leak prevention)
    • BUG-001: Added empty-data guard on create_entity insert response
    • BUG-002/SEC-002: Differentiated APIError (404) from infrastructure errors (500) in get_entity
    • SEC-004/FUNC-001: Added WITH CHECK clause to UPDATE RLS policy
    • FUNC-002/SEC-003: Added lower-bound clamping for offset/limit parameters
  • Test Coverage: 34 tests, all passing
  • Security: No critical issues remaining

Test Plan

  • Unit tests passing: uv run pytest tests/unit/test_entity_models.py tests/unit/test_entity_service.py -v (34/34)
  • Full test suite: uv run pytest tests/unit/ tests/integration/ -v (166/166)
  • Lint: uv run ruff check — clean
  • Types: uv run mypy app/services/ app/models/ — clean
  • Manual: Verify SQL migration syntax against Supabase CLI

Template False Positive Notes

Old tests in tests/api/routes/, tests/crud/, tests/scripts/ have pre-existing import errors (from app.models import Item, User) due to the SQLModel → Supabase migration in progress. These are not regressions from AYG-69 — they were broken before this PR by AYG-67's models/ package creation. Tracked for cleanup in a future story.

Related to AYG-64

Generated with Claude Code by Aygentic

amostt and others added 8 commits February 27, 2026 03:54
Rename default branch from master to main across all CI workflows,
documentation, and PRD. Also adds comprehensive project documentation
generated by the initialise skill (CLAUDE.md, docs/ structure).

🤖 Generated by Aygentic

Co-Authored-By: Aygentic <noreply@aygentic.com>
#1)

* feat(core): rewrite config, error handlers, and shared models [AYG-65]

- Replace PostgreSQL/SMTP settings with Supabase+Clerk env vars
- Add frozen Settings with production secret and CORS guards
- Create unified error response shape with global exception handlers
- Add shared Pydantic models: ErrorResponse, ValidationErrorResponse, PaginatedResponse, Principal

Fixes AYG-65

🤖 Generated by Aygentic

Co-Authored-By: Aygentic <noreply@aygentic.com>

* fix(core): untrack .env and guard conftest for migration [AYG-65]

- Remove .env from git tracking and add to .gitignore (SEC-001)
- Guard integration test conftest.py imports with try/except so unit
  tests run without --noconftest during the migration (FUNC-002)

Related to AYG-65

🤖 Generated by Aygentic

Co-Authored-By: Aygentic <noreply@aygentic.com>

* fix(tests): harden test isolation for template usage [AYG-65]

- Catch all exceptions (including ValidationError) in conftest guard so
  pytest doesn't crash when env vars are unset in a fresh template clone
- Explicitly delenv missing vars in test_missing_required_var_raises so
  the test is deterministic regardless of the caller's shell environment

Related to AYG-65

🤖 Generated by Aygentic

Co-Authored-By: Aygentic <noreply@aygentic.com>

* docs(project): update documentation before merge [skip ci]

Updated documentation based on AYG-65 code changes:
- setup.md: replaced PostgreSQL/SMTP/JWT env vars with Supabase/Clerk
- deployment/environments.md: updated all env tables and GitHub Secrets
- architecture/overview.md: added error handling framework, shared models, Clerk auth
- architecture/decisions/: new ADRs for error handling and models package
- api/overview.md: Clerk auth, standard error shape, paginated response pattern
- api/endpoints/login.md: deprecated (Clerk migration)
- api/endpoints/users.md + items.md: updated auth and error response docs
- data/models.md: new Shared Pydantic Models section
- testing/test-registry.md: +36 unit tests, closed config coverage gap

Related to AYG-65

🤖 Generated by Aygentic

Co-Authored-By: Aygentic <noreply@aygentic.com>

---------

Co-authored-by: Aygentic <noreply@aygentic.com>
…YG-66] (#2)

* feat(core): add structured logging and request pipeline middleware [AYG-66]

Add structlog-based structured logging (JSON/console modes) and request
pipeline middleware providing UUID v4 request IDs, correlation ID
propagation, security headers on all responses (including CORS preflight),
and status-based log levels (info/warning/error for 2xx/4xx/5xx).

- Create backend/app/core/logging.py: structlog configuration with
  JSON renderer (production) and ConsoleRenderer (local dev), base
  fields (timestamp, level, event, service, version, environment),
  contextvars integration for request-scoped fields
- Create backend/app/core/middleware.py: RequestPipelineMiddleware
  with request_id generation, X-Correlation-ID propagation, 6 security
  headers (X-Content-Type-Options, X-Frame-Options, X-XSS-Protection,
  Referrer-Policy, Permissions-Policy, HSTS production-only),
  X-Request-ID on every response including error and exception paths
- Wire into main.py with documented middleware ordering (outermost
  wraps CORSMiddleware so preflight OPTIONS get headers too)
- 32 new unit tests (6 logging + 26 middleware) covering CORS preflight
  header behavior, X-Request-ID on 4xx/5xx/exception paths, and
  negative tests proving Authorization/Cookie values never logged

Fixes AYG-66

🤖 Generated by Aygentic

Co-Authored-By: Aygentic <noreply@aygentic.com>

* docs(core): update architecture, test registry, and deployment docs for AYG-66 [skip ci]

Updated documentation based on structured logging and request pipeline middleware implementation.

Related to AYG-66

🤖 Generated by Aygentic

Co-Authored-By: Aygentic <noreply@aygentic.com>

---------

Co-authored-by: Aygentic <noreply@aygentic.com>
…ed deps [AYG-67] (#3)

Implement the three core infrastructure dependencies for the microservice template:

- Supabase client: factory function + FastAPI dependency from app.state
- Clerk JWT auth: validate Bearer tokens, extract Principal with roles,
  map error reasons to structured error codes (AUTH_MISSING_TOKEN,
  AUTH_EXPIRED_TOKEN, AUTH_INVALID_TOKEN)
- HTTP client wrapper: async httpx with retry on 502/503/504,
  exponential backoff, circuit breaker (5 failures/60s window),
  X-Request-ID/X-Correlation-ID header propagation from structlog
- Typed FastAPI dependencies: SupabaseDep, PrincipalDep, HttpClientDep,
  RequestIdDep via Annotated[T, Depends()]
- Lifespan context manager: init Supabase + HttpClient at startup,
  close httpx pool on shutdown
- Added session_id field to Principal model

44 new unit tests (113 total), all passing.

Fixes AYG-67

🤖 Generated by Aygentic

Co-authored-by: Aygentic <noreply@aygentic.com>
#4)

* feat(api): add operational endpoints health/readiness/version [AYG-68]

- Add /healthz, /readyz, /version root endpoints
- Add readiness Supabase check with resilient 503 response path
- Add integration tests for schemas, auth-free access, and failure paths

Fixes AYG-68

🤖 Generated by Aygentic

Co-Authored-By: Aygentic <noreply@aygentic.com>

* fix(api): address code review findings for health endpoints [AYG-68]

- Offload sync Supabase check to thread pool via anyio.to_thread.run_sync
  to avoid blocking the async event loop (BUG-001)
- Use select("*", head=True) for lighter HEAD connectivity probe (QUAL-001)
- Add explicit AttributeError catch for missing Supabase client (QUAL-002)
- Replace str(exc) with error_type= in logger to prevent credential leak (SEC-001)
- Patch settings in test_includes_service_name and test_default_values_for_unset_env_vars
  for CI robustness (TEST-001, TEST-002)
- Strengthen test_exception_does_not_crash to assert full body values (TEST-003)

Related to AYG-68

🤖 Generated by Aygentic

Co-Authored-By: Aygentic <noreply@aygentic.com>

---------

Co-authored-by: Aygentic <noreply@aygentic.com>
…y [skip ci] (#5)

- Create docs/api/endpoints/health.md with full schema docs for /healthz,
  /readyz, and /version including Kubernetes probe YAML examples
- Update docs/api/overview.md to add "Operational Endpoints" section at
  root level and mark legacy /utils/health-check/ as superseded
- Update docs/testing/test-registry.md: add 17 health integration tests,
  coverage 188 → 205 total

Related to AYG-68

🤖 Generated by Aygentic

Co-authored-by: Aygentic <noreply@aygentic.com>
…n [AYG-69]

Implements the entity resource foundation for the microservice starter template:

- Pure Pydantic models: EntityBase, EntityCreate, EntityUpdate, EntityPublic,
  EntitiesPublic with field validation (title 1-255 chars, description <= 1000)
- Service layer with CRUD operations via supabase-py table builder pattern:
  create_entity, get_entity, list_entities, update_entity, delete_entity
- All operations enforce ownership via owner_id filtering
- Pagination with offset/limit (capped at 100, clamped to valid range)
- Structured error handling: APIError → 404, infrastructure errors → 500
- Generic error messages (no exception details leaked to clients)
- Supabase SQL migration: entities table, owner index, updated_at trigger,
  RLS policies with WITH CHECK on UPDATE
- 34 unit tests covering happy paths, edge cases, and error handling

Fixes AYG-69
Related to AYG-64

Generated by Aygentic

Co-Authored-By: Aygentic <noreply@aygentic.com>
@amostt amostt closed this Feb 28, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant