-
Notifications
You must be signed in to change notification settings - Fork 1
feat(api.iam): workspace api routes & dto objects #206
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
…lembic - Add SQLAlchemy 2.0 with asyncpg for async database operations - Add Alembic for schema migrations - Add python-ulid for ULID support instead of UUID - Create read/write engine separation with connection pooling - Create FastAPI dependency injection for database sessions - Create SQLAlchemy declarative base with timestamp mixin - Initialize Alembic with async migration support - Create initial migration for teams table (ULID primary key) - Add comprehensive unit tests for engines and dependencies - Configure Alembic to use settings module for database URL - Enable ruff post-write hook for migration formatting Refs: AIHCM-121
- Add authzed library for SpiceDB integration - Add python-ulid for ULID support - Create ResourceType, RelationType, Permission enums (using Group not Team) - Create AuthorizationProvider protocol for swappable implementations - Implement SpiceDBClient with async methods for relationships and permissions - Create SpiceDB schema (.zed) with Tenant→Workspace→Group hierarchy - Create AuthorizationProbe for domain-oriented observability - Move ObservationContext to shared_kernel (fix architectural boundary) - Add 35 unit tests for types and probes - All 410 tests passing Refs: AIHCM-122
Resolved conflicts in authorization files by accepting remote changes: - shared_kernel/authorization/types.py (docstring fix) - shared_kernel/authorization/spicedb/client.py (_parse_reference helper)
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
…dd route documentation Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
…th TDD Co-Authored-By: Claude Sonnet 4.5 (1M context) <noreply@anthropic.com>
WalkthroughThis pull request introduces a new workspace API feature to the IAM presentation layer. It adds a FastAPI router with four CRUD endpoints (create, get, list, delete) for workspace management. The changes include API request/response models with Pydantic validation, route handlers with authentication and authorization checks, error handling mapped to HTTP status codes, and comprehensive unit test coverage for the endpoints. Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 1
🤖 Fix all issues with AI agents
In `@src/api/iam/presentation/workspaces/routes.py`:
- Around line 104-129: Replace the direct constructor call
WorkspaceId(workspace_id) in get_workspace with
WorkspaceId.from_string(workspace_id) so the ID is validated, and add an except
ValueError handler that raises HTTPException(status_code=400, detail=f"Invalid
workspace id: {workspace_id}") to return Bad Request for malformed IDs; keep the
existing HTTPException re-raise and the generic Exception -> 500 behavior
intact.
| async def get_workspace( | ||
| workspace_id: str, | ||
| current_user: Annotated[CurrentUser, Depends(get_current_user)], | ||
| service: Annotated[WorkspaceService, Depends(get_workspace_service)], | ||
| ) -> WorkspaceResponse: | ||
| """Get workspace by ID.""" | ||
| try: | ||
| workspace_id_obj = WorkspaceId(workspace_id) | ||
|
|
||
| workspace = await service.get_workspace(workspace_id_obj) | ||
|
|
||
| if workspace is None: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_404_NOT_FOUND, | ||
| detail=f"Workspace {workspace_id} not found", | ||
| ) | ||
|
|
||
| return WorkspaceResponse.from_domain(workspace) | ||
|
|
||
| except HTTPException: | ||
| raise | ||
| except Exception: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, | ||
| detail="Failed to retrieve workspace", | ||
| ) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧩 Analysis chain
🏁 Script executed:
# Find WorkspaceId class definition
fd -t f -e py | xargs rg "class WorkspaceId" -A 10Repository: openshift-hyperfleet/kartograph
Length of output: 669
🏁 Script executed:
# Also check for WorkspaceId imports in the routes file
cat -n src/api/iam/presentation/workspaces/routes.py | head -20Repository: openshift-hyperfleet/kartograph
Length of output: 857
🏁 Script executed:
# Search for WorkspaceId definition more broadly
rg "class WorkspaceId" -B 2 -A 15Repository: openshift-hyperfleet/kartograph
Length of output: 1056
🏁 Script executed:
# Find BaseId class definition
rg "class BaseId" -B 2 -A 20Repository: openshift-hyperfleet/kartograph
Length of output: 1482
🏁 Script executed:
# Get the entire value_objects.py file to see complete BaseId implementation
wc -l src/api/iam/domain/value_objects.pyRepository: openshift-hyperfleet/kartograph
Length of output: 114
🏁 Script executed:
# Read the value_objects.py file to see BaseId __post_init__ and any validation
cat -n src/api/iam/domain/value_objects.py | head -100Repository: openshift-hyperfleet/kartograph
Length of output: 3086
🏁 Script executed:
# Read the rest of the file to see if there's __post_init__ validation
cat -n src/api/iam/domain/value_objects.py | tail -100Repository: openshift-hyperfleet/kartograph
Length of output: 2926
🏁 Script executed:
# Check the exact code at lines 104-129 in the routes file
sed -n '104,129p' src/api/iam/presentation/workspaces/routes.pyRepository: openshift-hyperfleet/kartograph
Length of output: 917
🏁 Script executed:
# Check if from_string() is used elsewhere in the codebase with IDs
rg "\.from_string\(" src/api/iam/presentation/ -B 2 -A 2Repository: openshift-hyperfleet/kartograph
Length of output: 4007
🏁 Script executed:
# Check how other endpoints instantiate ID objects
rg "WorkspaceId\(" src/api/iam/presentation/ -B 2 -A 2Repository: openshift-hyperfleet/kartograph
Length of output: 1230
🏁 Script executed:
# Check one of the tenant routes to see exactly how from_string is handled
sed -n '1,150p' src/api/iam/presentation/tenants/routes.py | grep -A 8 "from_string"Repository: openshift-hyperfleet/kartograph
Length of output: 377
Use WorkspaceId.from_string() to validate ID format and return 400 for invalid IDs.
The direct constructor WorkspaceId(workspace_id) accepts any string without validation. Other routes (tenants, groups, API keys) use .from_string() which validates ULID format and raises ValueError for malformed IDs. Replace the direct instantiation with .from_string() and add a dedicated ValueError handler to return 400, consistent with the rest of the codebase.
🛠️ Suggested fix
try:
- workspace_id_obj = WorkspaceId(workspace_id)
+ workspace_id_obj = WorkspaceId.from_string(workspace_id)
workspace = await service.get_workspace(workspace_id_obj)
if workspace is None:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail=f"Workspace {workspace_id} not found",
)
return WorkspaceResponse.from_domain(workspace)
+ except ValueError as e:
+ raise HTTPException(
+ status_code=status.HTTP_400_BAD_REQUEST,
+ detail=f"Invalid workspace ID format: {e}",
+ ) from e
except HTTPException:
raise
except Exception:🤖 Prompt for AI Agents
In `@src/api/iam/presentation/workspaces/routes.py` around lines 104 - 129,
Replace the direct constructor call WorkspaceId(workspace_id) in get_workspace
with WorkspaceId.from_string(workspace_id) so the ID is validated, and add an
except ValueError handler that raises HTTPException(status_code=400,
detail=f"Invalid workspace id: {workspace_id}") to return Bad Request for
malformed IDs; keep the existing HTTPException re-raise and the generic
Exception -> 500 behavior intact.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
accepting as OK for now.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@jsell-rh, understood. Thank you for the feedback!
Summary by CodeRabbit
Release Notes
New Features
Tests