Skip to content

Commit

Permalink
Merge dbe56b2 into 32ddea2
Browse files Browse the repository at this point in the history
  • Loading branch information
EPICmynamesBG committed Oct 5, 2018
2 parents 32ddea2 + dbe56b2 commit 030d212
Show file tree
Hide file tree
Showing 18 changed files with 435 additions and 18 deletions.
6 changes: 6 additions & 0 deletions api/controllers/slackController.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ const errors = require('common-errors');
const logger = require('../helpers/logger');
const utils = require('../helpers/utils');
const slackUtils = require('../helpers/slack').utils;
const config = require('../../config/config');
const { CMD_USAGE, SUPPORTED_COMMANDS } = require('../../config/constants').SLACK_CONSTS;

const oauthService = require('../services/oauthService');
Expand Down Expand Up @@ -65,6 +66,11 @@ function handleSlack(req, res) {
lodash.set(req, 'swagger.params.body.value.userName', action.params.user.username);
importController.importSoups(req, res);
break;
case 'version':
utils.processResponse(null,
{ text: `subito-bot version ${lodash.get(config, 'SWAGGER.APP_VERSION', 'unknown')}` },
res);
break;
default: {
logger.warn('Unsupported command', action.command);
let message = "Whoops, I don't recognize that command. Try one of these instead!";
Expand Down
11 changes: 10 additions & 1 deletion api/helpers/utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -158,6 +158,14 @@ function parseTime (text) {
return time.format('HH:mm:ss');
}

class SafeError extends Error {
constructor(...args) {
super(...args);
this.safe = true;
this.name = 'SafeError';
}
}

module.exports = {
trimChar: trimChar,
textForDate: textForDate,
Expand All @@ -172,5 +180,6 @@ module.exports = {
textCleaner: textCleaner,
encrypt: encrypt,
decrypt: decrypt,
parseTime: parseTime
parseTime: parseTime,
SafeError: SafeError
};
57 changes: 56 additions & 1 deletion api/services/importerService.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

const _ = require('lodash');
const async = require('async');
const moment = require('moment');

const logger = require('../helpers/logger');
const importHelper = require('../helpers/importer');
const slack = require('../helpers/slack');
const soupCalendarService = require('./soupCalendarService');
const integrationSubscriberViewService = require('./integrationSubscriberViewService');
const { SafeError } = require('../helpers/utils');


function processUrl(db, url, options, callback) {
Expand All @@ -28,6 +31,10 @@ function processUrl(db, url, options, callback) {
`PDF Imported ${stats.rows} soups for ${stats.startDate} - ${stats.endDate}` :
'PDF Imported';
const message = err ? err.message : successMessage;

if (stats) {
module.exports.performDateValidation(db, [stats.startDate, stats.endDate]);
}
if (options.webhookUrl) {
slack.messageResponseUrl(options.webhookUrl, message);
} else {
Expand All @@ -37,6 +44,54 @@ function processUrl(db, url, options, callback) {
});
}

function _buildInvalidDatesMessage(invalidDates) {
const message = '*Warning*: The following dates do not have 2 soup records:';
return _.reduce(invalidDates, (str, { day, soup_count }) => {
return str + `\n> ${moment(day).format('dddd, MMM D')} - ${soup_count}`;
}, message);
}

/**
* Validates that each day in the date range has 2 rows
* @param db {object} Database object
* @param dateRange {text[]} date range to validate. example: [startDate, endDate]
* @param callback {function} callback. invoked with nothing
*/
function performDateValidation(db, dateRange, callback = _.noop) {
const [startDate, endDate] = dateRange;
async.autoInject({
invalidDates: (cb) => {
soupCalendarService.validateSoupsForRange(db, startDate, endDate, (err, rows) => {
if (err) cb(err);
else if (_.isEmpty(rows)) cb(new SafeError(`No rows breaking validation for ${startDate} - ${endDate}`));
else cb(err, rows);
});
},
admins: (invalidDates, cb) => integrationSubscriberViewService.getAdmins(db, true, cb),
sendMessages: (invalidDates, admins, cb) => {
const message = _buildInvalidDatesMessage(invalidDates);
logger.warn(message);
async.each(admins, (admin, ecb) => {
slack.messageUserAsBot(admin.slack_user_id, message, admin.slack_slash_token, (e, res) => {
if (e) logger.error('Validation message error for admin', admin.id, e);
else if (res) logger.debug('Validation message sent for admin', admin.id, res);
ecb();
});
}, cb);
}
}, (err, { invalidDates, admins }) => {
if (err && err.safe) {
logger.debug(err);
} else if (err) {
logger.error(err);
} else {
logger.info(`${admins.length} admins notified of ${invalidDates.length} days without 2 soups`);
}
callback(err);
});
}

module.exports = {
processUrl: processUrl
processUrl: processUrl,
performDateValidation: performDateValidation
};
14 changes: 13 additions & 1 deletion api/services/integrationSubscriberViewService.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,18 @@ function getAll(db, decrypt = false, callback) {
});
}

function getAdmins(db, decrypt = false, callback) {
if (typeof decrypt === 'function') {
callback = decrypt; // eslint-disable-line no-param-reassign
decrypt = false; // eslint-disable-line no-param-reassign
}
queryHelper.select(db, 'integration_subscriber_view', { is_admin: 1 }, (err, rows) => {
if (decrypt) callback(err, rows ? rows.map(_mapDecrypt) : []);
else callback(err, rows ? rows : []);
});
}

module.exports = {
getAll: getAll
getAll: getAll,
getAdmins: getAdmins
}
14 changes: 13 additions & 1 deletion api/services/soupCalendarService.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,8 +91,20 @@ function massUpdate(db, soupDays, user, callback) {
});
}

function validateSoupsForRange(db, startDate, endDate, callback) {
const start = moment(startDate).format('YYYY/MM/DD');
const end = moment(endDate).format('YYYY/MM/DD');
const queryStr = `SELECT COUNT(id) AS soup_count, day FROM soup_calendar
WHERE \`day\` >= DATE(?) AND \`day\` <= DATE(?)
GROUP BY day
HAVING soup_count != 2
ORDER BY \`day\`;`;
queryHelper.custom(db, queryStr, [start, end], callback);
}

module.exports = {
searchForSoup: searchForSoup,
searchForSoupOnDay: searchForSoupOnDay,
massUpdate: massUpdate
massUpdate: massUpdate,
validateSoupsForRange: validateSoupsForRange
};
7 changes: 7 additions & 0 deletions api/services/subscriberService.js
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ function getSubscribersForTeam(db, teamId, callback) {
queryHelper.select(db, 'subscribers', { slack_team_id: teamId }, callback);
}

function getAdmins(db, callback) {
queryHelper.select(db, 'subscribers', { is_admin: true }, (e, res) => callback(e, res || []));
}

function getSubscriberById(db, id, callback) {
queryHelper.selectOne(db, 'subscribers', { id: id }, callback);
}
Expand All @@ -76,6 +80,7 @@ function updateSubscriberBySlackUserId(db, slackId, updateObj, callback) {
if (clone.notify_time) {
clone.notify_time = utils.parseTime(clone.notify_time);
}
lodash.unset(clone, 'is_admin');
queryHelper.update(db, 'subscribers', clone, { slack_user_id: slackId }, callback);
}

Expand All @@ -91,10 +96,12 @@ function deleteSubscriberBySlackUsername(db, slackName, slackTeamId, callback) {
queryHelper.deleteOne(db, 'subscribers', { slack_username: slackName, slack_team_id: slackTeamId }, callback);
}


module.exports = {
addSubscriber: addSubscriber,
getSubscribers: getSubscribers,
getSubscribersForTeam: getSubscribersForTeam,
getAdmins: getAdmins,
getSubscriberById: getSubscriberById,
getSubscriberBySlackUserId: getSubscriberBySlackUserId,
getSubscriberBySlackUsername: getSubscriberBySlackUsername,
Expand Down
2 changes: 1 addition & 1 deletion config/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ const config = {

SWAGGER: {

APP_VERSION: '8.0.1',
APP_VERSION: '8.1.1',

HOSTNAME: process.env.HOST || 'localhost',

Expand Down
8 changes: 5 additions & 3 deletions config/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ const lodash = require('lodash');
module.exports = {
SLACK_CONSTS: {
SUPPORTED_COMMANDS: lodash.clone(
['day', 'search', 'subscribe', 'unsubscribe', 'feedback', 'week', 'settings', 'import']
['day', 'search', 'subscribe', 'unsubscribe', 'feedback', 'week', 'settings', 'import', 'version']
),

CMD_USAGE: {
Expand All @@ -16,7 +16,8 @@ module.exports = {
feedback: '<https://github.com/EPICmynamesBG/subito-bot/issues|Submit feedback on GitHub>',
week: '[today | tomorrow | yesterday | [YYYY-MM-DD]]',
settings: '[notify (example: 8:00 am)]',
import: '[PDF url]'
import: '[PDF url]',
version: ''
},

CMD_PARAM_MAP: lodash.cloneDeep({
Expand All @@ -29,7 +30,8 @@ module.exports = {
settings: {
notify: ['time']
},
import: ['url']
import: ['url'],
version: []
}),

CMD_TEMPLATE: lodash.cloneDeep({
Expand Down
2 changes: 1 addition & 1 deletion config/production.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ module.exports = {

SWAGGER: {

APP_VERSION: '8.0.1',
APP_VERSION: '8.1.1',

HOSTNAME: process.env.HOST || 'localhost',

Expand Down
15 changes: 15 additions & 0 deletions db-migrations/sql/13-subscribers-add-admin-down.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
CREATE OR REPLACE VIEW integration_subscriber_view AS (
SELECT subscribers.id,
subscribers.slack_user_id,
subscribers.slack_username,
subscribers.slack_team_id,
subscribers.search_term,
subscribers.notify_time,
oauth_integrations.domain AS slack_team_domain,
oauth_integrations.bot_token AS slack_slash_token,
oauth_integrations.webhook_url AS slack_webhook_url
FROM subscribers
INNER JOIN oauth_integrations ON (subscribers.slack_team_id = oauth_integrations.team_id)
);

ALTER TABLE subscribers DROP COLUMN is_admin;
17 changes: 17 additions & 0 deletions db-migrations/sql/13-subscribers-add-admin-up.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
ALTER TABLE subscribers
ADD COLUMN is_admin BOOLEAN NOT NULL DEFAULT FALSE;

CREATE OR REPLACE VIEW integration_subscriber_view AS (
SELECT subscribers.id,
subscribers.slack_user_id,
subscribers.slack_username,
subscribers.slack_team_id,
subscribers.search_term,
subscribers.notify_time,
subscribers.is_admin,
oauth_integrations.domain AS slack_team_domain,
oauth_integrations.bot_token AS slack_slash_token,
oauth_integrations.webhook_url AS slack_webhook_url
FROM subscribers
INNER JOIN oauth_integrations ON (subscribers.slack_team_id = oauth_integrations.team_id)
);
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "subito",
"version": "8.0.1",
"version": "8.1.1",
"private": false,
"description": "A slack slash bot for getting Subito soups",
"repository": {
Expand Down
23 changes: 22 additions & 1 deletion test/api/controllers/slackControllerTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const oauthService = require('../../../api/services/oauthService');
const importerService = require('../../../api/services/importerService');
const slack = require('../../../api/helpers/slack');

const { SLACK_VERIFICATION_TOKEN } = require('../../../config/config');
const { SLACK_VERIFICATION_TOKEN, SWAGGER } = require('../../../config/config');

const validAuth = {
team_id: 'ABCDEF123',
Expand Down Expand Up @@ -342,6 +342,27 @@ describe('slackController', () => {
});
});

it('should 200 and return text for "version"', (done) => {
request(server)
.post(url)
.type('form')
.send({
token: validAuth.token,
text: 'version',
user_id: 'ABC123',
user_name: 'testuser',
team_id: validAuth.team_id
})
.set('Accept', 'application/json')
.expect('Content-Type', /json/)
.expect(200)
.end((err, res) => {
should.not.exist(err);
assert.strictEqual(res.body.text, `subito-bot version ${SWAGGER.APP_VERSION}`);
done();
});
});

it('should 200 and return a usage description when given an unknown command', (done) => {
request(server)
.post(url)
Expand Down

0 comments on commit 030d212

Please sign in to comment.