fix: serialize Client/Team/Project with id not _id + guard undefined client hooks
Pydantic v2 + FastAPI serializes Field(alias="_id") as _id in JSON,
so client.id was always undefined on the frontend — causing option
values to fall back to text content ("3M") and firing /clients/3M/teams 404s.
- Remove Field(alias="_id") from Client/Team/Project models; id is now a
plain string field populated explicitly in _client_from_doc etc.
- API now returns id not _id, matching the TypeScript Client interface
- Add clientId !== "undefined" guard to useTeams, usePMs, useProjects
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
723bbbc695
commit
269ab09fa6
3 changed files with 32 additions and 20 deletions
|
|
@ -64,15 +64,36 @@ async def _get_project_or_404(project_id: str, client_id: str, db: AsyncIOMotorD
|
|||
|
||||
|
||||
def _client_from_doc(doc: dict) -> Client:
|
||||
return Client(**{**doc, "_id": str(doc["_id"])})
|
||||
return Client(
|
||||
id=str(doc["_id"]),
|
||||
name=doc["name"],
|
||||
slug=doc["slug"],
|
||||
is_active=doc.get("is_active", True),
|
||||
created_at=doc.get("created_at"),
|
||||
updated_at=doc.get("updated_at"),
|
||||
)
|
||||
|
||||
|
||||
def _team_from_doc(doc: dict) -> Team:
|
||||
return Team(**{**doc, "_id": str(doc["_id"])})
|
||||
return Team(
|
||||
id=str(doc["_id"]),
|
||||
name=doc["name"],
|
||||
client_id=doc["client_id"],
|
||||
member_user_ids=doc.get("member_user_ids", []),
|
||||
created_at=doc.get("created_at"),
|
||||
updated_at=doc.get("updated_at"),
|
||||
)
|
||||
|
||||
|
||||
def _project_from_doc(doc: dict) -> Project:
|
||||
return Project(**{**doc, "_id": str(doc["_id"])})
|
||||
return Project(
|
||||
id=str(doc["_id"]),
|
||||
name=doc["name"],
|
||||
client_id=doc["client_id"],
|
||||
is_active=doc.get("is_active", True),
|
||||
created_at=doc.get("created_at"),
|
||||
updated_at=doc.get("updated_at"),
|
||||
)
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ from datetime import datetime
|
|||
from typing import Optional, Annotated
|
||||
|
||||
from bson import ObjectId
|
||||
from pydantic import BaseModel, Field, BeforeValidator
|
||||
from pydantic import BaseModel, BeforeValidator
|
||||
|
||||
|
||||
def validate_object_id(v) -> str:
|
||||
|
|
@ -17,16 +17,13 @@ PyObjectId = Annotated[str, BeforeValidator(validate_object_id)]
|
|||
|
||||
|
||||
class Client(BaseModel):
|
||||
id: Optional[PyObjectId] = Field(None, alias="_id")
|
||||
id: Optional[str] = None
|
||||
name: str
|
||||
slug: str # lowercase, URL-safe identifier, unique
|
||||
slug: str
|
||||
is_active: bool = True
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
populate_by_name = True
|
||||
|
||||
|
||||
class ClientCreate(BaseModel):
|
||||
name: str
|
||||
|
|
@ -40,16 +37,13 @@ class ClientUpdate(BaseModel):
|
|||
|
||||
|
||||
class Team(BaseModel):
|
||||
id: Optional[PyObjectId] = Field(None, alias="_id")
|
||||
id: Optional[str] = None
|
||||
name: str
|
||||
client_id: str
|
||||
member_user_ids: list[str] = []
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
populate_by_name = True
|
||||
|
||||
|
||||
class TeamCreate(BaseModel):
|
||||
name: str
|
||||
|
|
@ -60,16 +54,13 @@ class TeamUpdate(BaseModel):
|
|||
|
||||
|
||||
class Project(BaseModel):
|
||||
id: Optional[PyObjectId] = Field(None, alias="_id")
|
||||
id: Optional[str] = None
|
||||
name: str
|
||||
client_id: str
|
||||
is_active: bool = True
|
||||
created_at: Optional[datetime] = None
|
||||
updated_at: Optional[datetime] = None
|
||||
|
||||
class Config:
|
||||
populate_by_name = True
|
||||
|
||||
|
||||
class ProjectCreate(BaseModel):
|
||||
name: str
|
||||
|
|
|
|||
|
|
@ -97,7 +97,7 @@ export function usePMs(clientId: string) {
|
|||
return useQuery({
|
||||
queryKey: ['clients', clientId, 'pm'],
|
||||
queryFn: () => apiClient.listPMs(clientId),
|
||||
enabled: !!clientId,
|
||||
enabled: !!clientId && clientId !== 'undefined',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -123,7 +123,7 @@ export function useTeams(clientId: string) {
|
|||
return useQuery({
|
||||
queryKey: ['clients', clientId, 'teams'],
|
||||
queryFn: () => apiClient.listTeams(clientId),
|
||||
enabled: !!clientId,
|
||||
enabled: !!clientId && clientId !== 'undefined',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -176,7 +176,7 @@ export function useProjects(clientId: string) {
|
|||
return useQuery({
|
||||
queryKey: ['clients', clientId, 'projects'],
|
||||
queryFn: () => apiClient.listProjects(clientId),
|
||||
enabled: !!clientId,
|
||||
enabled: !!clientId && clientId !== 'undefined',
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue