Compare commits

..

1 Commits

Author SHA1 Message Date
MirnaM
a0b4bc7879 Scrape lat lng from olx 2019-05-06 15:13:13 +02:00
53 changed files with 516 additions and 3520 deletions

View File

@@ -1,11 +1,48 @@
FROM postgres:11.3 #
# example Dockerfile for https://docs.docker.com/engine/examples/postgresql_service/
#
ENV POSTGIS_MAJOR 2.4 FROM ubuntu:16.04
RUN apt-get update \ # Add the PostgreSQL PGP key to verify their Debian packages.
&& apt-get --assume-yes install software-properties-common postgis\ # It should be the same key as https://www.postgresql.org/media/keys/ACCC4CF8.asc
&& rm -rf /var/lib/apt/lists/ RUN apt-key adv --keyserver hkp://p80.pool.sks-keyservers.net:80 --recv-keys B97B0AFCAA1A47F044F244A07FCC7D46ACCC4CF8
RUN mkdir -p /docker-entrypoint-initdb.d # Add PostgreSQL's repository. It contains the most recent stable release
# of PostgreSQL, ``9.3``.
RUN echo "deb http://apt.postgresql.org/pub/repos/apt/ precise-pgdg main" > /etc/apt/sources.list.d/pgdg.list
CMD ["postgres"] # Install ``python-software-properties``, ``software-properties-common`` and PostgreSQL 9.3
# There are some warnings (in red) that show up during the build. You can hide
# them by prefixing each apt-get statement with DEBIAN_FRONTEND=noninteractive
RUN apt-get update && apt-get install -y python-software-properties software-properties-common postgresql-9.3 postgresql-client-9.3 postgresql-contrib-9.3
# Note: The official Debian and Ubuntu images automatically ``apt-get clean``
# after each ``apt-get``
# Run the rest of the commands as the ``postgres`` user created by the ``postgres-9.3`` package when it was ``apt-get installed``
USER postgres
# Create a PostgreSQL role named ``docker`` with ``docker`` as the password and
# then create a database `docker` owned by the ``docker`` role.
# Note: here we use ``&&\`` to run commands one after the other - the ``\``
# allows the RUN command to span multiple lines.
RUN /etc/init.d/postgresql start &&\
psql --command "CREATE USER docker WITH SUPERUSER PASSWORD 'docker';" &&\
createdb -O docker marketalerts
# Adjust PostgreSQL configuration so that remote connections to the
# database are possible.
RUN echo "host all all 0.0.0.0/0 md5" >> /etc/postgresql/9.3/main/pg_hba.conf
# And add ``listen_addresses`` to ``/etc/postgresql/9.3/main/postgresql.conf``
RUN echo "listen_addresses='*'" >> /etc/postgresql/9.3/main/postgresql.conf
# Expose the PostgreSQL port
EXPOSE 5432
# Add VOLUMEs to allow backup of config, logs and databases
VOLUME ["/etc/postgresql", "/var/log/postgresql", "/var/lib/postgresql"]
# Set the default command to run when starting the container
CMD ["/usr/lib/postgresql/9.3/bin/postgres", "-D", "/var/lib/postgresql/9.3/main", "-c", "config_file=/etc/postgresql/9.3/main/postgresql.conf"]

View File

@@ -1,36 +1,10 @@
# MarketAlert # web
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 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 Run postgres image with:
docker run --name pg_test -d -p 5432:5432 marketalerts
### 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

View File

@@ -7,9 +7,9 @@
"dialect": "postgres" "dialect": "postgres"
}, },
"test": { "test": {
"use_env_variable": "DATABASE_URL" "use_env_variable": "JAWSDB_URL"
}, },
"production": { "production": {
"use_env_variable": "DATABASE_URL" "use_env_variable": "JAWSDB_URL"
} }
} }

View File

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

View File

@@ -1,26 +0,0 @@
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

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

27
app/controllers/grad.js Normal file
View File

@@ -0,0 +1,27 @@
const db = require('../models/index');
const { currentRERequest } = require('../helpers/url');
const { regions } = require('../helpers/codes');
const gradovi = regions();
const getGrad = (req,res) => {
const nextStep = req.query.nextStep || '/';
res.render('grad', {
nextStep,
gradovi
});
}
const postGrad = async (req, res) => {
const request = await currentRERequest(req);
const nextStep = req.query.nextStep || `/mjesto/${request.uniqueId}`;
request.city = req.body.grad;
await request.save();
res.redirect(nextStep)
}
module.exports = {
getGrad,
postGrad
};

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

@@ -0,0 +1,26 @@
const db = require('../models/index');
const { currentRERequest } = require('../helpers/url');
const { places } = require('../helpers/codes');
const getMjesto = async (req,res) => {
let request = await currentRERequest(req);
const mjesta = places(request.city);
const nextStep = req.query.nextStep || '/';
res.render('mjesto', {
nextStep,
mjesta
});
}
const postMjesto = async (req, res) => {
let request = await currentRERequest(req);
request.place = req.body.mjesto;
console.log("AAA ", req.body);
await request.save();
res.send("Result is " + JSON.stringify(request));
}
module.exports = {
getMjesto,
postMjesto
};

View File

@@ -1,25 +0,0 @@
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

@@ -1,35 +0,0 @@
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
};

View File

@@ -1,23 +0,0 @@
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

@@ -1,76 +0,0 @@
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

@@ -1,32 +0,0 @@
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

@@ -1,44 +0,0 @@
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

@@ -1,27 +0,0 @@
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
};

View File

@@ -1,26 +0,0 @@
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

@@ -0,0 +1,35 @@
const db = require('../models/index');
const vrsteNekretnina = [
{ ime: "Kuća", id: "kuca" },
{ ime: "Stan", id: "stan" },
{ ime: "Vikendica", id: "vikendica" }
];
const getVrstaNekretnine = (req,res) => {
const nextStep = req.query.nextStep;
res.render('vrsta_nekretnine', {
nextStep,
vrste: vrsteNekretnina
});
}
const postVrstaNekretnine = (req, res) => {
let nextStep = req.query.nextStep;
db.RealEstateRequest.create({
realEstateType: req.body.vrsta
}).then( (result) => {
nextStep = nextStep || `/grad/${result.uniqueId}`;
res.redirect(nextStep);
}).catch( (e) => {
res.send(e);
});
}
module.exports = {
getVrstaNekretnine,
postVrstaNekretnine
};

View File

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

File diff suppressed because it is too large Load Diff

View File

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

View File

@@ -1,57 +0,0 @@
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,
};

27
app/helpers/scraping.js Normal file
View File

@@ -0,0 +1,27 @@
let fetch = require("node-fetch");
const getRealEstateGeolocation = async (url) => {
let response = await fetch(url);
const body = await response.text();
let lat, long;
const googleMapRegex = new RegExp(/google.maps.LatLng\((.*?)\)/g);
const googleMapString = body.match(googleMapRegex);
if (googleMapString && googleMapString.length) {
const latLongRegex = new RegExp(/\((.*?)\)/g);
let latLongString = googleMapString[0].match(latLongRegex);
if (latLongString && latLongString.length) {
latLongString = latLongString[0].trim();
latLongString = latLongString.substr(1, latLongString.length - 2);
const latLongArray = latLongString.split(",");
if (latLongArray.length) {
lat = latLongArray[0];
long = latLongArray[1];
}
}
}
return { lat, long };
}
module.exports = {
getRealEstateGeolocation
};

View File

@@ -5,9 +5,10 @@ const currentRERequest = async (req) => {
if(!uniqueId) return null; if(!uniqueId) return null;
const request = await db.RealEstateRequest.findOne({ where: {uniqueId} }); const request = await db.RealEstateRequest.findOne({ where: {uniqueId} });
console.log("Request ", request);
return request; return request;
}; }
module.exports = { module.exports = {
currentRERequest currentRERequest
}; }

View File

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

View File

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

View File

@@ -1,11 +1,11 @@
const scrapTheItems = require("./scrapTheItems"); const scrapTheItems = require("./scraptheitems");
const convertToDate = require("./convertToDate"); const convertToDate = require("./convertToDate");
const AWS = require('aws-sdk'); const AWS = require('aws-sdk');
AWS.config.update({region: 'eu-central-1'}); AWS.config.update({region: 'eu-central-1'});
async function sendNotification(marketAlert) { async function sendNotification(marketAlert) {
const { id, email, olx_url } = marketAlert; const { id, email, olx_url, last_date } = marketAlert;
let url = let url =
"https://www.olx.ba/pretraga?" + olx_url + "&sort_order=desc&sort_po=datum"; "https://www.olx.ba/pretraga?" + olx_url + "&sort_order=desc&sort_po=datum";
let newItems = await scrapTheItems(url); let newItems = await scrapTheItems(url);
@@ -18,7 +18,7 @@ async function sendNotification(marketAlert) {
); );
// Create sendEmail params // Create sendEmail params
const params = { var params = {
Destination: { /* required */ Destination: { /* required */
CcAddresses: [ CcAddresses: [
], ],

View File

@@ -12,7 +12,8 @@ module.exports = {
type: Sequelize.UUID type: Sequelize.UUID
}, },
realEstateType: { realEstateType: {
type: Sequelize.STRING type: Sequelize.ENUM,
values: ['kuca','stan','vikendica','plac','poslovni_prostor','apartman','garaza']
}, },
email: { email: {
type: Sequelize.STRING type: Sequelize.STRING

View File

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

View File

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

View File

@@ -1,20 +0,0 @@
'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

@@ -1,20 +0,0 @@
'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

@@ -1,20 +0,0 @@
'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

@@ -1,15 +0,0 @@
'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

@@ -1,17 +0,0 @@
'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,21 +1,18 @@
'use strict'; 'use strict';
module.exports = (sequelize, DataTypes) => { module.exports = (sequelize, DataTypes) => {
const RealEstateRequest = sequelize.define('RealEstateRequest', { const RealEstateRequest = sequelize.define('RealEstateRequest', {
uniqueId: { uniqueId: {
type: DataTypes.UUID, type: DataTypes.UUID,
defaultValue: DataTypes.UUIDV4, defaultValue: DataTypes.UUIDV4,
allowNull: false allowNull: false
}, },
realEstateType: DataTypes.STRING, realEstateType: {
type: DataTypes.ENUM,
values: ['kuca','stan','vikendica','plac','poslovni_prostor','apartman','garaza']
},
email: DataTypes.STRING, email: DataTypes.STRING,
region: DataTypes.STRING, city: DataTypes.STRING,
municipality: DataTypes.STRING, place: DataTypes.STRING,
size: DataTypes.STRING,
gardenSize: DataTypes.STRING,
price: DataTypes.STRING,
bounding_box: DataTypes.GEOMETRY('POINT', 4326)
}, {}); }, {});
RealEstateRequest.associate = function(models) { RealEstateRequest.associate = function(models) {
// associations can be defined here // associations can be defined here

View File

@@ -1,36 +1,11 @@
.welcome-center-button { .dobrodosli-center-button {
width: 100%; width: 100%;
} }
.welcome-big-logo { .dobrodosli-big-logo {
font-size: 200pt; font-size: 200pt;
background-image: url(./images/logo.png); background-image: url(./images/logo.png);
background-size: contain; background-size: contain;
background-repeat: no-repeat; background-repeat: no-repeat;
color: rgba(0, 0, 0, 0); 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,7 +1,7 @@
<!-- --> <!-- -->
<div class="row center-align"> <div class="row center-align">
<span class="welcome-big-logo">🤙</span> <span class="dobrodosli-big-logo">🤙</span>
</div> </div>
<div class="row center-align"> <div class="row center-align">
<div>Sve nekretnine dostupne u oglasima.</div> <div>Sve nekretnine dostupne u oglasima.</div>
@@ -10,7 +10,7 @@
</div> </div>
<div class="row"> <div class="row">
<div class="col s6 push-s3"> <div class="col s6 push-s3">
<a href="<%= nextStep %>" class="welcome-center-button waves-effect waves-light btn"> <a href="<%= nextStep %>" class="dobrodosli-center-button waves-effect waves-light btn">
Javi mi Javi mi
</a> </a>
</div> </div>

View File

@@ -1,29 +0,0 @@
<!--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>

View File

@@ -1,25 +0,0 @@
<!--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>

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

@@ -0,0 +1,25 @@
<div class="row center-align">
<h2>U kojoj regiji tražite nekretninu?</h2>
</div>
<form method="POST" id="form-grad">
<div class="row center-align">
<ul class="collection with-header">
<% for(const grad of gradovi) { %>
<li class="collection-item" > <div id="<%= grad.id %>" ><%= grad.ime %><a href="#!" class="secondary-content"><i class="material-icons">send</i></a></div></li>
<% } %>
</ul>
<input type="hidden" name="grad" id="grad" />
</div>
</form>
<script>
$(document).ready( () => {
$(".collection-item").click( (e) => {
const clickedId = $(e.target).attr("id");
$("#grad").val(clickedId);
$("#form-grad").submit();
});
});
</script>

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

@@ -0,0 +1,25 @@
<div class="row center-align">
<h2>U kojem mjestu tražite nekretninu?</h2>
</div>
<form method="POST" id="form-mjesto">
<div class="row center-align">
<ul class="collection with-header">
<% for(const mjesto of mjesta) { %>
<li class="collection-item" > <div id="<%= mjesto.id %>" ><%= mjesto.ime %><a href="#!" class="secondary-content"><i class="material-icons">send</i></a></div></li>
<% } %>
</ul>
<input type="hidden" name="mjesto" id="mjesto" />
</div>
</form>
<script>
$(document).ready( () => {
$(".collection-item").click( (e) => {
const clickedId = $(e.target).attr("id");
$("#mjesto").val(clickedId);
$("#form-mjesto").submit();
});
});
</script>

View File

@@ -1,28 +0,0 @@
<!--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

@@ -1,90 +0,0 @@
<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>

View File

@@ -1,29 +0,0 @@
<!--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>

View File

@@ -1,36 +0,0 @@
<!--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>

View File

@@ -1,43 +0,0 @@
<!--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

@@ -1,33 +0,0 @@
<!--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>

View File

@@ -1,29 +0,0 @@
<!--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>

View File

@@ -1,29 +0,0 @@
<!--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

@@ -0,0 +1,29 @@
<div class="row center-align">
<h2>Koju nekretninu tražite?</h2>
</div>
<form method="POST" id="form-vrsta">
<div class="row center-align">
<ul class="collection with-header">
<% for(let vrsta of vrste) { %>
<li class="collection-item" > <div id="<%= vrsta.id %>" ><%= vrsta.ime %><a href="#!" class="secondary-content"><i class="material-icons">send</i></a></div></li>
<% } %>
</ul>
<input type="hidden" name="vrsta" id="vrsta" />
</div>
</form>
<script>
$(document).ready( () => {
$(".collection-item").click( (e) => {
const clickedId = $(e.target).attr("id");
$("#vrsta").val(clickedId);
$("#form-vrsta").submit();
});
});
</script>

View File

@@ -1,21 +1,14 @@
const welcome = require('./app/controllers/welcome').getWelcome; const dobrodosli = require('./app/controllers/dobrodosli').getDobrodosli;
const { getRealEstateTypes, postRealEstateTypes} = require('./app/controllers/realEstateTypes'); const { getVrstaNekretnine, postVrstaNekretnine} = require('./app/controllers/vrsta_nekretnine');
const { getRegion, postRegion } = require('./app/controllers/regions'); const { getGrad, postGrad } = require('./app/controllers/grad');
const { getMunicipality, postMunicipality } = require('./app/controllers/municipalities'); const { getMjesto, postMjesto } = require('./app/controllers/mjesto');
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"); let express = require("express");
const path = require("path"); const path = require("path");
const bodyParser = require("body-parser"); const bodyParser = require("body-parser");
const MarketAlert = require("./app/models/marketalert"); const MarketAlert = require("./app/models/marketalert");
const sendNotification = require("./app/lib/sendNotification"); const sendNotification = require("./app/lib/sendnotification");
const scrapTheItems = require("./app/lib/scrapTheItems"); const scrapTheItems = require("./app/lib/scraptheitems");
const sequelize = require("./app/models/index").sequelize; const sequelize = require("./app/models/index").sequelize;
const Twocheckout = require("2checkout-node"); const Twocheckout = require("2checkout-node");
const layout = require('express-layout'); const layout = require('express-layout');
@@ -33,7 +26,7 @@ app.use(layout());
const compression = require('compression'); const compression = require('compression');
app.use(compression()); app.use(compression());
app.get("/api/sendnotifications", async (req, res) => { app.get("/api/sendnotifications", async function(req, res) {
let marketAlerts = await MarketAlert.findAll(); let marketAlerts = await MarketAlert.findAll();
let lastDateUpdate = await Promise.all( let lastDateUpdate = await Promise.all(
@@ -66,7 +59,7 @@ app.get("/api/items/:url", async (req, res) => {
}); });
}); });
app.post("/api/marketalerts", (req, res) => { app.post("/api/marketalerts", function(req, res) {
const { email, last_date, olx_url } = req.body; const { email, last_date, olx_url } = req.body;
console.log(email, last_date, olx_url); console.log(email, last_date, olx_url);
sequelize.sync().then(() => { sequelize.sync().then(() => {
@@ -82,7 +75,7 @@ app.post("/api/marketalerts", (req, res) => {
}); });
}); });
app.post("/api/payforalert", (req, res) => { app.post("/api/payforalert", function(request, response) {
let tco = new Twocheckout({ let tco = new Twocheckout({
sellerId: "901402692", sellerId: "901402692",
privateKey: "A28DCE5F-9292-405C-8161-F84D8BB83AFC", privateKey: "A28DCE5F-9292-405C-8161-F84D8BB83AFC",
@@ -91,7 +84,7 @@ app.post("/api/payforalert", (req, res) => {
let params = { let params = {
merchantOrderId: "123", merchantOrderId: "123",
token: req.body.token, token: request.body.token,
currency: "USD", currency: "USD",
total: "2.00", total: "2.00",
billingAddr: { billingAddr: {
@@ -101,53 +94,34 @@ app.post("/api/payforalert", (req, res) => {
state: "BiH", state: "BiH",
zipCode: "71000", zipCode: "71000",
country: "BiH", country: "BiH",
email: req.body.email, email: request.body.email,
phoneNumber: "5555555555" phoneNumber: "5555555555"
} }
}; };
tco.checkout.authorize(params, function(error, data) { tco.checkout.authorize(params, function(error, data) {
if (error) { if (error) {
res.send(error.message); response.send(error.message);
} else { } else {
res.send(data.response.responseMsg); response.send(data.response.responseMsg);
} }
}); });
}); });
app.get('/', welcome); app.get('/', dobrodosli);
app.get('/vrstanekretnine/:request_id', getRealEstateTypes); app.get('/vrstanekretnine/:request_id', getVrstaNekretnine);
app.get('/vrstanekretnine', getRealEstateTypes); app.get('/vrstanekretnine', getVrstaNekretnine);
app.post('/vrstanekretnine/:request_id', postRealEstateTypes); app.post('/vrstanekretnine/:request_id', postVrstaNekretnine);
app.post('/vrstanekretnine', postRealEstateTypes); app.post('/vrstanekretnine', postVrstaNekretnine);
app.get('/grad/:request_id', getRegion); app.get('/grad/:request_id', getGrad);
app.post('/grad/:request_id', postRegion); app.post('/grad/:request_id', postGrad);
app.get('/mjesto/:request_id', getMunicipality); app.get('/mjesto/:request_id', getMjesto);
app.post('/mjesto/:request_id', postMunicipality); app.post('/mjesto/:request_id', postMjesto);
app.get('/naselje/:request_id/:municipality', getNeighborhood);
app.post('/naselje/:request_id/:municipality', postNeighborhood);
app.get('/povrsina/:request_id', getSize); app.use('/assets', express.static('./app/public'))
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}!`)); app.listen(port, () => console.log(`Example app listening on port ${port}!`));

2240
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,12 +5,7 @@
"main": "index.js", "main": "index.js",
"scripts": { "scripts": {
"test": "echo \"Error: no test specified\" && exit 1", "test": "echo \"Error: no test specified\" && exit 1",
"start": "node ./index.js", "start": "node ./index.js"
"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": { "repository": {
"type": "git", "type": "git",
@@ -37,8 +32,5 @@
"react-step-wizard": "^5.1.0", "react-step-wizard": "^5.1.0",
"sequelize": "^4.43.2", "sequelize": "^4.43.2",
"sequelize-cli": "^5.4.0" "sequelize-cli": "^5.4.0"
},
"devDependencies": {
"nodemon": "^1.19.0"
} }
} }