Refactor #5
41
README.md
41
README.md
@@ -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
|
||||
|
||||
@@ -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
|
||||
};
|
||||
24
app/controllers/municipalities.js
Normal file
24
app/controllers/municipalities.js
Normal 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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
25
app/controllers/regions.js
Normal file
25
app/controllers/regions.js
Normal 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
|
||||
};
|
||||
@@ -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
@@ -6,8 +6,8 @@ const currentRERequest = async (req) => {
|
||||
|
||||
const request = await db.RealEstateRequest.findOne({ where: {uniqueId} });
|
||||
return request;
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
currentRERequest
|
||||
}
|
||||
};
|
||||
|
||||
@@ -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;
|
||||
@@ -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 = [];
|
||||
@@ -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)) };
|
||||
}
|
||||
}
|
||||
19
app/migrations/20190516180226-rename-place-column.js
Normal file
19
app/migrations/20190516180226-rename-place-column.js
Normal 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'
|
||||
);
|
||||
}
|
||||
};
|
||||
19
app/migrations/20190516222240-rename-city-column.js
Normal file
19
app/migrations/20190516222240-rename-city-column.js
Normal 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'
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
|
||||
31
app/views/municipality.ejs
Normal file
31
app/views/municipality.ejs
Normal 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>
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
@@ -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>
|
||||
36
index.js
36
index.js
@@ -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
2480
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
10
package.json
10
package.json
@@ -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"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user