From f781dc1cd06a6847703588f8a468310bcbb17170 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Thu, 12 Sep 2019 06:21:49 +0200 Subject: [PATCH 01/20] create separate file for routes --- app/routes/index.js | 62 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 app/routes/index.js diff --git a/app/routes/index.js b/app/routes/index.js new file mode 100644 index 0000000..9872951 --- /dev/null +++ b/app/routes/index.js @@ -0,0 +1,62 @@ +"use strict"; + +const express = require("express"); + +const welcome = require("../controllers/welcome").getWelcome; +const { + getRealEstateTypes, + postRealEstateTypes +} = require("../controllers/realEstateTypes"); +const { getSize, postSize } = require("../controllers/sizes"); +const { getGardenSize, postGardenSize } = require("../controllers/gardenSizes"); +const { getPrice, postPrice } = require("../controllers/prices"); +const { + getQueryReview, + postQueryReview +} = require("../controllers/queryReview"); +const { + getQuerySubmit, + postQuerySubmit +} = require("../controllers/querySubmit"); +const { getGoAgain } = require("../controllers/goAgain"); +const { getLocation, postLocation } = require("../controllers/location"); +const { getUnsubscribe } = require("../controllers/unsubscribe"); +const { getRealEstates } = require("../controllers/realEstates"); +const { redirect } = require("../controllers/redirect"); + +const router = express.Router(); + +router.get("/", welcome); +router.get("/vrstanekretnine/:request_id", getRealEstateTypes); +router.get("/vrstanekretnine", getRealEstateTypes); + +router.post("/vrstanekretnine/:request_id", postRealEstateTypes); +router.post("/vrstanekretnine", postRealEstateTypes); + +router.get("/lokacija/:request_id", getLocation); +router.post("/lokacija/:request_id", postLocation); + +router.get("/povrsina/:request_id", getSize); +router.post("/povrsina/:request_id", postSize); + +router.get("/okucnica/:request_id", getGardenSize); +router.post("/okucnica/:request_id", postGardenSize); + +router.get("/cijena/:request_id", getPrice); +router.post("/cijena/:request_id", postPrice); + +router.get("/pregled/:request_id", getQueryReview); +router.post("/pregled/:request_id", postQueryReview); + +router.get("/posalji/:request_id", getQuerySubmit); +router.post("/posalji/:request_id", postQuerySubmit); + +router.get("/odjava/:request_id", getUnsubscribe); + +router.get("/ponovo", getGoAgain); + +router.get("/nekretnine/:request_id", getRealEstates); + +router.get("/redirect/:id", redirect); + +module.exports = router; -- 2.47.3 From a61816c5c2ab4686cd3b8645f35b7a7a3d913b3c Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Thu, 12 Sep 2019 06:23:03 +0200 Subject: [PATCH 02/20] move routes to separated file; stop running crawler in main loop --- index.js | 187 +++---------------------------------------------------- 1 file changed, 7 insertions(+), 180 deletions(-) diff --git a/index.js b/index.js index b08652d..c1a7b7d 100644 --- a/index.js +++ b/index.js @@ -1,45 +1,16 @@ require("dotenv").config(); -const { APP_PORT } = require("./app/config/appConfig"); -const welcome = require("./app/controllers/welcome").getWelcome; -const { - getRealEstateTypes, - postRealEstateTypes -} = require("./app/controllers/realEstateTypes"); -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 { getLocation, postLocation } = require("./app/controllers/location"); -const { getUnsubscribe } = require("./app/controllers/unsubscribe"); -const { getRealEstates } = require("./app/controllers/realEstates"); -const { redirect } = require("./app/controllers/redirect"); -const schedule = require("node-schedule"); -const crawlAll = require("./app/services/crawlerService"); -const processNotifications = require("./app/services/notificationService"); - -let express = require("express"); +const 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/models/index").sequelize; -const Twocheckout = require("2checkout-node"); const layout = require("express-layout"); +const compression = require("compression"); + +const { APP_PORT } = require("./app/config/appConfig"); +const routes = require("./app/routes"); const app = express(); + app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: true })); @@ -47,155 +18,11 @@ 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 (req, res) => { - let marketAlerts = await MarketAlert.findAll(); - - let lastDateUpdate = await Promise.all( - marketAlerts - .map(marketAlert => { - const { id, email, olx_url, last_date } = marketAlert.dataValues; - return { id, email, olx_url, last_date }; - }) - .map(sendNotification) - ); - lastDateUpdate = lastDateUpdate.filter(Boolean(dateUpdate)); - lastDateUpdate.length && - lastDateUpdate.forEach(dateUpdate => - MarketAlert.update( - { last_date: dateUpdate.date }, - { where: { id: dateUpdate.id } } - ) - ); -}); - -app.get("/api/items/:url", async (req, res) => { - let url = - "https://www.olx.ba/pretraga?" + - req.params.url + - "&sort_order=desc&sort_po=datum"; - let appts = await scrapTheItems(url); - res.json({ - last_date: appts[0] && appts[0].date, - items: appts - }); -}); - -app.post("/api/marketalerts", (req, res) => { - const { email, last_date, olx_url } = req.body; - console.log(email, last_date, olx_url); - sequelize.sync().then(() => { - MarketAlert.create({ - olx_url, - last_date, - email - }) - .then(() => { - res.json({ message: "Market Alert Created!" }); - }) - .catch(e => console.error(e)); - }); -}); - -app.post("/api/payforalert", (req, res) => { - let tco = new Twocheckout({ - sellerId: "901402692", - privateKey: "A28DCE5F-9292-405C-8161-F84D8BB83AFC", - sandbox: true - }); - - let params = { - merchantOrderId: "123", - token: req.body.token, - currency: "USD", - total: "2.00", - billingAddr: { - name: "Testing Tester", - addrLine1: "123 Test St", - city: "Sarajevo", - state: "BiH", - zipCode: "71000", - country: "BiH", - email: req.body.email, - phoneNumber: "5555555555" - } - }; - - tco.checkout.authorize(params, function(error, data) { - if (error) { - res.send(error.message); - } else { - res.send(data.response.responseMsg); - } - }); -}); - -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("/lokacija/:request_id", getLocation); -app.post("/lokacija/:request_id", postLocation); - -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("/odjava/:request_id", getUnsubscribe); - -app.get("/ponovo", getGoAgain); - -app.get("/nekretnine/:request_id", getRealEstates); - -app.get("/redirect/:id", redirect); +app.use("/", routes); app.use("/assets", express.static("./app/public")); app.listen(APP_PORT, () => console.log(`Example app listening on port ${APP_PORT}!`) ); - -//TODO: based on node-schedule package author, setInterval is better suited for this kind of the job -const rule = new schedule.RecurrenceRule(); -rule.second = 1; -schedule.scheduleJob(rule, async function() { - console.log(new Date(), "Crawler service started"); - await crawlAll(); - console.log( - new Date(), - "Crawler service finished, starting Notification service" - ); - await processNotifications(); - console.log(new Date(), "Notification service finished"); -}); - -/** - * Add flat method to Array - */ -Object.defineProperty(Array.prototype, "flat", { - value: function(depth = 1) { - return this.reduce(function(flat, toFlatten) { - return flat.concat( - Array.isArray(toFlatten) && depth > 1 - ? toFlatten.flat(depth - 1) - : toFlatten - ); - }, []); - } -}); -- 2.47.3 From 05f8cbd816396d25e5f5b6d1d4d0d1d90babf3aa Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Fri, 13 Sep 2019 08:57:41 +0200 Subject: [PATCH 03/20] move old models to oldModels folder --- app/{models => oldModels}/marketalert.js | 0 app/{models => oldModels}/realestaterequest.js | 0 2 files changed, 0 insertions(+), 0 deletions(-) rename app/{models => oldModels}/marketalert.js (100%) rename app/{models => oldModels}/realestaterequest.js (100%) diff --git a/app/models/marketalert.js b/app/oldModels/marketalert.js similarity index 100% rename from app/models/marketalert.js rename to app/oldModels/marketalert.js diff --git a/app/models/realestaterequest.js b/app/oldModels/realestaterequest.js similarity index 100% rename from app/models/realestaterequest.js rename to app/oldModels/realestaterequest.js -- 2.47.3 From 75daf55fdf730fb608ff9b5dc97a76a25ec0293f Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Fri, 13 Sep 2019 09:46:59 +0200 Subject: [PATCH 04/20] add migrations for new DB design --- .../20190912191829-add-realEstates-table.js | 64 +++++++++++++++++++ ...20190912215313-add-searchRequests-table.js | 60 +++++++++++++++++ ...12215556-add-searchRequestMatches-table.js | 45 +++++++++++++ 3 files changed, 169 insertions(+) create mode 100644 app/migrations/20190912191829-add-realEstates-table.js create mode 100644 app/migrations/20190912215313-add-searchRequests-table.js create mode 100644 app/migrations/20190912215556-add-searchRequestMatches-table.js diff --git a/app/migrations/20190912191829-add-realEstates-table.js b/app/migrations/20190912191829-add-realEstates-table.js new file mode 100644 index 0000000..a64fa1e --- /dev/null +++ b/app/migrations/20190912191829-add-realEstates-table.js @@ -0,0 +1,64 @@ +"use strict"; + +module.exports = { + up: (queryInterface, Sequelize) => { + const tableFields = { + id: { + type: Sequelize.BIGINT, + autoIncrement: true, + allowNull: false, + primaryKey: true + }, + url: { + type: Sequelize.TEXT, + allowNull: false + }, + agencyObjectId: { + type: Sequelize.TEXT, + allowNull: false + }, + originAgencyName: { + type: Sequelize.TEXT, + allowNull: false + }, + realEstateType: { + type: Sequelize.TEXT, + allowNull: false + }, + adType: { + type: Sequelize.TEXT, + allowNull: false + }, + price: Sequelize.REAL, + area: Sequelize.REAL, + gardenSize: Sequelize.REAL, + streetNumber: Sequelize.INTEGER, + streetName: Sequelize.TEXT, + locality: Sequelize.TEXT, + municipality: Sequelize.TEXT, + city: Sequelize.TEXT, + region: Sequelize.TEXT, + entity: Sequelize.TEXT, + country: Sequelize.TEXT, + locationLat: Sequelize.REAL, + locationLong: Sequelize.REAL, + lastTimeCrawled: { + type: Sequelize.DATE, + allowNull: false + }, + deleted: { + type: Sequelize.BOOLEAN, + allowNull: false + }, + sold: { + type: Sequelize.BOOLEAN, + allowNull: false + } + }; + return queryInterface.createTable("realEstates", tableFields); + }, + + down: queryInterface => { + return queryInterface.dropTable("realEstates", {}); + } +}; diff --git a/app/migrations/20190912215313-add-searchRequests-table.js b/app/migrations/20190912215313-add-searchRequests-table.js new file mode 100644 index 0000000..a0cf331 --- /dev/null +++ b/app/migrations/20190912215313-add-searchRequests-table.js @@ -0,0 +1,60 @@ +"use strict"; + +module.exports = { + up: (queryInterface, Sequelize) => { + const tableFields = { + id: { + type: Sequelize.UUID, + defaultValue: Sequelize.UUIDV4, + allowNull: false, + primaryKey: true + }, + areaToSearch: { + type: Sequelize.GEOMETRY("POINT", 4326), + allowNull: false + }, + realEstateType: { + type: Sequelize.TEXT, + allowNull: false + }, + adType: { + type: Sequelize.TEXT, + allowNull: false + }, + email: Sequelize.TEXT, + locality: Sequelize.TEXT, + municipality: Sequelize.TEXT, + city: Sequelize.TEXT, + region: Sequelize.TEXT, + entity: Sequelize.TEXT, + country: Sequelize.TEXT, + sizeMin: { + type: Sequelize.INTEGER, + allowNull: false + }, + sizeMax: { + type: Sequelize.INTEGER, + allowNull: false + }, + priceMin: { + type: Sequelize.INTEGER, + allowNull: false + }, + priceMax: { + type: Sequelize.INTEGER, + allowNull: false + }, + gardenSizeMin: Sequelize.INTEGER, + gardenSizeMax: Sequelize.INTEGER, + subscribed: { + type: Sequelize.BOOLEAN, + allowNull: false + } + }; + return queryInterface.createTable("searchRequests", tableFields); + }, + + down: queryInterface => { + return queryInterface.dropTable("searchRequests", {}); + } +}; diff --git a/app/migrations/20190912215556-add-searchRequestMatches-table.js b/app/migrations/20190912215556-add-searchRequestMatches-table.js new file mode 100644 index 0000000..24d80db --- /dev/null +++ b/app/migrations/20190912215556-add-searchRequestMatches-table.js @@ -0,0 +1,45 @@ +"use strict"; + +module.exports = { + up: (queryInterface, Sequelize) => { + const tableFields = { + id: { + type: Sequelize.BIGINT, + autoIncrement: true, + allowNull: false + }, + searchRequestId: { + type: Sequelize.UUID, + allowNull: false, + primaryKey: true, + references: { + model: "searchRequests", + key: "id" + }, + onUpdate: "CASCADE", + onDelete: "SET NULL" + }, + realEstateId: { + type: Sequelize.BIGINT, + allowNull: false, + primaryKey: true, + references: { + model: "realEstates", + key: "id" + }, + onUpdate: "CASCADE", + onDelete: "SET NULL" + }, + notified: { + type: Sequelize.BOOLEAN, + allowNull: false, + defaultValue: false + } + }; + return queryInterface.createTable("searchRequestMatches", tableFields); + }, + + down: queryInterface => { + return queryInterface.dropTable("searchRequestMatches", {}); + } +}; -- 2.47.3 From 81ecb37493a5cd27a759f4a8ffec52445e473197 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Fri, 13 Sep 2019 09:47:49 +0200 Subject: [PATCH 05/20] add models for new DB design --- app/models/realEstate.js | 65 ++++++++++++++++++++++++++++++++ app/models/searchRequest.js | 61 ++++++++++++++++++++++++++++++ app/models/searchRequestMatch.js | 47 +++++++++++++++++++++++ 3 files changed, 173 insertions(+) create mode 100644 app/models/realEstate.js create mode 100644 app/models/searchRequest.js create mode 100644 app/models/searchRequestMatch.js diff --git a/app/models/realEstate.js b/app/models/realEstate.js new file mode 100644 index 0000000..66b7a92 --- /dev/null +++ b/app/models/realEstate.js @@ -0,0 +1,65 @@ +"use strict"; + +module.exports = (sequelize, DataTypes) => { + const RealEstate = sequelize.define("RealEstate", { + id: { + type: DataTypes.BIGINT, + autoIncrement: true, + allowNull: false, + primaryKey: true + }, + url: { + type: DataTypes.TEXT, + allowNull: false + }, + agencyObjectId: { + type: DataTypes.TEXT, + allowNull: false + }, + originAgencyName: { + type: DataTypes.TEXT, + allowNull: false + }, + realEstateType: { + type: DataTypes.TEXT, + allowNull: false + }, + adType: { + type: DataTypes.TEXT, + allowNull: false + }, + price: DataTypes.REAL, + area: DataTypes.REAL, + gardenSize: DataTypes.REAL, + streetNumber: DataTypes.INTEGER, + streetName: DataTypes.TEXT, + locality: DataTypes.TEXT, + municipality: DataTypes.TEXT, + city: DataTypes.TEXT, + region: DataTypes.TEXT, + entity: DataTypes.TEXT, + country: DataTypes.TEXT, + locationLat: DataTypes.REAL, + locationLong: DataTypes.REAL, + lastTimeCrawled: { + type: DataTypes.DATE, + allowNull: false + }, + deleted: { + type: DataTypes.BOOLEAN, + allowNull: false + }, + sold: { + type: DataTypes.BOOLEAN, + allowNull: false + } + }); + + RealEstate.associate = models => { + RealEstate.belongsToMany(models.SearchRequestMatch, { + through: "SearchRequestMatch" + }); + }; + + return RealEstate; +}; diff --git a/app/models/searchRequest.js b/app/models/searchRequest.js new file mode 100644 index 0000000..9c350cd --- /dev/null +++ b/app/models/searchRequest.js @@ -0,0 +1,61 @@ +"use strict"; + +module.exports = (sequelize, DataTypes) => { + const SearchRequest = sequelize.define("SearchRequest", { + id: { + type: DataTypes.UUID, + defaultValue: DataTypes.UUIDV4, + allowNull: false, + primaryKey: true + }, + areaToSearch: { + type: DataTypes.GEOMETRY("POINT", 4326), + allowNull: false + }, + realEstateType: { + type: DataTypes.TEXT, + allowNull: false + }, + adType: { + type: DataTypes.TEXT, + allowNull: false + }, + email: DataTypes.TEXT, + locality: DataTypes.TEXT, + municipality: DataTypes.TEXT, + city: DataTypes.TEXT, + region: DataTypes.TEXT, + entity: DataTypes.TEXT, + country: DataTypes.TEXT, + sizeMin: { + type: DataTypes.INTEGER, + allowNull: false + }, + sizeMax: { + type: DataTypes.INTEGER, + allowNull: false + }, + priceMin: { + type: DataTypes.INTEGER, + allowNull: false + }, + priceMax: { + type: DataTypes.INTEGER, + allowNull: false + }, + gardenSizeMin: DataTypes.INTEGER, + gardenSizeMax: DataTypes.INTEGER, + subscribed: { + type: DataTypes.BOOLEAN, + allowNull: false + } + }); + + SearchRequest.associate = models => { + SearchRequest.belongsToMany(models.SearchRequestMatch, { + through: "SearchRequestMatch" + }); + }; + + return SearchRequest; +}; diff --git a/app/models/searchRequestMatch.js b/app/models/searchRequestMatch.js new file mode 100644 index 0000000..6f9d048 --- /dev/null +++ b/app/models/searchRequestMatch.js @@ -0,0 +1,47 @@ +"use strict"; + +module.exports = (sequelize, DataTypes) => { + const SearchRequestMatch = sequelize.define( + "SearchRequestMatch", + { + id: { + type: DataTypes.BIGINT, + autoIncrement: true, + allowNull: false + }, + searchRequestId: { + type: DataTypes.UUID, + allowNull: false, + primaryKey: true, + references: { + model: "SearchRequest", + key: "id" + } + }, + realEstateId: { + type: DataTypes.BIGINT, + allowNull: false, + primaryKey: true, + references: { + model: "RealEstate", + key: "id" + }, + onUpdate: "CASCADE", + onDelete: "SET NULL" + }, + notified: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false + } + }, + { + name: { + singular: "searchRequestMatch", + plural: "searchRequestMatches" + } + } + ); + + return SearchRequestMatch; +}; -- 2.47.3 From e32e98537c0f982a089917da3abaa18b3da2fde3 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Fri, 13 Sep 2019 10:56:46 +0200 Subject: [PATCH 06/20] add createdAt, updatedAt fields; add default values for not null fields --- .../20190912191829-add-realEstates-table.js | 12 +++++- ...20190912215313-add-searchRequests-table.js | 39 ++++++++++++++----- ...12215556-add-searchRequestMatches-table.js | 16 ++++++-- 3 files changed, 51 insertions(+), 16 deletions(-) diff --git a/app/migrations/20190912191829-add-realEstates-table.js b/app/migrations/20190912191829-add-realEstates-table.js index a64fa1e..e6b45b3 100644 --- a/app/migrations/20190912191829-add-realEstates-table.js +++ b/app/migrations/20190912191829-add-realEstates-table.js @@ -53,12 +53,20 @@ module.exports = { sold: { type: Sequelize.BOOLEAN, allowNull: false + }, + createdAt: { + type: Sequelize.DATE, + defaultValue: Sequelize.literal("NOW()") + }, + updatedAt: { + type: Sequelize.DATE, + defaultValue: Sequelize.literal("NOW()") } }; - return queryInterface.createTable("realEstates", tableFields); + return queryInterface.createTable("RealEstates", tableFields); }, down: queryInterface => { - return queryInterface.dropTable("realEstates", {}); + return queryInterface.dropTable("RealEstates", {}); } }; diff --git a/app/migrations/20190912215313-add-searchRequests-table.js b/app/migrations/20190912215313-add-searchRequests-table.js index a0cf331..ea2576d 100644 --- a/app/migrations/20190912215313-add-searchRequests-table.js +++ b/app/migrations/20190912215313-add-searchRequests-table.js @@ -10,8 +10,13 @@ module.exports = { primaryKey: true }, areaToSearch: { - type: Sequelize.GEOMETRY("POINT", 4326), - allowNull: false + type: Sequelize.GEOMETRY("POLYGON", 4326), + allowNull: false, + defaultValue: { + type: "Polygon", + coordinates: [[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]], + crs: { type: "name", properties: { name: "EPSG:4326" } } + } }, realEstateType: { type: Sequelize.TEXT, @@ -19,7 +24,8 @@ module.exports = { }, adType: { type: Sequelize.TEXT, - allowNull: false + allowNull: false, + defaultValue: "sell" }, email: Sequelize.TEXT, locality: Sequelize.TEXT, @@ -30,31 +36,44 @@ module.exports = { country: Sequelize.TEXT, sizeMin: { type: Sequelize.INTEGER, - allowNull: false + allowNull: false, + defaultValue: 0 }, sizeMax: { type: Sequelize.INTEGER, - allowNull: false + allowNull: false, + defaultValue: 0 }, priceMin: { type: Sequelize.INTEGER, - allowNull: false + allowNull: false, + defaultValue: 0 }, priceMax: { type: Sequelize.INTEGER, - allowNull: false + allowNull: false, + defaultValue: 0 }, gardenSizeMin: Sequelize.INTEGER, gardenSizeMax: Sequelize.INTEGER, subscribed: { type: Sequelize.BOOLEAN, - allowNull: false + allowNull: false, + defaultValue: true + }, + createdAt: { + type: Sequelize.DATE, + defaultValue: Sequelize.literal("NOW()") + }, + updatedAt: { + type: Sequelize.DATE, + defaultValue: Sequelize.literal("NOW()") } }; - return queryInterface.createTable("searchRequests", tableFields); + return queryInterface.createTable("SearchRequests", tableFields); }, down: queryInterface => { - return queryInterface.dropTable("searchRequests", {}); + return queryInterface.dropTable("SearchRequests", {}); } }; diff --git a/app/migrations/20190912215556-add-searchRequestMatches-table.js b/app/migrations/20190912215556-add-searchRequestMatches-table.js index 24d80db..c3e05ce 100644 --- a/app/migrations/20190912215556-add-searchRequestMatches-table.js +++ b/app/migrations/20190912215556-add-searchRequestMatches-table.js @@ -13,7 +13,7 @@ module.exports = { allowNull: false, primaryKey: true, references: { - model: "searchRequests", + model: "SearchRequests", key: "id" }, onUpdate: "CASCADE", @@ -24,7 +24,7 @@ module.exports = { allowNull: false, primaryKey: true, references: { - model: "realEstates", + model: "RealEstates", key: "id" }, onUpdate: "CASCADE", @@ -34,12 +34,20 @@ module.exports = { type: Sequelize.BOOLEAN, allowNull: false, defaultValue: false + }, + createdAt: { + type: Sequelize.DATE, + defaultValue: Sequelize.literal("NOW()") + }, + updatedAt: { + type: Sequelize.DATE, + defaultValue: Sequelize.literal("NOW()") } }; - return queryInterface.createTable("searchRequestMatches", tableFields); + return queryInterface.createTable("SearchRequestMatches", tableFields); }, down: queryInterface => { - return queryInterface.dropTable("searchRequestMatches", {}); + return queryInterface.dropTable("SearchRequestMatches", {}); } }; -- 2.47.3 From b7d147b0a68055e2da02021945baec7bd40228b4 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Fri, 13 Sep 2019 10:57:43 +0200 Subject: [PATCH 07/20] fix SearchRequest model, add default values for not null fields --- app/models/searchRequest.js | 25 ++++++++++++++++++------- 1 file changed, 18 insertions(+), 7 deletions(-) diff --git a/app/models/searchRequest.js b/app/models/searchRequest.js index 9c350cd..1670bde 100644 --- a/app/models/searchRequest.js +++ b/app/models/searchRequest.js @@ -9,8 +9,13 @@ module.exports = (sequelize, DataTypes) => { primaryKey: true }, areaToSearch: { - type: DataTypes.GEOMETRY("POINT", 4326), - allowNull: false + type: DataTypes.GEOMETRY("POLYGON", 4326), + allowNull: false, + defaultValue: { + type: "Polygon", + coordinates: [[[0, 0], [0, 0], [0, 0], [0, 0], [0, 0]]], + crs: { type: "name", properties: { name: "EPSG:4326" } } + } }, realEstateType: { type: DataTypes.TEXT, @@ -18,7 +23,8 @@ module.exports = (sequelize, DataTypes) => { }, adType: { type: DataTypes.TEXT, - allowNull: false + allowNull: false, + defaultValue: "sell" }, email: DataTypes.TEXT, locality: DataTypes.TEXT, @@ -29,24 +35,29 @@ module.exports = (sequelize, DataTypes) => { country: DataTypes.TEXT, sizeMin: { type: DataTypes.INTEGER, - allowNull: false + allowNull: false, + defaultValue: 0 }, sizeMax: { type: DataTypes.INTEGER, - allowNull: false + allowNull: false, + defaultValue: 0 }, priceMin: { type: DataTypes.INTEGER, - allowNull: false + allowNull: false, + defaultValue: 0 }, priceMax: { type: DataTypes.INTEGER, - allowNull: false + allowNull: false, + defaultValue: 0 }, gardenSizeMin: DataTypes.INTEGER, gardenSizeMax: DataTypes.INTEGER, subscribed: { type: DataTypes.BOOLEAN, + defaultValue: true, allowNull: false } }); -- 2.47.3 From 77d9669ad6c9cce1f6c311b72ac659599489eb68 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Fri, 13 Sep 2019 11:06:03 +0200 Subject: [PATCH 08/20] modify URL helper for getting searchRequestId --- app/helpers/url.js | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/app/helpers/url.js b/app/helpers/url.js index 10512ff..9ed771f 100644 --- a/app/helpers/url.js +++ b/app/helpers/url.js @@ -1,11 +1,12 @@ -const db = require("../models/index"); +const { getSearchRequest } = require("./db/searchRequest"); -const currentRERequest = async req => { - const uniqueId = req.params["request_id"]; - if (!uniqueId) return null; +const currentSearchRequest = async req => { + const searchRequestId = + req && req.params ? req.params["searchRequestId"] : null; + if (!searchRequestId) return null; - return await db.RealEstateRequest.findOne({ where: { uniqueId } }); + return await getSearchRequest(searchRequestId); }; module.exports = { - currentRERequest + currentSearchRequest }; -- 2.47.3 From 3cbd79dcc9aa4b3faedd514facaa1d927bdaa84a Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Fri, 13 Sep 2019 11:06:58 +0200 Subject: [PATCH 09/20] create SearchRequest db helper model --- app/helpers/db/searchRequest.js | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 app/helpers/db/searchRequest.js diff --git a/app/helpers/db/searchRequest.js b/app/helpers/db/searchRequest.js new file mode 100644 index 0000000..ebb2a18 --- /dev/null +++ b/app/helpers/db/searchRequest.js @@ -0,0 +1,15 @@ +"use strict"; +const db = require("../../models/index"); + +const getSearchRequest = async searchRequestId => { + return await db.SearchRequest.findOne({ where: searchRequestId }); +}; + +const createSearchRequest = async (searchRequestFields = {}) => { + return await db.SearchRequest.create(searchRequestFields); +}; + +module.exports = { + getSearchRequest, + createSearchRequest +}; -- 2.47.3 From 1a874d4d888fa4023ab1b0355bb0150bc48a107f Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Fri, 13 Sep 2019 11:08:45 +0200 Subject: [PATCH 10/20] adapt first step of search request to new DB design --- app/controllers/realEstateTypes.js | 42 ++++++++++++++++-------------- app/routes/index.js | 6 ++--- app/views/realEstateType.ejs | 6 ++--- 3 files changed, 29 insertions(+), 25 deletions(-) diff --git a/app/controllers/realEstateTypes.js b/app/controllers/realEstateTypes.js index 853b693..c02d2b9 100644 --- a/app/controllers/realEstateTypes.js +++ b/app/controllers/realEstateTypes.js @@ -1,6 +1,6 @@ -const db = require("../models/index"); +const { currentSearchRequest } = require("../helpers/url"); +const { createSearchRequest } = require("../helpers/db/searchRequest"); -const { currentRERequest } = require("../helpers/url"); const { realEstateTypes, getRealEstateTypeEnum } = require("../helpers/enums"); const getRealEstateTypes = (req, res) => { @@ -9,31 +9,35 @@ const getRealEstateTypes = (req, res) => { }; const postRealEstateTypes = async (req, res) => { - const request = await currentRERequest(req); + const searchRequest = await currentSearchRequest(req); + + //TODO: check if selected real estate type is valid + const selectedRealEstateType = req.body.realEstateType || null; const nextStepPage = req.query.nextStep || "lokacija"; - if (request && request.uniqueId) { - const nextStepUrl = `/${nextStepPage}/${request.uniqueId}`; - request.realEstateType = req.body.realestatetype; - if (!getRealEstateTypeEnum(request.realEstateType).hasGardenSize) { - request.gardenSize = null; + let nextStepUrl = ""; + if (searchRequest && searchRequest.id) { + nextStepUrl = `/${nextStepPage}/${searchRequest.id}`; + searchRequest.realEstateType = selectedRealEstateType; + if (!getRealEstateTypeEnum(selectedRealEstateType).hasGardenSize) { + searchRequest.gardenSizeMin = null; + searchRequest.gardenSizeMax = 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); + try { + const newSearchRequest = await createSearchRequest({ + realEstateType: selectedRealEstateType }); + + nextStepUrl = `/${nextStepPage}/${newSearchRequest.id}`; + } catch (error) { + console.log(error); + nextStepUrl = `/`; + } } + res.redirect(nextStepUrl); }; module.exports = { diff --git a/app/routes/index.js b/app/routes/index.js index 9872951..bba7973 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -27,10 +27,10 @@ const { redirect } = require("../controllers/redirect"); const router = express.Router(); router.get("/", welcome); -router.get("/vrstanekretnine/:request_id", getRealEstateTypes); -router.get("/vrstanekretnine", getRealEstateTypes); -router.post("/vrstanekretnine/:request_id", postRealEstateTypes); +router.get("/vrstanekretnine/:searchRequestId", getRealEstateTypes); +router.get("/vrstanekretnine", getRealEstateTypes); +router.post("/vrstanekretnine/:searchRequestId", postRealEstateTypes); router.post("/vrstanekretnine", postRealEstateTypes); router.get("/lokacija/:request_id", getLocation); diff --git a/app/views/realEstateType.ejs b/app/views/realEstateType.ejs index eac9357..cf14b03 100644 --- a/app/views/realEstateType.ejs +++ b/app/views/realEstateType.ejs @@ -1,5 +1,5 @@ -<% include partials/navBar %> +<% include partials/navBar %>
@@ -14,13 +14,13 @@ <% } %> - +
-- 2.47.3 From 8f09d4f2279695bcd74c65b7b3aab71d5ff61e94 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Fri, 13 Sep 2019 11:11:28 +0200 Subject: [PATCH 11/20] change url param case from snake_case to camelCase --- app/routes/index.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/app/routes/index.js b/app/routes/index.js index bba7973..6940323 100644 --- a/app/routes/index.js +++ b/app/routes/index.js @@ -33,29 +33,29 @@ router.get("/vrstanekretnine", getRealEstateTypes); router.post("/vrstanekretnine/:searchRequestId", postRealEstateTypes); router.post("/vrstanekretnine", postRealEstateTypes); -router.get("/lokacija/:request_id", getLocation); -router.post("/lokacija/:request_id", postLocation); +router.get("/lokacija/:searchRequestId", getLocation); +router.post("/lokacija/:searchRequestId", postLocation); -router.get("/povrsina/:request_id", getSize); -router.post("/povrsina/:request_id", postSize); +router.get("/povrsina/:searchRequestId", getSize); +router.post("/povrsina/:searchRequestId", postSize); -router.get("/okucnica/:request_id", getGardenSize); -router.post("/okucnica/:request_id", postGardenSize); +router.get("/okucnica/:searchRequestId", getGardenSize); +router.post("/okucnica/:searchRequestId", postGardenSize); -router.get("/cijena/:request_id", getPrice); -router.post("/cijena/:request_id", postPrice); +router.get("/cijena/:searchRequestId", getPrice); +router.post("/cijena/:searchRequestId", postPrice); -router.get("/pregled/:request_id", getQueryReview); -router.post("/pregled/:request_id", postQueryReview); +router.get("/pregled/:searchRequestId", getQueryReview); +router.post("/pregled/:searchRequestId", postQueryReview); -router.get("/posalji/:request_id", getQuerySubmit); -router.post("/posalji/:request_id", postQuerySubmit); +router.get("/posalji/:searchRequestId", getQuerySubmit); +router.post("/posalji/:searchRequestId", postQuerySubmit); -router.get("/odjava/:request_id", getUnsubscribe); +router.get("/odjava/:searchRequestId", getUnsubscribe); router.get("/ponovo", getGoAgain); -router.get("/nekretnine/:request_id", getRealEstates); +router.get("/nekretnine/:searchRequestId", getRealEstates); router.get("/redirect/:id", redirect); -- 2.47.3 From f258800fd86a506cd3561e1cbc16ffc67091d967 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Fri, 13 Sep 2019 12:33:16 +0200 Subject: [PATCH 12/20] change defaultValue for subscribed to false --- app/migrations/20190912215313-add-searchRequests-table.js | 2 +- app/models/searchRequest.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/app/migrations/20190912215313-add-searchRequests-table.js b/app/migrations/20190912215313-add-searchRequests-table.js index ea2576d..506cb1d 100644 --- a/app/migrations/20190912215313-add-searchRequests-table.js +++ b/app/migrations/20190912215313-add-searchRequests-table.js @@ -59,7 +59,7 @@ module.exports = { subscribed: { type: Sequelize.BOOLEAN, allowNull: false, - defaultValue: true + defaultValue: false }, createdAt: { type: Sequelize.DATE, diff --git a/app/models/searchRequest.js b/app/models/searchRequest.js index 1670bde..5e16c9e 100644 --- a/app/models/searchRequest.js +++ b/app/models/searchRequest.js @@ -57,7 +57,7 @@ module.exports = (sequelize, DataTypes) => { gardenSizeMax: DataTypes.INTEGER, subscribed: { type: DataTypes.BOOLEAN, - defaultValue: true, + defaultValue: false, allowNull: false } }); -- 2.47.3 From fbf7aabe93aa0042fceb708ed1783d55a4806e06 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Fri, 13 Sep 2019 12:35:23 +0200 Subject: [PATCH 13/20] use sequelize findByPk method for getting SearchRequest object --- app/helpers/db/searchRequest.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/helpers/db/searchRequest.js b/app/helpers/db/searchRequest.js index ebb2a18..e4bac0c 100644 --- a/app/helpers/db/searchRequest.js +++ b/app/helpers/db/searchRequest.js @@ -2,7 +2,7 @@ const db = require("../../models/index"); const getSearchRequest = async searchRequestId => { - return await db.SearchRequest.findOne({ where: searchRequestId }); + return await db.SearchRequest.findByPk(searchRequestId); }; const createSearchRequest = async (searchRequestFields = {}) => { -- 2.47.3 From db58d1e98b50e72b8f846235991d63c22112d791 Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Fri, 13 Sep 2019 12:37:53 +0200 Subject: [PATCH 14/20] adapt location step for new DB design --- app/controllers/location.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/app/controllers/location.js b/app/controllers/location.js index 185bee9..02076db 100644 --- a/app/controllers/location.js +++ b/app/controllers/location.js @@ -1,4 +1,4 @@ -const { currentRERequest } = require("../helpers/url"); +const { currentSearchRequest } = require("../helpers/url"); const getLocation = async (req, res) => { const title = "U kojem naselju tražite nekretninu?"; @@ -11,21 +11,22 @@ const getLocation = async (req, res) => { }; const postLocation = async (req, res) => { - let request = await currentRERequest(req); + let searchRequest = await currentSearchRequest(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.locationInput = + const locationInputValue = req.body.locationInput && req.body.locationInput.length > 0 ? req.body.locationInput : null; - request.boundingBox = { + searchRequest.areaToSearch = { type: "Polygon", - coordinates: [[northWest, northEast, southEast, southWest, northWest]] + coordinates: [[northWest, northEast, southEast, southWest, northWest]], + crs: { type: "name", properties: { name: "EPSG:4326" } } }; let locationInputData; @@ -37,10 +38,10 @@ const postLocation = async (req, res) => { } } - await request.save(); + await searchRequest.save(); const nextStepPage = req.query.nextStep || "povrsina"; - const nextStepUrl = `/${nextStepPage}/${request.uniqueId}`; + const nextStepUrl = `/${nextStepPage}/${searchRequest.id}`; res.redirect(nextStepUrl); }; -- 2.47.3 From ff68e96f4f982bc8b13a1ce7e0f6fabd28c6211a Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Fri, 13 Sep 2019 13:58:58 +0200 Subject: [PATCH 15/20] adapt size step for new DB design --- app/controllers/sizes.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/app/controllers/sizes.js b/app/controllers/sizes.js index b3898c4..8c2c843 100644 --- a/app/controllers/sizes.js +++ b/app/controllers/sizes.js @@ -1,4 +1,4 @@ -const { currentRERequest } = require("../helpers/url"); +const { currentSearchRequest } = require("../helpers/url"); const { sizes, getRealEstateTypeEnum } = require("../helpers/enums"); const getSize = (req, res) => { @@ -22,17 +22,21 @@ const getSize = (req, res) => { }; const postSize = async (req, res) => { - const request = await currentRERequest(req); - - const realEstateType = getRealEstateTypeEnum(request.realEstateType); + const searchRequest = await currentSearchRequest(req); + const realEstateType = getRealEstateTypeEnum(searchRequest.realEstateType); + const sizeMin = req.body.from || 0; + const sizeMax = req.body.to || 0; + //TODO: Validation, check if real estate type is valid, ... const nextStep = realEstateType && realEstateType.hasGardenSize ? "okucnica" : "cijena"; + const nextStepPage = req.query.nextStep || nextStep; - const nextStepUrl = `/${nextStepPage}/${request.uniqueId}`; - request.sizeMin = req.body.from; - request.sizeMax = req.body.to; - await request.save(); + const nextStepUrl = `/${nextStepPage}/${searchRequest.id}`; + + searchRequest.sizeMin = sizeMin; + searchRequest.sizeMax = sizeMax; + await searchRequest.save(); res.redirect(nextStepUrl); }; -- 2.47.3 From e26c2b6e8da6f7910db71e5da00b89469494a4ca Mon Sep 17 00:00:00 2001 From: Bilal Catic Date: Fri, 13 Sep 2019 14:17:46 +0200 Subject: [PATCH 16/20] adapt price and query review steps for new DB design --- app/controllers/prices.js | 15 ++++++---- app/controllers/queryReview.js | 51 ++++++++++++++++------------------ app/views/queryReview.ejs | 4 +-- 3 files changed, 35 insertions(+), 35 deletions(-) diff --git a/app/controllers/prices.js b/app/controllers/prices.js index 2ae5720..91ae0ae 100644 --- a/app/controllers/prices.js +++ b/app/controllers/prices.js @@ -1,4 +1,4 @@ -const { currentRERequest } = require("../helpers/url"); +const { currentSearchRequest } = require("../helpers/url"); const getPrice = (req, res) => { const title = "Koja Vam okvirna cijena odgovara ?"; @@ -22,14 +22,17 @@ const getPrice = (req, res) => { }; const postPrice = async (req, res) => { - const request = await currentRERequest(req); + const searchRequest = await currentSearchRequest(req); const nextStepPage = req.query.nextStep || "pregled"; - const nextStepUrl = `/${nextStepPage}/${request.uniqueId}`; + const nextStepUrl = `/${nextStepPage}/${searchRequest.id}`; + const priceMin = req.body.from || 0; + const priceMax = req.body.to || 0; + //TODO: price validation - request.priceMin = req.body.from; - request.priceMax = req.body.to; - await request.save(); + searchRequest.priceMin = priceMin; + searchRequest.priceMax = priceMax; + await searchRequest.save(); res.redirect(nextStepUrl); }; diff --git a/app/controllers/queryReview.js b/app/controllers/queryReview.js index e0ec70b..ac4bd1f 100644 --- a/app/controllers/queryReview.js +++ b/app/controllers/queryReview.js @@ -1,4 +1,4 @@ -const { currentRERequest } = require("../helpers/url"); +const { currentSearchRequest } = require("../helpers/url"); const { realEstateTypes, getEnumTypeTitle, @@ -7,23 +7,23 @@ const { const getQueryReview = async (req, res) => { const title = "Da li je ovo to što ste tražili ?"; - const request = await currentRERequest(req); + const searchRequest = await currentSearchRequest(req); const nextStep = req.query.nextStep; - if (!request || !request.dataValues) { + if (!searchRequest || !searchRequest.dataValues) { return null; } const { + id, realEstateType, sizeMin, sizeMax, gardenSizeMin, gardenSizeMax, priceMin, - priceMax, - locationInput - } = request.dataValues; + priceMax + } = searchRequest.dataValues; const realEstateTypeObject = getRealEstateTypeEnum(realEstateType); const enableGardenSizeEdit = realEstateTypeObject @@ -32,58 +32,55 @@ const getQueryReview = async (req, res) => { const realEstateTypeTitle = realEstateType ? getEnumTypeTitle(realEstateTypes, realEstateType) - : null; + : "-"; - const locationTitle = locationInput ? locationInput : "-"; + const locationTitle = "Location description - PLACEHOLDER"; + const sizeTitle = sizeMin && sizeMax ? `${sizeMin} - ${sizeMax} m2` : "-"; + const gardenSizeTitle = + enableGardenSizeEdit && gardenSizeMin && gardenSizeMax + ? `${gardenSizeMin} - ${gardenSizeMax} m2` + : "-"; + const priceTitle = + priceMin && priceMax ? `${priceMin} - ${priceMax} KM` : "-"; - const sizeTitle = sizeMin ? sizeMin + "-" + sizeMax + " m2" : null; - const gardenSizeTitle = gardenSizeMin - ? gardenSizeMin + "-" + gardenSizeMax + " m2" - : null; - const priceTitle = priceMin ? priceMin + "-" + priceMax + " KM" : null; - - const uniqueId = request.dataValues.uniqueId - ? request.dataValues.uniqueId - : ""; - - const queryData = [ + const queryReviewData = [ { id: "realEstateType", title: realEstateTypeTitle, - url: `/vrstanekretnine/${uniqueId}?nextStep=pregled` + url: `/vrstanekretnine/${id}?nextStep=pregled` }, { id: "location", title: locationTitle, - url: `/lokacija/${uniqueId}?nextStep=pregled` + url: `/lokacija/${id}?nextStep=pregled` }, { id: "size", title: sizeTitle, - url: `/povrsina/${uniqueId}?nextStep=pregled` + url: `/povrsina/${id}?nextStep=pregled` }, { id: "gardenSize", title: gardenSizeTitle, - url: enableGardenSizeEdit ? `/okucnica/${uniqueId}?nextStep=pregled` : "" + url: enableGardenSizeEdit ? `/okucnica/${id}?nextStep=pregled` : "" }, { id: "price", title: priceTitle, - url: `/cijena/${uniqueId}?nextStep=pregled` + url: `/cijena/${id}?nextStep=pregled` } ]; res.render("queryReview", { nextStep, - queryData, + queryReviewData, title }); }; const postQueryReview = async (req, res) => { - const request = await currentRERequest(req); - const nextStep = req.query.nextStep || `/posalji/${request.uniqueId}`; + const searchRequest = await currentSearchRequest(req); + const nextStep = req.query.nextStep || `/posalji/${searchRequest.id}`; res.redirect(nextStep); }; diff --git a/app/views/queryReview.ejs b/app/views/queryReview.ejs index e8105b4..87f99a8 100644 --- a/app/views/queryReview.ejs +++ b/app/views/queryReview.ejs @@ -1,10 +1,10 @@ -<% include partials/navBar %> +<% include partials/navBar %>