Complex request with tunguska-reactive-aggregate $lookup

I’m trying to use https://github.com/robfallows/tunguska-reactive-aggregate with $lookup.
Is it possible to publish a reactive aggregate on such structure :

const ContactsSchema = new SimpleSchema({
  userId: {
    type: String,
    label: 'The ID of the user',
  },
  followedBy: {
    type: Array,
    label: 'informations about user\'s followers',
    optional: true,
    defaultValue: [],
  },
  'followedBy.$': UserSchema,
  blocked: {
    type: Array,
    label: 'informations about users blocked by user',
    optional: true,
    defaultValue: [],
  },
  'blocked.$': UserSchema,
...
const UserSchema = new SimpleSchema({
  userId: {
    type: String,
    label: 'userId of User',
  },
  since: {
    type: Date,
    label: 'date since last operation on record',
  },
});

Standard publish :

Meteor.publish("contacts.byId", userId => {
  return Contacts.find({ userId: userId });
});

I would like that the published Contacts resolves each userId in followedBy, blocked arrays as selected values of Meteor.users (username, avatar…) in the same way a graphQL request would do.

[ { 
userId: xxxx, 
followedBy: [ 
  { userId: u1, username: n1, avatar: a1, since: date1}, 
  { userId: u2, username: n2, avatar: a2, since: date2}...], 
blocked: [
  { userId: u3, username: n3, avatar: a3, since: date3}
]
}

I’m not familiar enough with $lookup to find a way to solve this, even know if it is possible or not.
Unfortunatly Rob doesn’t have enough available time to help and invited me to post here :wink:
Thank you

Not a lot of help here… :frowning: , so I started learning by myself with this excellent tooling and tutorial from Studio3T.
Here is the current status of my pipeline (not finished but in good way…)

[
{ $match: { userId: xxxx } },
{ $facet: {
   followedBy: [ 
    	{ 
            $unwind: {
    		path : "$followedBy",
    		preserveNullAndEmptyArrays : true 
	    }
    	},
    	{ 
            $lookup: {
    		from: "users",
   		localField: "followedBy.userId",
  		foreignField: "_id",
    		as: "userFollowedBy"
	    }
    	},
    	{ 
            $group: {
    	        _id: "$_id",
    		followedBy: {
                    $push: { user: "$userFollowedBy", since: "$followedBy.since"}
                }
    	    }
    	}
    ],
   blocked: [ 
    	{  .... same that above with blocked .... }
   ],
]

The output is not as expected, problems to solve :

  • filter userFollowedBy to have only the needed items (and not the full record of user : …blablabla… above)
  • get all the data from the contacts collection outside of group
  • simplify the double followedBy entry added by the $group
"followedBy" : [
        {
            "_id" : "LyeNvNBy6hiKxLKjQ", 
            "followedBy" : [
                {
                    "user" : [
                        {
                            "_id" : "RD7spqgeyPMMqfbko", 
                            "username" : "Humberto79", 
                            .... blablabla....
                        }
                    ], 
                    "since" : ISODate("2019-10-06T21:46:48.547+0000")
                }
            ]
1 Like

With the help of my new friend Florian, here is the correct request to achieve the wanted publication :slight_smile:

[
      {
        $match: {
          userId: userId
        }
      },
      {
        $facet: {
          old: [{ $limit: 1 }],
          following: [
            {
              $unwind: {
                path: "$following",
                preserveNullAndEmptyArrays: true
              }
            },
            {
              $lookup: {
                from: "users",
                let: { userId: "$following.userId" },
                pipeline: [
                  { $match: { $expr: { $eq: ["$_id", "$$userId"] } } },
                  {
                    $project: {
                      username: 1,
                      profile: { name: 1, avatar: 1, verified: 1 }
                    }
                  }
                ],
                as: "userFollowing"
              }
            },
            {
              $replaceRoot: {
                newRoot: {
                  $mergeObjects: [
                    "$following",
                    { $arrayElemAt: ["$userFollowing", 0] }
                  ]
                }
              }
            }
          ],
          followedBy: [
            // same as above but for followedBy
          ],
          blocked: [
            // same as above but for blocked
          ]
        }
      },

      // Stage 3
      {
        $unwind: {
          path: "$old"
        }
      },

      // Stage 4
      {
        $replaceRoot: {
          newRoot: {
            $mergeObjects: [
              "$old",
              { following: "$following" },
              { followedBy: "$followedBy" },
              { blocked: "$blocked" }
            ]
          }
        }
      }
    ], 

I’ll get in greater details in an article… stay tuned… please click on “love” button to speed up the process of publication and show your interest for such :wink:

1 Like