Merge branch 'notification-email-subject' into 'master'

Notification email subject

See merge request saburly/marketalarm/web!19
This commit was merged in pull request #19.
This commit is contained in:
Bilal Catic
2019-07-04 07:44:49 +00:00
8 changed files with 115 additions and 23 deletions

View File

@@ -34,3 +34,10 @@ this will create and run postgres image and then execute migrations
5. Run app 5. Run app
`npm start` or `npm run start-mon` to run app with automatic restart on code change `npm start` or `npm run start-mon` to run app with automatic restart on code change
### AWS SES
- AWS SES credentials are handled with env vratiables
- Notification emails are sent in batches of 50, by using SES templates
- Make sure that you are using different templates for different envirorments

View File

@@ -2,8 +2,10 @@
const dotenv = require('dotenv').config(); const dotenv = require('dotenv').config();
const { getRealEstateTypeEnum } = require('./enums'); const { getRealEstateTypeEnum } = require('./enums');
const { getRegionName, getMunicipalityName } = require('./codes'); const { getRegionName, getMunicipalityName } = require('./codes');
const db = require('../models/index');
const { allRERequestByUiid } = require('./db/dbHelper');
var AWS = require('aws-sdk'); var AWS = require('aws-sdk');
const TEMPLATE_NAME = "MarketAlertTemplate" const TEMPLATE_NAME = "MarketAlertTemplateDevelop"
AWS.config.update({ AWS.config.update({
region: process.env.AMAZON_REGION, region: process.env.AMAZON_REGION,
@@ -37,7 +39,7 @@ const sendTemplatedEmail = async (email, request) => {
}, },
Subject: { Subject: {
Charset: 'UTF-8', Charset: 'UTF-8',
Data: `Javimi Potvrda: ${request.realEstateType} ${getRegionName(request.region)}, ${getMunicipalityName(request.region, request.municipality)}` Data: `Javimi Potvrda: ${getSubject(request.realEstateType, request.region, request.municipality)}`
} }
}, },
Source: process.env.SOURCE_EMAIL, /* required */ Source: process.env.SOURCE_EMAIL, /* required */
@@ -97,26 +99,65 @@ const sendBulkEmail = async (marketAlerts) => {
try { try {
destinations = [] destinations = []
groupedEmails = []; groupedRERequests = [];
marketAlerts.forEach(marketAlert => {
if (!groupedEmails[marketAlert.email]) { let RERequestUuids = marketAlerts.map(marketAlert => marketAlert.request);
groupedEmails[marketAlert.email] = [];
groupedEmails[marketAlert.email].push({ url: marketAlert.url, title: marketAlert.title }); RERequestUuids = Array.from(new Set(RERequestUuids));
} else {
groupedEmails[marketAlert.email].push({ url: marketAlert.url, title: marketAlert.title }); RERequestUuids = RERequestUuids.map(marketAlert => {
} return { uniqueId: marketAlert }
}); });
for (email in groupedEmails) { const RERequest = await allRERequestByUiid(RERequestUuids);
const requestDataValues = [];
const url = groupedEmails[email]; RERequest.forEach(RERequest => {
let repData = `{ "marketAlertUrl":[${toAWSArray(url)}], "favoriteanimal":"yak" }` var formatedRequest = {};
formatedRequest[RERequest.uniqueId] =
requestDataValues[RERequest.uniqueId] = {
realEstateType: RERequest.realEstateType,
region: RERequest.region,
municipality: RERequest.municipality
};
});
marketAlerts.forEach(marketAlert => {
const requestObject = {
email: marketAlert.email,
realEstateType: requestDataValues[marketAlert.request].realEstateType,
municipality: requestDataValues[marketAlert.request].municipality,
region: requestDataValues[marketAlert.request].region,
}
if (!groupedRERequests[marketAlert.request]) {
groupedRERequests[marketAlert.request] = {
requestObject: requestObject,
marketAlertArray: []
};
}
groupedRERequests[marketAlert.request].marketAlertArray.push({
url: marketAlert.url,
title: marketAlert.title,
});
});
for (request in groupedRERequests) {
const marketAlert = groupedRERequests[request];
let extractedData = toAWSArray(marketAlert.marketAlertArray);
const realEstateType = getRealEstateTypeEnum(marketAlert.requestObject.realEstateType).title;
const region = getRegionName(marketAlert.requestObject.region);
const municipality = getMunicipalityName(marketAlert.requestObject.region, marketAlert.requestObject.municipality);
let repData = `{ "marketAlertUrl":[${extractedData}], "realestateType":"${realEstateType}", "region":"${region}", "municipality":"${municipality}" }`
destinations.push({ destinations.push({
Destination: { Destination: {
ToAddresses: [ ToAddresses: [
email marketAlert.requestObject.email
] ]
}, },
ReplacementTemplateData: repData ReplacementTemplateData: repData
@@ -147,18 +188,17 @@ const sendBulkEmail = async (marketAlerts) => {
} catch (e) { } catch (e) {
console.log("Could not send bulk email", e) console.log("Could not send bulk email", e)
} }
} }
const toAWSArray = (urlArray) => { const toAWSArray = (urlArray) => {
let arrayString = "" let arrayString = ""
urlArray.forEach(element => { urlArray.forEach(element => {
const formatetdTitle = element.title.replace(/"/g, ""); const formatetdTitle = element.title.replace(/"/g, "");
arrayString = arrayString + `{"url":"${element.url.trim()}" , "title":"${formatetdTitle}"},` arrayString = arrayString + `{"url":"${element.url.trim()}" , "title":"${formatetdTitle}"},`
}); });
return arrayString.slice(0, -1); return arrayString.slice(0, -1);
} }
const getNotificationEmailHtml = () => { const getNotificationEmailHtml = () => {
@@ -179,8 +219,8 @@ const getNotificationEmailText = () => {
const createMarketAlertEmailTemplate = async () => { const createMarketAlertEmailTemplate = async () => {
const marketAlertTemplate = { const marketAlertTemplate = {
Template: { Template: {
TemplateName: "MarketAlertTemplate", TemplateName: TEMPLATE_NAME,
SubjectPart: "Javi mi obavijest", SubjectPart: "Javi mi obavijest: {{realestateType}}, {{region}}, {{municipality}}",
TextPart: getNotificationEmailText(), TextPart: getNotificationEmailText(),
HtmlPart: getNotificationEmailHtml() HtmlPart: getNotificationEmailHtml()
} }
@@ -195,6 +235,10 @@ const createMarketAlertEmailTemplate = async () => {
} }
} }
const getSubject = (realEstateType, region, municipality) => {
return `${getRealEstateTypeEnum(realEstateType).title} ${getRegionName(region)}, ${getMunicipalityName(region, municipality)}`
}
module.exports = { module.exports = {
sendTemplatedEmail, sendTemplatedEmail,
sendBulkEmail, sendBulkEmail,

View File

@@ -13,7 +13,7 @@ module.exports = class OlxCrawler {
this.maxResults = maxResults; this.maxResults = maxResults;
} }
async indexSingle(url, email) { async indexSingle(url, email, uuid) {
try { try {
const res = await fetch(url); const res = await fetch(url);
const body = await res.text(); const body = await res.text();
@@ -81,6 +81,7 @@ module.exports = class OlxCrawler {
const parsedPrice = parsePrice(price); const parsedPrice = parsePrice(price);
console.log(location);
const locationArray = location.split(","); const locationArray = location.split(",");
const region = locationArray[0]; const region = locationArray[0];
const municipality = locationArray[1]; const municipality = locationArray[1];
@@ -88,6 +89,7 @@ module.exports = class OlxCrawler {
const data = { const data = {
realEstateType: this.getCategoryId(realEstateType), realEstateType: this.getCategoryId(realEstateType),
email: email, email: email,
uuid: uuid,
olxId: olxId, olxId: olxId,
// category: category, // category: category,
url, url,
@@ -138,7 +140,7 @@ module.exports = class OlxCrawler {
for (let i = 0; i < hrefs.length; i++) { for (let i = 0; i < hrefs.length; i++) {
console.log(`indexing: ${hrefs[i]}`); console.log(`indexing: ${hrefs[i]}`);
const singleData = await this.indexSingle(hrefs[i], olxUrl.email); const singleData = await this.indexSingle(hrefs[i], olxUrl.email, olxUrl.uuid);
if (singleData) { if (singleData) {
results.push(singleData); results.push(singleData);
@@ -223,7 +225,8 @@ module.exports = class OlxCrawler {
const olxUrl = { const olxUrl = {
url: `https://www.olx.ba/pretraga?${realsestateType}&id=2&stanje=0&vrstapregleda=tabela&sort_order=desc&${region}&${municipality}&${priceMin}&${priceMax}&vrsta=samoprodaja&${sizeMin}&${sizeMax}`, url: `https://www.olx.ba/pretraga?${realsestateType}&id=2&stanje=0&vrstapregleda=tabela&sort_order=desc&${region}&${municipality}&${priceMin}&${priceMax}&vrsta=samoprodaja&${sizeMin}&${sizeMax}`,
email: request.email email: request.email,
uuid: request.uniqueId
} }
console.log(olxUrl.url); console.log(olxUrl.url);
urls.push(olxUrl); urls.push(olxUrl);

View File

@@ -11,6 +11,20 @@ const allRERequest = async () => {
}); });
} }
/**
* Find all subscribed RealEstateRequests by UUID
*/
const allRERequestByUiid = async (requestArray) => {
const Op = db.Sequelize.Op;
return await db.RealEstateRequest.findAll({
where: {
subscribed: true,
[Op.or]: requestArray
}
});
}
/** /**
* Find all , or all depending on notified bolean marketalerts, and order them by email * Find all , or all depending on notified bolean marketalerts, and order them by email
* *
@@ -50,5 +64,6 @@ const findPointInsideBoundingBox = async (latLng, email) => {
module.exports = { module.exports = {
allRERequest, allRERequest,
allMarketAlerts, allMarketAlerts,
allRERequestByUiid,
findPointInsideBoundingBox findPointInsideBoundingBox
}; };

View File

@@ -0,0 +1,20 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'MarketAlerts',
'request',
{
type: Sequelize.STRING
}
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn(
'MarketAlerts',
'request'
);
}
};

View File

@@ -13,6 +13,7 @@ module.exports = (sequelize, DataTypes) => {
realEstateType : DataTypes.STRING, realEstateType : DataTypes.STRING,
notified : DataTypes.BOOLEAN, notified : DataTypes.BOOLEAN,
title : DataTypes.STRING, title : DataTypes.STRING,
request: DataTypes.STRING,
email: { email: {
type: DataTypes.STRING, type: DataTypes.STRING,

View File

@@ -33,6 +33,7 @@ async function crawlAll() {
size: result.size, size: result.size,
price: result.price, price: result.price,
email: result.email, email: result.email,
request: result.uuid,
// lastDate: DataTypes.STRING, // lastDate: DataTypes.STRING,
municipality: result.municipality, municipality: result.municipality,
region: result.region, region: result.region,

View File

@@ -16,7 +16,7 @@ async function processNotifications() {
await sendBulkEmail(marketAlerts); await sendBulkEmail(marketAlerts);
} else { } else {
console.log("NOTIFICATION SERVICE: No new alerts"); console.log("NOTIFICATION SERVICE: No new alerts");
return; process.exit();
} }
await db.MarketAlert.update( await db.MarketAlert.update(
@@ -26,6 +26,7 @@ async function processNotifications() {
process.exit(); process.exit();
} catch (e) { } catch (e) {
console.log("NOTIFICATION SERVICE: could not send notifications reason: ", e); console.log("NOTIFICATION SERVICE: could not send notifications reason: ", e);
process.exit();
} }
} }