增加bd leader
This commit is contained in:
parent
0fdaec9ef9
commit
6d4648bf8d
File diff suppressed because it is too large
Load Diff
@ -221,6 +221,28 @@ message InviteBDResponse {
|
||||
RoleInvitation invitation = 1;
|
||||
}
|
||||
|
||||
message ListBDLeaderBDsRequest {
|
||||
RequestMeta meta = 1;
|
||||
int64 leader_user_id = 2;
|
||||
string status = 3;
|
||||
int32 page_size = 4;
|
||||
}
|
||||
|
||||
message ListBDLeaderBDsResponse {
|
||||
repeated BDProfile bd_profiles = 1;
|
||||
}
|
||||
|
||||
message ListBDLeaderAgenciesRequest {
|
||||
RequestMeta meta = 1;
|
||||
int64 leader_user_id = 2;
|
||||
string status = 3;
|
||||
int32 page_size = 4;
|
||||
}
|
||||
|
||||
message ListBDLeaderAgenciesResponse {
|
||||
repeated Agency agencies = 1;
|
||||
}
|
||||
|
||||
message ProcessRoleInvitationRequest {
|
||||
RequestMeta meta = 1;
|
||||
string command_id = 2;
|
||||
@ -438,6 +460,8 @@ service UserHostService {
|
||||
rpc KickAgencyHost(KickAgencyHostRequest) returns (KickAgencyHostResponse);
|
||||
rpc InviteAgency(InviteAgencyRequest) returns (InviteAgencyResponse);
|
||||
rpc InviteBD(InviteBDRequest) returns (InviteBDResponse);
|
||||
rpc ListBDLeaderBDs(ListBDLeaderBDsRequest) returns (ListBDLeaderBDsResponse);
|
||||
rpc ListBDLeaderAgencies(ListBDLeaderAgenciesRequest) returns (ListBDLeaderAgenciesResponse);
|
||||
rpc ProcessRoleInvitation(ProcessRoleInvitationRequest) returns (ProcessRoleInvitationResponse);
|
||||
rpc GetHostProfile(GetHostProfileRequest) returns (GetHostProfileResponse);
|
||||
rpc GetBDProfile(GetBDProfileRequest) returns (GetBDProfileResponse);
|
||||
|
||||
@ -25,6 +25,8 @@ const (
|
||||
UserHostService_KickAgencyHost_FullMethodName = "/hyapp.user.v1.UserHostService/KickAgencyHost"
|
||||
UserHostService_InviteAgency_FullMethodName = "/hyapp.user.v1.UserHostService/InviteAgency"
|
||||
UserHostService_InviteBD_FullMethodName = "/hyapp.user.v1.UserHostService/InviteBD"
|
||||
UserHostService_ListBDLeaderBDs_FullMethodName = "/hyapp.user.v1.UserHostService/ListBDLeaderBDs"
|
||||
UserHostService_ListBDLeaderAgencies_FullMethodName = "/hyapp.user.v1.UserHostService/ListBDLeaderAgencies"
|
||||
UserHostService_ProcessRoleInvitation_FullMethodName = "/hyapp.user.v1.UserHostService/ProcessRoleInvitation"
|
||||
UserHostService_GetHostProfile_FullMethodName = "/hyapp.user.v1.UserHostService/GetHostProfile"
|
||||
UserHostService_GetBDProfile_FullMethodName = "/hyapp.user.v1.UserHostService/GetBDProfile"
|
||||
@ -49,6 +51,8 @@ type UserHostServiceClient interface {
|
||||
KickAgencyHost(ctx context.Context, in *KickAgencyHostRequest, opts ...grpc.CallOption) (*KickAgencyHostResponse, error)
|
||||
InviteAgency(ctx context.Context, in *InviteAgencyRequest, opts ...grpc.CallOption) (*InviteAgencyResponse, error)
|
||||
InviteBD(ctx context.Context, in *InviteBDRequest, opts ...grpc.CallOption) (*InviteBDResponse, error)
|
||||
ListBDLeaderBDs(ctx context.Context, in *ListBDLeaderBDsRequest, opts ...grpc.CallOption) (*ListBDLeaderBDsResponse, error)
|
||||
ListBDLeaderAgencies(ctx context.Context, in *ListBDLeaderAgenciesRequest, opts ...grpc.CallOption) (*ListBDLeaderAgenciesResponse, error)
|
||||
ProcessRoleInvitation(ctx context.Context, in *ProcessRoleInvitationRequest, opts ...grpc.CallOption) (*ProcessRoleInvitationResponse, error)
|
||||
GetHostProfile(ctx context.Context, in *GetHostProfileRequest, opts ...grpc.CallOption) (*GetHostProfileResponse, error)
|
||||
GetBDProfile(ctx context.Context, in *GetBDProfileRequest, opts ...grpc.CallOption) (*GetBDProfileResponse, error)
|
||||
@ -129,6 +133,26 @@ func (c *userHostServiceClient) InviteBD(ctx context.Context, in *InviteBDReques
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *userHostServiceClient) ListBDLeaderBDs(ctx context.Context, in *ListBDLeaderBDsRequest, opts ...grpc.CallOption) (*ListBDLeaderBDsResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(ListBDLeaderBDsResponse)
|
||||
err := c.cc.Invoke(ctx, UserHostService_ListBDLeaderBDs_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *userHostServiceClient) ListBDLeaderAgencies(ctx context.Context, in *ListBDLeaderAgenciesRequest, opts ...grpc.CallOption) (*ListBDLeaderAgenciesResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(ListBDLeaderAgenciesResponse)
|
||||
err := c.cc.Invoke(ctx, UserHostService_ListBDLeaderAgencies_FullMethodName, in, out, cOpts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
func (c *userHostServiceClient) ProcessRoleInvitation(ctx context.Context, in *ProcessRoleInvitationRequest, opts ...grpc.CallOption) (*ProcessRoleInvitationResponse, error) {
|
||||
cOpts := append([]grpc.CallOption{grpc.StaticMethod()}, opts...)
|
||||
out := new(ProcessRoleInvitationResponse)
|
||||
@ -241,6 +265,8 @@ type UserHostServiceServer interface {
|
||||
KickAgencyHost(context.Context, *KickAgencyHostRequest) (*KickAgencyHostResponse, error)
|
||||
InviteAgency(context.Context, *InviteAgencyRequest) (*InviteAgencyResponse, error)
|
||||
InviteBD(context.Context, *InviteBDRequest) (*InviteBDResponse, error)
|
||||
ListBDLeaderBDs(context.Context, *ListBDLeaderBDsRequest) (*ListBDLeaderBDsResponse, error)
|
||||
ListBDLeaderAgencies(context.Context, *ListBDLeaderAgenciesRequest) (*ListBDLeaderAgenciesResponse, error)
|
||||
ProcessRoleInvitation(context.Context, *ProcessRoleInvitationRequest) (*ProcessRoleInvitationResponse, error)
|
||||
GetHostProfile(context.Context, *GetHostProfileRequest) (*GetHostProfileResponse, error)
|
||||
GetBDProfile(context.Context, *GetBDProfileRequest) (*GetBDProfileResponse, error)
|
||||
@ -279,6 +305,12 @@ func (UnimplementedUserHostServiceServer) InviteAgency(context.Context, *InviteA
|
||||
func (UnimplementedUserHostServiceServer) InviteBD(context.Context, *InviteBDRequest) (*InviteBDResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method InviteBD not implemented")
|
||||
}
|
||||
func (UnimplementedUserHostServiceServer) ListBDLeaderBDs(context.Context, *ListBDLeaderBDsRequest) (*ListBDLeaderBDsResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method ListBDLeaderBDs not implemented")
|
||||
}
|
||||
func (UnimplementedUserHostServiceServer) ListBDLeaderAgencies(context.Context, *ListBDLeaderAgenciesRequest) (*ListBDLeaderAgenciesResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method ListBDLeaderAgencies not implemented")
|
||||
}
|
||||
func (UnimplementedUserHostServiceServer) ProcessRoleInvitation(context.Context, *ProcessRoleInvitationRequest) (*ProcessRoleInvitationResponse, error) {
|
||||
return nil, status.Error(codes.Unimplemented, "method ProcessRoleInvitation not implemented")
|
||||
}
|
||||
@ -438,6 +470,42 @@ func _UserHostService_InviteBD_Handler(srv interface{}, ctx context.Context, dec
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _UserHostService_ListBDLeaderBDs_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListBDLeaderBDsRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(UserHostServiceServer).ListBDLeaderBDs(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: UserHostService_ListBDLeaderBDs_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(UserHostServiceServer).ListBDLeaderBDs(ctx, req.(*ListBDLeaderBDsRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _UserHostService_ListBDLeaderAgencies_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ListBDLeaderAgenciesRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(UserHostServiceServer).ListBDLeaderAgencies(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: UserHostService_ListBDLeaderAgencies_FullMethodName,
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(UserHostServiceServer).ListBDLeaderAgencies(ctx, req.(*ListBDLeaderAgenciesRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
func _UserHostService_ProcessRoleInvitation_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ProcessRoleInvitationRequest)
|
||||
if err := dec(in); err != nil {
|
||||
@ -649,6 +717,14 @@ var UserHostService_ServiceDesc = grpc.ServiceDesc{
|
||||
MethodName: "InviteBD",
|
||||
Handler: _UserHostService_InviteBD_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListBDLeaderBDs",
|
||||
Handler: _UserHostService_ListBDLeaderBDs_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ListBDLeaderAgencies",
|
||||
Handler: _UserHostService_ListBDLeaderAgencies_Handler,
|
||||
},
|
||||
{
|
||||
MethodName: "ProcessRoleInvitation",
|
||||
Handler: _UserHostService_ProcessRoleInvitation_Handler,
|
||||
|
||||
@ -69,6 +69,10 @@ type UserCountryQueryClient interface {
|
||||
type UserHostClient interface {
|
||||
SearchAgencies(ctx context.Context, req *userv1.SearchAgenciesRequest) (*userv1.SearchAgenciesResponse, error)
|
||||
ApplyToAgency(ctx context.Context, req *userv1.ApplyToAgencyRequest) (*userv1.ApplyToAgencyResponse, error)
|
||||
InviteAgency(ctx context.Context, req *userv1.InviteAgencyRequest) (*userv1.InviteAgencyResponse, error)
|
||||
InviteBD(ctx context.Context, req *userv1.InviteBDRequest) (*userv1.InviteBDResponse, error)
|
||||
ListBDLeaderBDs(ctx context.Context, req *userv1.ListBDLeaderBDsRequest) (*userv1.ListBDLeaderBDsResponse, error)
|
||||
ListBDLeaderAgencies(ctx context.Context, req *userv1.ListBDLeaderAgenciesRequest) (*userv1.ListBDLeaderAgenciesResponse, error)
|
||||
GetHostProfile(ctx context.Context, req *userv1.GetHostProfileRequest) (*userv1.GetHostProfileResponse, error)
|
||||
GetBDProfile(ctx context.Context, req *userv1.GetBDProfileRequest) (*userv1.GetBDProfileResponse, error)
|
||||
GetCoinSellerProfile(ctx context.Context, req *userv1.GetCoinSellerProfileRequest) (*userv1.GetCoinSellerProfileResponse, error)
|
||||
@ -315,6 +319,22 @@ func (c *grpcUserHostClient) ApplyToAgency(ctx context.Context, req *userv1.Appl
|
||||
return c.client.ApplyToAgency(ctx, req)
|
||||
}
|
||||
|
||||
func (c *grpcUserHostClient) InviteAgency(ctx context.Context, req *userv1.InviteAgencyRequest) (*userv1.InviteAgencyResponse, error) {
|
||||
return c.client.InviteAgency(ctx, req)
|
||||
}
|
||||
|
||||
func (c *grpcUserHostClient) InviteBD(ctx context.Context, req *userv1.InviteBDRequest) (*userv1.InviteBDResponse, error) {
|
||||
return c.client.InviteBD(ctx, req)
|
||||
}
|
||||
|
||||
func (c *grpcUserHostClient) ListBDLeaderBDs(ctx context.Context, req *userv1.ListBDLeaderBDsRequest) (*userv1.ListBDLeaderBDsResponse, error) {
|
||||
return c.client.ListBDLeaderBDs(ctx, req)
|
||||
}
|
||||
|
||||
func (c *grpcUserHostClient) ListBDLeaderAgencies(ctx context.Context, req *userv1.ListBDLeaderAgenciesRequest) (*userv1.ListBDLeaderAgenciesResponse, error) {
|
||||
return c.client.ListBDLeaderAgencies(ctx, req)
|
||||
}
|
||||
|
||||
func (c *grpcUserHostClient) GetHostProfile(ctx context.Context, req *userv1.GetHostProfileRequest) (*userv1.GetHostProfileResponse, error) {
|
||||
return c.client.GetHostProfile(ctx, req)
|
||||
}
|
||||
|
||||
@ -79,6 +79,11 @@ type UserHandlers struct {
|
||||
ListAgencyCenterApplications http.HandlerFunc
|
||||
ReviewAgencyCenterApplication http.HandlerFunc
|
||||
RemoveAgencyCenterHost http.HandlerFunc
|
||||
GetBDLeaderCenterOverview http.HandlerFunc
|
||||
ListBDLeaderCenterBDs http.HandlerFunc
|
||||
ListBDLeaderCenterAgencies http.HandlerFunc
|
||||
InviteBDLeaderBD http.HandlerFunc
|
||||
InviteBDLeaderAgency http.HandlerFunc
|
||||
CompleteMyOnboarding http.HandlerFunc
|
||||
UpdateMyProfile http.HandlerFunc
|
||||
ChangeMyCountry http.HandlerFunc
|
||||
@ -310,6 +315,11 @@ func (r routes) registerUserRoutes() {
|
||||
r.profile("/agency-center/applications", http.MethodGet, h.ListAgencyCenterApplications)
|
||||
r.profile("/agency-center/applications/{application_id}/review", http.MethodPost, h.ReviewAgencyCenterApplication)
|
||||
r.profile("/agency-center/hosts/{host_user_id}/remove", http.MethodPost, h.RemoveAgencyCenterHost)
|
||||
r.profile("/bd-leader-center/overview", http.MethodGet, h.GetBDLeaderCenterOverview)
|
||||
r.profile("/bd-leader-center/bds", http.MethodGet, h.ListBDLeaderCenterBDs)
|
||||
r.profile("/bd-leader-center/agencies", http.MethodGet, h.ListBDLeaderCenterAgencies)
|
||||
r.profile("/bd-leader/invitations/bd", http.MethodPost, h.InviteBDLeaderBD)
|
||||
r.profile("/bd-leader/invitations/agency", http.MethodPost, h.InviteBDLeaderAgency)
|
||||
r.auth("/users/me/onboarding/complete", "", h.CompleteMyOnboarding)
|
||||
r.profile("/users/me/profile/update", "", h.UpdateMyProfile)
|
||||
r.profile("/users/me/country/change", "", h.ChangeMyCountry)
|
||||
|
||||
@ -395,6 +395,18 @@ type fakeUserHostClient struct {
|
||||
lastApplyAgency *userv1.ApplyToAgencyRequest
|
||||
applyAgencyResp *userv1.ApplyToAgencyResponse
|
||||
applyAgencyErr error
|
||||
lastInviteAgency *userv1.InviteAgencyRequest
|
||||
inviteAgencyResp *userv1.InviteAgencyResponse
|
||||
inviteAgencyErr error
|
||||
lastInviteBD *userv1.InviteBDRequest
|
||||
inviteBDResp *userv1.InviteBDResponse
|
||||
inviteBDErr error
|
||||
lastLeaderBDs *userv1.ListBDLeaderBDsRequest
|
||||
leaderBDsResp *userv1.ListBDLeaderBDsResponse
|
||||
leaderBDsErr error
|
||||
lastLeaderAgencies *userv1.ListBDLeaderAgenciesRequest
|
||||
leaderAgenciesResp *userv1.ListBDLeaderAgenciesResponse
|
||||
leaderAgenciesErr error
|
||||
last *userv1.GetCoinSellerProfileRequest
|
||||
profile *userv1.CoinSellerProfile
|
||||
err error
|
||||
@ -1082,6 +1094,50 @@ func (f *fakeUserHostClient) ApplyToAgency(_ context.Context, req *userv1.ApplyT
|
||||
return &userv1.ApplyToAgencyResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeUserHostClient) InviteAgency(_ context.Context, req *userv1.InviteAgencyRequest) (*userv1.InviteAgencyResponse, error) {
|
||||
f.lastInviteAgency = req
|
||||
if f.inviteAgencyErr != nil {
|
||||
return nil, f.inviteAgencyErr
|
||||
}
|
||||
if f.inviteAgencyResp != nil {
|
||||
return f.inviteAgencyResp, nil
|
||||
}
|
||||
return &userv1.InviteAgencyResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeUserHostClient) InviteBD(_ context.Context, req *userv1.InviteBDRequest) (*userv1.InviteBDResponse, error) {
|
||||
f.lastInviteBD = req
|
||||
if f.inviteBDErr != nil {
|
||||
return nil, f.inviteBDErr
|
||||
}
|
||||
if f.inviteBDResp != nil {
|
||||
return f.inviteBDResp, nil
|
||||
}
|
||||
return &userv1.InviteBDResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeUserHostClient) ListBDLeaderBDs(_ context.Context, req *userv1.ListBDLeaderBDsRequest) (*userv1.ListBDLeaderBDsResponse, error) {
|
||||
f.lastLeaderBDs = req
|
||||
if f.leaderBDsErr != nil {
|
||||
return nil, f.leaderBDsErr
|
||||
}
|
||||
if f.leaderBDsResp != nil {
|
||||
return f.leaderBDsResp, nil
|
||||
}
|
||||
return &userv1.ListBDLeaderBDsResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeUserHostClient) ListBDLeaderAgencies(_ context.Context, req *userv1.ListBDLeaderAgenciesRequest) (*userv1.ListBDLeaderAgenciesResponse, error) {
|
||||
f.lastLeaderAgencies = req
|
||||
if f.leaderAgenciesErr != nil {
|
||||
return nil, f.leaderAgenciesErr
|
||||
}
|
||||
if f.leaderAgenciesResp != nil {
|
||||
return f.leaderAgenciesResp, nil
|
||||
}
|
||||
return &userv1.ListBDLeaderAgenciesResponse{}, nil
|
||||
}
|
||||
|
||||
func (f *fakeUserHostClient) GetHostProfile(_ context.Context, req *userv1.GetHostProfileRequest) (*userv1.GetHostProfileResponse, error) {
|
||||
f.lastHost = req
|
||||
if f.hostErr != nil {
|
||||
|
||||
@ -0,0 +1,363 @@
|
||||
package userapi
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
userv1 "hyapp.local/api/proto/user/v1"
|
||||
"hyapp/services/gateway-service/internal/auth"
|
||||
"hyapp/services/gateway-service/internal/transport/http/httpkit"
|
||||
)
|
||||
|
||||
const (
|
||||
adminSalaryAssetType = "ADMIN_SALARY_USD"
|
||||
bdStatusActive = "active"
|
||||
)
|
||||
|
||||
type bdLeaderProfileData struct {
|
||||
UserID string `json:"user_id"`
|
||||
Role string `json:"role"`
|
||||
RegionID int64 `json:"region_id"`
|
||||
ParentLeaderUserID string `json:"parent_leader_user_id,omitempty"`
|
||||
Status string `json:"status"`
|
||||
CreatedByUserID string `json:"created_by_user_id,omitempty"`
|
||||
CreatedAtMS int64 `json:"created_at_ms"`
|
||||
UpdatedAtMS int64 `json:"updated_at_ms"`
|
||||
}
|
||||
|
||||
func (h *Handler) getBDLeaderCenterOverview(writer http.ResponseWriter, request *http.Request) {
|
||||
userID := auth.UserIDFromContext(request.Context())
|
||||
profile, ok := h.resolveActiveBDLeader(writer, request, userID)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if h.walletClient == nil {
|
||||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||||
return
|
||||
}
|
||||
|
||||
users, err := h.userProfiles(request, []int64{userID})
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
salary, err := h.salaryBalance(request, userID, adminSalaryAssetType)
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
bdResp, err := h.userHostClient.ListBDLeaderBDs(request.Context(), &userv1.ListBDLeaderBDsRequest{
|
||||
Meta: httpkit.UserMeta(request, ""),
|
||||
LeaderUserId: userID,
|
||||
Status: bdStatusActive,
|
||||
PageSize: 100,
|
||||
})
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
agencyResp, err := h.userHostClient.ListBDLeaderAgencies(request.Context(), &userv1.ListBDLeaderAgenciesRequest{
|
||||
Meta: httpkit.UserMeta(request, ""),
|
||||
LeaderUserId: userID,
|
||||
Status: agencyStatusActive,
|
||||
PageSize: 100,
|
||||
})
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
|
||||
httpkit.WriteOK(writer, request, map[string]any{
|
||||
"profile": users[userID],
|
||||
"bd_profile": bdLeaderProfileFromProto(profile),
|
||||
"salary": salary,
|
||||
"overview": map[string]any{
|
||||
"total_bd": len(bdResp.GetBdProfiles()),
|
||||
"total_agency": len(agencyResp.GetAgencies()),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
func (h *Handler) listBDLeaderCenterBDs(writer http.ResponseWriter, request *http.Request) {
|
||||
userID := auth.UserIDFromContext(request.Context())
|
||||
if _, ok := h.resolveActiveBDLeader(writer, request, userID); !ok {
|
||||
return
|
||||
}
|
||||
pageSize := queryPageSize(request, 20)
|
||||
resp, err := h.userHostClient.ListBDLeaderBDs(request.Context(), &userv1.ListBDLeaderBDsRequest{
|
||||
Meta: httpkit.UserMeta(request, ""),
|
||||
LeaderUserId: userID,
|
||||
Status: bdStatusActive,
|
||||
PageSize: int32(pageSize),
|
||||
})
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
agencyResp, err := h.userHostClient.ListBDLeaderAgencies(request.Context(), &userv1.ListBDLeaderAgenciesRequest{
|
||||
Meta: httpkit.UserMeta(request, ""),
|
||||
LeaderUserId: userID,
|
||||
Status: agencyStatusActive,
|
||||
PageSize: 100,
|
||||
})
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
agencyCounts := make(map[int64]int)
|
||||
for _, agency := range agencyResp.GetAgencies() {
|
||||
agencyCounts[agency.GetParentBdUserId()]++
|
||||
}
|
||||
|
||||
userIDs := make([]int64, 0, len(resp.GetBdProfiles()))
|
||||
for _, profile := range resp.GetBdProfiles() {
|
||||
userIDs = append(userIDs, profile.GetUserId())
|
||||
}
|
||||
users, err := h.userProfiles(request, userIDs)
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
|
||||
items := make([]map[string]any, 0, len(resp.GetBdProfiles()))
|
||||
for _, profile := range resp.GetBdProfiles() {
|
||||
user := users[profile.GetUserId()]
|
||||
items = append(items, map[string]any{
|
||||
"user_id": int64String(profile.GetUserId()),
|
||||
"display_user_id": user.DisplayUserID,
|
||||
"username": user.Username,
|
||||
"avatar": user.Avatar,
|
||||
"role": profile.GetRole(),
|
||||
"status": profile.GetStatus(),
|
||||
"region_id": profile.GetRegionId(),
|
||||
"agency_count": agencyCounts[profile.GetUserId()],
|
||||
})
|
||||
}
|
||||
httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items)})
|
||||
}
|
||||
|
||||
func (h *Handler) listBDLeaderCenterAgencies(writer http.ResponseWriter, request *http.Request) {
|
||||
userID := auth.UserIDFromContext(request.Context())
|
||||
if _, ok := h.resolveActiveBDLeader(writer, request, userID); !ok {
|
||||
return
|
||||
}
|
||||
pageSize := queryPageSize(request, 20)
|
||||
resp, err := h.userHostClient.ListBDLeaderAgencies(request.Context(), &userv1.ListBDLeaderAgenciesRequest{
|
||||
Meta: httpkit.UserMeta(request, ""),
|
||||
LeaderUserId: userID,
|
||||
Status: agencyStatusActive,
|
||||
PageSize: int32(pageSize),
|
||||
})
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
|
||||
userIDs := make([]int64, 0, len(resp.GetAgencies()))
|
||||
for _, agency := range resp.GetAgencies() {
|
||||
userIDs = append(userIDs, agency.GetOwnerUserId(), agency.GetParentBdUserId())
|
||||
}
|
||||
users, err := h.userProfiles(request, userIDs)
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
|
||||
items := make([]map[string]any, 0, len(resp.GetAgencies()))
|
||||
for _, agency := range resp.GetAgencies() {
|
||||
membersResp, err := h.userHostClient.GetAgencyMembers(request.Context(), &userv1.GetAgencyMembersRequest{
|
||||
Meta: httpkit.UserMeta(request, ""),
|
||||
AgencyId: agency.GetAgencyId(),
|
||||
Status: membershipStatusActive,
|
||||
})
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
owner := users[agency.GetOwnerUserId()]
|
||||
parentBD := users[agency.GetParentBdUserId()]
|
||||
items = append(items, map[string]any{
|
||||
"agency": hostAgencyFromProto(agency),
|
||||
"owner": owner,
|
||||
"parent_bd": parentBD,
|
||||
"host_count": len(membersResp.GetMemberships()),
|
||||
"created_at_ms": agency.GetCreatedAtMs(),
|
||||
})
|
||||
}
|
||||
httpkit.WriteOK(writer, request, map[string]any{"items": items, "total": len(items)})
|
||||
}
|
||||
|
||||
func (h *Handler) inviteBDLeaderBD(writer http.ResponseWriter, request *http.Request) {
|
||||
h.inviteBDLeaderRole(writer, request, "bd")
|
||||
}
|
||||
|
||||
func (h *Handler) inviteBDLeaderAgency(writer http.ResponseWriter, request *http.Request) {
|
||||
h.inviteBDLeaderRole(writer, request, "agency")
|
||||
}
|
||||
|
||||
func (h *Handler) inviteBDLeaderRole(writer http.ResponseWriter, request *http.Request, role string) {
|
||||
userID := auth.UserIDFromContext(request.Context())
|
||||
if _, ok := h.resolveActiveBDLeader(writer, request, userID); !ok {
|
||||
return
|
||||
}
|
||||
var body map[string]any
|
||||
if !httpkit.Decode(writer, request, &body) {
|
||||
return
|
||||
}
|
||||
commandID := stringJSONValue(body["command_id"])
|
||||
targetUserID := int64JSONValue(body["target_user_id"])
|
||||
if commandID == "" || targetUserID <= 0 {
|
||||
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
||||
return
|
||||
}
|
||||
|
||||
if role == "bd" {
|
||||
resp, err := h.userHostClient.InviteBD(request.Context(), &userv1.InviteBDRequest{
|
||||
Meta: httpkit.UserMeta(request, ""),
|
||||
CommandId: commandID,
|
||||
InviterUserId: userID,
|
||||
TargetUserId: targetUserID,
|
||||
})
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
httpkit.WriteOK(writer, request, map[string]any{"invitation": roleInvitationData(resp.GetInvitation())})
|
||||
return
|
||||
}
|
||||
|
||||
agencyName := stringJSONValue(body["agency_name"])
|
||||
if agencyName == "" {
|
||||
users, err := h.userProfiles(request, []int64{targetUserID})
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
target := users[targetUserID]
|
||||
agencyName = firstBDLeaderNonBlank(target.Username, target.DisplayUserID, int64String(targetUserID))
|
||||
}
|
||||
resp, err := h.userHostClient.InviteAgency(request.Context(), &userv1.InviteAgencyRequest{
|
||||
Meta: httpkit.UserMeta(request, ""),
|
||||
CommandId: commandID,
|
||||
InviterUserId: userID,
|
||||
TargetUserId: targetUserID,
|
||||
AgencyName: agencyName,
|
||||
})
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
httpkit.WriteOK(writer, request, map[string]any{"invitation": roleInvitationData(resp.GetInvitation())})
|
||||
}
|
||||
|
||||
func (h *Handler) resolveActiveBDLeader(writer http.ResponseWriter, request *http.Request, userID int64) (*userv1.BDProfile, bool) {
|
||||
if h.userHostClient == nil {
|
||||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||||
return nil, false
|
||||
}
|
||||
resp, err := h.userHostClient.GetBDProfile(request.Context(), &userv1.GetBDProfileRequest{
|
||||
Meta: httpkit.UserMeta(request, ""),
|
||||
UserId: userID,
|
||||
})
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return nil, false
|
||||
}
|
||||
profile := resp.GetBdProfile()
|
||||
if profile == nil || profile.GetRole() != "bd_leader" || profile.GetStatus() != bdStatusActive {
|
||||
httpkit.WriteError(writer, request, http.StatusForbidden, httpkit.CodePermissionDenied, "permission denied")
|
||||
return nil, false
|
||||
}
|
||||
return profile, true
|
||||
}
|
||||
|
||||
func bdLeaderProfileFromProto(profile *userv1.BDProfile) bdLeaderProfileData {
|
||||
if profile == nil {
|
||||
return bdLeaderProfileData{}
|
||||
}
|
||||
return bdLeaderProfileData{
|
||||
UserID: int64String(profile.GetUserId()),
|
||||
Role: profile.GetRole(),
|
||||
RegionID: profile.GetRegionId(),
|
||||
ParentLeaderUserID: int64String(profile.GetParentLeaderUserId()),
|
||||
Status: profile.GetStatus(),
|
||||
CreatedByUserID: int64String(profile.GetCreatedByUserId()),
|
||||
CreatedAtMS: profile.GetCreatedAtMs(),
|
||||
UpdatedAtMS: profile.GetUpdatedAtMs(),
|
||||
}
|
||||
}
|
||||
|
||||
func roleInvitationData(invitation *userv1.RoleInvitation) map[string]any {
|
||||
if invitation == nil {
|
||||
return map[string]any{}
|
||||
}
|
||||
return map[string]any{
|
||||
"invitation_id": int64String(invitation.GetInvitationId()),
|
||||
"command_id": invitation.GetCommandId(),
|
||||
"invitation_type": invitation.GetInvitationType(),
|
||||
"status": invitation.GetStatus(),
|
||||
"inviter_user_id": int64String(invitation.GetInviterUserId()),
|
||||
"target_user_id": int64String(invitation.GetTargetUserId()),
|
||||
"region_id": invitation.GetRegionId(),
|
||||
"agency_name": invitation.GetAgencyName(),
|
||||
"parent_bd_user_id": int64String(invitation.GetParentBdUserId()),
|
||||
"parent_leader_user_id": int64String(invitation.GetParentLeaderUserId()),
|
||||
"processed_by_user_id": int64String(invitation.GetProcessedByUserId()),
|
||||
"process_reason": invitation.GetProcessReason(),
|
||||
"processed_at_ms": invitation.GetProcessedAtMs(),
|
||||
"created_at_ms": invitation.GetCreatedAtMs(),
|
||||
"updated_at_ms": invitation.GetUpdatedAtMs(),
|
||||
}
|
||||
}
|
||||
|
||||
func queryPageSize(request *http.Request, fallback int) int {
|
||||
value, _ := strconv.Atoi(strings.TrimSpace(request.URL.Query().Get("page_size")))
|
||||
if value <= 0 {
|
||||
value = fallback
|
||||
}
|
||||
if value > 100 {
|
||||
return 100
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
func stringJSONValue(value any) string {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
return strings.TrimSpace(v)
|
||||
case float64:
|
||||
return strconv.FormatInt(int64(v), 10)
|
||||
case int64:
|
||||
return strconv.FormatInt(v, 10)
|
||||
case int:
|
||||
return strconv.Itoa(v)
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func int64JSONValue(value any) int64 {
|
||||
switch v := value.(type) {
|
||||
case string:
|
||||
parsed, _ := strconv.ParseInt(strings.TrimSpace(v), 10, 64)
|
||||
return parsed
|
||||
case float64:
|
||||
return int64(v)
|
||||
case int64:
|
||||
return v
|
||||
case int:
|
||||
return int64(v)
|
||||
default:
|
||||
return 0
|
||||
}
|
||||
}
|
||||
|
||||
func firstBDLeaderNonBlank(values ...string) string {
|
||||
for _, value := range values {
|
||||
if trimmed := strings.TrimSpace(value); trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
}
|
||||
return "-"
|
||||
}
|
||||
@ -63,6 +63,11 @@ func (h *Handler) UserHandlers() httproutes.UserHandlers {
|
||||
ListAgencyCenterApplications: h.listAgencyCenterApplications,
|
||||
ReviewAgencyCenterApplication: h.reviewAgencyCenterApplication,
|
||||
RemoveAgencyCenterHost: h.removeAgencyCenterHost,
|
||||
GetBDLeaderCenterOverview: h.getBDLeaderCenterOverview,
|
||||
ListBDLeaderCenterBDs: h.listBDLeaderCenterBDs,
|
||||
ListBDLeaderCenterAgencies: h.listBDLeaderCenterAgencies,
|
||||
InviteBDLeaderBD: h.inviteBDLeaderBD,
|
||||
InviteBDLeaderAgency: h.inviteBDLeaderAgency,
|
||||
CompleteMyOnboarding: h.completeMyOnboarding,
|
||||
UpdateMyProfile: h.updateMyProfile,
|
||||
ChangeMyCountry: h.changeMyCountry,
|
||||
|
||||
@ -79,6 +79,9 @@ type InviteAgencyCommand struct {
|
||||
InviterUserID int64
|
||||
TargetUserID int64
|
||||
AgencyName string
|
||||
AgencyID int64
|
||||
MembershipID int64
|
||||
EventID int64
|
||||
NowMs int64
|
||||
}
|
||||
|
||||
@ -95,9 +98,24 @@ type InviteBDCommand struct {
|
||||
InvitationID int64
|
||||
InviterUserID int64
|
||||
TargetUserID int64
|
||||
EventID int64
|
||||
NowMs int64
|
||||
}
|
||||
|
||||
// ListBDLeaderBDsCommand 描述 BD Leader 查询直属 BD 列表的条件。
|
||||
type ListBDLeaderBDsCommand struct {
|
||||
LeaderUserID int64
|
||||
Status string
|
||||
PageSize int
|
||||
}
|
||||
|
||||
// ListBDLeaderAgenciesCommand 描述 BD Leader 查询下级 Agency 列表的条件。
|
||||
type ListBDLeaderAgenciesCommand struct {
|
||||
LeaderUserID int64
|
||||
Status string
|
||||
PageSize int
|
||||
}
|
||||
|
||||
// ProcessRoleInvitationInput 是处理角色邀请的外部输入。
|
||||
type ProcessRoleInvitationInput struct {
|
||||
CommandID string
|
||||
|
||||
@ -20,6 +20,8 @@ type Repository interface {
|
||||
KickAgencyHost(ctx context.Context, command KickAgencyHostCommand) (hostdomain.KickAgencyHostResult, error)
|
||||
InviteAgency(ctx context.Context, command InviteAgencyCommand) (hostdomain.RoleInvitation, error)
|
||||
InviteBD(ctx context.Context, command InviteBDCommand) (hostdomain.RoleInvitation, error)
|
||||
ListBDLeaderBDs(ctx context.Context, command ListBDLeaderBDsCommand) ([]hostdomain.BDProfile, error)
|
||||
ListBDLeaderAgencies(ctx context.Context, command ListBDLeaderAgenciesCommand) ([]hostdomain.Agency, error)
|
||||
ProcessRoleInvitation(ctx context.Context, command ProcessRoleInvitationCommand) (hostdomain.ProcessRoleInvitationResult, error)
|
||||
CreateBDLeader(ctx context.Context, command CreateBDLeaderCommand) (hostdomain.BDProfile, error)
|
||||
CreateBD(ctx context.Context, command CreateBDCommand) (hostdomain.BDProfile, error)
|
||||
@ -196,12 +198,12 @@ func (s *Service) GetAgency(ctx context.Context, agencyID int64) (hostdomain.Age
|
||||
return s.repository.GetAgency(ctx, agencyID)
|
||||
}
|
||||
|
||||
// InviteAgency 创建邀请用户成为 Agency 拥有者的待处理邀请。
|
||||
// InviteAgency 由 BD 直接创建用户的 Agency owner 身份,不再等待目标用户确认。
|
||||
func (s *Service) InviteAgency(ctx context.Context, command InviteAgencyInput) (hostdomain.RoleInvitation, error) {
|
||||
if err := s.requireWriteDependencies(); err != nil {
|
||||
return hostdomain.RoleInvitation{}, err
|
||||
}
|
||||
// Agency 名称在邀请创建时固化,接受时用这份快照创建 Agency。
|
||||
// Agency 名称在邀请动作里固化;持久化层会在同一事务中写 accepted invitation、Agency、owner Host 和 owner membership。
|
||||
agencyName := strings.TrimSpace(command.AgencyName)
|
||||
if strings.TrimSpace(command.CommandID) == "" || command.InviterUserID <= 0 || command.TargetUserID <= 0 || agencyName == "" {
|
||||
return hostdomain.RoleInvitation{}, xerr.New(xerr.InvalidArgument, "command_id, inviter_user_id, target_user_id and agency_name are required")
|
||||
@ -214,16 +216,19 @@ func (s *Service) InviteAgency(ctx context.Context, command InviteAgencyInput) (
|
||||
InviterUserID: command.InviterUserID,
|
||||
TargetUserID: command.TargetUserID,
|
||||
AgencyName: agencyName,
|
||||
AgencyID: s.idGenerator.NewInt64(),
|
||||
MembershipID: s.idGenerator.NewInt64(),
|
||||
EventID: s.nextEventIDRange(3),
|
||||
NowMs: nowMs,
|
||||
})
|
||||
}
|
||||
|
||||
// InviteBD 创建 BD 负责人邀请用户成为 BD 的待处理邀请。
|
||||
// InviteBD 由 BD Leader 直接创建用户的 BD 身份,不再等待目标用户确认。
|
||||
func (s *Service) InviteBD(ctx context.Context, command InviteBDInput) (hostdomain.RoleInvitation, error) {
|
||||
if err := s.requireWriteDependencies(); err != nil {
|
||||
return hostdomain.RoleInvitation{}, err
|
||||
}
|
||||
// 负责人身份、区域、目标是否已有 BD 都交给持久化层在同一事务中校验。
|
||||
// 负责人身份、区域、目标是否已有 BD 都交给持久化层在同一事务中校验,并和 accepted invitation 一起提交。
|
||||
if strings.TrimSpace(command.CommandID) == "" || command.InviterUserID <= 0 || command.TargetUserID <= 0 {
|
||||
return hostdomain.RoleInvitation{}, xerr.New(xerr.InvalidArgument, "command_id, inviter_user_id and target_user_id are required")
|
||||
}
|
||||
@ -234,10 +239,41 @@ func (s *Service) InviteBD(ctx context.Context, command InviteBDInput) (hostdoma
|
||||
InvitationID: s.idGenerator.NewInt64(),
|
||||
InviterUserID: command.InviterUserID,
|
||||
TargetUserID: command.TargetUserID,
|
||||
EventID: s.idGenerator.NewInt64(),
|
||||
NowMs: nowMs,
|
||||
})
|
||||
}
|
||||
|
||||
// ListBDLeaderBDs 返回当前 BD Leader 直属的有效 BD 列表。
|
||||
func (s *Service) ListBDLeaderBDs(ctx context.Context, command ListBDLeaderBDsCommand) ([]hostdomain.BDProfile, error) {
|
||||
if s.repository == nil {
|
||||
return nil, xerr.New(xerr.Unavailable, "host repository is not configured")
|
||||
}
|
||||
if command.LeaderUserID <= 0 {
|
||||
return nil, xerr.New(xerr.InvalidArgument, "leader_user_id is required")
|
||||
}
|
||||
return s.repository.ListBDLeaderBDs(ctx, ListBDLeaderBDsCommand{
|
||||
LeaderUserID: command.LeaderUserID,
|
||||
Status: normalizeStatus(command.Status),
|
||||
PageSize: normalizePageSize(command.PageSize),
|
||||
})
|
||||
}
|
||||
|
||||
// ListBDLeaderAgencies 返回当前 BD Leader 团队下的 Agency 列表。
|
||||
func (s *Service) ListBDLeaderAgencies(ctx context.Context, command ListBDLeaderAgenciesCommand) ([]hostdomain.Agency, error) {
|
||||
if s.repository == nil {
|
||||
return nil, xerr.New(xerr.Unavailable, "host repository is not configured")
|
||||
}
|
||||
if command.LeaderUserID <= 0 {
|
||||
return nil, xerr.New(xerr.InvalidArgument, "leader_user_id is required")
|
||||
}
|
||||
return s.repository.ListBDLeaderAgencies(ctx, ListBDLeaderAgenciesCommand{
|
||||
LeaderUserID: command.LeaderUserID,
|
||||
Status: normalizeStatus(command.Status),
|
||||
PageSize: normalizePageSize(command.PageSize),
|
||||
})
|
||||
}
|
||||
|
||||
// ProcessRoleInvitation 接受、拒绝或取消角色邀请。
|
||||
func (s *Service) ProcessRoleInvitation(ctx context.Context, command ProcessRoleInvitationInput) (hostdomain.ProcessRoleInvitationResult, error) {
|
||||
if err := s.requireWriteDependencies(); err != nil {
|
||||
@ -576,3 +612,13 @@ func normalizeAction(value string) string {
|
||||
func normalizeStatus(value string) string {
|
||||
return strings.ToLower(strings.TrimSpace(value))
|
||||
}
|
||||
|
||||
func normalizePageSize(value int) int {
|
||||
if value <= 0 {
|
||||
return 20
|
||||
}
|
||||
if value > 100 {
|
||||
return 100
|
||||
}
|
||||
return value
|
||||
}
|
||||
|
||||
@ -195,27 +195,23 @@ func TestAcceptAgencyInvitationCreatesOwnerHostAndRejectsActiveHost(t *testing.T
|
||||
if err != nil {
|
||||
t.Fatalf("InviteAgency failed: %v", err)
|
||||
}
|
||||
accepted, err := svc.ProcessRoleInvitation(ctx, hostservice.ProcessRoleInvitationInput{
|
||||
CommandID: "accept-agency-601",
|
||||
ActorUserID: 601,
|
||||
InvitationID: invitation.InvitationID,
|
||||
Action: hostdomain.InvitationActionAccept,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("accept agency invitation failed: %v", err)
|
||||
if invitation.Status != hostdomain.InvitationStatusAccepted {
|
||||
t.Fatalf("agency invitation should be accepted immediately: %+v", invitation)
|
||||
}
|
||||
if accepted.Agency.OwnerUserID != 601 || accepted.HostProfile.CurrentAgencyID != accepted.Agency.AgencyID || accepted.Membership.MembershipType != hostdomain.MembershipTypeOwner {
|
||||
t.Fatalf("owner agency facts mismatch: %+v", accepted)
|
||||
}
|
||||
|
||||
blockedInvitation, err := svc.InviteAgency(ctx, hostservice.InviteAgencyInput{
|
||||
CommandID: "invite-agency-602",
|
||||
InviterUserID: 501,
|
||||
TargetUserID: 602,
|
||||
AgencyName: "Blocked Agency",
|
||||
})
|
||||
summary, err := svc.GetUserRoleSummary(ctx, 601)
|
||||
if err != nil {
|
||||
t.Fatalf("InviteAgency for later active host failed: %v", err)
|
||||
t.Fatalf("GetUserRoleSummary failed: %v", err)
|
||||
}
|
||||
agency, err := svc.GetAgency(ctx, summary.AgencyID)
|
||||
if err != nil {
|
||||
t.Fatalf("GetAgency failed: %v", err)
|
||||
}
|
||||
hostProfile, err := svc.GetHostProfile(ctx, 601)
|
||||
if err != nil {
|
||||
t.Fatalf("GetHostProfile failed: %v", err)
|
||||
}
|
||||
if !summary.IsAgency || !summary.IsHost || agency.OwnerUserID != 601 || hostProfile.CurrentAgencyID != agency.AgencyID {
|
||||
t.Fatalf("owner agency facts mismatch: summary=%+v agency=%+v host=%+v", summary, agency, hostProfile)
|
||||
}
|
||||
repository.PutHostProfile(hostdomain.HostProfile{
|
||||
UserID: 602,
|
||||
@ -223,14 +219,14 @@ func TestAcceptAgencyInvitationCreatesOwnerHostAndRejectsActiveHost(t *testing.T
|
||||
RegionID: 10,
|
||||
Source: "test",
|
||||
})
|
||||
_, err = svc.ProcessRoleInvitation(ctx, hostservice.ProcessRoleInvitationInput{
|
||||
CommandID: "accept-agency-602",
|
||||
ActorUserID: 602,
|
||||
InvitationID: blockedInvitation.InvitationID,
|
||||
Action: hostdomain.InvitationActionAccept,
|
||||
_, err = svc.InviteAgency(ctx, hostservice.InviteAgencyInput{
|
||||
CommandID: "invite-agency-602",
|
||||
InviterUserID: 501,
|
||||
TargetUserID: 602,
|
||||
AgencyName: "Blocked Agency",
|
||||
})
|
||||
if got := xerr.CodeOf(err); got != xerr.Conflict {
|
||||
t.Fatalf("active host must not accept agency invitation: got %s err=%v", got, err)
|
||||
t.Fatalf("active host must not become agency owner: got %s err=%v", got, err)
|
||||
}
|
||||
}
|
||||
|
||||
@ -255,17 +251,15 @@ func TestAcceptBDInvitationDoesNotCreateHostProfile(t *testing.T) {
|
||||
if err != nil {
|
||||
t.Fatalf("InviteBD failed: %v", err)
|
||||
}
|
||||
accepted, err := svc.ProcessRoleInvitation(ctx, hostservice.ProcessRoleInvitationInput{
|
||||
CommandID: "accept-bd-702",
|
||||
ActorUserID: 702,
|
||||
InvitationID: invitation.InvitationID,
|
||||
Action: hostdomain.InvitationActionAccept,
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatalf("accept bd invitation failed: %v", err)
|
||||
if invitation.Status != hostdomain.InvitationStatusAccepted {
|
||||
t.Fatalf("bd invitation should be accepted immediately: %+v", invitation)
|
||||
}
|
||||
if accepted.BDProfile.UserID != 702 || accepted.BDProfile.Role != hostdomain.BDRoleBD || accepted.BDProfile.ParentLeaderUserID != 701 {
|
||||
t.Fatalf("bd profile mismatch: %+v", accepted.BDProfile)
|
||||
bdProfile, err := svc.GetBDProfile(ctx, 702)
|
||||
if err != nil {
|
||||
t.Fatalf("GetBDProfile failed: %v", err)
|
||||
}
|
||||
if bdProfile.UserID != 702 || bdProfile.Role != hostdomain.BDRoleBD || bdProfile.ParentLeaderUserID != 701 {
|
||||
t.Fatalf("bd profile mismatch: %+v", bdProfile)
|
||||
}
|
||||
if _, err := svc.GetHostProfile(ctx, 702); xerr.CodeOf(err) != xerr.NotFound {
|
||||
t.Fatalf("becoming bd must not create host profile: err=%v", err)
|
||||
@ -710,11 +704,11 @@ func TestGetUserRoleSummaryAggregatesRolesAndPendingInvitations(t *testing.T) {
|
||||
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)
|
||||
directBD, err := svc.GetUserRoleSummary(ctx, 932)
|
||||
if err != nil {
|
||||
t.Fatalf("GetUserRoleSummary pending failed: %v", err)
|
||||
t.Fatalf("GetUserRoleSummary direct bd failed: %v", err)
|
||||
}
|
||||
if pending.PendingRoleInvitations != 1 || pending.IsHost || pending.IsBD {
|
||||
t.Fatalf("pending role summary mismatch: %+v", pending)
|
||||
if directBD.PendingRoleInvitations != 0 || directBD.IsHost || !directBD.IsBD || directBD.BDID != 932 {
|
||||
t.Fatalf("direct bd role summary mismatch: %+v", directBD)
|
||||
}
|
||||
}
|
||||
|
||||
@ -69,7 +69,7 @@ func (r *Repository) InviteAgency(ctx context.Context, command hostservice.Invit
|
||||
InvitationID: command.InvitationID,
|
||||
CommandID: command.CommandID,
|
||||
InvitationType: hostdomain.InvitationTypeAgency,
|
||||
Status: hostdomain.InvitationStatusPending,
|
||||
Status: hostdomain.InvitationStatusAccepted,
|
||||
InviterUserID: command.InviterUserID,
|
||||
InviterBDUserID: inviter.UserID,
|
||||
TargetUserID: command.TargetUserID,
|
||||
@ -77,13 +77,42 @@ func (r *Repository) InviteAgency(ctx context.Context, command hostservice.Invit
|
||||
AgencyName: command.AgencyName,
|
||||
ParentBDUserID: inviter.UserID,
|
||||
ParentLeaderUserID: parentLeaderUserID,
|
||||
ProcessedByUserID: command.InviterUserID,
|
||||
ProcessReason: "direct_invite",
|
||||
ProcessedAtMs: command.NowMs,
|
||||
CreatedAtMs: command.NowMs,
|
||||
UpdatedAtMs: command.NowMs,
|
||||
}
|
||||
// 待处理邀请与 command_result 同事务写入,保证重复提交能按 invitation_id 回放。
|
||||
// 新邀请不再等待目标用户确认:accepted invitation、Agency、owner Host 和 owner membership 必须同事务提交。
|
||||
if err := insertRoleInvitation(ctx, tx, invitation); err != nil {
|
||||
return hostdomain.RoleInvitation{}, mapHostDuplicateError(err)
|
||||
}
|
||||
result := hostdomain.ProcessRoleInvitationResult{Invitation: invitation}
|
||||
created, err := r.acceptAgencyInvitation(ctx, tx, hostservice.ProcessRoleInvitationCommand{
|
||||
CommandID: command.CommandID,
|
||||
ActorUserID: command.InviterUserID,
|
||||
InvitationID: command.InvitationID,
|
||||
Action: hostdomain.InvitationActionAccept,
|
||||
Reason: "direct_invite",
|
||||
AgencyID: command.AgencyID,
|
||||
MembershipID: command.MembershipID,
|
||||
EventID: command.EventID,
|
||||
NowMs: command.NowMs,
|
||||
}, &invitation, &result)
|
||||
if err != nil {
|
||||
return hostdomain.RoleInvitation{}, err
|
||||
}
|
||||
if created {
|
||||
if err := insertHostOutbox(ctx, tx, command.EventID, "HostCreated", "host_profile", invitation.TargetUserID, command.NowMs); err != nil {
|
||||
return hostdomain.RoleInvitation{}, err
|
||||
}
|
||||
if err := insertHostOutbox(ctx, tx, command.EventID+1, "AgencyCreated", "agency", command.AgencyID, command.NowMs); err != nil {
|
||||
return hostdomain.RoleInvitation{}, err
|
||||
}
|
||||
if err := insertHostOutbox(ctx, tx, command.EventID+2, "AgencyMembershipChanged", "agency_membership", command.MembershipID, command.NowMs); err != nil {
|
||||
return hostdomain.RoleInvitation{}, err
|
||||
}
|
||||
}
|
||||
if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{
|
||||
CommandID: command.CommandID,
|
||||
CommandType: hostdomain.CommandTypeInviteAgency,
|
||||
@ -145,19 +174,37 @@ func (r *Repository) InviteBD(ctx context.Context, command hostservice.InviteBDC
|
||||
InvitationID: command.InvitationID,
|
||||
CommandID: command.CommandID,
|
||||
InvitationType: hostdomain.InvitationTypeBD,
|
||||
Status: hostdomain.InvitationStatusPending,
|
||||
Status: hostdomain.InvitationStatusAccepted,
|
||||
InviterUserID: command.InviterUserID,
|
||||
InviterBDUserID: inviter.UserID,
|
||||
TargetUserID: command.TargetUserID,
|
||||
RegionID: inviter.RegionID,
|
||||
ParentLeaderUserID: inviter.UserID,
|
||||
ProcessedByUserID: command.InviterUserID,
|
||||
ProcessReason: "direct_invite",
|
||||
ProcessedAtMs: command.NowMs,
|
||||
CreatedAtMs: command.NowMs,
|
||||
UpdatedAtMs: command.NowMs,
|
||||
}
|
||||
// 这里只创建待处理邀请;真正的 BD 身份行在目标用户接受时再落库。
|
||||
// 新 BD 邀请直接创建 BD 身份;accepted invitation 只作为审计事实和幂等回放结果。
|
||||
if err := insertRoleInvitation(ctx, tx, invitation); err != nil {
|
||||
return hostdomain.RoleInvitation{}, mapHostDuplicateError(err)
|
||||
}
|
||||
result := hostdomain.ProcessRoleInvitationResult{Invitation: invitation}
|
||||
if err := r.acceptBDInvitation(ctx, tx, hostservice.ProcessRoleInvitationCommand{
|
||||
CommandID: command.CommandID,
|
||||
ActorUserID: command.InviterUserID,
|
||||
InvitationID: command.InvitationID,
|
||||
Action: hostdomain.InvitationActionAccept,
|
||||
Reason: "direct_invite",
|
||||
EventID: command.EventID,
|
||||
NowMs: command.NowMs,
|
||||
}, &invitation, &result); err != nil {
|
||||
return hostdomain.RoleInvitation{}, err
|
||||
}
|
||||
if err := insertHostOutbox(ctx, tx, command.EventID, "BDCreated", "bd_profile", invitation.TargetUserID, command.NowMs); err != nil {
|
||||
return hostdomain.RoleInvitation{}, err
|
||||
}
|
||||
if err := insertCommandResult(ctx, tx, hostdomain.CommandResult{
|
||||
CommandID: command.CommandID,
|
||||
CommandType: hostdomain.CommandTypeInviteBD,
|
||||
|
||||
@ -281,6 +281,50 @@ func (r *Repository) HasActiveAgencyOwner(ctx context.Context, userID int64) (in
|
||||
return agency.AgencyID, true, nil
|
||||
}
|
||||
|
||||
// ListBDLeaderBDs 读取 BD Leader 直属 BD;调用前先校验 leader 当前仍有效,避免停用后继续读取团队数据。
|
||||
func (r *Repository) ListBDLeaderBDs(ctx context.Context, command hostservice.ListBDLeaderBDsCommand) ([]hostdomain.BDProfile, error) {
|
||||
leader, err := queryBDProfile(ctx, r.db, "WHERE user_id = ?", command.LeaderUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if leader.Status != hostdomain.BDStatusActive || leader.Role != hostdomain.BDRoleLeader {
|
||||
return nil, xerr.New(xerr.PermissionDenied, "leader is not active")
|
||||
}
|
||||
status := command.Status
|
||||
if status == "" {
|
||||
status = hostdomain.BDStatusActive
|
||||
}
|
||||
clause, args := appScopedClause(ctx, "WHERE role = ? AND parent_leader_user_id = ? AND status = ? ORDER BY created_at_ms DESC, user_id DESC LIMIT ?", hostdomain.BDRoleBD, command.LeaderUserID, status, command.PageSize)
|
||||
return queryBDProfiles(ctx, r.db, clause, args...)
|
||||
}
|
||||
|
||||
// ListBDLeaderAgencies 读取 BD Leader 团队 Agency;包含直属 leader 自己创建的 Agency 和下属 BD 创建的 Agency。
|
||||
func (r *Repository) ListBDLeaderAgencies(ctx context.Context, command hostservice.ListBDLeaderAgenciesCommand) ([]hostdomain.Agency, error) {
|
||||
leader, err := queryBDProfile(ctx, r.db, "WHERE user_id = ?", command.LeaderUserID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if leader.Status != hostdomain.BDStatusActive || leader.Role != hostdomain.BDRoleLeader {
|
||||
return nil, xerr.New(xerr.PermissionDenied, "leader is not active")
|
||||
}
|
||||
status := command.Status
|
||||
if status == "" {
|
||||
status = hostdomain.AgencyStatusActive
|
||||
}
|
||||
clause, args := appScopedClause(ctx, `WHERE status = ? AND (
|
||||
parent_bd_user_id = ?
|
||||
OR parent_bd_user_id IN (
|
||||
SELECT user_id
|
||||
FROM bd_profiles
|
||||
WHERE app_code = agencies.app_code
|
||||
AND role = ?
|
||||
AND parent_leader_user_id = ?
|
||||
AND status = ?
|
||||
)
|
||||
) ORDER BY created_at_ms DESC, agency_id DESC LIMIT ?`, status, command.LeaderUserID, hostdomain.BDRoleBD, command.LeaderUserID, hostdomain.BDStatusActive, command.PageSize)
|
||||
return queryAgencies(ctx, r.db, clause, args...)
|
||||
}
|
||||
|
||||
// ListAgencyMembers 读取 Agency 成员关系列表。
|
||||
func (r *Repository) ListAgencyMembers(ctx context.Context, agencyID int64, status string) ([]hostdomain.AgencyMembership, error) {
|
||||
query := fmt.Sprintf(`SELECT %s FROM agency_memberships WHERE app_code = ? AND agency_id = ?`, agencyMembershipColumns)
|
||||
@ -373,6 +417,24 @@ func queryAgency(ctx context.Context, q sqlQueryer, clause string, args ...any)
|
||||
return agency, err
|
||||
}
|
||||
|
||||
func queryAgencies(ctx context.Context, q sqlQueryer, clause string, args ...any) ([]hostdomain.Agency, error) {
|
||||
rows, err := q.QueryContext(ctx, fmt.Sprintf(`SELECT %s FROM agencies %s`, agencyColumns, clause), args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
agencies := make([]hostdomain.Agency, 0)
|
||||
for rows.Next() {
|
||||
agency, err := scanAgency(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
agencies = append(agencies, agency)
|
||||
}
|
||||
return agencies, rows.Err()
|
||||
}
|
||||
|
||||
func queryActiveAgencyByOwner(ctx context.Context, q sqlQueryer, ownerUserID int64, lock bool) (hostdomain.Agency, bool, error) {
|
||||
clause := "WHERE owner_user_id = ? AND status = ?"
|
||||
if lock {
|
||||
@ -461,6 +523,24 @@ func queryBDProfile(ctx context.Context, q sqlQueryer, clause string, args ...an
|
||||
return profile, err
|
||||
}
|
||||
|
||||
func queryBDProfiles(ctx context.Context, q sqlQueryer, clause string, args ...any) ([]hostdomain.BDProfile, error) {
|
||||
rows, err := q.QueryContext(ctx, fmt.Sprintf(`SELECT %s FROM bd_profiles %s`, bdProfileColumns, clause), args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
profiles := make([]hostdomain.BDProfile, 0)
|
||||
for rows.Next() {
|
||||
profile, err := scanBDProfile(rows)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
profiles = append(profiles, profile)
|
||||
}
|
||||
return profiles, rows.Err()
|
||||
}
|
||||
|
||||
func queryBDProfileMaybe(ctx context.Context, q sqlQueryer, userID int64, lock bool) (hostdomain.BDProfile, bool, error) {
|
||||
clause := "WHERE user_id = ? AND status = ?"
|
||||
if lock {
|
||||
|
||||
@ -98,7 +98,7 @@ func (s *Server) KickAgencyHost(ctx context.Context, req *userv1.KickAgencyHostR
|
||||
}, nil
|
||||
}
|
||||
|
||||
// InviteAgency 创建用户成为 Agency 拥有者的邀请。
|
||||
// InviteAgency 直接创建用户成为 Agency owner 的关系事实,不再等待目标用户确认。
|
||||
func (s *Server) InviteAgency(ctx context.Context, req *userv1.InviteAgencyRequest) (*userv1.InviteAgencyResponse, error) {
|
||||
if s.hostSvc == nil {
|
||||
return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
|
||||
@ -118,7 +118,7 @@ func (s *Server) InviteAgency(ctx context.Context, req *userv1.InviteAgencyReque
|
||||
return &userv1.InviteAgencyResponse{Invitation: toProtoRoleInvitation(invitation)}, nil
|
||||
}
|
||||
|
||||
// InviteBD 创建用户成为 BD 的邀请,只有 BD 负责人可调用。
|
||||
// InviteBD 直接创建用户成为 BD 的关系事实,只有 BD 负责人可调用。
|
||||
func (s *Server) InviteBD(ctx context.Context, req *userv1.InviteBDRequest) (*userv1.InviteBDResponse, error) {
|
||||
if s.hostSvc == nil {
|
||||
return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
|
||||
@ -137,6 +137,50 @@ func (s *Server) InviteBD(ctx context.Context, req *userv1.InviteBDRequest) (*us
|
||||
return &userv1.InviteBDResponse{Invitation: toProtoRoleInvitation(invitation)}, nil
|
||||
}
|
||||
|
||||
// ListBDLeaderBDs 返回当前 BD Leader 直属 BD 列表。
|
||||
func (s *Server) ListBDLeaderBDs(ctx context.Context, req *userv1.ListBDLeaderBDsRequest) (*userv1.ListBDLeaderBDsResponse, error) {
|
||||
if s.hostSvc == nil {
|
||||
return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
|
||||
}
|
||||
ctx = contextWithApp(ctx, req.GetMeta())
|
||||
profiles, err := s.hostSvc.ListBDLeaderBDs(ctx, hostservice.ListBDLeaderBDsCommand{
|
||||
LeaderUserID: req.GetLeaderUserId(),
|
||||
Status: req.GetStatus(),
|
||||
PageSize: int(req.GetPageSize()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerr.ToGRPCError(err)
|
||||
}
|
||||
|
||||
resp := &userv1.ListBDLeaderBDsResponse{BdProfiles: make([]*userv1.BDProfile, 0, len(profiles))}
|
||||
for _, profile := range profiles {
|
||||
resp.BdProfiles = append(resp.BdProfiles, toProtoBDProfile(profile))
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ListBDLeaderAgencies 返回当前 BD Leader 团队下的 Agency 列表。
|
||||
func (s *Server) ListBDLeaderAgencies(ctx context.Context, req *userv1.ListBDLeaderAgenciesRequest) (*userv1.ListBDLeaderAgenciesResponse, error) {
|
||||
if s.hostSvc == nil {
|
||||
return nil, xerr.ToGRPCError(xerr.New(xerr.Unavailable, "host service is not configured"))
|
||||
}
|
||||
ctx = contextWithApp(ctx, req.GetMeta())
|
||||
agencies, err := s.hostSvc.ListBDLeaderAgencies(ctx, hostservice.ListBDLeaderAgenciesCommand{
|
||||
LeaderUserID: req.GetLeaderUserId(),
|
||||
Status: req.GetStatus(),
|
||||
PageSize: int(req.GetPageSize()),
|
||||
})
|
||||
if err != nil {
|
||||
return nil, xerr.ToGRPCError(err)
|
||||
}
|
||||
|
||||
resp := &userv1.ListBDLeaderAgenciesResponse{Agencies: make([]*userv1.Agency, 0, len(agencies))}
|
||||
for _, agency := range agencies {
|
||||
resp.Agencies = append(resp.Agencies, toProtoAgency(agency))
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// ProcessRoleInvitation 处理 Agency 或 BD 邀请。
|
||||
func (s *Server) ProcessRoleInvitation(ctx context.Context, req *userv1.ProcessRoleInvitationRequest) (*userv1.ProcessRoleInvitationResponse, error) {
|
||||
if s.hostSvc == nil {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user