Refactor #5

Merged
bilal.catic merged 13 commits from refactor into master 2019-05-17 09:46:45 +02:00
22 changed files with 2787 additions and 485 deletions

View File

@@ -1,17 +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
Create postgres docker image
docker build -t marketalerts .
## Setup
Run postgres image with:
docker run --name pg_test -d -p 5432:5432 marketalerts
### Setup with npm commands
Run migrations in app folder
npx sequelize db:migrate
1. Run setup script
`npm run setup`
this will create and run postgres image and then execute migrations
Run app with:
$ npm install
$ npm start
2. Install packages
`npm install`
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

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

View File

@@ -0,0 +1,24 @@
const { currentRERequest } = require('../helpers/url');
const { getMunicipalitiesForRegion } = require('../helpers/codes');
const getMunicipality = async (req,res) => {
let request = await currentRERequest(req);
const municipalities = getMunicipalitiesForRegion(request.region);
const nextStep = req.query.nextStep || '/';
res.render('municipality', {
nextStep,
municipalities
});
};
const postMunicipality = async (req, res) => {
let request = await currentRERequest(req);
request.municipality = req.body.municipality;
await request.save();
res.send("Result is " + JSON.stringify(request));
};
module.exports = {
getMunicipality,
postMunicipality
};

View File

@@ -1,25 +0,0 @@
const db = require('../models/index');
const { currentRERequest } = require('../helpers/url');
const { places } = require('../helpers/codes');
const getNeighborhood = async (req,res) => {
let request = await currentRERequest(req);
const neighborhoods = places(request.city);
const nextStep = req.query.nextStep || '/';
res.render('neighborhood', {
nextStep,
neighborhoods
});
}
const postgNeighborhood = async (req, res) => {
let request = await currentRERequest(req);
request.neighborhood = req.body.neighborhood;
await request.save();
res.send("Result is " + JSON.stringify(request));
}
module.exports = {
getNeighborhood,
postgNeighborhood
};

View File

@@ -1,19 +1,18 @@
const db = require('../models/index');
const realEstateTypes = [
{ ime: "Kuća", id: "kuca" },
{ ime: "Stan", id: "stan" },
{ ime: "Kuća", id: "kuca" },
{ ime: "Stan", id: "stan" },
{ ime: "Vikendica", id: "vikendica" }
];
const getRealEstateTypes = (req,res) => {
const nextStep = req.query.nextStep;
res.render('real_estate_type', {
nextStep,
realEstateTypes: realEstateTypes
const nextStep = req.query.nextStep;
res.render('realEstateType', {
nextStep,
realEstateTypes: realEstateTypes
});
}
};
const postRealEstateTypes = (req, res) => {
let nextStep = req.query.nextStep;
@@ -21,14 +20,13 @@ const postRealEstateTypes = (req, res) => {
realEstateType: req.body.realestatetype
}).then( (result) => {
nextStep = nextStep || `/grad/${result.uniqueId}`;
res.redirect(nextStep);
res.redirect(nextStep);
}).catch( (e) => {
res.send(e);
});
}
});
};
module.exports = {
module.exports = {
getRealEstateTypes,
postRealEstateTypes
};

View File

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

View File

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

File diff suppressed because it is too large Load Diff

View File

@@ -6,8 +6,8 @@ const currentRERequest = async (req) => {
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,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

@@ -10,9 +10,9 @@ module.exports = (sequelize, DataTypes) => {
type: DataTypes.ENUM,
values: ['kuca','stan','vikendica','plac','poslovni_prostor','apartman','garaza']
},
email: DataTypes.STRING,
city: DataTypes.STRING,
place: DataTypes.STRING,
email: DataTypes.STRING,
region: DataTypes.STRING,
municipality: DataTypes.STRING,
}, {});
RealEstateRequest.associate = function(models) {
// associations can be defined here

View File

@@ -0,0 +1,31 @@
<!--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 id="<%= municipality.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>
$(document).ready( () => {
$(".collection-item").click( (e) => {
const clickedId = $(e.target).attr("id");
$("#municipality").val(clickedId);
$("#form-municipality").submit();
});
});
</script>

View File

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

View File

@@ -1,3 +1,4 @@
<!--suppress HtmlUnknownAnchorTarget -->
<div class="row center-align">
<h2>Koju nekretninu tražite?</h2>
</div>
@@ -5,8 +6,8 @@
<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" >
<% for(const realEstatetype of realEstateTypes) { %>
<li class="collection-item" >
<div id="<%= realEstatetype.id %>" ><%= realEstatetype.ime %>
<a href="#!" class="secondary-content">
<i class="material-icons">send</i>
@@ -22,9 +23,9 @@
<script>
$(document).ready( () => {
$(".collection-item").click( (e) => {
const clickedId = $(e.target).attr("id");
const clickedId = $(e.target).attr("id");
$("#realestatetype").val(clickedId);
$("#form-real-estate-type").submit();
$("#form-real-estate-type").submit();
});
});
</script>

View File

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

View File

@@ -1,14 +1,14 @@
const welcome = require('./app/controllers/welcome').getWelcome;
const { getRealEstateTypes, postRealEstateTypes} = require('./app/controllers/real_estate_types');
const { getCity, postCity } = require('./app/controllers/city');
const { getNeighborhood, postgNeighborhood } = require('./app/controllers/neighborhoods');
const { getRealEstateTypes, postRealEstateTypes} = require('./app/controllers/realEstateTypes');
const { getRegion, postRegion } = require('./app/controllers/regions');
const { getMunicipality, postMunicipality } = require('./app/controllers/municipalities');
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 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');
@@ -26,7 +26,7 @@ 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(
@@ -59,7 +59,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(() => {
@@ -75,7 +75,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",
@@ -84,7 +84,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: {
@@ -94,34 +94,34 @@ 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('/', welcome);
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', getCity);
app.post('/grad/:request_id', postCity);
app.get('/grad/:request_id', getRegion);
app.post('/grad/:request_id', postRegion);
app.get('/mjesto/:request_id', getNeighborhood);
app.post('/mjesto/:request_id', postgNeighborhood);
app.get('/mjesto/:request_id', getMunicipality);
app.post('/mjesto/:request_id', postMunicipality);
app.use('/assets', express.static('./app/public'))
app.use('/assets', express.static('./app/public'));
app.listen(port, () => console.log(`Example app listening on port ${port}!`));

2480
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -5,7 +5,12 @@
"main": "index.js",
"scripts": {
"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 --name pg_marketalerts -d -p 5432:5432 marketalerts && npm run migrate",
"docker-start" : "docker start pg_marketalerts",
"docker-stop" : "docker stop pg_marketalerts"
},
"repository": {
"type": "git",
@@ -32,5 +37,8 @@
"react-step-wizard": "^5.1.0",
"sequelize": "^4.43.2",
"sequelize-cli": "^5.4.0"
},
"devDependencies": {
"nodemon": "^1.19.0"
}
}