Sequelize js reformat nested response - node.js

I am using PostgreSQL as database and Sequelize JS to Query my database. One of the APIs has below code
var video_attributes = [[sequelize.cast(sequelize.fn("like_count", Sequelize.col("video_id")), 'integer'), "total_likes"]];
if (request.user) {
video_attributes.push([sequelize.fn("has_liked", request.user.id, Sequelize.col("user_bookmark.video_id")), "is_liked"]);
video_attributes.push([sequelize.fn("has_bookmarked", request.user.id, Sequelize.col("user_bookmark.video_id")), "is_bookmarked"]);
}
mod_idx.user_bookmark.findAll({
where: {"user_id": request.user.id},
include : [{
model:mod_idx.video, as: "video",
attributes: {include: video_attributes}
}],
attributes: {exclude: ["user_id", "video_id", "id"]},
order: [[
"id",
"desc"
]],
}).then(video_list => {
let r = { "data": video_list, "success": true }
response.status(200).json(r)
}).catch(function (err) {
console.log(err)
});
It returns below response:
{
"data": [
{
"video": {
"id": 189,
"total_likes": 0,
"is_liked": false,
"is_bookmarked": true
}
},
{
"video": {
"id": 261,
"total_likes": 0,
"is_liked": false,
"is_bookmarked": true
}
}
],
"success": true
}
Expected result:
{
"data": [
{
"id": 189,
"total_likes": 0,
"is_liked": false,
"is_bookmarked": true
},
{
"id": 261,
"total_likes": 0,
"is_liked": false,
"is_bookmarked": true
}
],
"success": true
}
I tried by making "raw" as true but it returns column names as 'video.column_name' (https://stackoverflow.com/a/53612698/1030951).
I don't want to use map function as it may slowdown while processing large number of records.

It is not pretty but you can do this by adding all attributes at top level.
mod_idx.user_bookmark.findAll({
where: {"user_id": request.user.id},
include : [{
model:mod_idx.video, as: "video",
attributes: [] // Do not include anything in nested object
}],
attributes: [
[sequelize.cast(sequelize.fn("like_count", Sequelize.col("video.video_id")), 'integer'), "total_likes"]
// Add more here
],
order: [[
"id",
"desc"
]],
})
================================================================
Update
You can do something like this. One thing to consider here is that you must avoid the column name conflicts across multiple tables (as you are putting all attributes at top level), so add exclude or rename to make sure you handle the conflicts.
I demonstrate here for a scenario that I want to include all video attributes except certain columns to avoid conflicts.
const videoExcludeList = ['createdAt', 'updatedAt'];
// Get all video attributes minus exclude list.
const videoAttrs = Object.keys(mod_idx.video.rawAttributes)
.filter((v) => !videoExcludeList.includes(v))
.map((v) => Sequelize.col(`video.${v}`));
// Custom attributes that uses fn/rename/etc
const customAttrs = [
[sequelize.cast(sequelize.fn("like_count", Sequelize.col("video.video_id")), 'integer'), "total_likes"],
// Add more here.
]
You can also combine with top-level exclude to exclude columns from user_bookmark.
mod_idx.user_bookmark.findAll({
where: {"user_id": request.user.id},
include : [{
model:mod_idx.video, as: "video",
attributes: [] // Do not include anything in nested object
}],
attributes: {
exclude: ['id'], // exclude these columns from user_bookmark
include: [...videoAttrs, ...customAttrs],
},
order: [[
"id",
"desc"
]],
raw: true // You need this.
})

Related

Mongoose populate 3 deep nested schema with JSON response

I have a find() query that when executed, I can see the json with the nested schemas that I want to see except for the 'artista' attribute only displays the id, instead of the properties I want. See below:
{
"total": 1,
"ordenes": [
{
"artpieces": [
{
"_id": "60c1388f30316c02b9f6351f",
"artista": "60c055736c7ca511055a0e1a",
"nombre": "LILIES"
},
{
"_id": "60c12fca30316c02b9f63519",
"nombre": "GERNICA",
"artista": "60c136bf30316c02b9f6351b"
}
],
"_id": "60c414f9ea108a14ef75a9fb",
"precio": 3000,
"usuario": {
"_id": "609c0068e67e68",
"nombre": "Arturo Filio"
}
}
]
}
The query I use to get the json above:
const [total, ordenes] = await Promise.all([
Orden.countDocuments(),
Orden.find()
.populate("usuario", "nombre")
.populate("artpieces", ["nombre","artista","nombre"])
]);
res.json({
total,
ordenes
});
It's an order schema that has artpieces. Each artpiece (I called it 'producto'), has a name a genre, an author/artist and the user which the order belongs to.
my Schema for the orden.js:
const { Schema, model } = require('mongoose');
const OrdenSchema = Schema({
artpieces: [
{
type: Schema.Types.ObjectId,
required: true,
ref: 'Producto'
}
],
estado: {
type: Boolean,
default: true,
required: true
},
usuario: {
type: Schema.Types.ObjectId,
ref: 'Usuario',
required: true
},
precio: {
type: Number,
required: true
}
})
OrdenSchema.methods.toJSON = function () {
const { __v, estado, ...data} = this.toObject();
return data;
}
module.exports = model('Orden', OrdenSchema);
Last thing I want to mention, I know for a fact that I have the code necessary in the artista.js model to display the name of the artist because I have a similar query to display all the artpieces with each artpiece have a genre and an artist.
That example looks like so (to give context):
{
"total": 4,
"productos": [
{
"precio": 0,
"_id": "60c12fca30316c02b9f63519",
"nombre": "GERNICA",
"categoria": {
"_id": "60c04e3605d3c10ed10389e4",
"nombre": "NEO CUBISMO"
},
"artista": {
"_id": "60c136bf30316c02b9f6351b",
"nombre": "PICASSO"
},
"usuario": {
"_id": "609c8c0068e67e68",
"nombre": "Arturo Filio"
}
}
]
}
What am I doing wrong that I can't get my json result at the top look like the json at the bottom, where the artist attribute is?
Also just to point out, I have checked how to nest populate methods in order SO posts including the path and the ref to the Schema and still haven't been able to get the expected result.

How to resolve promiss.all() and want to remove a level of object

I want to merge the result of two sequelize data. So I just created instances of two models and then resolve them by using promise.All() at once. I did the following to achieve the same:
let screensP = AppEvent.findAll({
attributes : [
'id',
'title'
],
});
let eventsP = AppScreen.findAll({
attributes : [
'id',
'title'
],
});
exports.settings = function(req, res, next){
Promise.all([{'screens' : screensP, 'events' : eventsP}])
.then((values) => {
res.json({
'status': 200,
'data': values,
});
});
}
But the response includes the promise object like below
{
"status": 500,
"data": [
{
"screens": {
"isFulfilled": true,
"isRejected": false,
"fulfillmentValue": [
{
"id": 1,
"title": "click"
},
{
"id": 2,
"title": "comment"
}
]
},
"events": {
"isFulfilled": true,
"isRejected": false,
"fulfillmentValue": []
}
}
]
}
I want the result something like this
{
"status": 200,
"events": [
{
"id": 1,
"title": "dashboard"
},
{
"id": 2,
"title": "profile"
},
{
"id": 3,
"title": "book"
}
],
"screens": []
}
What should I do to get the above response?
For the call to Promise.all() you are passing in an array that contains one element that is an object with two properties, each of them a promise. which is what you are seeing in your results (the isFulfilled and other properties). To resolve the promises you need to pass in a array of promises, not an object.
This example uses async/await which is easier to read.
exports.settings = async function(req, res) {
// you can deconstruct the returned elements when the promise is resolved
const [ screens, events ] = await Promise.all([
// returns a promise that will resolve "events"
AppEvent.findAll({
attributes : [
'id',
'title'
],
// if you don't need to parse to Instance objects, adding raw:true will be faster
// raw: true,
}),
// returns a promise that will resolve "screens"
AppScreen.findAll({
attributes : [
'id',
'title'
],
// if you don't need to parse to Instance objects, adding raw:true will be faster
})
]);
// return the results
return res.json({
'status': 200,
'data': {
screens,
events
},
});
};
The same example using thenables:
exports.settings = function(req, res) {
// you can deconstruct the returned elements when the promise is resolved
return Promise.all([
// returns a promise that will resolve "events"
AppEvent.findAll({
attributes : [
'id',
'title'
],
}),
// returns a promise that will resolve "screens"
AppScreen.findAll({
attributes : [
'id',
'title'
],
})
])
.then(([ screens, events ]) => {
// return the results
return res.json({
'status': 200,
'data': {
screens,
events
},
});
});
};
res.json({
'status': 200,
...values,
});

Nested inside nested Objects with same key aggregation with another collection in mongoDB

Here I am placing example collections and expected output. I have nested modules array of objects
When I get request from the api as student Id, course id & module id then I have to send module % if exists if not need to send % as 0
Course collection
{
"_id": "courseId1",
"courseName": "course1",
"isActive": true,
"modules": [
{
"_id":"id1",
"name":"mod1",
"isActive": true
},
{
"_id":"id2",
"name":"mod2",
"isActive": true
},
{
"_id":"id3",
"name":"mod3",
"isActive": true
"modules":[
{
"_id":"id4",
"name":"mod4",
"isActive": true
},
{
"_id":"id5",
"name":"mod5",
"isActive": true,
"modules":[
{
"_id":"id6",
"name":"mod6",
"isActive": true
}
]
}
]
}
]
}
Course activity collection
{
"id":"ca1",
"studentId:"std1",
"courseId:"courseId1",
mProgress:[{
"id":"ac1",
"modId":"id5",
"studentID":"std1",
"progress":20
}
{
"id":"ac2",
"modId":"id4",
"studentID":"std1",
"progress":10
}
]
}
If I get studentID="std1" , courseId="5f698ca6f5cd3551060d86e8" , moduleId="id3"
I need response link below
modules:
[
{
"modId":"id4",
"name:"mod4",
"progress":10
},
{
"modId":"id5",
"name:"mod5",
"progress":0
}
]
I achieved this by java script. 1st I found course object using mongo query after that I search for nested object(i.e modules array) by using the following function
findNestedObj(keyToFind, valToFind,obj ) {
let foundObj;
JSON.stringify(obj , (_, nestedValue) => {
if (nestedValue && nestedValue[keyToFind] === valToFind) {
foundObj = nestedValue;
}
return nestedValue;
});
return foundObj;
}
var obj = 'here your object';
findNestedObj('_id','id3',obj );
Then I have calculated % from that

Sequelize ORM return a weird response after inner join tables in nodejs

I use sequelize orm to manage my data base (mysql).
I make a inner join that work good but the problem that the table that join return a weird variable.
this is my code
const getReports = id => {
return new Promise((resolve, reject) => {
models.Report.findAll({
where: { companyID: [513603324, 515490704, 511493827] },
include: [{
model: models.Reports_type,
attributes:["name"],
required: true
}],
raw: true
})
.then(result => {
resolve(result);
})
.catch(err => {
reject(err);
});
});
};
The output is
[
{
"id": 8,
"creatorUserID": 1,
"currentUserEditorID": 1,
"companyID": 511493827,
"stageID": 1,
"scenarioID": 1,
"typeID": 1,
"year": 2020,
"deadLine": "2019-10-30T22:00:00.000Z",
"createdAt": "2019-10-29T08:31:19.000Z",
"updatedAt": "2019-10-29T08:31:19.000Z",
"Reports_type.name": "excelent",
"companyName": "energy",
}
]
The problem is i get it weird like this:
"Reports_type.name"
I want the output be:
"name"
This topic has been covered before - see this.
To avoid the prefix, attributes must be specified in the main model rather than the included model. The example below should produce all fields in Report plus Reports_type.name. Note: the alias of Reports_type may be a little different than I've guessed - if you get a "field does not exist", find the correct alias from the generated SQL.
models.Report.findAll({
where: { companyID: [513603324, 515490704, 511493827] },
include: [{
model: models.Reports_type,
attributes:[], // suppress here
required: true
}],
raw: true,
attributes: {
include: [[Sequelize.col("reports_types.name"), "name"]] // include here; table alias may be a little different!
}
})

How to get data from different schema in Nodejs

I have two schemas called employees (parent) and assessments(child)
Every assessment will have a pass percentage of employee id
so I have results like this
employees : [
{
"_id": 12345,
"name": "David",
"evaluated": false
},
{
"_id": 12346,
"name": "Miller",
"evaluated": false
}
]
Second Schema
assessments: [
{
"assessment_type": "basic",
"employee_id": 12345,
"qualified": true
},
{
"assessment_type": "advanced",
"employee_id": 12345,
"qualified": false
},
{
"assessment_type": "basic",
"employee_id": 12346,
"qualified": true
},
{
"assessment_type": "advanced",
"employee_id": 12346,
"qualified": true
}
]
So I want to get the employees with evaluated based on assessments qualified is true
can you please tell me what is the best approach for this?
Here is an example where we sort the employees by the assements they succeeded.
const employees = [{
_id: 12345,
name: 'David',
evaluated: false,
}, {
_id: 12346,
name: 'Miller',
evaluated: false,
}];
const assessments = [{
assessment_type: 'basic',
employee_id: 12345,
qualified: true,
}, {
assessment_type: 'advanced',
employee_id: 12345,
qualified: false,
}, {
assessment_type: 'basic',
employee_id: 12346,
qualified: true,
}, {
assessment_type: 'advanced',
employee_id: 12346,
qualified: true,
}];
// Loop at the employees
const sortByAssessment = employees.reduce((tmp, x) => {
// Get all the assessment about the employee
const employeeAssessment = assessments.filter(y => y.employee_id === x._id);
// Deal with each assessment
employeeAssessment.forEach((y) => {
// Only do something about successfull assessments
if (y.qualified) {
// In case this is the first time we are dealing with the assessment_type
// create an array where we are going to insert employees informations
tmp[y.assessment_type] = tmp[y.assessment_type] || [];
// Push the name of the employee inside of the assessment type array
tmp[y.assessment_type].push(x.name);
}
});
return tmp;
}, {});
console.log(sortByAssessment);
you can do 2 things join with $look up or populate with employee id
assessments.aggregate([
{
'$lookup': {
'from': 'employees',
'localField': 'employee_id',
'foreignField': '_id',
'as': 'datas'
}
},
{ "$unwind": "$datas" },
].exec(function(err,result){
console.log(result)
});
2nd way
//assessments your model name
assessments.populate('employee_id').exec(function(err,result){
console.log(result);
});

Resources