feat: add im relationship flows

This commit is contained in:
richjohnrambo
2026-08-12 11:31:16 +08:00
parent 5bd35f97a6
commit d02041659e
11 changed files with 1100 additions and 12 deletions
+152
View File
@@ -995,6 +995,157 @@ func (messagesMapper) Edit(tenantID types.TenantID, topic string, seq int, actor
return tenantAdp.TenantMessageEdit(tenantID, topic, seq, actor, expectedVersion, head, content, reason, editedAt)
}
type RelationsPersistenceInterface interface {
CreateFriendRequest(tenantID types.TenantID, req *types.FriendRequest) (*types.FriendRequest, error)
ResolveFriendRequest(tenantID types.TenantID, requestID string, actor types.Uid, status string, handledAt time.Time) (*types.FriendRequest, error)
GetRelationshipContext(tenantID types.TenantID, userID, peerID types.Uid, topic string) (*types.RelationshipContext, error)
CanSendP2P(tenantID types.TenantID, userID, peerID types.Uid) (bool, string, error)
BlockUser(tenantID types.TenantID, blockerID, blockedID types.Uid, now time.Time) (*types.UserBlock, error)
UnblockUser(tenantID types.TenantID, blockerID, blockedID types.Uid) error
DeleteFriend(tenantID types.TenantID, userID, peerID types.Uid, now time.Time) error
}
type relationsMapper struct{}
var Relations RelationsPersistenceInterface
func (relationsMapper) CreateFriendRequest(tenantID types.TenantID, req *types.FriendRequest) (*types.FriendRequest, error) {
if req == nil || req.TenantID != tenantID || req.ApplicantID.IsZero() || req.TargetID.IsZero() || req.ApplicantID == req.TargetID {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
return tenantAdp.TenantFriendRequestCreate(tenantID, req)
}
func (relationsMapper) ResolveFriendRequest(tenantID types.TenantID, requestID string, actor types.Uid, status string, handledAt time.Time) (*types.FriendRequest, error) {
if strings.TrimSpace(requestID) == "" || actor.IsZero() || (status != types.FriendRequestAccepted && status != types.FriendRequestRejected) {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
req, err := tenantAdp.TenantFriendRequestResolve(tenantID, requestID, actor, status, handledAt)
if err != nil {
return nil, err
}
if status == types.FriendRequestAccepted {
if err := tenantAdp.TenantFriendshipUpsertPair(tenantID, req.ApplicantID, req.TargetID, "", handledAt); err != nil {
return nil, err
}
}
return req, nil
}
func (relationsMapper) GetRelationshipContext(tenantID types.TenantID, userID, peerID types.Uid, topic string) (*types.RelationshipContext, error) {
if userID.IsZero() || peerID.IsZero() || userID == peerID {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
return relationshipContext(tenantAdp, tenantID, userID, peerID, topic)
}
func (relationsMapper) CanSendP2P(tenantID types.TenantID, userID, peerID types.Uid) (bool, string, error) {
ctx, err := (relationsMapper{}).GetRelationshipContext(tenantID, userID, peerID, "")
if err != nil {
return false, "", err
}
if ctx.Relationship == types.RelationshipFriend {
return true, ctx.Relationship, nil
}
return false, ctx.Relationship, nil
}
func (relationsMapper) BlockUser(tenantID types.TenantID, blockerID, blockedID types.Uid, now time.Time) (*types.UserBlock, error) {
if blockerID.IsZero() || blockedID.IsZero() || blockerID == blockedID {
return nil, types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return nil, err
}
return tenantAdp.TenantUserBlockUpsert(tenantID, blockerID, blockedID, now)
}
func (relationsMapper) UnblockUser(tenantID types.TenantID, blockerID, blockedID types.Uid) error {
if blockerID.IsZero() || blockedID.IsZero() || blockerID == blockedID {
return types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return err
}
return tenantAdp.TenantUserBlockDelete(tenantID, blockerID, blockedID)
}
func (relationsMapper) DeleteFriend(tenantID types.TenantID, userID, peerID types.Uid, now time.Time) error {
if userID.IsZero() || peerID.IsZero() || userID == peerID {
return types.ErrMalformed
}
tenantAdp, err := tenantBusinessAdapter(tenantID)
if err != nil {
return err
}
return tenantAdp.TenantFriendshipDeletePair(tenantID, userID, peerID, now)
}
func relationshipContext(tenantAdp adapter.TenantBusinessAdapter, tenantID types.TenantID, userID, peerID types.Uid, topic string) (*types.RelationshipContext, error) {
ctx := &types.RelationshipContext{
TenantID: tenantID,
UserID: userID,
PeerID: peerID,
Topic: topic,
Relationship: types.RelationshipNone,
}
peer, err := tenantAdp.TenantUserGet(tenantID, peerID)
if err != nil {
return nil, err
}
ctx.Peer = peer
if block, err := tenantAdp.TenantUserBlockGet(tenantID, userID, peerID); err != nil {
return nil, err
} else if block != nil {
ctx.Relationship = types.RelationshipBlockedByMe
return ctx, nil
}
if block, err := tenantAdp.TenantUserBlockGet(tenantID, peerID, userID); err != nil {
return nil, err
} else if block != nil {
ctx.Relationship = types.RelationshipBlockedByPeer
return ctx, nil
}
if friendship, err := tenantAdp.TenantFriendshipGet(tenantID, userID, peerID); err != nil {
return nil, err
} else if friendship != nil && friendship.Status == types.RelationshipFriend {
ctx.Relationship = types.RelationshipFriend
ctx.CanSend = true
if ctx.Topic == "" {
ctx.Topic = friendship.Topic
}
return ctx, nil
}
req, err := tenantAdp.TenantFriendRequestLatestBetween(tenantID, userID, peerID)
if err != nil {
return nil, err
}
if req != nil && req.Status == types.FriendRequestPending && req.ExpiresAt.After(time.Now()) {
ctx.RequestID = req.RequestID
ctx.PendingStatus = req.Status
if req.ApplicantID == userID {
ctx.Relationship = types.RelationshipPendingOutbound
} else {
ctx.Relationship = types.RelationshipPendingInbound
}
}
return ctx, nil
}
// Save message
func (messagesMapper) Save(tenantID types.TenantID, msg *types.Message, attachmentURLs []string, readBySender bool) (*types.MessageSaveResult, error) {
if msg == nil || msg.TenantID != tenantID {
@@ -1814,4 +1965,5 @@ func init() {
PCache = pcacheMapper{}
Verifications = verificationMapper{}
Security = userSecurityMapper{}
Relations = relationsMapper{}
}
+65
View File
@@ -0,0 +1,65 @@
package types
import "time"
const (
FriendRequestPending = "pending"
FriendRequestAccepted = "accepted"
FriendRequestRejected = "rejected"
FriendRequestExpired = "expired"
RelationshipNone = "none"
RelationshipFriend = "friend"
RelationshipPendingInbound = "pending_inbound"
RelationshipPendingOutbound = "pending_outbound"
RelationshipBlockedByMe = "blocked_by_me"
RelationshipBlockedByPeer = "blocked_by_peer"
)
type FriendRequest struct {
ID int64
TenantID TenantID `db:"tenant_id" json:"-" bson:"tenant_id"`
RequestID string `db:"request_id"`
ApplicantID Uid `db:"applicant_id"`
TargetID Uid `db:"target_id"`
Message string
Source string
SourceTopic string `db:"source_topic"`
Status string
IdempotencyKey string `db:"idempotency_key"`
ExpiresAt time.Time `db:"expires_at"`
HandledAt *time.Time `db:"handled_at"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
type Friendship struct {
TenantID TenantID `db:"tenant_id" json:"-" bson:"tenant_id"`
UserID Uid `db:"user_id"`
PeerID Uid `db:"peer_id"`
Status string
Remark string
Topic string
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
type UserBlock struct {
TenantID TenantID `db:"tenant_id" json:"-" bson:"tenant_id"`
BlockerID Uid `db:"blocker_id"`
BlockedID Uid `db:"blocked_id"`
CreatedAt time.Time `db:"created_at"`
UpdatedAt time.Time `db:"updated_at"`
}
type RelationshipContext struct {
TenantID TenantID `json:"-"`
UserID Uid `json:"-"`
PeerID Uid `json:"-"`
Topic string `json:"topic"`
Relationship string `json:"relationship"`
CanSend bool `json:"can_send"`
RequestID string `json:"request_id,omitempty"`
PendingStatus string `json:"pending_status,omitempty"`
Peer *User `json:"peer,omitempty"`
}