diff --git a/api/proto/user/v1/host.pb.go b/api/proto/user/v1/host.pb.go index 4c9e0852..251820fb 100644 --- a/api/proto/user/v1/host.pb.go +++ b/api/proto/user/v1/host.pb.go @@ -442,7 +442,7 @@ func (x *CoinSellerProfile) GetUpdatedAtMs() int64 { } // UserRoleSummary 是 App 我的页和入口显隐使用的轻量角色摘要。 -// 它把 host、Agency、BD 和币商身份收敛到一次 user-service RPC,避免 gateway 随角色扩张继续 fanout。 +// 它把 host、Agency、manager、BD 和币商身份收敛到一次 user-service RPC,避免 gateway 随角色扩张继续 fanout。 type UserRoleSummary struct { state protoimpl.MessageState `protogen:"open.v1"` 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"` 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"` + IsManager bool `protobuf:"varint,13,opt,name=is_manager,json=isManager,proto3" json:"is_manager,omitempty"` unknownFields protoimpl.UnknownFields sizeCache protoimpl.SizeCache } @@ -575,6 +576,13 @@ func (x *UserRoleSummary) GetPendingRoleInvitations() int64 { return 0 } +func (x *UserRoleSummary) GetIsManager() bool { + if x != nil { + return x.IsManager + } + return false +} + // AgencyMembership 是 host 与 Agency 的有时效关系事实。 type AgencyMembership struct { 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" + "\x12created_by_user_id\x18\x04 \x01(\x03R\x0fcreatedByUserId\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" + "\auser_id\x18\x01 \x01(\x03R\x06userId\x12\x17\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" + " \x01(\tR\bbdStatus\x12,\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" + "\rmembership_id\x18\x01 \x01(\x03R\fmembershipId\x12\x1b\n" + "\tagency_id\x18\x02 \x01(\x03R\bagencyId\x12 \n" + diff --git a/api/proto/user/v1/host.proto b/api/proto/user/v1/host.proto index 8c43f891..5e210ead 100644 --- a/api/proto/user/v1/host.proto +++ b/api/proto/user/v1/host.proto @@ -57,7 +57,7 @@ message CoinSellerProfile { } // UserRoleSummary 是 App 我的页和入口显隐使用的轻量角色摘要。 -// 它把 host、Agency、BD 和币商身份收敛到一次 user-service RPC,避免 gateway 随角色扩张继续 fanout。 +// 它把 host、Agency、manager、BD 和币商身份收敛到一次 user-service RPC,避免 gateway 随角色扩张继续 fanout。 message UserRoleSummary { int64 user_id = 1; bool is_host = 2; @@ -71,6 +71,7 @@ message UserRoleSummary { string bd_status = 10; string coin_seller_status = 11; int64 pending_role_invitations = 12; + bool is_manager = 13; } // AgencyMembership 是 host 与 Agency 的有时效关系事实。 diff --git a/docs/openapi/gateway.swagger.yaml b/docs/openapi/gateway.swagger.yaml index a0f44bf0..4c1cb207 100644 --- a/docs/openapi/gateway.swagger.yaml +++ b/docs/openapi/gateway.swagger.yaml @@ -2717,7 +2717,10 @@ definitions: description: 用户是否拥有 active host profile。 is_agent: type: boolean - description: 用户是否拥有 active agency owner 身份。 + description: 用户是否拥有 Agency 入口身份;active Agency owner 或 Agency 来源主播都可能为 true。 + is_manager: + type: boolean + description: 用户是否拥有经理中心身份;首版等价于 active Agency owner。 is_bd: type: boolean description: 用户是否拥有 active BD 或 BD Leader 身份。 @@ -2732,6 +2735,7 @@ definitions: required: - is_host - is_agency + - is_manager - is_bd - is_bd_leader - is_coin_seller @@ -2741,6 +2745,8 @@ definitions: type: boolean is_agency: type: boolean + is_manager: + type: boolean is_bd: type: boolean is_bd_leader: @@ -3170,6 +3176,7 @@ definitions: type: string enum: - host-center + - manager-center - bd-center - bd-leader-center - agency-center diff --git a/docs/我的页概览接口.md b/docs/我的页概览接口.md index c5b75e11..25f4def5 100644 --- a/docs/我的页概览接口.md +++ b/docs/我的页概览接口.md @@ -49,6 +49,7 @@ Authorization: Bearer "roles": { "is_host": true, "is_agency": false, + "is_manager": false, "is_bd": false, "is_bd_leader": false, "is_coin_seller": false, @@ -116,6 +117,7 @@ Authorization: Bearer | `apply_host` | `roles.is_host == false` | | `host_center` | `roles.is_host == true` | | `agency_center` | `roles.is_agency == true` | +| `manager_center` | `roles.is_manager == true` | | `bd_center` | `roles.is_bd == true` | | `bd_leader_center` | `roles.is_bd_leader == true` | | `coin_seller_center` | `roles.is_coin_seller == true` | diff --git a/server/admin/internal/repository/seed.go b/server/admin/internal/repository/seed.go index 02334caf..b2697376 100644 --- a/server/admin/internal/repository/seed.go +++ b/server/admin/internal/repository/seed.go @@ -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: &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: &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: "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: "主播列表", 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: "Host 列表", Code: "host-org-hosts", Path: "/host/hosts", Icon: "users", PermissionCode: "host:view", Sort: 84, 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 { if _, err := s.syncDefaultMenu(s.db, menu); err != nil { diff --git a/services/gateway-service/internal/transport/http/my_page_overview_handler_test.go b/services/gateway-service/internal/transport/http/my_page_overview_handler_test.go index 34dc17ef..bc7d8c12 100644 --- a/services/gateway-service/internal/transport/http/my_page_overview_handler_test.go +++ b/services/gateway-service/internal/transport/http/my_page_overview_handler_test.go @@ -34,6 +34,7 @@ func TestGetMyOverviewComposesProfileWalletVIPStatsRolesAndEntries(t *testing.T) UserId: 42, IsHost: true, IsAgency: true, + IsManager: true, IsBd: true, IsBdLeader: true, IsCoinSeller: true, @@ -81,7 +82,7 @@ func TestGetMyOverviewComposesProfileWalletVIPStatsRolesAndEntries(t *testing.T) t.Fatalf("profile stats mismatch: %+v", stats) } 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) } wallet := data["wallet"].(map[string]any) @@ -106,6 +107,7 @@ func TestGetMyOverviewComposesProfileWalletVIPStatsRolesAndEntries(t *testing.T) !overviewEntryVisible(entries, "task") || !overviewEntryVisible(entries, "host_center") || !overviewEntryVisible(entries, "agency_center") || + !overviewEntryVisible(entries, "manager_center") || !overviewEntryVisible(entries, "bd_center") || !overviewEntryVisible(entries, "bd_leader_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) } 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) } entries := data["entries"].([]any) @@ -162,11 +164,11 @@ func TestGetMyOverviewDegradesOptionalUpstreamFailures(t *testing.T) { t.Fatalf("profile stats degradation mismatch: %+v", stats) } 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) } 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) } } diff --git a/services/gateway-service/internal/transport/http/response_test.go b/services/gateway-service/internal/transport/http/response_test.go index 325d370a..0f6b3d28 100644 --- a/services/gateway-service/internal/transport/http/response_test.go +++ b/services/gateway-service/internal/transport/http/response_test.go @@ -2427,20 +2427,14 @@ func TestGetAppVersionReturnsLatestVersionAndUpdateDecision(t *testing.T) { func TestGetMyHostIdentityCombinesActiveHostAndBDProfiles(t *testing.T) { hostClient := &fakeUserHostClient{ - hostProfile: &userv1.HostProfile{ - UserId: 42, - Status: "active", - Source: "admin_create_agency", - }, - bdProfile: &userv1.BDProfile{ - UserId: 42, - Status: "active", - Role: "bd_leader", - }, - profile: &userv1.CoinSellerProfile{ - UserId: 42, - Status: "active", - MerchantAssetType: "COIN_SELLER_COIN", + roleSummary: &userv1.UserRoleSummary{ + UserId: 42, + IsHost: true, + IsAgency: true, + IsManager: true, + IsBd: true, + IsBdLeader: true, + IsCoinSeller: true, }, } handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) @@ -2456,14 +2450,8 @@ func TestGetMyHostIdentityCombinesActiveHostAndBDProfiles(t *testing.T) { if recorder.Code != http.StatusOK { 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") { - t.Fatalf("host identity host request mismatch: %+v", hostClient.lastHost) - } - 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) + if hostClient.lastRoleSummary == nil || hostClient.lastRoleSummary.GetUserId() != 42 || hostClient.lastRoleSummary.GetMeta().GetRequestId() != assertGeneratedRequestID(t, recorder, "req-host-identity") { + t.Fatalf("host identity role summary request mismatch: %+v", hostClient.lastRoleSummary) } var response httpkit.ResponseEnvelope 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 || data["is_host"] != true || data["is_agent"] != true || + data["is_manager"] != true || data["is_bd"] != true || data["is_bd_leader"] != true || data["is_coin_seller"] != true { @@ -2481,11 +2470,7 @@ func TestGetMyHostIdentityCombinesActiveHostAndBDProfiles(t *testing.T) { } func TestGetMyHostIdentityTreatsMissingProfilesAsFalse(t *testing.T) { - 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")), - } + hostClient := &fakeUserHostClient{} handler := NewHandlerWithClients(&fakeRoomClient{}, nil, nil, &fakeUserProfileClient{}) handler.SetUserHostClient(hostClient) router := handler.Routes(auth.NewVerifier("secret")) @@ -2507,6 +2492,7 @@ func TestGetMyHostIdentityTreatsMissingProfilesAsFalse(t *testing.T) { if response.Code != httpkit.CodeOK || !ok || data["is_host"] != false || data["is_agent"] != false || + data["is_manager"] != false || data["is_bd"] != false || data["is_bd_leader"] != false || data["is_coin_seller"] != false { diff --git a/services/gateway-service/internal/transport/http/userapi/host_identity_handler.go b/services/gateway-service/internal/transport/http/userapi/host_identity_handler.go index d265e5e4..415fbe5c 100644 --- a/services/gateway-service/internal/transport/http/userapi/host_identity_handler.go +++ b/services/gateway-service/internal/transport/http/userapi/host_identity_handler.go @@ -1,26 +1,17 @@ package userapi import ( - "hyapp/services/gateway-service/internal/transport/http/httpkit" "net/http" userv1 "hyapp.local/api/proto/user/v1" - "hyapp/pkg/xerr" "hyapp/services/gateway-service/internal/auth" -) - -const ( - hostIdentityActiveStatus = "active" - hostIdentityAgencyInvitation = "agency_invitation" - hostIdentityAdminCreateAgency = "admin_create_agency" - hostIdentityBDRole = "bd" - hostIdentityBDLeaderRole = "bd_leader" - hostIdentityCoinSellerAsset = "COIN_SELLER_COIN" + "hyapp/services/gateway-service/internal/transport/http/httpkit" ) type userHostIdentityData struct { IsHost bool `json:"is_host"` IsAgent bool `json:"is_agent"` + IsManager bool `json:"is_manager"` IsBD bool `json:"is_bd"` IsBDLeader bool `json:"is_bd_leader"` 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") return } - - userID := auth.UserIDFromContext(request.Context()) - identity := userHostIdentityData{} - - hostResp, err := h.userHostClient.GetHostProfile(request.Context(), &userv1.GetHostProfileRequest{ + resp, err := h.userHostClient.GetUserRoleSummary(request.Context(), &userv1.GetUserRoleSummaryRequest{ Meta: httpkit.UserMeta(request, ""), - UserId: userID, + UserId: auth.UserIDFromContext(request.Context()), }) if err != nil { - if xerr.ReasonFromGRPC(err) != xerr.NotFound { - httpkit.WriteRPCError(writer, request, err) - return - } - } else { - applyHostIdentity(&identity, hostResp.GetHostProfile()) + httpkit.WriteRPCError(writer, request, err) + return } - bdResp, err := h.userHostClient.GetBDProfile(request.Context(), &userv1.GetBDProfileRequest{ - Meta: httpkit.UserMeta(request, ""), - UserId: userID, + summary := resp.GetSummary() + httpkit.WriteOK(writer, request, userHostIdentityData{ + 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 返回我的页入口显隐所需的完整角色摘要。 @@ -96,6 +61,7 @@ func (h *Handler) getMyRoleSummary(writer http.ResponseWriter, request *http.Req httpkit.WriteOK(writer, request, overviewRolesData{ IsHost: summary.GetIsHost(), IsAgency: summary.GetIsAgency(), + IsManager: summary.GetIsManager(), IsBD: summary.GetIsBd(), IsBDLeader: summary.GetIsBdLeader(), IsCoinSeller: summary.GetIsCoinSeller(), @@ -107,39 +73,3 @@ func (h *Handler) getMyRoleSummary(writer http.ResponseWriter, request *http.Req 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 -} diff --git a/services/gateway-service/internal/transport/http/userapi/my_page_overview_handler.go b/services/gateway-service/internal/transport/http/userapi/my_page_overview_handler.go index 522e8473..1b2d80f3 100644 --- a/services/gateway-service/internal/transport/http/userapi/my_page_overview_handler.go +++ b/services/gateway-service/internal/transport/http/userapi/my_page_overview_handler.go @@ -35,6 +35,7 @@ type overviewWalletData struct { type overviewRolesData struct { IsHost bool `json:"is_host"` IsAgency bool `json:"is_agency"` + IsManager bool `json:"is_manager"` IsBD bool `json:"is_bd"` IsBDLeader bool `json:"is_bd_leader"` IsCoinSeller bool `json:"is_coin_seller"` @@ -183,6 +184,7 @@ func (h *Handler) getOverviewRoles(ctx context.Context, request *http.Request, u return overviewRolesData{ IsHost: summary.GetIsHost(), IsAgency: summary.GetIsAgency(), + IsManager: summary.GetIsManager(), IsBD: summary.GetIsBd(), IsBDLeader: summary.GetIsBdLeader(), 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: "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: "bd_center", Visible: roles.IsBD, Enabled: roles.IsBD, Route: "/bd", IconKey: "bd", Order: 80}, - {Key: "bd_leader_center", Visible: roles.IsBDLeader, Enabled: roles.IsBDLeader, Route: "/bd-leader", IconKey: "bd_leader", Order: 90}, - {Key: "coin_seller_center", Visible: roles.IsCoinSeller, Enabled: roles.IsCoinSeller, Route: "/coin-seller", IconKey: "coin_seller", Order: 100}, - {Key: "settings", Visible: true, Enabled: true, Route: "/settings", IconKey: "settings", Order: 110}, - {Key: "language", Visible: true, Enabled: true, Route: "/language", IconKey: "language", Order: 120}, + {Key: "manager_center", Visible: roles.IsManager, Enabled: roles.IsManager, Route: "/manager-center", IconKey: "manager_center", Order: 80}, + {Key: "bd_center", Visible: roles.IsBD, Enabled: roles.IsBD, Route: "/bd", IconKey: "bd", Order: 90}, + {Key: "bd_leader_center", Visible: roles.IsBDLeader, Enabled: roles.IsBDLeader, Route: "/bd-leader", IconKey: "bd_leader", Order: 100}, + {Key: "coin_seller_center", Visible: roles.IsCoinSeller, Enabled: roles.IsCoinSeller, Route: "/coin-seller", IconKey: "coin_seller", Order: 110}, + {Key: "settings", Visible: true, Enabled: true, Route: "/settings", IconKey: "settings", Order: 120}, + {Key: "language", Visible: true, Enabled: true, Route: "/language", IconKey: "language", Order: 130}, } } diff --git a/services/user-service/internal/domain/host/host.go b/services/user-service/internal/domain/host/host.go index a0de61db..da5fc52b 100644 --- a/services/user-service/internal/domain/host/host.go +++ b/services/user-service/internal/domain/host/host.go @@ -184,6 +184,7 @@ type UserRoleSummary struct { UserID int64 IsHost bool IsAgency bool + IsManager bool IsBD bool IsBDLeader bool IsCoinSeller bool diff --git a/services/user-service/internal/service/host/service_test.go b/services/user-service/internal/service/host/service_test.go index c59315c4..89feef1e 100644 --- a/services/user-service/internal/service/host/service_test.go +++ b/services/user-service/internal/service/host/service_test.go @@ -579,7 +579,7 @@ func TestGetUserRoleSummaryAggregatesRolesAndPendingInvitations(t *testing.T) { if err != nil { 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) } pending, err := svc.GetUserRoleSummary(ctx, 932) diff --git a/services/user-service/internal/storage/mysql/host/queries.go b/services/user-service/internal/storage/mysql/host/queries.go index 9900ea12..d4a38b9f 100644 --- a/services/user-service/internal/storage/mysql/host/queries.go +++ b/services/user-service/internal/storage/mysql/host/queries.go @@ -118,6 +118,8 @@ func (r *Repository) GetUserRoleSummary(ctx context.Context, userID int64) (host summary.IsHost = hostStatus == hostdomain.HostStatusActive if ownerAgencyID > 0 { summary.AgencyID = ownerAgencyID + // 经理中心首版授权边界是 active Agency owner;普通 Agency 成员只展示 Agency 入口,不具备 manager 身份。 + summary.IsManager = true } else { summary.AgencyID = hostAgencyID } diff --git a/services/user-service/internal/transport/grpc/convert.go b/services/user-service/internal/transport/grpc/convert.go index 4377b028..2e8c656e 100644 --- a/services/user-service/internal/transport/grpc/convert.go +++ b/services/user-service/internal/transport/grpc/convert.go @@ -351,6 +351,7 @@ func toProtoUserRoleSummary(summary hostdomain.UserRoleSummary) *userv1.UserRole UserId: summary.UserID, IsHost: summary.IsHost, IsAgency: summary.IsAgency, + IsManager: summary.IsManager, IsBd: summary.IsBD, IsBdLeader: summary.IsBDLeader, IsCoinSeller: summary.IsCoinSeller,