diff --git a/app/config/appConfig.js b/app/config/appConfig.js
index c56f93b..8a11650 100644
--- a/app/config/appConfig.js
+++ b/app/config/appConfig.js
@@ -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
};
diff --git a/app/helpers/db/realEstate.js b/app/helpers/db/realEstate.js
index fc5669e..fd9f086 100644
--- a/app/helpers/db/realEstate.js
+++ b/app/helpers/db/realEstate.js
@@ -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
};
diff --git a/app/helpers/db/searchRequestMatch.js b/app/helpers/db/searchRequestMatch.js
index cb9e9c3..d7cb215 100644
--- a/app/helpers/db/searchRequestMatch.js
+++ b/app/helpers/db/searchRequestMatch.js
@@ -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 = [];
diff --git a/app/helpers/emailContentGenerator.js b/app/helpers/emailContentGenerator.js
index ec4d35c..2236d02 100644
--- a/app/helpers/emailContentGenerator.js
+++ b/app/helpers/emailContentGenerator.js
@@ -10,6 +10,16 @@ const generateEmailFooter = searchRequestId => {
Vaš,
Javimi tim`;
};
+const generateRealEstateLinks = realEstates => {
+ let realEstateLinks = "";
+ for (const realEstate of realEstates) {
+ const { id: realEstateId, title } = realEstate;
+
+ realEstateLinks += `
${title}`;
+ }
+ 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 += `${title}
`;
- }
-
+ const realEstateLinks = generateRealEstateLinks(realEstatesToShow);
const moreRealEstates = `Kompletan spisak nekretnina možete pegledati na
listi nekretnina`;
-
const emailFooter = generateEmailFooter(searchRequestId);
return `
Zdravo
@@ -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 = `
+
+ U međuvremenu pogledajte neke od nedavno objavljenih nekretnina koje odgovaraju Vašim uslovima pretrage :
+ ${realEstateLinks}
+
`;
+
const gardenSize = realEstateType.hasGardenSize
- ? `
Kvadratura okućnice: Od ${gardenSizeMin} do ${gardenSizeMax} m2
`
+ ? `
Kvadratura okućnice: Od ${gardenSizeMin} do ${gardenSizeMax} m2
`
: ``;
const emailFooter = generateEmailFooter(id);
@@ -68,6 +77,7 @@ const generateNewSearchRequestEmail = searchRequest => {
${gardenSize}
Cijena: ${priceMin} do ${priceMax} KM
+ ${matchingRealEstates.length > 0 ? instantRealEstatesText : ""}
${emailFooter}`;
};
diff --git a/app/services/notificationService.js b/app/services/notificationService.js
index 52d171a..b0c84ff 100644
--- a/app/services/notificationService.js
+++ b/app/services/notificationService.js
@@ -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
diff --git a/app/services/searchMatchService.js b/app/services/searchMatchService.js
index 1075bee..4c83504 100644
--- a/app/services/searchMatchService.js
+++ b/app/services/searchMatchService.js
@@ -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
};
diff --git a/development.env b/development.env
index 6368ee7..94d8549 100644
--- a/development.env
+++ b/development.env
@@ -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)