Related
Lobbies.json
{"Lobbies":[]}
jsonWorker.js
const fs = require('fs');
function lobbyUpdater(name, password) {
let rawdata = fs.readFileSync('Lobbies.json');
let data = JSON.parse(rawdata);
// data.Lobbies.length = 0; // Remove this for production
let newLobby = {"Name":name, "Password":password, "Players":1, "Mode":"", "Word":""}
data.Lobbies.push(newLobby)
fs.writeFile('Lobbies.json', JSON.stringify(data), 'utf8', function(err) {
if (err) throw err;
});
}
function lobbyAvailable(name) {
let rawdata = fs.readFileSync('Lobbies.json');
let data = JSON.parse(rawdata);
for (let i = 0; i < data.Lobbies.length; i++) {
if (data.Lobbies[i].Name.toUpperCase() === name.toUpperCase()) {
return false;
}
}
return true;
}
module.exports = {
lobbyUpdater,
lobbyAvailable
};
post Request on index.js
app.post('/newLobby', (req, res) => {
console.log("Lobby Name:", req.body.lobbyName);
console.log("Lobby Password:", req.body.lobbyPassword);
const jsonWorker = require('./jsonWorker');
if (jsonWorker.lobbyAvailable(req.body.lobbyName)) {
jsonWorker.lobbyUpdater(req.body.lobbyName, req.body.lobbyPassword);
}
else {
res.sendStatus(403);
console.log("Stopped A Lobby From Being Created");
}
});
Code from react that is being followed
<Link id="testLink" to="/Waiting-For-Players"><button id="submit" onClick={sendingRequest} className="WaitingForPlayers">Create Lobby</button></Link>
sendingRequest function
function sendingRequest(event) {
event.preventDefault();
$.post("http://localhost:4000/newLobby",
{
lobbyName: document.getElementById('lobbyName').value,
lobbyPassword: document.getElementById('lobbyPassword').value
},
function (data, status) {
console.log("Data", data);
})
.fail(function(jqXHR, textStatus, errorThrown) {
console.log("Error received:", errorThrown);
console.log("Stopping the post request.");
return;
})
.then(function() {
// Follow the Link component
let linkToUse = document.getElementById("testLink");
window.location.assign(linkToUse.to);
console.log("Testing")
});
}
My goal is to only have the Link be stopped if the post request throws a 403 forbidden since the lobby name would have already been in the json. The link should go through otherwise. I believe that the issue is that the .then function isn't going through as the console.log isn't showing up but I am really not sure why it isn't working. Any help would be appreciated thanks!
I want to open a page up in node and process the contents in my application. Something like this seems to work well:
var opts = {host: host, path:pathname, port: 80};
http.get(opts, function(res) {
var page = '';
res.on('data', function (chunk) {
page += chunk;
});
res.on('end', function() {
// process page
});
This doesn't work, however, if the page returns an 301/302 redirect. How would I do that in a reusable way in case there are multiple redirects? Is there a wrapper module on top of the http to more easily handle processing http responses from a node application?
If all you want to do is follow redirects but still want to use the built-in HTTP and HTTPS modules, I suggest you use https://github.com/follow-redirects/follow-redirects.
yarn add follow-redirects
npm install follow-redirects
All you need to do is replace:
var http = require('http');
with
var http = require('follow-redirects').http;
... and all your requests will automatically follow redirects.
With TypeScript you can also install the types
npm install #types/follow-redirects
and then use
import { http, https } from 'follow-redirects';
Disclosure: I wrote this module.
Is there a wrapper module on top of the http to more easily handle processing http responses from a node application?
request
Redirection logic in request
Make another request based on response.headers.location:
const request = function(url) {
lib.get(url, (response) => {
var body = [];
if (response.statusCode == 302) {
body = [];
request(response.headers.location);
} else {
response.on("data", /*...*/);
response.on("end", /*...*/);
};
} ).on("error", /*...*/);
};
request(url);
Update:
Now you can follow all redirects with var request = require('request'); using the followAllRedirects param.
request({
followAllRedirects: true,
url: url
}, function (error, response, body) {
if (!error) {
console.log(response);
}
});
Here is my (recursive) approach to download JSON with plain node, no packages required.
import https from "https";
function get(url, resolve, reject) {
https.get(url, (res) => {
// if any other status codes are returned, those needed to be added here
if(res.statusCode === 301 || res.statusCode === 302) {
return get(res.headers.location, resolve, reject)
}
let body = [];
res.on("data", (chunk) => {
body.push(chunk);
});
res.on("end", () => {
try {
// remove JSON.parse(...) for plain data
resolve(JSON.parse(Buffer.concat(body).toString()));
} catch (err) {
reject(err);
}
});
});
}
async function getData(url) {
return new Promise((resolve, reject) => get(url, resolve, reject));
}
// call
getData("some-url-with-redirect").then((r) => console.log(r));
Here is function I use to fetch the url that have redirect:
const http = require('http');
const url = require('url');
function get({path, host}, callback) {
http.get({
path,
host
}, function(response) {
if (response.headers.location) {
var loc = response.headers.location;
if (loc.match(/^http/)) {
loc = new Url(loc);
host = loc.host;
path = loc.path;
} else {
path = loc;
}
get({host, path}, callback);
} else {
callback(response);
}
});
}
it work the same as http.get but follow redirect.
In case of PUT or POST Request. if you receive statusCode 405 or method not allowed. Try this implementation with "request" library, and add mentioned properties.
followAllRedirects: true,
followOriginalHttpMethod: true
const options = {
headers: {
Authorization: TOKEN,
'Content-Type': 'application/json',
'Accept': 'application/json'
},
url: `https://${url}`,
json: true,
body: payload,
followAllRedirects: true,
followOriginalHttpMethod: true
}
console.log('DEBUG: API call', JSON.stringify(options));
request(options, function (error, response, body) {
if (!error) {
console.log(response);
}
});
}
If you have https server, change your url to use https:// protocol.
I got into similar issue with this one. My url has http:// protocol and I want to make a POST request, but the server wants to redirect it to https. What happen is that, turns out to be node http behavior sends the redirect request (next) in GET method which is not the case.
What I did is to change my url to https:// protocol and it works.
Possibly a little bit of a necromancing post here, but...
here's a function that follows up to 10 redirects, and detects infinite redirect loops. also parses result into JSON
Note - uses a callback helper (shown at the end of this post)
( TLDR; full working demo in context here or remixed-version here)
function getJSON(url,cb){
var callback=errBack(cb);
//var callback=errBack(cb,undefined,false);//replace previous line with this to turn off logging
if (typeof url!=='string') {
return callback.error("getJSON:expecting url as string");
}
if (typeof cb!=='function') {
return callback.error("getJSON:expecting cb as function");
}
var redirs = [url],
fetch = function(u){
callback.info("hitting:"+u);
https.get(u, function(res){
var body = [];
callback.info({statusCode:res.statusCode});
if ([301,302].indexOf(res.statusCode)>=0) {
if (redirs.length>10) {
return callback.error("excessive 301/302 redirects detected");
} else {
if (redirs.indexOf(res.headers.location)<0) {
redirs.push(res.headers.location);
return fetch(res.headers.location);
} else {
return callback.error("301/302 redirect loop detected");
}
}
} else {
res.on('data', function(chunk){
body.push(chunk);
callback.info({onData:{chunkSize:chunk.length,chunks:body.length}});
});
res.on('end', function(){
try {
// convert to a single buffer
var json = Buffer.concat(body);
console.info({onEnd:{chunks:body.length,bodyLength:body.length}});
// parse the buffer as json
return callback.result(JSON.parse(json),json);
} catch (err) {
console.error("exception in getJSON.fetch:",err.message||err);
if (json.length>32) {
console.error("json==>|"+json.toString('utf-8').substr(0,32)+"|<=== ... (+"+(json.length-32)+" more bytes of json)");
} else {
console.error("json==>|"+json.toString('utf-8')+"|<=== json");
}
return callback.error(err,undefined,json);
}
});
}
});
};
fetch(url);
}
Note - uses a callback helper (shown below)
you can paste this into the node console and it should run as is.
( or for full working demo in context see here )
var
fs = require('fs'),
https = require('https');
function errBack (cb,THIS,logger) {
var
self,
EB=function(fn,r,e){
if (logger===false) {
fn.log=fn.info=fn.warn=fn.errlog=function(){};
} else {
fn.log = logger?logger.log : console.log.bind(console);
fn.info = logger?logger.info : console.info.bind(console);
fn.warn = logger?logger.warn : console.warn.bind(console);
fn.errlog = logger?logger.error : console.error.bind(console);
}
fn.result=r;
fn.error=e;
return (self=fn);
};
if (typeof cb==='function') {
return EB(
logger===false // optimization when not logging - don't log errors
? function(err){
if (err) {
cb (err);
return true;
}
return false;
}
: function(err){
if (err) {
self.errlog(err);
cb (err);
return true;
}
return false;
},
function () {
return cb.apply (THIS,Array.prototype.concat.apply([undefined],arguments));
},
function (err) {
return cb.apply (THIS,Array.prototype.concat.apply([typeof err==='string'?new Error(err):err],arguments));
}
);
} else {
return EB(
function(err){
if (err) {
if (typeof err ==='object' && err instanceof Error) {
throw err;
} else {
throw new Error(err);
}
return true;//redundant due to throw, but anyway.
}
return false;
},
logger===false
? self.log //optimization :resolves to noop when logger==false
: function () {
self.info("ignoring returned arguments:",Array.prototype.concat.apply([],arguments));
},
function (err) {
throw typeof err==='string'?new Error(err):err;
}
);
}
}
function getJSON(url,cb){
var callback=errBack(cb);
if (typeof url!=='string') {
return callback.error("getJSON:expecting url as string");
}
if (typeof cb!=='function') {
return callback.error("getJSON:expecting cb as function");
}
var redirs = [url],
fetch = function(u){
callback.info("hitting:"+u);
https.get(u, function(res){
var body = [];
callback.info({statusCode:res.statusCode});
if ([301,302].indexOf(res.statusCode)>=0) {
if (redirs.length>10) {
return callback.error("excessive 302 redirects detected");
} else {
if (redirs.indexOf(res.headers.location)<0) {
redirs.push(res.headers.location);
return fetch(res.headers.location);
} else {
return callback.error("302 redirect loop detected");
}
}
} else {
res.on('data', function(chunk){
body.push(chunk);
console.info({onData:{chunkSize:chunk.length,chunks:body.length}});
});
res.on('end', function(){
try {
// convert to a single buffer
var json = Buffer.concat(body);
callback.info({onEnd:{chunks:body.length,bodyLength:body.length}});
// parse the buffer as json
return callback.result(JSON.parse(json),json);
} catch (err) {
// read with "bypass refetch" option
console.error("exception in getJSON.fetch:",err.message||err);
if (json.length>32) {
console.error("json==>|"+json.toString('utf-8').substr(0,32)+"|<=== ... (+"+(json.length-32)+" more bytes of json)");
} else {
console.error("json==>|"+json.toString('utf-8')+"|<=== json");
}
return callback.error(err,undefined,json);
}
});
}
});
};
fetch(url);
}
var TLDs,TLDs_fallback = "com.org.tech.net.biz.info.code.ac.ad.ae.af.ag.ai.al.am.ao.aq.ar.as.at.au.aw.ax.az.ba.bb.bd.be.bf.bg.bh.bi.bj.bm.bn.bo.br.bs.bt.bv.bw.by.bz.ca.cc.cd.cf.cg.ch.ci.ck.cl.cm.cn.co.cr.cu.cv.cw.cx.cy.cz.de.dj.dk.dm.do.dz.ec.ee.eg.er.es.et.eu.fi.fj.fk.fm.fo.fr.ga.gb.gd.ge.gf.gg.gh.gi.gl.gm.gn.gp.gq.gr.gs.gt.gu.gw.gy.hk.hm.hn.hr.ht.hu.id.ie.il.im.in.io.iq.ir.is.it.je.jm.jo.jp.ke.kg.kh.ki.km.kn.kp.kr.kw.ky.kz.la.lb.lc.li.lk.lr.ls.lt.lu.lv.ly.ma.mc.md.me.mg.mh.mk.ml.mm.mn.mo.mp.mq.mr.ms.mt.mu.mv.mw.mx.my.mz.na.nc.ne.nf.ng.ni.nl.no.np.nr.nu.nz.om.pa.pe.pf.pg.ph.pk.pl.pm.pn.pr.ps.pt.pw.py.qa.re.ro.rs.ru.rw.sa.sb.sc.sd.se.sg.sh.si.sj.sk.sl.sm.sn.so.sr.st.su.sv.sx.sy.sz.tc.td.tf.tg.th.tj.tk.tl.tm.tn.to.tr.tt.tv.tw.tz.ua.ug.uk.us.uy.uz.va.vc.ve.vg.vi.vn.vu.wf.ws.ye.yt.za.zm.zw".split(".");
var TLD_url = "https://gitcdn.xyz/repo/umpirsky/tld-list/master/data/en/tld.json";
var TLD_cache = "./tld.json";
var TLD_refresh_msec = 15 * 24 * 60 * 60 * 1000;
var TLD_last_msec;
var TLD_default_filter=function(dom){return dom.substr(0,3)!="xn-"};
function getTLDs(cb,filter_func){
if (typeof cb!=='function') return TLDs;
var
read,fetch,
CB_WRAP=function(tlds){
return cb(
filter_func===false
? cb(tlds)
: tlds.filter(
typeof filter_func==='function'
? filter_func
: TLD_default_filter)
);
},
check_mtime = function(mtime) {
if (Date.now()-mtime > TLD_refresh_msec) {
return fetch();
}
if (TLDs) return CB_WRAP (TLDs);
return read();
};
fetch = function(){
getJSON(TLD_url,function(err,data){
if (err) {
console.log("exception in getTLDs.fetch:",err.message||err);
return read(true);
} else {
TLDs=Object.keys(data);
fs.writeFile(TLD_cache,JSON.stringify(TLDs),function(err){
if (err) {
// ignore save error, we have the data
CB_WRAP(TLDs);
} else {
// get mmtime for the file we just made
fs.stat(TLD_cache,function(err,stats){
if (!err && stats) {
TLD_last_msec = stats.mtimeMs;
}
CB_WRAP(TLDs);
});
}
});
}
});
};
read=function(bypassFetch) {
fs.readFile(TLD_cache,'utf-8',function(err,json){
try {
if (err) {
if (bypassFetch) {
// after a http errror, we fallback to hardcoded basic list of tlds
// if the disk file is not readable
console.log("exception in getTLDs.read.bypassFetch:",err.messsage||err);
throw err;
}
// if the disk read failed, get the data from the CDN server instead
return fetch();
}
TLDs=JSON.parse(json);
if (bypassFetch) {
// we need to update stats here as fetch called us directly
// instead of being called by check_mtime
return fs.stat(TLD_cache,function(err,stats){
if (err) return fetch();
TLD_last_msec =stats.mtimeMs;
return CB_WRAP(TLDs);
});
}
} catch (e){
// after JSON error, if we aren't in an http fail situation, refetch from cdn server
if (!bypassFetch) {
return fetch();
}
// after a http,disk,or json parse error, we fallback to hardcoded basic list of tlds
console.log("exception in getTLDs.read:",err.messsage||err);
TLDs=TLDs_fallback;
}
return CB_WRAP(TLDs);
});
};
if (TLD_last_msec) {
return check_mtime(TLD_last_msec);
} else {
fs.stat(TLD_cache,function(err,stats){
if (err) return fetch();
TLD_last_msec =stats.mtimeMs;
return check_mtime(TLD_last_msec);
});
}
}
getTLDs(console.log.bind(console));
I'm trying to write a piece of my upload code as a service, because I need that function overall in my software. My project use sails.js - here the doc for a service.
In a controller I got this code, which uploads a file and after success it calls the function saveTheCampaign() and saves the file information in the DB.
req.file('logo').upload({
maxBytes: 10000000,
saveAs: function (uploadFile, cb) {
cb(null, Date.now() + uploadFile.filename);
},
dirname: sails.config.appPath + '/assets/images/campaign/'
}, function (err, uploadedFiles) {
if (err) {
return res.json(500, err);
}
else if (uploadedFiles.length === 0) {
// proceed without files
res.json({ error: "No image found for upload!"})
}
else {
// Success: handle uploaded file
var fileName = uploadedFiles[0].fd.split('\\');
params["logo"] = fileName[fileName.length - 1];
sails.controllers.campaign.saveTheCampaign(params, req, res);
}
});
saveTheCampaign: function (params, req, res) { //...}
Now I wanted to write this snippet as a service. My service is called UploadService and has a function called upload(), services can take two(2) arguments, option and a callback function. So I tried this to call the upload function of my service:
UploadService.upload(options, sails.controllers.campaign.saveTheCampaign(params, req, res));
The problem is, the params of the callback function (params, req, res) are not known at the time of the call, I get them AFTER the upload function is finished. How can I handle this?
One way to make this happen by using Q Promise Library. The snippet below is a working example for the same. You'll need to set value for sails.config.appPath.
Routes.js
'POST /upload' : 'CampaignController.upload'
UploadService.js
let q = require("q"); // https://github.com/kriskowal/q
module.exports = {
upload: function(options) {
let deferred = q.defer();
options['req'].file(options['fileFieldName']).upload({
maxBytes: 10,
saveAs: function(uploadedFile, cb) {
cb(null, Date.now() + uploadedFile.filename);
},
dirname: sails.config.appPath + '/assets/images/'
}, function(err, uploadedFiles) {
if (err) {
deferred.reject(err);
} else if (uploadedFiles.length === 0) {
// proceed without files
deferred.reject("No image found for upload!");
} else {
// Success: handle uploaded file
let params = [];
var fileName = uploadedFiles[0].fd.split('\\');
params["logo"] = fileName[fileName.length - 1];
deferred.resolve(params)
}
});
return deferred.promise;
}
}
CampaignController.js
module.exports = {
upload: function(req, res) {
let options = [];
options['fileFieldName'] = 'logo';
options['req'] = req;
UploadService.upload(options)
.then((params) => {
sails.controllers.campaign.saveTheCampaign(params);
res.send("Campaign Saved Successfully");
})
.catch((err) => res.send(err))
},
saveTheCampaign: function(params) {
console.log(`campaign ${params['logo']} saved`);
}
}
I want to convert the following code to use promise. It is working and output a user's attributes within the active directory.
var client = ldap.createClient({
url: ldap_url
});
client.bind(ldap_username, ldap_password, function (err) {
client.search(ldap_dn_search, opts, function (err, search) {
search.on('searchEntry', function (entry) {
var user = entry.object;
// It is working!!!. It outputs all user attributes.
console.log(user);
});
});
});
The following is my attempt, butit doesn't output anything.
var Promise = require('promise');
var client_bind = Promise.denodeify(client.bind);
var client_search = Promise.denodeify(client.search);
client_bind(ldap_username, ldap_password)
.then(function(err){
client_search(ldap_dn_search, opts)
.then(function(search){
var search_on = Promise.denodeify(search.on);
search_on('searchEntry')
.then(function(entry){
var user = entry.object;
// It doesn't output anything !!!
console.log(user);
});
});
});
I had the same problem.
Search emits events, so we need something that processes them and passes further along the chain.
Here is piece of code, that works for me:
var ldap = require('ldapjs');
var promise = require('bluebird');
var client = ldap.createClient({url: app.settings['ldap']['server']});
var uid;
promise.promisifyAll(client);
function searchPromise(res, notfoundtext) {
return new Promise(function(resolve, reject) {
var found = false;
res.on('searchEntry', function(entry) {
found = true;
resolve(entry);
});
res.on('error', function(e) {
reject(e.message);
});
res.on('end', function() {
if (!found) {
reject(notfoundtext);
}
});
});
}
client.searchAsync(app.settings['ldap']['baseDn'], {filter: '(mail='+credentials.email+')', scope: 'sub'})
.then(function(res) {
return searchPromise(res, 'User isn\'t exists.');
})
.then(function (entry) {
uid = entry.object.uid;
return client.bindAsync(entry.object.dn, credentials.password);
})
.then(function() {
return client.searchAsync('cn='+app.settings['ldap']['group']+',cn=groups,'+app.settings['ldap']['baseDn'], {scope: 'sub', filter: '(memberUid='+uid+')'});
})
.then(function(res) {
return searchPromise(res, 'User is not in group ' + app.settings['ldap']['group']);
})
.then(function() {
console.log('All is ok');
})
.catch(function(message) {
console.log('Error:' + message);
});
Immediately after the search I add one more step that catches the events, processes them, and passes it further along the chain. This makes the function searchPromise.
Good luck coding )
Most likely those methods do require to be called on client as a context, so you will need to bind() them before passing them to Promise.denodeify:
var client_bind = Promise.denodeify(client.bind.bind(client));
var client_search = Promise.denodeify(client.search.bind(client));
Also, a proper use of promises would look like this:
client_bind(ldap_username, ldap_password).then(function() {
return client_search(ldap_dn_search, opts);
// ^^^^^^ always return something from the callback
}).then(function(search) { // flatten your chain
return Promise.denodeify(search.on).call(search, 'searchEntry');
// ^^^^^^ an alternative to `bind`
}).then(function(entry){
var user = entry.object;
console.log(user);
}).catch(function(err) { // always catch errors!
console.error(err);
});
Using Bluebird Promises, the easy way to do this is to create your client normally, and then run the promisifyAll() on the client.
var ldap = require('ldapjs');
var Promise = require('bluebird');
var client = ldap.createClient({
url: 'ldap://my-server:1234',
});
Promise.promisifyAll(client);
Now you can call client.addAsync() and client.searchAsync() and such.
client.bindAsync(secUserDn, secUserPassword)
.then(doSearch) // if it works, call doSearch
.catch(function (err) { // if bind fails, handle it
console.error('Error on bind', err)
});
function doSearch(data) {
client.searchAsync('CN=A Test,OU=Users,DC=website,DC=com', options)
.then(function (data) { // Handle the search result processing
console.log('I got a result');
})
.catch(function (err) { // Catch potential errors and handle them
console.error('Error on search', err);
});
}
i had the same issue here but i solved it by adding promise and resolve the response without using bluebird, this is an exemple of my code :
async getLdapUser(username: any): Promise<any> {
let myPromise = new Promise<boolean>((resolve, reject) => {
console.log('ssssssssss', username);
const adSuffix = 'OU=xxxx,OU=xxxxx,DC=xxxxxxx,DC=xxxxxx';
const password = 'xxxxxxxxxxxxx';
// Create client and bind to AD
const client = ldap.createClient({
url: 'ldap://1.1.1.1:389',
});
// promise.promisifyAll(client);
let resp = false;
// console.log(client);
client.bind('userTest', password,(err: any) => {
console.log('RESP', resp);
if (err) {
console.log('Error in new connetion ' + err);
} else {
/*if connection is success then go for any operation*/
console.log('Success');
const searchOptions: {} = {
scope: 'sub',
filter: '(sAMAccountName=' + username + ')',
attributes: ['sAMAccountName'],
};
client.search(adSuffix, searchOptions, (err: any, res: any) => {
assert.ifError(err);
res.on('searchEntry', (entry: any) => {
resp = true;
});
res.on('error', (error: any) => {
console.log('err');
reject(error.message);
});
await res.on('end', (result: any) => {
resolve(resp);
});
});
}
});
});
return myPromise;
}
I am looking to do a get, run a function on the results which will do some manipulation by updating a field, and then put that doc back into the database. Really my issue is being able to chain together multiple DB calls. I have been struggling with this the past week or so. Any suggestions appreciated, thanks.
Here is what I have tried so far but I am receiving an error:
function geocode_cleanup(request, response, next) {
r.table('dealer_locations').filter(r.row('geodata').match('No geodata found.'))
.do(function(row) {
var geodata = opencage_geocoder.geocode(row.Address, function(error, response) {
if (error) {
console.log("Error.");
row.geodata = "No geodata found.";
row.active = true;
} else if (response.length == 0) {
console.log("Empty response.");
} else {
console.log("Success.");
console.log(response);
var latitude = response[0].latitude;
var longitude = response[0].longitude;
row.geodata = r.point(longitude, latitude);
row.active = true;
}
});
return r.table('dealer_locations').update({
geodata: geodata
})
}).run(conn, function(error, cursor) {
response.setHeader("Content-Type", "application/json");
if (error) {
handleError(response, error);
} else {
cursor.toArray(function(error, results) {
if (error) {
handleError(response, error);
} else {
response.send(results);
};
});
}
next();
})
};
Also, this gives the desired results returned in the response, but the second db action never happens because I am still inside of the same db connection I think:
function geocode_cleanup(request, response, next) {
var conn = request._rdbConn;
r.table('dealer_locations').filter({geodata: "No geodata found."}).run(conn, function(error, cursor) {
if (error) {
handleError(response, error);
} else {
cursor.toArray(function(error, results) {
if (error) {
handleError(response, error);
} else {
var i = 1;
async.forEach(results, function(item, callback) {
var address = (item.Address + " " + item.City).toString();
opencage_geocoder.geocode(address, function(err, res) {
if (err) {
console.log(i);
console.log("Error.");
item.id = i;
item.geodata = "No geodata found.";
item.active = true;
i++;
callback();
} else if (res.length == 0) {
console.log(i);
console.log("Empty response.");
i++;
callback();
} else {
console.log(i);
console.log("Success.");
console.log(res);
var latitude = res[0].latitude;
console.log(i + " " + latitude);
var longitude = res[0].longitude;
console.log(i + " " + longitude);
item.id = i;
item.geodata = r.point(longitude, latitude);
item.active = true;
i++;
callback();
}
});
}, function() {
r.table('dealer_locations').insert(results, {
conflict: "replace"
}).run(request._rdbConn, function(error, results) {
if (error) {
console.log("Data not inserted!");
} else {
console.log("Data inserted!");
}
});
console.log("Done!");
response.send(results);
});
}
})
}
})
}
Here's a possible solution which uses promises to organize the code a little bit.
// Guarantee support for promises and provide the `promisify` function
var Promise = require('bluebird');
// Promisify the geocode function to make it easier to use
var geocode = Promise.promisify(opencage_geocoder.geocode);
function geocode_cleanup(request, response, next) {
var conn = request._rdbConn;
r
.table('dealer_locations')
.filter(r.row('geodata').match('No geodata found.'))
.coerceTo('array')
.run(conn)
.then(function(rows) {
// This promise will be resolve when all rows have been geocoded and updated
// We map the rows into an array of promises, which is what Promise.all takes
return Promise.all(rows.map(function (row) {
return geocode(row.Address)
.then(function (response) {
console.log("Success.");
var latitude = response[0].latitude;
var longitude = response[0].longitude;
row.geodata = r.point(longitude, latitude);
row.active = true;
// Return the row
return row;
});
});
}));
})
.then(function (rows) {
// Now that all `dealer_locations` have been updated, re-query them
return r
.table('dealer_locations')
.insert(rows, {conflict: "update", return_changes: true})
.run(conn);
})
.then(function (results) {
// Send the response;
response.setHeader("Content-Type", "application/json");
response.send(results);
return;
})
.catch(function (err) {
return handleError(null, error);
})
};
Some problems I noticed with your code:
1. Use of do
r.table('dealer_locations').filter(r.row('geodata').match('No geodata found.'))
.do(function(row) {
var geodata = opencage_geocoder.geocode ...
})
In this code snippet, you use a JS function inside of do. You can't do that. Remember that what happens inside of do happens in the RethinkDB server (not in your Node.js server). Your RethinkDB server has no knowledge of your opencage_geocoder function and so this woudn't work.
Whatever do returns must be a valid ReQL query or ReQL expression. You can't execute arbitrary JavaScript inside of it.
If you want to run JavaScript with your query results, you have to .run the query and then do whatever you want to do inside the callback or .then function. At that point, that code will get executed in JavaScript and not in your RethinkDB server.
2. Use of update
return r.table('dealer_locations').update({
geodata: geodata
})
The update method can only update a single document. You can't pass it an array of documents. In this scenario you what have needed to do r.table().get().update() in order for this to work, because you have to be referencing a single document when you update something.
If you have an array of documents that you want to update, you can use the forEach method.
r.table('hello')
.merge({
'new_property': 'hello!'
})
.forEach(function (row) {
// Insert that property into the document
return r.table('hello').get(row.id).update(row);
})
You can also do this (which you are already doing):
r.table('hello')
.merge({
'new_property': 'hello!'
})
.do(function (rows) {
// Insert that property into the document
return r.table('hello')
.insert(rows, {conflict: "update", return_changes: true});
})
OK, I have a suggestion. This queries for the documents you're interested in, modifies them (on your app server, not in the db) and then reinserts them using the nifty conflict: 'update' option. It also uses promises because I think that's a bit cleaner.
function geocode_cleanup(request, response, next) {
r.table('dealer_locations')
.filter(r.row('geodata').match('No geodata found.'))
.run(conn).then(function(cursor) {
var to_update = [];
return cursor.toArray().then(function getGeocodes(rows) {
return rows.map(function getGeocode(row) {
row.geodata = opencage_geocoder.geocode(row.Address, function(error, response) {
if (error) {
console.log("Error.");
row.geodata = "No geodata found.";
row.active = true;
} else if (response.length == 0) {
console.log("Empty response.");
} else {
console.log("Success.");
console.log(response);
var latitude = response[0].latitude;
var longitude = response[0].longitude;
row.geodata = r.point(longitude, latitude);
row.active = true;
}
});
return row;
});
});
}).then(function doneGeocoding(modified_rows){
return r.table('dealer_locations')
.insert(modified_rows, {conflict: "update", return_changes: true})('changes')
.coerceTo('array')
.run(conn);
}).then(function finishResponse(changes){
response.setHeader("Content-Type", "application/json");
response.send(results);
next();
}).catch(function(err) {
// handle errors here
});
};
Caveat emptor, I haven't run this, so there may be syntax errors and things