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:
1445043649
2026-05-12 22:37:29 +08:00
parent 634e5119e1
commit 91846a201e
3 changed files with 144 additions and 3 deletions
+20
View File
@@ -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.
+34 -3
View File
@@ -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] = {}