mongoDB autcomplete returns empty array - mongodb

I am using mongodb's sample movie database (https://docs.atlas.mongodb.com/sample-data/sample-mflix#std-label-sample-mflix) to experiment with mongodb's autocomplete functionality. The search always returns an empty array. I have set up a Search Index as follows:
{
"mappings": {
"dynamic": false,
"fields": {
"title": [
{
"minGrams": 3,
"tokenization": "edgeGram",
"type": "autocomplete"
}
]
}
}
}
The model and the search query are setup as follows:
// Creating the Movies model
const Movies = mongoose.model("Movies", new mongoose.Schema({}), "movies");
// Impplementing autocomplete search
app.get("/search", async (req, res) => {
try {
let result = await Movies.aggregate([
{
$search: {
autocomplete: {
path: "title",
query: req.query.title,
fuzzy: {
maxEdits: 2,
prefixLength: 3,
},
},
},
},
]);
res.status(200).json({
status: "success",
results: result.length,
data: { result },
});
} catch (error) {
console.log(error);
}
});
I am using postman to run test queries and a sample query is: 127.0.0.1:3030/search?title=black
The model can be queried using .find(), for example, and returns the full collection of documents.
Any and all help is greatly appreciated.

Related

Strapi: How to populate data two levels deep?

I want to populate data two levels deep. Therefore I wrote the following:
let homepage = qs.stringify({
populate: '*',
},{
encodeValuesOnly: true, // prettify URL
});
This returns the following JSON:
{
"data": {
"id": 1,
"attributes": {
},
"att1": {
},
"att2": {
},
"att3": {
},
"att4": [],
"Projekte": {
}
}
},
"meta": {}
}
The nested Object “Projekte” now does contain an image. How can I access it?
You should specify the fields you want to populate. In your case :
let homepage = qs.stringify({
populate: 'Projekte',
},{
encodeValuesOnly: true, // prettify URL
});

Search MongoDB autocomplete filtered

I want to search autocomplete on the following fields :contactfirstname, contactlastname and name
Also, want to filter based on userid(s) first then perform autocomplete search
Issue:
Without filter criteria, autocomplete is working fine
With filter criteria in compound query not working as getting empty array
Can anyone help please?
exports.userNameCitySearchAutocomplete = async function (req, res) {
try {
const { userNameCityQueryparam } = req.query;
console.log("search query param", userNameCityQueryparam);
const agg = [
{
$search: {
index: 'userNameCity',
'compound': {
"filter": [{
"text": {
"query": ["6271f2bb79cd80194c81f631"],
"path": "_id",
}
}],
"should": [
{
//search on user name
autocomplete: {
query: userNameCityQueryparam,
path: 'name',
fuzzy: {
maxEdits: 2,
prefixLength: 3
}
}},
//search on user city
{
autocomplete: {
query: userNameCityQueryparam,
path: 'city',
fuzzy: {
maxEdits: 2,
prefixLength: 3
}
},
}
,
//search on user contact first name
{
autocomplete: {
query: userNameCityQueryparam,
path: 'contactfirstname',
fuzzy: {
maxEdits: 2,
prefixLength: 3
}
},
}
,
//search on user contact last name
{
autocomplete: {
query: userNameCityQueryparam,
path: 'contactlastname',
fuzzy: {
maxEdits: 2,
prefixLength: 3
}
},
}
],
"minimumShouldMatch": 1
}
}
}
]
const response = await User.aggregate(agg);
return res.json(response);
// res.send(response);
} catch (error) {
console.log("autocomplete search error", error);
return res.json([]);
}
};
Index details in mongodb:
{
"mappings": {
"dynamic": false,
"fields": {
"_id": {
"type": "string"
},
"city": {
"type": "autocomplete"
},
"contactfirstname": {
"type": "autocomplete"
},
"contactlastname": {
"type": "autocomplete"
},
"name": {
"type": "autocomplete"
}
}
}
}
Image of collection in mongodb
image of empty array
for anyone looking for solution to this,
You can use the MQL where clause in your pipeline stage definition.
{
$match: {
email:"email#domain.com"
}
}
check here for an example

How to return the a formatted response from a mongo query/projection?

I'm trying to create an API to validate a promocode. I have minimal experience with mongo and the backend in general so I'm a bit confused in what is the best approach to do what I'm trying to accomplish.
I have this PromoCode form in the client. When a user types a promocode I would like for my backend to
verify if the code exists in one of the docs.
if it exists then return that code, the value for that code and the couponId
if the code doesn't exist then return an error.
My db is structured like this. The user will type one of those codes inside the codes: []
{
"_id": {
"$oid": "603f7a3b52e0233dd23bef79"
},
"couponId": "rate50",
"value": 50,
"codes": ["K3D01XJ50", "2PACYFN50", "COKRHEQ50"]
},
{
"_id": {
"$oid": "603f799d52e0233dd23bef78"
},
"couponId": "rate100",
"value": 100,
"codes": ["rdJ2ZMF100", "GKAAYLP100", "B9QZILN100"]
}
My route is structure like this:
router.post('/promoCode', (req, res, next) => {
const { promoCode } = req.body;
console.log('this is the req.body.promoCode on /promoCode', promoCode)
if (!promoCode) {
throw new Error('A promoCode needs to be passed')
}
promoCodesModel
.validatePromoCode(req.body.promoCode)
.then((response) => {
console.log('response inside /promoCode', response)
res.status(200).json({ data: response })
})
.catch((error) => {
res.status(400).json({ result: 'nok', error: error })
})
})
The validatePromoCode function is the following:
const validatePromoCode = async (code) => {
try {
let promoCode = await PromoCodesModel.find(
{"codes": code},
{_id: 0, codes: { $elemMatch: { $eq: code }} })
console.log('This is the promocode', promoCode)
return promoCode
} catch (err) {
throw new Error (err.stack)
}
}
All this seems to sort of work since I get the following response when the code is typed correctly
{
"data": [
{
"codes": [
"COKRHEQ50"
]
}
]
}
when typed incorrectly I get
{
"data": []
}
What I would like to get back is. (How can I accomplish this ?). Thanks
// when typed correctly
{
"data": { value: 50, couponId: "rate50", code: "COKRHEQ50" }
}
// when typed incorrectly
{
"error": "this is not valid code"
}
TL;DR: I would like to return a formatted query with specific values from a mongo query or an error object if that value does not exist on the document object.
Ok just figured it out
To be able to get the this responsed (what I wanted):
{
"data": [
{
"codes": [
"K3D01XJ50"
],
"couponId": "rate50",
"value": 50
}
]
}
I ended up having to do this on validatePromoCode
onst validatePromoCode = async (code) => {
try {
let promoCode = await PromoCodesModel.find(
{ codes: code },
{ _id: 0, codes: { $elemMatch: { $eq: code } }, couponId: 1, value: 1 },
)
return promoCode
} catch (err) {
throw new Error(err.stack)
}
}
But is there a better way on doing this ? Thanks

Mongoose update only fields available in request body

I am trying to update one document using findOneAndUpdate and $set but I clearly missing something very crucial here because the new request is overwriting old values.
My Device schema looks like this:
{
deviceId: {
type: String,
immutable: true,
required: true,
},
version: {
type: String,
required: true,
},
deviceStatus: {
sensors: [
{
sensorId: {
type: String,
enum: ['value1', 'value2', 'value3'],
},
status: { type: Number, min: -1, max: 2 },
},
],
},
}
And I am trying to update the document using this piece of code:
const deviceId = req.params.deviceId;
Device.findOneAndUpdate(
{ deviceId },
{ $set: req.body },
{},
(err, docs) => {
if (err) {
res.send(err);
} else {
res.send({ success: true });
}
}
);
And when I try to send a request from the postman with the body that contains one or multiple sensors, only the last request is saved in the database.
{
"deviceStatus": {
"sensors": [
{
"sensorId": "test",
"status": 1
}
]
}
}
I would like to be able to update values that are already in the database based on req.body or add new ones if needed. Any help will be appreciated.
The documentation said:
The $set operator replaces the value of a field with the specified
value.
You need the $push operator, it appends a specified value to an array.
Having this documents:
[
{
_id: 1,
"array": [
2,
4,
6
]
},
{
_id: 2,
"array": [
1,
3,
5
]
}
]
Using $set operator:
db.collection.update({
_id: 1
},
{
$set: {
array: 10
}
})
Result:
{
"_id": 1,
"array": 10
}
Using $push operator:
db.collection.update({
_id: 1
},
{
$push: {
array: 10
}
})
Result:
{
"_id": 1,
"array": [
2,
4,
6,
10
]
}
you want to using $push and $set in one findOneAndUpdate, that's impossible, I prefer use findById() and process and save() ,so just try
let result = await Device.findById(deviceId )
//implementation business logic on result
await result.save()
If you want to push new sensors every time you make request then update your code as shown below:
const deviceId = req.params.deviceId;
Device.findOneAndUpdate(
{ deviceId },
{
$push: {
"deviceStatus.sensors": { $each: req.body.sensors }
}
},
{},
(err, docs) => {
if (err) {
res.send(err);
} else {
res.send({ success: true });
}
}
);
Update to the old answer:
If you want to update sensors every time you make request then update your code as shown below:
const deviceId = req.params.deviceId;
Device.findOneAndUpdate(
{ "deviceId": deviceId },
{ "deviceStatus": req.body.sensors },
{ upsert: true },
(err, docs) => {
if (err) {
res.send(err);
} else {
res.send({ success: true });
}
}
);

auto-increment using loopback.js and MongoDB

i want to increase mongodb document number automatically using loopback.
I made function in mongo
function getNextSequence(name) {
var ret = db.counters.findAndModify(
{
query: { _id: name },
update: { $inc: { seq: 1 } },
new: true
}
);
return ret.seq;
}
db.tweet.insert(
{
"_id" : getNextSequence("userid"),
"content": "test",
"date": "1",
"ownerUsername": "1",
"ownerId": "1"
}
)
It is working in mongo shell.
However when I insert using loopback.js browser (http://localhost:3000/explorer/), It is not working.
400 error(SytaxError) code is showing.
I can not use mongo function in loopback rest API ?
I think problem is quotes in this line getNextSequence("userid"),
Create a collection counters with properties value and collection
{
"name": "counters",
"base": "PersistedModel",
"idInjection": true,
"options": {
"validateUpsert": true
},
"properties": {
"type": "number",
"collection": "string"
},
"validations": [],
"relations": {},
"acls": [
{
"accessType": "*",
"principalType": "ROLE",
"principalId": "$everyone",
"permission": "ALLOW"
}
],
"methods": []
}
Now supposing your auto-increment collection name tweets.
Insert this value to counters.
{
"value" : 0,
"collection" : "tweet"
}
Now common/models/tweet.js
tweet.observe('before save', function (ctx, next) {
var app = ctx.Model.app;
//Apply this hooks for save operation only..
if(ctx.isNewInstance){
//suppose my datasource name is mongodb
var mongoDb = app.dataSources.mongodb;
var mongoConnector = app.dataSources.mongodb.connector;
mongoConnector.collection("counters").findAndModify({collection: 'tweet'}, [['_id','asc']], {$inc: { value: 1 }}, {new: true}, function(err, sequence) {
if(err) {
throw err;
} else {
// Do what I need to do with new incremented value sequence.value
//Save the tweet id with autoincrement..
ctx.instance.id = sequence.value.value;
next();
} //else
});
} //ctx.isNewInstance
else{
next();
}
}); //Observe before save..
I would love to add 1 more point to Robins Answer,you can add upsert:true so that it automatically creates the document if it doesn't exist
tweet.observe('before save', function (ctx, next) {
var app = ctx.Model.app;
//Apply this hooks for save operation only..
if(ctx.isNewInstance){
//suppose my datasource name is mongodb
var mongoDb = app.dataSources.mongodb;
var mongoConnector = app.dataSources.mongodb.connector;
mongoConnector.collection("counters").findAndModify({collection: 'tweet'}, [['_id','asc']], {$inc: { value: 1 }}, {new: true,upsert:true}, function(err, sequence) {
if(err) {
throw err;
} else {
// Do what I need to do with new incremented value sequence.value
//Save the tweet id with autoincrement..
ctx.instance.id = sequence.value.value;
next();
} //else
});
} //ctx.isNewInstance
else{
next();
}
}); //Observe before save..
You can do something like in this example for loopback 4
let last_record = await this.testRepository.findOne({order: ['id DESC']});
if(last_record) invoice.id = last_record.id+1;
This will generate your model with the property:
#property({
type: 'number',
id: true,
default: 1,
generated: false
})
id: number;
Hopefully, this helps, please write me if there is any other code. Thanks
If you want to use MongoDB operators directly in loopback methods you need to enable the option "allowExtendedOperators", you can do so on a per model basis or at the data source level (will apply to all models using the data source).
datasources.json:
"MongoDs": {
"host": "127.0.0.1",
"port": 27017,
"url": "mongodb://localUser:MYPASSWORD!#127.0.0.1:27017/test-database",
"database": "test-database",
"password": "MYPASSWORD!",
"name": "MongoDs",
"user": "localUser",
"useNewUrlParser": true,
"connector": "mongodb",
"allowExtendedOperators": true
},