pg-promise - Not resolving multiple queries - postgresql

I'm in the process of creating a mass mailer that allows a company to send out an email template to a specific plan that contains X amount of subscribers.
However, when using pg-promise, I can't figure how to loop through an array that contains one or more unique plan names that'll then be used as query parameters for subsequent queries to gather a single array of results. The subsequent queries don't resolve before express sends back a specified error message.
For example:
const selectedTemplate = await db.oneOrNone("SELECT * FROM templates WHERE userid=$1 and uniqueTemplateName=$2", [req.session.id, req.body.uniquetemplatename])
returns a JSON object...
{ ...
plans : [ 'Unique Plan 1', 'Unique Plan 2', 'Unique Plan 3'...],
...
}
I then want to loop through this plans array to gather some subscriber emails. Since the selectedTemplate's plans array can contain a single "Unique Plan" or multiple, I'm trying to figure out how to get this query to work:
let subscriberEmails = [];
await each(selectedTemplate.plans, async plan => {
const emails = await db.any("SELECT email FROM subscribers WHERE userid=$1 AND planName=$2", [req.session.id, plan])
each(emails, ({email}) => subscriberEmails.push(email))
console.log('Found emails: ', subscriberEmails);
})
console.log('Selected emails: ', subscriberEmails);
res.status(500).json({ message: "Route not configured yet" })
The problem I'm having is that Express isn't waiting for the pg-promise to resolve. So it's triggering my 500 server "Route not configured yet" message, then resolves the queries:
14:48:33 SELECT * FROM templates WHERE userid='12345' and uniqueTemplateName='example-unique-template'
selectedTemplate: anonymous {
id: '84efa448-b149-11e8-a7fd-3b9e4e9e5ece',
key: 9,
userid: '12345',
status: 'active',
fromsender: 'example#helpdesk.com',
subject: 'Thank you for using our services!',
templatename: 'Example Unique Template',
uniquetemplatename: 'example-unique-template',
message: '<h3>This is a test!</h3>',
plans: [ 'Unique Plan 1', 'Unique Plan 2' ]
}
Selected emails: []
POST /api/messages/create 500 28 - 34.125 ms
14:48:33 SELECT email FROM subscribers WHERE userid='12345' AND planName='Unique Plan 1'
Found emails: [
'betatester19#example.com',
'betatester20#example.com',
'betatester21#example.com'
]
14:48:34 SELECT email FROM subscribers WHERE userid='12345' AND planName='Unique Plan 2'
Found emails: [
'betatester19#example.com',
'betatester20#example.com',
'betatester21#example.com',
'betatester1#example.com',
'betatester2#example.com',
'betatester3#example.com',
'betatester4#example.com',
'betatester5#example.com',
'betatester6#example.com',
'betatester7#example.com',
'betatester8#example.com',
'betatester9#example.com',
'betatester10#example.com',
'betatester11#example.com',
'betatester12#example.com',
'betatester13#example.com',
'betatester14#example.com',
'betatester15#example.com',
'betatester16#example.com',
'betatester17#example.com',
'betatester18#example.com'
]
The function as is:
async (req, res, done) => {
if (!req.body) {
return res.status(404).json({ message: "Missing query parameters" });
done();
}
try {
const selectedTemplate = await db.oneOrNone("SELECT * FROM templates WHERE userid=$1 and uniqueTemplateName=$2", [req.session.id, uniquetemplatename]);
if (!selectedTemplate) {
return res.status(404).json({ message: "Unable to locate selected template" });
done();
}
console.log('selectedTemplate', selectedTemplate);
let subscriberEmails = [];
await each(selectedTemplate.plans, async plan => {
const emails = await db.any("SELECT email FROM subscribers WHERE userid=$1 AND planName=$2", [req.session.id, plan])
each(emails, ({email}) => subscriberEmails.push(email))
console.log('Found emails: ', subscriberEmails);
})
console.log('Selected emails: ', subscriberEmails);
res.status(500).json({ message: "Route not configured yet" })
} catch (err) { res.status(500).json({ message: err }); done(); }
}
A vastly simplified DB structure:
├── Template
│ └── Plans (references an array of unique plan name(s))
|
|── Plan
| └── PlanName (unique plan name)
|
│── Subscriber
| └── PlanName (references a single unique plan name)
| └── Email (unique email)
I've tried using a db.task(), but again, the queries didn't resolve before Express sent back the message.

Figured out where the problem was. Just in case anyone else stumbles upon this predicament: Functions like "map" and "each" don't handle async/await. They instead just return a pending Promise. Therefore, you'll want to use a library like Bluebird, which implements Promise-based each/map/etc functions (found under "Collections").
The fix for the above to work:
try {
await db.task('create-message', async t => {
const selectedTemplate = await t.oneOrNone(findTemplateByName, [req.session.id, uniquetemplatename])
if (!selectedTemplate) return sendError(unableToLocate('template'), res, done);
const { fromsender, subject, message, plans, templatename } = selectedTemplate;
let subscriberEmails = [];
await Promise.each(plans, async plan => {
const emails = await t.any(getAllEmailsByPlan, [req.session.id, plan]);
each(emails, ({email}) => subscriberEmails.push(email));
})
if (isEmpty(subscriberEmails)) return sendError(unableToLocate('subscribers in the selected plan(s)'), res, done);
const msg = {
to: subscriberEmails,
from: fromsender,
replyTo: fromsender,
subject: subject,
html: message
};
await mailer.sendMultiple(msg);
await t.none(createMessageTransaction, [req.session.id, templatename, fromsender, subject, currentDate, plans]);
await t.none(createNotification, [req.session.id, 'mail_outline', `The following template: ${templatename} has been sent out to the subscibers in the following ${plans.length > 1 ? "plans" : "plan"}: ${plans}.`, date]);
return res.status(201).send(null);
})
} catch (err) { return sendError(err, res, done); }

Related

Image returned from REST API always displays broken

I am building a content management system for an art portfolio app, with React. The client will POST to the API which uses Mongoose to insert into a MongoDB. The API then queries the DB for the newly inserted image, and returns it to the client.
Here's my code to connect to MongoDB using Mongoose:
mongoose.connect('mongodb://localhost/test').then(() =>
console.log('connected to db')).catch(err => console.log(err))
mongoose.Promise = global.Promise
const db = mongoose.connection
db.on('error', console.error.bind(console, 'MongoDB connection error:'))
const Schema = mongoose.Schema;
const ImgSchema = new Schema({
img: { data: Buffer, contentType: String }
})
const Img = mongoose.model('Img', ImgSchema)
I am using multer and fs to handle the image file. My POST endpoint looks like this:
router.post('/', upload.single('image'), (req, res) => {
if (!req.file) {
res.send('no file')
} else {
const imgItem = new Img()
imgItem.img.data = fs.readFileSync(req.file.path)
imgItem.contentType = 'image/png'
imgItem
.save()
.then(data =>
Img.findById(data, (err, findImg) => {
console.log(findImg.img)
fs.writeFileSync('api/uploads/image.png', findImg.img.data)
res.sendFile(__dirname + '/uploads/image.png')
}))
}
})
I can see in the file structure that writeFileSync is writing the image to the disk. res.sendFile grabs it and sends it down to the client.
Client side code looks like this:
handleSubmit = e => {
e.preventDefault()
const img = new FormData()
img.append('image', this.state.file, this.state.file.name)
axios
.post('http://localhost:8000/api/gallery', img, {
onUploadProgress: progressEvent => {
console.log(progressEvent.loaded / progressEvent.total)
}
})
.then(res => {
console.log('responsed')
console.log(res)
const returnedFile = new File([res.data], 'image.png', { type: 'image/png' })
const reader = new FileReader()
reader.onloadend = () => {
this.setState({ returnedFile, returned: reader.result })
}
reader.readAsDataURL(returnedFile)
})
.catch(err => console.log(err))
}
This does successfully place both the returned file and the img data url on state. However, in my application, the image always displays broken.
Here's some screenshots:
How to fix this?
Avoid sending back base64 encoded images (multiple images + large files + large encoded strings = very slow performance). I'd highly recommend creating a microservice that only handles image uploads and any other image related get/post/put/delete requests. Separate it from your main application.
For example:
I use multer to create an image buffer
Then use sharp or fs to save the image (depending upon file type)
Then I send the filepath to my controller to be saved to my DB
Then, the front-end does a GET request when it tries to access: http://localhost:4000/uploads/timestamp-randomstring-originalname.fileext
In simple terms, my microservice acts like a CDN solely for images.
For example, a user sends a post request to http://localhost:4000/api/avatar/create with some FormData:
It first passes through some Express middlewares:
libs/middlewares.js
...
app.use(cors({credentials: true, origin: "http://localhost:3000" })) // allows receiving of cookies from front-end
app.use(morgan(`tiny`)); // logging framework
app.use(multer({
limits: {
fileSize: 10240000,
files: 1,
fields: 1
},
fileFilter: (req, file, next) => {
if (!/\.(jpe?g|png|gif|bmp)$/i.test(file.originalname)) {
req.err = `That file extension is not accepted!`
next(null, false)
}
next(null, true);
}
}).single(`file`))
app.use(bodyParser.json()); // parses header requests (req.body)
app.use(bodyParser.urlencoded({ limit: `10mb`, extended: true })); // allows objects and arrays to be URL-encoded
...etc
Then, hits the avatars route:
routes/avatars.js
app.post(`/api/avatar/create`, requireAuth, saveImage, create);
It then passes through some user authentication, then goes through my saveImage middleware:
services/saveImage.js
const createRandomString = require('../shared/helpers');
const fs = require("fs");
const sharp = require("sharp");
const randomString = createRandomString();
if (req.err || !req.file) {
return res.status(500).json({ err: req.err || `Unable to locate the requested file to be saved` })
next();
}
const filename = `${Date.now()}-${randomString}-${req.file.originalname}`;
const filepath = `uploads/${filename}`;
const setFilePath = () => { req.file.path = filepath; return next();}
(/\.(gif|bmp)$/i.test(req.file.originalname))
? fs.writeFile(filepath, req.file.buffer, (err) => {
if (err) {
return res.status(500).json({ err: `There was a problem saving the image.`});
next();
}
setFilePath();
})
: sharp(req.file.buffer).resize(256, 256).max().withoutEnlargement().toFile(filepath).then(() => setFilePath())
If the file is saved, it then sends a req.file.path to my create controller. This gets saved to my DB as a file path and as an image path (the avatarFilePath or /uploads/imagefile.ext is saved for removal purposes and the avatarURL or [http://localhost:4000]/uploads/imagefile.ext is saved and used for the front-end GET request):
controllers/avatars.js (I'm using Postgres, but you can substitute for Mongo)
create: async (req, res, done) => {
try {
const avatarurl = `${apiURL}/${req.file.path}`;
await db.result("INSERT INTO avatars(userid, avatarURL, avatarFilePath) VALUES ($1, $2, $3)", [req.session.id, avatarurl, req.file.path]);
res.status(201).json({ avatarurl });
} catch (err) { return res.status(500).json({ err: err.toString() }); done();
}
Then when the front-end tries to access the uploads folder via <img src={avatarURL} alt="image" /> or <img src="[http://localhost:4000]/uploads/imagefile.ext" alt="image" />, it gets served up by the microservice:
libs/server.js
const express = require("express");
const path = app.get("path");
const PORT = 4000;
//============================================================//
// EXPRESS SERVE AVATAR IMAGES
//============================================================//
app.use(`/uploads`, express.static(`uploads`));
//============================================================//
/* CREATE EXPRESS SERVER */
//============================================================//
app.listen(PORT);
What it looks when logging requests:
19:17:54 INSERT INTO avatars(userid, avatarURL, avatarFilePath) VALUES ('08861626-b6d0-11e8-9047-672b670fe126', 'http://localhost:4000/uploads/1536891474536-k9c7OdimjEWYXbjTIs9J4S3lh2ldrzV8-android.png', 'uploads/1536891474536-k9c7OdimjEWYXbjTIs9J4S3lh2ldrzV8-android.png')
POST /api/avatar/create 201 109 - 61.614 ms
GET /uploads/1536891474536-k9c7OdimjEWYXbjTIs9J4S3lh2ldrzV8-android.png 200 3027 - 3.877 ms
What the user sees upon successful GET request:

Unable to get Moxios stubRequest to work

I'm having issues getting stubRequest to work properly. Here's my code:
it('should stub my request', (done) => {
moxios.stubRequest('/authenticate', {
status: 200
})
//here a call to /authenticate is being made
SessionService.login('foo', 'bar')
moxios.wait(() => {
expect(something).toHaveHappened()
done()
})
})
This works fine:
it('should stub my request', (done) => {
SessionService.login('foo', 'bar')
moxios.wait(async () => {
let request = moxios.requests.mostRecent()
await request.respondWith({
status: 200
})
expect(something).toHaveHappened()
done()
})
})
The second method just get's the last call though, and I'd really like to be able to explicitely stub certain requests.
I'm running Jest with Vue.
I landed here with a similar goal and eventually solved it using a different approach that may be helpful to others:
moxios.requests has a method .get() (source code) that lets you grab a specific request from moxios.requests based on the url. This way, if you have multiple requests, your tests don't require the requests to occur in a specific order to work.
Here's what it looks like:
moxios.wait(() => {
// Grab a specific API request based on the URL
const request = moxios.requests.get('get', 'endpoint/to/stub');
// Stub the response with whatever you would like
request.respondWith(yourStubbedResponseHere)
.then(() => {
// Your assertions go here
done();
});
});
NOTE:
The name of the method .get() is a bit misleading. It can handle different types of HTTP requests. The type is passed as the first parameter like: moxios.requests.get(requestType, url)
it would be nice if you show us the service. Service call must be inside the moxios wait func and outside must be the axios call alone. I have pasted a simplified with stubRequest
describe('Fetch a product action', () => {
let onFulfilled;
let onRejected;
beforeEach(() => {
moxios.install();
store = mockStore({});
onFulfilled = sinon.spy();
onRejected = sinon.spy();
});
afterEach(() => {
moxios.uninstall();
});
it('can fetch the product successfully', done => {
const API_URL = `http://localhost:3000/products/`;
moxios.stubRequest(API_URL, {
status: 200,
response: mockDataSingleProduct
});
axios.get(API_URL, mockDataSingleProduct).then(onFulfilled);
const expectedActions = [
{
type: ACTION.FETCH_PRODUCT,
payload: mockDataSingleProduct
}
];
moxios.wait(function() {
const response = onFulfilled.getCall(0).args[0];
expect(onFulfilled.calledOnce).toBe(true);
expect(response.status).toBe(200);
expect(response.data).toEqual(mockDataSingleProduct);
return store.dispatch(fetchProduct(mockDataSingleProduct.id))
.then(() => {
var actions = store.getActions();
expect(actions.length).toBe(1);
expect(actions[0].type).toBe(ACTION.FETCH_PRODUCT);
expect(actions[0].payload).not.toBe(null || undefined);
expect(actions[0].payload).toEqual(mockDataSingleProduct);
expect(actions).toEqual(expectedActions);
done();
});
});
});
})

JSON formatting when saving using Mongoose is not bringing back the expected result

I have a code block in my Mongoose controller which attempts to find both Projects and Levels:
exports.landing = (req, res, next) => {
console.log(req.params.projectid);
Project.findById(req.params.projectid, (err, project) => {
if (err) return res.status(500).send(err);
//find the level based on the projectid
Level.find({'projectid': req.params.projectid}, (err, level) => {
if (err) return res.status(500).send(err);
//find the level based on the projectid
res.json({
success: true,
message: 'got',
level: level.leveltempnodes
});
//res.render(path + 'project', {project: project, moment: moment, level: level});
});
});
};
Within the res.json section, If I just use 'level' without the dot notation, all the results come back as expected. When I try and get the 'levelnodes' entry, nothing comes back. The only thing I see differently with the level document compared to the other documents is that the JSON result includes a '[':
{"success":true,"message":"got","level":{"_id":"5b4205ea5b44e146b5978175" ...
The above works fine. But I am not able to use dot syntax on the below result:
{"success":true,"message":"got","level":[{"_id":"5b4202fc94855d56204c8bb7"
I am saving the level document like this:
var data = {
levelname: levelname,
leveltempnodes: leveltempnodes,
projectid: projectid};
var level = new Level(data);
level.save(function (err) {
if (err) return handleError(err);
})
My error is nothing is coming back at all:
{"success":true,"message":"got"}
Schema:
const mongoose = require('mongoose');
const LevelSchema = mongoose.Schema({
levelname: String,
leveltempnodes: String,
projectid: String
});
module.exports = mongoose.model('Level', LevelSchema);
Data is being stored on the DB without issue. I am adding it via Ajax:
var p = {
projectname : $("#projectname").val(),
levelname : 'Root',
leveltempnodes : '{"class":"go.GraphLinksModel","nodeKeyProperty":"id","nodeDataArray":[{"id":1,"loc":"226 226","text":"sensor"},{"text":"perception","loc":"426 225.99999999999997","id":-2},{"text":"planning","loc":"626 225.99999999999997","id":-3},{"text":"gate","loc":"826 225.99999999999997","id":-4}],"linkDataArray":[{"from":1,"to":-2,"text":"msg","points":[296.7874157629703,237.73538061447854,340.03133208792605,227.76937481449303,383.33478829426565,227.0952320784595,426.7981545990892,236.1401244399739]},{"from":-2,"to":-3,"text":"msg","points":[523.225709890083,236.1861908341044,558.0349502392196,229.00680324793404,592.1479459982006,228.54232080927673,626.6289592123036,236.76409981273324]},{"from":-3,"to":-4,"text":"msg","points":[709.6483081744094,237.23795381070627,748.7663709980919,229.48139598538538,787.383185499046,229.48139598538538,826.1210439041331,238.64104211943584]}]}',
}
if(p.projectname == ''){
console.log('e');
}else{
$.ajax({
type: 'POST',
contentType : "application/json",
url: 'api/project/save',
data : JSON.stringify(p),
success: function(res) {
window.location.replace("/project/"+res.id);
}
});

facebook messenger bot encoding error

I have written sample echo message bot using facebook messenger api and wit.ai actions.
My message from facebook page is received and the proper action function defined using wit api's is also getting called. However
while returning the response, i am getting followin error as -
Oops! An error occurred while forwarding the response to : Error: (#100) Param message[text] must be a UTF-8 encoded string
at fetch.then.then.json (/app/index.js:106:13)
at process._tickCallback (internal/process/next_tick.js:103:7)
Here is the function which is used to return the response -
const fbMessage = (id, text) => {
const body = JSON.stringify({
recipient: { id },
message: { text },
});
const qs = 'access_token=' + encodeURIComponent(FB_PAGE_ACCESS_TOKEN);
return fetch('https://graph.facebook.com/v2.6/me/messages?' + qs, {
method: 'POST',
headers: {'Content-Type': 'application/json; charset=UTF-8'},
body
})
.then(rsp => rsp.json())
.then(json => {
if (json.error && json.error.message) {
throw new Error(json.error.message);`enter code here`
}
return json;
});
};
I have copied this function from the messenger.js file from the documentation since i am just trying the POC.
I checked the values for text and id in this function and verified using console.log statements and those are coming properly.
Can some experts help me to solve this error?
Note - I tried encoding the text using text.toString("utf8"); but it returns the encoding string as [object object] and thats the
response i get from bot. so it doesnt work.
Get the latest code from node-wit, there is a change in facebook id usage,
According to Facebook:
On Tue May 17 format of user and page ids delivered via webhooks will
change from an int to a string to better support default json encoder
in js (that trims long ints). Please make sure your app works with
string ids returned from webhooks as well as with ints.
Still you are getting issue with the api try to add if(event.message && !event.message.is_echo) condition as shown in below code.
// Message handler
app.post('/webhook', (req, res) => {
const data = req.body;
if (data.object === 'page') {
data.entry.forEach(entry => {
entry.messaging.forEach(event => {
if (event.message && !event.message.is_echo) {
const sender = event.sender.id;
const sessionId = findOrCreateSession(sender);
const {text, attachments} = event.message;
if (attachments) {
fbMessage(sender, 'Sorry I can only process text messages for now.')
.catch(console.error);
} else if (text) {
wit.runActions(
sessionId, // the user's current session
text, // the user's message
sessions[sessionId].context // the user's current session state
).then((context) => {
console.log('Waiting for next user messages');
sessions[sessionId].context = context;
})
.catch((err) => {
console.error('Oops! Got an error from Wit: ', err.stack || err);
})
}
} else {
console.log('received event', JSON.stringify(event));
}
});
});
}
res.sendStatus(200);
});
Reference:
no matching user bug
no matching user fix

how to deal with mongodb race condition in integration test

I have a mongoose schema with a unique field and I am trying to write a backend (express) integration test which checks that POSTing the same entity twice results in HTTP 400. When testing manually behaviour is as excpected. Automatic testing however requires a wait:
it('should not accept two projects with the same name', function(done) {
var project = // ...
postProjectExpect201(project,
() => {
setTimeout( () => {
postProjectExpect400(project, done);
},100);
}
);
});
The two post... methods do as named and the code above works fine, but if the timeout is removed, BOTH requests receive HTTP 200 (though only one entity created in the database).
I'm new to those technologies and I'm not sure what's going on. Could this be a mongodb related concurrency issue and if so how should I deal with it?
The database call looks like this:
Project.create(req.body)
.then(respondWithResult(res, 201))
.catch(next);
I already tried connecting to mongodb with ?w=1 option btw.
Update:
To be more verbose: Project is a mongoose model and next is my express error handler which catches the duplicate error.
The test functions:
var postProjectExpect201=function(project, done, validateProject) {
request(app)
.post('/api/projects')
.send(project)
.expect(201)
.expect('Content-Type', /json/)
.end((err, res) => {
if (err) {
return done(err);
}
validateProject && validateProject(res.body);
done();
});
};
var postProjectExpect400=function(project, done) {
request(app)
.post('/api/projects')
.send(project)
.expect(400)
.end((err, res) => {
if (err) {
return done(err);
}
done();
});
};