diff --git a/app/controllers/gardenSizes.js b/app/controllers/gardenSizes.js index 83a8fe1..4fb5c8e 100644 --- a/app/controllers/gardenSizes.js +++ b/app/controllers/gardenSizes.js @@ -1,4 +1,4 @@ -const { currentRERequest } = require("../helpers/url"); +const { currentSearchRequest } = require("../helpers/url"); const { getRealEstateTypeEnum } = require("../helpers/enums"); const getGardenSize = (req, res) => { @@ -23,16 +23,20 @@ const getGardenSize = (req, res) => { }; const postGardenSize = async (req, res) => { - const request = await currentRERequest(req); + const searchRequest = await currentSearchRequest(req); const nextStepPage = req.query.nextStep || "cijena"; - const nextStepUrl = `/${nextStepPage}/${request.uniqueId}`; + const nextStepUrl = `/${nextStepPage}/${searchRequest.id}`; - const realEstateType = getRealEstateTypeEnum(request.realEstateType); + const realEstateType = getRealEstateTypeEnum(searchRequest.realEstateType); if (realEstateType && realEstateType.hasGardenSize) { - request.gardenSizeMin = req.body.from; - request.gardenSizeMax = req.body.to; - await request.save(); + const gardenSizeMin = req.body.from || 0; + const gardenSizeMax = req.body.to || 0; + //TODO: Validate input + + searchRequest.gardenSizeMin = gardenSizeMin; + searchRequest.gardenSizeMax = gardenSizeMax; + await searchRequest.save(); } res.redirect(nextStepUrl); 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); }; 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/controllers/querySubmit.js b/app/controllers/querySubmit.js index de48eba..1768575 100644 --- a/app/controllers/querySubmit.js +++ b/app/controllers/querySubmit.js @@ -1,4 +1,4 @@ -const { currentRERequest } = require("../helpers/url"); +const { currentSearchRequest } = require("../helpers/url"); const { isValidEmail } = require("../helpers/email"); const { sendTemplatedEmail } = require("../helpers/awsEmail"); @@ -15,21 +15,13 @@ const getQuerySubmit = async (req, res) => { }; const postQuerySubmit = async (req, res) => { - const request = await currentRERequest(req); + const searchRequest = await currentSearchRequest(req); const nextStep = req.query.nextStep || "/ponovo"; const emailInput = req.body.email; const emailConfirmInput = req.body.confirm; let error = "Greška ! Unesite validan email"; - if (!isValidEmail(emailInput) || !isValidEmail(emailConfirmInput)) { - error = "Greška ! Unesite validan email"; - res.render("querySubmit", { - error - }); - return; - } - if (emailInput !== emailConfirmInput) { error = "Greška ! Unešeni emailovi nisu isti"; res.render("querySubmit", { @@ -38,10 +30,19 @@ const postQuerySubmit = async (req, res) => { return; } - request.email = req.body.email; - request.subscribed = true; - await request.save(); - sendTemplatedEmail(req.body.email, request); + if (!isValidEmail(emailInput)) { + error = "Greška ! Unesite validan email"; + res.render("querySubmit", { + error + }); + return; + } + + searchRequest.email = emailInput; + searchRequest.subscribed = true; + await searchRequest.save(); + + sendTemplatedEmail(emailInput, searchRequest); res.redirect(nextStep); }; diff --git a/app/controllers/realEstateTypes.js b/app/controllers/realEstateTypes.js index 853b693..7b84247 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,32 @@ 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; - } - await request.save(); + let nextStepUrl = ""; + if (searchRequest && searchRequest.id) { + nextStepUrl = `/${nextStepPage}/${searchRequest.id}`; + searchRequest.realEstateType = selectedRealEstateType; - res.redirect(nextStepUrl); + await searchRequest.save(); } 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/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); }; diff --git a/app/controllers/unsubscribe.js b/app/controllers/unsubscribe.js index 0199fa7..a6c400f 100644 --- a/app/controllers/unsubscribe.js +++ b/app/controllers/unsubscribe.js @@ -1,10 +1,10 @@ -const { currentRERequest } = require("../helpers/url"); +const { currentSearchRequest } = require("../helpers/url"); const getUnsubscribe = async (req, res) => { const title = "Uspješno ste se odjavili"; - const request = await currentRERequest(req); - request.subscribed = false; - await request.save(); + const searchRequest = await currentSearchRequest(req); + searchRequest.subscribed = false; + await searchRequest.save(); res.render("unsubscribe", { nextStep: "/vrstanekretnine", title }); }; diff --git a/app/helpers/awsEmail.js b/app/helpers/awsEmail.js index 4dbb22a..bb1c80c 100644 --- a/app/helpers/awsEmail.js +++ b/app/helpers/awsEmail.js @@ -69,8 +69,8 @@ const getGreetingsEmailHTML = realEstateRequest => {
-
Ako želis prestati dobijati obavještenja za ovu pretragu klikni ${APP_URL}/odjava/${realEstateRequest.uniqueId}
-
Ako želiš promijeniti uslove pretrage klikni ${APP_URL}/pregled/${realEstateRequest.uniqueId}
+
Ako želis prestati dobijati obavještenja za ovu pretragu klikni ${APP_URL}/odjava/${realEstateRequest.id}
+
Ako želiš promijeniti uslove pretrage klikni ${APP_URL}/pregled/${realEstateRequest.id}

Tvoj, Javimi tim.

`; @@ -91,9 +91,9 @@ const getGreetingsEmailTextVersion = realEstateRequest => { ${gardenSize}\n Cijena od ${realEstateRequest.priceMin} do ${realEstateRequest.priceMax} \n Ako želis prestati dobijati obavještenja za ovu pretragu klikni - ${APP_URL}/odjava/${realEstateRequest.uniqueId}\n + ${APP_URL}/odjava/${realEstateRequest.id}\n Ako želiš promijeniti uslove pretrage klikni - ${APP_URL}/odpregled/${realEstateRequest.uniqueId}\n + ${APP_URL}/odpregled/${realEstateRequest.id}\n Tvoj,\n Javimi tim`; }; @@ -109,7 +109,7 @@ const sendBulkEmail = async marketAlerts => { const RERequestUuidsArray = Array.from(new Set(RERequestUuidsMaped)); const RERequestUuids = RERequestUuidsArray.map(marketAlert => { - return { uniqueId: marketAlert }; + return { id: marketAlert }; }); const RERequests = await allRERequestByUiid(RERequestUuids); @@ -117,13 +117,11 @@ const sendBulkEmail = async marketAlerts => { RERequests.forEach(RERequest => { var formatedRequest = {}; - formatedRequest[RERequest.uniqueId] = requestDataValues[ - RERequest.uniqueId - ] = { + formatedRequest[RERequest.id] = requestDataValues[RERequest.id] = { realEstateType: RERequest.realEstateType, region: RERequest.region, municipality: RERequest.municipality, - requestUrl: `${APP_URL}/nekretnine/${RERequest.uniqueId}` + requestUrl: `${APP_URL}/nekretnine/${RERequest.id}` }; }); diff --git a/app/helpers/db/searchRequest.js b/app/helpers/db/searchRequest.js new file mode 100644 index 0000000..e4bac0c --- /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.findByPk(searchRequestId); +}; + +const createSearchRequest = async (searchRequestFields = {}) => { + return await db.SearchRequest.create(searchRequestFields); +}; + +module.exports = { + getSearchRequest, + createSearchRequest +}; 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 }; diff --git a/app/migrations/20190912191829-add-realEstates-table.js b/app/migrations/20190912191829-add-realEstates-table.js new file mode 100644 index 0000000..e6b45b3 --- /dev/null +++ b/app/migrations/20190912191829-add-realEstates-table.js @@ -0,0 +1,72 @@ +"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 + }, + createdAt: { + type: Sequelize.DATE, + defaultValue: Sequelize.literal("NOW()") + }, + updatedAt: { + type: Sequelize.DATE, + defaultValue: Sequelize.literal("NOW()") + } + }; + 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..506cb1d --- /dev/null +++ b/app/migrations/20190912215313-add-searchRequests-table.js @@ -0,0 +1,79 @@ +"use strict"; + +module.exports = { + up: (queryInterface, Sequelize) => { + const tableFields = { + id: { + type: Sequelize.UUID, + defaultValue: Sequelize.UUIDV4, + allowNull: false, + primaryKey: true + }, + areaToSearch: { + 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, + allowNull: false + }, + adType: { + type: Sequelize.TEXT, + allowNull: false, + defaultValue: "sell" + }, + 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, + defaultValue: 0 + }, + sizeMax: { + type: Sequelize.INTEGER, + allowNull: false, + defaultValue: 0 + }, + priceMin: { + type: Sequelize.INTEGER, + allowNull: false, + defaultValue: 0 + }, + priceMax: { + type: Sequelize.INTEGER, + allowNull: false, + defaultValue: 0 + }, + gardenSizeMin: Sequelize.INTEGER, + gardenSizeMax: Sequelize.INTEGER, + subscribed: { + 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("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..c3e05ce --- /dev/null +++ b/app/migrations/20190912215556-add-searchRequestMatches-table.js @@ -0,0 +1,53 @@ +"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 + }, + createdAt: { + type: Sequelize.DATE, + defaultValue: Sequelize.literal("NOW()") + }, + updatedAt: { + type: Sequelize.DATE, + defaultValue: Sequelize.literal("NOW()") + } + }; + return queryInterface.createTable("SearchRequestMatches", tableFields); + }, + + down: queryInterface => { + return queryInterface.dropTable("SearchRequestMatches", {}); + } +}; 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..5e16c9e --- /dev/null +++ b/app/models/searchRequest.js @@ -0,0 +1,72 @@ +"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("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, + allowNull: false + }, + adType: { + type: DataTypes.TEXT, + allowNull: false, + defaultValue: "sell" + }, + 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, + defaultValue: 0 + }, + sizeMax: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + priceMin: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + priceMax: { + type: DataTypes.INTEGER, + allowNull: false, + defaultValue: 0 + }, + gardenSizeMin: DataTypes.INTEGER, + gardenSizeMax: DataTypes.INTEGER, + subscribed: { + type: DataTypes.BOOLEAN, + defaultValue: false, + 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; +}; 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 diff --git a/app/routes/index.js b/app/routes/index.js new file mode 100644 index 0000000..6940323 --- /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/:searchRequestId", getRealEstateTypes); +router.get("/vrstanekretnine", getRealEstateTypes); +router.post("/vrstanekretnine/:searchRequestId", postRealEstateTypes); +router.post("/vrstanekretnine", postRealEstateTypes); + +router.get("/lokacija/:searchRequestId", getLocation); +router.post("/lokacija/:searchRequestId", postLocation); + +router.get("/povrsina/:searchRequestId", getSize); +router.post("/povrsina/:searchRequestId", postSize); + +router.get("/okucnica/:searchRequestId", getGardenSize); +router.post("/okucnica/:searchRequestId", postGardenSize); + +router.get("/cijena/:searchRequestId", getPrice); +router.post("/cijena/:searchRequestId", postPrice); + +router.get("/pregled/:searchRequestId", getQueryReview); +router.post("/pregled/:searchRequestId", postQueryReview); + +router.get("/posalji/:searchRequestId", getQuerySubmit); +router.post("/posalji/:searchRequestId", postQuerySubmit); + +router.get("/odjava/:searchRequestId", getUnsubscribe); + +router.get("/ponovo", getGoAgain); + +router.get("/nekretnine/:searchRequestId", getRealEstates); + +router.get("/redirect/:id", redirect); + +module.exports = router; 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 %>
- +
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 - ); - }, []); - } -});