增加用户的manager身份

This commit is contained in:
zhx 2026-05-25 23:41:50 +08:00
parent 245412ae95
commit ad4a755998
13 changed files with 75 additions and 130 deletions

View File

@ -442,7 +442,7 @@ func (x *CoinSellerProfile) GetUpdatedAtMs() int64 {
} }
// UserRoleSummary 是 App 我的页和入口显隐使用的轻量角色摘要。 // UserRoleSummary 是 App 我的页和入口显隐使用的轻量角色摘要。
// 它把 host、Agency、BD 和币商身份收敛到一次 user-service RPC避免 gateway 随角色扩张继续 fanout。 // 它把 host、Agency、manager、BD 和币商身份收敛到一次 user-service RPC避免 gateway 随角色扩张继续 fanout。
type UserRoleSummary struct { type UserRoleSummary struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"` UserId int64 `protobuf:"varint,1,opt,name=user_id,json=userId,proto3" json:"user_id,omitempty"`
@ -457,6 +457,7 @@ type UserRoleSummary struct {
BdStatus string `protobuf:"bytes,10,opt,name=bd_status,json=bdStatus,proto3" json:"bd_status,omitempty"` BdStatus string `protobuf:"bytes,10,opt,name=bd_status,json=bdStatus,proto3" json:"bd_status,omitempty"`
CoinSellerStatus string `protobuf:"bytes,11,opt,name=coin_seller_status,json=coinSellerStatus,proto3" json:"coin_seller_status,omitempty"` CoinSellerStatus string `protobuf:"bytes,11,opt,name=coin_seller_status,json=coinSellerStatus,proto3" json:"coin_seller_status,omitempty"`
PendingRoleInvitations int64 `protobuf:"varint,12,opt,name=pending_role_invitations,json=pendingRoleInvitations,proto3" json:"pending_role_invitations,omitempty"` PendingRoleInvitations int64 `protobuf:"varint,12,opt,name=pending_role_invitations,json=pendingRoleInvitations,proto3" json:"pending_role_invitations,omitempty"`
IsManager bool `protobuf:"varint,13,opt,name=is_manager,json=isManager,proto3" json:"is_manager,omitempty"`
unknownFields protoimpl.UnknownFields unknownFields protoimpl.UnknownFields
sizeCache protoimpl.SizeCache sizeCache protoimpl.SizeCache
} }
@ -575,6 +576,13 @@ func (x *UserRoleSummary) GetPendingRoleInvitations() int64 {
return 0 return 0
} }
func (x *UserRoleSummary) GetIsManager() bool {
if x != nil {
return x.IsManager
}
return false
}
// AgencyMembership 是 host 与 Agency 的有时效关系事实。 // AgencyMembership 是 host 与 Agency 的有时效关系事实。
type AgencyMembership struct { type AgencyMembership struct {
state protoimpl.MessageState `protogen:"open.v1"` state protoimpl.MessageState `protogen:"open.v1"`
@ -3689,7 +3697,7 @@ const file_proto_user_v1_host_proto_rawDesc = "" +
"\x13merchant_asset_type\x18\x03 \x01(\tR\x11merchantAssetType\x12+\n" + "\x13merchant_asset_type\x18\x03 \x01(\tR\x11merchantAssetType\x12+\n" +
"\x12created_by_user_id\x18\x04 \x01(\x03R\x0fcreatedByUserId\x12\"\n" + "\x12created_by_user_id\x18\x04 \x01(\x03R\x0fcreatedByUserId\x12\"\n" +
"\rcreated_at_ms\x18\x05 \x01(\x03R\vcreatedAtMs\x12\"\n" + "\rcreated_at_ms\x18\x05 \x01(\x03R\vcreatedAtMs\x12\"\n" +
"\rupdated_at_ms\x18\x06 \x01(\x03R\vupdatedAtMs\"\x95\x03\n" + "\rupdated_at_ms\x18\x06 \x01(\x03R\vupdatedAtMs\"\xb4\x03\n" +
"\x0fUserRoleSummary\x12\x17\n" + "\x0fUserRoleSummary\x12\x17\n" +
"\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x17\n" + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x17\n" +
"\ais_host\x18\x02 \x01(\bR\x06isHost\x12\x1b\n" + "\ais_host\x18\x02 \x01(\bR\x06isHost\x12\x1b\n" +
@ -3705,7 +3713,9 @@ const file_proto_user_v1_host_proto_rawDesc = "" +
"\tbd_status\x18\n" + "\tbd_status\x18\n" +
" \x01(\tR\bbdStatus\x12,\n" + " \x01(\tR\bbdStatus\x12,\n" +
"\x12coin_seller_status\x18\v \x01(\tR\x10coinSellerStatus\x128\n" + "\x12coin_seller_status\x18\v \x01(\tR\x10coinSellerStatus\x128\n" +
"\x18pending_role_invitations\x18\f \x01(\x03R\x16pendingRoleInvitations\"\xaa\x03\n" + "\x18pending_role_invitations\x18\f \x01(\x03R\x16pendingRoleInvitations\x12\x1d\n" +
"\n" +
"is_manager\x18\r \x01(\bR\tisManager\"\xaa\x03\n" +
"\x10AgencyMembership\x12#\n" + "\x10AgencyMembership\x12#\n" +
"\rmembership_id\x18\x01 \x01(\x03R\fmembershipId\x12\x1b\n" + "\rmembership_id\x18\x01 \x01(\x03R\fmembershipId\x12\x1b\n" +
"\tagency_id\x18\x02 \x01(\x03R\bagencyId\x12 \n" + "\tagency_id\x18\x02 \x01(\x03R\bagencyId\x12 \n" +

View File

@ -57,7 +57,7 @@ message CoinSellerProfile {
} }
// UserRoleSummary App 使 // UserRoleSummary App 使
// hostAgencyBD user-service RPC gateway fanout // hostAgencymanagerBD user-service RPC gateway fanout
message UserRoleSummary { message UserRoleSummary {
int64 user_id = 1; int64 user_id = 1;
bool is_host = 2; bool is_host = 2;
@ -71,6 +71,7 @@ message UserRoleSummary {
string bd_status = 10; string bd_status = 10;
string coin_seller_status = 11; string coin_seller_status = 11;
int64 pending_role_invitations = 12; int64 pending_role_invitations = 12;
bool is_manager = 13;
} }
// AgencyMembership host Agency // AgencyMembership host Agency

View File

@ -2717,7 +2717,10 @@ definitions:
description: 用户是否拥有 active host profile。 description: 用户是否拥有 active host profile。
is_agent: is_agent:
type: boolean type: boolean
description: 用户是否拥有 active agency owner 身份。 description: 用户是否拥有 Agency 入口身份active Agency owner 或 Agency 来源主播都可能为 true。
is_manager:
type: boolean
description: 用户是否拥有经理中心身份;首版等价于 active Agency owner。
is_bd: is_bd:
type: boolean type: boolean
description: 用户是否拥有 active BD 或 BD Leader 身份。 description: 用户是否拥有 active BD 或 BD Leader 身份。
@ -2732,6 +2735,7 @@ definitions:
required: required:
- is_host - is_host
- is_agency - is_agency
- is_manager
- is_bd - is_bd
- is_bd_leader - is_bd_leader
- is_coin_seller - is_coin_seller
@ -2741,6 +2745,8 @@ definitions:
type: boolean type: boolean
is_agency: is_agency:
type: boolean type: boolean
is_manager:
type: boolean
is_bd: is_bd:
type: boolean type: boolean
is_bd_leader: is_bd_leader:
@ -3170,6 +3176,7 @@ definitions:
type: string type: string
enum: enum:
- host-center - host-center
- manager-center
- bd-center - bd-center
- bd-leader-center - bd-leader-center
- agency-center - agency-center

View File

@ -49,6 +49,7 @@ Authorization: Bearer <access_token>
"roles": { "roles": {
"is_host": true, "is_host": true,
"is_agency": false, "is_agency": false,
"is_manager": false,
"is_bd": false, "is_bd": false,
"is_bd_leader": false, "is_bd_leader": false,
"is_coin_seller": false, "is_coin_seller": false,
@ -116,6 +117,7 @@ Authorization: Bearer <access_token>
| `apply_host` | `roles.is_host == false` | | `apply_host` | `roles.is_host == false` |
| `host_center` | `roles.is_host == true` | | `host_center` | `roles.is_host == true` |
| `agency_center` | `roles.is_agency == true` | | `agency_center` | `roles.is_agency == true` |
| `manager_center` | `roles.is_manager == true` |
| `bd_center` | `roles.is_bd == true` | | `bd_center` | `roles.is_bd == true` |
| `bd_leader_center` | `roles.is_bd_leader == true` | | `bd_leader_center` | `roles.is_bd_leader == true` |
| `coin_seller_center` | `roles.is_coin_seller == true` | | `coin_seller_center` | `roles.is_coin_seller == true` |

View File

@ -247,12 +247,12 @@ func (s *Store) seedMenus() error {
{ParentID: &gameID, Title: "游戏列表", Code: "game-list", Path: "/games", Icon: "sports_esports", PermissionCode: "game:view", Sort: 70, Visible: true}, {ParentID: &gameID, Title: "游戏列表", Code: "game-list", Path: "/games", Icon: "sports_esports", PermissionCode: "game:view", Sort: 70, Visible: true},
{ParentID: &geoID, Title: "国家管理", Code: "host-org-countries", Path: "/host/countries", Icon: "public", PermissionCode: "country:view", Sort: 70, Visible: true}, {ParentID: &geoID, Title: "国家管理", Code: "host-org-countries", Path: "/host/countries", Icon: "public", PermissionCode: "country:view", Sort: 70, Visible: true},
{ParentID: &geoID, Title: "区域管理", Code: "host-org-regions", Path: "/host/regions", Icon: "map", PermissionCode: "region:view", Sort: 71, Visible: true}, {ParentID: &geoID, Title: "区域管理", Code: "host-org-regions", Path: "/host/regions", Icon: "map", PermissionCode: "region:view", Sort: 71, Visible: true},
{ParentID: &hostOrgID, Title: "经理列表", Code: "host-org-managers", Path: "/host/managers", Icon: "users", PermissionCode: "agency:view", Sort: 80, Visible: true}, {ParentID: &hostOrgID, Title: "Manager列表", Code: "host-org-managers", Path: "/host/managers", Icon: "users", PermissionCode: "agency:view", Sort: 80, Visible: true},
{ParentID: &hostOrgID, Title: "Agency 列表", Code: "host-org-agencies", Path: "/host/agencies", Icon: "apartment", PermissionCode: "agency:view", Sort: 81, Visible: true}, {ParentID: &hostOrgID, Title: "Agency 列表", Code: "host-org-agencies", Path: "/host/agencies", Icon: "apartment", PermissionCode: "agency:view", Sort: 81, Visible: true},
{ParentID: &hostOrgID, Title: "BD Leader 列表", Code: "host-org-bd-leaders", Path: "/host/bd-leaders", Icon: "shield", PermissionCode: "bd:view", Sort: 82, Visible: true}, {ParentID: &hostOrgID, Title: "BD Leader 列表", Code: "host-org-bd-leaders", Path: "/host/bd-leaders", Icon: "shield", PermissionCode: "bd:view", Sort: 82, Visible: true},
{ParentID: &hostOrgID, Title: "BD 列表", Code: "host-org-bds", Path: "/host/bds", Icon: "team", PermissionCode: "bd:view", Sort: 83, Visible: true}, {ParentID: &hostOrgID, Title: "BD 列表", Code: "host-org-bds", Path: "/host/bds", Icon: "team", PermissionCode: "bd:view", Sort: 83, Visible: true},
{ParentID: &hostOrgID, Title: "主播列表", Code: "host-org-hosts", Path: "/host/hosts", Icon: "users", PermissionCode: "host:view", Sort: 84, Visible: true}, {ParentID: &hostOrgID, Title: "Host 列表", Code: "host-org-hosts", Path: "/host/hosts", Icon: "users", PermissionCode: "host:view", Sort: 84, Visible: true},
{ParentID: &hostOrgID, Title: "币商列表", Code: "host-org-coin-sellers", Path: "/host/coin-sellers", Icon: "wallet", PermissionCode: "coin-seller:view", Sort: 85, Visible: true}, {ParentID: &hostOrgID, Title: "Coin Saller列表", Code: "host-org-coin-sellers", Path: "/host/coin-sellers", Icon: "wallet", PermissionCode: "coin-seller:view", Sort: 85, Visible: true},
} }
for _, menu := range children { for _, menu := range children {
if _, err := s.syncDefaultMenu(s.db, menu); err != nil { if _, err := s.syncDefaultMenu(s.db, menu); err != nil {

View File

@ -34,6 +34,7 @@ func TestGetMyOverviewComposesProfileWalletVIPStatsRolesAndEntries(t *testing.T)
UserId: 42, UserId: 42,
IsHost: true, IsHost: true,
IsAgency: true, IsAgency: true,
IsManager: true,
IsBd: true, IsBd: true,
IsBdLeader: true, IsBdLeader: true,
IsCoinSeller: true, IsCoinSeller: true,
@ -81,7 +82,7 @@ func TestGetMyOverviewComposesProfileWalletVIPStatsRolesAndEntries(t *testing.T)
t.Fatalf("profile stats mismatch: %+v", stats) t.Fatalf("profile stats mismatch: %+v", stats)
} }
roles := data["roles"].(map[string]any) roles := data["roles"].(map[string]any)
if roles["is_host"] != true || roles["is_agency"] != true || roles["is_bd"] != true || roles["is_bd_leader"] != true || roles["is_coin_seller"] != true || roles["agency_id"] != float64(7001) { if roles["is_host"] != true || roles["is_agency"] != true || roles["is_manager"] != true || roles["is_bd"] != true || roles["is_bd_leader"] != true || roles["is_coin_seller"] != true || roles["agency_id"] != float64(7001) {
t.Fatalf("roles data mismatch: %+v", roles) t.Fatalf("roles data mismatch: %+v", roles)
} }
wallet := data["wallet"].(map[string]any) wallet := data["wallet"].(map[string]any)
@ -106,6 +107,7 @@ func TestGetMyOverviewComposesProfileWalletVIPStatsRolesAndEntries(t *testing.T)
!overviewEntryVisible(entries, "task") || !overviewEntryVisible(entries, "task") ||
!overviewEntryVisible(entries, "host_center") || !overviewEntryVisible(entries, "host_center") ||
!overviewEntryVisible(entries, "agency_center") || !overviewEntryVisible(entries, "agency_center") ||
!overviewEntryVisible(entries, "manager_center") ||
!overviewEntryVisible(entries, "bd_center") || !overviewEntryVisible(entries, "bd_center") ||
!overviewEntryVisible(entries, "bd_leader_center") || !overviewEntryVisible(entries, "bd_leader_center") ||
!overviewEntryVisible(entries, "coin_seller_center") || !overviewEntryVisible(entries, "coin_seller_center") ||
@ -136,7 +138,7 @@ func TestGetMyOverviewProjectsOrdinaryUserEntriesAndZeroWallet(t *testing.T) {
t.Fatalf("missing stats should be zero projection: %+v", stats) t.Fatalf("missing stats should be zero projection: %+v", stats)
} }
roles := data["roles"].(map[string]any) roles := data["roles"].(map[string]any)
if roles["is_host"] != false || roles["is_bd"] != false || roles["is_coin_seller"] != false { if roles["is_host"] != false || roles["is_manager"] != false || roles["is_bd"] != false || roles["is_coin_seller"] != false {
t.Fatalf("ordinary roles mismatch: %+v", roles) t.Fatalf("ordinary roles mismatch: %+v", roles)
} }
entries := data["entries"].([]any) entries := data["entries"].([]any)
@ -162,11 +164,11 @@ func TestGetMyOverviewDegradesOptionalUpstreamFailures(t *testing.T) {
t.Fatalf("profile stats degradation mismatch: %+v", stats) t.Fatalf("profile stats degradation mismatch: %+v", stats)
} }
roles := data["roles"].(map[string]any) roles := data["roles"].(map[string]any)
if roles["unavailable"] != true || roles["is_host"] != false || roles["is_bd"] != false || roles["is_coin_seller"] != false { if roles["unavailable"] != true || roles["is_host"] != false || roles["is_manager"] != false || roles["is_bd"] != false || roles["is_coin_seller"] != false {
t.Fatalf("roles degradation mismatch: %+v", roles) t.Fatalf("roles degradation mismatch: %+v", roles)
} }
entries := data["entries"].([]any) entries := data["entries"].([]any)
if overviewEntryVisible(entries, "host_center") || overviewEntryVisible(entries, "bd_center") || overviewEntryVisible(entries, "coin_seller_center") { if overviewEntryVisible(entries, "host_center") || overviewEntryVisible(entries, "manager_center") || overviewEntryVisible(entries, "bd_center") || overviewEntryVisible(entries, "coin_seller_center") {
t.Fatalf("degraded roles must not expose privileged entries: %+v", entries) t.Fatalf("degraded roles must not expose privileged entries: %+v", entries)
} }
} }

View File

@ -2427,20 +2427,14 @@ func TestGetAppVersionReturnsLatestVersionAndUpdateDecision(t *testing.T) {
func TestGetMyHostIdentityCombinesActiveHostAndBDProfiles(t *testing.T) { func TestGetMyHostIdentityCombinesActiveHostAndBDProfiles(t *testing.T) {
hostClient := &fakeUserHostClient{ hostClient := &fakeUserHostClient{
hostProfile: &userv1.HostProfile{ roleSummary: &userv1.UserRoleSummary{
UserId: 42, UserId: 42,
Status: "active", IsHost: true,
Source: "admin_create_agency", IsAgency: true,
}, IsManager: true,
bdProfile: &userv1.BDProfile{ IsBd: true,
UserId: 42, IsBdLeader: true,
Status: "active", IsCoinSeller: true,
Role: "bd_leader",
},
profile: &userv1.CoinSellerProfile{
UserId: 42,
Status: "active",
MerchantAssetType: "COIN_SELLER_COIN",
}, },
} }
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
@ -2456,14 +2450,8 @@ func TestGetMyHostIdentityCombinesActiveHostAndBDProfiles(t *testing.T) {
if recorder.Code != http.StatusOK { if recorder.Code != http.StatusOK {
t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String()) t.Fatalf("status mismatch: got %d body=%s", recorder.Code, recorder.Body.String())
} }
if hostClient.lastHost == nil || hostClient.lastHost.GetUserId() != 42 || hostClient.lastHost.GetMeta().GetRequestId() != assertGeneratedRequestID(t, recorder, "req-host-identity") { if hostClient.lastRoleSummary == nil || hostClient.lastRoleSummary.GetUserId() != 42 || hostClient.lastRoleSummary.GetMeta().GetRequestId() != assertGeneratedRequestID(t, recorder, "req-host-identity") {
t.Fatalf("host identity host request mismatch: %+v", hostClient.lastHost) t.Fatalf("host identity role summary request mismatch: %+v", hostClient.lastRoleSummary)
}
if hostClient.lastBD == nil || hostClient.lastBD.GetUserId() != 42 {
t.Fatalf("host identity bd request mismatch: %+v", hostClient.lastBD)
}
if hostClient.last == nil || hostClient.last.GetUserId() != 42 {
t.Fatalf("host identity coin seller request mismatch: %+v", hostClient.last)
} }
var response httpkit.ResponseEnvelope var response httpkit.ResponseEnvelope
if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil { if err := json.NewDecoder(recorder.Body).Decode(&response); err != nil {
@ -2473,6 +2461,7 @@ func TestGetMyHostIdentityCombinesActiveHostAndBDProfiles(t *testing.T) {
if response.Code != httpkit.CodeOK || !ok || if response.Code != httpkit.CodeOK || !ok ||
data["is_host"] != true || data["is_host"] != true ||
data["is_agent"] != true || data["is_agent"] != true ||
data["is_manager"] != true ||
data["is_bd"] != true || data["is_bd"] != true ||
data["is_bd_leader"] != true || data["is_bd_leader"] != true ||
data["is_coin_seller"] != true { data["is_coin_seller"] != true {
@ -2481,11 +2470,7 @@ func TestGetMyHostIdentityCombinesActiveHostAndBDProfiles(t *testing.T) {
} }
func TestGetMyHostIdentityTreatsMissingProfilesAsFalse(t *testing.T) { func TestGetMyHostIdentityTreatsMissingProfilesAsFalse(t *testing.T) {
hostClient := &fakeUserHostClient{ hostClient := &fakeUserHostClient{}
hostErr: xerr.ToGRPCError(xerr.New(xerr.NotFound, "host profile not found")),
bdErr: xerr.ToGRPCError(xerr.New(xerr.NotFound, "bd profile not found")),
err: xerr.ToGRPCError(xerr.New(xerr.NotFound, "coin seller profile not found")),
}
handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{})
handler.SetUserHostClient(hostClient) handler.SetUserHostClient(hostClient)
router := handler.Routes(auth.NewVerifier("secret")) router := handler.Routes(auth.NewVerifier("secret"))
@ -2507,6 +2492,7 @@ func TestGetMyHostIdentityTreatsMissingProfilesAsFalse(t *testing.T) {
if response.Code != httpkit.CodeOK || !ok || if response.Code != httpkit.CodeOK || !ok ||
data["is_host"] != false || data["is_host"] != false ||
data["is_agent"] != false || data["is_agent"] != false ||
data["is_manager"] != false ||
data["is_bd"] != false || data["is_bd"] != false ||
data["is_bd_leader"] != false || data["is_bd_leader"] != false ||
data["is_coin_seller"] != false { data["is_coin_seller"] != false {

View File

@ -1,26 +1,17 @@
package userapi package userapi
import ( import (
"hyapp/services/gateway-service/internal/transport/http/httpkit"
"net/http" "net/http"
userv1 "hyapp.local/api/proto/user/v1" userv1 "hyapp.local/api/proto/user/v1"
"hyapp/pkg/xerr"
"hyapp/services/gateway-service/internal/auth" "hyapp/services/gateway-service/internal/auth"
) "hyapp/services/gateway-service/internal/transport/http/httpkit"
const (
hostIdentityActiveStatus = "active"
hostIdentityAgencyInvitation = "agency_invitation"
hostIdentityAdminCreateAgency = "admin_create_agency"
hostIdentityBDRole = "bd"
hostIdentityBDLeaderRole = "bd_leader"
hostIdentityCoinSellerAsset = "COIN_SELLER_COIN"
) )
type userHostIdentityData struct { type userHostIdentityData struct {
IsHost bool `json:"is_host"` IsHost bool `json:"is_host"`
IsAgent bool `json:"is_agent"` IsAgent bool `json:"is_agent"`
IsManager bool `json:"is_manager"`
IsBD bool `json:"is_bd"` IsBD bool `json:"is_bd"`
IsBDLeader bool `json:"is_bd_leader"` IsBDLeader bool `json:"is_bd_leader"`
IsCoinSeller bool `json:"is_coin_seller"` IsCoinSeller bool `json:"is_coin_seller"`
@ -32,50 +23,24 @@ func (h *Handler) getMyHostIdentity(writer http.ResponseWriter, request *http.Re
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error") httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
return return
} }
resp, err := h.userHostClient.GetUserRoleSummary(request.Context(), &userv1.GetUserRoleSummaryRequest{
userID := auth.UserIDFromContext(request.Context())
identity := userHostIdentityData{}
hostResp, err := h.userHostClient.GetHostProfile(request.Context(), &userv1.GetHostProfileRequest{
Meta: httpkit.UserMeta(request, ""), Meta: httpkit.UserMeta(request, ""),
UserId: userID, UserId: auth.UserIDFromContext(request.Context()),
}) })
if err != nil { if err != nil {
if xerr.ReasonFromGRPC(err) != xerr.NotFound { httpkit.WriteRPCError(writer, request, err)
httpkit.WriteRPCError(writer, request, err) return
return
}
} else {
applyHostIdentity(&identity, hostResp.GetHostProfile())
} }
bdResp, err := h.userHostClient.GetBDProfile(request.Context(), &userv1.GetBDProfileRequest{ summary := resp.GetSummary()
Meta: httpkit.UserMeta(request, ""), httpkit.WriteOK(writer, request, userHostIdentityData{
UserId: userID, IsHost: summary.GetIsHost(),
IsAgent: summary.GetIsAgency(),
IsManager: summary.GetIsManager(),
IsBD: summary.GetIsBd(),
IsBDLeader: summary.GetIsBdLeader(),
IsCoinSeller: summary.GetIsCoinSeller(),
}) })
if err != nil {
if xerr.ReasonFromGRPC(err) != xerr.NotFound {
httpkit.WriteRPCError(writer, request, err)
return
}
} else {
applyBDIdentity(&identity, bdResp.GetBdProfile())
}
coinSellerResp, err := h.userHostClient.GetCoinSellerProfile(request.Context(), &userv1.GetCoinSellerProfileRequest{
Meta: httpkit.UserMeta(request, ""),
UserId: userID,
})
if err != nil {
if xerr.ReasonFromGRPC(err) != xerr.NotFound {
httpkit.WriteRPCError(writer, request, err)
return
}
} else {
applyCoinSellerIdentity(&identity, coinSellerResp.GetCoinSellerProfile())
}
httpkit.WriteOK(writer, request, identity)
} }
// getMyRoleSummary 返回我的页入口显隐所需的完整角色摘要。 // getMyRoleSummary 返回我的页入口显隐所需的完整角色摘要。
@ -96,6 +61,7 @@ func (h *Handler) getMyRoleSummary(writer http.ResponseWriter, request *http.Req
httpkit.WriteOK(writer, request, overviewRolesData{ httpkit.WriteOK(writer, request, overviewRolesData{
IsHost: summary.GetIsHost(), IsHost: summary.GetIsHost(),
IsAgency: summary.GetIsAgency(), IsAgency: summary.GetIsAgency(),
IsManager: summary.GetIsManager(),
IsBD: summary.GetIsBd(), IsBD: summary.GetIsBd(),
IsBDLeader: summary.GetIsBdLeader(), IsBDLeader: summary.GetIsBdLeader(),
IsCoinSeller: summary.GetIsCoinSeller(), IsCoinSeller: summary.GetIsCoinSeller(),
@ -107,39 +73,3 @@ func (h *Handler) getMyRoleSummary(writer http.ResponseWriter, request *http.Req
PendingRoleInvitations: summary.GetPendingRoleInvitations(), PendingRoleInvitations: summary.GetPendingRoleInvitations(),
}) })
} }
func applyHostIdentity(identity *userHostIdentityData, profile *userv1.HostProfile) {
if profile == nil || profile.GetStatus() != hostIdentityActiveStatus {
return
}
identity.IsHost = true
switch profile.GetSource() {
case hostIdentityAgencyInvitation, hostIdentityAdminCreateAgency:
identity.IsAgent = true
}
}
func applyBDIdentity(identity *userHostIdentityData, profile *userv1.BDProfile) {
if profile == nil || profile.GetStatus() != hostIdentityActiveStatus {
return
}
switch profile.GetRole() {
case hostIdentityBDLeaderRole:
identity.IsBD = true
identity.IsBDLeader = true
case hostIdentityBDRole:
identity.IsBD = true
}
}
func applyCoinSellerIdentity(identity *userHostIdentityData, profile *userv1.CoinSellerProfile) {
if profile == nil ||
profile.GetStatus() != hostIdentityActiveStatus ||
profile.GetMerchantAssetType() != hostIdentityCoinSellerAsset {
return
}
identity.IsCoinSeller = true
}

View File

@ -35,6 +35,7 @@ type overviewWalletData struct {
type overviewRolesData struct { type overviewRolesData struct {
IsHost bool `json:"is_host"` IsHost bool `json:"is_host"`
IsAgency bool `json:"is_agency"` IsAgency bool `json:"is_agency"`
IsManager bool `json:"is_manager"`
IsBD bool `json:"is_bd"` IsBD bool `json:"is_bd"`
IsBDLeader bool `json:"is_bd_leader"` IsBDLeader bool `json:"is_bd_leader"`
IsCoinSeller bool `json:"is_coin_seller"` IsCoinSeller bool `json:"is_coin_seller"`
@ -183,6 +184,7 @@ func (h *Handler) getOverviewRoles(ctx context.Context, request *http.Request, u
return overviewRolesData{ return overviewRolesData{
IsHost: summary.GetIsHost(), IsHost: summary.GetIsHost(),
IsAgency: summary.GetIsAgency(), IsAgency: summary.GetIsAgency(),
IsManager: summary.GetIsManager(),
IsBD: summary.GetIsBd(), IsBD: summary.GetIsBd(),
IsBDLeader: summary.GetIsBdLeader(), IsBDLeader: summary.GetIsBdLeader(),
IsCoinSeller: summary.GetIsCoinSeller(), IsCoinSeller: summary.GetIsCoinSeller(),
@ -245,10 +247,11 @@ func overviewEntries(roles overviewRolesData) []overviewEntryData {
{Key: "apply_host", Visible: !roles.IsHost, Enabled: true, Route: "/host/apply", IconKey: "host_apply", Order: 50}, {Key: "apply_host", Visible: !roles.IsHost, Enabled: true, Route: "/host/apply", IconKey: "host_apply", Order: 50},
{Key: "host_center", Visible: roles.IsHost, Enabled: roles.IsHost, Route: "/host", IconKey: "host_center", Order: 60}, {Key: "host_center", Visible: roles.IsHost, Enabled: roles.IsHost, Route: "/host", IconKey: "host_center", Order: 60},
{Key: "agency_center", Visible: roles.IsAgency, Enabled: roles.IsAgency, Route: "/agency", IconKey: "agency", Order: 70}, {Key: "agency_center", Visible: roles.IsAgency, Enabled: roles.IsAgency, Route: "/agency", IconKey: "agency", Order: 70},
{Key: "bd_center", Visible: roles.IsBD, Enabled: roles.IsBD, Route: "/bd", IconKey: "bd", Order: 80}, {Key: "manager_center", Visible: roles.IsManager, Enabled: roles.IsManager, Route: "/manager-center", IconKey: "manager_center", Order: 80},
{Key: "bd_leader_center", Visible: roles.IsBDLeader, Enabled: roles.IsBDLeader, Route: "/bd-leader", IconKey: "bd_leader", Order: 90}, {Key: "bd_center", Visible: roles.IsBD, Enabled: roles.IsBD, Route: "/bd", IconKey: "bd", Order: 90},
{Key: "coin_seller_center", Visible: roles.IsCoinSeller, Enabled: roles.IsCoinSeller, Route: "/coin-seller", IconKey: "coin_seller", Order: 100}, {Key: "bd_leader_center", Visible: roles.IsBDLeader, Enabled: roles.IsBDLeader, Route: "/bd-leader", IconKey: "bd_leader", Order: 100},
{Key: "settings", Visible: true, Enabled: true, Route: "/settings", IconKey: "settings", Order: 110}, {Key: "coin_seller_center", Visible: roles.IsCoinSeller, Enabled: roles.IsCoinSeller, Route: "/coin-seller", IconKey: "coin_seller", Order: 110},
{Key: "language", Visible: true, Enabled: true, Route: "/language", IconKey: "language", Order: 120}, {Key: "settings", Visible: true, Enabled: true, Route: "/settings", IconKey: "settings", Order: 120},
{Key: "language", Visible: true, Enabled: true, Route: "/language", IconKey: "language", Order: 130},
} }
} }

View File

@ -184,6 +184,7 @@ type UserRoleSummary struct {
UserID int64 UserID int64
IsHost bool IsHost bool
IsAgency bool IsAgency bool
IsManager bool
IsBD bool IsBD bool
IsBDLeader bool IsBDLeader bool
IsCoinSeller bool IsCoinSeller bool

View File

@ -579,7 +579,7 @@ func TestGetUserRoleSummaryAggregatesRolesAndPendingInvitations(t *testing.T) {
if err != nil { if err != nil {
t.Fatalf("GetUserRoleSummary failed: %v", err) t.Fatalf("GetUserRoleSummary failed: %v", err)
} }
if !summary.IsHost || !summary.IsAgency || !summary.IsBD || !summary.IsBDLeader || !summary.IsCoinSeller || summary.AgencyID != 933 || summary.BDID != 931 { if !summary.IsHost || !summary.IsAgency || !summary.IsManager || !summary.IsBD || !summary.IsBDLeader || !summary.IsCoinSeller || summary.AgencyID != 933 || summary.BDID != 931 {
t.Fatalf("role summary mismatch: %+v", summary) t.Fatalf("role summary mismatch: %+v", summary)
} }
pending, err := svc.GetUserRoleSummary(ctx, 932) pending, err := svc.GetUserRoleSummary(ctx, 932)

View File

@ -118,6 +118,8 @@ func (r *Repository) GetUserRoleSummary(ctx context.Context, userID int64) (host
summary.IsHost = hostStatus == hostdomain.HostStatusActive summary.IsHost = hostStatus == hostdomain.HostStatusActive
if ownerAgencyID > 0 { if ownerAgencyID > 0 {
summary.AgencyID = ownerAgencyID summary.AgencyID = ownerAgencyID
// 经理中心首版授权边界是 active Agency owner普通 Agency 成员只展示 Agency 入口,不具备 manager 身份。
summary.IsManager = true
} else { } else {
summary.AgencyID = hostAgencyID summary.AgencyID = hostAgencyID
} }

View File

@ -351,6 +351,7 @@ func toProtoUserRoleSummary(summary hostdomain.UserRoleSummary) *userv1.UserRole
UserId: summary.UserID, UserId: summary.UserID,
IsHost: summary.IsHost, IsHost: summary.IsHost,
IsAgency: summary.IsAgency, IsAgency: summary.IsAgency,
IsManager: summary.IsManager,
IsBd: summary.IsBD, IsBd: summary.IsBD,
IsBdLeader: summary.IsBDLeader, IsBdLeader: summary.IsBDLeader,
IsCoinSeller: summary.IsCoinSeller, IsCoinSeller: summary.IsCoinSeller,