-
Notifications
You must be signed in to change notification settings - Fork 0
Implement agents CRUD endpoints (DEV-70) #20
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
Merged
+164
−9
Merged
Changes from 3 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
dcad66c
Implement agents CRUD endpoints (DEV-71)
sonya-q 3d11499
Merge branch 'main' into feature/DEV-70/agents-crud
sonya-q 61b671d
Fix linting errors (DEV-70)
sonya-q abb3bc8
style: apply Black formatting (DEV-70)
sonya-q 0588b9b
fix: resolve merge conflict in agents.py (DEV-70)
sonya-q e999306
fix: resolve merge conflict in agents.py (DEV-70)
sonya-q 01f0c2d
Reformat, again, with Black (DEV-70)
sonya-q 9cafd34
Refactor agents into services layer (DEV-70)
sonya-q 93b66ac
style: apply Black formatting (DEV-70)
sonya-q fdc7d14
add import
sonya-q 5e9512b
remove unnecessary import (agency) (DEV-70)
sonya-q 031dc03
fix: handle IntegrityError and raise ValueError (DEV-70)
sonya-q ec00698
add: prevent committing when no update fields are provided in update_…
sonya-q 3a39ea7
Merge origin/main into current branch
sonya-q File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,17 +1,120 @@ | ||
| """Agents REST API. | ||
|
|
||
| Full CRUD for agents resource. | ||
| Mirrors agency.py. | ||
| Agents belong to agencies, so POST and PUT validate that the agency_id exists. | ||
| Endpoints for Agency agents (Supabase Auth; link via supabase_user_id). | ||
| """ | ||
|
|
||
| from fastapi import APIRouter, Response, status | ||
| from fastapi import APIRouter, Depends, HTTPException, status | ||
| from sqlalchemy import select | ||
| from sqlalchemy.ext.asyncio import AsyncSession | ||
|
|
||
| from ..database import get_db | ||
| from ..models import Agency, Agent | ||
| from ..schemas import AgentCreate, AgentUpdate, Agent as AgentSchema | ||
|
|
||
sonya-q marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| router = APIRouter() | ||
|
|
||
| # Helpers | ||
kenzysoror marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| async def get_agent_or_404(agent_id: str, db: AsyncSession) -> Agent: | ||
| """ | ||
| Reusable helper: looks up agent by ID | ||
| If not found, raise a 404 immediately | ||
| """ | ||
| result = await db.execute(select(Agent).where(Agent.id == agent_id)) | ||
| agent = result.scalar_one_or_none() | ||
| if not agent: | ||
| raise HTTPException( | ||
| status_code=status.HTTP_404_NOT_FOUND, | ||
| detail = f"Agent with agent ID '{agent_id}' not found", | ||
| ) | ||
| return agent | ||
|
|
||
| async def validate_agency_exists(agency_id: str, db: AsyncSession) -> None: | ||
| """ | ||
| Reusable helper: confirms agency_id refers to real agecny | ||
| If not, raise a 404 error | ||
| """ | ||
| result = await db.execute(select(Agency).where(Agency.id == agency_id)) | ||
| agency = result.scalar_one_or_none() | ||
| if not agency: | ||
| raise HTTPException( | ||
| status_code = status.HTTP_404_NOT_FOUND, | ||
| detail = f"Agency with agency ID '{agency_id}' not found. Cannot assign agent to non-existent agency." | ||
| ) | ||
|
|
||
| @router.get("") | ||
| async def list_agents(): | ||
| """List agents. Placeholder — implement with Agent model and schemas.""" | ||
| return Response( | ||
| content="Not implemented — see docs/STARTER_BACKEND_GUIDE.md", | ||
| status_code=status.HTTP_501_NOT_IMPLEMENTED, | ||
| # Endpoints | ||
|
|
||
| @router.get("", response_model = list[AgentSchema]) | ||
| async def list_agents(db: AsyncSession = Depends(get_db)): | ||
| """ | ||
| GET /api/agents | ||
| Returns list of all agents, ordered by last name, first name | ||
| """ | ||
| result = await db.execute( | ||
| select(Agent).order_by(Agent.last_name, Agent.first_name) | ||
| ) | ||
| return list(result.scalars().all()) | ||
|
|
||
| @router.post("", response_model = AgentSchema, status_code = status.HTTP_201_CREATED) | ||
| async def create_agent(agent: AgentCreate, db: AsyncSession = Depends(get_db)): | ||
| """ | ||
| POST /api/agents | ||
| Creates a new agent | ||
|
|
||
| Validates that the agency_id in the request body refers to a real agency before saving | ||
| Returns 201 Created with the new agent on success | ||
| """ | ||
| await validate_agency_exists(agent.agency_id, db) | ||
|
|
||
| db_agent = Agent(**agent.model_dump()) | ||
|
|
||
| db.add(db_agent) | ||
| await db.commit() | ||
| await db.refresh(db_agent) | ||
| return db_agent | ||
|
|
||
| @router.get("/{agent_id}", response_model = AgentSchema) | ||
| async def get_agent(agent_id: str, db: AsyncSession = Depends(get_db)): | ||
| """ | ||
| GET /api/agents/{agent_id} | ||
| Returns a single agent by ID. | ||
| Returns 404 if not found. | ||
| """ | ||
| return await get_agent_or_404(agent_id, db) | ||
|
|
||
| @router.put("/{agent_id}", response_model=AgentSchema) | ||
| async def update_agent( | ||
| agent_id: str, | ||
| payload: AgentUpdate, | ||
| db: AsyncSession = Depends(get_db), | ||
| ): | ||
| """ | ||
| Updates existing agent. | ||
| Validates agency_id if being changed. | ||
| Returns 404 if agnecy doesn't exist. | ||
| """ | ||
| agent = await get_agent_or_404(agent_id, db) | ||
| data = payload.model_dump(exclude_unset=True) | ||
| if "agency_id" in data: | ||
| await validate_agency_exists(data["agency_id"], db) | ||
|
|
||
| for key, value in data.items(): | ||
| setattr(agent, key, value) | ||
|
|
||
| await db.commit() | ||
| await db.refresh(agent) | ||
| return agent | ||
|
|
||
| @router.delete("/{agent_id}", status_code=status.HTTP_204_NO_CONTENT) | ||
| async def delete_agent(agent_id: str, db: AsyncSession = Depends(get_db)): | ||
| """ | ||
| Deletes an agent permanently. | ||
| Returns 204 No Content on success. | ||
| Returns 404 if the agent doesn't exist. | ||
| """ | ||
| agent = await get_agent_or_404(agent_id, db) | ||
| await db.delete(agent) | ||
| await db.commit() | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.