elemMatch for fields in different levels - mongodb

I'm asking this question after trying solve this problem all day long.
I want to get my users address by userId and by addressId. Since I receive data from post requests, I need to ensure that the query contains both userId and addressId in order to avoid security problems. The result of the query below returns all addresses from my user, instead of just the address containing the correct addressId.
async getUserAddress(_id: string, addressId: string) {
const user = await this.userModel.findOne(
{
_id,
addresses: {
$elemMatch: {
addressId: Types.ObjectId(addressId)
}
}
}
)
if (!user) {
throw new NotFoundException();
}
return user.addresses[0];
}
Since is impossible to use $elemMatch in different document levels, or even at the top level of the query, I find no better way to make this query without using filters. Are there any insights?
Tks in advance

Related

Mongodb Ref dynamic populate with grapqhl?

I have to decide whether to populate or not according to the query request, but I don't know how to do it.
So Example
If my model User is looks like this
below syntax is from typegoose and typegraphql
class User {
#Field()
#prop()
name: string;
#Field()
#prop(ref:"House")
house: Ref<House>
}
And here is two diffent query
Query1
user {
name
}
Query2
user {
name
house {
location
}
}
And in the resolver
User: () => {
const user = UserModel.find(blahblah)**.populate("house")**
return user
}
Query1 dose not need populate
but Query2 need
in same resolver!
I want to decide whether to populate or not depending on the requirements of the query.
I can't decide whether to populate or not without knowing what is the actual query was in resolver.
I found very similar question in stackoverflow
But there is not proper answer...
Solving relationships in Mongoose and GraphQL
i dont know much about graphql, but i think there is some method to get if that path is requested, so the following should work:
let query = Model.find(basicQuery);
if (req.path) { // replace this with graphql's method to get if the path is requested
query = query.populate(yourPath);
}
const doc = await query.exec();
PS: but as an comment already noted, i think there is some better method to do this in graphql (another resolver?)

Dataloader did not return an array of the same length?

I Am building an express JS application with graphql, and mongodb (mongoose). I am using facebooks Dataloader to batch and cache requests.
Its working perfectly fine except for this use case.
I have a database filled with users posts. Each post contains the users ID for reference. When i make a call to return all the posts in the database. The posts are returned fine but if i try to get the user in each post. Users with multiple posts will only return a single user because the key for the second user is cached. So 2 posts(keys) from user "x" will only return 1 user object "x".
However Dataloader has to return the same amount of promises as keys that it recieves.
It has a option to specify cache as false so each key will make a request. But this doesnt seem to work for my use case.
Sorry if i havn't explained this very well.
this is my graphql request
query {
getAllPosts {
_id // This is returned fine
user {
_id
}
}
}
Returned error:
DataLoader must be constructed with a function which accepts Array<key> and returns Promise<Array<value>>, but the function did not return a Promise of an Array of the same length as the Array of keys.
are you trying to batch post keys [1, 2, 3] and expecting to get user results [{ user1 }, {user2}, { user1 }]?
or are you trying to batch user keys [1, 2] and expecting to get post results [{ post1}, {post3}] and [{ post2 }]?
seems like only in the second case will you run into a situation where you have length of keys differing from length of results array.
to solve the second, you could do something like this in sql:
const loader = new Dataloader(userIDs => {
const promises = userIDs.map(id => {
return db('user_posts')
.where('user_id', id);
});
return Promise.all(promises);
})
loader.load(1)
loader.load(2)
so you return [[{ post1}, {post3}], [{ post2 }]] which dataloader can unwrap.
if you had done this instead:
const loader = new Dataloader(userIDs => {
return db('user_posts')
.where('user_id', [userIDs]);
})
loader.load(1)
loader.load(2)
you will instead get [{ post1}, {post3}, { post2 }] and hence the error: the function did not return a Promise of an Array of the same length as the Array of keys
not sure if the above is relevant / helpful. i can revise if you can provide a snippet of your batch load function
You need to map the data returned from the database to the Array of keys.
Dataloader: The Array of values must be the same length as the Array of keys
This issue is well explained in this YouTube Video - Dataloader - Highly recommended

Sequelize: where query string is in array of strings postgresql

I am trying to perform a query in sequelize where I want to get only users that have the correct role. Roles are stored as an array of strings. For example ['student'] or ['school_owner', 'admin'].
In this particular case, I'm actually trying to get a school and include the school owners for that school. The failing relevant query is
const ownerQuery: { [key: string]: any } = {};
ownerQuery.roles = {$in: ["{school_owner}"]};
School
.findById(req.params.id,{include: [{ model: User, where: ownerQuery }]})
.then((school: School | null) => {
if (school === null) {
res.status(404).json({message: "Not Found"})
} else {
res.json(school)
}
}, (error) => next(error))
sequelize is storing the array values as something like {school_owner, admin}. The documentation says that I can use the following instead for my $in query
ownerQuery.roles = {$in: ["school_owner"]};
Which removes the {} but it gives me a Array value must start with "{" or dimension information.' error.
In the first example, the query doesn't fail, but it doesn't work like an $in query either. I have to match the contents of roles exactly. For example, if a user has both admin and school_owner roles I have to say
ownerQuery.roles = {$in: ["{school_owner, admin}"]};
What's the correct way to perform an $in query so that I can match all users that have a specific roles?
The correct way to implement this functionality is to do the following
ownerQuery.roles = { $contains: ["school_owner"] };
This will return all users that have a role school_owner in their array of roles

Meteor Subscriptions Selecting the Entire Set?

I've defined a publication:
Meteor.publish('uninvited', function (courseId: string) {
return Users.find({
'profile.courses': {
$ne: courseId
}
});
});
So, in when a subscriber subscribes to this, I expect Users.find() to return only users that are not enrolled in that particular course. So, on my client, when I write:
this.uninvitedSub = MeteorObservable.subscribe("uninvited", this.courseId).subscribe(() => {
this.uninvited = Users.find().zone()});
I expect uninvited to contain only a subset of users, however, I'm getting the entire set of users regardless of whether or not they are enrolled in a particular course. I've made sure that my data is correct and that there are users enrolled in the course that I'm concerned with. I've also verified that this.courseId is working as expected. Is there an error with my code, or should I further look into my data to see if there's anything wrong with it?
**Note:
When I write this:
this.uninvitedSub = MeteorObservable.subscribe("uninvited", this.courseId).subscribe(() => {
this.uninvited = Users.find({
'profile.courses': {}
}).zone();
});
With this, it works as expected! Why? The difference is that my query now contains 'profile.courses': {}.

Send more than one term to algolia search

I'm implementing algolia search in my site and i want to get a set of data matching any id's i send to the search, so i need to know how could i send more than one parameter to the search, so i can send a set of ids, something like this:
let client = algoliasearch(APP_ID, API_KEY),
index = client.initIndex(INDEX_NAME);
let term=["3223212","2423434"];
index.search(term, callback)
This is not working right now, have any idea? or even how could i achieve my goal using another algolia feautre like filtering for instance?
If you're trying to retrieve objects by their objectIDs (which you can manually set at creation time to match your database ids), you can simply use the getObjects method.
Extract from the documentation:
You can also retrieve a set of objects:
index.getObjects(['myObj1', 'myObj2'], function(err, content) {
console.log(content);
});
If you're trying to list all the records that belong to a group with a specific id, you can use a facet that will contain this id and filter on it.
Inside your record:
{
"group_id": "3223212",
// or
"group_ids": ["3223212", "2423434"]
}
Inside your index settings:
{
attributesForFaceting: [
'onlyFilter(group_id)'
]
}
At query time:
let ids = ["3223212", "2423434"];
let filters = ids.map(id => `group_id:${id}`).join(' OR ');
index.search('', { filters: filters }, callback);