Mongoose find by a subdocument's value - javascript

I have 2 schemas
const schema = Schema({
headLine: {
type: String,
required: false
},
availableDays: [{
type: Schema.Types.ObjectId,
ref: AvailableDay
}]
}, {collection: 'providers', timestamps: true});
module.exports = mongoose.model("Provider", schema);
const schema = Schema({
day: {
type: String,
enum: ['Mondays','Tuesdays','Wednesdays','Thursdays','Fridays','Saturdays','Sundays']
},
timeFrom: String,
timeTo: String
}, {collection: 'availableDays', timestamps: true});
module.exports = mongoose.model("AvailableDay", schema);
Then in a route I call to a repository like this
router.get('/', async (req, res) => {
const match = {};
const sort = {};
const options = {};
// Arrange sort
if(req.query.sortBy){
const sortArray = JSON.parse(req.query.sortBy);
sortArray.map(e => sort[e[0]] = e[1] && e[1] === 'desc' ? -1 : 1);
options['sort'] = sort
}
// Get the pagination: limit how many, skip where it starts
if(req.query.limit) {
options['limit'] = parseInt(req.query.limit);
}
if(req.query.skip) {
options['skip'] = parseInt(req.query.skip);
}
const docs = await ProviderRepository.findBy(match, {}, options);
res.status(200).json(docs)
});
So what I need here is to filter providers for an AvailableDay monday and return the docs and count the total docs for pagination. I'm doing something like this without success
const findBy = async (params, projection = "", options = {}, callback) => {
const data = () => {
Provider.find(params, projection, options)
.populate([{path: 'user', match: {gender: 'F'}}]).exec((error, e) => {
if (error) {
console.log('error:', error)
return {error: error}; // returns error in json
}
return e.filter(i => i.user);
});
};
const total = await Provider.countDocuments(params).exec();
return {data(), total}
}
Thanks in advance

Use mongoose-aggregate-paginate-v2 and update your schema. If you use that package then you have to convert your queries from populate to aggregate style.
STEP 1: Update schema. Sample Schema:
const mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-aggregate-paginate-v2');
const Schema = mongoose.Schema;
let definition = {
headLine: {
type: String,
required: false
},
availableDays: [{
type: Schema.Types.ObjectId,
ref: AvailableDay
}]
};
let options = {
collection: 'providers'
};
let providerSchema = new Schema(definition, options);
providerSchema.plugin(mongoosePaginate);
module.exports = mongoose.model('providers', providerSchema);
STEP 2: Update controller. Sample code in controller:
router.get('/', async (req, res) => {
const match = {}
const sort = {
// Fill it based on your sort logic.
}
const paginateOptions = {
page: req.query.page, // Page number like: 1, 2, 3...
limit: req.query.limit // Limit like: 10, 15, 20...
};
ProviderRepository
.findBy(match, {}, sort, paginateOptions)
.then(() => {
res.status(200).json(docs)
})
.catch(() => {
res.status(HTTP_ERROR_CODE).json({ "error": "Your error message" })
})
});
STEP 3: Update manager. Sample code in manager:
const findBy = (match, projection, sort, paginateOptions) => {
if (!paginateOptions) {
paginateOptions = {
pagination: false
};
}
let providerAggregate = providerSchema.aggregate([
{
$lookup: {
from: "availableDays",
let: { days: "$availableDays" },
pipeline: [
{
$match: {
$expr: {
$in: ["$$availableDays", "$day"]
}
}
}
],
as: "availableDays"
}
},
{
$lookup: {
from: "users", // I dont know the collection name
let: { user_id: "$user" }
pipeline: [
{
$match: {
"gender": 'F',
$expr: {
$eq: ["$_id", "$$user_id"]
}
}
}
],
as: "users"
}
}
{ $sort: sort }
]);
return providerSchema
.aggregatePaginate(providerAggregate, paginateOptions)
.then(res => {
return res;
})
.catch(err => {
throw err;
});
};

Related

Prisma find many and count in one request

I have a pagination in my category service, and I have to return obj with total count of categories and data
But there's can be some parameters. As example, I should return categories that was created by certain user:
async findAll(
{ onlyParents }: ParamsCategoryDto,
user: ITokenPayload | undefined,
): Promise<IFilterRes> {
const categories = await this.prisma.category.findMany({
where: {
user_id: user?.id,
},
});
return {
pagination: {
total: this.prisma.category.count({
where: { // <- duplicate
user_id: user?.id,
},
}),
},
data: categories,
};
}
I should duplicate where in both query. Which is not very nice. Is there any option to do it in one request.
P.S. I can make some var for where, but in this way I lose typification, which I also don't like.
This is my example code to acheive it with a single transaction, no duplicate code and not losing type autocomplete
import { Prisma } from '#prisma/client';
import { PrismaClient } from '#prisma/client'
const prisma = new PrismaClient()
const findAll = async (userId: String) => {
const query: Prisma.categoriesFindManyArgs = {
where: {
user_id: userId,
}
};
const [categories, count] = prisma.$transaction([
prisma.categories.findMany(query),
prisma.categories.count({ where: query.where })
]);
return {
pagination: {
total: count
},
data: categories
};
};

Got error when I type special symbol in input

When I type ";/" in search input I will get this error:
Unhandled Runtime Error TypeError: invitees.filter is not a function
Here is my following code in front-end:
const { tab, teamId, privateTeamId, fetchTeamData } = props;
const [searchQuery, setSearchQuery] = useState("");
const [invitees, setInvitees] = useState([]);
const handleChange = (event) => {
event.preventDefault();
setSearchQuery(event.target.value);
};
const getUserToInvite = async () => {
const res = await axios.get(
`/api/v1/search/users/invite/${searchQuery}/${teamId}`
);
setInvitees(res.data[0]);
setShowInvitees(!showInvitees);
};
<>
{invitees
?.filter(
(user) =>
user.Memberships.length < 1 ||
(user.Memberships.every(
(member) => member.teamId !== privateTeamId
) &&
user.InvitesApplications.response !== "Waiting on response")
)
.sort(
(a, b) =>
new Date(a.InvitesApplications[0]?.createdAt) -
new Date(b.InvitesApplications[0]?.createdAt)
)
...
</>
and here is my following code in searchController in back-end:
exports.searchUserToInvite = async (req, res) => {
// Grab query
const query = req.params.q;
// Search for users
const usersFound = await models.User.findAll({
where: {
[Op.or]: [
{
fullname: {
[Op.iLike]: query + "%",
},
},
],
},
attributes: [
"id",
"fullname",
"public_user_id",
"institution",
"location",
"webpage",
"linkedin",
"major",
"picture",
"verifiedDT",
],
include: [
{
model: models.Rating,
attributes: ["skillset_rating", "team_member_rating"],
},
{
model: models.Skill,
attributes: ["skill"],
},
{
model: models.Membership,
attributes: ["teamId"],
},
{
model: models.SubMembership,
attributes: ["subTeamId"],
},
{
model: models.InvitesApplications,
attributes: [
"id",
"response",
"teamId",
"subTeamId",
"createdAt",
"updatedAt",
],
},
],
});
// Run searches
const searchData = await Promise.all([usersFound]);
// Return results
if (query.length <= 0) {
return res.status(200).json([]);
}
res.status(200).json(searchData);
};
How can I fix this error? Is this because my backend code is wrong or I need to improve my front end code?
Putting your search query as a path parameter seems quite odd but your problem is that you are not encoding the value correctly for use in an URL.
Run the values through encodeURIComponent()...
const res = await axios.get(
`/api/v1/search/users/invite/${encodeURIComponent(searchQuery)}/${encodeURIComponent(teamId)}`
);
IMO, search parameters are best handled through query parameters which Axios encodes correctly automatically
const res = await axios.get(url, {
params: {
q: searchQuery
}
})
On the server-side, you would read this through req.query.q

Mongoose: How to update an existing element in array?

I was wondering if there is a better way to update an existing element in an array instead of fetching database three times. If you have any ideas I would appreciate it. Thank you!
const creatStock = async (symbol, webApiData) => {
try {
// reversed array
const webApiDataReversed = webApiData.reverse();
const query = { symbol };
const update = { $addToSet: { data: webApiDataReversed } };
const options = { upsert: true, new: true };
// create/update Stock
const stockResult = await Stock.findOneAndUpdate(query, update, options);
const lastElement = stockResult.data.length - 1;
const updatePull = {
$pull: { data: { date: stockResult.data[lastElement].date } },
};
// removes last date from data array
await Stock.findOneAndUpdate(query, updatePull);
// update Stock
await Stock.findOneAndUpdate(query, update);
} catch (ex) {
console.log(`creatStock error: ${ex}`.red);
}
};
Schema
const ChildSchemaData = new mongoose.Schema({
_id: false,
date: { type: mongoose.Types.Decimal128 },
open: { type: mongoose.Types.Decimal128 },
high: { type: mongoose.Types.Decimal128 },
low: { type: mongoose.Types.Decimal128 },
close: { type: mongoose.Types.Decimal128 },
volume: { type: mongoose.Types.Decimal128 },
});
const ParentSchemaSymbol = new mongoose.Schema({
symbol: {
type: String,
unique: true,
},
// Array of subdocuments
data: [ChildSchemaData],
});
module.exports.Stock = mongoose.model('Stock', ParentSchemaSymbol);
Output
Well, if you don't need to return the updated document, Please try this one - this will just return a write result, with this things can be achieved in one DB call :
const creatStock = async (symbol, webApiData) => {
try {
// reversed array
const webApiDataReversed = webApiData.reverse();
const query = { symbol };
await Stock.bulkWrite([
{
updateOne:
{
"filter": query,
"update": { $pop: { data: 1 } }
}
}, {
updateOne:
{
"filter": query,
"update": {
$addToSet: {
data: webApiDataReversed
}
}
}
}
])
} catch (ex) {
console.log(`creatStock error: ${ex}`.red);
}
};
Ref : mongoDB bulkWrite
you can do like this way :
const creatStock = async (symbol, webApiData) => {
try {
// reversed array
const webApiDataReversed = webApiData.reverse();
const query = { symbol };
let stock = await Stock.findOne(query);
if(stock){
let stockData = JSON.parse(JSON.stringify(stock.data));
if(stockData.length>0){
stockData.pop();
}
stockData.concat(webApiDataReversed);
stock.data = stockData;
await stock.save();
}
} catch (ex) {
console.log(`creatStock error: ${ex}`.red);
}

Update fields in object with mongoose in mongodb

I have a simple collection in mongodb.
I use mongoose.
I have users model with one field type object.
And I want change this object dynamically. But this code doesn't work, I used findByIdAndUpdate(), findById, findOne(), findOneAndUpdate().
const UsersSchema = mongoose.Schema({
likes: {}
},
{ collection: 'users' });
const Users = mongoose.model('Users', UsersSchema);
const id ="5b4c540f14f353a4b9875af4";
const thems = ['foo', 'bar'];
Users.findById(id, (err, res) => {
thems.map(item => {
if (res.like[item]) {
res.like[item] = res.like[item] + 1;
} else {
res.like[item] = 1;
}
});
res.save();
});
I believe that, for solve this problem you need to add more fields in your schema:
I created one example with this data:
const UsersSchema = new mongoose.Schema({
likes :[
{
thema:{
type: String
},
likes_amount:{
type: Number
},
_id:false
}]
});
module.exports = mongoose.model('Users', UsersSchema);
I added one user:
var newUser = new UserModel({
likes:[{
thema:'foo',
likes_amount:1
}]
});
newUser.save();
Here the code that increment the likes per thema:
const thems = ['foo', 'bar'];
const userId = "5b4d0b1a1ce6ac3153850b6a";
UserModel.findOne({_id:userId})
.then((result) => {
var userThemas = result.likes.map(item => {
return item.thema;
});
for (var i = 0; i < thems.length; i++) {
//if exists it will increment 1 like
if (userThemas.includes(thems[i])) {
UserModel.update({_id: result._id, "likes.thema" : thems[i]}, {$inc: {"likes.$.likes_amount": 1}})
.then((result) => {
console.log(result);
}).catch((err) => {
console.log(err)
});
} else {
//if doesn't exist it will create a thema with 1 like
UserModel.update({_id: result._id},
{
$addToSet: {
likes: {
$each: [{thema: thems[i], likes_amount: 1}]
}
}})
.then((result) => {
console.log(result);
}).catch((err) => {
console.log(err)
});
}
}
}).catch((err) => {
console.log(err)
});
Database result of this increment:
I hope that it can help you.

Promise in cron job

I try to run the code without success.
Only the first call works in start of node.
Here is my current code:
const Product = require('../models/Product');
const Price = require('../models/Price');
const cron = require('node-cron');
const amazon = require('amazon-product-api');
const util = require('util');
const _ = require('underscore')
/**
* Cron job
* Tracking price
*/
exports.track = () => {
cron.schedule('* * * * *', () => {
const client = amazon.createClient({
awsId: process.env.AWS_ID,
awsSecret: process.env.AWS_SECRET,
assocId: process.env.AWS_TAG
});
Promise.all([Product.getAsin()])
.then(([asin]) => {
let listId = _.pluck(asin, '_id');
let listAsin = _.pluck(asin, 'asin');
if (asin.length === 0) {
return
}
client.itemLookup({
idType: 'ASIN',
itemId: listAsin,
domain: 'webservices.amazon.fr',
responseGroup: 'ItemAttributes,OfferFull,SalesRank'
}).then((results) => {
for(i=0; i<listId.length; i++){
results[i].id = listId[i];
}
for(res of results) {
Price.addPrice({
asin: res.ASIN[0],
product: res.id,
salePrice: res.Offers[0].Offer[0].OfferListing[0].Price[0].Amount[0],
})
}
console.log(listId);
Product.makeUpdate(listId);
}).catch(function(err) {
console.log(err);
console.log(util.inspect(err, true, null));
});
})
.catch((err) => {
console.log(err);
})
})
}
Requests to MongoDB are asynchronous.
Product
const mongoose = require('mongoose');
mongoose.Promise = Promise;
const _ = require('underscore');
const moment = require('moment');
const productSchema = new mongoose.Schema({
name: String,
domain: String,
originUrl: { type: String, unique: true },
check: { type: Number, default: 0 },
ean: String,
asin: String
}, { timestamps: true });
Object.assign(productSchema.statics, {
getAsin() {
return this.find(
{ updatedAt: { $lt: oneMin },
asin: { $ne: null }
}
).limit(10)
.select({ asin: 1 })
.exec()//.then((tuples) => _.pluck(tuples, 'asin'))
},
makeUpdate(id) {
console.log('list des ID updated => ' + id);
return this.update({ _id: { $in: id } }, { $inc : { "check": 1 } } , {multi: true}).exec();
}
});
const Product = mongoose.model('Product', productSchema);
module.exports = Product;
const oneMin = moment().subtract(1, 'minutes').format();
Also, since I'm absolutely new to JavaScript and Node.js in general, any best practices or general tips will be greatly appreciated! :)

Categories

Resources