Запрос агрегации для возврата самых последних сообщений пользователю A и от него не работает должным образом - PullRequest
2 голосов
/ 07 ноября 2019

У меня есть модель MostRecentMessage, определенная как

const MostRecentMessage = new Schema({
  to: {
    type: mongoose.Schema.Types.ObjectId,
    ref: "user"
  },
  from: {
    type: mongoose.Schema.Types.ObjectId,
    ref: "user"
  },
  conversation: {
    type: mongoose.Schema.Types.ObjectId,
    ref: "conversation"
  },
  date: {
    type: Date,
    default: Date.now
  }
});

Всякий раз, когда пользователь A отправляет сообщение пользователю B, документ создается, если его не существует. Каждый раз, когда пользователь A отправляет сообщение пользователю B, этот документ обновляется новыми conversation и date. Примером документа является:

_id: ObjectId(5dc46521cf670a0017d2434d)
to: ObjectId(5dc464ce2fd75700178c1ad4) // User B
from: ObjectId(5dc464fc2fd75700178c1ad5) // User A
conversation: ObjectId(5dc465c6cf670a0017d24363)
date: 2019-11-07T18:40:33.242+00:00
__v: 0

Идея состоит в том, что это поможет отследить все последние сообщения определенным пользователям.

Аналогично, пользователь B может отправить пользователю A сообщение. Будет создан новый документ, аналогичный приведенному выше,

_id: ObjectId(5dc46521cf670a0017d2434d)
to: ObjectId(5dc464fc2fd75700178c1ad5) // User A
from: ObjectId(5dc464ce2fd75700178c1ad4) // User B
conversation: ObjectId(5dc465c6cf670a0017d24363)
date: 2019-11-07T18:40:33.242+00:00
__v: 0

И этот документ будет обновляться при каждом новом сообщении, которое пользователь B отправляет пользователю A.

Теперь ниже мойзапрос агрегации .. но проблема в том, что он возвращает только один разговор. Может быть 10 пользователей, разговаривающих с пользователем A, но он все равно вернет только один документ.

  const { id } = req.user;

  try {
    await MostRecentMessages.aggregate(
      [
        {
          $match: {
            $or: [
              { from: mongoose.Types.ObjectId(id) },
              { to: mongoose.Types.ObjectId(id) }
            ]
          }
        },
        { $project: { _id: 1, from: 1, to: 1, conversation: 1, date: 1 } },
        { $sort: { date: -1 } },
        {
          $group: {
            _id: null,
            from: { $first: "$from" },
            to: { $first: "$to" },
            date: { $first: "$date" },
            conversation: { $first: "$conversation" }
          }
        },
        {
          $lookup: {
            from: "conversations",
            localField: "conversation",
            foreignField: "_id",
            as: "conversation"
          }
        },
        { $unwind: { path: "$conversation" } },
        {
          $lookup: {
            from: "users",
            localField: "to",
            foreignField: "_id",
            as: "to"
          }
        },
        { $unwind: { path: "$to" } },
        {
          $lookup: {
            from: "users",
            localField: "from",
            foreignField: "_id",
            as: "from"
          }
        },
        { $unwind: { path: "$from" } }
      ],
      function(err, docs) {
        if (err) {
          console.log(err);
        } else {
          return res.json(docs);
        }
      }
    );
  } catch (err) {
    console.log(err);

    return res.status(500).send("Server error");
  }

Что я здесь не так делаю?


Редактировать: что у меня сейчас:

С этим кодом, если

User A messages User B, 'Test1'
User B messages User A, 'Test2'
User A messages User B, 'Test3'

, то для обоих пользователей будет отображаться только сообщение Test2. Как я могу получить «Test3» для отображения для обоих пользователей?

    await MostRecentMessages.aggregate(
      [
        {
          $match: {
            $or: [
              { from: mongoose.Types.ObjectId(id) },
              { to: mongoose.Types.ObjectId(id) }
            ]
            // deletedBy: { $ne: id }
          }
        },
        { $project: { _id: 1, from: 1, to: 1, conversation: 1, date: 1 } },
        { $sort: { date: -1 } },
        {
          $group: {
            _id: {
              userConcerned: {
                $cond: {
                  if: {
                    $eq: ["$to", mongoose.Types.ObjectId(id)]
                  },
                  then: "$to",
                  else: "$from"
                }
              },
              interlocutor: {
                $cond: {
                  if: {
                    $eq: ["$to", mongoose.Types.ObjectId(id)]
                  },
                  then: "$from",
                  else: "$to"
                }
              }
            },
            from: { $first: "$from" },
            to: { $first: "$to" },
            date: { $first: "$date" },
            conversation: { $first: "$conversation" }
          }
        },
        {
          $lookup: {
            from: "conversations",
            localField: "conversation",
            foreignField: "_id",
            as: "conversation"
          }
        },
        { $unwind: { path: "$conversation" } },
        {
          $lookup: {
            from: "users",
            localField: "to",
            foreignField: "_id",
            as: "to"
          }
        },
        { $unwind: { path: "$to" } },
        {
          $lookup: {
            from: "users",
            localField: "from",
            foreignField: "_id",
            as: "from"
          }
        },
        { $unwind: { path: "$from" } }
      ],
      function(err, docs) {
        if (err) {
          console.log(err);
        } else {
          return res.json(docs);
        }
      }

1 Ответ

0 голосов
/ 08 ноября 2019

Как сказано в моем комментарии, групповой этап отвечает за уникальный результат, который вы получаете. Хитрость заключается в том, чтобы создать 2 поля, из поля «from» или «to» (в зависимости от objectId, который вы предоставляете в param), представляющего пользователя, которого вы предоставляете, и его собеседника. (независимо от 'от' до '). Вот идея, но в настоящее время я не могу проверить решение.

...

  {
    $group: {
      _id: {
      userConcerned: {
        $cond: {
          if: {
            $eq: [
              "$to",
              mongoose.Types.ObjectId(id)
            ]
          },
          then: "$to",
          else: "$from"
        }
      },
      interlocutor: {
        $cond: {
          if: {
            $eq: [
              "$to",
              mongoose.Types.ObjectId(id)
            ]
          },
          then: "$from",
          else: "$to"
        }
      }
    },
        from: { $first: "$from" },
        to: { $first: "$to" },
        date: { $first: "$date" },
        conversation: { $first: "$conversation" }
    }
  }
  ...

Вот POC

(Есливы на самом деле не имеете значения «от» и «до», вы можете использовать два новых созданных поля для поиска и удалить из и в групповой этап.

Надеюсь, это поможет

--- РЕДАКТИРОВАТЬ --- Чтобы завершить и объяснить мой ответ: Группировка с этим вычисленным _id вместо null сгруппирует любой разговор между A и B (A-> B или B-> A), A и C, Aи D, ... A всегда будет в 'userConcerned', другой собеседник. Таким образом, вы получите документ для каждого разговора A, с последним сообщением (благодаря предыдущему этапу $ sort и аккумулятору $ first) между А и его собеседником, не заботясь о том, является ли А отправителем или получателем (от или до) сообщения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...