337 lines
12 KiB
Go
337 lines
12 KiB
Go
package http
|
||
|
||
import (
|
||
"hyapp/services/gateway-service/internal/transport/http/httpkit"
|
||
"net/http"
|
||
"strings"
|
||
|
||
userv1 "hyapp.local/api/proto/user/v1"
|
||
"hyapp/services/gateway-service/internal/auth"
|
||
)
|
||
|
||
type profileVisitRecordData struct {
|
||
VisitorUserID string `json:"visitor_user_id"`
|
||
TargetUserID string `json:"target_user_id"`
|
||
VisitCount int64 `json:"visit_count"`
|
||
LastVisitedAtMS int64 `json:"last_visited_at_ms"`
|
||
}
|
||
|
||
type followRecordData struct {
|
||
FollowerUserID string `json:"follower_user_id"`
|
||
FolloweeUserID string `json:"followee_user_id"`
|
||
FollowedAtMS int64 `json:"followed_at_ms"`
|
||
}
|
||
|
||
type friendRecordData struct {
|
||
UserID string `json:"user_id"`
|
||
FriendUserID string `json:"friend_user_id"`
|
||
FriendedAtMS int64 `json:"friended_at_ms"`
|
||
}
|
||
|
||
type friendApplicationData struct {
|
||
RequesterUserID string `json:"requester_user_id"`
|
||
TargetUserID string `json:"target_user_id"`
|
||
Status string `json:"status"`
|
||
CreatedAtMS int64 `json:"created_at_ms"`
|
||
UpdatedAtMS int64 `json:"updated_at_ms"`
|
||
}
|
||
|
||
// listMyProfileVisitors 返回访问我的主页的人;这是关系 read model,不实时扫用户行为日志。
|
||
func (h *Handler) listMyProfileVisitors(writer http.ResponseWriter, request *http.Request) {
|
||
if h.userSocialClient == nil {
|
||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||
return
|
||
}
|
||
page, pageSize, ok := socialPage(request)
|
||
if !ok {
|
||
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
||
return
|
||
}
|
||
resp, err := h.userSocialClient.ListProfileVisitors(request.Context(), &userv1.ListProfileVisitorsRequest{
|
||
Meta: authRequestMeta(request, ""),
|
||
UserId: auth.UserIDFromContext(request.Context()),
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
records := make([]profileVisitRecordData, 0, len(resp.GetRecords()))
|
||
for _, record := range resp.GetRecords() {
|
||
records = append(records, profileVisitData(record))
|
||
}
|
||
httpkit.WriteOK(writer, request, map[string]any{"records": records, "total": resp.GetTotal()})
|
||
}
|
||
|
||
// listMyFollowing 返回我关注的用户列表;关注我的列表后续单独建接口,避免语义混淆。
|
||
func (h *Handler) listMyFollowing(writer http.ResponseWriter, request *http.Request) {
|
||
if h.userSocialClient == nil {
|
||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||
return
|
||
}
|
||
page, pageSize, ok := socialPage(request)
|
||
if !ok {
|
||
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
||
return
|
||
}
|
||
resp, err := h.userSocialClient.ListFollowing(request.Context(), &userv1.ListFollowingRequest{
|
||
Meta: authRequestMeta(request, ""),
|
||
UserId: auth.UserIDFromContext(request.Context()),
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
records := make([]followRecordData, 0, len(resp.GetRecords()))
|
||
for _, record := range resp.GetRecords() {
|
||
records = append(records, followData(record))
|
||
}
|
||
httpkit.WriteOK(writer, request, map[string]any{"records": records, "total": resp.GetTotal()})
|
||
}
|
||
|
||
// listMyFriends 返回双向好友列表。
|
||
func (h *Handler) listMyFriends(writer http.ResponseWriter, request *http.Request) {
|
||
if h.userSocialClient == nil {
|
||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||
return
|
||
}
|
||
page, pageSize, ok := socialPage(request)
|
||
if !ok {
|
||
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
||
return
|
||
}
|
||
resp, err := h.userSocialClient.ListFriends(request.Context(), &userv1.ListFriendsRequest{
|
||
Meta: authRequestMeta(request, ""),
|
||
UserId: auth.UserIDFromContext(request.Context()),
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
records := make([]friendRecordData, 0, len(resp.GetRecords()))
|
||
for _, record := range resp.GetRecords() {
|
||
records = append(records, friendData(record))
|
||
}
|
||
httpkit.WriteOK(writer, request, map[string]any{"records": records, "total": resp.GetTotal()})
|
||
}
|
||
|
||
// listMyFriendApplications 返回待处理申请;direction=incoming/outgoing。
|
||
func (h *Handler) listMyFriendApplications(writer http.ResponseWriter, request *http.Request) {
|
||
if h.userSocialClient == nil {
|
||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||
return
|
||
}
|
||
page, pageSize, ok := socialPage(request)
|
||
if !ok {
|
||
httpkit.WriteError(writer, request, http.StatusBadRequest, httpkit.CodeInvalidArgument, "invalid argument")
|
||
return
|
||
}
|
||
resp, err := h.userSocialClient.ListFriendApplications(request.Context(), &userv1.ListFriendApplicationsRequest{
|
||
Meta: authRequestMeta(request, ""),
|
||
UserId: auth.UserIDFromContext(request.Context()),
|
||
Direction: strings.TrimSpace(request.URL.Query().Get("direction")),
|
||
Page: page,
|
||
PageSize: pageSize,
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
applications := make([]friendApplicationData, 0, len(resp.GetApplications()))
|
||
for _, application := range resp.GetApplications() {
|
||
applications = append(applications, friendApplicationDataFromProto(application))
|
||
}
|
||
httpkit.WriteOK(writer, request, map[string]any{"applications": applications, "total": resp.GetTotal()})
|
||
}
|
||
|
||
// userSocialAction 处理 /users/{user_id}/visit|follow|friend-requests|friend-requests/accept|friend。
|
||
func (h *Handler) userSocialAction(writer http.ResponseWriter, request *http.Request) {
|
||
if h.userSocialClient == nil {
|
||
httpkit.WriteError(writer, request, http.StatusBadGateway, httpkit.CodeUpstreamError, "upstream service error")
|
||
return
|
||
}
|
||
targetUserID, ok := httpkit.PositiveInt64PathValue(request, "user_id")
|
||
if !ok {
|
||
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
|
||
return
|
||
}
|
||
action := strings.Trim(request.PathValue("action"), "/")
|
||
|
||
switch action {
|
||
case "visit":
|
||
h.recordProfileVisit(writer, request, targetUserID)
|
||
case "follow":
|
||
h.handleFollowAction(writer, request, targetUserID)
|
||
case "friend-requests":
|
||
h.applyFriend(writer, request, targetUserID)
|
||
case "friend-requests/accept":
|
||
h.acceptFriendApplication(writer, request, targetUserID)
|
||
case "friend":
|
||
h.deleteFriend(writer, request, targetUserID)
|
||
default:
|
||
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
|
||
}
|
||
}
|
||
|
||
func (h *Handler) recordProfileVisit(writer http.ResponseWriter, request *http.Request, targetUserID int64) {
|
||
if request.Method != http.MethodPost {
|
||
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
|
||
return
|
||
}
|
||
resp, err := h.userSocialClient.RecordProfileVisit(request.Context(), &userv1.RecordProfileVisitRequest{
|
||
Meta: authRequestMeta(request, ""),
|
||
VisitorUserId: auth.UserIDFromContext(request.Context()),
|
||
TargetUserId: targetUserID,
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
httpkit.WriteOK(writer, request, map[string]any{"recorded": resp.GetRecorded(), "target_stats": overviewStatsFromProto(resp.GetTargetStats())})
|
||
}
|
||
|
||
func (h *Handler) handleFollowAction(writer http.ResponseWriter, request *http.Request, followeeUserID int64) {
|
||
userID := auth.UserIDFromContext(request.Context())
|
||
switch request.Method {
|
||
case http.MethodPost:
|
||
resp, err := h.userSocialClient.FollowUser(request.Context(), &userv1.FollowUserRequest{
|
||
Meta: authRequestMeta(request, ""),
|
||
FollowerUserId: userID,
|
||
FolloweeUserId: followeeUserID,
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
httpkit.WriteOK(writer, request, map[string]any{"following": resp.GetFollowing(), "follower_stats": overviewStatsFromProto(resp.GetFollowerStats())})
|
||
case http.MethodDelete:
|
||
resp, err := h.userSocialClient.UnfollowUser(request.Context(), &userv1.UnfollowUserRequest{
|
||
Meta: authRequestMeta(request, ""),
|
||
FollowerUserId: userID,
|
||
FolloweeUserId: followeeUserID,
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
httpkit.WriteOK(writer, request, map[string]any{"following": resp.GetFollowing(), "follower_stats": overviewStatsFromProto(resp.GetFollowerStats())})
|
||
default:
|
||
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
|
||
}
|
||
}
|
||
|
||
func (h *Handler) applyFriend(writer http.ResponseWriter, request *http.Request, targetUserID int64) {
|
||
if request.Method != http.MethodPost {
|
||
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
|
||
return
|
||
}
|
||
resp, err := h.userSocialClient.ApplyFriend(request.Context(), &userv1.ApplyFriendRequest{
|
||
Meta: authRequestMeta(request, ""),
|
||
RequesterUserId: auth.UserIDFromContext(request.Context()),
|
||
TargetUserId: targetUserID,
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
httpkit.WriteOK(writer, request, map[string]any{"application": friendApplicationDataFromProto(resp.GetApplication()), "already_friends": resp.GetAlreadyFriends()})
|
||
}
|
||
|
||
func (h *Handler) acceptFriendApplication(writer http.ResponseWriter, request *http.Request, requesterUserID int64) {
|
||
if request.Method != http.MethodPost {
|
||
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
|
||
return
|
||
}
|
||
resp, err := h.userSocialClient.AcceptFriendApplication(request.Context(), &userv1.AcceptFriendApplicationRequest{
|
||
Meta: authRequestMeta(request, ""),
|
||
AccepterUserId: auth.UserIDFromContext(request.Context()),
|
||
RequesterUserId: requesterUserID,
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
httpkit.WriteOK(writer, request, map[string]any{"friend": friendData(resp.GetFriend())})
|
||
}
|
||
|
||
func (h *Handler) deleteFriend(writer http.ResponseWriter, request *http.Request, friendUserID int64) {
|
||
if request.Method != http.MethodDelete {
|
||
httpkit.WriteError(writer, request, http.StatusNotFound, httpkit.CodeNotFound, "not found")
|
||
return
|
||
}
|
||
resp, err := h.userSocialClient.DeleteFriend(request.Context(), &userv1.DeleteFriendRequest{
|
||
Meta: authRequestMeta(request, ""),
|
||
UserId: auth.UserIDFromContext(request.Context()),
|
||
FriendUserId: friendUserID,
|
||
})
|
||
if err != nil {
|
||
httpkit.WriteRPCError(writer, request, err)
|
||
return
|
||
}
|
||
httpkit.WriteOK(writer, request, map[string]any{"deleted": resp.GetDeleted()})
|
||
}
|
||
|
||
func socialPage(request *http.Request) (int32, int32, bool) {
|
||
page, ok := parsePositiveInt32Query(request, "page", 1)
|
||
if !ok {
|
||
return 0, 0, false
|
||
}
|
||
pageSize, ok := parsePositiveInt32Query(request, "page_size", 20)
|
||
if !ok {
|
||
return 0, 0, false
|
||
}
|
||
return page, pageSize, true
|
||
}
|
||
|
||
func profileVisitData(record *userv1.ProfileVisitRecord) profileVisitRecordData {
|
||
if record == nil {
|
||
return profileVisitRecordData{}
|
||
}
|
||
return profileVisitRecordData{
|
||
VisitorUserID: userIDString(record.GetVisitorUserId()),
|
||
TargetUserID: userIDString(record.GetTargetUserId()),
|
||
VisitCount: record.GetVisitCount(),
|
||
LastVisitedAtMS: record.GetLastVisitedAtMs(),
|
||
}
|
||
}
|
||
|
||
func followData(record *userv1.FollowRecord) followRecordData {
|
||
if record == nil {
|
||
return followRecordData{}
|
||
}
|
||
return followRecordData{
|
||
FollowerUserID: userIDString(record.GetFollowerUserId()),
|
||
FolloweeUserID: userIDString(record.GetFolloweeUserId()),
|
||
FollowedAtMS: record.GetFollowedAtMs(),
|
||
}
|
||
}
|
||
|
||
func friendData(record *userv1.FriendRecord) friendRecordData {
|
||
if record == nil {
|
||
return friendRecordData{}
|
||
}
|
||
return friendRecordData{
|
||
UserID: userIDString(record.GetUserId()),
|
||
FriendUserID: userIDString(record.GetFriendUserId()),
|
||
FriendedAtMS: record.GetFriendedAtMs(),
|
||
}
|
||
}
|
||
|
||
func friendApplicationDataFromProto(application *userv1.FriendApplication) friendApplicationData {
|
||
if application == nil {
|
||
return friendApplicationData{}
|
||
}
|
||
return friendApplicationData{
|
||
RequesterUserID: userIDString(application.GetRequesterUserId()),
|
||
TargetUserID: userIDString(application.GetTargetUserId()),
|
||
Status: application.GetStatus(),
|
||
CreatedAtMS: application.GetCreatedAtMs(),
|
||
UpdatedAtMS: application.GetUpdatedAtMs(),
|
||
}
|
||
}
|