Compare commits

...

62 Commits

Author SHA1 Message Date
Nedim Uka
6fbacb326f Fixed bounding box bug, and removed unecesary params 2019-05-27 09:18:54 +02:00
Nedim Uka
be416ffc0c Added google maps step 2019-05-24 16:16:47 +02:00
Nedim Uka
a3d9a82fee Merge branch 'fix-minor-bugs' into 'master'
Fix minor bugs

See merge request saburly/marketalarm/web!9
2019-05-24 13:54:40 +00:00
Nedim Uka
6772f8a953 Merge branch 'enable-return-to-query-review-directly' into 'master'
Enable return to query review directly

See merge request saburly/marketalarm/web!8
2019-05-24 13:53:27 +00:00
Nedim Uka
89a3c9e355 Merge branch 'change-migrations-use-string-instead-of-enums' into 'master'
Change migrations - use string instead of enum

See merge request saburly/marketalarm/web!7
2019-05-24 13:50:55 +00:00
Bilal Catic
dd38602c5a add simple email validation 2019-05-22 16:57:08 +02:00
Bilal Catic
a3f76d20fe fix URL on send icon 2019-05-22 15:58:42 +02:00
Bilal Catic
fc1275566e handle undefined realEstateType 2019-05-22 13:19:27 +02:00
Bilal Catic
c64ee42914 Skip and prevent saving garden size if not needed 2019-05-22 11:36:01 +02:00
Bilal Catic
aa3c965d5c skip to query review directly when editing data 2019-05-21 15:32:47 +02:00
Bilal Catic
126da48852 modify realEstateRequest model to use String instead of enum 2019-05-21 15:26:53 +02:00
Bilal Catic
58ae430564 modify migrations - use string instead of enum 2019-05-21 15:26:53 +02:00
Senad Uka
315a29749c Update database configuration 2019-05-21 12:11:30 +02:00
Nedim Uka
02bee9cf2c Merge branch 'add-steps-to-wizard' into 'master'
Add steps to wizard

See merge request saburly/marketalarm/web!6
2019-05-21 09:39:43 +00:00
Bilal Catic
1c2847509a add final page 2019-05-19 19:45:19 +02:00
Bilal Catic
87dc742e41 add query submit page 2019-05-19 13:34:44 +02:00
Bilal Catic
70ddc1f734 add query review page 2019-05-19 12:29:55 +02:00
Bilal Catic
2c415bbd79 use enums from enum file 2019-05-19 10:03:52 +02:00
Bilal Catic
b07eb5bbeb fix available value input for size 2019-05-19 10:03:36 +02:00
Bilal Catic
53585d3ae1 use enums from enum file 2019-05-19 02:14:20 +02:00
Bilal Catic
c652a306db add price screen 2019-05-17 11:32:41 +02:00
Bilal Catic
b15295bfe6 add garden size screen 2019-05-17 11:12:24 +02:00
Bilal Catic
7ad1117cae add size screen 2019-05-17 11:06:32 +02:00
Nedim Uka
393f6731e6 Merge branch 'refactor' into 'master'
Refactor

See merge request saburly/marketalarm/web!5
2019-05-17 07:46:44 +00:00
Bilal Catic
93faa7c9e3 update readme 2019-05-17 09:14:16 +02:00
Bilal Catic
93f5d8071e add npm commands for docker and setup 2019-05-17 09:10:03 +02:00
Bilal Catic
7192c28c07 update readme 2019-05-17 08:55:36 +02:00
Bilal Catic
76f9457d4f add nodemon and migrate scripts 2019-05-17 08:49:01 +02:00
Bilal Catic
68172951ed change region and municipality property names to english 2019-05-17 00:52:43 +02:00
Bilal Catic
dbf40b199e Merge branch 'refactor' of https://gitlab.com/saburly/marketalarm/web into refactor 2019-05-17 00:34:13 +02:00
Bilal Catic
4309bc709d change column name from 'city' to 'region' 2019-05-17 00:33:10 +02:00
Bilal Catic
4323017d02 fix Readme 2019-05-16 21:34:32 +00:00
Bilal Catic
42505a7089 change column name from 'place' to 'municipality' 2019-05-16 23:32:18 +02:00
Bilal Catic
1542310a81 clean code 2019-05-16 19:58:48 +02:00
Bilal Catic
c505062770 change controller file name to plural 2019-05-16 19:42:15 +02:00
Bilal Catic
ab681e5eeb change file names to CamelCase 2019-05-16 19:40:26 +02:00
Bilal Catic
616eddbb19 improve Readme 2019-05-16 17:12:17 +02:00
Bilal Catic
e5eb6b99a2 Merge branch 'rename' into 'master'
Refactoring

See merge request saburly/marketalarm/web!4
2019-05-16 12:49:52 +00:00
Nedim Uka
27fa721627 Renaming to english 2019-05-16 13:00:08 +02:00
Senad Uka
9fdfce49ed Merge branch 'dockerize-database' into 'master'
Dockerize database

See merge request saburly/marketalarm/web!2
2019-04-30 12:07:09 +00:00
MirnaM
59723410b6 Update README 2019-04-30 09:47:50 +02:00
MirnaM
51ed3551c7 Use default postgres port 2019-04-30 09:19:52 +02:00
MirnaM
58177a8cce Add dockerfile 2019-04-30 09:06:46 +02:00
Senad Uka
864b917b4f Make place selection possible 2019-04-30 06:48:41 +02:00
Senad Uka
a2f6f033bf Places almost finished 2019-04-28 11:13:46 +02:00
Senad Uka
64f2cb82a8 Scrape kantoni into html file 2019-04-28 09:02:46 +02:00
Senad Uka
17492eb52c City is now saved 2019-04-27 07:08:36 +02:00
Senad Uka
298c901759 Added migrations and saving real estate type correctly 2019-04-20 05:26:14 +02:00
Senad Uka
c534c1ee34 Added a new model - does not work yet 2019-04-16 06:27:11 +02:00
Senad Uka
2380c85122 Now posting the type of real estate 2019-04-15 06:56:03 +02:00
Senad Uka
0f7e9f9285 Type of real estate 2019-04-14 06:01:37 +02:00
Senad Uka
dee4df9bd8 Added compression 2019-04-13 10:38:25 +02:00
Senad Uka
4248e6304a Poruka 2019-04-13 10:27:35 +02:00
Senad Uka
9fd9fe8b82 Fixed css 2019-04-12 06:47:51 +02:00
Senad Uka
467d551857 Logo set up 2019-04-12 05:40:52 +02:00
Senad Uka
28f95b9c05 Logo and button - logo unfinished 2019-04-11 05:27:55 +02:00
Senad Uka
9aba66c273 Logo and button - logo unfinished 2019-04-11 05:27:34 +02:00
Senad Uka
d03e85a0dc Switched to materializecss + jquery 2019-04-10 05:17:39 +02:00
Senad Uka
9d49e72bb4 Fix mixed content 2019-04-10 05:01:19 +02:00
Senad Uka
efe2dd66a3 Heroku fix fix 2019-04-09 06:11:41 +02:00
Senad Uka
5f2fee504a Heroku postbuild fix 2019-04-09 06:08:22 +02:00
Senad Uka
add905c793 Saved what was unsaved 2019-04-09 06:04:17 +02:00
59 changed files with 5700 additions and 140 deletions

11
Dockerfile Normal file
View File

@@ -0,0 +1,11 @@
FROM postgres:11.3
ENV POSTGIS_MAJOR 2.4
RUN apt-get update \
&& apt-get --assume-yes install software-properties-common postgis\
&& rm -rf /var/lib/apt/lists/
RUN mkdir -p /docker-entrypoint-initdb.d
CMD ["postgres"]

View File

@@ -1,12 +1,36 @@
# web
# MarketAlert
The purpose of this project is to build a web application that enables subscribing to notifications when new products are published on various ad based marketplaces. The MVP will be only based on OLX.ba
## Setup
ENV:
JAWSDB_URL='mysql://sq4dlf9mz49avli0:gqy5vzmzyhp0837x@tuy8t6uuvh43khkk.cbetxkdyhwsb.us-east-1.rds.amazonaws.com:3306/rxhzg1550441ftqk'
### Setup with npm commands
Run with:
$ npm start
1. Install packages
`npm install`
2. Run setup script
`npm run setup`
this will create and run postgres image and then execute migrations
3. Run app
`npm start` to run app without restart on changes or
`npm run start-mon` to run app with automatic restart on code change
### Manual setup
1. Create postgres docker image
`docker build -t marketalerts .`
2. Run postgres image with
`docker run --name pg_marketalerts -d -p 5432:5432 marketalerts`
3. Install packages
`npm install`
4. Run migrations from `app` folder
`npm run migrate` or `npx sequelize db:migrate`
5. Run app
`npm start` or `npm run start-mon` to run app with automatic restart on code change

15
app/config/config.json Normal file
View File

@@ -0,0 +1,15 @@
{
"development": {
"username": "docker",
"password": "docker",
"database": "marketalerts",
"port": "5432",
"dialect": "postgres"
},
"test": {
"use_env_variable": "DATABASE_URL"
},
"production": {
"use_env_variable": "DATABASE_URL"
}
}

View File

@@ -1,7 +0,0 @@
const getDobrodosli = (req,res) => {
res.render('dobrodosli', { nextStep: '/vrstanekretnine' } );
}
module.exports = {
getDobrodosli
};

View File

@@ -0,0 +1,26 @@
const { currentRERequest } = require('../helpers/url');
const { gardenSizes, getRealEstateTypeEnum } = require('../helpers/enums');
const getGardenSize = (req,res) => {
res.render('gardenSize', { gardenSizes });
};
const postGardenSize = async (req, res) => {
const request = await currentRERequest(req);
const nextStepPage = req.query.nextStep || 'cijena';
const nextStepUrl = `/${nextStepPage}/${request.uniqueId}`;
const realEstateType = getRealEstateTypeEnum(request.realEstateType);
if (realEstateType && realEstateType.hasGardenSize) {
request.gardenSize = req.body.gardensize;
await request.save();
}
res.redirect(nextStepUrl);
};
module.exports = {
getGardenSize,
postGardenSize
};

View File

@@ -0,0 +1,7 @@
const getGoAgain = async (req,res) => {
res.render('goAgain');
};
module.exports = {
getGoAgain
};

View File

@@ -0,0 +1,25 @@
const { currentRERequest } = require('../helpers/url');
const { getMunicipalitiesForRegion, getMunicipalityName } = require('../helpers/codes');
const getMunicipality = async (req, res) => {
let request = await currentRERequest(req);
const municipalities = getMunicipalitiesForRegion(request.region);
res.render('municipality', { municipalities });
};
const postMunicipality = async (req, res) => {
const request = await currentRERequest(req);
const nextStepPage = req.query.nextStep || 'naselje';
const nextStepUrl = `/${nextStepPage}/${request.uniqueId}/${getMunicipalityName(request.region, req.body.municipality)}`;
request.municipality = req.body.municipality;
await request.save();
res.redirect(nextStepUrl);
};
module.exports = {
getMunicipality,
postMunicipality
};

View File

@@ -0,0 +1,35 @@
const { currentRERequest } = require('../helpers/url');
const getNeighborhood = async (req, res) => {
const municipality = req.params.municipality
const nextStep = req.query.nextStep || '/';
res.render('neighborhoodMap', {
nextStep,
municipality
});
};
const postNeighborhood = async (req, res) => {
let request = await currentRERequest(req);
const northWest = [req.body.west, req.body.north];
const northEast = [req.body.east, req.body.north];
const southEast = [req.body.east, req.body.south];
const southWest = [req.body.west, req.body.south];
request.bounding_box = {
type: 'Polygon', coordinates: [
[northWest, northEast, southEast,
southWest, northWest]
]
};
await request.save();
const nextStep = req.query.nextStep || `/povrsina/${request.uniqueId}`;
res.redirect(nextStep);
};
module.exports = {
getNeighborhood,
postNeighborhood
};

23
app/controllers/prices.js Normal file
View File

@@ -0,0 +1,23 @@
const { currentRERequest } = require('../helpers/url');
const { prices } = require('../helpers/enums');
const getPrice = (req,res) => {
res.render('price', { prices });
};
const postPrice = async (req, res) => {
const request = await currentRERequest(req);
const nextStepPage = req.query.nextStep || 'pregled';
const nextStepUrl = `/${nextStepPage}/${request.uniqueId}`;
request.price = req.body.price;
await request.save();
res.redirect(nextStepUrl);
};
module.exports = {
getPrice,
postPrice
};

View File

@@ -0,0 +1,76 @@
const { currentRERequest } = require('../helpers/url');
const { getRegionName, getMunicipalityName } = require('../helpers/codes');
const { realEstateTypes, sizes, gardenSizes, prices, getEnumTypeTitle, getRealEstateTypeEnum } = require('../helpers/enums');
const getQueryReview = async (req,res) => {
const request = await currentRERequest(req);
const nextStep = req.query.nextStep;
if (!request || !request.dataValues) {
return null;
}
const { realEstateType, region, municipality, size, gardenSize, price } = request.dataValues;
const realEstateTypeObject = getRealEstateTypeEnum(realEstateType);
const enableGardenSizeEdit = realEstateTypeObject ? realEstateTypeObject.hasGardenSize : false;
const realEstateTypeTitle = realEstateType ? getEnumTypeTitle(realEstateTypes, realEstateType) : null;
const regionName = region ? getRegionName(region) : null;
const municipalityName = (region && municipality) ? getMunicipalityName(region, municipality) : null;
const sizeTitle = size ? getEnumTypeTitle(sizes, size) : null;
const gardenSizeTitle = gardenSize ? getEnumTypeTitle(gardenSizes, gardenSize) : null;
const priceTitle = price ? getEnumTypeTitle(prices, price) : null;
const uniqueId = request.dataValues.uniqueId ? request.dataValues.uniqueId : '';
const queryData = [
{
id: 'realEstateType',
title: realEstateTypeTitle,
url: `/vrstanekretnine/${uniqueId}?nextStep=pregled`,
},
{
id: 'region',
title: regionName,
url: `/grad/${uniqueId}?nextStep=mjesto`,
},
{
id: 'municipality',
title: municipalityName,
url: `/mjesto/${uniqueId}?nextStep=pregled`,
},
{
id: 'size',
title: sizeTitle,
url: `/povrsina/${uniqueId}?nextStep=pregled`,
},
{
id: 'gardenSize',
title: gardenSizeTitle,
url: enableGardenSizeEdit ? `/okucnica/${uniqueId}?nextStep=pregled` : '',
},
{
id: 'price',
title: priceTitle,
url: `/cijena/${uniqueId}?nextStep=pregled`
}
];
res.render('queryReview', {
nextStep,
queryData,
});
};
const postQueryReview = async (req, res) => {
const request = await currentRERequest(req);
const nextStep = req.query.nextStep || `/posalji/${request.uniqueId}`;
res.redirect(nextStep);
};
module.exports = {
getQueryReview,
postQueryReview
};

View File

@@ -0,0 +1,32 @@
const { currentRERequest } = require('../helpers/url');
const { isValidEmail } = require('../helpers/email');
const getQuerySubmit = async (req,res) => {
const nextStep = req.query.nextStep;
const error = req.query.error;
res.render('querySubmit', {
nextStep,
error
});
};
const postQuerySubmit = async (req, res) => {
const request = await currentRERequest(req);
const nextStep = req.query.nextStep || '/ponovo';
const emailInput = req.body.email;
if (isValidEmail(emailInput)){
request.email = req.body.email;
await request.save();
res.redirect(nextStep);
} else {
res.redirect('?error=1');
}
};
module.exports = {
getQuerySubmit,
postQuerySubmit
};

View File

@@ -0,0 +1,44 @@
const db = require('../models/index');
const { currentRERequest } = require('../helpers/url');
const { realEstateTypes, getRealEstateTypeEnum } = require('../helpers/enums');
const getRealEstateTypes = (req,res) => {
res.render('realEstateType', { realEstateTypes });
};
const postRealEstateTypes = async (req, res) => {
const request = await currentRERequest(req);
const nextStepPage = req.query.nextStep || 'grad';
if (request && request.uniqueId) {
const nextStepUrl = `/${nextStepPage}/${request.uniqueId}`;
request.realEstateType = req.body.realestatetype;
if (!getRealEstateTypeEnum(request.realEstateType).hasGardenSize){
request.gardenSize = null;
}
await request.save();
res.redirect(nextStepUrl)
} else {
db.RealEstateRequest.create({
realEstateType: req.body.realestatetype
}).then( (result) => {
const nextStepUrl = `/${nextStepPage}/${result.uniqueId}`;
res.redirect(nextStepUrl);
}).catch( (e) => {
res.send(e);
});
}
};
module.exports = {
getRealEstateTypes,
postRealEstateTypes
};

View File

@@ -0,0 +1,27 @@
const { currentRERequest } = require('../helpers/url');
const { getRegions } = require('../helpers/codes');
const regions = getRegions();
const getRegion = (req,res) => {
res.render('region', { regions });
};
const postRegion = async (req, res) => {
const request = await currentRERequest(req);
const nextStepQueryParam = req.query.nextStep ? '?nextStep=pregled' : '';
const nextStepPage = req.query.nextStep || 'mjesto';
const nextStepUrl = `/${nextStepPage}/${request.uniqueId}${nextStepQueryParam}`;
request.region = req.body.region;
request.municipality = null;
await request.save();
res.redirect(nextStepUrl)
};
module.exports = {
getRegion,
postRegion
};

26
app/controllers/sizes.js Normal file
View File

@@ -0,0 +1,26 @@
const { currentRERequest } = require('../helpers/url');
const { sizes, getRealEstateTypeEnum } = require('../helpers/enums');
const getSize = (req,res) => {
res.render('size', { sizes });
};
const postSize = async (req, res) => {
const request = await currentRERequest(req);
const realEstateType = getRealEstateTypeEnum(request.realEstateType);
const nextStep = realEstateType && realEstateType.hasGardenSize ? 'okucnica' : 'cijena';
const nextStepPage = req.query.nextStep || nextStep;
const nextStepUrl = `/${nextStepPage}/${request.uniqueId}`;
request.size = req.body.size;
await request.save();
res.redirect(nextStepUrl);
};
module.exports = {
getSize,
postSize
};

View File

@@ -1,7 +0,0 @@
const getVrstaNekretnine = (req,res) => {
res.render('vrsta_nekretnine', { nextStep: '/' } );
}
module.exports = {
getVrstaNekretnine
};

View File

@@ -0,0 +1,7 @@
const getWelcome = (req,res) => {
res.render('welcome', { nextStep: '/vrstanekretnine' } );
};
module.exports = {
getWelcome
};

View File

@@ -1,4 +0,0 @@
const Sequelize = require("sequelize");
const sequelize = new Sequelize(process.env.JAWSDB_URL);
module.exports = sequelize;

912
app/helpers/codes.js Normal file
View File

@@ -0,0 +1,912 @@
const regions = [
{
"name":" Sarajevo",
"id":"sarajevo",
"olxid": "9",
"municipalities":[
{
"name":"Hadžići",
"id":"hadii",
"olxid":"3817"
},
{
"name":"Ilidža",
"id":"ilida",
"olxid":"3879"
},
{
"name":"Ilijaš",
"id":"ilija",
"olxid":"3892"
},
{
"name":"Sarajevo - Centar",
"id":"sarajevocentar",
"olxid":"3812"
},
{
"name":"Sarajevo-Novi Grad",
"id":"sarajevonovigrad",
"olxid":"3969"
},
{
"name":"Sarajevo-Novo Sarajevo",
"id":"sarajevonovosarajevo",
"olxid":"5896"
},
{
"name":"Sarajevo-Stari Grad",
"id":"sarajevostarigrad",
"olxid":"4048"
},
{
"name":"Trnovo",
"id":"trnovo",
"olxid":"4063"
},
{
"name":"Vogošća",
"id":"vogoa",
"olxid":"4126"
}
]
},
{
"name":" Unsko-sanski",
"id":"unskosanski",
"olxid": "9",
"municipalities":[
{
"name":"Bihać",
"id":"biha",
"olxid":"75"
},
{
"name":"Bosanska Krupa",
"id":"bosanskakrupa",
"olxid":"373"
},
{
"name":"Bosanski Petrovac",
"id":"bosanskipetrovac",
"olxid":"504"
},
{
"name":"Bužim",
"id":"buim",
"olxid":"374"
},
{
"name":"Cazin",
"id":"cazin",
"olxid":"857"
},
{
"name":"Ključ",
"id":"klju",
"olxid":"2362"
},
{
"name":"Sanski Most",
"id":"sanskimost",
"olxid":"3738"
},
{
"name":"Velika Kladuša",
"id":"velikakladua",
"olxid":"5122"
}
]
},
{
"name":" Posavski",
"id":"posavski",
"olxid": "15",
"municipalities":[
{
"name":"Domaljevac",
"id":"domaljevac",
"olxid":"6144"
},
{
"name":"Odžak",
"id":"odak",
"olxid":"424"
},
{
"name":"Orašje",
"id":"oraje",
"olxid":"3252"
},
{
"name":"Šamac",
"id":"amac",
"olxid":"540"
}
]
},
{
"name":" Tuzlanski",
"id":"tuzlanski",
"olxid": "15",
"municipalities":[
{
"name":"Banovići",
"id":"banovii",
"olxid":"2"
},
{
"name":"Doboj-Istok",
"id":"dobojistok",
"olxid":"1090"
},
{
"name":"Gradačac",
"id":"gradaac",
"olxid":"1854"
},
{
"name":"Gračanica",
"id":"graanica",
"olxid":"1826"
},
{
"name":"Kalesija",
"id":"kalesija",
"olxid":"2129"
},
{
"name":"Kladanj",
"id":"kladanj",
"olxid":"2319"
},
{
"name":"Lukavac",
"id":"lukavac",
"olxid":"2840"
},
{
"name":"Sapna",
"id":"sapna",
"olxid":"5699"
},
{
"name":"Srebrenik",
"id":"srebrenik",
"olxid":"4391"
},
{
"name":"Teočak",
"id":"teoak",
"olxid":"5010"
},
{
"name":"Tuzla",
"id":"tuzla",
"olxid":"4944"
},
{
"name":"Čelić",
"id":"eli",
"olxid":"2801"
},
{
"name":"Živinice",
"id":"ivinice",
"olxid":"5774"
}
]
},
{
"name":" Zeničko-dobojski",
"id":"zenickodobojski",
"olxid": "15",
"municipalities":[
{
"name":"Breza",
"id":"breza",
"olxid":"704"
},
{
"name":"Doboj-Jug",
"id":"dobojjug",
"olxid":"1122"
},
{
"name":"Kakanj",
"id":"kakanj",
"olxid":"2022"
},
{
"name":"Maglaj",
"id":"maglaj",
"olxid":"2941"
},
{
"name":"Olovo",
"id":"olovo",
"olxid":"1925"
},
{
"name":"Tešanj",
"id":"teanj",
"olxid":"4594"
},
{
"name":"Usora",
"id":"usora",
"olxid":"1087"
},
{
"name":"Vareš",
"id":"vare",
"olxid":"5037"
},
{
"name":"Visoko",
"id":"visoko",
"olxid":"5171"
},
{
"name":"Zavidovići",
"id":"zavidovii",
"olxid":"5548"
},
{
"name":"Zenica",
"id":"zenica",
"olxid":"4571"
},
{
"name":"Žepče",
"id":"epe",
"olxid":"2940"
}
]
},
{
"name":" Bosansko-podrinjski",
"id":"bosanskopodrinjski",
"olxid": "15",
"municipalities":[
{
"name":"Foča",
"id":"foa",
"olxid":"1289"
},
{
"name":"Goražde",
"id":"gorade",
"olxid":"1588"
},
{
"name":"Pale",
"id":"pale",
"olxid":"3546"
}
]
},
{
"name":" Srednjobosanski",
"id":"srednjobosanski",
"olxid": "6",
"municipalities":[
{
"name":"Bugojno",
"id":"bugojno",
"olxid":"732"
},
{
"name":"Busovača",
"id":"busovaa",
"olxid":"810"
},
{
"name":"Dobretići",
"id":"dobretii",
"olxid":"4151"
},
{
"name":"Donji Vakuf",
"id":"donjivakuf",
"olxid":"1160"
},
{
"name":"Fojnica",
"id":"fojnica",
"olxid":"1407"
},
{
"name":"Gornji Vakuf - Uskoplje",
"id":"gornjivakufuskoplje",
"olxid":"1775"
},
{
"name":"Jajce",
"id":"jajce",
"olxid":"1960"
},
{
"name":"Kiseljak",
"id":"kiseljak",
"olxid":"2237"
},
{
"name":"Kreševo",
"id":"kreevo",
"olxid":"2608"
},
{
"name":"Novi Travnik",
"id":"novitravnik",
"olxid":"3477"
},
{
"name":"Travnik",
"id":"travnik",
"olxid":"4678"
},
{
"name":"Vitez",
"id":"vitez",
"olxid":"5422"
}
]
},
{
"name":" Hercegovačko-neretvanski",
"id":"hercegovackoneretvanski",
"olxid": "7",
"municipalities":[
{
"name":"Grad Mostar",
"id":"gradmostar",
"olxid":"3017"
},
{
"name":"Jablanica",
"id":"jablanica",
"olxid":"1930"
},
{
"name":"Konjic",
"id":"konjic",
"olxid":"2169"
},
{
"name":"Neum",
"id":"neum",
"olxid":"3111"
},
{
"name":"Prozor",
"id":"prozor",
"olxid":"3421"
},
{
"name":"Ravno",
"id":"ravno",
"olxid":"4769"
},
{
"name":"Stolac",
"id":"stolac",
"olxid":"4439"
},
{
"name":"Čapljina",
"id":"apljina",
"olxid":"947"
},
{
"name":"Čitluk",
"id":"itluk",
"olxid":"1009"
}
]
},
{
"name":" Zapadno-hercegovački",
"id":"zapadnohercegovacki",
"olxid": "8",
"municipalities":[
{
"name":"Grude",
"id":"grude",
"olxid":"1892"
},
{
"name":"Ljubuški",
"id":"ljubuki",
"olxid":"2905"
},
{
"name":"Posušje",
"id":"posuje",
"olxid":"3268"
},
{
"name":"Široki Brijeg",
"id":"irokibrijeg",
"olxid":"2708"
}
]
},
{
"name":" Livanjski",
"id":"livanjski",
"olxid": "10",
"municipalities":[
{
"name":"Bosansko Grahovo",
"id":"bosanskograhovo",
"olxid":"560"
},
{
"name":"Drvar",
"id":"drvar",
"olxid":"4640"
},
{
"name":"Glamoč",
"id":"glamo",
"olxid":"1533"
},
{
"name":"Kupres",
"id":"kupres",
"olxid":"2635"
},
{
"name":"Livno",
"id":"livno",
"olxid":"2741"
},
{
"name":"Tomislavgrad",
"id":"tomislavgrad",
"olxid":"1228"
}
]
},
{
"name":" Banjalučka",
"id":"banjalučka",
"olxid": "14",
"municipalities":[
{
"name":"Banja Luka",
"id":"banjaluka",
"olxid":"21"
},
{
"name":"Gradiška",
"id":"gradika",
"olxid":"305"
},
{
"name":"Istočni Drvar",
"id":"istonidrvar",
"olxid":"4662"
},
{
"name":"Jezero",
"id":"jezero",
"olxid":"1965"
},
{
"name":"Kneževo",
"id":"kneevo",
"olxid":"4147"
},
{
"name":"Kostajnica",
"id":"kostajnica",
"olxid":"6142"
},
{
"name":"Kotor Varoš",
"id":"kotorvaro",
"olxid":"2574"
},
{
"name":"Kozarska Dubica",
"id":"kozarskadubica",
"olxid":"244"
},
{
"name":"Krupa na uni",
"id":"krupanauni",
"olxid":"382"
},
{
"name":"Kupres ",
"id":"kupres",
"olxid":"2654"
},
{
"name":"Laktaši",
"id":"laktai",
"olxid":"2671"
},
{
"name":"Mrkonjić Grad",
"id":"mrkonjigrad",
"olxid":"3073"
},
{
"name":"Novi Grad",
"id":"novigrad",
"olxid":"444"
},
{
"name":"Oštra Luka",
"id":"otraluka",
"olxid":"3737"
},
{
"name":"Petrovac",
"id":"petrovac",
"olxid":"515"
},
{
"name":"Prijedor",
"id":"prijedor",
"olxid":"3287"
},
{
"name":"Prnjavor",
"id":"prnjavor",
"olxid":"3358"
},
{
"name":"Ribnik",
"id":"ribnik",
"olxid":"2365"
},
{
"name":"Srbac",
"id":"srbac",
"olxid":"4271"
},
{
"name":"Čelinac",
"id":"elinac",
"olxid":"979"
},
{
"name":"Šipovo",
"id":"ipovo",
"olxid":"4509"
}
]
},
{
"name":" Dobojsko-Bijeljinska",
"id":"dobojskobijeljinska",
"olxid": "15",
"municipalities":[
{
"name":"Bijeljina",
"id":"bijeljina",
"olxid":"123"
},
{
"name":"Bosanski Brod",
"id":"bosanskibrod",
"olxid":"421"
},
{
"name":"Derventa",
"id":"derventa",
"olxid":"1030"
},
{
"name":"Doboj",
"id":"doboj",
"olxid":"1088"
},
{
"name":"Donji Žabar",
"id":"donjiabar",
"olxid":"3254"
},
{
"name":"Lopare",
"id":"lopare",
"olxid":"2800"
},
{
"name":"Lukavac",
"id":"lukavac",
"olxid":"6029"
},
{
"name":"Modriča",
"id":"modria",
"olxid":"2996"
},
{
"name":"Pelagićevo",
"id":"pelagievo",
"olxid":"1856"
},
{
"name":"Petrovo",
"id":"petrovo",
"olxid":"1827"
},
{
"name":"Stanari",
"id":"stanari",
"olxid":"1148"
},
{
"name":"Teslić",
"id":"tesli",
"olxid":"4549"
},
{
"name":"Tešanj",
"id":"teanj",
"olxid":"4636"
},
{
"name":"Travnik",
"id":"travnik",
"olxid":"4692"
},
{
"name":"Tuzla",
"id":"tuzla",
"olxid":"4966"
},
{
"name":"Ugljevik",
"id":"ugljevik",
"olxid":"5009"
},
{
"name":"Vukosavlje",
"id":"vukosavlje",
"olxid":"3197"
},
{
"name":"Šamac",
"id":"amac",
"olxid":"539"
}
]
},
{
"name":" Sarajevsko-Zvornička",
"id":"sarajevskozvornicka",
"olxid": "16",
"municipalities":[
{
"name":"Bratunac",
"id":"bratunac",
"olxid":"595"
},
{
"name":"Han Pijesak",
"id":"hanpijesak",
"olxid":"1904"
},
{
"name":"Ilijaš",
"id":"ilija",
"olxid":"3947"
},
{
"name":"Istočni Stari Grad",
"id":"istonistarigrad",
"olxid":"4049"
},
{
"name":"Kasindo",
"id":"kasindo",
"olxid":"3880"
},
{
"name":"Kladanj",
"id":"kladanj",
"olxid":"2325"
},
{
"name":"Lukavica",
"id":"lukavica",
"olxid":"3971"
},
{
"name":"Milići",
"id":"milii",
"olxid":"6143"
},
{
"name":"Olovo",
"id":"olovo",
"olxid":"3221"
},
{
"name":"Osmaci",
"id":"osmaci",
"olxid":"2128"
},
{
"name":"Pale",
"id":"pale",
"olxid":"3978"
},
{
"name":"Rogatica",
"id":"rogatica",
"olxid":"3529"
},
{
"name":"Rudo",
"id":"rudo",
"olxid":"3648"
},
{
"name":"Sarajevo-Novi Grad",
"id":"sarajevonovigrad",
"olxid":"6069"
},
{
"name":"Sokolac",
"id":"sokolac",
"olxid":"4183"
},
{
"name":"Srebrenica",
"id":"srebrenica",
"olxid":"4310"
},
{
"name":"Trnovo",
"id":"trnovo",
"olxid":"4067"
},
{
"name":"Ustiprača",
"id":"ustipraa",
"olxid":"1593"
},
{
"name":"Višegrad",
"id":"viegrad",
"olxid":"5259"
},
{
"name":"Vlasenica",
"id":"vlasenica",
"olxid":"5456"
},
{
"name":"Zvornik",
"id":"zvornik",
"olxid":"5684"
},
{
"name":"Šekovići",
"id":"ekovii",
"olxid":"4475"
},
{
"name":"Žepa",
"id":"epa",
"olxid":"1906"
}
]
},
{
"name":" Trebinjsko-Fočanska",
"id":"trebinjskofocanska",
"olxid": "17",
"municipalities":[
{
"name":"Berkovići",
"id":"berkovii",
"olxid":"4441"
},
{
"name":"Bileća",
"id":"bilea",
"olxid":"183"
},
{
"name":"Foča",
"id":"foa",
"olxid":"1287"
},
{
"name":"Gacko",
"id":"gacko",
"olxid":"1462"
},
{
"name":"Istočni Mostar",
"id":"istonimostar",
"olxid":"3038"
},
{
"name":"Kalinovik",
"id":"kalinovik",
"olxid":"2164"
},
{
"name":"Ljubinje",
"id":"ljubinje",
"olxid":"2884"
},
{
"name":"Nevesinje",
"id":"nevesinje",
"olxid":"3138"
},
{
"name":"Trebinje",
"id":"trebinje",
"olxid":"4766"
},
{
"name":"Čajniče",
"id":"ajnie",
"olxid":"911"
}
]
},
{
"name":"Distrikt Brčko",
"id":"distriktbrcko",
"olxid": "12",
"municipalities":[
{
"name":"Brčko",
"id":"brko",
"olxid":"12"
}
]
}
];
const getRegions = () => {
return regions.map( (g) => ({ name: g.name, id: g.id, olxid: g.olxid }) );
};
const getRegion = (regionId) => {
return regions.find(region => region.id === regionId);
};
const getRegionName = (regionId) => {
const region = getRegion(regionId);
return (region && region.name) ? region.name : null;
};
const getMunicipalitiesForRegion = (regionId) => {
const region = getRegion(regionId);
return (region && region.municipalities) ? region.municipalities : null;
};
const getMunicipalityName = (regionId, municipalityId) => {
const region = getRegion(regionId);
if (!region){
return null;
}
const municipality = region.municipalities.find(municipality => municipality.id === municipalityId);
if (!municipality) {
return null;
}
return municipality.name;
};
module.exports = {
getRegions,
getRegionName,
getMunicipalitiesForRegion,
getMunicipalityName,
};

9
app/helpers/email.js Normal file
View File

@@ -0,0 +1,9 @@
const isValidEmail = (email) => {
const simpleEmailRegex = /^.+@.+\..+$/;
return (email && email.length < 250 && simpleEmailRegex.test(email));
};
module.exports = {
isValidEmail
};

57
app/helpers/enums.js Normal file
View File

@@ -0,0 +1,57 @@
const realEstateTypes = [
{ title: "Kuća", id: "kuca", hasGardenSize: true },
{ title: "Stan", id: "stan", hasGardenSize: false },
{ title: "Vikendica", id: "vikendica", hasGardenSize: true }
];
const sizes = [
{ title: "do 50 m2", id: "50m2" },
{ title: "do 75 m2", id: "75m2" },
{ title: "do 100 m2", id: "100m2" },
{ title: "do 150 m2", id: "150m2" },
{ title: "do 200 m2", id: "200m2" },
{ title: "preko 200 m2", id: "moreThan200m2" }
];
const gardenSizes = [
{ title: "do 100 m2", id: "100m2" },
{ title: "do 500 m2", id: "500m2" },
{ title: "do 1 dunum", id: "1000m2" },
{ title: "do 2 dunuma", id: "2000m2" },
{ title: "do 3 dunuma", id: "3000m2" },
{ title: "preko 3 dunuma", id: "moreThan3000m2" }
];
const prices = [
{ title: "do 50 000 KM", id: "50kKM" },
{ title: "do 100 000 KM", id: "100kKM" },
{ title: "do 150 000 KM", id: "150kKM" },
{ title: "do 200 000 KM", id: "200kKM" },
{ title: "do 250 000 KM", id: "250kKM" },
{ title: "preko 250 000 KM", id: "moreThan250kKM" }
];
const getEnumObject = (enumType, enumId) => {
return enumType.find(enumValue => enumValue.id === enumId);
};
const getRealEstateTypeEnum = (enumId) => {
return getEnumObject(realEstateTypes, enumId) || null;
}
const getEnumTypeTitle = (enumType, enumId) => {
const enumObject = getEnumObject(enumType, enumId);
if (!enumObject){
return null;
}
return enumObject.title;
};
module.exports = {
realEstateTypes,
sizes,
gardenSizes,
prices,
getRealEstateTypeEnum,
getEnumTypeTitle,
};

13
app/helpers/url.js Normal file
View File

@@ -0,0 +1,13 @@
const db = require('../models/index');
const currentRERequest = async (req) => {
const uniqueId = req.params['request_id'];
if(!uniqueId) return null;
const request = await db.RealEstateRequest.findOne({ where: {uniqueId} });
return request;
};
module.exports = {
currentRERequest
};

View File

@@ -1,8 +1,10 @@
const convertToDate = require("./convertToDate");
function areThereAnyNewItems(lastItemDate, controlDate) {
if (!lastItemDate) {
return true;
}
return new Date(controlDate) < convertToDate(lastItemDate);
}
module.exports = areThereAnyNewItems;

View File

@@ -1,6 +1,6 @@
let fetch = require("node-fetch");
let cheerio = require("cheerio");
const areThereAnyNewItems = require("./arethereanynewitems");
const areThereAnyNewItems = require("./areThereAnyNewItems");
async function scrapTheItems(url, controlDate, noNewItems = false) {
let items = [];

View File

@@ -1,11 +1,11 @@
const scrapTheItems = require("./scraptheitems");
const scrapTheItems = require("./scrapTheItems");
const convertToDate = require("./convertToDate");
const AWS = require('aws-sdk');
AWS.config.update({region: 'eu-central-1'});
async function sendNotification(marketAlert) {
const { id, email, olx_url, last_date } = marketAlert;
const { id, email, olx_url } = marketAlert;
let url =
"https://www.olx.ba/pretraga?" + olx_url + "&sort_order=desc&sort_po=datum";
let newItems = await scrapTheItems(url);
@@ -17,8 +17,8 @@ async function sendNotification(marketAlert) {
""
);
// Create sendEmail params
var params = {
// Create sendEmail params
const params = {
Destination: { /* required */
CcAddresses: [
],
@@ -50,7 +50,7 @@ async function sendNotification(marketAlert) {
if (message) {
const sendPromise = new AWS.SES({apiVersion: '2010-12-01'}).sendEmail(params).promise();
await sendPromise;
await sendPromise;
return { id, date: String(convertToDate(lastDate)) };
}
}

View File

@@ -0,0 +1,34 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('MarketAlerts', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
olxUrl: {
type: Sequelize.STRING
},
lastDate: {
type: Sequelize.STRING
},
email: {
type: Sequelize.STRING,
allowNull: false
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('MarketAlerts');
}
};

View File

@@ -0,0 +1,33 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.createTable('RealEstateRequests', {
id: {
allowNull: false,
autoIncrement: true,
primaryKey: true,
type: Sequelize.INTEGER
},
uniqueId: {
type: Sequelize.UUID
},
realEstateType: {
type: Sequelize.STRING
},
email: {
type: Sequelize.STRING
},
createdAt: {
allowNull: false,
type: Sequelize.DATE
},
updatedAt: {
allowNull: false,
type: Sequelize.DATE
}
});
},
down: (queryInterface, Sequelize) => {
return queryInterface.dropTable('RealEstateRequests');
}
};

View File

@@ -0,0 +1,18 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'RealEstateRequests',
'city',
Sequelize.STRING
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn(
'RealEstateRequests',
'city'
);
}
};

View File

@@ -0,0 +1,18 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'RealEstateRequests',
'place',
Sequelize.STRING
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn(
'RealEstateRequests',
'place'
);
}
};

View File

@@ -0,0 +1,19 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.renameColumn(
'RealEstateRequests',
'place',
'municipality'
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.renameColumn(
'RealEstateRequests',
'municipality',
'place'
);
}
};

View File

@@ -0,0 +1,19 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.renameColumn(
'RealEstateRequests',
'city',
'region'
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.renameColumn(
'RealEstateRequests',
'region',
'city'
);
}
};

View File

@@ -0,0 +1,20 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'RealEstateRequests',
'size',
{
type: Sequelize.STRING
}
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn(
'RealEstateRequests',
'size'
);
}
};

View File

@@ -0,0 +1,20 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'RealEstateRequests',
'gardenSize',
{
type: Sequelize.STRING
}
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn(
'RealEstateRequests',
'gardenSize'
);
}
};

View File

@@ -0,0 +1,20 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'RealEstateRequests',
'price',
{
type: Sequelize.STRING
}
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn(
'RealEstateRequests',
'price'
);
}
};

View File

@@ -0,0 +1,15 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.query("CREATE EXTENSION postgis").then(([results, metadata]) => {
/// No result
})
},
down: (queryInterface, Sequelize) => {
return queryInterface.sequelize.query("DROP EXTENSION IF EXISTS postgis").then(([results, metadata]) => {
/// No result
})
}
};

View File

@@ -0,0 +1,17 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.query("ALTER TABLE \"RealEstateRequests\" ADD COLUMN bounding_box geometry(Polygon);").then(([results, metadata]) => {
/// No result
})
},
down: (queryInterface, Sequelize) => {
return queryInterface.sequelize.query("ALTER TABLE \"RealEstateRequests\" DROP COLUMN bounding_box").then(([results, metadata]) => {
/// No result
})
}
};

View File

@@ -1,17 +0,0 @@
const Sequelize = require("sequelize");
const sequelize = require("../db/db");
const MarketAlert = sequelize.define("market_alert", {
id: {
type: Sequelize.INTEGER,
autoIncrement: true,
primaryKey: true
},
olx_url: Sequelize.STRING,
last_date: Sequelize.STRING,
email: {
type: Sequelize.STRING,
allowNull: false
}
});
module.exports = MarketAlert;

37
app/models/index.js Normal file
View File

@@ -0,0 +1,37 @@
'use strict';
const fs = require('fs');
const path = require('path');
const Sequelize = require('sequelize');
const basename = path.basename(__filename);
const env = process.env.NODE_ENV || 'development';
const config = require(__dirname + '/../config/config.json')[env];
const db = {};
let sequelize;
if (config.use_env_variable) {
sequelize = new Sequelize(process.env[config.use_env_variable], config);
} else {
sequelize = new Sequelize(config.database, config.username, config.password, config);
}
fs
.readdirSync(__dirname)
.filter(file => {
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
})
.forEach(file => {
const model = sequelize['import'](path.join(__dirname, file));
db[model.name] = model;
});
Object.keys(db).forEach(modelName => {
if (db[modelName].associate) {
db[modelName].associate(db);
}
});
db.sequelize = sequelize;
db.Sequelize = Sequelize;
module.exports = db;

15
app/models/marketalert.js Normal file
View File

@@ -0,0 +1,15 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const MarketAlert = sequelize.define('MarketAlert', {
olxUrl: DataTypes.STRING,
lastDate: DataTypes.STRING,
email: {
type: DataTypes.STRING,
allowNul: false
}
}, {});
MarketAlert.associate = function(models) {
// associations can be defined here
};
return MarketAlert;
};

View File

@@ -0,0 +1,24 @@
'use strict';
module.exports = (sequelize, DataTypes) => {
const RealEstateRequest = sequelize.define('RealEstateRequest', {
uniqueId: {
type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4,
allowNull: false
},
realEstateType: DataTypes.STRING,
email: DataTypes.STRING,
region: DataTypes.STRING,
municipality: DataTypes.STRING,
size: DataTypes.STRING,
gardenSize: DataTypes.STRING,
price: DataTypes.STRING,
bounding_box: DataTypes.GEOMETRY('POINT', 4326)
}, {});
RealEstateRequest.associate = function(models) {
// associations can be defined here
};
return RealEstateRequest;
};

BIN
app/public/images/logo.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 26 KiB

36
app/public/main.css Normal file
View File

@@ -0,0 +1,36 @@
.welcome-center-button {
width: 100%;
}
.welcome-big-logo {
font-size: 200pt;
background-image: url(./images/logo.png);
background-size: contain;
background-repeat: no-repeat;
color: rgba(0, 0, 0, 0);
}
#map {
height: 50%;
}
html,
body {
height: 100%;
margin: 0;
padding: 0;
}
#floating-panel {
top: 10px;
left: 25%;
z-index: 5;
background-color: #fff;
padding: 5px;
border: 1px solid #999;
text-align: center;
font-family: 'Roboto', 'sans-serif';
line-height: 30px;
padding-left: 10px;
}

View File

@@ -1,3 +0,0 @@
<a href="<%= nextStep %>"><button>Kreni</button></a>

29
app/views/gardenSize.ejs Normal file
View File

@@ -0,0 +1,29 @@
<!--suppress HtmlUnknownAnchorTarget -->
<div class="row center-align">
<h2>Koliko okućnice tražite ?</h2>
</div>
<form method="POST" id="form-gardensize">
<div class="row center-align">
<ul class="collection with-header">
<% for(const gardenSize of gardenSizes) { %>
<li class="collection-item" >
<div id="<%= gardenSize.id %>" onclick="saveAndSubmit(this.id)"><%= gardenSize.title %>
<a href="#" class="secondary-content">
<i class="material-icons">send</i>
</a>
</div>
</li>
<% } %>
</ul>
<input type="hidden" name="gardensize" id="gardensize" />
</div>
</form>
<script>
function saveAndSubmit(id) {
$("#gardensize").val(id);
$("#form-gardensize").submit();
}
</script>

25
app/views/goAgain.ejs Normal file
View File

@@ -0,0 +1,25 @@
<!--suppress HtmlUnknownAnchorTarget -->
<div class="row center-align">
<h4>Provjerite Vaš email !</h4>
</div>
<div class="row center-align">
<h4>Želite li pretražiti još jednu nekretninu ?</h4>
</div>
<form method="POST" id="form-goagain">
<div class="row">
<div class="col s3 push-s3">
<a href="/" class="welcome-center-button waves-effect waves-light btn">
Da
</a>
</div>
<div class="col s3 push-s3">
<a href="/" class="welcome-center-button waves-effect waves-light btn">
Ne
</a>
</div>
</div>
</form>

View File

@@ -1,14 +1,17 @@
<!doctype>
<html>
<head>
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" />
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<meta charset="UTF-8" />
<link rel="stylesheet" href="/assets/main.css">
</head>
<body>
Something in your body:
<%-body%>
<div class="container">
<%-body%>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
</body>
</html>

View File

@@ -0,0 +1,28 @@
<!--suppress HtmlUnknownAnchorTarget -->
<div class="row center-align">
<h2>U kojem mjestu tražite nekretninu?</h2>
</div>
<form method="POST" id="form-municipality">
<div class="row center-align">
<ul class="collection with-header">
<% for(const municipality of municipalities) { %>
<li class="collection-item">
<div val="<%= municipality.name %>" id="<%= municipality.id %>" onclick="saveAndSubmit(this.id)"><%= municipality.name %>
<a href="#" class="secondary-content">
<i class="material-icons">send</i>
</a>
</div>
</li>
<% } %>
</ul>
<input type="hidden" name="municipality" id="municipality" />
</div>
</form>
<script>
function saveAndSubmit(id, name) {
$("#municipality").val(id);
$("#form-municipality").submit();
}
</script>

View File

@@ -0,0 +1,90 @@
<div class="row center-align">
<h2>U kojem naselju tražite nekretninu?</h2>
</div>
<div class="row center-align" >
<div id="floating-panel">
<input id="address" type="textbox" value="">
<input id="submit" type="button" value="Trazi">
</div>
<div id="map"></div>
</div>
<form method="POST" id="form-map-output">
<div class="row center-align">
<div class="col s6 push-s3">
<a id="btnsubmit" href="#" class="welcome-center-button waves-effect waves-light btn">
Dalje
</a>
</div>
</div>
<input type="hidden" name="north" id=north />
<input type="hidden" name="south" id=south />
<input type="hidden" name="east" id=east />
<input type="hidden" name="west" id=west />
</form>
<script>
var map;
var municipality = "<%= municipality%>";
var defaultAddress = document.getElementById('address');
var BOSNIA_BOUNDS = {
north: 45.70,
south: 41.69,
west: 15.55,
east: 20.77,
};
function initMap() {
map = new google.maps.Map(document.getElementById('map'), {
center: { lat: -34.397, lng: 150.644 },
zoom: 11,
restriction: {
latLngBounds: BOSNIA_BOUNDS,
strictBounds: false,
},
});
var geocoder = new google.maps.Geocoder();
document.getElementById('submit').addEventListener('click', function () {
geocodeAddress(geocoder, map);
});
function geocodeAddress(geocoder, resultsMap) {
var address = document.getElementById('address').value;
geocoder.geocode({ 'address': address }, function (results, status) {
if (status === 'OK') {
resultsMap.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker({
map: resultsMap,
position: results[0].geometry.location
});
} else {
alert('Geocode was not successful for the following reason: ' + status);
}
});
}
defaultAddress.value = municipality;
geocodeAddress(geocoder, map);
$(document).ready(() => {
$("#btnsubmit").click(() => {
var bounds = map.getBounds();
$("#north").val(bounds.na.l);
$("#south").val(bounds.na.j);
$("#east").val(bounds.ia.l);
$("#west").val(bounds.ia.j);
$("#form-map-output").submit();
});
});
}
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAna8ohfV2HBMcxGk_29vqxU5Z_bDickqg&callback=initMap"
async defer></script>
</div>

29
app/views/price.ejs Normal file
View File

@@ -0,0 +1,29 @@
<!--suppress HtmlUnknownAnchorTarget -->
<div class="row center-align">
<h2>Koja Vam okvirna cijena odgovara ?</h2>
</div>
<form method="POST" id="form-price">
<div class="row center-align">
<ul class="collection with-header">
<% for(const price of prices) { %>
<li class="collection-item" >
<div id="<%= price.id %>" onclick="saveAndSubmit(this.id)"><%= price.title %>
<a href="#" class="secondary-content">
<i class="material-icons">send</i>
</a>
</div>
</li>
<% } %>
</ul>
<input type="hidden" name="price" id="price" />
</div>
</form>
<script>
function saveAndSubmit(id) {
$("#price").val(id);
$("#form-price").submit();
}
</script>

36
app/views/queryReview.ejs Normal file
View File

@@ -0,0 +1,36 @@
<!--suppress HtmlUnknownAnchorTarget -->
<div class="row center-align">
<h2>Da li je ovo to što ste tražili ?</h2>
</div>
<form method="POST" id="form-queryreview">
<div class="row center-align">
<ul class="collection with-header">
<% for(const stepData of queryData) { %>
<li class="collection-item" >
<div id="<%= stepData.id %>" ><%= stepData.title || '-' %>
<a href="<%= stepData.url %>" class="secondary-content">
<i class="material-icons">edit</i>
</a>
</div>
</li>
<% } %>
</ul>
</div>
<div class="row">
<div class="col s6 push-s3">
<a id="submit" href="#" class="welcome-center-button waves-effect waves-light btn">
To je to
</a>
</div>
</div>
</form>
<script>
$(document).ready( () => {
$("#submit").click( () => {
$("#form-queryreview").submit();
});
});
</script>

43
app/views/querySubmit.ejs Normal file
View File

@@ -0,0 +1,43 @@
<!--suppress HtmlUnknownAnchorTarget -->
<div class="row center-align">
<h4>Da Vam javimo kada se Vaša željena nekretnina pojavi u oglasima, upišite svoj e-mail</h4>
</div>
<form method="POST" id="form-submitquery">
<div class="row center-align">
<div class="col s6 push-s3">
<input id="email" name="email" type="email" placeholder="vas.email@mail.com" required size="250" />
</div>
</div>
<% if (error) {%>
<div class="row">
<div class="col s6 push-s3">
<h6 style="color: red">Greška ! Unesite ispravan email</h6>
</div>
</div>
<%}%>
<div class="row">
<div class="col s6 push-s3">
<a id="submit" href="#" class="welcome-center-button waves-effect waves-light btn">
Javi mi
</a>
</div>
</div>
<div class="row">
<div class="col s6 push-s3">
<p>* U svakom trenutku možete prekinuti slanje objava kroz link u e-mailu</p>
</div>
</div>
</form>
<script>
$(document).ready( () => {
$("#submit").click( () => {
const emailField = document.getElementById('email');
if (emailField.validity.valid){
$("#form-submitquery").submit();
}
});
});
</script>

View File

@@ -0,0 +1,33 @@
<!--suppress HtmlUnknownAnchorTarget -->
<div class="row center-align">
<h2>Koju nekretninu tražite?</h2>
</div>
<form method="POST" id="form-real-estate-type">
<div class="row center-align">
<ul class="collection with-header">
<% for(const realEstateType of realEstateTypes) { %>
<li class="collection-item">
<div id="<%= realEstateType.id %>" onclick="saveAndSubmit(this.id)"><%= realEstateType.title %>
<a href="#" class="secondary-content">
<i class="material-icons">send</i>
</a>
</div>
</li>
<% } %>
</ul>
<input type="hidden" name="realestatetype" id="realestatetype" />
</div>
</form>
<script>
function saveAndSubmit(id) {
$("#realestatetype").val(id);
$("#form-real-estate-type").submit();
}
</script>

29
app/views/region.ejs Normal file
View File

@@ -0,0 +1,29 @@
<!--suppress HtmlUnknownAnchorTarget -->
<div class="row center-align">
<h2>U kojoj regiji tražite nekretninu?</h2>
</div>
<form method="POST" id="form-region">
<div class="row center-align">
<ul class="collection with-header">
<% for(const region of regions) { %>
<li class="collection-item">
<div id="<%= region.id %>" onclick="saveAndSubmit(this.id)"><%= region.name %>
<a href="#" class="secondary-content">
<i class="material-icons">send</i>
</a>
</div>
</li>
<% } %>
</ul>
<input type="hidden" name="region" id="region" />
</div>
</form>
<script>
function saveAndSubmit(id) {
$("#region").val(id);
$("#form-region").submit();
}
</script>

29
app/views/size.ejs Normal file
View File

@@ -0,0 +1,29 @@
<!--suppress HtmlUnknownAnchorTarget -->
<div class="row center-align">
<h2>Do koliko kvadrata tražite nekretninu ?</h2>
</div>
<form method="POST" id="form-size">
<div class="row center-align">
<ul class="collection with-header">
<% for(const size of sizes) { %>
<li class="collection-item">
<div id="<%= size.id %>" onclick="saveAndSubmit(this.id)"><%= size.title %>
<a href="#" class="secondary-content">
<i class="material-icons">send</i>
</a>
</div>
</li>
<% } %>
</ul>
<input type="hidden" name="size" id="size" />
</div>
</form>
<script>
function saveAndSubmit(id) {
$("#size").val(id);
$("#form-size").submit();
}
</script>

View File

@@ -1,3 +0,0 @@
<a href="<%= nextStep %>"> >>> </a>

17
app/views/welcome.ejs Normal file
View File

@@ -0,0 +1,17 @@
<!-- -->
<div class="row center-align">
<span class="welcome-big-logo">🤙</span>
</div>
<div class="row center-align">
<div>Sve nekretnine dostupne u oglasima.</div>
<div> Na vaš email. </div>
<div> BESPLATNO </div>
</div>
<div class="row">
<div class="col s6 push-s3">
<a href="<%= nextStep %>" class="welcome-center-button waves-effect waves-light btn">
Javi mi
</a>
</div>
</div>

View File

@@ -1,13 +1,22 @@
const dobrodosli = require('./app/controllers/dobrodosli').getDobrodosli;
const getVrstaNekretnine = require('./app/controllers/vrsta_nekretnine').getVrstaNekretnine;
const welcome = require('./app/controllers/welcome').getWelcome;
const { getRealEstateTypes, postRealEstateTypes} = require('./app/controllers/realEstateTypes');
const { getRegion, postRegion } = require('./app/controllers/regions');
const { getMunicipality, postMunicipality } = require('./app/controllers/municipalities');
const { getSize, postSize } = require('./app/controllers/sizes');
const { getGardenSize, postGardenSize } = require('./app/controllers/gardenSizes');
const { getPrice, postPrice } = require('./app/controllers/prices');
const { getQueryReview, postQueryReview } = require('./app/controllers/queryReview');
const { getQuerySubmit, postQuerySubmit } = require('./app/controllers/querySubmit');
const { getGoAgain } = require('./app/controllers/goAgain');
const { getNeighborhood, postNeighborhood } = require('./app/controllers/neighborhoodMap');
let express = require("express");
const path = require("path");
const bodyParser = require("body-parser");
const MarketAlert = require("./app/models/MarketAlert");
const sendNotification = require("./app/lib/sendnotification");
const scrapTheItems = require("./app/lib/scraptheitems");
const sequelize = require("./app/db/db");
const MarketAlert = require("./app/models/marketalert");
const sendNotification = require("./app/lib/sendNotification");
const scrapTheItems = require("./app/lib/scrapTheItems");
const sequelize = require("./app/models/index").sequelize;
const Twocheckout = require("2checkout-node");
const layout = require('express-layout');
@@ -21,8 +30,10 @@ app.set('views', path.join(__dirname, '/app/views'));
app.set('view engine', 'ejs');
app.use(layout());
const compression = require('compression');
app.use(compression());
app.get("/api/sendnotifications", async function(req, res) {
app.get("/api/sendnotifications", async (req, res) => {
let marketAlerts = await MarketAlert.findAll();
let lastDateUpdate = await Promise.all(
@@ -55,7 +66,7 @@ app.get("/api/items/:url", async (req, res) => {
});
});
app.post("/api/marketalerts", function(req, res) {
app.post("/api/marketalerts", (req, res) => {
const { email, last_date, olx_url } = req.body;
console.log(email, last_date, olx_url);
sequelize.sync().then(() => {
@@ -71,7 +82,7 @@ app.post("/api/marketalerts", function(req, res) {
});
});
app.post("/api/payforalert", function(request, response) {
app.post("/api/payforalert", (req, res) => {
let tco = new Twocheckout({
sellerId: "901402692",
privateKey: "A28DCE5F-9292-405C-8161-F84D8BB83AFC",
@@ -80,7 +91,7 @@ app.post("/api/payforalert", function(request, response) {
let params = {
merchantOrderId: "123",
token: request.body.token,
token: req.body.token,
currency: "USD",
total: "2.00",
billingAddr: {
@@ -90,21 +101,53 @@ app.post("/api/payforalert", function(request, response) {
state: "BiH",
zipCode: "71000",
country: "BiH",
email: request.body.email,
email: req.body.email,
phoneNumber: "5555555555"
}
};
tco.checkout.authorize(params, function(error, data) {
if (error) {
response.send(error.message);
res.send(error.message);
} else {
response.send(data.response.responseMsg);
res.send(data.response.responseMsg);
}
});
});
app.get('/', dobrodosli);
app.get('/vrstanekretnine', getVrstaNekretnine);
app.get('/', welcome);
app.get('/vrstanekretnine/:request_id', getRealEstateTypes);
app.get('/vrstanekretnine', getRealEstateTypes);
app.post('/vrstanekretnine/:request_id', postRealEstateTypes);
app.post('/vrstanekretnine', postRealEstateTypes);
app.get('/grad/:request_id', getRegion);
app.post('/grad/:request_id', postRegion);
app.get('/mjesto/:request_id', getMunicipality);
app.post('/mjesto/:request_id', postMunicipality);
app.get('/naselje/:request_id/:municipality', getNeighborhood);
app.post('/naselje/:request_id/:municipality', postNeighborhood);
app.get('/povrsina/:request_id', getSize);
app.post('/povrsina/:request_id', postSize);
app.get('/okucnica/:request_id', getGardenSize);
app.post('/okucnica/:request_id', postGardenSize);
app.get('/cijena/:request_id', getPrice);
app.post('/cijena/:request_id', postPrice);
app.get('/pregled/:request_id', getQueryReview);
app.post('/pregled/:request_id', postQueryReview);
app.get('/posalji/:request_id', getQuerySubmit);
app.post('/posalji/:request_id', postQuerySubmit);
app.get('/ponovo', getGoAgain);
app.use('/assets', express.static('./app/public'));
app.listen(port, () => console.log(`Example app listening on port ${port}!`));

3176
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -6,7 +6,11 @@
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node ./index.js",
"heroku-postbuild": "cd frontend-react && npm install && npm run build"
"start-mon": "nodemon ./index.js",
"migrate": "cd app && npx sequelize db:migrate",
"setup": "docker build -t marketalerts . && docker run -e POSTGRES_USER=docker -e POSTGRES_PASSWORD=docker -e POSTGRES_DB=marketalerts --name pg_marketalerts -d -p 5432:5432 marketalerts && sleep 4 && npm run migrate",
"docker-start": "docker start pg_marketalerts",
"docker-stop": "docker stop pg_marketalerts"
},
"repository": {
"type": "git",
@@ -22,13 +26,19 @@
"@sendgrid/mail": "^6.3.1",
"aws-sdk": "^2.422.0",
"cheerio": "^1.0.0-rc.2",
"compression": "^1.7.4",
"dotenv": "^7.0.0",
"ejs": "^2.6.1",
"express": "^4.16.4",
"express-ejs-layouts": "^2.5.0",
"express-layout": "^0.1.0",
"mysql2": "^1.6.4",
"node-fetch": "^2.3.0",
"pg": "^7.10.0",
"react-step-wizard": "^5.1.0",
"sequelize": "^4.42.0"
"sequelize": "^4.43.2",
"sequelize-cli": "^5.4.0"
},
"devDependencies": {
"nodemon": "^1.19.0"
}
}

389
tools/kantoni.html Normal file
View File

@@ -0,0 +1,389 @@
<!DOCTYPE>
<html>
<head>
<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()">
<option value="">Iz svih lokacija</option>
<option value="" disabled="">Federacija BiH</option>
<option value="9">&nbsp;Sarajevo</option>
<option value="3">&nbsp;Tuzlanski</option>
<option value="4">&nbsp;Zeničko-Dobojski</option>
<option value="1">&nbsp;Unsko-Sanski</option>
<option value="2">&nbsp;Posavski</option>
<option value="5">&nbsp;Bosansko-podrinjski</option>
<option value="6">&nbsp;Srednjobosanski</option>
<option value="7">&nbsp;Hercegovačko-Neretvanski</option>
<option value="8">&nbsp;Zapadno-Hercegovački</option>
<option value="10">&nbsp;Livanjski</option>
<option value="" disabled="">Republika Srpska</option>
<option value="14">&nbsp;Banjalučka</option>
<option value="15">&nbsp;Dobojsko-Bijeljinska</option>
<option value="16">&nbsp;Sarajevsko-Zvornička</option>
<option value="17">&nbsp;Trebinjsko-Fočanska</option>
<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()">
<option value="0">Mjesto</option>
<option value="75">Bihać</option>
<option value="373">Bosanska Krupa</option>
<option value="504">Bosanski Petrovac</option>
<option value="374">Bužim</option>
<option value="857">Cazin</option>
<option value="2362">Ključ</option>
<option value="3738">Sanski Most</option>
<option value="5122">Velika Kladuša</option>
</select>
</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()">
<option value="0">Mjesto</option>
<option value="6144">Domaljevac</option>
<option value="424">Odžak</option>
<option value="3252">Orašje</option>
<option value="540">Šamac</option>
</select>
</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()">
<option value="0">Mjesto</option>
<option value="2">Banovići</option>
<option value="1090">Doboj-Istok</option>
<option value="1854">Gradačac</option>
<option value="1826">Gračanica</option>
<option value="2129">Kalesija</option>
<option value="2319">Kladanj</option>
<option value="2840">Lukavac</option>
<option value="5699">Sapna</option>
<option value="4391">Srebrenik</option>
<option value="5010">Teočak</option>
<option value="4944">Tuzla</option>
<option value="2801">Čelić</option>
<option value="5774">Živinice</option>
</select>
</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()">
<option value="0">Mjesto</option>
<option value="704">Breza</option>
<option value="1122">Doboj-Jug</option>
<option value="2022">Kakanj</option>
<option value="2941">Maglaj</option>
<option value="1925">Olovo</option>
<option value="4594">Tešanj</option>
<option value="1087">Usora</option>
<option value="5037">Vareš</option>
<option value="5171">Visoko</option>
<option value="5548">Zavidovići</option>
<option value="4571">Zenica</option>
<option value="2940">Žepče</option>
</select>
</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()">
<option value="0">Mjesto</option>
<option value="1289">Foča</option>
<option value="1588">Goražde</option>
<option value="3546">Pale</option>
</select>
</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()">
<option value="0">Mjesto</option>
<option value="732">Bugojno</option>
<option value="810">Busovača</option>
<option value="4151">Dobretići</option>
<option value="1160">Donji Vakuf</option>
<option value="1407">Fojnica</option>
<option value="1775">Gornji Vakuf - Uskoplje</option>
<option value="1960">Jajce</option>
<option value="2237">Kiseljak</option>
<option value="2608">Kreševo</option>
<option value="3477">Novi Travnik</option>
<option value="4678">Travnik</option>
<option value="5422">Vitez</option>
</select>
</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()">
<option value="0">Mjesto</option>
<option value="3017">Grad Mostar</option>
<option value="1930">Jablanica</option>
<option value="2169">Konjic</option>
<option value="3111">Neum</option>
<option value="3421">Prozor</option>
<option value="4769">Ravno</option>
<option value="4439">Stolac</option>
<option value="947">Čapljina</option>
<option value="1009">Čitluk</option>
</select>
</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()">
<option value="0">Mjesto</option>
<option value="1892">Grude</option>
<option value="2905">Ljubuški</option>
<option value="3268">Posušje</option>
<option value="2708">Široki Brijeg</option>
</select>
</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()">
<option value="0">Mjesto</option>
<option value="3817">Hadžići</option>
<option value="3879">Ilidža</option>
<option value="3892">Ilijaš</option>
<option value="3812">Sarajevo - Centar</option>
<option value="3969">Sarajevo-Novi Grad</option>
<option value="5896">Sarajevo-Novo Sarajevo</option>
<option value="4048">Sarajevo-Stari Grad</option>
<option value="4063">Trnovo</option>
<option value="4126">Vogošća</option>
</select>
</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()">
<option value="0">Mjesto</option>
<option value="560">Bosansko Grahovo</option>
<option value="4640">Drvar</option>
<option value="1533">Glamoč</option>
<option value="2635">Kupres</option>
<option value="2741">Livno</option>
<option value="1228">Tomislavgrad</option>
</select>
</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()">
<option value="0">Mjesto</option>
<option value="645">distriktbrcko</option>
</select>
</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()">
<option value="0">Mjesto</option>
<option value="21">Banja Luka</option>
<option value="305">Gradiška</option>
<option value="4662">Istočni Drvar</option>
<option value="1965">Jezero</option>
<option value="4147">Kneževo</option>
<option value="6142">Kostajnica</option>
<option value="2574">Kotor Varoš</option>
<option value="244">Kozarska Dubica</option>
<option value="382">Krupa na uni</option>
<option value="2654">Kupres </option>
<option value="2671">Laktaši</option>
<option value="3073">Mrkonjić Grad</option>
<option value="444">Novi Grad</option>
<option value="3737">Oštra Luka</option>
<option value="515">Petrovac</option>
<option value="3287">Prijedor</option>
<option value="3358">Prnjavor</option>
<option value="2365">Ribnik</option>
<option value="4271">Srbac</option>
<option value="979">Čelinac</option>
<option value="4509">Šipovo</option>
</select>
</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()">
<option value="0">Mjesto</option>
<option value="123">Bijeljina</option>
<option value="421">Bosanski Brod</option>
<option value="1030">Derventa</option>
<option value="1088">Doboj</option>
<option value="3254">Donji Žabar</option>
<option value="2800">Lopare</option>
<option value="6029">Lukavac</option>
<option value="2996">Modriča</option>
<option value="1856">Pelagićevo</option>
<option value="1827">Petrovo</option>
<option value="1148">Stanari</option>
<option value="4549">Teslić</option>
<option value="4636">Tešanj</option>
<option value="4692">Travnik</option>
<option value="4966">Tuzla</option>
<option value="5009">Ugljevik</option>
<option value="3197">Vukosavlje</option>
<option value="539">Šamac</option>
</select>
</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()">
<option value="0">Mjesto</option>
<option value="595">Bratunac</option>
<option value="1904">Han Pijesak</option>
<option value="3947">Ilijaš</option>
<option value="4049">Istočni Stari Grad</option>
<option value="3880">Kasindo</option>
<option value="2325">Kladanj</option>
<option value="3971">Lukavica</option>
<option value="6143">Milići</option>
<option value="3221">Olovo</option>
<option value="2128">Osmaci</option>
<option value="3978">Pale</option>
<option value="3529">Rogatica</option>
<option value="3648">Rudo</option>
<option value="6069">Sarajevo-Novi Grad</option>
<option value="4183">Sokolac</option>
<option value="4310">Srebrenica</option>
<option value="4067">Trnovo</option>
<option value="1593">Ustiprača</option>
<option value="5259">Višegrad</option>
<option value="5456">Vlasenica</option>
<option value="5684">Zvornik</option>
<option value="4475">Šekovići</option>
<option value="1906">Žepa</option>
</select>
</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()">
<option value="0">Mjesto</option>
<option value="4441">Berkovići</option>
<option value="183">Bileća</option>
<option value="1287">Foča</option>
<option value="1462">Gacko</option>
<option value="3038">Istočni Mostar</option>
<option value="2164">Kalinovik</option>
<option value="2884">Ljubinje</option>
<option value="3138">Nevesinje</option>
<option value="4766">Trebinje</option>
<option value="911">Čajniče</option>
</select>
</span>
</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"},
];
for (let grad of gradovi) {
const mjesta = [];
$('#' + grad.id).children().each( function() {
const mjesto = $(this).text();
mjesta.push( {
ime: mjesto,
id: mjesto.toLowerCase().replace(/[^a-z]/g,''),
olxid: $(this).val()
});
});
grad.mjesta = mjesta;
}
console.log(JSON.stringify(gradovi));
</script>
</body>
</html>