Snippets

VirtualWolf sendUpdate() before and after

Created by VirtualWolf last modified
// Original version using callbacks
sendUpdate: callback => {
    const request = require('superagent');

    HipChatWeather.find({})
    .exec((HipChatWeatherModelError, results) => {
        if (HipChatWeatherModelError) { sails.log.error(HipChatWeatherModelError); return callback(HipChatWeatherModelError, null); }
        if (results.length === 0) { return callback(null, null); }

        ninjaBlockService.getCurrentTemperatureAndHumidity({location: 'outdoor'}, (ninjaBlockServiceError, data) => {
            if (ninjaBlockServiceError) { return callback(ninjaBlockServiceError, null); }

            async.parallel(results.map(install => cb => {
                hipChatTokenService.getToken({
                    capabilitiesUrl: install.capabilitiesurl,
                    oauthId: install.oauthid,
                    oauthSecret: install.oauthsecret,
                }, (tokenError, tokenData) => {
                    if (tokenError) { return cb(tokenError, null); }
                    request.post(`${tokenData.apiUrl}/addon/ui/room/${install.roomid}`)
                        .set('Authorization', `Bearer ${tokenData.accessToken}`)
                        .send({
                            glance: [
                                {
                                    content: {
                                        label: {
                                            type: 'html',
                                            value: `<b>${data.temperature}</b>&deg;C &amp; ${data.humidity}%`,
                                        },
                                    },
                                    key: 'weather-glance',
                                },
                            ],
                        })
                        .end((postUpdateError, res) => {
                            if (postUpdateError) { return cb(postUpdateError.response.res.body, null); }
                            return cb(null, res.body);
                        });
                });
            }), (err, results) => {
                if (err) { return callback(err, null); }
                return callback(null, results);
            });
        });
    });
};
// Updated version using Promises. Much easier to follow!
sendUpdate: () => {
    const request = require('superagent');

    const getRoomDetails = options => {
        return hipChatTokenService.getToken({
            capabilitiesUrl: options.capabilitiesUrl,
            oauthId: options.oauthId,
            oauthSecret: options.oauthSecret,
        }).then(results => {
            return {
                roomId: options.roomId,
                groupId: options.groupId,
                accessToken: results.accessToken,
                apiUrl: results.apiUrl,
            };
        });
    };

    const postUpdate = options => {
        return request.post(`${options.apiUrl}/addon/ui/room/${options.roomId}`)
        .set('Authorization', `Bearer ${options.accessToken}`)
        .send({
            glance: [{
                    content: {
                        label: {
                            type: 'html',
                            value: `<b>${options.temperature}</b>&deg;C &amp; ${options.humidity}%`,
                        },
                    },
                    key: 'weather-glance',
                },],
        }).then(response => {
            return response;
        }).catch(err => {
            throw new Error(err);
        });
    };

    return HipChatWeather.find({})
    .then(results => {
        if (results.length === 0) { throw new Error('No installations registered'); }
        return results;
    }).then(results => {
        return Promise.all(results.map(installation => {
            return getRoomDetails({
                roomId: installation.roomid,
                groupId: installation.groupid,
                capabilitiesUrl: installation.capabilitiesurl,
                oauthId: installation.oauthid,
                oauthSecret: installation.oauthsecret,
            });
        }));
    }).then(rooms => {
        return Promise.all([
            rooms,
            ninjaBlockService.getCurrentTemperatureAndHumidity({location: 'outdoor'}),
        ]);
    }).then(results => {
        const rooms = results[0];
        const currentTemperatureData = results[1];
        return Promise.all(rooms.map(room => {
            return postUpdate({
                apiUrl: room.apiUrl,
                accessToken: room.accessToken,
                roomId: room.roomId,
                temperature: currentTemperatureData.temperature,
                humidity: currentTemperatureData.humidity,
            });
        }));
    }).catch(err => {
        throw err;
    });
};
// The original Promise version above turned out to be not particularly easy to follow when coming back to it after several months!
//
// This is a fair bit longer than either of the above, but contains _all_ of the little helper functions along the way (the previous
// versions cheated a bit by not including everything). The final updateGlance() function is the one that hooks it all together.

findCurrentInstallations: () => {
    return HipChatWeather.find({})
    .then(results => {
        if (results.length === 0) { throw new Error('No installations registered'); }
        return results;
    }).catch(err => {
        throw err;
    })
};

getTokenAndApiUrls: capabilitiesUrl => {
    return request.get(capabilitiesUrl)
    .then(result => {
        return {
            tokenUrl: result.body.capabilities.oauth2Provider.tokenUrl,
            apiUrl: result.body.links.api,
        }
    })
    .catch(err => {
        throw err;
    });
};

generateAccessTokenTuples: (installations, tokenAndApiUrls) => {
    return Promise.all(installations.map((install, index, array) => {
        return {
            tokenUrl: tokenAndApiUrls[index].tokenUrl,
            apiUrl: tokenAndApiUrls[index].apiUrl,
            oauthId: install.oauthid,
            oauthSecret: install.oauthsecret,
            roomId: install.roomid,
        }
    }));
};

getAccessToken: ({tokenUrl, oauthId, oauthSecret}) => {
    return request.post(tokenUrl)
        .type('form')
        .send('grant_type=client_credentials')
        .auth(oauthId, oauthSecret)
    .then(result => result.body.access_token)
    .catch(err => {
        throw err;
    });
};

postUpdateToHipChat: ({apiUrl, roomId, accessToken, payload}) => {
    const url = `${apiUrl}/addon/ui/room/${roomId}`;

    return request.post(url)
        .set('Authorization', `Bearer ${accessToken}`)
        .send(payload)
    .catch(err => {
        throw err;
    })
};

updateGlance: () => {
    return hipChatService.findCurrentInstallations()
    .then(results => Promise.all([
        results,
        Promise.all(results.map(result => hipChatService.getTokenAndApiUrls(result.capabilitiesurl))),
    ]))
    .then(results => {
        const [installations, tokenAndApiUrls] = results;
        return hipChatService.generateAccessTokenTuples(installations, tokenAndApiUrls);
    })
    .then(results => {
        return Promise.all([
            results,
            Promise.all(results.map(result => hipChatService.getAccessToken({
                tokenUrl: result.tokenUrl,
                oauthId: result.oauthId,
                oauthSecret: result.oauthSecret})
            ))
        ])
    })
    .then(results => {
        return Promise.all([
            results,
            weatherService.getCurrentTemperatureAndHumidity({location: 'outdoor'}),
        ]);
    })
    .then(results => {
        const [[installations, tokens], weather] = results;
        const payload = {
            glance: [
                {
                    content: {
                        label: {
                            type: 'html',
                            value: `<b>${weather.temperature}</b>&deg;C &amp; ${weather.humidity}%`,
                        },
                    },
                    key: 'weather-glance',
                },
            ],
        };

        return Promise.all(installations.map((install, index, array) => {
            return hipChatService.postUpdateToHipChat({
                apiUrl: install.apiUrl,
                roomId: install.roomId,
                accessToken: tokens[index],
                payload,
            });
        }));
    })
    .catch(err => {
        throw err;
    });
};
// The above file _still_ wasn't great. updateGlance() at the bottom is hard to follow, and thanks
// to all the `Promise.all`s, if any of the steps failed at any point, the other installations wouldn't
// be updated. This updated version uses Bluebird instead of native Promises and passes each call's
// results along via `this`, and each installation is fired off independently of the others. I find it
// reads a hell a lot more easily, especially sendUpdateToHipChatInstallation() on line 34.

module.exports = {
    updateGlances: () => {
        return findCurrentInstallations()
        .then(function(results) {
            return results.map(function(result) {
                return Promise
                    .resolve()
                    .bind(result)
                    .then(sendUpdateToHipChatInstallation);
            });
        })
        .catch(err => {
            throw err;
        });
    },
};

function findCurrentInstallations() {
    return HipChatWeather.find({})
    .then(results => {
        if (results.length === 0) { throw new Error('No installations registered'); }
        return results;
    }).catch(err => {
        throw err;
    });
}

function sendUpdateToHipChatInstallation() {
    return Promise
        .resolve()
        .bind(this)
        .then(getTokenAndApiUrls)
        .then(getAccessToken)
        .then(getLatestWeatherData)
        .then(postUpdateToHipChat)
        .catch(err => {
            sails.log.error(err);
        });
}

function getTokenAndApiUrls() {
    return Promise
        .resolve()
        .bind(this)
        .then(function() {
            return request.get(this.capabilitiesurl);
        })
        .then(function(result) {
            this.tokenurl = result.body.capabilities.oauth2Provider.tokenUrl;
            this.apiurl = result.body.links.api;
            return this;
        })
        .catch(err => {
            throw new Error(err.message);
        });
}

function getAccessToken() {
    return Promise
        .resolve()
        .bind(this)
        .then(function() {
            return request.post(this.tokenurl)
                .type('form')
                .send('grant_type=client_credentials')
                .auth(this.oauthid, this.oauthsecret);
        })
        .then(function(result) {
            this.accesstoken = result.body.access_token;
            return this;
        })
        .catch(err => {
            throw new Error(err.message);
        });
}

function getLatestWeatherData() {
    return Promise
        .resolve()
        .bind(this)
        .then(function() {
            return weatherService.getCurrentTemperatureAndHumidity({location: 'outdoor'});
        })
        .then(function(result) {
            this.weatherData = result;
            return this;
        })
        .catch(err => {
            throw err;
        });
}

function postUpdateToHipChat() {
    return Promise
        .resolve()
        .bind(this)
        .then(function() {
            const payload = { glance: [ {
                content: {
                    label: {
                        type: 'html',
                        value: `<b>${this.weatherData.temperature}</b>&deg;C &amp; ${this.weatherData.humidity}%`,
                    },
                },
                key: 'weather-glance',
            }]};

            return request.post(`${this.apiurl}/addon/ui/room/${this.roomid}`)
                .set('Authorization', `Bearer ${this.accesstoken}`)
                .send(payload);
        })
        .catch(err => {
            throw new Error(err.message);
        });
}
// And finally, ES2017's async/await! I'm not sure it could be much simpler than this, and it reads
// just like regular synchronous code. It does the same as above, triggering updateGlances() will
// fire off individual requests to each installation, independent of the others.

module.exports = {
    updateGlances: async () => {
        const installations = await findCurrentInstallations();

        return await Promise.all(installations.map(async function(result) {
            await sendUpdateToHipChatInstallation(result);
        }));
    },
};

async function findCurrentInstallations() {
    const results = await HipChatWeather.find({});
    if (results.length === 0) { throw new Error('No installations registered'); }
    return results;
}

async function sendUpdateToHipChatInstallation(installation) {
    const {oauthid, oauthsecret, capabilitiesurl, roomid, groupid} = installation;

    try {
        const {tokenurl, apiurl}    = await getTokenAndApiUrls(capabilitiesurl);
        const accesstoken           = await getAccessToken({tokenurl, oauthid, oauthsecret});
        const weatherData           = await weatherService.getCurrentTemperatureAndHumidity({location: 'outdoor'});
        await postUpdateToHipChat({apiurl, roomid, accesstoken, weatherData});
    } catch (err) {
        sails.log.error(err);
    }
}

async function getTokenAndApiUrls(capabilitiesurl) {
    const result = await request.get(capabilitiesurl);

    return {
        tokenurl: result.body.capabilities.oauth2Provider.tokenUrl,
        apiurl: result.body.links.api,
    }
}

async function getAccessToken({tokenurl, oauthid, oauthsecret}) {
    const result = await request.post(tokenurl)
        .type('form')
        .send('grant_type=client_credentials')
        .auth(oauthid, oauthsecret);

    return result.body.access_token;
}

async function postUpdateToHipChat({apiurl, roomid, accesstoken, weatherData}) {
    const payload = {
        glance: [ {
            content: {
                label: {
                    type: 'html',
                    value: `<b>${weatherData.temperature}</b>&deg;C &amp; ${weatherData.humidity}%`,
                },
            },
            key: 'weather-glance',
        }, ],
    };

    return await request.post(`${apiurl}/addon/ui/room/${roomid}`)
        .set('Authorization', `Bearer ${accesstoken}`)
        .send(payload);
}

Comments (0)