Compare commits

...

11 Commits

Author SHA1 Message Date
Naida Vatric
d23ddf849f Results title text made into link. 2020-01-07 01:06:22 +01:00
Naida Vatric
38bd0343f5 Merge branch 'results-link' of gitlab.com:saburly/marketalarm/web into no-all-results-email 2020-01-07 01:01:57 +01:00
Naida Vatric
259799144e Merge branch 'rental-crawler-fix' into 'master'
Rental crawler fix

See merge request saburly/marketalarm/web!80
2020-01-06 23:12:52 +00:00
Naida Vatric
bc73d4159d Merge branch 'master' into 'rental-crawler-fix'
# Conflicts:
#   .gitignore
2020-01-06 23:12:40 +00:00
Naida Vatric
37ad32fe76 Merge branch 'edit-location-start' into 'master'
Edit location start

See merge request saburly/marketalarm/web!79
2020-01-06 23:10:16 +00:00
Naida Vatric
94875a0fa3 Merge branch 'add-currency-to-price-filters' into 'master'
Add currency to price filters

See merge request saburly/marketalarm/web!78
2020-01-06 23:09:40 +00:00
Naida Vatric
fa4e0d64de Changed email content to show number of all matching real estates. 2020-01-06 23:59:56 +01:00
Naida Vatric
0c2d218d29 Changed floor numbers and basement-attic tag. 2020-01-02 00:10:31 +01:00
Naida Vatric
fed2dc00dc Changed number of rooms. 2019-12-29 23:42:39 +01:00
Naida Vatric
cc78e5acd5 Updated location to start from selected when edit. 2019-12-20 01:02:57 +01:00
Naida Vatric
55319a54e9 WIP Idea to implement bound map to be equal to selected 2019-12-19 02:12:23 +01:00
9 changed files with 197 additions and 73 deletions

2
.gitignore vendored
View File

@@ -2,4 +2,4 @@ node_modules/
.env
.idea/
.eslintrc
.vscode/
.vscode/

View File

@@ -4,9 +4,37 @@ const getLocation = async (req, res) => {
const title = "Odaberite lokaciju";
const nextStep = req.query.nextStep || "/";
//Check if location data already exists (active request)
//If it does then get location is called through edit field query
//and map should show already selected location not initial map
let selectedLatLngBounds = {};
let boundsSelected = false;
const searchRequest = await currentSearchRequest(req);
if (!searchRequest || !searchRequest.dataValues) {
res.render("notFound", { title: " " });
return;
}
const selectedArea = searchRequest.areaToSearch;
const sw = selectedArea.coordinates[0][3];
const ne = selectedArea.coordinates[0][1];
if (sw[0] && ne[0]) {
selectedLatLngBounds = {
swLat: sw[1],
swLng: sw[0],
neLat: ne[1],
neLng: ne[0]
};
boundsSelected = true;
}
res.render("location", {
nextStep,
title
title,
boundsSelected,
selectedLatLngBounds
});
};

View File

@@ -312,7 +312,7 @@ class RentalCrawler {
let numberOfRooms =
parseInt(extractedData["re_realEstates_roomsNO"]) +
parseInt(extractedData["re_realEstates_bedroomNO"]) || null,
parseInt(extractedData["re_realEstates_bedNO"]) || null,
numberOfFloors =
parseInt(extractedData["re_realEstates_floorsNO"]) ||
this.getNumberOfFloorsFromFloorId(extractedData["re_floorNO_id"]),
@@ -352,7 +352,9 @@ class RentalCrawler {
realEstatePropertiesFromInfrastructure.phoneConnection,
cableTV = realEstatePropertiesFromInfrastructure.cableTV,
internet = realEstatePropertiesFromInfrastructure.internet,
basementAttic = realEstatePropertiesFromSpaces.basementAttic,
basementAttic =
realEstatePropertiesFromSpaces.basementAttic ||
this.checkBasemAtticFromFloors(extractedData["re_floorNO_id"]),
storeRoom = realEstatePropertiesFromSpaces.storeRoom,
videoSurveillance =
realEstatePropertiesFromDescriptions.videoSurveillance ||
@@ -397,9 +399,7 @@ class RentalCrawler {
);
if (!publishedDateMoment.isValid()) {
throw {
message: `Invalid published date : ${
extractedData["re_realEstates_inserted"]
}`
message: `Invalid published date : ${extractedData["re_realEstates_inserted"]}`
};
}
@@ -410,9 +410,7 @@ class RentalCrawler {
);
if (!renewedDateMoment.isValid()) {
throw {
message: `Invalid renewed date : ${
extractedData["re_realEstates_edited"]
}`
message: `Invalid renewed date : ${extractedData["re_realEstates_edited"]}`
};
}
@@ -782,8 +780,42 @@ class RentalCrawler {
if (floorIds.length === 0) {
return null;
}
let noOfFloors = floorIds.length;
// Floors of 'suteren', 'podrum', 'tavan' and 'potkrovlje' are not counted
floorIds.forEach(id => {
if (
parseInt(id) === 1 ||
parseInt(id) === 2 ||
parseInt(id) === 12 ||
parseInt(id) === 14
) {
noOfFloors--;
}
});
return noOfFloors;
}
return floorIds.length;
checkBasemAtticFromFloors(floorsIdText) {
// floorIdText can be array of numbers, separated by comma or number
const floorIds = floorsIdText.split(",");
let check = false;
if (floorIds.length === 0) {
check = false;
}
//If floors 'suteren', 'podrum', 'tavan' and 'potkrovlje' exists then tag for basement-attic is true
floorIds.forEach(id => {
if (
parseInt(id) === 1 ||
parseInt(id) === 2 ||
parseInt(id) === 12 ||
parseInt(id) === 14
) {
check = true;
}
});
return check;
}
async sleep(ms) {

View File

@@ -23,16 +23,19 @@ const generateRealEstateLinks = realEstates => {
const generateNotificationEmail = (
realEstates,
searchRequestId,
noAllRealEstates,
dailyNotification = false
) => {
const truncateList = realEstates.length > MAX_REAL_ESTATES_IN_EMAIL;
const realEstatesToShow = truncateList
? realEstates.slice(0, MAX_REAL_ESTATES_IN_EMAIL)
: realEstates;
const allRealEstatesLink = `${APP_URL}/nekretnine/${searchRequestId}`;
const realEstateLinks = generateRealEstateLinks(realEstatesToShow);
const moreRealEstates = `<div>Kompletan spisak nekretnina možete pogledati na <a href="${allRealEstatesLink}">listi nekretnina</a><div>`;
const moreRealEstates = `<div>Kompletan spisak nekretnina (${noAllRealEstates}) možete pogledati na <a href="${allRealEstatesLink}">listi nekretnina</a><div>`;
const emailFooter = generateEmailFooter(searchRequestId);
const asapMessageBody =
realEstates.length > 1
@@ -70,6 +73,7 @@ const generateNewSearchRequestEmail = (searchRequest, matchingRealEstates) => {
} = 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/>

View File

@@ -154,3 +154,7 @@ h3 {
margin-top: 2rem;
margin-bottom: 1rem;
}
.estates-link {
color: rgba(0, 0, 0, 0.87);
}

View File

@@ -8,7 +8,10 @@ const {
generateNewSearchRequestEmail,
generateEmailSubject
} = require("../helpers/emailContentGenerator");
const { findNotNotifiedMatches } = require("../helpers/db/searchRequestMatch");
const {
findNotNotifiedMatches,
findRealEstatesForSearchRequest
} = require("../helpers/db/searchRequestMatch");
const { sendEmail } = require("../services/emailService");
const notifyForNewRealEstates = async newRealEstates => {
@@ -39,10 +42,18 @@ const notifyMatches = async (matches, dailyNotification = false) => {
const { email, subscribed } = searchRequest;
if (notifyNow && subscribed) {
const allMatchingRealEstates = matches[id].realEstates || [];
//Variable allMatchingRealEstates are real estates that are "new" on the market
//the ones that we notify user in this moment, not all that already exists in db
//New variable allRealEstates are all real estates that exists in db for search req
const allRealEstates = await findRealEstatesForSearchRequest(id);
const noAllRealEstates = allRealEstates.length;
if (allMatchingRealEstates.length > 0) {
const emailContent = generateNotificationEmail(
allMatchingRealEstates,
id,
noAllRealEstates,
dailyNotification
);
const emailSubject = generateEmailSubject(

View File

@@ -1,13 +1,17 @@
<div class="row center-align">
<h3>
Područje na mapi će biti uključeno u pretragu. Namjestite mapu na ulice
koje želite da budu vidljive.
Područje na mapi će biti uključeno u pretragu. Namjestite mapu na ulice koje
želite da budu vidljive.
</h3>
</div>
<div class="row center-align">
<div class="col s12 m12 l12 xl12">
<input id="autocompleteInput" placeholder="Unesite grad, naselje ili ulicu..." type="text" />
<input
id="autocompleteInput"
placeholder="Unesite grad, naselje ili ulicu..."
type="text"
/>
</div>
</div>
@@ -17,12 +21,17 @@
</div>
</div>
<br>
<br />
<form method="POST" id="form-map-output">
<div class="row center-align">
<div class="col s6 push-s3">
<a id="submit" href="#" class="welcome-center-button waves-effect waves-light btn">Dalje</a>
<a
id="submit"
href="#"
class="welcome-center-button waves-effect waves-light btn"
>Dalje</a
>
</div>
</div>
<input type="hidden" name="north" id="north" />
@@ -41,15 +50,15 @@
function locateMe() {
if (navigator.geolocation) {
function onLocationSuccess (position) {
const coordinates = position && position.coords ? position.coords : null;
if (coordinates){
function onLocationSuccess(position) {
const coordinates =
position && position.coords ? position.coords : null;
if (coordinates) {
const longitude = coordinates.longitude || null;
const latitude = coordinates.latitude || null;
if (longitude && latitude && map){
map.setCenter({lat: latitude, lng: longitude});
if (longitude && latitude && map) {
map.setCenter({ lat: latitude, lng: longitude });
map.setZoom(16);
}
}
@@ -61,20 +70,20 @@
function initMap() {
const BOSNIA_BOUNDS = {
north: 45.70,
north: 45.7,
south: 41.69,
west: 15.55,
east: 20.77,
east: 20.77
};
const SARAJEVO_COORDINATES = {
lat: 43.85,
lng: 18.41,
lng: 18.41
};
const mapElement = document.getElementById('map');
const mapElement = document.getElementById("map");
const restrictMapPanningToBosniaOnly = {
latLngBounds: BOSNIA_BOUNDS,
strictBounds: true,
strictBounds: true
};
const initialMapParams = {
center: SARAJEVO_COORDINATES,
@@ -87,38 +96,50 @@
};
map = new google.maps.Map(mapElement, initialMapParams);
const inputElement = document.getElementById('autocompleteInput');
const restrictAutocompleteResultsToBosniaOnly = {'country': 'ba'};
const inputElement = document.getElementById("autocompleteInput");
const restrictAutocompleteResultsToBosniaOnly = { country: "ba" };
const initialAutocompleteParams = {
types: ['geocode'],
types: ["geocode"],
componentRestrictions: restrictAutocompleteResultsToBosniaOnly,
fields: ['geometry', 'types', 'address_components']
fields: ["geometry", "types", "address_components"]
};
autocomplete = new google.maps.places.Autocomplete(inputElement, initialAutocompleteParams);
autocomplete.bindTo('bounds', map);
autocomplete.addListener('place_changed', onPlaceChanged);
autocomplete = new google.maps.places.Autocomplete(
inputElement,
initialAutocompleteParams
);
autocomplete.bindTo("bounds", map);
autocomplete.addListener("place_changed", onPlaceChanged);
pacSelectFirst(inputElement);
addLocateMeButton(map);
//After map initialization we check if area is already selected
//If yes we bound map to show already selected area
const boundsSelected = <%- boundsSelected %>;
const selectedLatLngBounds = <%- JSON.stringify(selectedLatLngBounds) %>;
if (boundsSelected) {
boundMapToSelected(map, selectedLatLngBounds);
}
}
function addLocateMeButton(map) {
var parent = document.createElement('div');
var parent = document.createElement("div");
parent.className = "locate-me-container";
var a = document.createElement('a');
var a = document.createElement("a");
a.id = "locateMe";
a.className = "btn-floating";
var i = document.createElement('i');
var i = document.createElement("i");
i.innerText = "gps_fixed";
i.className = "material-icons right";
a.appendChild(i)
a.appendChild(i);
a.addEventListener("click", locateMe);
parent.appendChild(a)
parent.appendChild(a);
map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(parent)
map.controls[google.maps.ControlPosition.RIGHT_BOTTOM].push(parent);
}
function onPlaceChanged() {
@@ -133,33 +154,49 @@
function pacSelectFirst(input) {
// store the original event binding function
const _addEventListener = input.addEventListener
? input.addEventListener
: input.attachEvent
? input.addEventListener
: input.attachEvent;
function addEventListenerWrapper (type, listener) {
function addEventListenerWrapper(type, listener) {
// Simulate a 'down arrow' keypress on hitting 'return' when no pac suggestion is selected,
// and then trigger the original listener.
if (type == 'keydown') {
const originalListener = listener
listener = function (event) {
const suggestionSelected = $('.pac-item-selected').length > 0
if (event.key == 'Enter' && !suggestionSelected) {
const simulatedDownArrow = $.Event('keydown', {
if (type == "keydown") {
const originalListener = listener;
listener = function(event) {
const suggestionSelected = $(".pac-item-selected").length > 0;
if (event.key == "Enter" && !suggestionSelected) {
const simulatedDownArrow = $.Event("keydown", {
keyCode: 40,
which: 40
})
originalListener.apply(input, [simulatedDownArrow])
});
originalListener.apply(input, [simulatedDownArrow]);
}
originalListener.apply(input, [event])
}
originalListener.apply(input, [event]);
};
}
_addEventListener.apply(input, [type, listener])
_addEventListener.apply(input, [type, listener]);
}
input.addEventListener = addEventListenerWrapper
input.attachEvent = addEventListenerWrapper
input.addEventListener = addEventListenerWrapper;
input.attachEvent = addEventListenerWrapper;
}
function boundMapToSelected(map, selectedLatLngBounds) {
const swBound = new google.maps.LatLng(
selectedLatLngBounds.swLat,
selectedLatLngBounds.swLng
);
const neBound = new google.maps.LatLng(
selectedLatLngBounds.neLat,
selectedLatLngBounds.neLng
);
let bounds = new google.maps.LatLngBounds();
bounds.extend(swBound);
bounds.extend(neBound);
map.fitBounds(bounds);
}
$(document).ready(function() {
@@ -171,11 +208,16 @@
$("#east").val(mapBounds.getNorthEast().lng());
$("#west").val(mapBounds.getSouthWest().lng());
$("#locationInput").val(document.getElementById('autocompleteInput').value);
$("#locationInput").val(
document.getElementById("autocompleteInput").value
);
$("#form-map-output").submit();
});
});
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=<%= process.env.API_MAP_KEY %>&language=bs&libraries=places&callback=initMap" async
defer></script>
<script
src="https://maps.googleapis.com/maps/api/js?key=<%= process.env.API_MAP_KEY %>&language=bs&libraries=places&callback=initMap"
async
defer
></script>

View File

@@ -1,13 +1,16 @@
<div class="row center-align">
<ul class="collection with-header">
<% for(const realEstate of realEstates) { %>
<li class="collection-item">
<div><%= realEstate.title %>
<a href="<%= realEstate.url %>" class="kivi-color secondary-content">
<ul class="collection with-header">
<% for(const realEstate of realEstates) { %>
<li class="collection-item">
<div>
<a href="<%= realEstate.url %>" class="estates-link">
<%= realEstate.title %>
<div class="kivi-color secondary-content">
<i class="material-icons">send</i>
</a>
</div>
</li>
<% } %>
</ul>
</div>
</div>
</a>
</div>
</li>
<% } %>
</ul>
</div>

View File

@@ -13,5 +13,5 @@ if (urlToScrape) {
})();
} else {
console.log("No URL to scrape. Use like this : ");
console.log("npm run test-olx-scraper -- URL_TO_SCRAPE");
console.log("npm run test-rental-scraper -- URL_TO_SCRAPE");
}