I'm using Node/Express/Mongoose to accept a JSON file containing a list of product details. These products are looped through, the images are uploaded to AWS S3, and the product is either accepted or rejected depending on validation. In the end the accepted products are uploaded to Mongo via Mongoose and all are returned to provide info to the uploader. This is my first time with any of these frameworks so I'm looking to improve best practices and find potential points of failure.
ProductRoutes.js
const keys = require("../config/keys.js");
const mongoose = require("mongoose");
const requireLogin = require("../middlewares/requireLogin");
var validator = require("validator");
const fileUpload = require("express-fileupload");
var fs = require("fs");
const aws = require("aws-sdk");
const S3_BUCKET = keys.awsBucket;
var path = require("path");
var request = require("request");
aws.config.update({
region: "us-east-2",
accessKeyId: keys.awsAccessKey,
secretAccessKey: keys.awsSecretKey
});
require("../models/Product");
const Product = mongoose.model("product");
function validate(value, type) {
switch (type) {
case "string":
return value && !validator.isEmpty(value, { ignore_whitespace: true });
case "url":
return (
value &&
!validator.isURL(value, {
protocols: ["https, http"],
require_protocol: true
})
);
default:
return value && validator.isEmpty(value, { ignore_whitespace: true });
}
return value == null || value.length === 0;
}
function saveImage(url, key) {
let ext = path.extname(url);
let params = {
Key: key + ext,
Bucket: S3_BUCKET,
ACL: "public-read"
};
return new Promise(function(resolve, reject) {
request.get(url).on("response", function(response) {
if (response.statusCode === 200) {
params.ContentType = response.headers["content-type"];
var s3 = new aws.S3({ params })
.upload({ Body: response })
.send(function(err, data) {
resolve(data);
});
} else {
// return false;
reject(false);
}
});
});
}
module.exports = app => {
app.use(fileUpload());
app.post("/product/addProduct", requireLogin, async (req, res) => {
let products = req.files.file.data;
try {
products = JSON.parse(products);
} catch (e) {
return res
.status(400)
.json({ success: false, message: "Invalid JSON product feed" });
}
let accepted = [];
let rejected = [];
for (const product of products) {
if (!validate(product.sku, "string")) {
rejected.push(product);
return;
}
if (!validate(product.image_url, "url")) {
rejected.push(product);
return;
}
try {
let result = await saveImage(product.image_url, `${product.owner}/${product.sku}`);
product.image_url = result.Location;
} catch (err) {
// catches errors both in fetch and response.json
return res.status(400).json({
success: false,
message: "Could not upload image",
error: err
});
}
let upsertProduct = {
updateOne: {
filter: { sku: product.sku },
update: product,
upsert: true
}
};
accepted.push(upsertProduct);
}
// now bulkWrite (note the use of 'Model.collection')
Product.collection.bulkWrite(accepted, function(err, docs) {
if (err) {
return res.status(400).json({
success: false,
message: "Something went wrong, please try again"
});
} else {
return res.status(200).json({
success: true,
message: "Company successfully created",
accepted: { count: accepted.length, list: accepted },
rejected: { count: rejected.length, rejected: rejected },
affiliate: docs
});
}
});
});
app.get("/product/fetchAffiliateProducts", requireLogin, (req, res) => {
var affiliateId = req.query.affiliateId;
Product.find({ owner: affiliateId }, function(err, products) {
if (err) {
return res.status(400).json({
success: false,
message: "Could not find the requested company's products"
});
} else {
return res.status(200).json({
success: true,
message: "Products successfully found",
products: products
});
}
});
});
};
Product.js (model):
const mongoose = require('mongoose');
const {Schema} = mongoose;
const productSchema = new Schema({
sku: {type: String, unique: true, required: true},
name: {type: String, required: true},
owner: {type: String, required: true},
image_url: {type: String, required: true}
});
mongoose.model('product', productSchema);
Sample input
[
{
"sku": "123",
"name": "Test Product 1",
"owner": "Test Company 1",
"image_url": "https://exmaple.com/src/assets/product1.png"
},
{
"sku": "456",
"name": "Test Product 3",
"owner": "Test Company 2",
"image_url": "https://exmaple.com/src/assets/product2.png"
},
{
"sku": "789",
"name": "Test Product 3",
"owner": "Test Company 3",
"image_url": "https://exmaple.com/src/assets/product3.png"
}
]
If there are other contextual files/code needed let me know; this is all that seemed relevant.