Compare commits

..

8 Commits

Author SHA1 Message Date
Naida Vatric
a508f72d7c Merge branch 'master' into 'checkup-email-bug-fix'
# Conflicts:
#   app/services/notificationService.js
2020-02-25 15:12:47 +00:00
Naida Vatric
08ad9edfe1 Merge branch 'scraper-api-support' into 'master'
Added Scraper API option.

See merge request saburly/marketalarm/web!100
2020-02-25 15:10:39 +00:00
Naida Vatric
ce857ddce9 Renamed var. 2020-02-23 23:11:21 +01:00
Naida Vatric
148b2ea863 Changed default. 2020-02-23 16:38:40 +01:00
Naida Vatric
d436d4a37b Added Scraper API option. 2020-02-22 22:15:27 +01:00
Naida Vatric
bc7ce9d708 Changed checkup query to miliseconds. 2020-02-17 21:22:45 +01:00
Naida Vatric
22bffc126d For staging - checkup fix. 2020-02-15 02:39:38 +01:00
Naida Vatric
06f80296f3 Changed checkup email logic. 2020-02-15 02:30:02 +01:00
8 changed files with 219 additions and 69 deletions

View File

@@ -45,6 +45,9 @@ const USER_AGENT =
process.env.USER_AGENT || process.env.USER_AGENT ||
"Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36"; "Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/65.0.3325.181 Safari/537.36";
const USE_SCRAPER_API = process.env.USE_SCRAPER_API || 1; //Default to use
const SCRAPER_API_KEY = process.env.SCRAPER_API_KEY || "";
module.exports = { module.exports = {
APP_PORT, APP_PORT,
APP_URL, APP_URL,
@@ -59,5 +62,7 @@ module.exports = {
STAGING, STAGING,
CHECK_UP_DAYS, CHECK_UP_DAYS,
PROSTOR_LOGIN, PROSTOR_LOGIN,
USER_AGENT USER_AGENT,
USE_SCRAPER_API,
SCRAPER_API_KEY
}; };

View File

@@ -3,6 +3,7 @@ const db = require("../../models/index");
const sequelize = require("sequelize"); const sequelize = require("sequelize");
const Op = sequelize.Op; const Op = sequelize.Op;
const { AD_CATEGORY } = require("../../common/enums"); const { AD_CATEGORY } = require("../../common/enums");
const { CHECK_UP_DAYS } = require("../../config/appConfig");
const getSearchRequest = async searchRequestId => { const getSearchRequest = async searchRequestId => {
try { try {
@@ -16,6 +17,22 @@ const getSearchRequest = async searchRequestId => {
const createSearchRequest = async (searchRequestFields = {}) => { const createSearchRequest = async (searchRequestFields = {}) => {
return await db.SearchRequest.create(searchRequestFields); return await db.SearchRequest.create(searchRequestFields);
}; };
const findAllRequestsForCheckUp = async () => {
const checkUpOffset = 24 * 60 * 60 * 1000 * CHECK_UP_DAYS; //in miliseconds
const checkupDate = new Date();
checkupDate.setTime(checkupDate.getTime() - checkUpOffset);
const dateQuery = {
notifiedAt: {
[Op.lte]: checkupDate
}
};
const allRequestsForCheckUp = await db.SearchRequest.findAll({
where: dateQuery
});
return allRequestsForCheckUp;
};
const findSearchRequestsForRealEstate = async realEstate => { const findSearchRequestsForRealEstate = async realEstate => {
const { const {
@@ -156,11 +173,33 @@ const findSearchRequestsForRealEstate = async realEstate => {
}; };
} else { } else {
// If real estate dont have defined number of rooms ex. null // If real estate dont have defined number of rooms ex. null
//It returns all search requests except for ones that dont want incpomlete ads //It returns requests that didn't choose number of rooms - also null
//Or ones that picked some values but also picked to includeIncomplete ads (or default)
numberOfRoomsQuery = { numberOfRoomsQuery = {
includeIncompleteAds: { [Op.or]: [
[Op.ne]: false {
} [Op.and]: [
{
numberOfRoomsMin: {
[Op.is]: null
}
},
{
numberOfRoomsMax: {
[Op.is]: null
}
}
]
},
{
includeIncompleteAds: {
[Op.or]: {
[Op.eq]: true,
[Op.is]: null
}
}
}
]
}; };
} }
} }
@@ -190,9 +229,30 @@ const findSearchRequestsForRealEstate = async realEstate => {
}; };
} else { } else {
numberOfFloorsQuery = { numberOfFloorsQuery = {
includeIncompleteAds: { [Op.or]: [
[Op.ne]: false {
} [Op.and]: [
{
numberOfFloorsMin: {
[Op.is]: null
}
},
{
numberOfFloorsMax: {
[Op.is]: null
}
}
]
},
{
includeIncompleteAds: {
[Op.or]: {
[Op.eq]: true,
[Op.is]: null
}
}
}
]
}; };
} }
} }
@@ -221,9 +281,30 @@ const findSearchRequestsForRealEstate = async realEstate => {
}; };
} else { } else {
floorQuery = { floorQuery = {
includeIncompleteAds: { [Op.or]: [
[Op.ne]: false {
} [Op.and]: [
{
floorMin: {
[Op.is]: null
}
},
{
floorMax: {
[Op.is]: null
}
}
]
},
{
includeIncompleteAds: {
[Op.or]: {
[Op.eq]: true,
[Op.is]: null
}
}
}
]
}; };
} }
} }
@@ -232,7 +313,7 @@ const findSearchRequestsForRealEstate = async realEstate => {
//If user dont check checkbox for ex. elevator it does not mean he only wants no elevator //If user dont check checkbox for ex. elevator it does not mean he only wants no elevator
//If real estate characteristic =true find all req, one that wants charachertistic or dont care - dont need query //If real estate characteristic =true find all req, one that wants charachertistic or dont care - dont need query
//If real estate characteristic = false, find all req exept for ones that wants characteristic to be true //If real estate characteristic = false, find all req exept for ones that wants characteristic to be true
//If real estate characteristic = null, dont know if true or false, find all req except ones that dont want incomplete ads //If real estate characteristic = null, dont know if true or false, find req that dont care or want char and want incomplete ads
let balconyQuery = {}; let balconyQuery = {};
if (realEstateTypeObject.hasBalconyProp && balcony !== true) { if (realEstateTypeObject.hasBalconyProp && balcony !== true) {
if (balcony === false) { if (balcony === false) {
@@ -243,9 +324,30 @@ const findSearchRequestsForRealEstate = async realEstate => {
}; };
} else if (balcony === null) { } else if (balcony === null) {
balconyQuery = { balconyQuery = {
includeIncompleteAds: { [Op.or]: [
[Op.ne]: false {
} balcony: {
[Op.ne]: true
}
},
{
[Op.and]: [
{
balcony: {
[Op.eq]: true
}
},
{
includeIncompleteAds: {
[Op.or]: {
[Op.eq]: true,
[Op.is]: null
}
}
}
]
}
]
}; };
} }
} }
@@ -259,9 +361,30 @@ const findSearchRequestsForRealEstate = async realEstate => {
}; };
} else if (newBuilding === null) { } else if (newBuilding === null) {
newBuildingQuery = { newBuildingQuery = {
includeIncompleteAds: { [Op.or]: [
[Op.ne]: false {
} newBuilding: {
[Op.ne]: true
}
},
{
[Op.and]: [
{
newBuilding: {
[Op.eq]: true
}
},
{
includeIncompleteAds: {
[Op.or]: {
[Op.eq]: true,
[Op.is]: null
}
}
}
]
}
]
}; };
} }
} }
@@ -275,13 +398,33 @@ const findSearchRequestsForRealEstate = async realEstate => {
}; };
} else if (elevator === null) { } else if (elevator === null) {
elevatorQuery = { elevatorQuery = {
includeIncompleteAds: { [Op.or]: [
[Op.ne]: false {
} elevator: {
[Op.ne]: true
}
},
{
[Op.and]: [
{
elevator: {
[Op.eq]: true
}
},
{
includeIncompleteAds: {
[Op.or]: {
[Op.eq]: true,
[Op.is]: null
}
}
}
]
}
]
}; };
} }
} }
//General query consists of each individual query //General query consists of each individual query
const query = { const query = {
adType, adType,
@@ -333,5 +476,6 @@ const findSearchRequestsForRealEstate = async realEstate => {
module.exports = { module.exports = {
getSearchRequest, getSearchRequest,
createSearchRequest, createSearchRequest,
findSearchRequestsForRealEstate findSearchRequestsForRealEstate,
findAllRequestsForCheckUp
}; };

View File

@@ -2,7 +2,6 @@
const db = require("../../models/index"); const db = require("../../models/index");
const sequelize = require("sequelize"); const sequelize = require("sequelize");
const Op = sequelize.Op; const Op = sequelize.Op;
const { CHECK_UP_DAYS } = require("../../config/appConfig");
const findRealEstatesForSearchRequest = async searchRequestId => { const findRealEstatesForSearchRequest = async searchRequestId => {
const query = { const query = {
@@ -43,42 +42,6 @@ const findNotNotifiedMatches = async () => {
return matchingRecords; return matchingRecords;
}; };
const findAllRequestsForCheckUp = async () => {
//First we find IDs of search request that don't need to be emailed for check up - to EXCLUDE
//The ones that received notification for real estate CHECK_UP_DAYS days from now
const date = new Date();
const checkUpDate = date.getDate() - CHECK_UP_DAYS;
date.setDate(checkUpDate);
const dateQuery = {
createdAt: {
[Op.gte]: date
}
};
const excludedMatches = await db.SearchRequestMatch.findAll({
attributes: ["searchRequestId"],
where: dateQuery,
order: [["searchRequestId", "ASC"]]
});
const excludedRequestsAll = excludedMatches.map(match => {
return match.dataValues.searchRequestId;
});
//Removing duplicate search request id-s for optimization
const excludedRequests = [...new Set(excludedRequestsAll)];
const query = {
subscribed: true,
id: {
[Op.notIn]: excludedRequests
}
};
const allRequestsForCheckUp = await db.SearchRequest.findAll({
where: query
});
return allRequestsForCheckUp;
};
const addMatches = async matchingRecords => { const addMatches = async matchingRecords => {
return await db.SearchRequestMatch.bulkCreate(matchingRecords, { return await db.SearchRequestMatch.bulkCreate(matchingRecords, {
@@ -89,6 +52,5 @@ const addMatches = async matchingRecords => {
module.exports = { module.exports = {
findRealEstatesForSearchRequest, findRealEstatesForSearchRequest,
addMatches, addMatches,
findNotNotifiedMatches, findNotNotifiedMatches
findAllRequestsForCheckUp
}; };

View File

@@ -1,5 +1,9 @@
const nodeFetch = require("node-fetch"); const nodeFetch = require("node-fetch");
const { USER_AGENT } = require("../config/appConfig"); const {
USER_AGENT,
USE_SCRAPER_API,
SCRAPER_API_KEY
} = require("../config/appConfig");
const fetch = async (url, options = {}) => { const fetch = async (url, options = {}) => {
const newOptions = Object.assign({}, options); const newOptions = Object.assign({}, options);
@@ -7,7 +11,11 @@ const fetch = async (url, options = {}) => {
newOptions["headers"] = {}; newOptions["headers"] = {};
} }
newOptions["headers"]["User-Agent"] = USER_AGENT; newOptions["headers"]["User-Agent"] = USER_AGENT;
return nodeFetch(url, newOptions); const urlAdaptedForScraping = USE_SCRAPER_API
? `http://api.scraperapi.com/?api_key=${SCRAPER_API_KEY}&url=${url}`
: url;
return nodeFetch(urlAdaptedForScraping, newOptions);
}; };
module.exports = fetch; module.exports = fetch;

View File

@@ -0,0 +1,14 @@
"use strict";
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn("SearchRequests", "notifiedAt", {
type: Sequelize.DATE,
defaultValue: new Date()
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn("SearchRequests", "notifiedAt");
}
};

View File

@@ -82,7 +82,11 @@ module.exports = (sequelize, DataTypes) => {
floorMin: DataTypes.INTEGER, floorMin: DataTypes.INTEGER,
floorMax: DataTypes.INTEGER, floorMax: DataTypes.INTEGER,
accessRoadType: DataTypes.TEXT, accessRoadType: DataTypes.TEXT,
heatingType: DataTypes.TEXT heatingType: DataTypes.TEXT,
notifiedAt: {
type: DataTypes.DATE,
defaultValue: new Date()
}
}); });
return SearchRequest; return SearchRequest;

View File

@@ -15,9 +15,10 @@ const {
} = require("../helpers/emailContentGenerator"); } = require("../helpers/emailContentGenerator");
const { const {
findNotNotifiedMatches, findNotNotifiedMatches,
findAllRequestsForCheckUp,
findRealEstatesForSearchRequest findRealEstatesForSearchRequest
} = require("../helpers/db/searchRequestMatch"); } = require("../helpers/db/searchRequestMatch");
const { findAllRequestsForCheckUp } = require("../helpers/db/searchRequest");
const { sendEmail } = require("../services/emailService"); const { sendEmail } = require("../services/emailService");
const notifyForNewRealEstates = async newRealEstates => { const notifyForNewRealEstates = async newRealEstates => {
@@ -35,7 +36,7 @@ const notifyForNewSearchRequest = async searchRequest => {
matchingRealEstates matchingRealEstates
); );
const { email } = searchRequest; const { email } = searchRequest;
//In case of the new search req, notifiedAt column is populated with default value - now (moment of creation)
await sendEmail( await sendEmail(
email, email,
`${stagingTag} Kivi - novi zahtjev za pretragu`, `${stagingTag} Kivi - novi zahtjev za pretragu`,
@@ -76,6 +77,10 @@ const notifyMatches = async (matches, dailyNotification = false) => {
sendEmailPromise.catch(err => sendEmailPromise.catch(err =>
console.log("[Email Sending Failed]", err) console.log("[Email Sending Failed]", err)
); );
//Change time of notified At for searchReq
searchRequest.notifiedAt = new Date();
searchRequest.save();
} }
} }
} }
@@ -131,7 +136,7 @@ const notifyRequestsWithDailyOption = async () => {
}; };
const checkUpNotify = async () => { const checkUpNotify = async () => {
/* const searchRequestsForCheckUp = await findAllRequestsForCheckUp(); const searchRequestsForCheckUp = await findAllRequestsForCheckUp();
const asyncSendEmailActions = []; const asyncSendEmailActions = [];
@@ -143,8 +148,12 @@ const checkUpNotify = async () => {
const sendEmailPromise = sendEmail(email, emailSubject, emailContent); const sendEmailPromise = sendEmail(email, emailSubject, emailContent);
asyncSendEmailActions.push(sendEmailPromise); asyncSendEmailActions.push(sendEmailPromise);
sendEmailPromise.catch(err => console.log("[Email Sending Failed]", err)); sendEmailPromise.catch(err => console.log("[Email Sending Failed]", err));
//Change time of notified At for searchReq
searchRequest.notifiedAt = new Date();
searchRequest.save();
} }
await Promise.all(asyncSendEmailActions); */ await Promise.all(asyncSendEmailActions);
}; };
module.exports = { module.exports = {

View File

@@ -22,6 +22,10 @@ GA_ID=Google Analytics ID
#=============== GOOGLE MAPS =============# #=============== GOOGLE MAPS =============#
API_MAP_KEY=(your-key-here) API_MAP_KEY=(your-key-here)
#=============== SCRAPER API SUPORT =============#
USE_SCRAPER_API= To turn it on (1) or off (0)
SCRAPER_API_KEY= Key for Scraper api
#=============== AWS SDK EMAIL SETTINGS =======# #=============== AWS SDK EMAIL SETTINGS =======#
AWS_KEY_ID=(your-key-here) AWS_KEY_ID=(your-key-here)
AWS_SECRET_ACCESS_KEY=(your-key-here) AWS_SECRET_ACCESS_KEY=(your-key-here)