fix: type member invitation API response

This commit is contained in:
yyh
2026-07-13 16:36:00 +08:00
parent e35187f376
commit 2980ae722e
3 changed files with 112 additions and 30 deletions

View File

@ -1,4 +1,5 @@
from http import HTTPStatus
from typing import Annotated, Literal
from urllib import parse
from uuid import UUID
@ -43,7 +44,7 @@ from services.feature_service import FeatureService
class MemberInvitePayload(BaseModel):
emails: list[str] = Field(default_factory=list)
emails: list[str] = Field(min_length=1)
role: str
language: str | None = None
@ -70,11 +71,28 @@ class OwnerTransferPayload(BaseModel):
token: str
class MemberInviteResultResponse(ResponseModel):
status: str
class MemberInviteSuccessResponse(ResponseModel):
status: Literal["success"]
email: str
url: str | None = None
message: str | None = None
url: str
class MemberInviteAlreadyMemberResponse(ResponseModel):
status: Literal["already_member"]
email: str
message: str
class MemberInviteFailedResponse(ResponseModel):
status: Literal["failed"]
email: str
message: str
MemberInviteResultResponse = Annotated[
MemberInviteSuccessResponse | MemberInviteAlreadyMemberResponse | MemberInviteFailedResponse,
Field(discriminator="status"),
]
class MemberActionResponse(ResponseModel):
@ -83,7 +101,7 @@ class MemberActionResponse(ResponseModel):
class MemberInviteResponse(ResponseModel):
result: str
result: Literal["success"]
invitation_results: list[MemberInviteResultResponse]
tenant_id: str
@ -103,7 +121,9 @@ register_response_schema_models(
AccountWithRoleListResponse,
MemberActionResponse,
MemberInviteResponse,
MemberInviteResultResponse,
MemberInviteSuccessResponse,
MemberInviteAlreadyMemberResponse,
MemberInviteFailedResponse,
SimpleResultDataResponse,
SimpleResultResponse,
VerificationTokenResponse,
@ -279,7 +299,7 @@ class MemberInviteEmailApi(Resource):
)
encoded_invitee_email = parse.quote(invitee_email)
invitation_results.append(
MemberInviteResultResponse(
MemberInviteSuccessResponse(
status="success",
email=invitee_email,
url=f"{console_web_url}/activate?email={encoded_invitee_email}&token={token}",
@ -287,7 +307,7 @@ class MemberInviteEmailApi(Resource):
)
except AccountAlreadyInTenantError:
invitation_results.append(
MemberInviteResultResponse(
MemberInviteAlreadyMemberResponse(
status="already_member",
email=invitee_email,
message="Account already in workspace.",
@ -295,14 +315,20 @@ class MemberInviteEmailApi(Resource):
)
except Exception as e:
invitation_results.append(
MemberInviteResultResponse(status="failed", email=invitee_email, message=str(e))
MemberInviteFailedResponse(status="failed", email=invitee_email, message=str(e))
)
return MemberInviteResponse(
result="success",
invitation_results=invitation_results,
tenant_id=inviter.current_tenant.id if inviter.current_tenant else "",
).model_dump(mode="json"), HTTPStatus.CREATED
return (
dump_response(
MemberInviteResponse,
{
"result": "success",
"invitation_results": invitation_results,
"tenant_id": inviter.current_tenant.id if inviter.current_tenant else "",
},
),
HTTPStatus.CREATED,
)
@console_ns.route("/workspaces/current/members/<uuid:member_id>")

View File

@ -161,14 +161,24 @@ export type EndpointUpdatePayload = {
}
export type MemberInvitePayload = {
emails?: Array<string>
emails: Array<string>
language?: string | null
role: string
}
export type MemberInviteResponse = {
invitation_results: Array<MemberInviteResultResponse>
result: string
invitation_results: Array<
| ({
status: 'success'
} & MemberInviteSuccessResponse)
| ({
status: 'already_member'
} & MemberInviteAlreadyMemberResponse)
| ({
status: 'failed'
} & MemberInviteFailedResponse)
>
result: 'success'
tenant_id: string
}
@ -1128,11 +1138,22 @@ export type EndpointListItemResponse = {
url: string
}
export type MemberInviteResultResponse = {
export type MemberInviteSuccessResponse = {
email: string
message?: string | null
status: string
url?: string | null
status: 'success'
url: string
}
export type MemberInviteAlreadyMemberResponse = {
email: string
message: string
status: 'already_member'
}
export type MemberInviteFailedResponse = {
email: string
message: string
status: 'failed'
}
export type ProviderResponse = {

View File

@ -90,7 +90,7 @@ export const zEndpointUpdatePayload = z.object({
* MemberInvitePayload
*/
export const zMemberInvitePayload = z.object({
emails: z.array(z.string()).optional(),
emails: z.array(z.string()).min(1),
language: z.string().nullish(),
role: z.string(),
})
@ -816,21 +816,56 @@ export const zAccountWithRoleListResponse = z.object({
})
/**
* MemberInviteResultResponse
* MemberInviteSuccessResponse
*/
export const zMemberInviteResultResponse = z.object({
export const zMemberInviteSuccessResponse = z.object({
email: z.string(),
message: z.string().nullish(),
status: z.string(),
url: z.string().nullish(),
status: z.literal('success'),
url: z.string(),
})
/**
* MemberInviteAlreadyMemberResponse
*/
export const zMemberInviteAlreadyMemberResponse = z.object({
email: z.string(),
message: z.string(),
status: z.literal('already_member'),
})
/**
* MemberInviteFailedResponse
*/
export const zMemberInviteFailedResponse = z.object({
email: z.string(),
message: z.string(),
status: z.literal('failed'),
})
/**
* MemberInviteResponse
*/
export const zMemberInviteResponse = z.object({
invitation_results: z.array(zMemberInviteResultResponse),
result: z.string(),
invitation_results: z.array(
z.union([
z
.object({
status: z.literal('success'),
})
.and(zMemberInviteSuccessResponse),
z
.object({
status: z.literal('already_member'),
})
.and(zMemberInviteAlreadyMemberResponse),
z
.object({
status: z.literal('failed'),
})
.and(zMemberInviteFailedResponse),
]),
),
result: z.literal('success'),
tenant_id: z.string(),
})