diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..2071d4e --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,34 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project + +**phoenixvc-dev-api** — Development API for the phoenixvc organization. Built with FastAPI. + +## Status: Integration Review Pending + +Needs analysis of where this fits between the templating repos (azure-project-template, azure-infrastructure), other APIs (ai-gateway, codeflow-engine), and how it serves the phoenixvc ecosystem (cognitive-mesh, PhoenixRooivalk, Mystira, etc.). + +## Tech Stack + +- **Backend**: Python, FastAPI +- **Org**: phoenixvc +- **Environment**: dev + +## Key Commands + +```bash +pip install -r requirements.txt # Install dependencies +uvicorn main:app --reload # Start dev server +pytest # Run tests +mypy . # Type check +``` + +## AgentKit Forge + +This project has not yet been onboarded to [AgentKit Forge](https://github.com/phoenixvc/agentkit-forge). To request onboarding, [create a ticket](https://github.com/phoenixvc/agentkit-forge/issues/new?title=Onboard+phoenixvc-dev-api-fastapi&labels=onboarding). + +## Baton Integration + +Baton is the shared task graph for cross-repo work. When the `baton` MCP server is available, agents should check for existing work with `task_check` at the start of meaningful tasks, create or claim visible work with `task_notify`/`log_agent_message`, update the task when significant new information becomes available, and log completion or blockers before handing off. diff --git a/src/api-hexagonal/domain/entities/user.py b/src/api-hexagonal/domain/entities/user.py index c2add8f..13e9a25 100644 --- a/src/api-hexagonal/domain/entities/user.py +++ b/src/api-hexagonal/domain/entities/user.py @@ -2,15 +2,17 @@ from datetime import datetime from typing import Optional + @dataclass class User: """User domain entity""" + id: Optional[int] email: str name: str password_hash: str created_at: datetime updated_at: datetime - + def is_valid_email(self) -> bool: - return '@' in self.email and '.' in self.email.split('@')[1] + return "@" in self.email and "." in self.email.split("@")[1] diff --git a/src/api-hexagonal/domain/repositories/user_repository.py b/src/api-hexagonal/domain/repositories/user_repository.py index 47c64df..03aa025 100644 --- a/src/api-hexagonal/domain/repositories/user_repository.py +++ b/src/api-hexagonal/domain/repositories/user_repository.py @@ -1,9 +1,12 @@ -from abc import ABC, abstractmethod +from abc import ABC from typing import Optional + from domain.entities.user import User + class UserRepository(ABC): async def create(self, user: User) -> User: pass + async def get_by_id(self, user_id: int) -> Optional[User]: pass diff --git a/src/api-standard/main.py b/src/api-standard/main.py index 7944b9f..01f9224 100644 --- a/src/api-standard/main.py +++ b/src/api-standard/main.py @@ -1,10 +1,13 @@ from fastapi import FastAPI + app = FastAPI() -@app.get('/') + +@app.get("/") async def root(): - return {'message': 'API Standard'} + return {"message": "API Standard"} + -@app.get('/health') +@app.get("/health") async def health(): - return {'status': 'healthy'} + return {"status": "healthy"} diff --git a/tests/unit/test_user_entity.py b/tests/unit/test_user_entity.py new file mode 100644 index 0000000..39686f3 --- /dev/null +++ b/tests/unit/test_user_entity.py @@ -0,0 +1,46 @@ +from datetime import datetime +from importlib.util import module_from_spec, spec_from_file_location +from pathlib import Path + + +def load_user_class(): + user_path = ( + Path(__file__).parents[2] + / "src" + / "api-hexagonal" + / "domain" + / "entities" + / "user.py" + ) + spec = spec_from_file_location("user_entity", user_path) + module = module_from_spec(spec) + spec.loader.exec_module(module) + return module.User + + +def test_user_validates_email_shape(): + User = load_user_class() + user = User( + id=1, + email="user@example.com", + name="Test User", + password_hash="hashed", + created_at=datetime(2026, 1, 1), + updated_at=datetime(2026, 1, 1), + ) + + assert user.is_valid_email() + + +def test_user_rejects_email_without_domain_dot(): + User = load_user_class() + user = User( + id=1, + email="user@example", + name="Test User", + password_hash="hashed", + created_at=datetime(2026, 1, 1), + updated_at=datetime(2026, 1, 1), + ) + + assert not user.is_valid_email()