Sequelize CreateOrUpdate Function - javascript

I am using node version v10.15.3 and "sequelize": "^4.22.8". When using bulkCreate I am getting double values in my db:
My model looks like the following:
module.exports = (sequelize, DataTypes) => {
const Company = sequelize.define('Company', {
name: DataTypes.STRING,
url: DataTypes.STRING,
symbol: DataTypes.STRING,
description: DataTypes.STRING,
}, {})
Company.associate = function(models) {
Company.hasMany(models.Rating)
};
return Company
};
I have created a custom createOrUpdateCompany() function. Find below my minimum executable example:
const models = require('./src/models');
const Company = require('./src/models').Company;
async function getAllCompanies() {
try {
let res = await Company.findAll()
return res;
} catch (error) {
console.log(error)
}
}
async function createOrUpdateCompany(dataArray) {
if (dataArray === undefined || dataArray.length === 0) return ''
let iss = []
const allCompanies = await getAllCompanies()
// flatten array
dataArray = [].concat.apply([], dataArray)
if (allCompanies !== undefined) {
// 1. remove exact dedupes from dataArray
dataArray = [...new Map(dataArray.map(obj => [JSON.stringify(obj), obj])).values()]
// 2. compare dataArray to allCompanies and remove difference
// dataArray = dataArray.filter(cv => !allCompanies.find(e => e.symbol === cv.symbol))
dataArray = dataArray.filter(cv => !allCompanies.find(e => e.symbol === cv.symbol))
// 3. Remove null values for link and "" values for name
dataArray = dataArray.filter(cv => !(cv.name === '' || cv.url === null))
}
try {
iss = await Company.bulkCreate(dataArray, {
fields: [
'name',
'url',
'symbol',
'description',
]
})
} catch (error) {
console.log(error)
}
return iss
}
let data = [{
"date": "9/14/2019",
"issuer": "Issuer6",
"name": "Name1",
"symbol": "Symbol2",
"url": "www.url.com"
}, {
"date": "9/11/2029",
"issuer": "Issuer3",
"name": "Name1",
"symbol": "Symbol1",
"url": "www.url.com"
}, {
"date": "8/13/2019",
"issuer": "Issuer1",
"name": "Name1",
"symbol": "Symbol1",
"url": "www.url.com"
}]
async function main() {
// setup db
await models.sequelize.sync({
force: true
})
await createOrUpdateCompany(data)
await createOrUpdateCompany(data)
await createOrUpdateCompany(data)
await createOrUpdateCompany(data)
console.log("##################### DONE #####################");
}
main()
When executing the above example I get the following in my db:
However, I would like to get only the following as result:
As you can see I only get two entries based on the unique result.
Any suggestions why my createOrUpdateCompany() is wrong?
I appreciate your replies!

There are 2 ways to achieve uniqueness in your result
Filter your array data before execution (Javascript stuff)
Unique elements from array
Make specific fields unique in the DB (composite unique) (Database stuff)Add Composite unique key in MySQL

Related

use the filesystem module and the string.replace() to Substitute tokens in a nested object

When I run node index I get undefined. I am trying to use the filesystem module and the string.replace() method to replace tokens in a nested object in collections.js with values from another file (values.json). But when I run the code, nothing changes, and when I console.log finalData, I get undefined.
index.js
const fs = require("fs").promises;
async function dataReader(filePath, data) {
const result = await fs.readFile(filePath);
try {
return JSON.parse(result);
} catch (e) {
console.error(e);
}
}
//read values.json
(async () => {
const value = await dataReader("./values.json");
//read collection.json
const data = await dataReader("./collections.json");
//replace tokens in `collection.js` with `values.js`
let finalData = JSON.stringify(data);
Object.keys(value).forEach((token) => {
finalData = finalData.replaceAll(`__${token}__`, value[token])
});
// write/save the new replaced token values in collection.json
await fs.writeFile("./collections.json", finalData, (err) => {
if (err) {
console.error(err);
}
});
});
collection.js
{
"collection" : [
{
"fruit": "__fruit_type__",
"clothings":{
"item": "__clothing_type__}"
}
},
{
"fitness": "__fitness_equipment__",
"mindfulness": "app called __meditation_app__"
}
]
}
**values.js**
{
"clothing_type": "winter",
"fruit_type": "apple",
"fitness_equipment": "treadmill",
"meditation_app": "calm"
}
expected result:
The collection file after replacing the tokens will have below content:
{
"collection": [
{
"fruit":"apple",
"clothings":{
"item":"winter}"
}
},
{
"fitness":"treadmill",
"mindfulness":"app called calm"
}
]
}
Your collection.jsonĀ is missing some comas and is not well formatted, try to change it to:
const data = {
"collection" : [
{
"fruit": "__fruit_type__",
"clothings": {
"item": "__clothing_type__"
}
},
{
"fitness": "__fitness_equipment__",
"mindfulness": "app called __meditation_app__"
}
]
}
const value = {
"clothing_type": "winter",
"fruit_type": "apple",
"fitness_equipment": "treadmill",
"meditation_app": "calm"
}
let finalData = JSON.stringify(data);
Object.keys(value).forEach((token) => {
finalData = finalData.replaceAll(`__${token}__`, value[token]);
});
console.log(JSON.parse(finalData))

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 find by a subdocument's value

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;
});
};

Removing an object from an array by a property

I'm writing an endpoint to delete a record from a dummy database. The dummy database right now is written as an array of objects (it will then be changed to a class, then an actual database).
I have a dbHelpers.js file:
module.exports = {
createId(data) {
// ...
},
findById(data, recordId) {
// ...
},
deleteById(data, recordId) {
data.splice(data.findIndex(item => item.id === recordId), 1)
return data;
}
};
And i'm calling it in controllers/envelope.js
exports.deleteEnvelope = async (req, res) => {
try {
const { id } = req.params;
const envelopes = await dbEnvelopes;
const envelope = findById(envelopes, id);
if (!envelope) {
return res.status(404).send({
message: 'Envelope Not Found',
});
}
const updatedEnvelopes = deleteById(envelopes, id);
return res.status(200).send(updatedEnvelopes);
} catch (err) {
res.status(500).send(err)
}
};
However, for some reason, my findIndex function is not working and it's always returning -1. What is wrong with the function?
Below is the dummy db in config/db.js:
const envelopes = [
{
"id": 1,
"title": "Rent",
"budget": 1000
},
{
id: 2,
"title": "Groceries",
"budget": 300
},
{
id: 3,
"title": "Entertainment",
"budget": 400
}
]
module.exports = envelopes;
I think you can use array.filter to remove the object in array by matching the id.
deleteById(data, recordId) {
return data.filter(item => item.id !== recordId)
}

Make multiple calls in nodejs

I have some mock data for below 2 URLS:
1. Get the list of users from 'https://myapp.com/authors'.
2. Get the list of Books from 'https://myapp.com/books'.
Now my task is to sort the Books by name and write the sorted list to the file mysortedbooks.json as JSON
Then I have to create an array of authors with books property that has all the books of that author.
If the author has no books then this array should be empty. Sorting is not needed for this case, and data should be stored in file authorBooks.json as JSON.
Now I have to return a promise that resolves when the above steps are complete. For example, I should return the final saveToFile call in below code.
const fs = require('fs');
function getFromURL(url) {
switch (url) {
case 'https://myapp.com/authors':
return Promise.resolve([
{ name: "Chinua Achebe", id: "1" },
{ name: "Hans Christian Andersen", id: "2" },
{ name: "Dante Alighieri", id: "3" },
]);
case 'https://myapp.com/books':
return Promise.resolve([
{ name: "Things Fall Apart", authorId: "1" },
{ name: "The Epic Of Gilgamesh", authorId: "1" },
{ name: "Fairy tales", authorId: "2" },
{ name: "The Divine Comedy", authorId: "2" },
{ name: "One Thousand and One Nights", authorId: "1" },
{ name: "Pride and Prejudice", authorId: "2" },
]);
}
}
const outFile = fs.createWriteStream('...out-put-path...');
function saveToFile(fileName, data) {
outFile.write(`${fileName}: ${data}\n`);
return Promise.resolve();
}
function processData() {
const authors = getFromURL('https://myapp.com/authors').then(author => {
return authors;
});
const books = getFromURL('https://myapp.com/authors').then(books => {
return books.sort();
});
return saveToFile('mysortedbooks.json', JSON.stringify(books)).then(() => {
const authorAndBooks = authors.map(author => {
var jsonData = {};
jsonData['name'] = author.name;
jsonData['books'] = [];
for(var i=0; i<books.length; i++) {
if(authod.id == books[i].authorId) {
jsonData['books'].push(books[i].name);
}
}
});
saveToFile('authorBooks.json', authorAndBooks);
});
}
processData().then(() => outFile.end());
The main logic I have to implement is in processData method.
I tried adding code to solve the requirement but I got stuck how to return promise after all the operations. Also how to build my authorAndBooks JSON content.
Please help me with this.
const authors = getFromURL('https://myapp.com/authors').then(author => {
return authors;
});
const books = getFromURL('https://myapp.com/authors').then(books => {
return books.sort();
});
//authors and books are both promises here, so await them
return Promise.all([authors, books]).then(function(results){
authors = results[0];
books = results[1];
return saveToFile(...);
});
alternatively declare your function async and do
const authors = await getFromURL('https://myapp.com/authors').then(author => {
return authors;
});
const books = await getFromURL('https://myapp.com/authors').then(books => {
return books.sort();
});
return await saveToFile(...);
Refactored code with Promise Chaining and to create multiple file streams
const fs = require('fs');
function getFromURL(url) {
switch (url) {
case 'https://myapp.com/authors':
return Promise.resolve([
{ name: "Chinua Achebe", id: "1" },
{ name: "Hans Christian Andersen", id: "2" },
{ name: "Dante Alighieri", id: "3" },
]);
case 'https://myapp.com/books':
return Promise.resolve([
{ name: "Things Fall Apart", authorId: "1" },
{ name: "The Epic Of Gilgamesh", authorId: "1" },
{ name: "Fairy tales", authorId: "2" },
{ name: "The Divine Comedy", authorId: "2" },
{ name: "One Thousand and One Nights", authorId: "1" },
{ name: "Pride and Prejudice", authorId: "2" },
]);
}
}
function saveToFile(fileName, data) {
const outFile = fs.createWriteStream(`/var/${fileName}`);
outFile.write(data);
return Promise.resolve(outFile);
}
function authorBookMapping(data) {
let [authors, books] = data;
var jsonData = {};
authors.map(author => {
jsonData['name'] = author.name;
jsonData['books'] = [];
for(var i=0; i<books.length; i++) {
if(author.id == books[i].authorId) {
jsonData['books'].push(books[i].name);
}
}
});
return {
books: books,
authorAndBooks: jsonData
};
}
function writeFile(data) {
if(data) {
const {books, authorAndBooks} = data;
const book = saveToFile('mysortedbooks.json', JSON.stringify(books));
const author = saveToFile('authorBooks.json', JSON.stringify(authorAndBooks));
return Promise.all([book, author]);
}
}
function processData() {
const authors = getFromURL('https://myapp.com/authors');
const books = getFromURL('https://myapp.com/authors');
return Promise.all([authors, books])
.then(authorBookMapping)
.then(writeFile)
}
processData().then((stream) => {
for(let s in stream) {
stream[s].close();
}
})
.catch((err) => {
console.log("Err :", err);
}) ;
lot of mistakes in your code. I will try to explain one by one, read comments between code. I would recommend you to read some basics of file operations and promises. Problem is in your saveToFile method and how you are chaining promises in processData method.
Change your saveToFIle function as following. You can also use promise supporting fs libraries like fs-extra but I'm not sure if you want to use an external library.
const path = require('path');
const basePath = '.';//whatever base path of your directories
function saveToFile(fileName, data) {
// fs.writeFile method uses callback, you can use many ways to convert a callback method to support promises
// this is one of the simple and doesn't require any libraries to import
return new Promise((resolve,reject)=>{
let filePath = path.join(basePath,fileName);
return fs.writeFile(filePath,data,(err, data)=>{
if(err) reject(err);
else resolve();
});
})
}
Now change your processData function to use promise.all and to sort boooks in right way
function processData() {
let books, authors;
//Promise.all can be used when operations are not interdependent, fteches result fasetr as works like parallel requests
return Promise.all([
getFromURL('https://myapp.com/books'),
getFromURL('https://myapp.com/authors')
]).then(data => {
books = data[0];
authors = data[1];
let authorAndBooks = authors.map(author => {
let jsonData = {};
jsonData['name'] = author.name;
jsonData['books'] = [];
for(var i=0; i<books.length; i++) {
if(author.id == books[i].authorId) {
jsonData['books'].push(books[i].name);
}
}
return jsonData;
console.log(jsonData);
});
// you will have to use a comparator to sort objects, given below it will sort books based on names.
books.sort((first,second)=>{ return first.name>second.name ?1:-1})
return Promise.all([
saveToFile("mysortedbooks.json",JSON.stringify(books)),
saveToFile("authorBooks.json",JSON.stringify(authorAndBooks))])
}).then(data=>{
console.log('All operations complete');
})
}
processData();
Have you considered looking at this in a different way? If this is going to be the case for other APIs I'd think about aggregating those APIs in an aggregator service or the API itself if you can.
It is always better to receive all the data you need at once rather than multiple calls, you will incur latency and complexity.

Categories

Resources