Skip to content

Commit

Permalink
Merge pull request #2 from sinhadotabhinav/issue-#1
Browse files Browse the repository at this point in the history
closing Issue #1 per this pul request
  • Loading branch information
Abhinav Sinha authored May 11, 2021
2 parents cf1ccae + 07adc4c commit b9879c4
Show file tree
Hide file tree
Showing 14 changed files with 274 additions and 149 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ const SERVICE_PROVIDER = 'Gmail';
const RECIPIENT = 'mail1@gmail.com,mail2@gmail.com,mail3@gmail.com';
```

Finally, you can also alter the date range with which the application will fetch vaccination slots by customising **DATE_RANGE** value in [`src/configs/schedulerConfig.js`](https://github.com/sinhadotabhinav/covid-19-vaccine-alerts-cowin/blob/master/src/configs/schedulerConfig.js) file. By default it is set to **10** but, you can change it to 7 or 15 for example, based on your need. The config file also allows changes in the periodic schedule with which the application runs. By default, **SCHEDULE** value depicts a cron schedule **every 3 hours at minute 0**. To alter this schedule, you need to be familiar with the [cron scheduler](https://linuxhint.com/cron_jobs_complete_beginners_tutorial/#:~:text=The%20scheduled%20commands%20and%20scripts,Task%20Scheduler%20in%20Windows%20OS). I use [Crontab Guru](https://crontab.guru) website to test my cron schedules.
Finally, you can also alter the date range with which the application will fetch vaccination slots by customising **DATE_RANGE** value in [`src/configs/schedulerConfig.js`](https://github.com/sinhadotabhinav/covid-19-vaccine-alerts-cowin/blob/master/src/configs/schedulerConfig.js) file. By default it is set to **7** but, you can change it to 10 or 15 for example, based on your need. The config file also allows changes in the periodic schedule with which the application runs. By default, **SCHEDULE** value depicts a cron schedule **every 2 hours at minute 0**. To alter this schedule, you need to be familiar with the [cron scheduler](https://linuxhint.com/cron_jobs_complete_beginners_tutorial/#:~:text=The%20scheduled%20commands%20and%20scripts,Task%20Scheduler%20in%20Windows%20OS). I use [Crontab Guru](https://crontab.guru) website to test my cron schedules.

```
const SCHEDULE = '0 */3 * * *';
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "covid-19-vaccine-alerts-cowin",
"version": "1.0.0",
"version": "1.0.1",
"description": "This is an alerting application that sends email notifications to beneficiaries in India using COWIN platform for vaccine availability",
"main": "src/app.js",
"scripts": {
Expand All @@ -10,7 +10,7 @@
"license": "Self",
"dependencies": {
"axios": "^0.21.1",
"cron": "^1.8.2",
"cron-parser": "^3.5.0",
"dotenv": "^8.2.0",
"moment": "^2.29.1",
"node-cron": "^3.0.0",
Expand Down
8 changes: 4 additions & 4 deletions src/api/routes.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ async function getStates() {
if (result.status == apiConfig.STATUS_OK) {
return result;
} else {
throw 'Unable to get list of states, status code ' + result.status + ': ' + result.statusText;
throw 'Unable to get list of states, status code', result.status, ':', result.statusText;
}
})
}
Expand All @@ -28,7 +28,7 @@ async function getDistricts(districtId) {
if (result.status == apiConfig.STATUS_OK) {
return result;
} else {
throw 'Unable to get list of districts, status code ' + result.status + ': ' + result.statusText;
throw 'Unable to get list of districts, status code', result.status, ':', result.statusText;
}
})
}
Expand All @@ -44,7 +44,7 @@ async function getVaccinationSlotsByPincode(pinCode, date) {
if (result.status == apiConfig.STATUS_OK) {
return result;
} else {
throw 'Unable to get vaccine sessions by pincode, status code ' + result.status + ': ' + result.statusText;
throw 'Unable to get vaccine sessions by pincode, status code', result.status, ':', result.statusText;
}
})
}
Expand All @@ -60,7 +60,7 @@ async function getVaccinationSlotsByDistrict(districtId, date) {
if (result.status == apiConfig.STATUS_OK) {
return result;
} else {
throw 'Unable to get vaccine sessions by distict, status code ' + result.status + ': ' + result.statusText;
throw 'Unable to get vaccine sessions by distict, status code', result.status, ':', result.statusText;
}
})
}
Expand Down
119 changes: 72 additions & 47 deletions src/app.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,29 @@
require('dotenv').config()
const moment = require('moment');
const cron = require('node-cron');
const axios = require('axios');
const moment = require('moment');
const parser = require('cron-parser');
const routes = require('./api/routes')
const appConfig = require('./configs/appConfig');
const schedulerConfig = require('./configs/schedulerConfig');
const alerts = require('./services/alerts');
const routes = require('./api/routes');
const locations = require('./utilities/locations');
const mailConfig = require('./configs/mailConfig');
const schedulerConfig = require('./configs/schedulerConfig');;
const alerts = require('./utilities/alerts');
const appointments = require('./utilities/appointments');
const dailyDigest = require('./utilities/dailyDigest');
const htmlBuilder = require('./utilities/htmlBuilder');
const locations = require('./utilities/locations');
const logger = require('./utilities/logger');

let runCounter = 0;
let firstRun = true;
let vaccinationSlots = [];

async function main() {
try {
cron.schedule(schedulerConfig.SCHEDULE, async () => {
cron.schedule(schedulerConfig.SCHEDULE, async function() {
await fetchVaccinationSlots();
});
} catch (e) {
console.log('There was an error fetching vaccine slots: ' + JSON.stringify(e, null, 2));
throw e;
} catch (error) {
console.log(logger.getLog('There was an error fetching vaccine slots: ' + JSON.stringify(error, null, 2)));
}
}

Expand All @@ -32,26 +36,37 @@ async function fetchVaccinationSlots() {
let pincodeArray = appConfig.PINCODE.split(',');
for( counter = 0; counter < pincodeArray.length; counter++) {
if (pincodeArray[counter].toString().length < 6) {
console.log('Invalid pincode ' + pincodeArray[counter] + ' provided in config file: (src/config/appConfig.js)');
console.log(logger.getLog('Invalid pincode ' + pincodeArray[counter] + ' provided in config file: (src/config/appConfig.js).'));
pincodeArray.splice(counter, 1);
}
}
getAppointmentsByPincode(pincodeArray, dates);
} else if (appConfig.PINCODE.toString().length < 6) {
throw 'Application is set to fetch vaccine slots by pincode but no pincode/ invalid pincode provided in config file: (src/config/appConfig.js)';
throw 'Application is set to fetch vaccine slots by pincode but no pincode/ invalid pincode provided in config file: (src/config/appConfig.js).';
} else {
pincodeArray.push(appConfig.PINCODE);
getAppointmentsByPincode(pincodeArray, dates);
}
} else {
if (appConfig.DISTRICT.length == 0) {
throw 'Application is set to fetch vaccine slots by district but no district name provided in config file: (src/config/appConfig.js)';
throw 'Application is set to fetch vaccine slots by district but no district name provided in config file: (src/config/appConfig.js).';
}
getAppointmentsByDistrict(dates);
}
} catch (e) {
console.log(e);
} catch (error) {
console.log(logger.getLog(error));
}
}

async function getTwoWeekDateArray() {
let dateArray = [];
let currentDate = moment();
for(let counter = 1; counter <= schedulerConfig.DATE_RANGE; counter++) {
let date = currentDate.format(schedulerConfig.DATE_FORMAT);
dateArray.push(date);
currentDate.add(1, 'day');
}
return dateArray;
}

async function getAppointmentsByPincode(pincodeArray, dates) {
Expand All @@ -63,7 +78,8 @@ async function getAppointmentsByPincode(pincodeArray, dates) {
return appointments.getFilteredSlots(date, result.data.sessions);
})
.catch(function (error) {
console.log('Unable to get appointment slots at pincode: ' + pin + ' for the date: ' + date + ', ' + error.response.statusText);
console.log(logger.getLog('Unable to get appointment slots at pincode: ' + pin + ' for the date: ' + date +
', ' + error.response.statusText));
});
slotsArray.push(slots);
};
Expand All @@ -77,63 +93,72 @@ async function getAppointmentsByDistrict(dates) {
let stateId = await locations.getStateId(appConfig.STATE);
let districtId = await locations.getDistrictId(stateId, appConfig.DISTRICT);
if (stateId == undefined) {
throw 'Unable to find state id. Please verify state name in config file: src/config/appConfig.js';
throw 'Unable to find state id. Please verify state name in config file: src/config/appConfig.js.';
} else if (districtId == undefined) {
throw 'Unable to find district id. Please verify district name in config file: src/config/appConfig.js';
throw 'Unable to find district id. Please verify district name in config file: src/config/appConfig.js.';
}
for await (const date of dates) {
let slots = await routes.getVaccinationSlotsByDistrict(districtId, date)
.then(function (result) {
return appointments.getFilteredSlots(date, result.data.sessions);
})
.catch(function (error) {
console.log('Unable to get appointment slots at district: ' + districtId + ' for the date: ' + date + ', ' + error.response.statusText);
console.log(logger.getLog('Unable to get appointment slots at district: ' + districtId + ' for the date: ' + date +
', ' + error.response.statusText));
});
slotsArray.push(slots);
};
sendEmailAlert(slotsArray);
} catch (e) {
console.log(e);
}
}

async function getTwoWeekDateArray() {
let dateArray = [];
let currentDate = moment();
for(let counter = 1; counter <= schedulerConfig.DATE_RANGE; counter++) {
let date = currentDate.format(schedulerConfig.DATE_FORMAT);
dateArray.push(date);
currentDate.add(1, 'day');
} catch (error) {
console.log(logger.getLog(error));
}
return dateArray;
}

async function sendEmailAlert(slotsArray) {
let outputArray = [];
if (slotsArray[0] == undefined) {
console.log('No sessions to process at this time');
} else {
for(let counter1 = 0; counter1 < slotsArray.length; counter1++) {
for(let counter1 = 0; counter1 < slotsArray.length; counter1++) {
if (slotsArray[counter1] != undefined || slotsArray[counter1] != undefined && slotsArray[counter1].length > 0) {
for(let counter2 = 0; counter2 < slotsArray[counter1].length; counter2++) {
outputArray.push(slotsArray[counter1][counter2]);
}
}
if (Boolean(await appointments.compareVaccinationSlots(outputArray, vaccinationSlots))) {
console.log('No new sessions to process at this time');
} else {
let htmlBody = await htmlBuilder.prepareHtmlBody(outputArray);
vaccinationSlots = outputArray;
alerts.sendEmailAlert(htmlBody, (err, result) => {
if(err) {
console.error({err});
}
if (outputArray.length > 0 && !Boolean(await appointments.compareVaccinationSlots(outputArray, vaccinationSlots))) {
vaccinationSlots = outputArray;
alerts.sendEmailAlert(mailConfig.SUBJECT, await htmlBuilder.prepareHtmlBody(outputArray), (error, result) => {
if(error) {
console.log(logger.getLog(error));
} else {
console.log(logger.getLog('New sessions have been processed and sent as an email alert to the recipient(s).'));
}
});
} else {
console.log(logger.getLog('No new sessions to process at this time.'));
if (Boolean(firstRun)) {
alerts.sendEmailAlert(mailConfig.FIRST_EMAIL_SUBJECT, await htmlBuilder.prepareFirstEmail(), (error, result) => {
if(error) {
console.log(logger.getLog(error));
} else {
console.log('New sessions have been processed and sent as an email alert');
console.log(logger.getLog('Welcome email alert has been sent to the recipient(s).'));
}
})
});
}
}
firstRun = false;
runCounter = runCounter + 1;
await dailyDigest.updateRunCounter(runCounter, vaccinationSlots);
await resetDailyCounter();
};

async function resetDailyCounter() {
const interval = parser.parseExpression(schedulerConfig.SCHEDULE);
let difference = moment(new Date(interval.next().toString())).diff(moment(), 'days', true);
if (difference.toFixed() > 0) {
await dailyDigest.prepareReport();
runCounter = 0;
}
};

main().then(() => {
console.log('The covid-19 vaccine alerting application has started!');
console.log(logger.getLog('The covid-19 vaccination alerting application has started!'));
});
1 change: 1 addition & 0 deletions src/configs/apiConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const STATES_URI = '/admin/location/states';
const DISTRICTS_URI = '/admin/location/districts/';
const APPOINTMENTS_PINCODE_URI = '/appointment/sessions/public/findByPin';
const APPOINTMENTS_DISTRICTS_URI = '/appointment/sessions/public/findByDistrict';

let xApiKey = '';
if (typeof Buffer.from === "function") {
xApiKey = Buffer.from(X_API_KEY, 'base64');
Expand Down
2 changes: 1 addition & 1 deletion src/configs/appConfig.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
const FINDBYPINCODE = true;
const FINDBYPINCODE = false;
// location configs
const PINCODE = '800001';
const STATE = 'BIHAR';
Expand Down
10 changes: 8 additions & 2 deletions src/configs/mailConfig.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ const SERVICE_PROVIDER = 'Gmail';
const RECIPIENT = 'asinha093@gmail.com';
const SENDER = 'covid-19-vaccine-alerts-cowin';
const SUBJECT = 'New vaccination slots are available on COWIN. Book appointment now';
const BODY = 'There are now new COVID-19 vaccination slots available in your requested location(s) in the next 10 days';
const DAILY_DIGEST_SUBJECT = 'Daily digest: covid-19-vaccine-alerts-cowin';
const FIRST_EMAIL_SUBJECT = 'Welcome to covid-19-vaccine-alerts-cowin application';
const COWIN_URL = 'https://www.cowin.gov.in/home';
const MOFHW_URL = 'https://www.mohfw.gov.in/covid_vaccination/vaccination/faqs.html';
const COWIN_LOGO_URL = 'https://github.com/sinhadotabhinav/covid-19-vaccine-alerts-cowin/blob/master/src/assets/cowin-logo.png?raw=true';
const WHO_VACCINE_LOGO_URL = 'https://github.com/sinhadotabhinav/covid-19-vaccine-alerts-cowin/blob/master/src/assets/vaccine.png?raw=true';

module.exports = { SERVICE_PROVIDER, RECIPIENT, SENDER, SUBJECT, BODY };
module.exports = { SERVICE_PROVIDER, RECIPIENT, SENDER, SUBJECT, DAILY_DIGEST_SUBJECT, FIRST_EMAIL_SUBJECT, COWIN_URL, MOFHW_URL,
COWIN_LOGO_URL, WHO_VACCINE_LOGO_URL };
4 changes: 2 additions & 2 deletions src/configs/schedulerConfig.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
const SCHEDULE = '0 */3 * * *';
const DATE_RANGE = 10;
const SCHEDULE = '0 */2 * * *';
const DATE_RANGE = 7;
const DATE_FORMAT = 'DD-MM-YYYY';

module.exports = { SCHEDULE, DATE_RANGE, DATE_FORMAT };
6 changes: 3 additions & 3 deletions src/services/alerts.js → src/utilities/alerts.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,12 @@ let mailerTransport = mailer.createTransport({
}
});

function sendEmailAlert (htmlBody, callback) {
function sendEmailAlert (subject, htmlBody, callback) {
let options = {
from: String(mailConfig.SENDER + `<${appConfig.EMAIL}>`),
to: mailConfig.RECIPIENT,
subject: mailConfig.SUBJECT,
text: mailConfig.BODY,
subject: subject,
text: 'Email alert',
html: htmlBody
};
mailerTransport.sendMail(options, (error, info) => {
Expand Down
6 changes: 3 additions & 3 deletions src/utilities/appointments.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
const appConfig = require('../configs/appConfig');
const logger = require('../utilities/logger');

function getFilteredSlots (date, sessions) {
let validSlots = sessions.filter(slot => slot.min_age_limit <= appConfig.AGE && slot.available_capacity > 0)
if(validSlots.length > 0) {
console.log('Vaccination slots returned:');
console.log({date:date, validSlots: validSlots.length});
console.log(logger.getLog(`Vaccination slots returned for ${date}: ${validSlots.length}`));
}
return validSlots;
};
Expand All @@ -13,7 +13,7 @@ async function compareVaccinationSlots(outputArray, vaccinationSlots) {
if (outputArray.length == vaccinationSlots.length) {
let equalCount = 0;
for(let counter = 0; counter < outputArray.length; counter++) {
if (JSON.stringify(outputArray[counter]) == JSON.stringify(vaccinationSlots[counter])) {
if (outputArray[counter].name == vaccinationSlots[counter].name) {
equalCount = equalCount + 1;
}
}
Expand Down
28 changes: 28 additions & 0 deletions src/utilities/dailyDigest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
const appConfig = require('../configs/appConfig');
const mailConfig = require('../configs/mailConfig');
const alerts = require('../utilities/alerts');
const htmlBuilder = require('../utilities/htmlBuilder');
const logger = require('../utilities/logger');

let runs = 0;
let slots = [];

async function prepareReport () {
let regionType = Boolean(appConfig.FINDBYPINCODE) ? 'pincode' : 'district';
let region = regionType == 'pincode' ? appConfig.PINCODE : appConfig.DISTRICT;
alerts.sendEmailAlert(mailConfig.DAILY_DIGEST_SUBJECT,
await htmlBuilder.prepareDailyDigestEmail(runs, slots.length, regionType, region), (error, result) => {
if(error) {
console.log(logger.getLog(error));
} else {
console.log(logger.getLog('Daily digest email alert has been sent to the recipient(s).'));
}
});
}

async function updateRunCounter (runCounter, vaccinationSlots) {
runs = runCounter;
slots = vaccinationSlots;
}

module.exports = { prepareReport, updateRunCounter };
Loading

0 comments on commit b9879c4

Please sign in to comment.