I'm building a movie rating system.
After entering the user ID, content ID, and rating registered in the rating document,
It was implemented by updating the rating in the content document.
How can I update the content document while I have already found it like the code below?
router.post('/', authenticate, async (req: IRequest<IRating>, res) => {
try {
const document = await Rating.create({
contentId: req.body.contentId,
contentType: req.body.contentType,
rating: req.body.rating,
review: req.body.review,
userId: req.user?._id,
});
const content = await Content.findOne({
type: req.body.contentType,
_id: document._id,
});
if (content) {
await content.updateOne(
{},
{
average: (content.average + document.rating) / content.count + 1,
count: content.count + 1,
},
);
}
res.json({ success: true, document, content });
} catch (err) {
res.status(404).json({ success: false, message: 'sever error' });
}
});
You can update with pipeline instead of use 2 queries, which for your current code can look like:
await Content.findOneAndUpdate(
{
type: req.body.contentType,
_id: document._id,
},
[{$set: {
average: {$divide: [{$add: ["$content.average", document.rating]},
{$add: ["$content.count", 1]}]},
count: {$add: ["$content.count", 1]}
}}])
But I don't think this is the right way to calculate an average. You should consider multiplying the existing $content.average by $content.count before adding the new document.rating
Related
I am making an API that shows a collection of ads with MongoDB and Node.js
I need to display the list of collection tags in a JSON string.
Example: 'home', 'mobile', 'motor'
This is the API initializer code:
const readline = require('readline');
const Product = require('./models/Product');
async function main() {
const advance = await question('Are you sure to continue with the deletion of the database? (yes or no) ')
if (!advance) {
process.exit();
}
const connection = require('./lib/connectMongoose')
await initProducts();
connection.close();
}
async function initProducts() {
const deleted = await Product.deleteMany();
console.log(`Remove ${deleted.deletedCount} products.`);
const inserted = await Product.insertMany([
{name: 'Table', sale: true, price: 150, photo: 'Table.png', tags: ['home']},
{name: 'Iphone 13 pro', sale: false, price: 950, photo: 'Iphone 13 pro.png', tags: ['mobile']},
{name: 'Car Mini Cooper', sale: true, price: 1000, photo: 'Car Mini Cooper.png', tags: ['motor']}
]);
console.log(`Create ${inserted.length} products.`)
}
main().catch(err => console.log('Hubo un error', err))
function question(text) {
return new Promise((resolve, reject) => {
const interface = readline.createInterface({
input: process.stdin,
output: process.stdout
});
interface.question(text, answer => {
interface.close();
if (answer.toLowerCase() === 'yes') {
resolve(true);
return;
}
resolve(false);
})
})
}
I need to find a MongoDB method that allows me to show when the API route calls the list that shows in JSON format all the tags that the collection includes
If I've understood correctly, one option is $unwind the tags array to get all tags as strings and be able to $group adding to a set to avoid duplicates.
db.collection.aggregate([
{
"$unwind": "$tags"
},
{
"$group": {
"_id": null,
"tags": {
"$addToSet": "$tags"
}
}
}
])
I think this works but $unwind and $group the entire collection is not always a good idea. It may be a slow process.
Example here
I'm trying to make some comprobations on my API (Node.js + Mongodb)
I want to check if the proposerId is equal to the eventOrganizer. To do so
I'm sending this on the body:
{
"participantId": "6238a608170aff10d16ccd89",
"proposerId": "62385d8caee17d13a1762b39", // this guy id is also an organizer
"gender": "female",
"groupId": "623aea21fcfad83bcf8d5bc4"
}
in my PATCH controller to add a user I have this verification:
exports.addParticipantToEvent = async (req, res, next) => {
// Group Organizer constants
const eventId = req.params.eventId;
const groupId = req.body.groupId;
const proposerId = req.body.proposerId; // it can be an admin adding someone to the participants list
// Participant constants
const participantId = req.body.participantId;
const gender = req.body.gender;
// EVENT
// Does the event even exist?
const eventData = await Event.findById(eventId);
if (!eventData) {
return res.status(406).json({
code: 'EVENT_DOESNT_EXIST',
message: 'The event is not valid.',
});
}
console.log(eventData);
// What kind of users can participate in this event?
const allowedParticipants = eventData.allowedParticipants;
// whos the event organizer?
const eventOrganizer = eventData.organizer._id;
console.log('Organizer: ' + eventOrganizer);
console.log('Proposer: ' + proposerId);
console.log('Result: ' + proposerId === eventOrganizer);
try {
return res.status(200).json({
message: 'The participant can be added',
participantId: participantId,
gender: gender,
allowedParticipants: allowedParticipants,
});
} catch (err) {
return res.status(400).json({ message: err });
}
};
I want to verify is the proposerId is an admin or an organizer of the event, so I console.log the eventData entirely and later I console log all the constants I want to verify and the result, it gives me false all the time.
Maybe I need to specify better something?
{
location: { province: 'Barcelona' },
_id: new ObjectId("634ffee75990124926431e6f"),
title: 'Test open close 14',
sport: new ObjectId("622ce6ca672c3d4447676705"),
group: new ObjectId("623aea21fcfad83bcf8d5bc4"),
organizer: new ObjectId("62385d8caee17d13a1762b39"),
participants: [ new ObjectId("62385d8caee17d13a1762b39") ],
replacements: [],
invitations: [],
when: 2022-10-09T13:43:02.999Z,
open: true,
costPerParticipant: 4.4,
skill: 'novice',
allowedGender: 'female',
minParticipants: 2,
maxParticipants: 5,
visible: false,
externalLink: 'https://www.komoot.es/tour/731122050?ref=wta',
allowInvitations: true,
allowReplacements: false,
allowedParticipants: 'only-members',
createdAt: 2022-10-19T13:43:03.006Z,
updatedAt: 2022-10-19T13:43:03.006Z,
__v: 0
}
Organizer: 62385d8caee17d13a1762b39
Proposer: 62385d8caee17d13a1762b39
false
As you can see, both Organizer and proposer are same id, yet I get false.
After lurking some more, I have found that to validate mongos object ids with strings I need to use equals(). So now I have the solution.
I'm building an API to add movies to wishlist. I have an endpoint to get all movies in wishlist. My approach was to get the movie ids (not from mongodb) and make an API request to another API to get the movie objects.
This has been successful so far but the problem now is I am getting two objects fused into one object like below:
{
id: 7,
url: 'https://www.tvmaze.com/shows/7/homeland',
name: 'Homeland',
language: 'English',
genres: [ 'Drama', 'Thriller', 'Espionage' ],
status: 'Ended',
runtime: 60,
averageRuntime: 60,
premiered: '2011-10-02',
officialSite: 'http://www.sho.com/sho/homeland/home',
schedule: { time: '21:00', days: [ 'Sunday' ] },
rating: { average: 8.2 },
image: {
medium: 'https://static.tvmaze.com/uploads/images/medium_portrait/230/575652.jpg',
original: 'https://static.tvmaze.com/uploads/images/original_untouched/230/575652.jpg'
},
summary: '<p>The winner of 6 Emmy Awards including Outstanding Drama Series, <b>Homeland</b> is an edge-of-your-seat sensation. Marine Sergeant Nicholas Brody is both a decorated hero and a serious threat. CIA officer Carrie Mathison is tops in her field despite being bipolar. The delicate dance these two complex characters perform, built on lies, suspicion, and desire, is at the heart of this gripping, emotional thriller in which nothing short of the fate of our nation is at stake.</p>',
}
This is the second object below. Notice how there's no comma separating both objects
{
id: 1,
url: 'https://www.tvmaze.com/shows/1/under-the-dome',
name: 'Under the Dome',
language: 'English',
genres: [ 'Drama', 'Science-Fiction', 'Thriller' ],
status: 'Ended',
runtime: 60,
averageRuntime: 60,
premiered: '2013-06-24',
schedule: { time: '22:00', days: [ 'Thursday' ] },
rating: { average: 6.6 },
image: {
medium: 'https://static.tvmaze.com/uploads/images/medium_portrait/81/202627.jpg',
original: 'https://static.tvmaze.com/uploads/images/original_untouched/81/202627.jpg'
},
summary: "<p><b>Under the Dome</b> is the story of a small town that is suddenly and inexplicably sealed off from the rest of the world by an enormous transparent dome. The town's inhabitants must deal with surviving the post-apocalyptic conditions while searching for answers about the dome, where it came from and if and when it will go away.</p>",
}
My question now is how do I convert both objects to an array and send as a response from my own API. API code is below:
module.exports = {
fetchAll: async (req, res, next) => {
var idsArr = [];
var showsArr;
var shows;
try {
let wishlist = await Wishlist.find({});
if (wishlist == null) {
res.status(404)
.json({
success: false,
msg: 'No Movies Found in Wishlist',
wishlist: []
})
}
// console.log(wishlist);
wishlist.map((item) => {
idsArr.push(item.id);
})
console.log(idsArr);
idsArr.map(async (id) => {
shows = await axios.get(`https://api.tvmaze.com/shows/${id}`);
console.log(shows.data);
// console.log(showsArr);
// showsArr = [shows.data];
})
console.log(showsArr);
return res.status(200)
.json({
success: true,
msg: 'All Movies in Wishlist Fetched',
wishlist: showsArr
})
} catch (err) {
console.log(err);
next(err);
}
},
... // other methods
}
I have tried creating an empty array. shows.data which is the actual response and then I've tried adding it to my array using showsArr.push(shows.data) previously without much success. I get undefined when I log to console.
Here the ids range from 1 to 240+, in case one wants to try out the endpoint - https://api.tvmaze.com/shows/${id}
How would I go about achieving this? Thanks.
Just like when converting the wishlist array to an array of ids, you would need to push the data items into your new showsArr.
However, this doesn't actually work, since it's asynchronous - you also need to wait for them, using Promise.all on an array of promises. And you actually shouldn't be using push at all with map, a map call already creates an array containing the callback return values for you. So you can simplify the code to
module.exports = {
async fetchAll(req, res, next) {
try {
const wishlist = await Wishlist.find({});
if (wishlist == null) {
res.status(404)
.json({
success: false,
msg: 'No Movies Found in Wishlist',
wishlist: []
})
}
const idsArr = wishlist.map((item) => {
// ^^^^^^^^^^^^^^
return item.id;
// ^^^^^^
});
console.log(idsArr);
const promisesArr = idsArr.map(async (id) => {
const show = await axios.get(`https://api.tvmaze.com/shows/${id}`);
console.log(shows.data);
return shows.data;
// ^^^^^^^^^^^^^^^^^^
});
const showsArr = await Promise.all(promisesArr);
// ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
console.log(showsArr);
return res.status(200)
.json({
success: true,
msg: 'All Movies in Wishlist Fetched',
wishlist: showsArr
})
} catch (err) {
console.log(err);
next(err);
}
}
};
I have 2 issues
FIRST ONE:
I am trying to make review schema that a user should add 1 review per bootcamp
Code:
ReviewSchema.index({ bootcamp: 1, user: 1 }, { unique: true });
It doesnt work .. and the user still can add more than one review
SECOND ISSUE:
I am trying to calculate the averagerating of reviews but it doesn`t get added to the db when am fetching the bootcamps
Code:
// Static Method to get the avg rating of reviews and save
ReviewSchema.statics.getAverageRating = async function (bootcampId) {
const obj = await this.aggregate([
{
$match: { bootcamp: bootcampId },
},
{
$group: {
_id: '$bootcamp',
averageRating: { $avg: '$rating' },
},
},
]);
try {
await this.model('Bootcamp').findByIdAndUpdate(bootcampId, {
averageRating: obj[0].averageRating,
});
} catch (err) {
console.log(err);
}
//Call averageRating after save
ReviewSchema.post('save', async function () {
await this.constructor.getAverageRating(this.bootcamp);
});
//Call averageRating before remove
ReviewSchema.pre('remove', async function () {
await this.constructor.getAverageRating(this.bootcamp);
});
** It doesnt work and the averagerating never gets added to the database (as a bootcamp`s field)**
I Did the same as the tutorial and it didn`t work at the first but then i figured out that missing a semi-colon.
I'm making app with MEAN stack and I want on every get request to increase viewCounter on specific document ( Property ) inside collection.
If i put this code inside get request of requested property
Property.findByIdAndUpdate('id', { $inc: { counter: 1 } }, {new: true})
It will increase loading of data and i want to do that after user gets his data.
So is the best way to do this just to send additional request to the database after initial data is loaded ?
Property {
name: '',
description: '',
...,
viewCounter: 5
}
exports.getProperty = catchAsync(async (req, res, next) => {
query = await Property.findById(req.params.id).lean();
if(!query) {
return next(new AppError('No property found with that ID', 404))
}
res.status(200).json({
status: 'success',
data: {
query
}
})
})
Node events can be used to keep the counter of events.
Official document
Reference for code
eventEmitter.on('db_view', ({ parameters }) => {
eventTracker.track(
'db_view',
parameters
);
})
eventEmitter.on('db_view', async ({ user, company }) => {
Property.findByIdAndUpdate('id', { $inc: { counter: 1 } }, {new: true})
})
Try to send request after making sure your document has loaded.
angular.element($window).bind('load', function() {
//put your code
});