Compare commits

...

12 Commits

Author SHA1 Message Date
Naida Vatric
8435afe9c5 Renamed variables to describe purpose. 2020-02-23 18:47:27 +01:00
Bilal Catic
6791a509d0 make user agent header configurable through env variable 2020-02-20 21:07:16 +01:00
Bilal Catic
edc6e2bbf7 Merge branch 'create-fetch-wrapper-with-user-agent' into 'master'
Create fetch wrapper with user agent

See merge request saburly/marketalarm/web!98
2020-02-20 19:58:32 +00:00
Bilal Catic
4f230020d7 use fetch wrapper instead of node-fetch 2020-02-20 19:49:29 +01:00
Bilal Catic
f62a7200c7 create fetch wrapper with mandatory user agent header 2020-02-20 19:47:30 +01:00
Bilal Catic
cff7cc2e9c apply prettier 2020-02-20 19:46:39 +01:00
Naida Vatric
df2a962d0f Merge branch 'prostor-vip-ads-fix' into 'master'
Prostor VIP ads fixed.

See merge request saburly/marketalarm/web!94
2020-02-17 14:44:58 +00:00
Naida Vatric
be4508ebea Merge branch 'include-incomplete-ads-inverse' into 'master'
Default true for include incomplete ads.

See merge request saburly/marketalarm/web!96
2020-02-17 14:44:35 +00:00
Naida Vatric
81fa3f046d Default true for include incomplete ads. 2020-02-15 00:52:06 +01:00
Naida Vatric
5bdc8e149a Prostor VIP ads fixed. 2020-02-14 22:41:51 +01:00
Senad Uka
fc7fe3c0b3 Notificaton service disabled 2020-02-14 15:07:42 +01:00
Naida Vatric
b3007123a5 Merge branch 'rename-settings-var' into 'master'
Rename settings var

See merge request saburly/marketalarm/web!93
2020-02-10 20:17:08 +00:00
17 changed files with 96 additions and 63 deletions

View File

@@ -9,14 +9,14 @@ const APP_URL =
? process.env.APP_URL || "http://market-alarm"
: process.env.APP_URL || `${APP_BASE_URL}:${APP_PORT}`;
const STAGING = process.env.ENVIRONMENT !== "production";
const USE_KIVI_ENVIRONMENT_TAG = process.env.KIVI_ENVIRONMENT !== "production";
const DEFAULT_TIMEZONE = "Europe/Sarajevo";
const CRAWLER_INTERVAL = parseInt(process.env.CRAWLER_INTERVAL) || 60;
const STOP_CRAWLER = !!parseInt(process.env.STOP_CRAWLER);
const CHECK_UP_DAYS = parseInt(process.env.CHECK_UP_DAYS) || 10;
const NO_CHECK_UP_DAYS = parseInt(process.env.NO_CHECK_UP_DAYS) || 10;
const AWS_EMAIL_CONFIG = {
REGION: process.env.AWS_REGION || "",
@@ -34,13 +34,17 @@ const MAX_REAL_ESTATES_IN_FIRST_EMAIL =
const PRINT_CRAWLER_DEBUG = process.env.PRINT_CRAWLER_DEBUG_INFO || 0;
const API_MAP_KEY = process.env.API_MAP_KEY || "";
const GOOGLE_MAP_KEY = process.env.GOOGLE_MAP_KEY || "";
const PROSTOR_LOGIN = {
EMAIL: process.env.PROSTOR_LOGIN_EMAIL,
PASSWORD: process.env.PROSTOR_LOGIN_PASS
};
const 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";
module.exports = {
APP_PORT,
APP_URL,
@@ -51,8 +55,9 @@ module.exports = {
MAX_REAL_ESTATES_IN_EMAIL,
MAX_REAL_ESTATES_IN_FIRST_EMAIL,
PRINT_CRAWLER_DEBUG,
API_MAP_KEY,
STAGING,
CHECK_UP_DAYS,
PROSTOR_LOGIN
GOOGLE_MAP_KEY,
USE_KIVI_ENVIRONMENT_TAG,
NO_CHECK_UP_DAYS,
PROSTOR_LOGIN,
USER_AGENT
};

View File

@@ -17,15 +17,15 @@ const getLocation = async (req, res) => {
return;
}
const selectedArea = searchRequest.areaToSearch;
const sw = selectedArea.coordinates[0][3];
const ne = selectedArea.coordinates[0][1];
const southWest = selectedArea.coordinates[0][3];
const northEast = selectedArea.coordinates[0][1];
if (sw[0] && ne[0]) {
selectedLatLngBounds = {
swLat: sw[1],
swLng: sw[0],
neLat: ne[1],
neLng: ne[0]
swLat: southWest[1],
swLng: southWest[0],
neLat: northEast[1],
neLng: northEast[0]
};
boundsSelected = true;
}

View File

@@ -1,6 +1,6 @@
"use strict";
const fetch = require("node-fetch");
const fetch = require("../../helpers/fetchWrapper");
const cheerio = require("cheerio");
const Promise = require("bluebird");
const moment = require("moment-timezone");

View File

@@ -1,6 +1,6 @@
"use strict";
const fetch = require("node-fetch");
const fetch = require("../../helpers/fetchWrapper");
const cheerio = require("cheerio");
const Promise = require("bluebird");
const moment = require("moment-timezone");

View File

@@ -1,6 +1,6 @@
"use strict";
const fetch = require("node-fetch");
const fetch = require("../../helpers/fetchWrapper");
const cheerio = require("cheerio");
const moment = require("moment-timezone");
const FormData = require("form-data");
@@ -191,13 +191,7 @@ class ProstorCrawler {
const { lat, lng, property_name, price, size, link, status } = realEstate;
//Status information is given already in realestate list
//For VIP Ads status ='' canot be used, but no VIP ads are crawled
//We will make "fake" vip ad for RE that have size=55
//It is weird because yesterday it said 'VIP ponuda' ???
const adStatus =
size === "55"
? ProstorCrawler.getStatusId("VIP ponuda")
: ProstorCrawler.getStatusId(status);
const adStatus = ProstorCrawler.getStatusId(status);
const url = `https://prostor.ba${link}`;

View File

@@ -1,6 +1,6 @@
"use strict";
const fetch = require("node-fetch");
const fetch = require("../../helpers/fetchWrapper");
const cheerio = require("cheerio");
const Promise = require("bluebird");
const moment = require("moment-timezone");
@@ -399,7 +399,9 @@ class RentalCrawler {
);
if (!publishedDateMoment.isValid()) {
throw {
message: `Invalid published date : ${extractedData["re_realEstates_inserted"]}`
message: `Invalid published date : ${
extractedData["re_realEstates_inserted"]
}`
};
}
@@ -410,7 +412,9 @@ class RentalCrawler {
);
if (!renewedDateMoment.isValid()) {
throw {
message: `Invalid renewed date : ${extractedData["re_realEstates_edited"]}`
message: `Invalid renewed date : ${
extractedData["re_realEstates_edited"]
}`
};
}

View File

@@ -1,6 +1,6 @@
"use strict";
const fetch = require("node-fetch");
const fetch = require("../../helpers/fetchWrapper");
const cheerio = require("cheerio");
const moment = require("moment-timezone");

View File

@@ -332,10 +332,14 @@ const findRealEstatesForSearchRequest = async (searchRequest, maxResults) => {
};
}
//When includeIncompleteAds are not defined - null it will consider it true
const order = [["updatedAt", "desc"]];
return db.RealEstate.findAll({
where: includeIncompleteAds ? queryIncludeIncomplete : query,
where:
includeIncompleteAds || includeIncompleteAds == null
? queryIncludeIncomplete
: query,
limit: maxResults,
order
});

View File

@@ -157,7 +157,7 @@ const findSearchRequestsForRealEstate = async realEstate => {
} else {
// If real estate dont have defined number of rooms ex. null
//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 ones that picked some values but also picked to includeIncomplete ads (or default)
numberOfRoomsQuery = {
[Op.or]: [
{
@@ -176,7 +176,10 @@ const findSearchRequestsForRealEstate = async realEstate => {
},
{
includeIncompleteAds: {
[Op.eq]: true
[Op.or]: {
[Op.eq]: true,
[Op.is]: null
}
}
}
]
@@ -226,7 +229,10 @@ const findSearchRequestsForRealEstate = async realEstate => {
},
{
includeIncompleteAds: {
[Op.eq]: true
[Op.or]: {
[Op.eq]: true,
[Op.is]: null
}
}
}
]
@@ -275,7 +281,10 @@ const findSearchRequestsForRealEstate = async realEstate => {
},
{
includeIncompleteAds: {
[Op.eq]: true
[Op.or]: {
[Op.eq]: true,
[Op.is]: null
}
}
}
]
@@ -313,7 +322,10 @@ const findSearchRequestsForRealEstate = async realEstate => {
},
{
includeIncompleteAds: {
[Op.eq]: true
[Op.or]: {
[Op.eq]: true,
[Op.is]: null
}
}
}
]
@@ -347,7 +359,10 @@ const findSearchRequestsForRealEstate = async realEstate => {
},
{
includeIncompleteAds: {
[Op.eq]: true
[Op.or]: {
[Op.eq]: true,
[Op.is]: null
}
}
}
]
@@ -381,7 +396,10 @@ const findSearchRequestsForRealEstate = async realEstate => {
},
{
includeIncompleteAds: {
[Op.eq]: true
[Op.or]: {
[Op.eq]: true,
[Op.is]: null
}
}
}
]
@@ -423,10 +441,13 @@ const findSearchRequestsForRealEstate = async realEstate => {
[Op.eq]: "ANY"
};
}
//Tag to check if incomplete ads are accepted in query
//Tag to check if incomplete ads are accepted in query which is default
if (checkForIncompleteWanted) {
query.includeIncompleteAds = {
[Op.eq]: true
[Op.or]: {
[Op.eq]: true,
[Op.is]: null
}
};
}

View File

@@ -2,7 +2,7 @@
const db = require("../../models/index");
const sequelize = require("sequelize");
const Op = sequelize.Op;
const { CHECK_UP_DAYS } = require("../../config/appConfig");
const { NO_CHECK_UP_DAYS } = require("../../config/appConfig");
const findRealEstatesForSearchRequest = async searchRequestId => {
const query = {
@@ -45,9 +45,9 @@ const findNotNotifiedMatches = async () => {
};
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
//The ones that received notification for real estate NO_CHECK_UP_DAYS days from now
const date = new Date();
const checkUpDate = date.getDate() - CHECK_UP_DAYS;
const checkUpDate = date.getDate() - NO_CHECK_UP_DAYS;
date.setDate(checkUpDate);
const dateQuery = {
createdAt: {

View File

@@ -3,12 +3,12 @@
const {
MAX_REAL_ESTATES_IN_EMAIL,
APP_URL,
STAGING
USE_KIVI_ENVIRONMENT_TAG
} = require("../config/appConfig");
const { AD_CATEGORY, AD_TYPE, EMAIL_FREQUENCY } = require("../common/enums");
//Tag to recognize staging from development
const stagingTag = STAGING ? "[STAGING] " : "";
//Tag to recognize staging from development if needed
const stagingTag = USE_KIVI_ENVIRONMENT_TAG ? "[STAGING] " : "";
const generateEmailFooter = (searchRequestId, emailFrequencyTitle) => {
return ` <div>Trenutno ste prijavljeni da obavještenja o novim nekretninama primate <strong>${emailFrequencyTitle.toLowerCase()} </strong>.</div>

View File

@@ -0,0 +1,13 @@
const nodeFetch = require("node-fetch");
const { USER_AGENT } = require("../config/appConfig");
const fetch = async (url, options = {}) => {
const newOptions = Object.assign({}, options);
if (!newOptions["headers"]) {
newOptions["headers"] = {};
}
newOptions["headers"]["User-Agent"] = USER_AGENT;
return nodeFetch(url, newOptions);
};
module.exports = fetch;

View File

@@ -15,15 +15,7 @@ module.exports = (sequelize, DataTypes) => {
allowNull: false,
defaultValue: {
type: "Polygon",
coordinates: [
[
[0, 0],
[0, 0],
[0, 0],
[0, 0],
[0, 0]
]
],
coordinates: [[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]],
crs: { type: "name", properties: { name: "EPSG:4326" } }
}
},

View File

@@ -1,7 +1,7 @@
"use strict";
const { STAGING } = require("../config/appConfig");
const { USE_KIVI_ENVIRONMENT_TAG } = require("../config/appConfig");
const stagingTag = STAGING ? "[STAGING] " : "";
const stagingTag = USE_KIVI_ENVIRONMENT_TAG ? "[STAGING] " : "";
const {
matchRealEstates,
@@ -131,7 +131,7 @@ const notifyRequestsWithDailyOption = async () => {
};
const checkUpNotify = async () => {
const searchRequestsForCheckUp = await findAllRequestsForCheckUp();
/* const searchRequestsForCheckUp = await findAllRequestsForCheckUp();
const asyncSendEmailActions = [];
@@ -144,7 +144,7 @@ const checkUpNotify = async () => {
asyncSendEmailActions.push(sendEmailPromise);
sendEmailPromise.catch(err => console.log("[Email Sending Failed]", err));
}
await Promise.all(asyncSendEmailActions);
await Promise.all(asyncSendEmailActions); */
};
module.exports = {

View File

@@ -61,9 +61,8 @@
<p class="distinguished">
<label class="checkbox-label">
<input type="checkbox" class="filled-in" name="includeIncompleteAds"
<% if (includeIncompleteAds) { %>
checked
<% } %>>
>
<span>Uključi i oglase bez potpunih informacija</span>
</label>
</p>

View File

@@ -217,7 +217,7 @@
});
</script>
<script
src="https://maps.googleapis.com/maps/api/js?key=<%= process.env.API_MAP_KEY %>&language=bs&libraries=places&callback=initMap"
src="https://maps.googleapis.com/maps/api/js?key=<%= process.env.GOOGLE_MAP_KEY %>&language=bs&libraries=places&callback=initMap"
async
defer
></script>

View File

@@ -8,18 +8,19 @@ 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
ENVIRONMENT=Variable to denote development, staging and production
KIVI_ENVIRONMENT=Variable to denote development, staging and production
USER_AGENT=User agent header to send in fetch requests
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
CHECK_UP_DAYS=Check up email is sent after this number of days without notification
NO_CHECK_UP_DAYS=Check up email is sent after this number of days without notification
#=============== GOOGLE ANALYTICS =============#
GA_ID=Google Analytics ID
#=============== GOOGLE MAPS =============#
API_MAP_KEY=(your-key-here)
GOOGLE_MAP_KEY=(your-key-here)
#=============== AWS SDK EMAIL SETTINGS =======#
AWS_KEY_ID=(your-key-here)
@@ -69,4 +70,4 @@ AKTIDO_FORCE_CRAWL=Non-zero value will force crawler to crawl all pages without
SALJIC_MAX_RESULTS_PER_PAGE=For Saljic crawler, this represents how many ads are crawled at once
SALJIC_CRAWLER_AD_TYPE=enum name of what type of ads should be crawled, check common/enums.js file for valid values
SALJIC_CRAWLER_AD_CATEGORIES=comma separated list of enum names of categories to be included, check common/enums.js file for valid values
SALJIC_FORCE_CRAWL=Non-zero value will force crawler to crawl all pages without stopping when known real estate is found
SALJIC_FORCE_CRAWL=Non-zero value will force crawler to crawl all pages without stopping when known real estate is found