feat: expose current invite referrer
This commit is contained in:
parent
4b02562864
commit
cb8cfd2167
@ -52,6 +52,7 @@ type UserProfileClient interface {
|
||||
|
||||
// UserInviteClient 抽象 H5 邀请页对 user-service 邀请人搜索和绑定能力的依赖。
|
||||
type UserInviteClient interface {
|
||||
GetInviteAttribution(ctx context.Context, req *userv1.GetInviteAttributionRequest) (*userv1.GetInviteAttributionResponse, error)
|
||||
SearchInviteReferrer(ctx context.Context, req *userv1.SearchInviteReferrerRequest) (*userv1.SearchInviteReferrerResponse, error)
|
||||
BindInviteReferrer(ctx context.Context, req *userv1.BindInviteReferrerRequest) (*userv1.BindInviteReferrerResponse, error)
|
||||
}
|
||||
@ -392,6 +393,10 @@ func (c *grpcUserInviteClient) SearchInviteReferrer(ctx context.Context, req *us
|
||||
return c.client.SearchInviteReferrer(ctx, req)
|
||||
}
|
||||
|
||||
func (c *grpcUserInviteClient) GetInviteAttribution(ctx context.Context, req *userv1.GetInviteAttributionRequest) (*userv1.GetInviteAttributionResponse, error) {
|
||||
return c.client.GetInviteAttribution(ctx, req)
|
||||
}
|
||||
|
||||
func (c *grpcUserInviteClient) BindInviteReferrer(ctx context.Context, req *userv1.BindInviteReferrerRequest) (*userv1.BindInviteReferrerResponse, error) {
|
||||
return c.client.BindInviteReferrer(ctx, req)
|
||||
}
|
||||
|
||||
@ -73,6 +73,7 @@ type UserHandlers struct {
|
||||
BatchRoomDisplayProfiles http.HandlerFunc
|
||||
GetMyOverview http.HandlerFunc
|
||||
GetMyInviteOverview http.HandlerFunc
|
||||
GetMyInviteReferrer http.HandlerFunc
|
||||
SearchInviteReferrer http.HandlerFunc
|
||||
BindInviteReferrer http.HandlerFunc
|
||||
GetMyGiftWall http.HandlerFunc
|
||||
@ -422,6 +423,7 @@ func (r routes) registerUserRoutes() {
|
||||
r.profile("/users/room-display-profiles:batch", http.MethodGet, h.BatchRoomDisplayProfiles)
|
||||
r.profile("/users/me/overview", "", h.GetMyOverview)
|
||||
r.profile("/users/me/invite-overview", http.MethodGet, h.GetMyInviteOverview)
|
||||
r.profile("/users/me/invite-referrer", http.MethodGet, h.GetMyInviteReferrer)
|
||||
r.profile("/users/me/invite-referrer/search", http.MethodGet, h.SearchInviteReferrer)
|
||||
r.profile("/users/me/invite-referrer/bind", http.MethodPost, h.BindInviteReferrer)
|
||||
r.profile("/users/me/gift-wall", http.MethodGet, h.GetMyGiftWall)
|
||||
|
||||
@ -58,6 +58,7 @@ func (h *Handler) UserHandlers() httproutes.UserHandlers {
|
||||
ResolveDisplayUserID: h.resolveDisplayUserID,
|
||||
GetMyOverview: h.getMyOverview,
|
||||
GetMyInviteOverview: h.getMyInviteOverview,
|
||||
GetMyInviteReferrer: h.getMyInviteReferrer,
|
||||
SearchInviteReferrer: h.searchInviteReferrer,
|
||||
BindInviteReferrer: h.bindInviteReferrer,
|
||||
GetMyIdentity: h.getMyIdentity,
|
||||
|
||||
@ -14,6 +14,55 @@ type bindInviteReferrerData struct {
|
||||
Inviter userProfileData `json:"inviter"`
|
||||
}
|
||||
|
||||
type currentInviteReferrerData struct {
|
||||
Invite *inviteBindingData `json:"invite,omitempty"`
|
||||
Inviter *userProfileData `json:"inviter,omitempty"`
|
||||
}
|
||||
|
||||
// getMyInviteReferrer 返回当前用户已经绑定的邀请人;未绑定时返回空对象,让 H5 保持搜索入口。
|
||||
func (h *Handler) getMyInviteReferrer(writer http.ResponseWriter, request *http.Request) {
|
||||
if h.userInviteClient == nil {
|
||||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||||
return
|
||||
}
|
||||
currentUserID := auth.UserIDFromContext(request.Context())
|
||||
|
||||
// 绑定关系以 user-service 的归因表为准,前端不能根据最近一次搜索或本地缓存推断是否已经绑定。
|
||||
attributionResp, err := h.userInviteClient.GetInviteAttribution(request.Context(), &userv1.GetInviteAttributionRequest{
|
||||
Meta: httpkit.UserMeta(request, ""),
|
||||
InvitedUserId: currentUserID,
|
||||
})
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
attribution := attributionResp.GetAttribution()
|
||||
if attribution == nil || !attribution.GetFound() || strings.TrimSpace(attribution.GetInviteCode()) == "" {
|
||||
httpkit.WriteOK(writer, request, currentInviteReferrerData{})
|
||||
return
|
||||
}
|
||||
|
||||
// 归因表只保存绑定事实和邀请码;展示资料继续走同一个邀请码搜索校验,保证国家、自邀等展示过滤规则和绑定前预览一致。
|
||||
searchResp, err := h.userInviteClient.SearchInviteReferrer(request.Context(), &userv1.SearchInviteReferrerRequest{
|
||||
Meta: httpkit.UserMeta(request, ""),
|
||||
UserId: currentUserID,
|
||||
InviteCode: attribution.GetInviteCode(),
|
||||
})
|
||||
if err != nil {
|
||||
httpkit.WriteRPCError(writer, request, err)
|
||||
return
|
||||
}
|
||||
inviter := profileData(searchResp.GetInviter(), 0)
|
||||
httpkit.WriteOK(writer, request, currentInviteReferrerData{
|
||||
Invite: &inviteBindingData{
|
||||
Bound: true,
|
||||
InviteCode: attribution.GetInviteCode(),
|
||||
InviterUserID: userIDString(attribution.GetInviterUserId()),
|
||||
},
|
||||
Inviter: &inviter,
|
||||
})
|
||||
}
|
||||
|
||||
// searchInviteReferrer 只按邀请码暴露当前用户同国家、可绑定的邀请人资料。
|
||||
func (h *Handler) searchInviteReferrer(writer http.ResponseWriter, request *http.Request) {
|
||||
if h.userInviteClient == nil {
|
||||
|
||||
@ -13,8 +13,18 @@ import (
|
||||
)
|
||||
|
||||
type fakeInviteReferrerClient struct {
|
||||
lastSearch *userv1.SearchInviteReferrerRequest
|
||||
lastBind *userv1.BindInviteReferrerRequest
|
||||
attribution *userv1.InviteAttribution
|
||||
lastGet *userv1.GetInviteAttributionRequest
|
||||
lastSearch *userv1.SearchInviteReferrerRequest
|
||||
lastBind *userv1.BindInviteReferrerRequest
|
||||
}
|
||||
|
||||
func (f *fakeInviteReferrerClient) GetInviteAttribution(_ context.Context, req *userv1.GetInviteAttributionRequest) (*userv1.GetInviteAttributionResponse, error) {
|
||||
f.lastGet = req
|
||||
if f.attribution == nil {
|
||||
return &userv1.GetInviteAttributionResponse{Attribution: &userv1.InviteAttribution{}}, nil
|
||||
}
|
||||
return &userv1.GetInviteAttributionResponse{Attribution: f.attribution}, nil
|
||||
}
|
||||
|
||||
func (f *fakeInviteReferrerClient) SearchInviteReferrer(_ context.Context, req *userv1.SearchInviteReferrerRequest) (*userv1.SearchInviteReferrerResponse, error) {
|
||||
@ -46,6 +56,57 @@ func (f *fakeInviteReferrerClient) BindInviteReferrer(_ context.Context, req *us
|
||||
}, nil
|
||||
}
|
||||
|
||||
func TestInviteReferrerGetReturnsBoundInviter(t *testing.T) {
|
||||
client := &fakeInviteReferrerClient{
|
||||
attribution: &userv1.InviteAttribution{
|
||||
Found: true,
|
||||
InvitedUserId: 10001,
|
||||
InviterUserId: 9001,
|
||||
InviteCode: "A1B2C3",
|
||||
},
|
||||
}
|
||||
handler := New(Config{UserInviteClient: client})
|
||||
request := newInviteReferrerRequest(http.MethodGet, "/api/v1/users/me/invite-referrer", "")
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
handler.getMyInviteReferrer(recorder, request)
|
||||
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("get status = %d body=%s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
if client.lastGet == nil || client.lastGet.GetInvitedUserId() != 10001 {
|
||||
t.Fatalf("get request mismatch: %+v", client.lastGet)
|
||||
}
|
||||
if client.lastSearch == nil || client.lastSearch.GetUserId() != 10001 || client.lastSearch.GetInviteCode() != "A1B2C3" {
|
||||
t.Fatalf("search request mismatch: %+v", client.lastSearch)
|
||||
}
|
||||
if !strings.Contains(recorder.Body.String(), `"invite_code":"A1B2C3"`) || !strings.Contains(recorder.Body.String(), `"username":"Alice"`) {
|
||||
t.Fatalf("bound inviter response mismatch: %s", recorder.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestInviteReferrerGetReturnsEmptyWhenUnbound(t *testing.T) {
|
||||
client := &fakeInviteReferrerClient{}
|
||||
handler := New(Config{UserInviteClient: client})
|
||||
request := newInviteReferrerRequest(http.MethodGet, "/api/v1/users/me/invite-referrer", "")
|
||||
recorder := httptest.NewRecorder()
|
||||
|
||||
handler.getMyInviteReferrer(recorder, request)
|
||||
|
||||
if recorder.Code != http.StatusOK {
|
||||
t.Fatalf("get status = %d body=%s", recorder.Code, recorder.Body.String())
|
||||
}
|
||||
if client.lastGet == nil || client.lastGet.GetInvitedUserId() != 10001 {
|
||||
t.Fatalf("get request mismatch: %+v", client.lastGet)
|
||||
}
|
||||
if client.lastSearch != nil {
|
||||
t.Fatalf("unbound relation must not search inviter: %+v", client.lastSearch)
|
||||
}
|
||||
if strings.Contains(recorder.Body.String(), `"inviter"`) || strings.Contains(recorder.Body.String(), `"invite"`) {
|
||||
t.Fatalf("unbound response should not include inviter data: %s", recorder.Body.String())
|
||||
}
|
||||
}
|
||||
|
||||
func TestInviteReferrerSearchUsesInviteCode(t *testing.T) {
|
||||
client := &fakeInviteReferrerClient{}
|
||||
handler := New(Config{UserInviteClient: client})
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user