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 邀请人搜索和绑定能力的依赖。
|
// UserInviteClient 抽象 H5 邀请页对 user-service 邀请人搜索和绑定能力的依赖。
|
||||||
type UserInviteClient interface {
|
type UserInviteClient interface {
|
||||||
|
GetInviteAttribution(ctx context.Context, req *userv1.GetInviteAttributionRequest) (*userv1.GetInviteAttributionResponse, error)
|
||||||
SearchInviteReferrer(ctx context.Context, req *userv1.SearchInviteReferrerRequest) (*userv1.SearchInviteReferrerResponse, error)
|
SearchInviteReferrer(ctx context.Context, req *userv1.SearchInviteReferrerRequest) (*userv1.SearchInviteReferrerResponse, error)
|
||||||
BindInviteReferrer(ctx context.Context, req *userv1.BindInviteReferrerRequest) (*userv1.BindInviteReferrerResponse, 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)
|
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) {
|
func (c *grpcUserInviteClient) BindInviteReferrer(ctx context.Context, req *userv1.BindInviteReferrerRequest) (*userv1.BindInviteReferrerResponse, error) {
|
||||||
return c.client.BindInviteReferrer(ctx, req)
|
return c.client.BindInviteReferrer(ctx, req)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -73,6 +73,7 @@ type UserHandlers struct {
|
|||||||
BatchRoomDisplayProfiles http.HandlerFunc
|
BatchRoomDisplayProfiles http.HandlerFunc
|
||||||
GetMyOverview http.HandlerFunc
|
GetMyOverview http.HandlerFunc
|
||||||
GetMyInviteOverview http.HandlerFunc
|
GetMyInviteOverview http.HandlerFunc
|
||||||
|
GetMyInviteReferrer http.HandlerFunc
|
||||||
SearchInviteReferrer http.HandlerFunc
|
SearchInviteReferrer http.HandlerFunc
|
||||||
BindInviteReferrer http.HandlerFunc
|
BindInviteReferrer http.HandlerFunc
|
||||||
GetMyGiftWall 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/room-display-profiles:batch", http.MethodGet, h.BatchRoomDisplayProfiles)
|
||||||
r.profile("/users/me/overview", "", h.GetMyOverview)
|
r.profile("/users/me/overview", "", h.GetMyOverview)
|
||||||
r.profile("/users/me/invite-overview", http.MethodGet, h.GetMyInviteOverview)
|
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/search", http.MethodGet, h.SearchInviteReferrer)
|
||||||
r.profile("/users/me/invite-referrer/bind", http.MethodPost, h.BindInviteReferrer)
|
r.profile("/users/me/invite-referrer/bind", http.MethodPost, h.BindInviteReferrer)
|
||||||
r.profile("/users/me/gift-wall", http.MethodGet, h.GetMyGiftWall)
|
r.profile("/users/me/gift-wall", http.MethodGet, h.GetMyGiftWall)
|
||||||
|
|||||||
@ -58,6 +58,7 @@ func (h *Handler) UserHandlers() httproutes.UserHandlers {
|
|||||||
ResolveDisplayUserID: h.resolveDisplayUserID,
|
ResolveDisplayUserID: h.resolveDisplayUserID,
|
||||||
GetMyOverview: h.getMyOverview,
|
GetMyOverview: h.getMyOverview,
|
||||||
GetMyInviteOverview: h.getMyInviteOverview,
|
GetMyInviteOverview: h.getMyInviteOverview,
|
||||||
|
GetMyInviteReferrer: h.getMyInviteReferrer,
|
||||||
SearchInviteReferrer: h.searchInviteReferrer,
|
SearchInviteReferrer: h.searchInviteReferrer,
|
||||||
BindInviteReferrer: h.bindInviteReferrer,
|
BindInviteReferrer: h.bindInviteReferrer,
|
||||||
GetMyIdentity: h.getMyIdentity,
|
GetMyIdentity: h.getMyIdentity,
|
||||||
|
|||||||
@ -14,6 +14,55 @@ type bindInviteReferrerData struct {
|
|||||||
Inviter userProfileData `json:"inviter"`
|
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 只按邀请码暴露当前用户同国家、可绑定的邀请人资料。
|
// searchInviteReferrer 只按邀请码暴露当前用户同国家、可绑定的邀请人资料。
|
||||||
func (h *Handler) searchInviteReferrer(writer http.ResponseWriter, request *http.Request) {
|
func (h *Handler) searchInviteReferrer(writer http.ResponseWriter, request *http.Request) {
|
||||||
if h.userInviteClient == nil {
|
if h.userInviteClient == nil {
|
||||||
|
|||||||
@ -13,8 +13,18 @@ import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
type fakeInviteReferrerClient struct {
|
type fakeInviteReferrerClient struct {
|
||||||
lastSearch *userv1.SearchInviteReferrerRequest
|
attribution *userv1.InviteAttribution
|
||||||
lastBind *userv1.BindInviteReferrerRequest
|
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) {
|
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
|
}, 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) {
|
func TestInviteReferrerSearchUsesInviteCode(t *testing.T) {
|
||||||
client := &fakeInviteReferrerClient{}
|
client := &fakeInviteReferrerClient{}
|
||||||
handler := New(Config{UserInviteClient: client})
|
handler := New(Config{UserInviteClient: client})
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user