Redesign db and adapt search request #29
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
|
||||
@@ -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 = {
|
||||
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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 });
|
||||
};
|
||||
|
||||
@@ -69,8 +69,8 @@ const getGreetingsEmailHTML = realEstateRequest => {
|
||||
<div>
|
||||
|
||||
</div>
|
||||
<div><strong> Ako želis prestati dobijati obavještenja za ovu pretragu klikni ${APP_URL}/odjava/${realEstateRequest.uniqueId} </strong></div>
|
||||
<div><strong>Ako želiš promijeniti uslove pretrage klikni ${APP_URL}/pregled/${realEstateRequest.uniqueId} </strong></div>
|
||||
<div><strong> Ako želis prestati dobijati obavještenja za ovu pretragu klikni ${APP_URL}/odjava/${realEstateRequest.id} </strong></div>
|
||||
<div><strong>Ako želiš promijeniti uslove pretrage klikni ${APP_URL}/pregled/${realEstateRequest.id} </strong></div>
|
||||
<h4> Tvoj,
|
||||
Javimi tim.
|
||||
</h4>`;
|
||||
@@ -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}`
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
15
app/helpers/db/searchRequest.js
Normal file
15
app/helpers/db/searchRequest.js
Normal file
@@ -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
|
||||
};
|
||||
@@ -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
|
||||
};
|
||||
|
||||
72
app/migrations/20190912191829-add-realEstates-table.js
Normal file
72
app/migrations/20190912191829-add-realEstates-table.js
Normal file
@@ -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", {});
|
||||
}
|
||||
};
|
||||
79
app/migrations/20190912215313-add-searchRequests-table.js
Normal file
79
app/migrations/20190912215313-add-searchRequests-table.js
Normal file
@@ -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", {});
|
||||
}
|
||||
};
|
||||
@@ -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", {});
|
||||
}
|
||||
};
|
||||
65
app/models/realEstate.js
Normal file
65
app/models/realEstate.js
Normal file
@@ -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;
|
||||
};
|
||||
72
app/models/searchRequest.js
Normal file
72
app/models/searchRequest.js
Normal file
@@ -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;
|
||||
};
|
||||
47
app/models/searchRequestMatch.js
Normal file
47
app/models/searchRequestMatch.js
Normal file
@@ -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;
|
||||
};
|
||||
62
app/routes/index.js
Normal file
62
app/routes/index.js
Normal file
@@ -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;
|
||||
@@ -1,10 +1,10 @@
|
||||
<!--suppress HtmlUnknownAnchorTarget -->
|
||||
<% include partials/navBar %>
|
||||
<% include partials/navBar %>
|
||||
|
||||
<form method="POST" id="form-queryreview">
|
||||
<div class="row center-align">
|
||||
<ul class="collection with-header">
|
||||
<% for(const stepData of queryData) { %>
|
||||
<% for(const stepData of queryReviewData) { %>
|
||||
<li class="collection-item" >
|
||||
<div id="<%= stepData.id %>" ><%= stepData.title || '-' %>
|
||||
<a href="<%= stepData.url %>" class="secondary-content">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<!--suppress HtmlUnknownAnchorTarget -->
|
||||
<% include partials/navBar %>
|
||||
<% include partials/navBar %>
|
||||
|
||||
<form method="POST" id="form-real-estate-type">
|
||||
<div class="row center-align">
|
||||
@@ -14,13 +14,13 @@
|
||||
</li>
|
||||
<% } %>
|
||||
</ul>
|
||||
<input type="hidden" name="realestatetype" id="realestatetype" />
|
||||
<input type="hidden" name="realEstateType" id="realEstateType" />
|
||||
</div>
|
||||
</form>
|
||||
|
||||
<script>
|
||||
function saveAndSubmit(id) {
|
||||
$("#realestatetype").val(id);
|
||||
$("#realEstateType").val(id);
|
||||
$("#form-real-estate-type").submit();
|
||||
}
|
||||
</script>
|
||||
|
||||
187
index.js
187
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
|
||||
);
|
||||
}, []);
|
||||
}
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user