find matching real estates for new search request and notify
This commit is contained in:
@@ -25,6 +25,8 @@ const AWS_EMAIL_CONFIG = {
|
||||
|
||||
const MAX_REAL_ESTATES_IN_EMAIL =
|
||||
parseInt(process.env.MAX_REAL_ESTATES_IN_EMAIL) || 10;
|
||||
const MAX_REAL_ESTATES_IN_FIRST_EMAIL =
|
||||
parseInt(process.env.MAX_REAL_ESTATES_IN_FIRST_EMAIL) || 5;
|
||||
|
||||
module.exports = {
|
||||
APP_PORT,
|
||||
@@ -33,5 +35,6 @@ module.exports = {
|
||||
CRAWLER_INTERVAL,
|
||||
STOP_CRAWLER,
|
||||
AWS_EMAIL_CONFIG,
|
||||
MAX_REAL_ESTATES_IN_EMAIL
|
||||
MAX_REAL_ESTATES_IN_EMAIL,
|
||||
MAX_REAL_ESTATES_IN_FIRST_EMAIL
|
||||
};
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
"use strict";
|
||||
const db = require("../../models/index");
|
||||
const sequelize = require("sequelize");
|
||||
const Op = sequelize.Op;
|
||||
|
||||
const bulkUpsertRealEstates = async realEstateData => {
|
||||
try {
|
||||
@@ -26,10 +28,12 @@ const bulkUpsertRealEstates = async realEstateData => {
|
||||
"updatedAt",
|
||||
"renewedDate"
|
||||
];
|
||||
const order = [["updatedAt", "desc"]];
|
||||
|
||||
return await db.RealEstate.bulkCreate(realEstateData, {
|
||||
updateOnDuplicate: fieldsToUpdateIfDuplicate,
|
||||
returning: true
|
||||
returning: true,
|
||||
order
|
||||
});
|
||||
} catch (e) {
|
||||
console.log("Error bulk upserting realEstates : ", e);
|
||||
@@ -40,7 +44,68 @@ const getRealEstateById = async id => {
|
||||
return db.RealEstate.findByPk(id);
|
||||
};
|
||||
|
||||
const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
|
||||
const {
|
||||
priceMin,
|
||||
priceMax,
|
||||
sizeMin,
|
||||
sizeMax,
|
||||
adType,
|
||||
realEstateType,
|
||||
areaToSearch
|
||||
} = searchRequest;
|
||||
|
||||
const longitudeColumn = sequelize.col("locationLong");
|
||||
const latitudeColumn = sequelize.col("locationLat");
|
||||
|
||||
const pointGeometry = sequelize.fn(
|
||||
"ST_Point",
|
||||
longitudeColumn,
|
||||
latitudeColumn
|
||||
);
|
||||
const pointWithSRID = sequelize.fn("ST_SetSRID", pointGeometry, 4326);
|
||||
const areaToSearchAsGeometry = sequelize.fn(
|
||||
"ST_GeomFromGeoJSON",
|
||||
JSON.stringify(areaToSearch)
|
||||
);
|
||||
const areaToSearchWithSRID = sequelize.fn(
|
||||
"ST_SetSRID",
|
||||
areaToSearchAsGeometry,
|
||||
4326
|
||||
);
|
||||
const contains = sequelize.fn(
|
||||
"ST_Contains",
|
||||
areaToSearchWithSRID,
|
||||
pointWithSRID
|
||||
);
|
||||
|
||||
const geoSearchQueryPart = sequelize.where(contains, true);
|
||||
|
||||
const query = {
|
||||
adType,
|
||||
realEstateType,
|
||||
price: {
|
||||
[Op.lte]: priceMax,
|
||||
[Op.gte]: priceMin
|
||||
},
|
||||
area: {
|
||||
[Op.lte]: sizeMax,
|
||||
[Op.gte]: sizeMin
|
||||
},
|
||||
[Op.and]: geoSearchQueryPart
|
||||
};
|
||||
|
||||
const order = [["updatedAt", "desc"]];
|
||||
|
||||
return await db.RealEstate.findAll({
|
||||
where: query,
|
||||
limit: maxResults,
|
||||
order
|
||||
});
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
bulkUpsertRealEstates,
|
||||
getRealEstateById
|
||||
getRealEstateById,
|
||||
findRealEstatesForSearchRequest
|
||||
};
|
||||
|
||||
@@ -6,11 +6,14 @@ const findRealEstatesForSearchRequest = async searchRequestId => {
|
||||
searchRequestId
|
||||
};
|
||||
|
||||
const include = [{ model: db.RealEstate, as: "realEstates" }];
|
||||
const realEstatesModel = { model: db.RealEstate, as: "realEstates" };
|
||||
const order = [[realEstatesModel, "updatedAt", "desc"]];
|
||||
const include = [realEstatesModel];
|
||||
|
||||
const matches = await db.SearchRequestMatch.findAll({
|
||||
where: query,
|
||||
include
|
||||
include,
|
||||
order
|
||||
});
|
||||
|
||||
const matchingRealEstates = [];
|
||||
|
||||
@@ -10,6 +10,16 @@ const generateEmailFooter = searchRequestId => {
|
||||
<strong>Vaš,<br/>Javimi tim</strong>`;
|
||||
};
|
||||
|
||||
const generateRealEstateLinks = realEstates => {
|
||||
let realEstateLinks = "";
|
||||
for (const realEstate of realEstates) {
|
||||
const { id: realEstateId, title } = realEstate;
|
||||
|
||||
realEstateLinks += `<li><a href="${APP_URL}/redirect/${realEstateId}">${title}</a></li>`;
|
||||
}
|
||||
return realEstateLinks;
|
||||
};
|
||||
|
||||
const generateNotificationEmail = (realEstates, searchRequestId) => {
|
||||
const truncateList = realEstates.length > MAX_REAL_ESTATES_IN_EMAIL;
|
||||
const realEstatesToShow = truncateList
|
||||
@@ -17,16 +27,8 @@ const generateNotificationEmail = (realEstates, searchRequestId) => {
|
||||
: realEstates;
|
||||
|
||||
const allRealEstatesLink = `${APP_URL}/nekretnine/${searchRequestId}`;
|
||||
|
||||
let realEstateLinks = "";
|
||||
for (const realEstate of realEstatesToShow) {
|
||||
const { id: realEstateId, title } = realEstate;
|
||||
|
||||
realEstateLinks += `<li><a href="${APP_URL}/redirect/${realEstateId}">${title}</a></li><br />`;
|
||||
}
|
||||
|
||||
const realEstateLinks = generateRealEstateLinks(realEstatesToShow);
|
||||
const moreRealEstates = `<div>Kompletan spisak nekretnina možete pegledati na <a href="${allRealEstatesLink}">listi nekretnina</a><div>`;
|
||||
|
||||
const emailFooter = generateEmailFooter(searchRequestId);
|
||||
|
||||
return `<h3>Zdravo</h3>
|
||||
@@ -40,7 +42,7 @@ const generateNotificationEmail = (realEstates, searchRequestId) => {
|
||||
${emailFooter}`;
|
||||
};
|
||||
|
||||
const generateNewSearchRequestEmail = searchRequest => {
|
||||
const generateNewSearchRequestEmail = (searchRequest, matchingRealEstates) => {
|
||||
const realEstateType = AD_CATEGORY[searchRequest.realEstateType];
|
||||
const {
|
||||
id,
|
||||
@@ -52,8 +54,15 @@ const generateNewSearchRequestEmail = searchRequest => {
|
||||
priceMax
|
||||
} = searchRequest;
|
||||
|
||||
const realEstateLinks = generateRealEstateLinks(matchingRealEstates);
|
||||
const instantRealEstatesText = `<br/>
|
||||
<div>
|
||||
U međuvremenu pogledajte neke od nedavno objavljenih nekretnina koje odgovaraju Vašim uslovima pretrage :<br/>
|
||||
${realEstateLinks}
|
||||
</div>`;
|
||||
|
||||
const gardenSize = realEstateType.hasGardenSize
|
||||
? `<div><strong>Kvadratura okućnice: Od ${gardenSizeMin} do ${gardenSizeMax} m2 </strong></div>`
|
||||
? `<div><strong>Kvadratura okućnice: Od ${gardenSizeMin} do ${gardenSizeMax} m2</strong></div>`
|
||||
: ``;
|
||||
|
||||
const emailFooter = generateEmailFooter(id);
|
||||
@@ -68,6 +77,7 @@ const generateNewSearchRequestEmail = searchRequest => {
|
||||
${gardenSize}
|
||||
<div><strong>Cijena:</strong> ${priceMin} do ${priceMax} KM</div>
|
||||
</div>
|
||||
${matchingRealEstates.length > 0 ? instantRealEstatesText : ""}
|
||||
<br/>
|
||||
${emailFooter}`;
|
||||
};
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
"use strict";
|
||||
const { matchRealEstates } = require("../services/searchMatchService");
|
||||
const {
|
||||
matchRealEstates,
|
||||
matchSearchRequest
|
||||
} = require("../services/searchMatchService");
|
||||
const {
|
||||
generateNotificationEmail,
|
||||
generateNewSearchRequestEmail
|
||||
@@ -8,6 +11,24 @@ const { sendEmail } = require("../services/emailService");
|
||||
|
||||
const notifyForNewRealEstates = async newRealEstates => {
|
||||
const matches = await matchRealEstates(newRealEstates);
|
||||
await notifyMatches(matches);
|
||||
};
|
||||
|
||||
const notifyForNewSearchRequest = async searchRequest => {
|
||||
const matches = await matchSearchRequest(searchRequest);
|
||||
|
||||
const searchRequestId = searchRequest.id;
|
||||
const matchingRealEstates = matches[searchRequestId].realEstates;
|
||||
|
||||
const emailContent = generateNewSearchRequestEmail(
|
||||
searchRequest,
|
||||
matchingRealEstates
|
||||
);
|
||||
const { email } = searchRequest;
|
||||
await sendEmail(email, "Market Alert", emailContent);
|
||||
};
|
||||
|
||||
const notifyMatches = async matches => {
|
||||
const searchRequestsToNotify = Object.keys(matches);
|
||||
|
||||
const asyncSendEmailActions = [];
|
||||
@@ -27,12 +48,6 @@ const notifyForNewRealEstates = async newRealEstates => {
|
||||
await Promise.all(asyncSendEmailActions);
|
||||
};
|
||||
|
||||
const notifyForNewSearchRequest = async searchRequest => {
|
||||
const emailContent = generateNewSearchRequestEmail(searchRequest);
|
||||
const { email } = searchRequest;
|
||||
await sendEmail(email, "Market Alert", emailContent);
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
notifyForNewRealEstates,
|
||||
notifyForNewSearchRequest
|
||||
|
||||
@@ -3,7 +3,9 @@
|
||||
const {
|
||||
findSearchRequestsForRealEstate
|
||||
} = require("../helpers/db/searchRequest");
|
||||
const { findRealEstatesForSearchRequest } = require("../helpers/db/realEstate");
|
||||
const { addMatches } = require("../helpers/db/searchRequestMatch");
|
||||
const { MAX_REAL_ESTATES_IN_FIRST_EMAIL } = require("../config/appConfig");
|
||||
|
||||
const matchRealEstates = async realEstates => {
|
||||
if (Array.isArray(realEstates)) {
|
||||
@@ -40,6 +42,35 @@ const matchRealEstates = async realEstates => {
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
matchRealEstates
|
||||
const matchSearchRequest = async searchRequest => {
|
||||
const { id: searchRequestId } = searchRequest;
|
||||
|
||||
const realEstates = await findRealEstatesForSearchRequest(
|
||||
searchRequest,
|
||||
MAX_REAL_ESTATES_IN_FIRST_EMAIL
|
||||
);
|
||||
const matches = {
|
||||
[searchRequestId]: {
|
||||
searchRequest,
|
||||
realEstates: []
|
||||
}
|
||||
};
|
||||
const matchingRecords = [];
|
||||
|
||||
for (const realEstate of realEstates) {
|
||||
matches[searchRequestId].realEstates.push(realEstate);
|
||||
matchingRecords.push({
|
||||
searchRequestId,
|
||||
realEstateId: realEstate.id,
|
||||
notified: false
|
||||
});
|
||||
}
|
||||
|
||||
await addMatches(matchingRecords);
|
||||
return matches;
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
matchRealEstates,
|
||||
matchSearchRequest
|
||||
};
|
||||
|
||||
@@ -8,7 +8,8 @@ SEQUELIZE_LOGGING=0- no sequelize logging, 1- log to the console
|
||||
PORT=Port for the app, defaults to 5000
|
||||
APP_BASE_URL=base url for the app
|
||||
|
||||
MAX_REAL_ESTATES_IN_EMAIL=Number of real estates that will be shown in URL, others will be truncated and URL with full list will be shwon
|
||||
MAX_REAL_ESTATES_IN_EMAIL=Max number of real estates that will be shown in email, others will be truncated and URL with full list will be shwon
|
||||
MAX_REAL_ESTATES_IN_FIRST_EMAIL=Max number of real estates that will be shown in first (welcome) email
|
||||
|
||||
AWS_KEY_ID=(your-key-here)
|
||||
AWS_SECRET_ACCESS_KEY=(your-key-here)
|
||||
|
||||
Reference in New Issue
Block a user