2026-07-09 18:15:57 +08:00

624 lines
23 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package grpc 将 user-service 认证、用户和短号用例适配成 protobuf gRPC 服务。
package grpc
import (
userv1 "hyapp.local/api/proto/user/v1"
authdomain "hyapp/services/user-service/internal/domain/auth"
hostdomain "hyapp/services/user-service/internal/domain/host"
invitedomain "hyapp/services/user-service/internal/domain/invite"
mictimedomain "hyapp/services/user-service/internal/domain/mictime"
userdomain "hyapp/services/user-service/internal/domain/user"
authservice "hyapp/services/user-service/internal/service/auth"
userservice "hyapp/services/user-service/internal/service/user"
)
// toProtoApp 把 App 注册表事实转换为 protobuf 投影。
func toProtoApp(app userdomain.App) *userv1.App {
return &userv1.App{
AppId: app.AppID,
AppCode: app.AppCode,
AppName: app.AppName,
PackageName: app.PackageName,
Platform: app.Platform,
Status: app.Status,
CreatedAtMs: app.CreatedAtMs,
UpdatedAtMs: app.UpdatedAtMs,
}
}
// toProtoToken 把领域令牌转换为 protobuf 响应。
func toProtoToken(token authdomain.Token) *userv1.AuthToken {
// Token 中包含 refresh token 原文,只能出现在登录/刷新响应路径。
return &userv1.AuthToken{
AppCode: token.AppCode,
UserId: token.UserID,
DisplayUserId: token.DisplayUserID,
DefaultDisplayUserId: token.DefaultDisplayUserID,
DisplayUserIdKind: token.DisplayUserIDKind,
DisplayUserIdExpiresAtMs: token.DisplayUserIDExpiresAtMs,
ProfileCompleted: token.ProfileCompleted,
OnboardingStatus: token.OnboardingStatus,
SessionId: token.SessionID,
AccessToken: token.AccessToken,
RefreshToken: token.RefreshToken,
ExpiresInSec: token.ExpiresInSec,
TokenType: token.TokenType,
}
}
func toProtoRegisterRiskConfig(config authdomain.RegisterRiskConfig) *userv1.RegisterRiskConfig {
return &userv1.RegisterRiskConfig{
AppCode: config.AppCode,
MaxAccountsPerDevice: config.MaxAccountsPerDevice,
UpdatedAtMs: config.UpdatedAtMs,
UpdatedByAdminId: config.UpdatedByAdminID,
}
}
// toProtoUser 把领域用户转换为 protobuf 用户投影。
func toProtoUser(user userdomain.User) *userv1.User {
// User proto 只暴露用户主状态和当前展示短号,不包含密码、三方身份或房间状态。
return &userv1.User{
AppCode: user.AppCode,
UserId: user.UserID,
Status: toProtoStatus(user.Status),
CreatedAtMs: user.CreatedAtMs,
UpdatedAtMs: user.UpdatedAtMs,
DisplayUserId: user.CurrentDisplayUserID,
DefaultDisplayUserId: user.DefaultDisplayUserID,
DisplayUserIdKind: string(user.CurrentDisplayUserIDKind),
DisplayUserIdExpiresAtMs: user.CurrentDisplayUserIDExpiresAtMs,
Username: user.Username,
Gender: user.Gender,
Country: user.Country,
Avatar: user.Avatar,
ProfileBgImg: user.ProfileBgImg,
ContactInfo: user.ContactInfo,
WithdrawUsdtTrc20Address: user.WithdrawUSDTTRC20Address,
Birth: user.BirthDate,
Language: user.Language,
CountryId: user.CountryID,
CountryName: user.CountryName,
CountryDisplayName: user.CountryDisplayName,
RegionId: user.RegionID,
RegionCode: user.RegionCode,
RegionName: user.RegionName,
ProfileCompleted: user.ProfileCompleted,
ProfileCompletedAtMs: user.ProfileCompletedAtMs,
OnboardingStatus: string(user.OnboardingStatus),
IsoNumeric: user.ISONumeric,
PhoneCountryCode: user.PhoneCountryCode,
CountryEnabled: user.CountryEnabled,
Invite: toProtoInviteOverview(user.InviteOverview),
PrettyId: user.PrettyID,
PrettyDisplayUserId: user.PrettyDisplayUserID,
}
}
func toProtoBusinessUserLookupItem(item userdomain.BusinessUserLookupItem) *userv1.BusinessUserLookupItem {
return &userv1.BusinessUserLookupItem{
UserId: item.UserID,
DisplayUserId: item.DisplayUserID,
Username: item.Username,
Avatar: item.Avatar,
Status: toProtoStatus(item.Status),
RegionId: item.RegionID,
RegionCode: item.RegionCode,
RegionName: item.RegionName,
}
}
func toProtoManagerUserBlock(block userservice.ManagerUserBlock) *userv1.ManagerUserBlock {
// 经理封禁记录是 H5 弹窗的列表数据;这里仅暴露可展示和可操作的稳定字段。
return &userv1.ManagerUserBlock{
BlockId: block.BlockID,
ManagerUserId: block.ManagerUserID,
TargetUserId: block.TargetUserID,
TargetDisplayUserId: block.TargetDisplayUserID,
TargetUsername: block.TargetUsername,
TargetAvatar: block.TargetAvatar,
Country: block.Country,
BlockedUntilMs: block.BlockedUntilMS,
Status: block.Status,
Reason: block.Reason,
CreatedAtMs: block.CreatedAtMS,
UpdatedAtMs: block.UpdatedAtMS,
}
}
func toProtoInviteOverview(overview invitedomain.Overview) *userv1.InviteOverview {
if overview.MyInviteCode == "" && !overview.InviteEnabled && overview.InviteCount == 0 && overview.ValidInviteCount == 0 && overview.ValidInviteThresholdCoin == 0 {
return nil
}
return &userv1.InviteOverview{
MyInviteCode: overview.MyInviteCode,
InviteEnabled: overview.InviteEnabled,
InviteCount: overview.InviteCount,
ValidInviteCount: overview.ValidInviteCount,
ValidInviteThresholdCoin: overview.ValidInviteThresholdCoin,
}
}
func toProtoInviteBinding(binding invitedomain.Binding) *userv1.InviteBinding {
if !binding.Bound && binding.InviteCode == "" && binding.InviterUserID == 0 {
return nil
}
return &userv1.InviteBinding{
Bound: binding.Bound,
InviteCode: binding.InviteCode,
InviterUserId: binding.InviterUserID,
}
}
func toProtoInviteAttribution(attribution invitedomain.Attribution) *userv1.InviteAttribution {
return &userv1.InviteAttribution{
Found: attribution.Found,
AppCode: attribution.AppCode,
InvitedUserId: attribution.InvitedUserID,
InviterUserId: attribution.InviterUserID,
InviteCode: attribution.InviteCode,
BoundAtMs: attribution.BoundAtMS,
ValidityStatus: attribution.ValidityStatus,
AccumulatedRechargeCoinAmount: attribution.AccumulatedRechargeCoinAmount,
RequiredRechargeCoinAmount: attribution.RequiredRechargeCoinAmount,
ValidAtMs: attribution.ValidAtMS,
}
}
func toProtoMicLifetimeStats(stats mictimedomain.LifetimeStats) *userv1.UserMicLifetimeStats {
return &userv1.UserMicLifetimeStats{
AppCode: stats.AppCode,
UserId: stats.UserID,
SeatOccupiedMs: stats.SeatOccupiedMs,
MicOnlineMs: stats.MicOnlineMs,
SessionCount: stats.SessionCount,
FirstMicAtMs: stats.FirstMicAtMs,
LastMicAtMs: stats.LastMicAtMs,
UpdatedAtMs: stats.UpdatedAtMs,
}
}
func toProtoProfileStats(stats userdomain.ProfileStats) *userv1.UserProfileStats {
return &userv1.UserProfileStats{
AppCode: stats.AppCode,
UserId: stats.UserID,
VisitorsCount: stats.VisitorsCount,
FollowingCount: stats.FollowingCount,
FriendsCount: stats.FriendsCount,
UpdatedAtMs: stats.UpdatedAtMs,
}
}
func toProtoProfileVisitRecord(record userdomain.ProfileVisitRecord) *userv1.ProfileVisitRecord {
return &userv1.ProfileVisitRecord{
VisitorUserId: record.VisitorUserID,
TargetUserId: record.TargetUserID,
VisitCount: record.VisitCount,
LastVisitedAtMs: record.LastVisitedAtMs,
}
}
func toProtoFollowRecord(record userdomain.FollowRecord) *userv1.FollowRecord {
return &userv1.FollowRecord{
FollowerUserId: record.FollowerUserID,
FolloweeUserId: record.FolloweeUserID,
FollowedAtMs: record.FollowedAtMs,
}
}
func toProtoFriendRecord(record userdomain.FriendRecord) *userv1.FriendRecord {
return &userv1.FriendRecord{
UserId: record.UserID,
FriendUserId: record.FriendUserID,
FriendedAtMs: record.FriendedAtMs,
}
}
func toProtoFriendApplication(application userdomain.FriendApplication) *userv1.FriendApplication {
return &userv1.FriendApplication{
RequesterUserId: application.RequesterUserID,
TargetUserId: application.TargetUserID,
Status: application.Status,
CreatedAtMs: application.CreatedAtMs,
UpdatedAtMs: application.UpdatedAtMs,
}
}
func toProtoUserReport(report userdomain.Report) *userv1.UserReport {
return &userv1.UserReport{
ReportId: report.ReportID,
ReporterUserId: report.ReporterUserID,
TargetType: report.TargetType,
UserId: report.UserID,
RoomId: report.RoomID,
ReportType: report.ReportType,
Reason: report.Reason,
ImageUrls: append([]string{}, report.ImageURLs...),
Status: report.Status,
CreatedAtMs: report.CreatedAtMs,
}
}
// toProtoCountry 把国家领域对象转换为管理 RPC 响应。
func toProtoCountry(country userdomain.Country) *userv1.Country {
return &userv1.Country{
AppCode: country.AppCode,
CountryId: country.CountryID,
CountryName: country.CountryName,
CountryCode: country.CountryCode,
CountryDisplayName: country.CountryDisplayName,
SortOrder: country.SortOrder,
CreatedAtMs: country.CreatedAtMs,
UpdatedAtMs: country.UpdatedAtMs,
IsoAlpha3: country.ISOAlpha3,
IsoNumeric: country.ISONumeric,
PhoneCountryCode: country.PhoneCountryCode,
Enabled: country.Enabled,
Flag: country.Flag,
}
}
// toProtoRegion 把区域领域对象转换为管理 RPC 响应。
func toProtoRegion(region userdomain.Region) *userv1.Region {
return &userv1.Region{
AppCode: region.AppCode,
RegionId: region.RegionID,
RegionCode: region.RegionCode,
Name: region.Name,
Status: region.Status,
Countries: append([]string{}, region.Countries...),
SortOrder: region.SortOrder,
CreatedAtMs: region.CreatedAtMs,
UpdatedAtMs: region.UpdatedAtMs,
}
}
// toProtoIdentity 把短号领域投影转换为 protobuf 响应。
func toProtoIdentity(identity userdomain.Identity) *userv1.UserIdentity {
// Identity proto 用于短号解析和修改结果,字段语义与领域 Identity 保持一致。
// PrettyId/PrettyDisplayUserId 只表示当前 active 靓号,没有靓号时为空,不回填默认短号。
return &userv1.UserIdentity{
AppCode: identity.AppCode,
UserId: identity.UserID,
DisplayUserId: identity.DisplayUserID,
Status: string(identity.Status),
DefaultDisplayUserId: identity.DefaultDisplayUserID,
DisplayUserIdKind: string(identity.DisplayUserIDKind),
DisplayUserIdExpiresAtMs: identity.DisplayUserIDExpiresAtMs,
PrettyId: identity.PrettyID,
PrettyDisplayUserId: identity.PrettyDisplayUserID,
}
}
func toProtoPrettyDisplayIDPool(pool userdomain.PrettyDisplayIDPool) *userv1.PrettyDisplayIDPool {
// 池配置是后台和 App 可申请列表共用的展示信息,等级轨道和区间必须原样透出。
return &userv1.PrettyDisplayIDPool{
AppCode: pool.AppCode,
PoolId: pool.PoolID,
Name: pool.Name,
LevelTrack: pool.LevelTrack,
MinLevel: pool.MinLevel,
MaxLevel: pool.MaxLevel,
RuleType: pool.RuleType,
RuleConfigJson: pool.RuleConfigJSON,
Status: pool.Status,
SortOrder: pool.SortOrder,
CreatedByAdminId: pool.CreatedByAdminID,
UpdatedByAdminId: pool.UpdatedByAdminID,
CreatedAtMs: pool.CreatedAtMs,
UpdatedAtMs: pool.UpdatedAtMs,
}
}
func toProtoPrettyDisplayID(item userdomain.PrettyDisplayID) *userv1.PrettyDisplayID {
// 后台发放号码没有 pool_idPool 会是零值;前端以 source 判断是否展示池信息。
return &userv1.PrettyDisplayID{
AppCode: item.AppCode,
PrettyId: item.PrettyID,
PoolId: item.PoolID,
Source: item.Source,
DisplayUserId: item.DisplayUserID,
Status: item.Status,
AssignedUserId: item.AssignedUserID,
AssignedLeaseId: item.AssignedLeaseID,
AssignedAtMs: item.AssignedAtMs,
ReleasedAtMs: item.ReleasedAtMs,
ReleaseReason: item.ReleaseReason,
GeneratedBatchId: item.GeneratedBatchID,
CreatedByAdminId: item.CreatedByAdminID,
CreatedAtMs: item.CreatedAtMs,
UpdatedAtMs: item.UpdatedAtMs,
Pool: toProtoPrettyDisplayIDPool(item.Pool),
}
}
func toProtoPrettyDisplayIDBatch(batch userdomain.PrettyDisplayIDGenerationBatch) *userv1.PrettyDisplayIDGenerationBatch {
// 批次响应保留 requested/generated/skipped 三个计数,后台可以直接解释生成不足是否来自冲突。
return &userv1.PrettyDisplayIDGenerationBatch{
AppCode: batch.AppCode,
BatchId: batch.BatchID,
PoolId: batch.PoolID,
RuleType: batch.RuleType,
RuleConfigJson: batch.RuleConfigJSON,
RequestedCount: batch.RequestedCount,
GeneratedCount: batch.GeneratedCount,
SkippedConflictCount: batch.SkippedConflictCount,
Status: batch.Status,
OperatorAdminId: batch.OperatorAdminID,
RequestId: batch.RequestID,
CreatedAtMs: batch.CreatedAtMs,
UpdatedAtMs: batch.UpdatedAtMs,
}
}
// toProtoStatus 固定未知状态的安全默认值。
func toProtoStatus(status userdomain.Status) userv1.UserStatus {
// 状态枚举转换集中在这里,未知状态使用 UNSPECIFIED 安全兜底。
switch status {
case userdomain.StatusActive:
return userv1.UserStatus_USER_STATUS_ACTIVE
case userdomain.StatusDisabled:
return userv1.UserStatus_USER_STATUS_DISABLED
case userdomain.StatusBanned:
return userv1.UserStatus_USER_STATUS_BANNED
default:
return userv1.UserStatus_USER_STATUS_UNSPECIFIED
}
}
func fromProtoStatus(status userv1.UserStatus) userdomain.Status {
switch status {
case userv1.UserStatus_USER_STATUS_ACTIVE:
return userdomain.StatusActive
case userv1.UserStatus_USER_STATUS_DISABLED:
return userdomain.StatusDisabled
case userv1.UserStatus_USER_STATUS_BANNED:
return userdomain.StatusBanned
default:
return ""
}
}
// authMeta 提取认证链路审计所需的 gateway 透传字段。
func authMeta(meta *userv1.RequestMeta) authservice.Meta {
if meta == nil {
// meta 可选,缺失时审计字段为空但不能让 RPC panic。
return authservice.Meta{}
}
// 只提取认证审计需要的字段device_id 在各 RPC 中单独传入。
return authservice.Meta{
AppCode: meta.GetAppCode(),
RequestID: meta.GetRequestId(),
ClientIP: meta.GetClientIp(),
UserAgent: meta.GetUserAgent(),
CountryByIP: meta.GetCountryByIp(),
Platform: meta.GetPlatform(),
Language: meta.GetLanguage(),
Timezone: meta.GetTimezone(),
}
}
// toProtoHostProfile 把 host profile 事实转换为内部 gRPC 投影。
func toProtoHostProfile(profile hostdomain.HostProfile) *userv1.HostProfile {
if profile.UserID == 0 {
return nil
}
return &userv1.HostProfile{
UserId: profile.UserID,
Status: profile.Status,
RegionId: profile.RegionID,
CurrentAgencyId: profile.CurrentAgencyID,
CurrentMembershipId: profile.CurrentMembershipID,
CurrentAgencyOwnerUserId: profile.CurrentAgencyOwnerUserID,
Source: profile.Source,
FirstBecameHostAtMs: profile.FirstBecameHostAtMs,
CreatedAtMs: profile.CreatedAtMs,
UpdatedAtMs: profile.UpdatedAtMs,
}
}
// toProtoAgency 把 Agency 事实转换为内部 gRPC 投影。
func toProtoAgency(agency hostdomain.Agency) *userv1.Agency {
if agency.AgencyID == 0 {
return nil
}
return &userv1.Agency{
AgencyId: agency.AgencyID,
OwnerUserId: agency.OwnerUserID,
RegionId: agency.RegionID,
ParentBdUserId: agency.ParentBDUserID,
Name: agency.Name,
Status: agency.Status,
JoinEnabled: agency.JoinEnabled,
MaxHosts: agency.MaxHosts,
CreatedByUserId: agency.CreatedByUserID,
CreatedAtMs: agency.CreatedAtMs,
UpdatedAtMs: agency.UpdatedAtMs,
Avatar: agency.Avatar,
HostCount: agency.ActiveHostCount,
}
}
// toProtoBDProfile 把 BD profile 事实转换为内部 gRPC 投影。
func toProtoBDProfile(profile hostdomain.BDProfile) *userv1.BDProfile {
if profile.UserID == 0 {
return nil
}
return &userv1.BDProfile{
UserId: profile.UserID,
Role: profile.Role,
RegionId: profile.RegionID,
ParentLeaderUserId: profile.ParentLeaderUserID,
Status: profile.Status,
CreatedByUserId: profile.CreatedByUserID,
CreatedAtMs: profile.CreatedAtMs,
UpdatedAtMs: profile.UpdatedAtMs,
}
}
// toProtoCoinSellerProfile 把币商身份事实转换为内部 gRPC 投影。
func toProtoCoinSellerProfile(profile hostdomain.CoinSellerProfile) *userv1.CoinSellerProfile {
if profile.UserID == 0 {
return nil
}
return &userv1.CoinSellerProfile{
UserId: profile.UserID,
Status: profile.Status,
MerchantAssetType: profile.MerchantAssetType,
CanManageSubCoinSellers: profile.CanManageSubCoinSellers,
CreatedByUserId: profile.CreatedByUserID,
CreatedAtMs: profile.CreatedAtMs,
UpdatedAtMs: profile.UpdatedAtMs,
}
}
// toProtoCoinSellerListItem 把区域币商列表项转换为 App 读取投影。
func toProtoCoinSellerListItem(item hostdomain.CoinSellerListItem) *userv1.CoinSellerListItem {
if item.UserID == 0 {
return nil
}
return &userv1.CoinSellerListItem{
UserId: item.UserID,
DisplayUserId: item.DisplayUserID,
Username: item.Username,
Avatar: item.Avatar,
CountryId: item.CountryID,
CountryCode: item.CountryCode,
CountryName: item.CountryName,
CountryDisplayName: item.CountryDisplayName,
RegionId: item.RegionID,
RegionCode: item.RegionCode,
RegionName: item.RegionName,
Status: item.Status,
MerchantAssetType: item.MerchantAssetType,
ContactInfo: item.ContactInfo,
CanManageSubCoinSellers: item.CanManageSubCoinSellers,
UpdatedAtMs: item.UpdatedAtMs,
}
}
// toProtoCoinSellerSubRelation 把父子币商关系事实转换为内部 gRPC 投影。
func toProtoCoinSellerSubRelation(relation hostdomain.CoinSellerSubRelation) *userv1.CoinSellerSubRelation {
if relation.RelationID == 0 {
return nil
}
return &userv1.CoinSellerSubRelation{
RelationId: relation.RelationID,
ParentUserId: relation.ParentUserID,
ChildUserId: relation.ChildUserID,
Status: relation.Status,
CreatedByUserId: relation.CreatedByUserID,
CreatedAtMs: relation.CreatedAtMs,
UpdatedAtMs: relation.UpdatedAtMs,
}
}
// toProtoCoinSellerSubApplication 把子币商审核申请转换为内部 gRPC 投影。
func toProtoCoinSellerSubApplication(application hostdomain.CoinSellerSubApplication) *userv1.CoinSellerSubApplication {
if application.ApplicationID == 0 {
return nil
}
return &userv1.CoinSellerSubApplication{
ApplicationId: application.ApplicationID,
CommandId: application.CommandID,
ParentUserId: application.ParentUserID,
TargetUserId: application.TargetUserID,
Status: application.Status,
RelationId: application.RelationID,
ReviewedByAdminId: application.ReviewedByAdminID,
ReviewReason: application.ReviewReason,
ReviewedAtMs: application.ReviewedAtMs,
CreatedAtMs: application.CreatedAtMs,
UpdatedAtMs: application.UpdatedAtMs,
}
}
// toProtoUserRoleSummary 把 host domain 入口显隐摘要转换为 gRPC 投影。
func toProtoUserRoleSummary(summary hostdomain.UserRoleSummary) *userv1.UserRoleSummary {
if summary.UserID == 0 {
return nil
}
return &userv1.UserRoleSummary{
UserId: summary.UserID,
IsHost: summary.IsHost,
IsAgency: summary.IsAgency,
IsManager: summary.IsManager,
IsBd: summary.IsBD,
IsBdLeader: summary.IsBDLeader,
IsCoinSeller: summary.IsCoinSeller,
HostStatus: summary.HostStatus,
AgencyId: summary.AgencyID,
BdId: summary.BDID,
BdStatus: summary.BDStatus,
CoinSellerStatus: summary.CoinSellerStatus,
PendingRoleInvitations: summary.PendingRoleInvitations,
}
}
// toProtoAgencyMembership 把 Agency membership 事实转换为内部 gRPC 投影。
func toProtoAgencyMembership(membership hostdomain.AgencyMembership) *userv1.AgencyMembership {
if membership.MembershipID == 0 {
return nil
}
return &userv1.AgencyMembership{
MembershipId: membership.MembershipID,
AgencyId: membership.AgencyID,
HostUserId: membership.HostUserID,
RegionId: membership.RegionID,
MembershipType: membership.MembershipType,
Status: membership.Status,
JoinedAtMs: membership.JoinedAtMs,
EndedAtMs: membership.EndedAtMs,
EndedByUserId: membership.EndedByUserID,
EndedReason: membership.EndedReason,
CreatedAtMs: membership.CreatedAtMs,
UpdatedAtMs: membership.UpdatedAtMs,
}
}
// toProtoAgencyApplication 把 Agency application 事实转换为内部 gRPC 投影。
func toProtoAgencyApplication(application hostdomain.AgencyApplication) *userv1.AgencyApplication {
if application.ApplicationID == 0 {
return nil
}
return &userv1.AgencyApplication{
ApplicationId: application.ApplicationID,
CommandId: application.CommandID,
ApplicantUserId: application.ApplicantUserID,
AgencyId: application.AgencyID,
RegionId: application.RegionID,
Status: application.Status,
ReviewedByUserId: application.ReviewedByUserID,
ReviewReason: application.ReviewReason,
ReviewedAtMs: application.ReviewedAtMs,
CreatedAtMs: application.CreatedAtMs,
UpdatedAtMs: application.UpdatedAtMs,
}
}
// toProtoRoleInvitation 把角色邀请事实转换为内部 gRPC 投影。
func toProtoRoleInvitation(invitation hostdomain.RoleInvitation) *userv1.RoleInvitation {
if invitation.InvitationID == 0 {
return nil
}
return &userv1.RoleInvitation{
InvitationId: invitation.InvitationID,
CommandId: invitation.CommandID,
InvitationType: invitation.InvitationType,
Status: invitation.Status,
InviterUserId: invitation.InviterUserID,
InviterBdUserId: invitation.InviterBDUserID,
TargetUserId: invitation.TargetUserID,
RegionId: invitation.RegionID,
AgencyName: invitation.AgencyName,
ParentBdUserId: invitation.ParentBDUserID,
ParentLeaderUserId: invitation.ParentLeaderUserID,
ProcessedByUserId: invitation.ProcessedByUserID,
ProcessReason: invitation.ProcessReason,
ProcessedAtMs: invitation.ProcessedAtMs,
CreatedAtMs: invitation.CreatedAtMs,
UpdatedAtMs: invitation.UpdatedAtMs,
}
}