package logic import ( "context" "errors" "regexp" "strconv" "strings" "spark-server/internal/module/friend/entity" userentity "spark-server/internal/module/user/entity" "spark-server/internal/pkg/errs" "spark-server/internal/pkg/i18n" "spark-server/internal/pkg/reqctx" ) var ( lookupEmailRe = regexp.MustCompile(`^[^@\s]+@[^@\s]+\.[^@\s]+$`) lookupDigitsRe = regexp.MustCompile(`^[0-9]+$`) ) // LookupInput finds a user to add. Q is email, uid, spark_id, or qrcode. type LookupInput struct { Q string `json:"q" validate:"required,max=255" label:"field.friend_query" errmsg:"required:validate.friend.query_required"` } func (s *Service) Lookup(ctx context.Context, userID int64, in LookupInput) (*FriendView, error) { if !s.ready() || userID <= 0 { return nil, errs.ErrUnauthorized } target, err := s.resolveLookupQuery(ctx, strings.TrimSpace(in.Q)) if err != nil { return nil, err } blockedBy, err := s.repo.IsBlocked(ctx, target.ID, userID) if err != nil { return nil, err } if blockedBy { return nil, errs.ErrNotFound } rel, err := s.repo.GetRelation(ctx, userID, target.ID) if err != nil && !errors.Is(err, errs.ErrNotFound) { return nil, err } if errors.Is(err, errs.ErrNotFound) { rel = nil } friend, err := s.AreFriends(ctx, userID, target.ID) if err != nil { return nil, err } blockedMe := rel != nil && rel.Blocked view := viewOf(target, rel) view.Relation = entity.ViewRelation(friend, blockedMe) if !friend { view.CreatedAt = 0 view.Pinned = false if rel == nil { view.Remark = "" } } return view, nil } func (s *Service) resolveLookupQuery(ctx context.Context, q string) (*userentity.User, error) { if q == "" { return nil, errs.ErrInvalidArgument.WithMsg(i18n.T(reqctx.Lang(ctx), i18n.KeyFriendQueryRequired, nil)) } switch { case lookupEmailRe.MatchString(q): u, err := s.users.GetByEmail(ctx, userentity.NormalizeEmail(q)) u, _, err = s.ensureActive(ctx, u, err, entity.SourceEmail) return u, err case lookupDigitsRe.MatchString(q): id, err := strconv.ParseInt(q, 10, 64) if err != nil || id <= 0 { return nil, errs.ErrNotFound } return s.activeUser(ctx, id) default: return s.lookupSparkIDThenQR(ctx, q) } } func (s *Service) lookupSparkIDThenQR(ctx context.Context, q string) (*userentity.User, error) { u, err := s.users.GetBySparkID(ctx, q) u, _, err = s.ensureActive(ctx, u, err, entity.SourceSparkID) if err == nil { return u, nil } if !errors.Is(err, errs.ErrNotFound) { return nil, err } u, err = s.users.GetByQRCode(ctx, q) u, _, err = s.ensureActive(ctx, u, err, entity.SourceQRCode) return u, err }