DeleteFriend 一次删掉两条好友边,消息仍留在会话里。 Co-authored-by: Cursor <cursoragent@cursor.com>
152 lines
4.1 KiB
Go
152 lines
4.1 KiB
Go
package logic
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
|
|
"spark-server/internal/module/friend/entity"
|
|
"spark-server/internal/pkg/errs"
|
|
"spark-server/internal/pkg/i18n"
|
|
"spark-server/internal/pkg/reqctx"
|
|
)
|
|
|
|
// FollowView is the viewer's follow state toward one user.
|
|
type FollowView struct {
|
|
UID int64 `json:"uid"`
|
|
Following bool `json:"following"`
|
|
FollowedBy bool `json:"followed_by"`
|
|
Mutual bool `json:"mutual"`
|
|
Friend bool `json:"friend"`
|
|
}
|
|
|
|
func (s *Service) Follow(ctx context.Context, userID, targetID int64) (*FollowView, error) {
|
|
if err := s.followReady(ctx, userID, targetID); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := s.activeUser(ctx, targetID); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.followBlocks(ctx, userID, targetID); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.repo.PutFollow(ctx, userID, targetID); err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.friendIfMutual(ctx, userID, targetID); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.followView(ctx, userID, targetID)
|
|
}
|
|
|
|
func (s *Service) Unfollow(ctx context.Context, userID, targetID int64) (*FollowView, error) {
|
|
if err := s.followReady(ctx, userID, targetID); err != nil {
|
|
return nil, err
|
|
}
|
|
mutual, err := s.mutualFollow(ctx, userID, targetID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := s.repo.DeleteFollow(ctx, userID, targetID); err != nil {
|
|
return nil, err
|
|
}
|
|
if mutual {
|
|
// DeleteFriend removes both directed edges. Messages stay.
|
|
if err := s.repo.DeleteFriend(ctx, userID, targetID); err != nil && !errors.Is(err, errs.ErrNotFound) {
|
|
return nil, err
|
|
}
|
|
}
|
|
return s.followView(ctx, userID, targetID)
|
|
}
|
|
|
|
func (s *Service) GetFollow(ctx context.Context, userID, targetID int64) (*FollowView, error) {
|
|
if err := s.followReady(ctx, userID, targetID); err != nil {
|
|
return nil, err
|
|
}
|
|
if _, err := s.activeUser(ctx, targetID); err != nil {
|
|
return nil, err
|
|
}
|
|
return s.followView(ctx, userID, targetID)
|
|
}
|
|
|
|
func (s *Service) followReady(ctx context.Context, userID, targetID int64) error {
|
|
if !s.ready() || userID <= 0 {
|
|
return errs.ErrUnauthorized
|
|
}
|
|
if targetID <= 0 {
|
|
return errs.ErrInvalidArgument
|
|
}
|
|
if entity.SameUser(userID, targetID) {
|
|
return errs.ErrInvalidArgument.WithMsg(i18n.T(reqctx.Lang(ctx), i18n.KeyFriendSelf, nil))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) followBlocks(ctx context.Context, from, to int64) error {
|
|
lang := reqctx.Lang(ctx)
|
|
blockedMe, err := s.repo.IsBlocked(ctx, from, to)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if blockedMe {
|
|
return errs.ErrForbidden.WithMsg(i18n.T(lang, i18n.KeyFriendBlocked, nil))
|
|
}
|
|
blockedBy, err := s.repo.IsBlocked(ctx, to, from)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if blockedBy {
|
|
return errs.ErrNotFound
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) friendIfMutual(ctx context.Context, userID, targetID int64) error {
|
|
mutual, err := s.mutualFollow(ctx, userID, targetID)
|
|
if err != nil || !mutual {
|
|
return err
|
|
}
|
|
ok, err := s.AreFriends(ctx, userID, targetID)
|
|
if err != nil || ok {
|
|
return err
|
|
}
|
|
now := time.Now()
|
|
if err := s.repo.AcceptFriend(ctx, entity.AcceptParams{Edges: []*entity.Relation{
|
|
entity.NewFriendEdge(userID, targetID, "", entity.SourceFollow, now),
|
|
entity.NewFriendEdge(targetID, userID, "", entity.SourceFollow, now),
|
|
}}); err != nil {
|
|
return err
|
|
}
|
|
if s.greeter != nil {
|
|
_ = s.greeter.BecameFriends(ctx, userID, targetID)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (s *Service) mutualFollow(ctx context.Context, userID, targetID int64) (bool, error) {
|
|
following, err := s.repo.HasFollow(ctx, userID, targetID)
|
|
if err != nil || !following {
|
|
return false, err
|
|
}
|
|
return s.repo.HasFollow(ctx, targetID, userID)
|
|
}
|
|
|
|
func (s *Service) followView(ctx context.Context, userID, targetID int64) (*FollowView, error) {
|
|
following, err := s.repo.HasFollow(ctx, userID, targetID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
followedBy, err := s.repo.HasFollow(ctx, targetID, userID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
friend, err := s.AreFriends(ctx, userID, targetID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &FollowView{
|
|
UID: targetID, Following: following, FollowedBy: followedBy,
|
|
Mutual: following && followedBy, Friend: friend,
|
|
}, nil
|
|
}
|