feat(auth): /auth/me returns workspaces[] with id/name/slug/role
GET /auth/me now responds with the new UserMeResponse: the existing user fields plus default_workspace_id and a workspaces list that joins WorkspaceRepository.list_by_user with the caller's role from WorkspaceMembershipRepository.list_by_user. Stage 0 every user has exactly one entry there, but the shape is forward-compatible for Stage 2 multi-workspace memberships. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -47,6 +47,26 @@ class UserResponse(BaseModel):
|
||||
needs_setup: bool = False
|
||||
|
||||
|
||||
class UserMeWorkspace(BaseModel):
|
||||
"""One workspace entry in ``GET /auth/me`` (Stage 0 PR4)."""
|
||||
|
||||
id: str
|
||||
name: str
|
||||
slug: str
|
||||
role: str
|
||||
|
||||
|
||||
class UserMeResponse(BaseModel):
|
||||
"""Response model for ``GET /auth/me`` — extends UserResponse with workspaces."""
|
||||
|
||||
id: str
|
||||
email: str
|
||||
system_role: Literal["admin", "user"]
|
||||
needs_setup: bool = False
|
||||
default_workspace_id: str | None = None
|
||||
workspaces: list[UserMeWorkspace] = []
|
||||
|
||||
|
||||
class ActiveWorkspace(BaseModel):
|
||||
"""Lightweight workspace proxy injected into the request-scoped contextvar.
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ from app.gateway.auth import (
|
||||
)
|
||||
from app.gateway.auth.config import get_auth_config
|
||||
from app.gateway.auth.errors import AuthErrorCode, AuthErrorResponse
|
||||
from app.gateway.auth.models import UserMeResponse, UserMeWorkspace
|
||||
from app.gateway.auth.workspace_slug import auto_slug_from_email, next_available_slug
|
||||
from app.gateway.csrf_middleware import is_secure_request
|
||||
from app.gateway.deps import get_current_user_from_request, get_local_provider
|
||||
@@ -447,11 +448,41 @@ async def change_password(request: Request, response: Response, body: ChangePass
|
||||
return MessageResponse(message="Password changed successfully")
|
||||
|
||||
|
||||
@router.get("/me", response_model=UserResponse)
|
||||
@router.get("/me", response_model=UserMeResponse)
|
||||
async def get_me(request: Request):
|
||||
"""Get current authenticated user info."""
|
||||
"""Get current authenticated user info, including the workspaces they belong to."""
|
||||
from deerflow.persistence.engine import get_session_factory
|
||||
from deerflow.persistence.workspace import WorkspaceRepository
|
||||
from deerflow.persistence.workspace_membership import WorkspaceMembershipRepository
|
||||
|
||||
user = await get_current_user_from_request(request)
|
||||
return UserResponse(id=str(user.id), email=user.email, system_role=user.system_role, needs_setup=user.needs_setup)
|
||||
|
||||
sf = get_session_factory()
|
||||
workspaces: list[UserMeWorkspace] = []
|
||||
if sf is not None:
|
||||
ws_repo = WorkspaceRepository(sf)
|
||||
m_repo = WorkspaceMembershipRepository(sf)
|
||||
ws_rows = await ws_repo.list_by_user(user_id=str(user.id))
|
||||
memberships = await m_repo.list_by_user(user_id=str(user.id))
|
||||
role_by_ws = {m["workspace_id"]: m["role"] for m in memberships}
|
||||
workspaces = [
|
||||
UserMeWorkspace(
|
||||
id=w["id"],
|
||||
name=w["name"],
|
||||
slug=w["slug"],
|
||||
role=role_by_ws.get(w["id"], "member"),
|
||||
)
|
||||
for w in ws_rows
|
||||
]
|
||||
|
||||
return UserMeResponse(
|
||||
id=str(user.id),
|
||||
email=user.email,
|
||||
system_role=user.system_role,
|
||||
needs_setup=user.needs_setup,
|
||||
default_workspace_id=user.default_workspace_id,
|
||||
workspaces=workspaces,
|
||||
)
|
||||
|
||||
|
||||
_SETUP_STATUS_COOLDOWN: dict[str, float] = {}
|
||||
|
||||
Reference in New Issue
Block a user