Fix user emails use userout (#2511)

Follow-up to #2495, actually ensure org subscription data is in included
in admin email response

---------

Co-authored-by: Tessa Walsh <tessa@bitarchivist.net>
This commit is contained in:
Ilya Kreymer 2025-03-24 12:04:39 -07:00 committed by GitHub
parent 46be6a0cf6
commit 21a372057b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 42 additions and 36 deletions

View File

@ -212,28 +212,6 @@ class UserOrgInfoOut(BaseModel):
role: UserRole
# ============================================================================
class UserOut(BaseModel):
"""Output User model"""
id: UUID
name: str = ""
email: EmailStr
is_superuser: bool = False
is_verified: bool = False
orgs: List[UserOrgInfoOut]
# ============================================================================
class UserEmailWithOrgInfo(BaseModel):
"""Output model for getting user email list with org info for each"""
email: EmailStr
orgs: List[UserOrgInfoOut]
# ============================================================================
### CRAWL STATES
@ -1833,6 +1811,8 @@ class SubscriptionCanceledResponse(BaseModel):
canceled: bool
# ============================================================================
# User Org Info With Subs
# ============================================================================
class UserOrgInfoOutWithSubs(UserOrgInfoOut):
"""org per user with sub info"""
@ -1843,6 +1823,24 @@ class UserOrgInfoOutWithSubs(UserOrgInfoOut):
subscription: Optional[Subscription] = None
# ============================================================================
class UserOutNoId(BaseModel):
"""Output User Model, no ID"""
name: str = ""
email: EmailStr
orgs: List[UserOrgInfoOut | UserOrgInfoOutWithSubs]
is_verified: bool = False
# ============================================================================
class UserOut(UserOutNoId):
"""Output User Model"""
id: UUID
is_superuser: bool = False
# ============================================================================
# ORGS
# ============================================================================
@ -2890,10 +2888,10 @@ class PaginatedCrawlErrorResponse(PaginatedResponse):
# ============================================================================
class PaginatedUserEmailsResponse(PaginatedResponse):
class PaginatedUserOutResponse(PaginatedResponse):
"""Response model for user emails with org info"""
items: List[UserEmailWithOrgInfo]
items: List[UserOutNoId]
# ============================================================================

View File

@ -28,6 +28,7 @@ from .models import (
UserOrgInfoOut,
UserOrgInfoOutWithSubs,
UserOut,
UserOutNoId,
UserRole,
InvitePending,
InviteOut,
@ -35,8 +36,7 @@ from .models import (
FailedLogin,
UpdatedResponse,
SuccessResponse,
UserEmailWithOrgInfo,
PaginatedUserEmailsResponse,
PaginatedUserOutResponse,
)
from .pagination import DEFAULT_PAGE_SIZE, paginated_format
from .utils import is_bool, dt_now
@ -166,8 +166,11 @@ class UserManager:
return user
async def get_user_info_with_orgs(
self, user: User, info_out_cls: Type[UserOrgInfoOut] = UserOrgInfoOut
) -> UserOut:
self,
user: User,
info_out_cls: Type[UserOrgInfoOut | UserOrgInfoOutWithSubs] = UserOrgInfoOut,
user_out_cls: Type[UserOut | UserOutNoId] = UserOut,
) -> UserOut | UserOutNoId:
"""return User info"""
user_orgs, _ = await self.org_ops.get_orgs_for_user(
user,
@ -196,7 +199,7 @@ class UserManager:
else:
orgs = []
return UserOut(
return user_out_cls(
id=user.id,
email=user.email,
name=user.name,
@ -558,23 +561,23 @@ class UserManager:
self,
page_size: int = DEFAULT_PAGE_SIZE,
page: int = 1,
) -> Tuple[List[UserEmailWithOrgInfo], int]:
) -> Tuple[List[UserOutNoId], int]:
"""Get user emails with org info for each for paginated endpoint"""
# Zero-index page for query
page = page - 1
skip = page_size * page
emails: List[UserEmailWithOrgInfo] = []
emails: List[UserOutNoId] = []
total = await self.users.count_documents({"is_superuser": False})
async for res in self.users.find(
{"is_superuser": False}, skip=skip, limit=page_size
):
user = User(**res)
user_out = await self.get_user_info_with_orgs(user, UserOrgInfoOutWithSubs)
emails.append(
UserEmailWithOrgInfo(email=user_out.email, orgs=user_out.orgs)
user_out = await self.get_user_info_with_orgs(
user, UserOrgInfoOutWithSubs, UserOutNoId
)
emails.append(user_out)
return emails, total
@ -739,7 +742,7 @@ def init_users_router(
return paginated_format(pending_invites, total, page, pageSize)
@users_router.get(
"/emails", tags=["users"], response_model=PaginatedUserEmailsResponse
"/emails", tags=["users"], response_model=PaginatedUserOutResponse
)
async def get_user_emails(
user: User = Depends(current_active_user),

View File

@ -59,7 +59,6 @@ def test_me_with_orgs(crawler_auth_headers, default_org_id):
data = r.json()
assert data["email"] == CRAWLER_USERNAME_LOWERCASE
assert data["id"]
# assert data["is_active"]
assert data["is_superuser"] is False
assert data["is_verified"] is True
assert data["name"] == "new-crawler"
@ -801,6 +800,9 @@ def test_user_emails_endpoint_superuser(admin_auth_headers, default_org_id):
for user in user_emails:
assert user["email"]
assert "id" not in user
assert "is_superuser" not in user
assert user["is_verified"] == True
orgs = user.get("orgs")
if orgs == []:
continue
@ -810,6 +812,9 @@ def test_user_emails_endpoint_superuser(admin_auth_headers, default_org_id):
assert org["name"]
assert org["slug"]
assert org["default"] in (True, False)
assert "readOnly" in org
assert "readOnlyReason" in org
assert "subscription" in org
role = org["role"]
assert role
assert isinstance(role, int)