feat: complete im p2p relationship onboarding

This commit is contained in:
richjohnrambo
2026-08-12 15:20:15 +08:00
parent d02041659e
commit 943ebe1397
7 changed files with 666 additions and 17 deletions
+143 -2
View File
@@ -1014,10 +1014,39 @@ func serveIMConversationContext(wrt http.ResponseWriter, req *http.Request) {
}
func serveIMFriendRequests(wrt http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodPost {
switch req.Method {
case http.MethodGet:
serveIMFriendRequestList(wrt, req)
case http.MethodPost:
serveIMFriendRequestCreate(wrt, req)
default:
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
}
}
func serveIMFriendRequestList(wrt http.ResponseWriter, req *http.Request) {
principal, ok := authenticateIMHTTPBearer(wrt, req)
if !ok {
return
}
limit := parseIMLimit(req.URL.Query().Get("limit"), 20, 100)
items, nextCursor, err := store.Relations.ListFriendRequests(principal.TenantID, principal.Uid,
strings.TrimSpace(req.URL.Query().Get("direction")),
strings.TrimSpace(req.URL.Query().Get("status")),
strings.TrimSpace(req.URL.Query().Get("cursor")),
limit)
if err != nil {
writeIMRelationError(wrt, req, err)
return
}
payload := make([]map[string]any, 0, len(items))
for _, item := range items {
payload = append(payload, friendRequestListItemPayload(item))
}
writeIMSuccess(wrt, req, map[string]any{"items": payload, "next_cursor": nextCursor})
}
func serveIMFriendRequestCreate(wrt http.ResponseWriter, req *http.Request) {
principal, ok := authenticateIMHTTPBearer(wrt, req)
if !ok {
return
@@ -1060,6 +1089,32 @@ func serveIMFriendRequests(wrt http.ResponseWriter, req *http.Request) {
writeIMSuccess(wrt, req, friendRequestPayload(created))
}
func serveIMFriends(wrt http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodGet {
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
return
}
principal, ok := authenticateIMHTTPBearer(wrt, req)
if !ok {
return
}
limit := parseIMLimit(req.URL.Query().Get("limit"), 50, 100)
items, nextCursor, err := store.Relations.ListFriends(principal.TenantID, principal.Uid,
strings.TrimSpace(req.URL.Query().Get("query")),
strings.TrimSpace(req.URL.Query().Get("initial")),
strings.TrimSpace(req.URL.Query().Get("cursor")),
limit)
if err != nil {
writeIMRelationError(wrt, req, err)
return
}
payload := make([]map[string]any, 0, len(items))
for _, item := range items {
payload = append(payload, friendListItemPayload(item))
}
writeIMSuccess(wrt, req, map[string]any{"items": payload, "next_cursor": nextCursor})
}
func serveIMFriendRequestAction(wrt http.ResponseWriter, req *http.Request) {
if req.Method != http.MethodPost {
writeIMError(wrt, req, http.StatusMethodNotAllowed, 30002, imReasonMethodNotAllowed, "method not allowed", nil)
@@ -1615,7 +1670,9 @@ func friendRequestPayload(req *types.FriendRequest) map[string]any {
"status": req.Status,
"created_at": req.CreatedAt.UTC().Format(time.RFC3339Nano),
"expires_at": req.ExpiresAt.UTC().Format(time.RFC3339Nano),
"topic": req.ApplicantID.P2PName(req.TargetID),
}
if topic := req.ApplicantID.P2PName(req.TargetID); topic != "" {
data["topic"] = topic
}
if req.SourceTopic != "" {
data["source_topic"] = req.SourceTopic
@@ -1626,6 +1683,90 @@ func friendRequestPayload(req *types.FriendRequest) map[string]any {
return data
}
func friendRequestListItemPayload(item types.FriendRequestListItem) map[string]any {
data := friendRequestPayload(&item.Request)
data["direction"] = item.Direction
if item.Peer != nil {
data["peer"] = userSummaryPayload(item.Peer, "")
}
return data
}
func friendListItemPayload(item types.FriendListItem) map[string]any {
data := map[string]any{
"user_id": item.Friendship.PeerID.UserId(),
"relationship": item.Friendship.Status,
"remark": item.Friendship.Remark,
"topic": item.Friendship.Topic,
"online_visibility": "unknown",
"created_at": item.Friendship.CreatedAt.UTC().Format(time.RFC3339Nano),
"updated_at": item.Friendship.UpdatedAt.UTC().Format(time.RFC3339Nano),
"capabilities": map[string]any{"can_send": item.Friendship.Status == types.RelationshipFriend, "can_call": item.Friendship.Status == types.RelationshipFriend},
"display_name_source": "profile",
}
if item.Friendship.Remark != "" {
data["display_name"] = item.Friendship.Remark
data["display_name_source"] = "remark"
}
if item.Peer != nil {
for key, value := range userSummaryPayload(item.Peer, item.Friendship.Remark) {
_, exists := data[key]
if !exists || key == "avatar_file_id" || (key == "display_name" && item.Friendship.Remark == "") {
data[key] = value
}
}
}
data["initial"] = displayInitial(data["display_name"])
return data
}
func userSummaryPayload(user *types.User, remark string) map[string]any {
uid := types.ParseUid(user.Id)
userID := uid.UserId()
if uid.IsZero() {
userID = user.Id
}
data := map[string]any{"user_id": userID}
if remark != "" {
data["display_name"] = remark
}
if public, ok := user.Public.(map[string]any); ok {
copyUserPublicSummary(data, public)
} else if public, ok := user.Public.(types.KVMap); ok {
copyUserPublicSummary(data, map[string]any(public))
}
if _, ok := data["display_name"]; !ok {
data["display_name"] = userID
}
return data
}
func copyUserPublicSummary(data map[string]any, public map[string]any) {
for _, key := range []string{"display_name", "fn", "name", "nickname"} {
if value, ok := public[key]; ok && strings.TrimSpace(fmt.Sprint(value)) != "" {
data["display_name"] = value
break
}
}
for _, key := range []string{"avatar_file_id", "photo", "avatar"} {
if value, ok := public[key]; ok && strings.TrimSpace(fmt.Sprint(value)) != "" {
data["avatar_file_id"] = value
break
}
}
}
func displayInitial(value any) string {
name := strings.TrimSpace(fmt.Sprint(value))
if name == "" {
return "#"
}
for _, r := range name {
return strings.ToUpper(string(r))
}
return "#"
}
func writeIMRelationError(wrt http.ResponseWriter, req *http.Request, err error) {
switch {
case errors.Is(err, types.ErrNotFound):