经理中心配置

This commit is contained in:
hy001 2026-06-22 17:34:52 +08:00
parent e370c3ca32
commit 53c6f361f9
4 changed files with 355 additions and 11 deletions

View File

@ -37,6 +37,15 @@ func registerManagerCenterRoutes(engine *gin.Engine, javaClient authGateway, ser
writeOK(c, resp) writeOK(c, resp)
}) })
group.GET("/countries", func(c *gin.Context) {
resp, err := service.ListCountries(c.Request.Context(), mustAuthUser(c))
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
group.GET("/props", func(c *gin.Context) { group.GET("/props", func(c *gin.Context) {
resp, err := service.ListProps(c.Request.Context(), mustAuthUser(c), c.Query("type")) resp, err := service.ListProps(c.Request.Context(), mustAuthUser(c), c.Query("type"))
if err != nil { if err != nil {
@ -110,4 +119,18 @@ func registerManagerCenterRoutes(engine *gin.Engine, javaClient authGateway, ser
} }
writeOK(c, resp) writeOK(c, resp)
}) })
group.POST("/users/country", func(c *gin.Context) {
var req managercenter.ChangeUserCountryRequest
if err := c.ShouldBindJSON(&req); err != nil {
writeError(c, managercenter.NewAppError(http.StatusBadRequest, "bad_request", err.Error()))
return
}
resp, err := service.ChangeUserCountry(c.Request.Context(), mustAuthUser(c), req)
if err != nil {
writeError(c, err)
return
}
writeOK(c, resp)
})
} }

View File

@ -6,6 +6,7 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"strings" "strings"
"time"
"chatapp3-golang/internal/integration" "chatapp3-golang/internal/integration"
"chatapp3-golang/internal/model" "chatapp3-golang/internal/model"
@ -32,21 +33,38 @@ func (teamManagerInfoRow) TableName() string {
} }
type userBaseInfoRow struct { type userBaseInfoRow struct {
ID int64 `gorm:"column:id;primaryKey"` ID int64 `gorm:"column:id;primaryKey"`
Account string `gorm:"column:account"` Account string `gorm:"column:account"`
UserAvatar string `gorm:"column:user_avatar"` UserAvatar string `gorm:"column:user_avatar"`
UserNickname string `gorm:"column:user_nickname"` UserNickname string `gorm:"column:user_nickname"`
CountryCode string `gorm:"column:country_code"` CountryID int64 `gorm:"column:country_id"`
CountryName string `gorm:"column:country_name"` CountryCode string `gorm:"column:country_code"`
OriginSys string `gorm:"column:origin_sys"` CountryName string `gorm:"column:country_name"`
AccountStatus string `gorm:"column:account_status"` OriginSys string `gorm:"column:origin_sys"`
IsDel bool `gorm:"column:is_del"` AccountStatus string `gorm:"column:account_status"`
IsDel bool `gorm:"column:is_del"`
UpdateTime time.Time `gorm:"column:update_time"`
UpdateUser int64 `gorm:"column:update_user"`
} }
func (userBaseInfoRow) TableName() string { func (userBaseInfoRow) TableName() string {
return "user_base_info" return "user_base_info"
} }
type sysCountryCodeRow struct {
ID int64 `gorm:"column:id;primaryKey"`
AlphaTwo string `gorm:"column:alpha_two"`
CountryName string `gorm:"column:country_name"`
NationalFlag string `gorm:"column:national_flag"`
PhonePrefix int `gorm:"column:phone_prefix"`
Open bool `gorm:"column:is_open"`
Sort int `gorm:"column:sort"`
}
func (sysCountryCodeRow) TableName() string {
return "sys_country_code"
}
type propsSourceRecordRow struct { type propsSourceRecordRow struct {
ID int64 `gorm:"column:id;primaryKey"` ID int64 `gorm:"column:id;primaryKey"`
Type string `gorm:"column:type"` Type string `gorm:"column:type"`
@ -106,6 +124,34 @@ func (s *Service) SearchUser(ctx context.Context, user AuthUser, account string)
return &UserSearchResponse{User: view}, nil return &UserSearchResponse{User: view}, nil
} }
// ListCountries 给经理中心返回当前开放国家H5 只展示可绑定目标,避免经理手输无效国家码。
func (s *Service) ListCountries(ctx context.Context, user AuthUser) (*CountryListResponse, error) {
if err := s.ensureManager(ctx, user); err != nil {
return nil, err
}
if s.db == nil {
return nil, serviceUnavailable("database_unavailable", "database is unavailable")
}
var rows []sysCountryCodeRow
if err := s.db.WithContext(ctx).
Table("sys_country_code").
Select("id, alpha_two, country_name, national_flag, phone_prefix, is_open, sort").
Where("is_open = ?", true).
Order("sort ASC, country_name ASC, id ASC").
Find(&rows).Error; err != nil {
return nil, serverError("country_query_failed", err.Error())
}
records := make([]CountryView, 0, len(rows))
for _, row := range rows {
if view, ok := countryRowToView(row); ok {
records = append(records, view)
}
}
return &CountryListResponse{Records: records}, nil
}
func (s *Service) ListProps(ctx context.Context, user AuthUser, propsType string) (*PropsListResponse, error) { func (s *Service) ListProps(ctx context.Context, user AuthUser, propsType string) (*PropsListResponse, error) {
if err := s.ensureManager(ctx, user); err != nil { if err := s.ensureManager(ctx, user); err != nil {
return nil, err return nil, err
@ -332,6 +378,66 @@ func (s *Service) UnbanUser(ctx context.Context, user AuthUser, req UnbanUserReq
}, nil }, nil
} }
// ChangeUserCountry 只允许经理移动普通用户国家;用户已有业务身份时拒绝,避免跨国家后团队、币商和主播归属数据不一致。
func (s *Service) ChangeUserCountry(ctx context.Context, user AuthUser, req ChangeUserCountryRequest) (*ChangeUserCountryResponse, error) {
if err := s.ensureManager(ctx, user); err != nil {
return nil, err
}
if req.UserID.Int64() <= 0 {
return nil, badRequest("user_required", "userId is required")
}
if req.CountryID.Int64() <= 0 {
return nil, badRequest("country_required", "countryId is required")
}
if s.db == nil {
return nil, serviceUnavailable("database_unavailable", "database is unavailable")
}
target, err := s.loadUserRowByID(ctx, req.UserID.Int64())
if err != nil {
if errors.Is(err, gorm.ErrRecordNotFound) {
return nil, notFound("user_not_found", "user not found")
}
return nil, serverError("user_query_failed", err.Error())
}
country, err := s.loadOpenCountryByID(ctx, req.CountryID.Int64())
if err != nil {
return nil, err
}
if err := s.ensureUserCountryMovable(ctx, target.ID); err != nil {
return nil, err
}
// 只改国家三元组,不触碰昵称、头像等资料审核字段;清理 Java 资料缓存后 App/H5 会重新读到新国家。
updates := map[string]any{
"country_id": country.ID,
"country_code": strings.ToUpper(strings.TrimSpace(country.AlphaTwo)),
"country_name": strings.TrimSpace(country.CountryName),
"update_time": time.Now(),
"update_user": user.UserID,
}
if err := s.db.WithContext(ctx).
Table("user_base_info").
Where("id = ? AND is_del = ?", target.ID, false).
Updates(updates).Error; err != nil {
return nil, serverError("user_country_update_failed", err.Error())
}
if s.java != nil {
if err := s.java.RemoveUserProfileCacheAll(ctx, target.ID); err != nil {
return nil, serverError("remove_profile_cache_failed", err.Error())
}
}
target.CountryID = country.ID
target.CountryCode = strings.ToUpper(strings.TrimSpace(country.AlphaTwo))
target.CountryName = strings.TrimSpace(country.CountryName)
view, err := s.userRowToView(ctx, target)
if err != nil {
return nil, err
}
return &ChangeUserCountryResponse{Success: true, User: view}, nil
}
func (s *Service) ensureManager(ctx context.Context, user AuthUser) error { func (s *Service) ensureManager(ctx context.Context, user AuthUser) error {
_, err := s.requireManagerInfo(ctx, user) _, err := s.requireManagerInfo(ctx, user)
return err return err
@ -444,6 +550,65 @@ func (s *Service) loadUserRowByAccount(ctx context.Context, account string) (use
return row, err return row, err
} }
func (s *Service) loadOpenCountryByID(ctx context.Context, countryID int64) (sysCountryCodeRow, error) {
var row sysCountryCodeRow
err := s.db.WithContext(ctx).
Table("sys_country_code").
Select("id, alpha_two, country_name, national_flag, phone_prefix, is_open, sort").
Where("id = ? AND is_open = ?", countryID, true).
First(&row).Error
if errors.Is(err, gorm.ErrRecordNotFound) {
return sysCountryCodeRow{}, notFound("country_not_found", "country not found")
}
if err != nil {
return sysCountryCodeRow{}, serverError("country_query_failed", err.Error())
}
if strings.TrimSpace(row.AlphaTwo) == "" {
return sysCountryCodeRow{}, badRequest("country_code_required", "country code is required")
}
return row, nil
}
// ensureUserCountryMovable 串行检查所有会绑定归属关系的身份;任一命中都返回同一个业务错误,前端显示统一提示。
func (s *Service) ensureUserCountryMovable(ctx context.Context, userID int64) error {
locked, err := s.userHasLockedHistoryIdentity(ctx, userID)
if err != nil {
return err
}
if locked {
return badRequest("user_identity_locked", "user has host, agency, BD, BD leader or coin seller identity and cannot be moved")
}
locked, err = s.userIsBDLeader(ctx, userID)
if err != nil {
return err
}
if locked {
return badRequest("user_identity_locked", "user has host, agency, BD, BD leader or coin seller identity and cannot be moved")
}
if s.java == nil {
return serviceUnavailable("java_gateway_unavailable", "java gateway is unavailable")
}
locked, err = s.java.ExistsFreightSeller(ctx, userID)
if err != nil {
return serverError("coin_seller_query_failed", err.Error())
}
if locked {
return badRequest("user_identity_locked", "user has host, agency, BD, BD leader or coin seller identity and cannot be moved")
}
return nil
}
func (s *Service) userHasLockedHistoryIdentity(ctx context.Context, userID int64) (bool, error) {
var count int64
if err := s.db.WithContext(ctx).
Table("user_history_identity").
Where("user_id = ? AND (is_host = ? OR is_agent = ? OR is_bd = ?)", userID, true, true, true).
Count(&count).Error; err != nil {
return false, serverError("history_identity_query_failed", err.Error())
}
return count > 0, nil
}
func (s *Service) userRowToView(ctx context.Context, row userBaseInfoRow) (UserView, error) { func (s *Service) userRowToView(ctx context.Context, row userBaseInfoRow) (UserView, error) {
hasRecharge, canBan := s.userRechargeEligibility(ctx, row.ID) hasRecharge, canBan := s.userRechargeEligibility(ctx, row.ID)
canBan = canBan && !isUnbannableAccountStatus(row.AccountStatus) canBan = canBan && !isUnbannableAccountStatus(row.AccountStatus)
@ -452,6 +617,7 @@ func (s *Service) userRowToView(ctx context.Context, row userBaseInfoRow) (UserV
Account: row.Account, Account: row.Account,
UserAvatar: row.UserAvatar, UserAvatar: row.UserAvatar,
UserNickname: row.UserNickname, UserNickname: row.UserNickname,
CountryID: ID(row.CountryID),
CountryCode: row.CountryCode, CountryCode: row.CountryCode,
CountryName: row.CountryName, CountryName: row.CountryName,
OriginSys: row.OriginSys, OriginSys: row.OriginSys,
@ -469,6 +635,7 @@ func (s *Service) javaProfileToView(ctx context.Context, profile integration.Use
Account: profile.Account, Account: profile.Account,
UserAvatar: profile.UserAvatar, UserAvatar: profile.UserAvatar,
UserNickname: profile.UserNickname, UserNickname: profile.UserNickname,
CountryID: ID(profile.CountryID),
CountryCode: profile.CountryCode, CountryCode: profile.CountryCode,
CountryName: profile.CountryName, CountryName: profile.CountryName,
OriginSys: profile.OriginSys, OriginSys: profile.OriginSys,
@ -494,6 +661,9 @@ func overlayJavaProfile(view *UserView, profile integration.UserProfile) {
if profile.CountryName != "" { if profile.CountryName != "" {
view.CountryName = profile.CountryName view.CountryName = profile.CountryName
} }
if int64(profile.CountryID) > 0 {
view.CountryID = ID(profile.CountryID)
}
if profile.OriginSys != "" { if profile.OriginSys != "" {
view.OriginSys = profile.OriginSys view.OriginSys = profile.OriginSys
} }
@ -505,6 +675,21 @@ func overlayJavaProfile(view *UserView, profile integration.UserProfile) {
} }
} }
func countryRowToView(row sysCountryCodeRow) (CountryView, bool) {
code := strings.ToUpper(strings.TrimSpace(row.AlphaTwo))
name := strings.TrimSpace(row.CountryName)
if row.ID <= 0 || code == "" || name == "" {
return CountryView{}, false
}
return CountryView{
ID: ID(row.ID),
CountryCode: code,
CountryName: name,
NationalFlag: strings.TrimSpace(row.NationalFlag),
PhonePrefix: row.PhonePrefix,
}, true
}
func (s *Service) userHasRecharge(ctx context.Context, userID int64) (bool, error) { func (s *Service) userHasRecharge(ctx context.Context, userID int64) (bool, error) {
if userID <= 0 { if userID <= 0 {
return false, nil return false, nil

View File

@ -17,6 +17,8 @@ import (
type fakeManagerGateway struct { type fakeManagerGateway struct {
recharges map[int64][]integration.UserTotalRecharge recharges map[int64][]integration.UserTotalRecharge
rechargeErr error rechargeErr error
freightSellers map[int64]bool
freightSellerErr error
abilities map[int64]integration.PropsNobleVIPAbility abilities map[int64]integration.PropsNobleVIPAbility
maxAbility integration.PropsNobleVIPAbility maxAbility integration.PropsNobleVIPAbility
giveRequests []integration.GivePropsBackpackRequest giveRequests []integration.GivePropsBackpackRequest
@ -52,6 +54,13 @@ func (f *fakeManagerGateway) MapUserTotalRecharge(_ context.Context, userIDs []i
return result, nil return result, nil
} }
func (f *fakeManagerGateway) ExistsFreightSeller(_ context.Context, userID int64) (bool, error) {
if f.freightSellerErr != nil {
return false, f.freightSellerErr
}
return f.freightSellers[userID], nil
}
func (f *fakeManagerGateway) GivePropsBackpack(_ context.Context, req integration.GivePropsBackpackRequest) error { func (f *fakeManagerGateway) GivePropsBackpack(_ context.Context, req integration.GivePropsBackpackRequest) error {
f.giveRequests = append(f.giveRequests, req) f.giveRequests = append(f.giveRequests, req)
return nil return nil
@ -732,6 +741,98 @@ func TestUnbanUserAllowsFrozenUser(t *testing.T) {
} }
} }
func TestListCountriesReturnsOpenCountries(t *testing.T) {
service, _ := newManagerCenterTestService(t)
db := service.db.(*gorm.DB)
seedManager(t, db, 1)
seedCountries(t, db,
sysCountryCodeRow{ID: 10, AlphaTwo: "SA", CountryName: "Saudi Arabia", Open: true, Sort: 2},
sysCountryCodeRow{ID: 11, AlphaTwo: "AE", CountryName: "United Arab Emirates", Open: true, Sort: 1},
sysCountryCodeRow{ID: 12, AlphaTwo: "XX", CountryName: "Closed", Open: false, Sort: 3},
)
resp, err := service.ListCountries(context.Background(), AuthUser{UserID: 1})
if err != nil {
t.Fatalf("ListCountries() error = %v", err)
}
if len(resp.Records) != 2 {
t.Fatalf("countries = %+v, want two open rows", resp.Records)
}
if resp.Records[0].CountryCode != "AE" || resp.Records[1].CountryCode != "SA" {
t.Fatalf("countries = %+v, want sort order AE then SA", resp.Records)
}
}
func TestChangeUserCountryUpdatesCountryAndClearsCache(t *testing.T) {
service, fake := newManagerCenterTestService(t)
db := service.db.(*gorm.DB)
seedManager(t, db, 1)
seedUsers(t, db, userBaseInfoRow{ID: 2, Account: "2", CountryID: 10, CountryCode: "SA", CountryName: "Saudi Arabia"})
seedCountries(t, db, sysCountryCodeRow{ID: 11, AlphaTwo: "AE", CountryName: "United Arab Emirates", Open: true})
resp, err := service.ChangeUserCountry(context.Background(), AuthUser{UserID: 1}, ChangeUserCountryRequest{
UserID: ID(2),
CountryID: ID(11),
})
if err != nil {
t.Fatalf("ChangeUserCountry() error = %v", err)
}
if !resp.Success || resp.User.CountryCode != "AE" || resp.User.CountryName != "United Arab Emirates" {
t.Fatalf("resp = %+v, want changed country", resp)
}
var row userBaseInfoRow
if err := db.Where("id = ?", int64(2)).First(&row).Error; err != nil {
t.Fatalf("load user: %v", err)
}
if row.CountryID != 11 || row.CountryCode != "AE" || row.CountryName != "United Arab Emirates" || row.UpdateUser != 1 {
t.Fatalf("user row = %+v, want AE country and manager updater", row)
}
if !equalInt64Slice(fake.cacheCleared, []int64{2}) {
t.Fatalf("cacheCleared = %+v, want [2]", fake.cacheCleared)
}
}
func TestChangeUserCountryRejectsHistoryIdentity(t *testing.T) {
service, _ := newManagerCenterTestService(t)
db := service.db.(*gorm.DB)
seedManager(t, db, 1)
seedUsers(t, db, userBaseInfoRow{ID: 2, Account: "2", CountryID: 10, CountryCode: "SA"})
seedCountries(t, db, sysCountryCodeRow{ID: 11, AlphaTwo: "AE", CountryName: "United Arab Emirates", Open: true})
if err := db.Create(&userHistoryIdentityRow{UserID: 2, IsHost: true}).Error; err != nil {
t.Fatalf("seed identity: %v", err)
}
_, err := service.ChangeUserCountry(context.Background(), AuthUser{UserID: 1}, ChangeUserCountryRequest{UserID: ID(2), CountryID: ID(11)})
assertAppErrorCode(t, err, "user_identity_locked")
}
func TestChangeUserCountryRejectsBDLeader(t *testing.T) {
service, _ := newManagerCenterTestService(t)
db := service.db.(*gorm.DB)
seedManager(t, db, 1)
seedUsers(t, db, userBaseInfoRow{ID: 2, Account: "2", CountryID: 10, CountryCode: "SA"})
seedCountries(t, db, sysCountryCodeRow{ID: 11, AlphaTwo: "AE", CountryName: "United Arab Emirates", Open: true})
if err := db.Create(&bdLeaderRow{ID: 20, UserID: 2, Origin: "LIKEI", RegionID: "SA"}).Error; err != nil {
t.Fatalf("seed bd leader: %v", err)
}
_, err := service.ChangeUserCountry(context.Background(), AuthUser{UserID: 1}, ChangeUserCountryRequest{UserID: ID(2), CountryID: ID(11)})
assertAppErrorCode(t, err, "user_identity_locked")
}
func TestChangeUserCountryRejectsCoinSeller(t *testing.T) {
service, fake := newManagerCenterTestService(t)
db := service.db.(*gorm.DB)
seedManager(t, db, 1)
seedUsers(t, db, userBaseInfoRow{ID: 2, Account: "2", CountryID: 10, CountryCode: "SA"})
seedCountries(t, db, sysCountryCodeRow{ID: 11, AlphaTwo: "AE", CountryName: "United Arab Emirates", Open: true})
fake.freightSellers[2] = true
_, err := service.ChangeUserCountry(context.Background(), AuthUser{UserID: 1}, ChangeUserCountryRequest{UserID: ID(2), CountryID: ID(11)})
assertAppErrorCode(t, err, "user_identity_locked")
}
func newManagerCenterTestService(t *testing.T) (*Service, *fakeManagerGateway) { func newManagerCenterTestService(t *testing.T) (*Service, *fakeManagerGateway) {
t.Helper() t.Helper()
db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{}) db, err := gorm.Open(sqlite.Open("file:"+t.Name()+"?mode=memory&cache=shared"), &gorm.Config{})
@ -741,6 +842,7 @@ func newManagerCenterTestService(t *testing.T) (*Service, *fakeManagerGateway) {
if err := db.AutoMigrate( if err := db.AutoMigrate(
&teamManagerInfoRow{}, &teamManagerInfoRow{},
&userBaseInfoRow{}, &userBaseInfoRow{},
&sysCountryCodeRow{},
&bdLeaderRow{}, &bdLeaderRow{},
&businessDevelopmentBaseInfoRow{}, &businessDevelopmentBaseInfoRow{},
&userHistoryIdentityRow{}, &userHistoryIdentityRow{},
@ -755,8 +857,9 @@ func newManagerCenterTestService(t *testing.T) (*Service, *fakeManagerGateway) {
t.Fatalf("auto migrate: %v", err) t.Fatalf("auto migrate: %v", err)
} }
fake := &fakeManagerGateway{ fake := &fakeManagerGateway{
recharges: map[int64][]integration.UserTotalRecharge{}, recharges: map[int64][]integration.UserTotalRecharge{},
abilities: map[int64]integration.PropsNobleVIPAbility{}, freightSellers: map[int64]bool{},
abilities: map[int64]integration.PropsNobleVIPAbility{},
} }
return NewService(db, fake), fake return NewService(db, fake), fake
} }
@ -803,6 +906,15 @@ func seedUsers(t *testing.T, db *gorm.DB, rows ...userBaseInfoRow) {
} }
} }
func seedCountries(t *testing.T, db *gorm.DB, rows ...sysCountryCodeRow) {
t.Helper()
for _, row := range rows {
if err := db.Create(&row).Error; err != nil {
t.Fatalf("seed country: %v", err)
}
}
}
func seedProps(t *testing.T, db *gorm.DB, rows ...propsSourceRecordRow) { func seedProps(t *testing.T, db *gorm.DB, rows ...propsSourceRecordRow) {
t.Helper() t.Helper()
for _, row := range rows { for _, row := range rows {

View File

@ -60,6 +60,7 @@ type managerGateway interface {
GetUserProfile(ctx context.Context, userID int64) (integration.UserProfile, error) GetUserProfile(ctx context.Context, userID int64) (integration.UserProfile, error)
GetUserProfileByAccountOne(ctx context.Context, account string) (integration.UserProfile, error) GetUserProfileByAccountOne(ctx context.Context, account string) (integration.UserProfile, error)
MapUserTotalRecharge(ctx context.Context, userIDs []int64) (map[int64][]integration.UserTotalRecharge, error) MapUserTotalRecharge(ctx context.Context, userIDs []int64) (map[int64][]integration.UserTotalRecharge, error)
ExistsFreightSeller(ctx context.Context, userID int64) (bool, error)
GivePropsBackpack(ctx context.Context, req integration.GivePropsBackpackRequest) error GivePropsBackpack(ctx context.Context, req integration.GivePropsBackpackRequest) error
GetNobleVIPAbility(ctx context.Context, sourceID int64) (integration.PropsNobleVIPAbility, error) GetNobleVIPAbility(ctx context.Context, sourceID int64) (integration.PropsNobleVIPAbility, error)
GetUserMaxNobleVIPAbility(ctx context.Context, userID int64) (integration.PropsNobleVIPAbility, error) GetUserMaxNobleVIPAbility(ctx context.Context, userID int64) (integration.PropsNobleVIPAbility, error)
@ -130,11 +131,24 @@ type UserSearchResponse struct {
User UserView `json:"user"` User UserView `json:"user"`
} }
type CountryListResponse struct {
Records []CountryView `json:"records"`
}
type CountryView struct {
ID ID `json:"id"`
CountryCode string `json:"countryCode"`
CountryName string `json:"countryName"`
NationalFlag string `json:"nationalFlag,omitempty"`
PhonePrefix int `json:"phonePrefix,omitempty"`
}
type UserView struct { type UserView struct {
UserID ID `json:"userId"` UserID ID `json:"userId"`
Account string `json:"account"` Account string `json:"account"`
UserAvatar string `json:"userAvatar"` UserAvatar string `json:"userAvatar"`
UserNickname string `json:"userNickname"` UserNickname string `json:"userNickname"`
CountryID ID `json:"countryId,omitempty"`
CountryCode string `json:"countryCode,omitempty"` CountryCode string `json:"countryCode,omitempty"`
CountryName string `json:"countryName,omitempty"` CountryName string `json:"countryName,omitempty"`
OriginSys string `json:"originSys,omitempty"` OriginSys string `json:"originSys,omitempty"`
@ -196,6 +210,16 @@ type UnbanUserResponse struct {
AccountStatus string `json:"accountStatus"` AccountStatus string `json:"accountStatus"`
} }
type ChangeUserCountryRequest struct {
UserID ID `json:"userId"`
CountryID ID `json:"countryId"`
}
type ChangeUserCountryResponse struct {
Success bool `json:"success"`
User UserView `json:"user"`
}
type BDLeaderListResponse struct { type BDLeaderListResponse struct {
Count int `json:"count"` Count int `json:"count"`
Records []BDLeaderView `json:"records"` Records []BDLeaderView `json:"records"`