Merge branch 'redesign-DB-and-adapt-search-request' into 'master'

Redesign db and adapt search request

See merge request saburly/marketalarm/web!29
This commit was merged in pull request #29.
This commit is contained in:
Bilal Catic
2019-09-13 16:55:11 +00:00
23 changed files with 595 additions and 293 deletions

View File

@@ -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);

View File

@@ -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);
};

View File

@@ -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);
};

View File

@@ -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);
};

View File

@@ -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);
};

View File

@@ -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 = {

View File

@@ -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);
};

View File

@@ -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 });
};

View File

@@ -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}`
};
});

View 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
};

View File

@@ -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
};

View 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", {});
}
};

View 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", {});
}
};

View File

@@ -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
View 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;
};

View 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;
};

View 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
View 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;

View File

@@ -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">

View File

@@ -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
View File

@@ -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
);
}, []);
}
});