remove all logging noise

This commit is contained in:
Bilal Catic
2019-09-06 12:01:25 +02:00
parent 1d29f6c8ac
commit b0f9c2c47b
6 changed files with 753 additions and 752 deletions

View File

@@ -1,11 +1,8 @@
const { allMarketAlertsByRequest } = require("../helpers/db/dbHelper");
const getRealEstates = async (req, res) => {
console.log("Enter get realestates");
const request = req.params["request_id"];
console.log(req.params["request_id"]);
const realEstates = await allMarketAlertsByRequest(request);
console.log(realEstates);
const title = "Ovo su nekretnine koje smo pronašli za vas";
res.render("realEstates", { realEstates, title });

View File

@@ -198,8 +198,6 @@ const sendBulkEmail = async marketAlerts => {
ReplacementTemplateData: repData
});
}
console.log("AWS EMAIL : Bulk email replacement data:");
console.log(destinations);
var params = {
Destinations: destinations,
@@ -214,8 +212,6 @@ const sendBulkEmail = async marketAlerts => {
.sendBulkTemplatedEmail(params)
.promise();
const awsResult = await sendPromise;
console.log("AWS SES bulk email response");
console.log(awsResult);
} catch (e) {
console.log("Could not send bulk email", e);
}

View File

@@ -1,8 +1,8 @@
const fetch = require('node-fetch');
const cheerio = require('cheerio');
const { allRERequest, findPointInsideBoundingBox } = require('../db/dbHelper');
const { getRealEstateTypeEnum } = require('../enums');
const { getRegion, getMunicipality } = require('../codes')
const fetch = require("node-fetch");
const cheerio = require("cheerio");
const { allRERequest, findPointInsideBoundingBox } = require("../db/dbHelper");
const { getRealEstateTypeEnum } = require("../enums");
const { getRegion, getMunicipality } = require("../codes");
const Promise = require("bluebird");
module.exports = class OlxCrawler {
@@ -20,56 +20,62 @@ module.exports = class OlxCrawler {
return Promise.map(indexers, function(indexer) {
return indexer.indexWithPagination();
}).then(async (results) => {
return results
})
}).then(async results => {
return results;
});
}
async crawl() {
console.log("OLX CRAWLER: start crawl");
const filteredResults = [];
const realestateRequests = await allRERequest();
console.log("OLX CRAWLER: found " + realestateRequests.length + "subscribed RealEstateRequests");
const urls = this.createRequestUrls(realestateRequests);
let results = await this.indexPages(urls, this.fromPage, this.toPage, this.maxResults);
console.log("Final crawler results");
let results = await this.indexPages(
urls,
this.fromPage,
this.toPage,
this.maxResults
);
const flatResults = results.flat();
console.log(flatResults);
if (flatResults) {
console.log(flatResults.length);
for (const finalResult of flatResults) {
if (null !== finalResult) {
if (finalResult.lat !== undefined && finalResult.lat !== null && finalResult.lat !== "") {
const pointInsideBoundingBox = await findPointInsideBoundingBox([finalResult.lng, finalResult.lat], finalResult.email, finalResult.uuid);
if (
finalResult.lat !== undefined &&
finalResult.lat !== null &&
finalResult.lat !== ""
) {
const pointInsideBoundingBox = await findPointInsideBoundingBox(
[finalResult.lng, finalResult.lat],
finalResult.email,
finalResult.uuid
);
if (pointInsideBoundingBox[0].length !== 0) {
finalResult.hasLocation = true
finalResult.hasLocation = true;
filteredResults.push(finalResult);
} else {
finalResult.hasLocation = false
finalResult.hasLocation = false;
filteredResults.push(finalResult);
}
}
}
}
console.log("OLX CRAWLER: number of olx crawler results, after geo location filtering: " + filteredResults.length);
return filteredResults;
}
return []
return [];
}
createRequestUrls(realestateRequests) {
const urls = []
const urls = [];
for (const request of realestateRequests) {
const realsestateType = "kategorija=" + getRealEstateTypeEnum(request.realEstateType).olxCategory;
const realsestateType =
"kategorija=" +
getRealEstateTypeEnum(request.realEstateType).olxCategory;
const region = "kanton=" + getRegion(request.region).olxid;
const municipality = "grad%5B%5D=" + getMunicipality(request.region, request.municipality).olxid;
const municipality =
"grad%5B%5D=" +
getMunicipality(request.region, request.municipality).olxid;
const sizeMin = "kvadrata_min=" + request.sizeMin;
const sizeMax = "kvadrata_max=" + request.sizeMax;
const priceMin = "od=" + request.priceMin;
@@ -80,8 +86,7 @@ module.exports = class OlxCrawler {
email: request.email,
uuid: request.uniqueId,
hrefs: this.hrefs
}
console.log(olxUrl.url);
};
urls.push(olxUrl);
}
@@ -89,9 +94,7 @@ module.exports = class OlxCrawler {
}
};
class Indexer {
/**
*
* @param {String|Array} olxUrl single or array of objects containing url email and uuid
@@ -104,83 +107,72 @@ class Indexer {
}
async indexWithPagination(pageNumber = 1) {
console.log("This is olxUrl:" + this.olxUrl.url);
const pageNr = this.olxUrl.url.match(/\d+$/);
const indexers = this.prepareIndexers(pageNumber ? [pageNumber] : pageNr);
try {
return Promise.map(indexers.indexers, function(indexer) {
return indexer.indexPage(pageNumber);
}).then(async (results) => {
}).then(async results => {
let hasResults = false;
results.forEach(result => {
if (!hasResults) {
console.log("No results detected")
hasResults = result.hasResults
hasResults = result.hasResults;
}
});
if (!hasResults) {
console.log("HAS NO MORE RESULTS, stop the paging, there are some results and they should contain only HREFS");
console.log(results.length);
const singlePageIndexers = this.prepareHrefIndexers(results);
if (singlePageIndexers.length === 0) {
console.log("THERE IS NOT EVEN SINGLE RESULT");
return []
return [];
}
return Promise.map(singlePageIndexers, function(indexer) {
return indexer.indexSingle();
}).then(async (results) => {
console.log("SinglePageMethod in HAS NO RESULTS, MarketAralms");
console.log(results.length);
}).then(async results => {
return results;
});
} else {
console.log("HAS MORE RESULTS, should only contain HREFS");
console.log(results.length);
const newResults = await this.indexWithPagination(results[0].pageNumber + 5);
const newResults = await this.indexWithPagination(
results[0].pageNumber + 5
);
const singlePageIndexers = this.prepareHrefIndexers(results);
const newerResults = await Promise.map(singlePageIndexers, function (indexer) {
const newerResults = await Promise.map(singlePageIndexers, function(
indexer
) {
return indexer.indexSingle();
}).then(async (results) => {
console.log("SinglePageMethod HAS RESULTS, should contain MarketAlerts only");
console.log(results.length);
}).then(async results => {
return results;
});
Array.prototype.push.apply(newResults, newerResults);
return newResults;
}
});
} catch (e) {
console.error("Error has accured", e);
}
}
prepareIndexers(pageNr) {
console.log("Entering prepareIndexers : page nr - " + pageNr);
const indexers = [];
let lastPageNumber;
if (pageNr) {
for (let index = Number(pageNr[0]); index <= Number(pageNr[0]) + 5; index++) {
for (
let index = Number(pageNr[0]);
index <= Number(pageNr[0]) + 5;
index++
) {
lastPageNumber = index;
const newOlxUrl = {
url: this.olxUrl.url.replace(/\d+$/, "") + index,
email: this.olxUrl.email,
uuid: this.olxUrl.uuid,
hrefs: this.olxUrl.hrefs
}
};
indexers.push(new Indexer(newOlxUrl));
}
} else {
for (let index = 1; index <= 5; index++) {
@@ -190,7 +182,7 @@ class Indexer {
email: this.olxUrl.email,
uuid: this.olxUrl.uuid,
hrefs: this.olxUrl.hrefs
}
};
indexers.push(new Indexer(newOlxUrl));
}
}
@@ -201,7 +193,7 @@ class Indexer {
}
prepareHrefIndexers(results) {
const indexers = []
const indexers = [];
if (!Array.isArray(results)) {
results.hrefs.forEach(href => {
@@ -210,29 +202,24 @@ class Indexer {
email: results.olxUrl.email,
uuid: results.olxUrl.uuid,
hrefs: this.olxUrl.hrefs
}
};
indexers.push(new Indexer(newOlxUrl));
});
} else {
results.forEach(result => {
if (result !== null && result.hasOwnProperty('hrefs')) {
if (result !== null && result.hasOwnProperty("hrefs")) {
result.hrefs.forEach(href => {
const newOlxUrl = {
url: href,
email: result.olxUrl.email,
uuid: result.olxUrl.uuid,
hrefs: this.olxUrl.hrefs
}
};
indexers.push(new Indexer(newOlxUrl));
})
});
}
});
}
@@ -240,53 +227,47 @@ class Indexer {
}
async indexPage(pageNumber) {
console.log("Page number in index page, max page number :")
console.log(pageNumber);
try {
console.log("Indexing page: " + this.olxUrl.url);
const res = await fetch(this.olxUrl.url);
const body = await res.text();
const $ = cheerio.load(body);
const hrefs = [];
let hasResults = false
let hasResults = false;
$('#rezultatipretrage').find('.listitem').each((i, elem) => {
hasResults = true
const href = $(elem).find('a').first().attr('href');
$("#rezultatipretrage")
.find(".listitem")
.each((i, elem) => {
hasResults = true;
const href = $(elem)
.find("a")
.first()
.attr("href");
hrefs.push(href);
});
console.log("this is hrefs for olxUrl" + this.olxUrl.url);
console.log("NUMBER OF HREFS " + hrefs.length);
return {
hrefs: hrefs,
hasResults: hasResults,
pageNumber: pageNumber,
olxUrl: this.olxUrl
}
};
} catch (e) {
console.error('Exception caught:' + e);
console.error("Exception caught:" + e);
}
}
async indexSingle() {
try {
console.log("Index single");
console.log(this.olxUrl.url);
if (this.olxUrl.url === undefined) {
return {}
return {};
}
// if (global.hrefs) {
if (this.olxUrl.hrefs[this.olxUrl.uuid] && this.olxUrl.hrefs[this.olxUrl.uuid].includes(this.olxUrl.url)) {
console.log("We found duplicate URL");
return null
if (
this.olxUrl.hrefs[this.olxUrl.uuid] &&
this.olxUrl.hrefs[this.olxUrl.uuid].includes(this.olxUrl.url)
) {
return null;
}
// }
@@ -294,26 +275,36 @@ class Indexer {
const body = await res.text();
const $ = cheerio.load(body);
const title = $('#naslovartikla').text().trim();
const realEstateType = $('#artikal_glavni_div > div.artikal_lijevo > div:nth-child(3) > div > span:nth-child(3) > a > span').text();
const title = $("#naslovartikla")
.text()
.trim();
const realEstateType = $(
"#artikal_glavni_div > div.artikal_lijevo > div:nth-child(3) > div > span:nth-child(3) > a > span"
).text();
const price = $('#pc > p:nth-child(2)').text();
const size = $('#dodatnapolja1 > div:nth-child(1) > div.df2').text();
const rooms = $('#dodatnapolja1 > div:nth-child(2) > div.df2').text();
const address = $('#dodatnapolja1 > div:nth-child(5) > div.df2').text();
const gardenSize = $('#dodatnapolja1 > div:nth-child(6) > div.df2').text();
const location = $('#artikal_glavni_div > div.artikal_lijevo > div.op.pop.mobile-lokacija').attr('data-content');
const price = $("#pc > p:nth-child(2)").text();
const size = $("#dodatnapolja1 > div:nth-child(1) > div.df2").text();
const rooms = $("#dodatnapolja1 > div:nth-child(2) > div.df2").text();
const address = $("#dodatnapolja1 > div:nth-child(5) > div.df2").text();
const gardenSize = $(
"#dodatnapolja1 > div:nth-child(6) > div.df2"
).text();
const location = $(
"#artikal_glavni_div > div.artikal_lijevo > div.op.pop.mobile-lokacija"
).attr("data-content");
const time = $('time').attr('datetime');
const olxId = $('#artikal_glavni_div > div.artikal_lijevo > div:nth-child(15) > div:nth-child(4) > div.df2').text();
const time = $("time").attr("datetime");
const olxId = $(
"#artikal_glavni_div > div.artikal_lijevo > div:nth-child(15) > div:nth-child(4) > div.df2"
).text();
const descriptions = $('.artikal_detaljniopis_tekst');
const descriptions = $(".artikal_detaljniopis_tekst");
const latLngRe = /LatLng\(([0-9]+\.[0-9]+)\,\s+([0-9]+\.[0-9]+)\)/g;
const imgRe = /href":("[^"]*")/g;
const matches = latLngRe.exec(body);
let lng = '',
lat = '';
const parsePrice = (price) => parseFloat(price.replace(".", ""))
let lng = "",
lat = "";
const parsePrice = price => parseFloat(price.replace(".", ""));
if (matches && matches.length >= 3) {
lat = matches[1];
@@ -344,32 +335,30 @@ class Indexer {
longDescription: descriptions.last().text(),
lat,
lng,
loc: [parseFloat(lat), parseFloat(lng)],
loc: [parseFloat(lat), parseFloat(lng)]
};
return data;
} catch (e) {
console.error('Exception caught: ' + e.message);
console.error("Exception caught: " + e.message);
}
return null;
}
getCategoryId(category) {
switch (category) {
case 'Stanovi':
return 'stan';
case "Stanovi":
return "stan";
case 'Vikendice':
return 'vikendica'
case "Vikendice":
return "vikendica";
case 'Kuće':
return 'kuca';
case "Kuće":
return "kuca";
default:
return '';
return "";
}
}
}

View File

@@ -4,8 +4,6 @@ const db = require("../models/index");
const { allMarketAlerts } = require("../helpers/db/dbHelper");
async function crawlAll() {
console.log("CRAWLER SERVICE: crawlAll");
try {
const marketAlertsFromDb = await allMarketAlerts(true);
const hrefs = [];
@@ -18,8 +16,6 @@ async function crawlAll() {
hrefs[marketAlert.request].push(marketAlert.url);
});
console.log("CRAWLER SERVICE: GLOBAL HREFS");
console.log(hrefs);
const olxCrawler = new OlxCrawler(hrefs);
const crawlers = [olxCrawler];
@@ -30,11 +26,6 @@ async function crawlAll() {
try {
const marketAlertsFromDb = await allMarketAlerts(false, true);
console.log(
"CRAWLER SERVICE: number of existing MarketAlerts from db: " +
marketAlertsFromDb.length
);
const marketAlerts = [];
const mergedResults = [].concat.apply([], results);
@@ -56,9 +47,6 @@ async function crawlAll() {
hasLocation: result.hasLocation
});
}
console.log(
"CRAWLER SERVICE: Number of crawler results: " + marketAlerts.length
);
try {
const filteredMarketAlerts = marketAlerts.filter(
@@ -67,10 +55,6 @@ async function crawlAll() {
return elem.url === url && elem.request === request;
})
);
console.log(
"CRAWLER SERVICE: Number of new crawler results: " +
filteredMarketAlerts.length
);
await db.MarketAlert.bulkCreate(filteredMarketAlerts);
} catch (e) {

View File

@@ -8,15 +8,9 @@ const {
async function processNotifications() {
try {
const marketAlerts = await allMarketAlerts(false, false);
console.log(marketAlerts.length);
await createMarketAlertEmailTemplate();
if (marketAlerts.length > 0) {
console.log(
"NOTIFICATION SERVICE: Number of new alerts: " + marketAlerts.length
);
await sendBulkEmail(marketAlerts);
} else {
console.log("NOTIFICATION SERVICE: No new alerts");
}
await db.MarketAlert.update(

View File

@@ -4,7 +4,12 @@
<script src="https://code.jquery.com/jquery-3.4.0.js"></script>
</head>
<body>
<select name="kanton" id="kanton" class="drop-select" onchange="pronadji_gradove()">
<select
name="kanton"
id="kanton"
class="drop-select"
onchange="pronadji_gradove()"
>
<option value="">Iz svih lokacija</option>
<option value="" disabled="">Federacija BiH</option>
@@ -28,12 +33,15 @@
<option value="12">Disktrikt Brčko</option>
</select>
<div style="height:40px;">
<span class="drop-container" style="width: 168px;">
<span class="drop-label">Mjesto</span><span class="drop-arrow"></span>
<select name="unskosanski" id="unskosanski" class="drop-select" onchange="stavi_grad()">
<select
name="unskosanski"
id="unskosanski"
class="drop-select"
onchange="stavi_grad()"
>
<option value="0">Mjesto</option>
<option value="75">Bihać</option>
<option value="373">Bosanska Krupa</option>
@@ -47,12 +55,15 @@
</span>
</div>
<div style="height:40px;">
<span class="drop-container" style="width: 168px;">
<span class="drop-label">Mjesto</span><span class="drop-arrow"></span>
<select name="posavski" id="posavski" class="drop-select" onchange="stavi_grad()">
<select
name="posavski"
id="posavski"
class="drop-select"
onchange="stavi_grad()"
>
<option value="0">Mjesto</option>
<option value="6144">Domaljevac</option>
<option value="424">Odžak</option>
@@ -62,12 +73,15 @@
</span>
</div>
<div style="height:40px;">
<span class="drop-container" style="width: 168px;">
<span class="drop-label">Mjesto</span><span class="drop-arrow"></span>
<select name="tuzlanski" id="tuzlanski" class="drop-select" onchange="stavi_grad()">
<select
name="tuzlanski"
id="tuzlanski"
class="drop-select"
onchange="stavi_grad()"
>
<option value="0">Mjesto</option>
<option value="2">Banovići</option>
<option value="1090">Doboj-Istok</option>
@@ -86,12 +100,15 @@
</span>
</div>
<div style="height:40px;">
<span class="drop-container" style="width: 168px;">
<span class="drop-label">Mjesto</span><span class="drop-arrow"></span>
<select name="zenickodobojski" id="zenickodobojski" class="drop-select" onchange="stavi_grad()">
<select
name="zenickodobojski"
id="zenickodobojski"
class="drop-select"
onchange="stavi_grad()"
>
<option value="0">Mjesto</option>
<option value="704">Breza</option>
<option value="1122">Doboj-Jug</option>
@@ -109,12 +126,15 @@
</span>
</div>
<div style="height:40px;">
<span class="drop-container" style="width: 168px;">
<span class="drop-label">Mjesto</span><span class="drop-arrow"></span>
<select name="" id="bosanskopodrinjski" class="drop-select" onchange="stavi_grad()">
<select
name=""
id="bosanskopodrinjski"
class="drop-select"
onchange="stavi_grad()"
>
<option value="0">Mjesto</option>
<option value="1289">Foča</option>
<option value="1588">Goražde</option>
@@ -123,12 +143,15 @@
</span>
</div>
<div style="height:40px;">
<span class="drop-container" style="width: 168px;">
<span class="drop-label">Mjesto</span><span class="drop-arrow"></span>
<select name="" id="srednjobosanski" class="drop-select" onchange="stavi_grad()">
<select
name=""
id="srednjobosanski"
class="drop-select"
onchange="stavi_grad()"
>
<option value="0">Mjesto</option>
<option value="732">Bugojno</option>
<option value="810">Busovača</option>
@@ -146,12 +169,15 @@
</span>
</div>
<div style="height:40px;">
<span class="drop-container" style="width: 168px;">
<span class="drop-label">Mjesto</span><span class="drop-arrow"></span>
<select name="" id="hercegovackoneretvanski" class="drop-select" onchange="stavi_grad()">
<select
name=""
id="hercegovackoneretvanski"
class="drop-select"
onchange="stavi_grad()"
>
<option value="0">Mjesto</option>
<option value="3017">Grad Mostar</option>
<option value="1930">Jablanica</option>
@@ -166,12 +192,15 @@
</span>
</div>
<div style="height:40px;">
<span class="drop-container" style="width: 168px;">
<span class="drop-label">Mjesto</span><span class="drop-arrow"></span>
<select name="" id="zapadnohercegovacki" class="drop-select" onchange="stavi_grad()">
<select
name=""
id="zapadnohercegovacki"
class="drop-select"
onchange="stavi_grad()"
>
<option value="0">Mjesto</option>
<option value="1892">Grude</option>
<option value="2905">Ljubuški</option>
@@ -181,12 +210,15 @@
</span>
</div>
<div style="height:40px;">
<span class="drop-container" style="width: 168px;">
<span class="drop-label">Mjesto</span><span class="drop-arrow"></span>
<select name="" id="sarajevo" class="drop-select" onchange="stavi_grad()">
<select
name=""
id="sarajevo"
class="drop-select"
onchange="stavi_grad()"
>
<option value="0">Mjesto</option>
<option value="3817">Hadžići</option>
<option value="3879">Ilidža</option>
@@ -201,12 +233,15 @@
</span>
</div>
<div style="height:40px;">
<span class="drop-container" style="width: 168px;">
<span class="drop-label">Mjesto</span><span class="drop-arrow"></span>
<select name="" id="livanjski" class="drop-select" onchange="stavi_grad()">
<select
name=""
id="livanjski"
class="drop-select"
onchange="stavi_grad()"
>
<option value="0">Mjesto</option>
<option value="560">Bosansko Grahovo</option>
<option value="4640">Drvar</option>
@@ -218,9 +253,7 @@
</span>
</div>
<div style="height:40px;">
<span class="drop-container" style="width: 168px;">
<span class="drop-label">Mjesto</span><span class="drop-arrow"></span>
<select name="" id="grad11" class="drop-select" onchange="stavi_grad()">
@@ -230,12 +263,15 @@
</span>
</div>
<div style="height:40px;">
<span class="drop-container" style="width: 168px;">
<span class="drop-label">Mjesto</span><span class="drop-arrow"></span>
<select name="" id="banjalučka" class="drop-select" onchange="stavi_grad()">
<select
name=""
id="banjalučka"
class="drop-select"
onchange="stavi_grad()"
>
<option value="0">Mjesto</option>
<option value="21">Banja Luka</option>
<option value="305">Gradiška</option>
@@ -262,12 +298,15 @@
</span>
</div>
<div style="height:40px;">
<span class="drop-container" style="width: 168px;">
<span class="drop-label">Mjesto</span><span class="drop-arrow"></span>
<select name="" id="dobojskobijeljinska" class="drop-select" onchange="stavi_grad()">
<select
name=""
id="dobojskobijeljinska"
class="drop-select"
onchange="stavi_grad()"
>
<option value="0">Mjesto</option>
<option value="123">Bijeljina</option>
<option value="421">Bosanski Brod</option>
@@ -291,12 +330,15 @@
</span>
</div>
<div style="height:40px;">
<span class="drop-container" style="width: 168px;">
<span class="drop-label">Mjesto</span><span class="drop-arrow"></span>
<select name="" id="sarajevskozvornicka" class="drop-select" onchange="stavi_grad()">
<select
name=""
id="sarajevskozvornicka"
class="drop-select"
onchange="stavi_grad()"
>
<option value="0">Mjesto</option>
<option value="595">Bratunac</option>
<option value="1904">Han Pijesak</option>
@@ -325,12 +367,15 @@
</span>
</div>
<div style="height:40px;">
<span class="drop-container" style="width: 168px;">
<span class="drop-label">Mjesto</span><span class="drop-arrow"></span>
<select name="" id="trebinjskofocanska" class="drop-select" onchange="stavi_grad()">
<select
name=""
id="trebinjskofocanska"
class="drop-select"
onchange="stavi_grad()"
>
<option value="0">Mjesto</option>
<option value="4441">Berkovići</option>
<option value="183">Bileća</option>
@@ -347,43 +392,39 @@
</div>
<script>
const stavi_grad = () => {};
const gradovi = [
{"ime":" Sarajevo","id":"sarajevo"},
{"ime":" Unsko-sanski","id":"unskosanski"},
{"ime":" Posavski","id":"posavski"},
{"ime":" Tuzlanski","id":"tuzlanski"},
{"ime":" Zeničko-dobojski","id":"zenickodobojski"},
{"ime":" Bosansko-podrinjski","id":"bosanskopodrinjski"},
{"ime":" Srednjobosanski","id":"srednjobosanski"},
{"ime":" Hercegovačko-neretvanski","id":"hercegovackoneretvanski"},
{"ime":" Zapadno-hercegovački","id":"zapadnohercegovacki"},
{"ime":" Livanjski","id":"livanjski"},
{"ime":" Banjalučka","id":"banjalučka"},
{"ime":" Dobojsko-Bijeljinska","id":"dobojskobijeljinska"},
{"ime":" Sarajevsko-Zvornička","id":"sarajevskozvornicka"},
{"ime":" Trebinjsko-Fočanska","id":"trebinjskofocanska"},
{"ime":"Distrikt Brčko","id":"distriktbrcko"},
{ ime: " Sarajevo", id: "sarajevo" },
{ ime: " Unsko-sanski", id: "unskosanski" },
{ ime: " Posavski", id: "posavski" },
{ ime: " Tuzlanski", id: "tuzlanski" },
{ ime: " Zeničko-dobojski", id: "zenickodobojski" },
{ ime: " Bosansko-podrinjski", id: "bosanskopodrinjski" },
{ ime: " Srednjobosanski", id: "srednjobosanski" },
{ ime: " Hercegovačko-neretvanski", id: "hercegovackoneretvanski" },
{ ime: " Zapadno-hercegovački", id: "zapadnohercegovacki" },
{ ime: " Livanjski", id: "livanjski" },
{ ime: " Banjalučka", id: "banjalučka" },
{ ime: " Dobojsko-Bijeljinska", id: "dobojskobijeljinska" },
{ ime: " Sarajevsko-Zvornička", id: "sarajevskozvornicka" },
{ ime: " Trebinjsko-Fočanska", id: "trebinjskofocanska" },
{ ime: "Distrikt Brčko", id: "distriktbrcko" }
];
for (let grad of gradovi) {
const mjesta = [];
$('#' + grad.id).children().each( function() {
$("#" + grad.id)
.children()
.each(function() {
const mjesto = $(this).text();
mjesta.push({
ime: mjesto,
id: mjesto.toLowerCase().replace(/[^a-z]/g,''),
id: mjesto.toLowerCase().replace(/[^a-z]/g, ""),
olxid: $(this).val()
});
});
grad.mjesta = mjesta;
}
console.log(JSON.stringify(gradovi));
</script>
</body>
</html>