Compare commits

...

50 Commits

Author SHA1 Message Date
Nedim Uka
64aee0167f Added hrefs to global varialbe 2019-07-10 12:27:30 +02:00
Bilal Catic
efea857889 Merge branch 'services-scheduler' into 'master'
Added node schedule to run crawler and notification service

See merge request saburly/marketalarm/web!21
2019-07-09 21:51:15 +00:00
Nedim Uka
a43723485c Added node schedule to run crawler and notification service 2019-07-09 16:33:00 +02:00
Nedim Uka
1b098f181c Reduced pager to 5 pages at a time 2019-07-08 13:02:28 +02:00
Bilal Catic
2dd1eaa5fd Merge branch 'crawler-optimisation' into 'master'
Crawler optimisation

See merge request saburly/marketalarm/web!20
2019-07-08 08:01:48 +00:00
Nedim Uka
039b1a6376 Optimiset crawlers , and pagingation 2019-07-05 17:18:47 +02:00
Nedim Uka
222a134bbf Optimised crawler speed by using promises 2019-07-04 17:28:09 +02:00
Nedim Uka
0672f3c019 Changed template name 2019-07-04 09:51:04 +02:00
Bilal Catic
e4b3e3961d Merge branch 'notification-email-subject' into 'master'
Notification email subject

See merge request saburly/marketalarm/web!19
2019-07-04 07:44:49 +00:00
Nedim Uka
a807cb5bf2 Bulk emali subject 2019-07-03 16:01:55 +02:00
Nedim Uka
b79a274f96 Added formated subject to bulk email 2019-07-02 21:49:56 +02:00
Bilal Catic
7f0b2d299e Merge branch 'send-notification' into 'master'
Send notification

See merge request saburly/marketalarm/web!18
2019-07-02 10:32:11 +00:00
Nedim Uka
8b20f0e170 Formated title 2019-07-02 12:25:22 +02:00
Nedim Uka
93c147e73b Looged amazon send bulk email response, fixed some emails not sent bug 2019-07-02 11:54:33 +02:00
Nedim Uka
96e9da1fb1 Send templated bulk email, and remember notifed marketalerts 2019-06-28 18:06:19 +02:00
Nedim Uka
b3baffe174 Send notification email 2019-06-27 17:29:57 +02:00
Nedim Uka
208faa08df Added send notification service, and queried unsent marketalerts, fixed some issues with crawler, and added proper logging 2019-06-25 17:07:02 +02:00
Bilal Catic
5ffdaef1bf Merge branch 'crawler-service' into 'master'
Crawler service

See merge request saburly/marketalarm/web!17
2019-06-24 14:09:47 +00:00
Nedim Uka
1aa91fb4e2 Fixed gardenSize 2019-06-24 15:34:59 +02:00
Nedim Uka
2cf6f6f1ff Code refactoring, fixed bug with price parsing: 2019-06-24 14:20:31 +02:00
Nedim Uka
6eba5c2a97 gardenSize nan 2019-06-24 11:49:13 +02:00
Nedim Uka
2f474619ca Compare crawler results with db, and only save new if necessary 2019-06-21 16:48:19 +02:00
Nedim Uka
80ff9bcb6b saving additional fields, improved async functions with promises 2019-06-21 15:14:43 +02:00
Nedim Uka
3c59292f23 refactoring 2019-06-20 21:27:51 +02:00
Nedim Uka
1bcc5e8e5d Preparing to save results to db 2019-06-20 14:51:14 +02:00
Nedim Uka
c8ee848f0e Improved results filtering by lat lng 2019-06-20 10:57:37 +02:00
Nedim Uka
0f630e9ea4 Olix crawling, filter crawling result by lat, lng 2019-06-19 17:12:22 +02:00
Nedim Uka
9a8a27d1d9 Scheduler 2019-06-18 15:05:40 +02:00
Nedim Uka
b17b6862ba Added migrations, expanded maketalert table 2019-06-18 14:01:09 +02:00
Nedim Uka
6aaaea1612 working on crawler 2019-06-13 15:49:31 +02:00
Nedim Uka
fdd0124924 Added crawler service 2019-06-13 13:31:35 +02:00
Nedim Uka
c15f45e8f4 Fixed map not loding bug 2019-06-12 15:20:58 +02:00
Nedim Uka
371eac900e Merge branch 'range-slider' into 'master'
Range slider

See merge request saburly/marketalarm/web!16
2019-06-12 11:37:15 +00:00
Nedim Uka
5d6e7f3938 fixed slider css overlaping 2019-06-12 13:36:49 +02:00
Nedim Uka
efda7fdccd Added nouiRange slider 2019-06-12 13:32:28 +02:00
Nedim Uka
8bb0908c45 Slider thumb fix 2019-06-11 16:22:17 +02:00
Bilal Catic
5c75d690b0 Merge branch 'slider-bug' into 'master'
Sliders bug

See merge request saburly/marketalarm/web!15
2019-06-11 11:21:33 +00:00
Nedim Uka
f0e8a72756 Sliders now returning to correct range if they go beyond allowed value 2019-06-11 11:44:59 +02:00
Bilal Catic
62bf3380cd Merge branch 'confirmation-email' into 'master'
Geocoding restricttions, added values for range finders, added confirmation email, and .env file

See merge request saburly/marketalarm/web!14
2019-06-11 08:42:06 +00:00
Nedim Uka
caa1871939 deleted env file 2019-06-11 10:34:48 +02:00
Nedim Uka
506ac67956 Fixed garden size email issues 2019-06-11 10:26:48 +02:00
Nedim Uka
8f9e3ae46a Geocoding restricttions, added values for range finders, added confirmation email, and .env file 2019-06-10 17:29:31 +02:00
Bilal Catic
d6e999fcf1 Merge branch 'double-email' into 'master'
Double email

See merge request saburly/marketalarm/web!13
2019-05-30 12:08:25 +00:00
Nedim Uka
08a94ca4f8 Fixed google maps bug, changed size, gardenSize, and price colum names, fixed bug with query review not showing default values 2019-05-30 10:43:47 +02:00
Nedim Uka
a0f2b044b2 Added validation to email confirmation 2019-05-29 17:10:41 +02:00
Nedim Uka
7db74acad7 Set range fileds to be integer instead of strings 2019-05-29 17:10:41 +02:00
Bilal Catic
56865b4670 Merge branch 'realestate-size-slider' into 'master'
Real Estate Slider

See merge request saburly/marketalarm/web!12
2019-05-29 10:18:17 +00:00
Nedim Uka
1a8ac3fba4 Added range slider to gardensize and price 2019-05-29 11:03:01 +02:00
Nedim Uka
de3c76315e Real Estate Slider 2019-05-28 16:46:38 +02:00
Bilal Catic
e969a8dc8b Merge branch 'edit-bug-fix' into 'master'
Fixed bug related to map region edit

See merge request saburly/marketalarm/web!11
2019-05-27 15:52:11 +00:00
41 changed files with 2196 additions and 807 deletions

1
.gitignore vendored
View File

@@ -1 +1,2 @@
node_modules/ node_modules/
.env

View File

@@ -34,3 +34,10 @@ this will create and run postgres image and then execute migrations
5. Run app 5. Run app
`npm start` or `npm run start-mon` to run app with automatic restart on code change `npm start` or `npm run start-mon` to run app with automatic restart on code change
### AWS SES
- AWS SES credentials are handled with env vratiables
- Notification emails are sent in batches of 50, by using SES templates
- Make sure that you are using different templates for different envirorments

View File

@@ -1,8 +1,24 @@
const { currentRERequest } = require('../helpers/url'); const { currentRERequest } = require('../helpers/url');
const { gardenSizes, getRealEstateTypeEnum } = require('../helpers/enums'); const { getRealEstateTypeEnum } = require('../helpers/enums');
const getGardenSize = (req,res) => { const getGardenSize = (req,res) => {
res.render('gardenSize', { gardenSizes });
const unit = " m2"
const rangeFrom = {
min : 10,
max : 3000,
value : 0,
step : 10
}
const rangeTo = {
min : 10,
max : 3000,
value : 100,
step : 10
}
res.render('gardenSize', { rangeFrom, rangeTo, unit });
}; };
const postGardenSize = async (req, res) => { const postGardenSize = async (req, res) => {
@@ -13,7 +29,8 @@ const postGardenSize = async (req, res) => {
const realEstateType = getRealEstateTypeEnum(request.realEstateType); const realEstateType = getRealEstateTypeEnum(request.realEstateType);
if (realEstateType && realEstateType.hasGardenSize) { if (realEstateType && realEstateType.hasGardenSize) {
request.gardenSize = req.body.gardensize; request.gardenSizeMin = req.body.from;
request.gardenSizeMax = req.body.to;
await request.save(); await request.save();
} }

View File

@@ -1,8 +1,24 @@
const { currentRERequest } = require('../helpers/url'); const { currentRERequest } = require('../helpers/url');
const { prices } = require('../helpers/enums');
const getPrice = (req,res) => { const getPrice = (req,res) => {
res.render('price', { prices });
const unit = " KM"
const rangeFrom = {
min : 1000,
max : 250000,
value : 0,
step : 1000
}
const rangeTo = {
min : 1000,
max : 250000,
value : 50000,
step : 1000
}
res.render('price', {rangeFrom, rangeTo, unit });
}; };
const postPrice = async (req, res) => { const postPrice = async (req, res) => {
@@ -11,7 +27,8 @@ const postPrice = async (req, res) => {
const nextStepPage = req.query.nextStep || 'pregled'; const nextStepPage = req.query.nextStep || 'pregled';
const nextStepUrl = `/${nextStepPage}/${request.uniqueId}`; const nextStepUrl = `/${nextStepPage}/${request.uniqueId}`;
request.price = req.body.price; request.priceMin = req.body.from;
request.priceMax = req.body.to;
await request.save(); await request.save();
res.redirect(nextStepUrl); res.redirect(nextStepUrl);

View File

@@ -10,7 +10,16 @@ const getQueryReview = async (req,res) => {
return null; return null;
} }
const { realEstateType, region, municipality, size, gardenSize, price } = request.dataValues; const {
realEstateType,
region,
municipality,
sizeMin,
sizeMax,
gardenSizeMin,
gardenSizeMax,
priceMin,
priceMax } = request.dataValues;
const realEstateTypeObject = getRealEstateTypeEnum(realEstateType); const realEstateTypeObject = getRealEstateTypeEnum(realEstateType);
const enableGardenSizeEdit = realEstateTypeObject ? realEstateTypeObject.hasGardenSize : false; const enableGardenSizeEdit = realEstateTypeObject ? realEstateTypeObject.hasGardenSize : false;
@@ -18,9 +27,9 @@ const getQueryReview = async (req,res) => {
const realEstateTypeTitle = realEstateType ? getEnumTypeTitle(realEstateTypes, realEstateType) : null; const realEstateTypeTitle = realEstateType ? getEnumTypeTitle(realEstateTypes, realEstateType) : null;
const regionName = region ? getRegionName(region) : null; const regionName = region ? getRegionName(region) : null;
const municipalityName = (region && municipality) ? getMunicipalityName(region, municipality) : null; const municipalityName = (region && municipality) ? getMunicipalityName(region, municipality) : null;
const sizeTitle = size ? getEnumTypeTitle(sizes, size) : null; const sizeTitle = sizeMin ? sizeMin + "-" + sizeMax + " m2" : null;
const gardenSizeTitle = gardenSize ? getEnumTypeTitle(gardenSizes, gardenSize) : null; const gardenSizeTitle = gardenSizeMin ? gardenSizeMin + "-" + gardenSizeMax + " m2" : null;
const priceTitle = price ? getEnumTypeTitle(prices, price) : null; const priceTitle = priceMin ? priceMin + "-" + priceMax + " KM" : null;
const uniqueId = request.dataValues.uniqueId ? request.dataValues.uniqueId : ''; const uniqueId = request.dataValues.uniqueId ? request.dataValues.uniqueId : '';

View File

@@ -1,7 +1,8 @@
const { currentRERequest } = require('../helpers/url'); const { currentRERequest } = require('../helpers/url');
const { isValidEmail } = require('../helpers/email'); const { isValidEmail } = require('../helpers/email');
const { sendTemplatedEmail} = require('../helpers/awsEmail');
const getQuerySubmit = async (req,res) => { const getQuerySubmit = async (req, res) => {
const nextStep = req.query.nextStep; const nextStep = req.query.nextStep;
const error = req.query.error; const error = req.query.error;
@@ -16,14 +17,32 @@ const postQuerySubmit = async (req, res) => {
const nextStep = req.query.nextStep || '/ponovo'; const nextStep = req.query.nextStep || '/ponovo';
const emailInput = req.body.email; const emailInput = req.body.email;
const emailConfirmInput = req.body.confirm;
let error = "Greška ! Unesite validan email";
if (isValidEmail(emailInput)){ if (!isValidEmail(emailInput) || !isValidEmail(emailConfirmInput)) {
request.email = req.body.email;
await request.save(); error = "Greška ! Unesite validan email";
res.redirect(nextStep); res.render('querySubmit', {
} else { error
res.redirect('?error=1'); });
return;
} }
if (emailInput !== emailConfirmInput) {
error = "Greška ! Unešeni emailovi nisu isti";
res.render('querySubmit', {
error
});
return;
}
request.email = req.body.email;
request.subscribed = true;
await request.save();
sendTemplatedEmail(req.body.email, request);
res.redirect(nextStep);
}; };
module.exports = { module.exports = {

View File

@@ -2,7 +2,23 @@ const { currentRERequest } = require('../helpers/url');
const { sizes, getRealEstateTypeEnum } = require('../helpers/enums'); const { sizes, getRealEstateTypeEnum } = require('../helpers/enums');
const getSize = (req,res) => { const getSize = (req,res) => {
res.render('size', { sizes });
const unit = " m2"
const rangeFrom = {
min : 10,
max : 250,
value : 0,
step : 10
}
const rangeTo = {
min : 10,
max : 250,
value : 50,
step : 10
}
res.render('size', { rangeFrom, rangeTo, unit });
}; };
const postSize = async (req, res) => { const postSize = async (req, res) => {
@@ -13,8 +29,8 @@ const postSize = async (req, res) => {
const nextStep = realEstateType && realEstateType.hasGardenSize ? 'okucnica' : 'cijena'; const nextStep = realEstateType && realEstateType.hasGardenSize ? 'okucnica' : 'cijena';
const nextStepPage = req.query.nextStep || nextStep; const nextStepPage = req.query.nextStep || nextStep;
const nextStepUrl = `/${nextStepPage}/${request.uniqueId}`; const nextStepUrl = `/${nextStepPage}/${request.uniqueId}`;
request.sizeMin = req.body.from;
request.size = req.body.size; request.sizeMax = req.body.to;
await request.save(); await request.save();
res.redirect(nextStepUrl); res.redirect(nextStepUrl);

View File

@@ -0,0 +1,15 @@
const { currentRERequest } = require('../helpers/url');
const getUnsubscribe = async (req, res) => {
const request = await currentRERequest(req);
request.subscribed = false;
await request.save();
res.render('unsubscribe', { nextStep: '/vrstanekretnine' });
};
module.exports = {
getUnsubscribe
};

245
app/helpers/awsEmail.js Normal file
View File

@@ -0,0 +1,245 @@
const dotenv = require('dotenv').config();
const { getRealEstateTypeEnum } = require('./enums');
const { getRegionName, getMunicipalityName } = require('./codes');
const { allRERequestByUiid } = require('./db/dbHelper');
var AWS = require('aws-sdk');
const TEMPLATE_NAME = "MarketAlertTemplate"
AWS.config.update({
region: process.env.AMAZON_REGION,
credentials:
{
accessKeyId: process.env.AMAZON_ACCES_KEY_ID,
secretAccessKey: process.env.AMAZON_SECRET_ACCESS_KEY
}
});
const sendTemplatedEmail = async (email, request) => {
const params = {
Destination: { /* required */
CcAddresses: [
],
ToAddresses: [
email
]
},
Message: { /* required */
Body: { /* required */
Html: {
Charset: "UTF-8",
Data: getGreetingsEmailHTML(request)
},
Text: {
Charset: "UTF-8",
Data: getGreetingsEmaiTextVersion(request)
}
},
Subject: {
Charset: 'UTF-8',
Data: `Javimi Potvrda: ${getSubject(request.realEstateType, request.region, request.municipality)}`
}
},
Source: process.env.SOURCE_EMAIL, /* required */
ReplyToAddresses: [
process.env.SOURCE_EMAIL,
],
};
const sendEmailPromise = new AWS.SES({ apiVersion: '2010-12-01' }).sendEmail(params).promise();
await sendEmailPromise;
}
const getGreetingsEmailHTML = (realestateRequest) => {
const realEstateType = getRealEstateTypeEnum(realestateRequest.realEstateType);
const gardenSize = realEstateType.hasGardenSize ? `<div><strong>Kvadratura okućnice: Od ${realestateRequest.gardenSizeMin} do ${realestateRequest.gardenSizeMax} m2 </strong></div>` : ``
return `<h1> Zdravo,
Naručio/la si da ti javimo ako se nekretnina pojavi u oglasima. </h1>
<h2> Ovo je tražena nekretnina: </h2>
<div>
<div> <strong>Tip nekretnine: ${realEstateType.title} </strong></div>
<div><strong>Područje: ${getRegionName(realestateRequest.region)} </strong></div>
<div><strong>Mjesto: ${getMunicipalityName(realestateRequest.region, realestateRequest.municipality)} </strong></div>
<div><strong>Kvadratura nekretnine: Od ${realestateRequest.sizeMin} do ${realestateRequest.sizeMax} m2 </strong></div>
${gardenSize}
<div><strong>Cijena: ${realestateRequest.priceMin} do ${realestateRequest.priceMax} KM </strong></div>
</div>
<div>
</div>
<div><strong> Ako želis prestati dobijati obavještenja za ovu pretragu klikni ${process.env.APP_URL}/odjava/${realestateRequest.uniqueId} </strong></div>
<div><strong>Ako želiš promijeniti uslove pretrage klikni ${process.env.APP_URL}/pregled/${realestateRequest.uniqueId} </strong></div>
<h4> Tvoj,
Javimi tim.
</h4>`
}
const getGreetingsEmaiTextVersion = (realestateRequest) => {
const realEstateType = getRealEstateTypeEnum(realestateRequest.realEstateType);
const gardenSize = realEstateType.hasGardenSize ? "Kvadratura okućnice od " + realestateRequest.gardenSizeMin + " do " + realestateRequest.gardenSizeMax : ""
const text = "Zdravo, \n Naručio/la si da ti javimo ako se nekretnina pojavi u oglasima \n Ovo je tražena nekretnina: \n , Tip nekretnine: "
+ realestateRequest.realEstateType + "\n Područje" + getRegionName(realestateRequest.region) + "\n Mjesto " + getMunicipalityName(realestateRequest.region, realestateRequest.municipality)
+ "\n Kvadratura nekretnine Od " + realestateRequest.sizeMin + " do " + realestateRequest.sizeMaX +
+ gardenSize
"\n Cijena od " + realestateRequest.priceMin + " do " + realestateRequest.priceMax +
"\n Ako želis prestati dobijati obavještenja za ovu pretragu klikni" + process.env.APP_URL + "/odjava/" + realestateRequest.uniqueId +
"\n Ako želiš promijeniti uslove pretrage klikni " + process.env.APP_URL + "/odpregled/" + realestateRequest.uniqueId +
"\n Tvoj,\n Javimi tim"
return text;
}
const sendBulkEmail = async (marketAlerts) => {
try {
destinations = []
groupedRERequests = [];
const RERequestUuidsMaped = marketAlerts.map(marketAlert => marketAlert.request);
const RERequestUuidsArray = Array.from(new Set(RERequestUuidsMaped));
const RERequestUuids = RERequestUuidsArray.map(marketAlert => {
return { uniqueId: marketAlert }
});
const RERequest = await allRERequestByUiid(RERequestUuids);
const requestDataValues = [];
RERequest.forEach(RERequest => {
var formatedRequest = {};
formatedRequest[RERequest.uniqueId] =
requestDataValues[RERequest.uniqueId] = {
realEstateType: RERequest.realEstateType,
region: RERequest.region,
municipality: RERequest.municipality
};
});
marketAlerts.forEach(marketAlert => {
const requestObject = {
email: marketAlert.email,
realEstateType: requestDataValues[marketAlert.request].realEstateType,
municipality: requestDataValues[marketAlert.request].municipality,
region: requestDataValues[marketAlert.request].region,
}
if (!groupedRERequests[marketAlert.request]) {
groupedRERequests[marketAlert.request] = {
requestObject: requestObject,
marketAlertArray: []
};
}
groupedRERequests[marketAlert.request].marketAlertArray.push({
url: marketAlert.url,
title: marketAlert.title,
});
});
for (request in groupedRERequests) {
const marketAlert = groupedRERequests[request];
let extractedData = toAWSArray(marketAlert.marketAlertArray);
const realEstateType = getRealEstateTypeEnum(marketAlert.requestObject.realEstateType).title;
const region = getRegionName(marketAlert.requestObject.region);
const municipality = getMunicipalityName(marketAlert.requestObject.region, marketAlert.requestObject.municipality);
let repData = `{ "marketAlertUrl":[${extractedData}], "realestateType":"${realEstateType}", "region":"${region}", "municipality":"${municipality}" }`
destinations.push({
Destination: {
ToAddresses: [
marketAlert.requestObject.email
]
},
ReplacementTemplateData: repData
})
}
console.log("AWS EMAIL : Bulk email replacement data:");
console.log(destinations);
var params = {
Destinations:
destinations,
Source: process.env.SOURCE_EMAIL, /* required */
Template: TEMPLATE_NAME, /* required */
DefaultTemplateData: '{ \"REPLACEMENT_TAG_NAME\":\"REPLACEMENT_VALUE\" }',
ReplyToAddresses: [
process.env.SOURCE_EMAIL,
]
};
// Create the promise and SES service object
const sendPromise = new AWS.SES({ apiVersion: '2010-12-01' }).sendBulkTemplatedEmail(params).promise();
const awsResult = await sendPromise;
console.log("AWS SES bulk email response");
console.log(awsResult);
} catch (e) {
console.log("Could not send bulk email", e)
}
}
const toAWSArray = (urlArray) => {
let arrayString = ""
urlArray.forEach(element => {
const formatetdTitle = element.title.replace(/"/g, "");
arrayString = arrayString + `{"url":"${element.url.trim()}" , "title":"${formatetdTitle}"},`
});
return arrayString.slice(0, -1);
}
const getNotificationEmailHtml = () => {
return `<h2> Zdravo,
Pronašli smo nekretninu koju ste tražili. </h2>
<h3> Ovo su tražene nekretnine: </h3>
<div>
<div>{{#each marketAlertUrl}}<li><a href="{{url}}">{{title}}</a></li><br />{{/each}}<div/>
<div/>
</div>`
}
const getNotificationEmailText = () => {
return ` Zdravo,
Pronašli smo nekretninu koju ste tražili. Ovo su tražene nekretnine: {{#each marketAlertUrl}} {{url}} {{title}} {{/each}}`
}
const createMarketAlertEmailTemplate = async () => {
const marketAlertTemplate = {
Template: {
TemplateName: TEMPLATE_NAME,
SubjectPart: "Javi mi obavijest: {{realestateType}}, {{region}}, {{municipality}}",
TextPart: getNotificationEmailText(),
HtmlPart: getNotificationEmailHtml()
}
}
try {
const templatePromise = new AWS.SES({ apiVersion: '2010-12-01' }).updateTemplate(marketAlertTemplate).promise();
await templatePromise
} catch (e) {
console.log("Could not create MarketAlertEmailTemplate", e);
}
}
const getSubject = (realEstateType, region, municipality) => {
return `${getRealEstateTypeEnum(realEstateType).title} ${getRegionName(region)}, ${getMunicipalityName(region, municipality)}`
}
module.exports = {
sendTemplatedEmail,
sendBulkEmail,
createMarketAlertEmailTemplate
};

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,355 @@
const fetch = require('node-fetch');
const cheerio = require('cheerio');
const { allRERequest, findPointInsideBoundingBox } = require('../db/dbHelper');
const { getRealEstateTypeEnum } = require('../enums');
const { getRegion, getMunicipality } = require('../codes')
const Promise = require("bluebird");
module.exports = class OlxCrawler {
//TODO figure best way to handle paging
constructor(fromPage = 0, toPage = 10, maxResults = 1000) {
this.fromPage = fromPage;
this.toPage = toPage;
this.maxResults = maxResults;
}
async indexPages(urls) {
const indexers = [];
urls.forEach(url => {
indexers.push(new Indexer(url));
});
return Promise.map(indexers, function (indexer) {
return indexer.indexWithPagination();
}).then(async (results) => {
return results
})
}
async crawl() {
console.log("OLX CRAWLER: start crawl");
const filteredResults = [];
const realestateRequests = await allRERequest();
console.log("OLX CRAWLER: found " + realestateRequests.length + "subscribed RealEstateRequests");
const urls = this.createRequestUrls(realestateRequests);
let results = await this.indexPages(urls, this.fromPage, this.toPage, this.maxResults);
console.log("Final crawler results");
if (results[0]) {
console.log(results[0].length);
for (const finalResult of results[0]) {
if (null !== finalResult) {
if (finalResult.lat !== undefined && finalResult.lat !== null && finalResult.lat !== "") {
const pointInsideBoundingBox = await findPointInsideBoundingBox([finalResult.lng, finalResult.lat], finalResult.email);
if (pointInsideBoundingBox[0].length !== 0) {
filteredResults.push(finalResult);
}
}
}
}
console.log("OLX CRAWLER: number of olx crawler results, after geo location filtering: " + filteredResults.length);
return filteredResults;
}
return []
}
createRequestUrls(realestateRequests) {
const urls = []
for (const request of realestateRequests) {
const realsestateType = "kategorija=" + getRealEstateTypeEnum(request.realEstateType).olxCategory;
const region = "kanton=" + getRegion(request.region).olxid;
const municipality = "grad%5B%5D=" + getMunicipality(request.region, request.municipality).olxid;
const sizeMin = "kvadrata_min=" + request.sizeMin;
const sizeMax = "kvadrata_max=" + request.sizeMax;
const priceMin = "od=" + request.priceMin;
const priceMax = "do=" + request.priceMax;
const olxUrl = {
url: `https://www.olx.ba/pretraga?${realsestateType}&id=2&stanje=0&vrstapregleda=tabela&sort_order=desc&${region}&${municipality}&${priceMin}&${priceMax}&vrsta=samoprodaja&${sizeMin}&${sizeMax}&stranica=`,
email: request.email,
uuid: request.uniqueId
}
console.log(olxUrl.url);
urls.push(olxUrl);
}
return urls;
}
};
class Indexer {
/**
*
* @param {String|Array} olxUrl single or array of objects containing url email and uuid
* @param {Array} hrefResutls array contaning urls from crawler results
*/
constructor(olxUrl, hrefResutls) {
this.olxUrl = olxUrl;
this.hrefResutls = hrefResutls;
}
async indexWithPagination(pageNumber = 1) {
console.log("This is olxUrl:" + this.olxUrl.url);
const pageNr = this.olxUrl.url.match(/\d+$/);
const indexers = this.prepareIndexers(pageNumber ? [pageNumber] : pageNr);
try {
return Promise.map(indexers.indexers, function (indexer) {
return indexer.indexPage(pageNumber);
}).then(async (results) => {
let hasResults = false;
results.forEach(result => {
if (!hasResults) {
console.log("No results detected")
hasResults = result.hasResults
}
});
if (!hasResults) {
console.log("HAS NO MORE RESULTS, stop the paging, there are some results and they should contain only HREFS");
console.log(results.length);
const singlePageIndexers = this.prepareHrefIndexers(results);
if (singlePageIndexers.length === 0) {
console.log("THERE IS NOT EVEN SINGLE RESULT");
return []
}
return Promise.map(singlePageIndexers, function (indexer) {
return indexer.indexSingle();
}).then(async (results) => {
console.log("SinglePageMethod in HAS NO RESULTS, MarketAralms");
console.log(results.length);
return results;
});
} else {
console.log("HAS MORE RESULTS, should only contain HREFS");
console.log(results.length);
const newResults = await this.indexWithPagination(results[0].pageNumber + 5);
const singlePageIndexers = this.prepareHrefIndexers(results);
const newerResults = await Promise.map(singlePageIndexers, function (indexer) {
return indexer.indexSingle();
}).then(async (results) => {
console.log("SinglePageMethod HAS RESULTS, should contain MarketAlerts only");
console.log(results.length);
return results;
});
Array.prototype.push.apply(newResults, newerResults);
return newResults;
}
});
} catch (e) {
console.error("Error has accured", e);
}
}
prepareIndexers(pageNr) {
console.log("Entering prepareIndexers : page nr - " + pageNr);
const indexers = [];
let lastPageNumber;
if (pageNr) {
for (let index = Number(pageNr[0]); index <= Number(pageNr[0]) + 5; index++) {
lastPageNumber = index;
const newOlxUrl = {
url: this.olxUrl.url.replace(/\d+$/, "") + index,
email: this.olxUrl.email,
uuid: this.olxUrl.uuid
}
indexers.push(new Indexer(newOlxUrl));
}
} else {
for (let index = 1; index <= 5; index++) {
lastPageNumber = index;
const newOlxUrl = {
url: this.olxUrl.url + index,
email: this.olxUrl.email,
uuid: this.olxUrl.uuid
}
indexers.push(new Indexer(newOlxUrl));
}
}
return {
indexers: indexers,
lastPageNumber: lastPageNumber
};
}
prepareHrefIndexers(results) {
const indexers = []
if (!Array.isArray(results)) {
results.hrefs.forEach(href => {
const newOlxUrl = {
url: href,
email: results.olxUrl.email,
uuid: results.olxUrl.uuid
}
indexers.push(new Indexer(newOlxUrl));
});
} else {
results.forEach(result => {
if (result !== null && result.hasOwnProperty('hrefs')) {
result.hrefs.forEach(href => {
// console.log(href);
const newOlxUrl = {
url: href,
email: result.olxUrl.email,
uuid: result.olxUrl.uuid
}
indexers.push(new Indexer(newOlxUrl));
})
}
});
}
return indexers;
}
async indexPage(pageNumber) {
console.log("Page number in index page, max page number :")
console.log(pageNumber);
try {
console.log("Indexing page: " + this.olxUrl.url);
const res = await fetch(this.olxUrl.url);
const body = await res.text();
const $ = cheerio.load(body);
const hrefs = [];
let hasResults = false
$('#rezultatipretrage').find('.listitem').each((i, elem) => {
hasResults = true
const href = $(elem).find('a').first().attr('href');
hrefs.push(href);
});
console.log("this is hrefs for olxUrl" + this.olxUrl.url);
console.log("NUMBER OF HREFS " + hrefs.length);
return {
hrefs: hrefs,
hasResults: hasResults,
pageNumber: pageNumber,
olxUrl: this.olxUrl
}
} catch (e) {
console.error('Exception caught:' + e);
}
}
async indexSingle() {
try {
console.log("Index single");
console.log(this.olxUrl.url);
if (this.olxUrl.url === undefined) {
return {}
}
const res = await fetch(this.olxUrl.url);
const body = await res.text();
const $ = cheerio.load(body);
const title = $('#naslovartikla').text().trim();
const realEstateType = $('#artikal_glavni_div > div.artikal_lijevo > div:nth-child(3) > div > span:nth-child(3) > a > span').text();
const price = $('#pc > p:nth-child(2)').text();
const size = $('#dodatnapolja1 > div:nth-child(1) > div.df2').text();
const rooms = $('#dodatnapolja1 > div:nth-child(2) > div.df2').text();
const address = $('#dodatnapolja1 > div:nth-child(5) > div.df2').text();
const gardenSize = $('#dodatnapolja1 > div:nth-child(6) > div.df2').text();
const location = $('#artikal_glavni_div > div.artikal_lijevo > div.op.pop.mobile-lokacija').attr('data-content');
const time = $('time').attr('datetime');
const olxId = $('#artikal_glavni_div > div.artikal_lijevo > div:nth-child(15) > div:nth-child(4) > div.df2').text();
const descriptions = $('.artikal_detaljniopis_tekst');
const latLngRe = /LatLng\(([0-9]+\.[0-9]+)\,\s+([0-9]+\.[0-9]+)\)/g;
const imgRe = /href":("[^"]*")/g;
const matches = latLngRe.exec(body);
let lng = '',
lat = '';
const parsePrice = (price) => parseFloat(price.replace(".", ""))
if (matches && matches.length >= 3) {
lat = matches[1];
lng = matches[2];
}
const parsedPrice = parsePrice(price);
const locationArray = location.split(",");
const region = locationArray[0];
const municipality = locationArray[1];
const data = {
realEstateType: this.getCategoryId(realEstateType),
email: this.olxUrl.email,
uuid: this.olxUrl.uuid,
olxId: olxId,
url: this.olxUrl.url,
title,
price: isNaN(parsedPrice) ? 0 : parsedPrice,
size: parseFloat(size),
gardenSize: isNaN(parseFloat(gardenSize)) ? 0 : parseFloat(gardenSize),
address,
region,
municipality,
time,
shortDescription: descriptions.first().text(),
longDescription: descriptions.last().text(),
lat,
lng,
loc: [parseFloat(lat), parseFloat(lng)],
};
return data;
} catch (e) {
console.error('Exception caught: ' + e.message);
}
return null;
}
getCategoryId(category) {
switch (category) {
case 'Stanovi':
return 'stan';
case 'Vikendice':
return 'vikendica'
case 'Kuće':
return 'kuca';
default:
return '';
}
}
}

View File

@@ -0,0 +1,69 @@
const db = require('../../models/index');
/**
* Find all subscribed RealEstateRequests
*/
const allRERequest = async () => {
return await db.RealEstateRequest.findAll({
where: {
subscribed: true
}
});
}
/**
* Find all subscribed RealEstateRequests by UUID
*/
const allRERequestByUiid = async (requestArray) => {
const Op = db.Sequelize.Op;
return await db.RealEstateRequest.findAll({
where: {
subscribed: true,
[Op.or]: requestArray
}
});
}
/**
* Find all , or all depending on notified bolean marketalerts, and order them by email
*
* @param fechAll bolean
* @param notified bolean
*
* @returns array of MarketAlerts
*/
const allMarketAlerts = async (fetchAll, notified) => {
let queryObject = {
order: [
['email', 'DESC'],
]
}
if (!fetchAll){
queryObject.where = {
notified: notified
}
}
return await db.MarketAlert.findAll(queryObject);
}
/**
* Find all unnotified marketalerts
* @param latLng array
* @param email string
*
* @returns array of MarketAlerts
*/
const findPointInsideBoundingBox = async (latLng, email) => {
return await db.sequelize.query(`SELECT * FROM "RealEstateRequests" WHERE email = '${email}' AND subscribed = true AND ST_Contains("RealEstateRequests".bounding_box, ST_GEOMFROMTEXT('POINT (${latLng[0]} ${latLng[1]})'))`);
}
module.exports = {
allRERequest,
allMarketAlerts,
allRERequestByUiid,
findPointInsideBoundingBox
};

View File

@@ -1,7 +1,7 @@
const realEstateTypes = [ const realEstateTypes = [
{ title: "Kuća", id: "kuca", hasGardenSize: true }, { title: "Kuća", id: "kuca", hasGardenSize: true, olxCategory: 24 },
{ title: "Stan", id: "stan", hasGardenSize: false }, { title: "Stan", id: "stan", hasGardenSize: false, olxCategory: 23},
{ title: "Vikendica", id: "vikendica", hasGardenSize: true } { title: "Vikendica", id: "vikendica", hasGardenSize: true, olxCategory: 26 }
]; ];
const sizes = [ const sizes = [

View File

@@ -7,7 +7,6 @@ const currentRERequest = async (req) => {
const request = await db.RealEstateRequest.findOne({ where: {uniqueId} }); const request = await db.RealEstateRequest.findOne({ where: {uniqueId} });
return request; return request;
}; };
module.exports = { module.exports = {
currentRERequest currentRERequest,
}; };

View File

@@ -1,7 +1,7 @@
const scrapTheItems = require("./scrapTheItems"); const scrapTheItems = require("./scrapTheItems");
const convertToDate = require("./convertToDate"); const convertToDate = require("./convertToDate");
const AWS = require('aws-sdk'); const AWS = require('aws-sdk');
AWS.config.update({region: 'eu-central-1'}); // AWS.config.update({region: 'eu-central-1'});
async function sendNotification(marketAlert) { async function sendNotification(marketAlert) {

View File

@@ -0,0 +1,27 @@
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction((t) => {
return Promise.all([
queryInterface.addColumn('RealEstateRequests', 'sizeRange', {
type: Sequelize.STRING
}, { transaction: t }),
queryInterface.addColumn('RealEstateRequests', 'gardenSizeRange', {
type: Sequelize.STRING,
}, { transaction: t }),
queryInterface.addColumn('RealEstateRequests', 'priceRange', {
type: Sequelize.STRING,
}, { transaction: t })
])
})
},
down: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction((t) => {
return Promise.all([
queryInterface.removeColumn('RealEstateRequests', 'sizeRange', { transaction: t }),
queryInterface.removeColumn('RealEstateRequests', 'gardenSizeRange', { transaction: t }),
queryInterface.removeColumn('RealEstateRequests', 'priceRange', { transaction: t })
])
})
}
};

View File

@@ -0,0 +1,63 @@
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction((t) => {
return Promise.all([
queryInterface.removeColumn('RealEstateRequests', 'sizeRange', { transaction: t }),
queryInterface.removeColumn('RealEstateRequests', 'gardenSizeRange', { transaction: t }),
queryInterface.removeColumn('RealEstateRequests', 'priceRange', { transaction: t }),
queryInterface.removeColumn('RealEstateRequests', 'size', { transaction: t }),
queryInterface.removeColumn('RealEstateRequests', 'gardenSize', { transaction: t }),
queryInterface.removeColumn('RealEstateRequests', 'price', { transaction: t }),
queryInterface.addColumn('RealEstateRequests', 'gardenSizeMin', {
type: Sequelize.INTEGER,
}, { transaction: t }),
queryInterface.addColumn('RealEstateRequests', 'gardenSizeMax', {
type: Sequelize.INTEGER,
}, { transaction: t }),
queryInterface.addColumn('RealEstateRequests', 'sizeMin', {
type: Sequelize.INTEGER
}, { transaction: t }),
queryInterface.addColumn('RealEstateRequests', 'sizeMax', {
type: Sequelize.INTEGER,
}, { transaction: t }),
queryInterface.addColumn('RealEstateRequests', 'priceMin', {
type: Sequelize.INTEGER,
}, { transaction: t }),
queryInterface.addColumn('RealEstateRequests', 'priceMax', {
type: Sequelize.INTEGER
}, { transaction: t })
])
})
},
down: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction((t) => {
return Promise.all([
queryInterface.removeColumn('RealEstateRequests', 'gardenSizeMin', { transaction: t }),
queryInterface.removeColumn('RealEstateRequests', 'gardenSizeMax', { transaction: t }),
queryInterface.removeColumn('RealEstateRequests', 'sizeMin', { transaction: t }),
queryInterface.removeColumn('RealEstateRequests', 'sizeMax', { transaction: t }),
queryInterface.removeColumn('RealEstateRequests', 'priceMin', { transaction: t }),
queryInterface.removeColumn('RealEstateRequests', 'priceMin', { transaction: t }),
queryInterface.addColumn('RealEstateRequests', 'priceMax', {
type: Sequelize.STRING
}, { transaction: t }),
queryInterface.addColumn('RealEstateRequests', 'gardenSizeRange', {
type: Sequelize.STRING,
}, { transaction: t }),
queryInterface.addColumn('RealEstateRequests', 'priceRange', {
type: Sequelize.STRING,
}, { transaction: t }),
queryInterface.addColumn('RealEstateRequests', 'size', {
type: Sequelize.STRING
}, { transaction: t }),
queryInterface.addColumn('RealEstateRequests', 'gardenSize', {
type: Sequelize.STRING,
}, { transaction: t }),
queryInterface.addColumn('RealEstateRequests', 'price', {
type: Sequelize.STRING,
}, { transaction: t })
])
})
}
};

View File

@@ -0,0 +1,18 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'RealEstateRequests',
'subscribed',
Sequelize.BOOLEAN
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn(
'RealEstateRequests',
'subscribed'
);
}
};

View File

@@ -0,0 +1,37 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction((t) => {
return Promise.all([
queryInterface.addColumn('MarketAlerts', 'size', {
type: Sequelize.INTEGER,
}, { transaction: t }),
queryInterface.addColumn('MarketAlerts', 'gardenSize', {
type: Sequelize.INTEGER,
}, { transaction: t }),
queryInterface.addColumn('MarketAlerts', 'price', {
type: Sequelize.INTEGER,
}, { transaction: t }),
queryInterface.addColumn('MarketAlerts', 'municipality', {
type: Sequelize.STRING,
}, { transaction: t }),
queryInterface.addColumn('MarketAlerts', 'region', {
type: Sequelize.STRING,
}, { transaction: t })
])
})
},
down: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction((t) => {
return Promise.all([
queryInterface.removeColumn('MarketAlerts', 'size', { transaction: t }),
queryInterface.removeColumn('MarketAlerts', 'gardenSize', { transaction: t }),
queryInterface.removeColumn('MarketAlerts', 'price', { transaction: t }),
queryInterface.removeColumn('MarketAlerts', 'municipality', { transaction: t }),
queryInterface.removeColumn('MarketAlerts', 'region', { transaction: t })
])
})
}
};

View File

@@ -0,0 +1,33 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction((t) => {
return Promise.all([
queryInterface.removeColumn('MarketAlerts', 'olxUrl', { transaction: t }),
queryInterface.addColumn('MarketAlerts', 'url', {
type: Sequelize.STRING,
}, { transaction: t }),
queryInterface.addColumn('MarketAlerts', 'realestateOrigin', {
type: Sequelize.STRING,
}, { transaction: t }),
queryInterface.addColumn('MarketAlerts', 'originId', {
type: Sequelize.STRING,
}, { transaction: t })
])
})
},
down: (queryInterface, Sequelize) => {
return queryInterface.sequelize.transaction((t) => {
return Promise.all([
queryInterface.removeColumn('MarketAlerts', 'url', { transaction: t }),
queryInterface.removeColumn('MarketAlerts', 'realestateOrigin', { transaction: t }),
queryInterface.removeColumn('MarketAlerts', 'originId', { transaction: t }),
queryInterface.addColumn('MarketAlerts', 'olxUrl', {
type: Sequelize.STRING
}, { transaction: t })
])
})
}
};

View File

@@ -0,0 +1,20 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'MarketAlerts',
'realEstateType',
{
type: Sequelize.STRING
}
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn(
'MarketAlerts',
'realEstateType'
);
}
};

View File

@@ -0,0 +1,20 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'MarketAlerts',
'notified',
{
type: Sequelize.BOOLEAN
}
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn(
'MarketAlerts',
'notified'
);
}
};

View File

@@ -0,0 +1,20 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'MarketAlerts',
'title',
{
type: Sequelize.STRING
}
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn(
'MarketAlerts',
'title'
);
}
};

View File

@@ -0,0 +1,20 @@
'use strict';
module.exports = {
up: (queryInterface, Sequelize) => {
return queryInterface.addColumn(
'MarketAlerts',
'request',
{
type: Sequelize.STRING
}
);
},
down: (queryInterface, Sequelize) => {
return queryInterface.removeColumn(
'MarketAlerts',
'request'
);
}
};

View File

@@ -1,8 +1,20 @@
'use strict'; 'use strict';
module.exports = (sequelize, DataTypes) => { module.exports = (sequelize, DataTypes) => {
const MarketAlert = sequelize.define('MarketAlert', { const MarketAlert = sequelize.define('MarketAlert', {
olxUrl: DataTypes.STRING, url: DataTypes.STRING,
realestateOrigin: DataTypes.STRING,
originId: DataTypes.STRING,
lastDate: DataTypes.STRING, lastDate: DataTypes.STRING,
size : DataTypes.INTEGER,
gardenSize : DataTypes.INTEGER,
price : DataTypes.INTEGER,
municipality : DataTypes.STRING,
region : DataTypes.STRING,
realEstateType : DataTypes.STRING,
notified : DataTypes.BOOLEAN,
title : DataTypes.STRING,
request: DataTypes.STRING,
email: { email: {
type: DataTypes.STRING, type: DataTypes.STRING,
allowNul: false allowNul: false

View File

@@ -12,10 +12,14 @@ module.exports = (sequelize, DataTypes) => {
email: DataTypes.STRING, email: DataTypes.STRING,
region: DataTypes.STRING, region: DataTypes.STRING,
municipality: DataTypes.STRING, municipality: DataTypes.STRING,
size: DataTypes.STRING, sizeMin: DataTypes.INTEGER,
gardenSize: DataTypes.STRING, sizeMax: DataTypes.INTEGER,
price: DataTypes.STRING, gardenSizeMin: DataTypes.INTEGER,
bounding_box: DataTypes.GEOMETRY('POINT', 4326) gardenSizeMax: DataTypes.INTEGER,
priceMin: DataTypes.INTEGER,
priceMax: DataTypes.INTEGER,
bounding_box: DataTypes.GEOMETRY('POINT', 4326),
subscribed: DataTypes.BOOLEAN
}, {}); }, {});
RealEstateRequest.associate = function(models) { RealEstateRequest.associate = function(models) {
// associations can be defined here // associations can be defined here

View File

@@ -9,6 +9,9 @@
background-repeat: no-repeat; background-repeat: no-repeat;
color: rgba(0, 0, 0, 0); color: rgba(0, 0, 0, 0);
} }
.no-ui-slider {
width: 95%
}
#map { #map {

View File

@@ -0,0 +1,79 @@
const Promise = require("bluebird");
const OlxCrawler = require("../helpers/crawlers/olxClawler");
const db = require("../models/index");
const { allMarketAlerts } = require('../helpers/db/dbHelper');
const olxCrawler = new OlxCrawler(1, 2, 3);
const crawlers = [
olxCrawler,
];
async function crawlAll() {
console.log("CRAWLER SERVICE: crawlAll");
return Promise.map(crawlers, function (crawler) {
return crawler.crawl();
}).then(async (results) => {
try {
const marketAlertsFromDb = await allMarketAlerts(true);
const hrefs = [];
const subscribedMakretAlerts = marketAlertsFromDb.filter(marketAlert => {
return marketAlert.subscribed;
});
marketAlertsFromDb.map(marketAlert => {
if (hrefs[marketAlert.request] === undefined) {
hrefs[marketAlert.request] = []
}
hrefs[marketAlert.request].push(marketAlert.url);
})
global.hrefs = hrefs;
console.log(global.hrefs);
console.log("CRAWLER SERVICE: number of existing MarketAlerts from db: " + subscribedMakretAlerts.length);
const marketAlerts = [];
const mergedResults = [].concat.apply([], results);
for (const result of mergedResults) {
marketAlerts.push({
url: result.url,
realestateOrigin: "OLX",
originId: 1,
size: result.size,
price: result.price,
email: result.email,
request: result.uuid,
// lastDate: DataTypes.STRING,
municipality: result.municipality,
region: result.region,
gardenSize: isNaN(result.gardenSize) ? 0 : result.gardenSize,
realEstateType: result.realEstateType,
title: result.title,
notified: false
})
}
console.log("CRAWLER SERVICE: Number of crawler results: " + marketAlerts.length);
try {
const filteredMarketAlerts = marketAlerts.filter((elem) => !subscribedMakretAlerts.find(({ url }) => elem.url === url));
console.log("CRAWLER SERVICE: Number of new crawler results: " + filteredMarketAlerts.length);
await db.MarketAlert.bulkCreate(filteredMarketAlerts);
} catch (e) {
console.log("CRAWLER SERVICE: Could not bulkCreate marketalers reason: ", e);
}
} catch (e) {
console.log("CRAWLER SERVICE: Error crawling. Trying next crawler! ", e);
}
})
};
module.exports = crawlAll;
// crawlAll();

View File

@@ -0,0 +1,29 @@
const db = require("../models/index");
const { allMarketAlerts } = require('../helpers/db/dbHelper');
const { createMarketAlertEmailTemplate, sendBulkEmail } = require('../helpers/awsEmail');
async function processNotifications() {
try {
const marketAlerts = await allMarketAlerts(false, false);
console.log(marketAlerts.length)
await createMarketAlertEmailTemplate();
if (marketAlerts.length > 0) {
console.log("NOTIFICATION SERVICE: Number of new alerts: " + marketAlerts.length)
await sendBulkEmail(marketAlerts);
} else {
console.log("NOTIFICATION SERVICE: No new alerts");
}
await db.MarketAlert.update(
{ notified: true }, /* set attributes' value */
{ where: { notified: false } } /* where criteria */
);
} catch (e) {
console.log("NOTIFICATION SERVICE: could not send notifications reason: ", e);
}
}
module.exports = processNotifications;

View File

@@ -3,27 +3,4 @@
<h2>Koliko okućnice tražite ?</h2> <h2>Koliko okućnice tražite ?</h2>
</div> </div>
<form method="POST" id="form-gardensize"> <% include partials/range %>
<div class="row center-align">
<ul class="collection with-header">
<% for(const gardenSize of gardenSizes) { %>
<li class="collection-item" >
<div id="<%= gardenSize.id %>" onclick="saveAndSubmit(this.id)"><%= gardenSize.title %>
<a href="#" class="secondary-content">
<i class="material-icons">send</i>
</a>
</div>
</li>
<% } %>
</ul>
<input type="hidden" name="gardensize" id="gardensize" />
</div>
</form>
<script>
function saveAndSubmit(id) {
$("#gardensize").val(id);
$("#form-gardensize").submit();
}
</script>

View File

@@ -3,6 +3,7 @@
<head> <head>
<link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet"> <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css"> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/css/materialize.min.css">
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/13.1.5/nouislider.min.css">
<meta name="viewport" content="width=device-width, initial-scale=1.0"/> <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
<script src="https://code.jquery.com/jquery-2.1.1.min.js"></script> <script src="https://code.jquery.com/jquery-2.1.1.min.js"></script>
<meta charset="UTF-8" /> <meta charset="UTF-8" />
@@ -13,5 +14,9 @@
<%-body%> <%-body%>
</div> </div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/materialize/1.0.0/js/materialize.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/noUiSlider/13.1.5/nouislider.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/wnumb/1.1.0/wNumb.min.js"></script>
</body> </body>
</html> </html>

View File

@@ -2,89 +2,128 @@
<h2>U kojem naselju tražite nekretninu?</h2> <h2>U kojem naselju tražite nekretninu?</h2>
</div> </div>
<div class="row center-align" > <div class="row center-align">
<div id="floating-panel"> <div id="floating-panel">
<input id="address" type="textbox" value=""> <input id="address" type="textbox" value="">
<input id="submit" type="button" value="Trazi"> <input id="submit" type="button" value="Trazi">
</div> </div>
<div id="map"></div> <div id="map"></div>
</div> </div>
<form method="POST" id="form-map-output"> <form method="POST" id="form-map-output">
<div class="row center-align"> <div class="row center-align">
<div class="col s6 push-s3"> <div class="col s6 push-s3">
<a id="btnsubmit" href="#" class="welcome-center-button waves-effect waves-light btn"> <a id="btnsubmit" href="#" class="welcome-center-button waves-effect waves-light btn">
Dalje Dalje
</a> </a>
</div>
</div> </div>
<input type="hidden" name="north" id=north /> </div>
<input type="hidden" name="south" id=south /> <input type="hidden" name="north" id=north />
<input type="hidden" name="east" id=east /> <input type="hidden" name="south" id=south />
<input type="hidden" name="west" id=west /> <input type="hidden" name="east" id=east />
</form> <input type="hidden" name="west" id=west />
<script> </form>
<script>
var map; var map;
var municipality = "<%= municipality%>"; var municipality = "<%= municipality%>";
var defaultAddress = document.getElementById('address'); var defaultAddress = document.getElementById('address');
var latLngRestrictions = [];
var BOSNIA_BOUNDS = { var BOSNIA_BOUNDS = {
north: 45.70, north: 45.70,
south: 41.69, south: 41.69,
west: 15.55, west: 15.55,
east: 20.77, east: 20.77,
}; };
function initMap() { function initMap() {
map = new google.maps.Map(document.getElementById('map'), { var geocoder = new google.maps.Geocoder();
center: { lat: -34.397, lng: 150.644 },
zoom: 11, document.getElementById('submit').addEventListener('click', function () {
restriction: { geocodeAddress(geocoder, map, false, latLngRestrictions);
latLngBounds: BOSNIA_BOUNDS, });
strictBounds: false,
},
defaultAddress.value = municipality;
geocodeAddress(geocoder, map, true);
$(document).ready(() => {
$("#btnsubmit").click(() => {
var bounds = map.getBounds();
$("#north").val(map.getBounds().getNorthEast().lat());
$("#south").val(map.getBounds().getSouthWest().lat());
$("#east").val(map.getBounds().getNorthEast().lng());
$("#west").val(map.getBounds().getSouthWest().lng());
$("#form-map-output").submit();
}); });
var geocoder = new google.maps.Geocoder(); });
document.getElementById('submit').addEventListener('click', function () {
geocodeAddress(geocoder, map);
});
function geocodeAddress(geocoder, resultsMap) { }
var address = document.getElementById('address').value;
geocoder.geocode({ 'address': address }, function (results, status) { function geocodeAddress(geocoder, resultsMap, isInit, geocoderRestrictions) {
if (status === 'OK') {
resultsMap.setCenter(results[0].geometry.location);
var marker = new google.maps.Marker({
map: resultsMap, var address = document.getElementById('address').value;
position: results[0].geometry.location let geocoderOptions = geocoderRestrictions
}); ? { 'address': address, 'bounds': geocoderRestrictions }
} else { : { 'address': address }
alert('Geocode was not successful for the following reason: ' + status);
geocoder.geocode(geocoderOptions, function (results, status) {
if (status === 'OK') {
var bounds = results[0].geometry.bounds;
var resultBounds = new google.maps.LatLngBounds(
results[0].geometry.viewport.getSouthWest(),
results[0].geometry.viewport.getNorthEast()
);
if (isInit) {
map = new google.maps.Map(document.getElementById('map'), {
zoom: 11,
});
resultsMap = map
}
// map.fitBounds(resultBounds);
resultsMap.setCenter(results[0].geometry.location);
if (isInit) {
latLngRestrictions = new google.maps.LatLngBounds(
new google.maps.LatLng(bounds.getSouthWest().lat(), bounds.getSouthWest().lng()),
new google.maps.LatLng(bounds.getNorthEast().lng(), bounds.getNorthEast().lng()));
let latLngRestrictionsa = {
west: bounds.getSouthWest().lng(),
east: bounds.getNorthEast().lng(),
north: bounds.getNorthEast().lat(),
south: bounds.getSouthWest().lat()
} }
}); map.setOptions({
restriction: {
latLngBounds: latLngRestrictionsa,
strictBounds: false,
}
})
} else {
resultsMap.setZoom(17);
}
} else {
alert('Geocode was not successful for the following reason: ' + status);
} }
});
defaultAddress.value = municipality; }
geocodeAddress(geocoder, map); </script>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAna8ohfV2HBMcxGk_29vqxU5Z_bDickqg&callback=initMap" async
$(document).ready(() => { defer></script>
$("#btnsubmit").click(() => {
var bounds = map.getBounds();
$("#north").val(bounds.na.l);
$("#south").val(bounds.na.j);
$("#east").val(bounds.ia.l);
$("#west").val(bounds.ia.j);
$("#form-map-output").submit();
});
});
}
</script>
<script src="https://maps.googleapis.com/maps/api/js?key=AIzaSyAna8ohfV2HBMcxGk_29vqxU5Z_bDickqg&callback=initMap"
async defer></script>
</div> </div>

View File

@@ -0,0 +1,53 @@
<form method="POST" id="form-range">
<div class="row center-align no-ui-slider" id="slider"></div>
<div class="col s6 push-s3">
<a id="btnsubmit" href="#" class="welcome-center-button waves-effect waves-light btn">
Dalje
</a>
</div>
<input type="hidden" name="from" id="from" />
<input type="hidden" name="to" id="to" />
</form>
<script>
$(document).ready(() => {
var slider = document.getElementById('slider');
const unitFormat = wNumb({
decimals: 3,
thousand: '.',
suffix: '<%= unit %>'
})
noUiSlider.create(slider, {
start: [<%= rangeFrom.value %>, <%= rangeTo.value %>],
connect: true,
tooltips: true,
step: <%= rangeFrom.step %>,
range: {
'min': <%= rangeFrom.min %>,
'max': <%= rangeTo.max %>
},
format: unitFormat
});
$("#btnsubmit").click(() => {
const sliderValues = slider.noUiSlider.get();
$("#from").val(unitFormat.from(sliderValues[0]));
$("#to").val(unitFormat.from(sliderValues[1]));
$("#form-range").submit();
// });
});
});
</script>

View File

@@ -3,27 +3,4 @@
<h2>Koja Vam okvirna cijena odgovara ?</h2> <h2>Koja Vam okvirna cijena odgovara ?</h2>
</div> </div>
<form method="POST" id="form-price"> <% include partials/range %>
<div class="row center-align">
<ul class="collection with-header">
<% for(const price of prices) { %>
<li class="collection-item" >
<div id="<%= price.id %>" onclick="saveAndSubmit(this.id)"><%= price.title %>
<a href="#" class="secondary-content">
<i class="material-icons">send</i>
</a>
</div>
</li>
<% } %>
</ul>
<input type="hidden" name="price" id="price" />
</div>
</form>
<script>
function saveAndSubmit(id) {
$("#price").val(id);
$("#form-price").submit();
}
</script>

View File

@@ -9,13 +9,25 @@
<input id="email" name="email" type="email" placeholder="vas.email@mail.com" required size="250" /> <input id="email" name="email" type="email" placeholder="vas.email@mail.com" required size="250" />
</div> </div>
</div> </div>
<% if (error) {%>
<div class="row"> <div class="row">
<div class="col s6 push-s3"> <div class="col s6 push-s3">
<h6 style="color: red">Greška ! Unesite ispravan email</h6> <h6 id="error-lable-email" style="color: red"><%= error %> </h6>
</div>
</div> </div>
<%}%> </div>
<div class="row center-align">
<div class="col s6 push-s3">
<input id="confirm" name="confirm" type="email" placeholder="potvrdite.email@mail.com" required size="250" />
</div>
</div>
<div class="row">
<div class="col s6 push-s3">
<h6 id="error-lable-email-confirm" style="color: red"></h6>
</div>
</div>
<div class="row"> <div class="row">
<div class="col s6 push-s3"> <div class="col s6 push-s3">
<a id="submit" href="#" class="welcome-center-button waves-effect waves-light btn"> <a id="submit" href="#" class="welcome-center-button waves-effect waves-light btn">
@@ -31,13 +43,26 @@
</form> </form>
<script> <script>
$(document).ready( () => { $(document).ready(() => {
$("#submit").click( () => { $("#submit").click(() => {
const emailField = document.getElementById('email'); const emailField = document.getElementById('email');
if (emailField.validity.valid){ const emailConfirmField = document.getElementById('confirm');
$("#form-submitquery").submit(); const errorMessage = "Greška ! Unedite validan email";
} $("#error-lable-email").text("");
}); $("#error-lable-email-confirm").text("");
});
</script>
if (!emailField.validity.valid) {
$("#error-lable-email").text(errorMessage);
return
}
if (!emailConfirmField.validity.valid) {
$("#error-lable-email-confirm").text(errorMessage);
return
}
$("#form-submitquery").submit();
});
});
</script>

View File

@@ -1,29 +1,6 @@
<!--suppress HtmlUnknownAnchorTarget --> <!--suppress HtmlUnknownAnchorTarget -->
<div class="row center-align"> <div class="row center-align">
<h2>Do koliko kvadrata tražite nekretninu ?</h2> <h2>Od koliko kvadrata tražite nekretninu ?</h2>
</div> </div>
<form method="POST" id="form-size"> <% include partials/range %>
<div class="row center-align">
<ul class="collection with-header">
<% for(const size of sizes) { %>
<li class="collection-item">
<div id="<%= size.id %>" onclick="saveAndSubmit(this.id)"><%= size.title %>
<a href="#" class="secondary-content">
<i class="material-icons">send</i>
</a>
</div>
</li>
<% } %>
</ul>
<input type="hidden" name="size" id="size" />
</div>
</form>
<script>
function saveAndSubmit(id) {
$("#size").val(id);
$("#form-size").submit();
}
</script>

14
app/views/unsubscribe.ejs Normal file
View File

@@ -0,0 +1,14 @@
<div class="row center-align">
<span class="welcome-big-logo">🤙</span>
</div>
<div class="row center-align">
<div>Uspješno ste se odjavili</div>
</div>
<div class="row">
<div class="col s6 push-s3">
<a href="<%= nextStep %>" class="welcome-center-button waves-effect waves-light btn">
Nova pretraga
</a>
</div>
</div>

5
development.env Normal file
View File

@@ -0,0 +1,5 @@
AMAZON_ACCES_KEY_ID=(your-key-here)
AMAZON_SECRET_ACCESS_KEY=(your-key-here)
AMAZON_REGION=eu-west-1
APP_URL=http://localhost:3001
SOURCE_EMAIL=info@saburly.com

View File

@@ -1,5 +1,5 @@
const welcome = require('./app/controllers/welcome').getWelcome; const welcome = require('./app/controllers/welcome').getWelcome;
const { getRealEstateTypes, postRealEstateTypes} = require('./app/controllers/realEstateTypes'); const { getRealEstateTypes, postRealEstateTypes } = require('./app/controllers/realEstateTypes');
const { getRegion, postRegion } = require('./app/controllers/regions'); const { getRegion, postRegion } = require('./app/controllers/regions');
const { getMunicipality, postMunicipality } = require('./app/controllers/municipalities'); const { getMunicipality, postMunicipality } = require('./app/controllers/municipalities');
const { getSize, postSize } = require('./app/controllers/sizes'); const { getSize, postSize } = require('./app/controllers/sizes');
@@ -9,6 +9,10 @@ const { getQueryReview, postQueryReview } = require('./app/controllers/queryRevi
const { getQuerySubmit, postQuerySubmit } = require('./app/controllers/querySubmit'); const { getQuerySubmit, postQuerySubmit } = require('./app/controllers/querySubmit');
const { getGoAgain } = require('./app/controllers/goAgain'); const { getGoAgain } = require('./app/controllers/goAgain');
const { getNeighborhood, postNeighborhood } = require('./app/controllers/neighborhoodMap'); const { getNeighborhood, postNeighborhood } = require('./app/controllers/neighborhoodMap');
const { getUnsubscribe } = require('./app/controllers/unsubscribe');
const schedule = require('node-schedule');
const crawlAll = require('./app/services/crawlerService')
const processNotifications = require('./app/services/notificationService')
let express = require("express"); let express = require("express");
const path = require("path"); const path = require("path");
@@ -106,7 +110,7 @@ app.post("/api/payforalert", (req, res) => {
} }
}; };
tco.checkout.authorize(params, function(error, data) { tco.checkout.authorize(params, function (error, data) {
if (error) { if (error) {
res.send(error.message); res.send(error.message);
} else { } else {
@@ -115,6 +119,25 @@ app.post("/api/payforalert", (req, res) => {
}); });
}); });
var runServices = async () => {
}
runServices();
var rule = new schedule.RecurrenceRule();
rule.seccond = 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');
});
app.get('/', welcome); app.get('/', welcome);
app.get('/vrstanekretnine/:request_id', getRealEstateTypes); app.get('/vrstanekretnine/:request_id', getRealEstateTypes);
app.get('/vrstanekretnine', getRealEstateTypes); app.get('/vrstanekretnine', getRealEstateTypes);
@@ -146,6 +169,8 @@ app.post('/pregled/:request_id', postQueryReview);
app.get('/posalji/:request_id', getQuerySubmit); app.get('/posalji/:request_id', getQuerySubmit);
app.post('/posalji/:request_id', postQuerySubmit); app.post('/posalji/:request_id', postQuerySubmit);
app.get('/odjava/:request_id', getUnsubscribe);
app.get('/ponovo', getGoAgain); app.get('/ponovo', getGoAgain);
app.use('/assets', express.static('./app/public')); app.use('/assets', express.static('./app/public'));

326
package-lock.json generated
View File

@@ -327,9 +327,9 @@
"dev": true "dev": true
}, },
"bluebird": { "bluebird": {
"version": "3.5.3", "version": "3.5.5",
"resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.3.tgz", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.5.5.tgz",
"integrity": "sha512-/qKPUQlaW1OyR51WeCPBvRnAlnZFUJkCSG5HzGnuIqhgyJtF+T94lFnn33eiazjRm2LAHVy2guNnaq48X9SJuw==" "integrity": "sha512-5am6HnnfN+urzt4yfg7IgTbotDjIT/u8AJpEt0sIU9FtXfVeezXAPKswrG+xKUCOYAINpSdgZVDU6QFh+cuH3w=="
}, },
"body-parser": { "body-parser": {
"version": "1.18.3", "version": "1.18.3",
@@ -559,13 +559,38 @@
} }
}, },
"cliui": { "cliui": {
"version": "4.1.0", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/cliui/-/cliui-4.1.0.tgz", "resolved": "https://registry.npmjs.org/cliui/-/cliui-5.0.0.tgz",
"integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "integrity": "sha512-PYeGSEmmHM6zvoef2w8TPzlrnNpXIjTipYK780YswmIP9vjxmd6Y2a3CB2Ks6/AU8NHjZugXvo8w3oWM2qnwXA==",
"requires": { "requires": {
"string-width": "^2.1.1", "string-width": "^3.1.0",
"strip-ansi": "^4.0.0", "strip-ansi": "^5.2.0",
"wrap-ansi": "^2.0.0" "wrap-ansi": "^5.1.0"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"requires": {
"ansi-regex": "^4.1.0"
}
}
} }
}, },
"cls-bluebird": { "cls-bluebird": {
@@ -577,11 +602,6 @@
"shimmer": "^1.1.0" "shimmer": "^1.1.0"
} }
}, },
"code-point-at": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz",
"integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c="
},
"collection-visit": { "collection-visit": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz", "resolved": "https://registry.npmjs.org/collection-visit/-/collection-visit-1.0.0.tgz",
@@ -701,9 +721,9 @@
"dev": true "dev": true
}, },
"core-js": { "core-js": {
"version": "2.6.5", "version": "2.6.9",
"resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.5.tgz", "resolved": "https://registry.npmjs.org/core-js/-/core-js-2.6.9.tgz",
"integrity": "sha512-klh/kDpwX8hryYL14M9w/xei6vrv6sE8gTHDG7/T/+SEovB/G4ejwcfE/CBzO6Edsu+OETZMZ3wcX/EjUkrl5A==" "integrity": "sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A=="
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@@ -719,6 +739,15 @@
"capture-stack-trace": "^1.0.0" "capture-stack-trace": "^1.0.0"
} }
}, },
"cron-parser": {
"version": "2.12.0",
"resolved": "https://registry.npmjs.org/cron-parser/-/cron-parser-2.12.0.tgz",
"integrity": "sha512-1GU6CQJ6gT9XDEGeTuzfhZgFMf82BSs3ihFA3i2wr4qGKJLhO1kOvaIF9biIo39CaPgzZ17U8FgYxRv/+UR50A==",
"requires": {
"is-nan": "^1.2.1",
"moment-timezone": "^0.5.25"
}
},
"cross-spawn": { "cross-spawn": {
"version": "6.0.5", "version": "6.0.5",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-6.0.5.tgz",
@@ -754,11 +783,12 @@
"integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg==" "integrity": "sha512-a+EPoD+uZiNfh+5fxw2nO9QwFa6nJe2Or35fGY6Ipw1R3R4AGz1d1TEZrCegvw2YTmZ0jXirGYlzxxpYSHwpEg=="
}, },
"d": { "d": {
"version": "1.0.0", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/d/-/d-1.0.0.tgz", "resolved": "https://registry.npmjs.org/d/-/d-1.0.1.tgz",
"integrity": "sha1-dUu1v+VUUdpppYuU1F9MWwRi1Y8=", "integrity": "sha512-m62ShEObQ39CfralilEQRjH6oAMtNCV1xJyEx5LpRYUVN+EviphDgUc/F3hnYbADmkiNs67Y+3ylmlG7Lnu+FA==",
"requires": { "requires": {
"es5-ext": "^0.10.9" "es5-ext": "^0.10.50",
"type": "^1.0.1"
} }
}, },
"dashdash": { "dashdash": {
@@ -799,6 +829,14 @@
"resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz", "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-2.2.1.tgz",
"integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA==" "integrity": "sha512-R9hc1Xa/NOBi9WRVUWg19rl1UB7Tt4kuPd+thNJgFZoxXsTz7ncaPaeIm+40oSGuP33DfMb4sZt1QIGiJzC4EA=="
}, },
"define-properties": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz",
"integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==",
"requires": {
"object-keys": "^1.0.12"
}
},
"define-property": { "define-property": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz", "resolved": "https://registry.npmjs.org/define-property/-/define-property-2.0.2.tgz",
@@ -941,6 +979,11 @@
"resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz", "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz",
"integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==" "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ=="
}, },
"emoji-regex": {
"version": "7.0.3",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-7.0.3.tgz",
"integrity": "sha512-CwBLREIQ7LvYFB0WyRvwhq5N5qPhc6PMjD6bYggFlI5YyDgl+0vxq5VHbMOFqLg7hfWzmu8T5Z1QofhmTIhItA=="
},
"encodeurl": { "encodeurl": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz",
@@ -960,9 +1003,9 @@
"integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w==" "integrity": "sha512-f2LZMYl1Fzu7YSBKg+RoROelpOaNrcGmE9AZubeDfrCEia483oW4MI4VyFd5VNHIgQ/7qm1I0wUHK1eJnn2y2w=="
}, },
"es5-ext": { "es5-ext": {
"version": "0.10.49", "version": "0.10.50",
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.49.tgz", "resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.50.tgz",
"integrity": "sha512-3NMEhi57E31qdzmYp2jwRArIUsj1HI/RxbQ4bgnSB+AIKIxsAmTiK83bYMifIcpWvEc3P1X30DhUKOqEtF/kvg==", "integrity": "sha512-KMzZTPBkeQV/JcSQhI5/z6d9VWJ3EnQ194USTUwIYZ2ZbpN8+SGXQKt1h68EX44+qt+Fzr8DO17vnxrw7c3agw==",
"requires": { "requires": {
"es6-iterator": "~2.0.3", "es6-iterator": "~2.0.3",
"es6-symbol": "~3.1.1", "es6-symbol": "~3.1.1",
@@ -989,13 +1032,13 @@
} }
}, },
"es6-weak-map": { "es6-weak-map": {
"version": "2.0.2", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.2.tgz", "resolved": "https://registry.npmjs.org/es6-weak-map/-/es6-weak-map-2.0.3.tgz",
"integrity": "sha1-XjqzIlH/0VOKH45f+hNXdy+S2W8=", "integrity": "sha512-p5um32HOTO1kP+w7PRnB+5lQ43Z6muuMuIMffvDN8ZB4GcnjLBV6zGStpbASIMk4DCAvEaamhe2zhyCb/QXXsA==",
"requires": { "requires": {
"d": "1", "d": "1",
"es5-ext": "^0.10.14", "es5-ext": "^0.10.46",
"es6-iterator": "^2.0.1", "es6-iterator": "^2.0.3",
"es6-symbol": "^3.1.1" "es6-symbol": "^3.1.1"
} }
}, },
@@ -1353,7 +1396,8 @@
"ansi-regex": { "ansi-regex": {
"version": "2.1.1", "version": "2.1.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"aproba": { "aproba": {
"version": "1.2.0", "version": "1.2.0",
@@ -1374,12 +1418,14 @@
"balanced-match": { "balanced-match": {
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"brace-expansion": { "brace-expansion": {
"version": "1.1.11", "version": "1.1.11",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -1394,17 +1440,20 @@
"code-point-at": { "code-point-at": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"concat-map": { "concat-map": {
"version": "0.0.1", "version": "0.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"console-control-strings": { "console-control-strings": {
"version": "1.1.0", "version": "1.1.0",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"core-util-is": { "core-util-is": {
"version": "1.0.2", "version": "1.0.2",
@@ -1521,7 +1570,8 @@
"inherits": { "inherits": {
"version": "2.0.3", "version": "2.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"ini": { "ini": {
"version": "1.3.5", "version": "1.3.5",
@@ -1533,6 +1583,7 @@
"version": "1.0.0", "version": "1.0.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"number-is-nan": "^1.0.0" "number-is-nan": "^1.0.0"
} }
@@ -1547,6 +1598,7 @@
"version": "3.0.4", "version": "3.0.4",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"brace-expansion": "^1.1.7" "brace-expansion": "^1.1.7"
} }
@@ -1554,12 +1606,14 @@
"minimist": { "minimist": {
"version": "0.0.8", "version": "0.0.8",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"minipass": { "minipass": {
"version": "2.3.5", "version": "2.3.5",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"safe-buffer": "^5.1.2", "safe-buffer": "^5.1.2",
"yallist": "^3.0.0" "yallist": "^3.0.0"
@@ -1578,6 +1632,7 @@
"version": "0.5.1", "version": "0.5.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"minimist": "0.0.8" "minimist": "0.0.8"
} }
@@ -1658,7 +1713,8 @@
"number-is-nan": { "number-is-nan": {
"version": "1.0.1", "version": "1.0.1",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"object-assign": { "object-assign": {
"version": "4.1.1", "version": "4.1.1",
@@ -1670,6 +1726,7 @@
"version": "1.4.0", "version": "1.4.0",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"wrappy": "1" "wrappy": "1"
} }
@@ -1755,7 +1812,8 @@
"safe-buffer": { "safe-buffer": {
"version": "5.1.2", "version": "5.1.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"safer-buffer": { "safer-buffer": {
"version": "2.1.2", "version": "2.1.2",
@@ -1791,6 +1849,7 @@
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"code-point-at": "^1.0.0", "code-point-at": "^1.0.0",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^1.0.0",
@@ -1810,6 +1869,7 @@
"version": "3.0.1", "version": "3.0.1",
"bundled": true, "bundled": true,
"dev": true, "dev": true,
"optional": true,
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^2.0.0"
} }
@@ -1853,12 +1913,14 @@
"wrappy": { "wrappy": {
"version": "1.0.2", "version": "1.0.2",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
}, },
"yallist": { "yallist": {
"version": "3.0.3", "version": "3.0.3",
"bundled": true, "bundled": true,
"dev": true "dev": true,
"optional": true
} }
} }
}, },
@@ -1868,9 +1930,9 @@
"integrity": "sha512-dEkxmX+egB2o4NR80c/q+xzLLzLX+k68/K8xv81XprD+Sk7ZtP14VugeCz+fUwv5FzpWq40pPtAkzPRqT8ka9w==" "integrity": "sha512-dEkxmX+egB2o4NR80c/q+xzLLzLX+k68/K8xv81XprD+Sk7ZtP14VugeCz+fUwv5FzpWq40pPtAkzPRqT8ka9w=="
}, },
"get-caller-file": { "get-caller-file": {
"version": "1.0.3", "version": "2.0.5",
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz", "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
"integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w==" "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
}, },
"get-stream": { "get-stream": {
"version": "4.1.0", "version": "4.1.0",
@@ -1895,9 +1957,9 @@
} }
}, },
"glob": { "glob": {
"version": "7.1.3", "version": "7.1.4",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.4.tgz",
"integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "integrity": "sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A==",
"requires": { "requires": {
"fs.realpath": "^1.0.0", "fs.realpath": "^1.0.0",
"inflight": "^1.0.4", "inflight": "^1.0.4",
@@ -2243,6 +2305,14 @@
"is-path-inside": "^1.0.0" "is-path-inside": "^1.0.0"
} }
}, },
"is-nan": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.2.1.tgz",
"integrity": "sha1-n69ltvttskt/XAYoR16nH5iEAeI=",
"requires": {
"define-properties": "^1.1.1"
}
},
"is-npm": { "is-npm": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz", "resolved": "https://registry.npmjs.org/is-npm/-/is-npm-1.0.0.tgz",
@@ -2353,14 +2423,14 @@
"integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc="
}, },
"js-beautify": { "js-beautify": {
"version": "1.9.1", "version": "1.10.0",
"resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.9.1.tgz", "resolved": "https://registry.npmjs.org/js-beautify/-/js-beautify-1.10.0.tgz",
"integrity": "sha512-oxxvVZdOdUfzk8IOLBF2XUZvl2GoBEfA+b0of4u2EBY/46NlXasi8JdFvazA5lCrf9/lQhTjyVy2QCUW7iq0MQ==", "integrity": "sha512-OMwf/tPDpE/BLlYKqZOhqWsd3/z2N3KOlyn1wsCRGFwViE8LOQTcDtathQvHvZc+q+zWmcNAbwKSC+iJoMaH2Q==",
"requires": { "requires": {
"config-chain": "^1.1.12", "config-chain": "^1.1.12",
"editorconfig": "^0.15.2", "editorconfig": "^0.15.3",
"glob": "^7.1.3", "glob": "^7.1.3",
"mkdirp": "~0.5.0", "mkdirp": "~0.5.1",
"nopt": "~4.0.1" "nopt": "~4.0.1"
} }
}, },
@@ -2440,6 +2510,11 @@
"resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz",
"integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg=="
}, },
"long-timeout": {
"version": "0.1.1",
"resolved": "https://registry.npmjs.org/long-timeout/-/long-timeout-0.1.1.tgz",
"integrity": "sha1-lyHXiLR+C8taJMLivuGg2lXatRQ="
},
"lowercase-keys": { "lowercase-keys": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz",
@@ -2685,6 +2760,16 @@
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz",
"integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA=="
}, },
"node-schedule": {
"version": "1.3.2",
"resolved": "https://registry.npmjs.org/node-schedule/-/node-schedule-1.3.2.tgz",
"integrity": "sha512-GIND2pHMHiReSZSvS6dpZcDH7pGPGFfWBIEud6S00Q8zEIzAs9ommdyRK1ZbQt8y1LyZsJYZgPnyi7gpU2lcdw==",
"requires": {
"cron-parser": "^2.7.3",
"long-timeout": "0.1.1",
"sorted-array-functions": "^1.0.0"
}
},
"nodemon": { "nodemon": {
"version": "1.19.0", "version": "1.19.0",
"resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.0.tgz", "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-1.19.0.tgz",
@@ -2751,11 +2836,6 @@
"boolbase": "~1.0.0" "boolbase": "~1.0.0"
} }
}, },
"number-is-nan": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz",
"integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0="
},
"oauth-sign": { "oauth-sign": {
"version": "0.9.0", "version": "0.9.0",
"resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz", "resolved": "https://registry.npmjs.org/oauth-sign/-/oauth-sign-0.9.0.tgz",
@@ -2797,6 +2877,11 @@
} }
} }
}, },
"object-keys": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
"integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA=="
},
"object-visit": { "object-visit": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz", "resolved": "https://registry.npmjs.org/object-visit/-/object-visit-1.0.1.tgz",
@@ -3309,14 +3394,14 @@
"integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I=" "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
}, },
"require-main-filename": { "require-main-filename": {
"version": "1.0.1", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-1.0.1.tgz", "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
"integrity": "sha1-l/cXtp1IeE9fUmpsWqj/3aBVpNE=" "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
}, },
"resolve": { "resolve": {
"version": "1.10.0", "version": "1.11.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.10.0.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.11.1.tgz",
"integrity": "sha512-3sUr9aq5OfSg2S9pNtPA9hL1FVEAjvfOC4leW0SNf/mpnaakz2a9femSd6LqAww2RaFctwyf1lCqnTHuF1rxDg==", "integrity": "sha512-vIpgF6wfuJOZI7KKKSP+HmiKggadPQAdsp5HiC1mvqnfp0gF1vdwgBWZIdrVft9pgqoMFQN+R7BSWZiBxx+BBw==",
"requires": { "requires": {
"path-parse": "^1.0.6" "path-parse": "^1.0.6"
} }
@@ -3440,9 +3525,9 @@
} }
}, },
"sequelize-cli": { "sequelize-cli": {
"version": "5.4.0", "version": "5.5.0",
"resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-5.4.0.tgz", "resolved": "https://registry.npmjs.org/sequelize-cli/-/sequelize-cli-5.5.0.tgz",
"integrity": "sha512-4Gvl0yH0T3hhSdiiOci3+IKIfVG9x2os0hGWsbfa8QuyGgk9mZOqgTBnSCRtuxsdAyzUix9kfcTnfNolVNtprg==", "integrity": "sha512-twVQ02alCpr2XvxNmpi32C48WZs6xHTH1OFTfTS5Meg3BVqOM8ghiZoml4FITFjlD8sAJSQjlAHTwqTbuolA6Q==",
"requires": { "requires": {
"bluebird": "^3.5.3", "bluebird": "^3.5.3",
"cli-color": "^1.4.0", "cli-color": "^1.4.0",
@@ -3451,7 +3536,7 @@
"lodash": "^4.17.5", "lodash": "^4.17.5",
"resolve": "^1.5.0", "resolve": "^1.5.0",
"umzug": "^2.1.0", "umzug": "^2.1.0",
"yargs": "^12.0.5" "yargs": "^13.1.0"
} }
}, },
"serve-static": { "serve-static": {
@@ -3633,6 +3718,11 @@
} }
} }
}, },
"sorted-array-functions": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/sorted-array-functions/-/sorted-array-functions-1.2.0.tgz",
"integrity": "sha512-sWpjPhIZJtqO77GN+LD8dDsDKcWZ9GCOJNqKzi1tvtjGIzwfoyuRH8S0psunmc6Z5P+qfDqztSbwYR5X/e1UTg=="
},
"source-map": { "source-map": {
"version": "0.5.7", "version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -3721,6 +3811,7 @@
"version": "2.1.1", "version": "2.1.1",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz",
"integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==",
"dev": true,
"requires": { "requires": {
"is-fullwidth-code-point": "^2.0.0", "is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^4.0.0" "strip-ansi": "^4.0.0"
@@ -3738,6 +3829,7 @@
"version": "4.0.0", "version": "4.0.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz",
"integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=",
"dev": true,
"requires": { "requires": {
"ansi-regex": "^3.0.0" "ansi-regex": "^3.0.0"
}, },
@@ -3745,7 +3837,8 @@
"ansi-regex": { "ansi-regex": {
"version": "3.0.0", "version": "3.0.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz",
"integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=" "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=",
"dev": true
} }
} }
}, },
@@ -3944,6 +4037,11 @@
"resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz",
"integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=" "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q="
}, },
"type": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/type/-/type-1.0.1.tgz",
"integrity": "sha512-MAM5dBMJCJNKs9E7JXo4CXRAansRfG0nlJxW7Wf6GZzSOvH31zClSaHdIMWLehe/EGMBkqeC55rrkaOr5Oo7Nw=="
},
"type-is": { "type-is": {
"version": "1.6.16", "version": "1.6.16",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz",
@@ -4206,38 +4304,36 @@
} }
}, },
"wrap-ansi": { "wrap-ansi": {
"version": "2.1.0", "version": "5.1.0",
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-5.1.0.tgz",
"integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "integrity": "sha512-QC1/iN/2/RPVJ5jYK8BGttj5z83LmSKmvbvrXPNCLZSEb32KKVDJDl/MOt2N01qU2H/FkzEa9PKto1BqDjtd7Q==",
"requires": { "requires": {
"string-width": "^1.0.1", "ansi-styles": "^3.2.0",
"strip-ansi": "^3.0.1" "string-width": "^3.0.0",
"strip-ansi": "^5.0.0"
}, },
"dependencies": { "dependencies": {
"is-fullwidth-code-point": { "ansi-regex": {
"version": "1.0.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
"requires": {
"number-is-nan": "^1.0.0"
}
}, },
"string-width": { "string-width": {
"version": "1.0.2", "version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"requires": { "requires": {
"code-point-at": "^1.0.0", "emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^1.0.0", "is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^3.0.0" "strip-ansi": "^5.1.0"
} }
}, },
"strip-ansi": { "strip-ansi": {
"version": "3.0.1", "version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"requires": { "requires": {
"ansi-regex": "^2.0.0" "ansi-regex": "^4.1.0"
} }
} }
} }
@@ -4294,28 +4390,52 @@
"integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=" "integrity": "sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI="
}, },
"yargs": { "yargs": {
"version": "12.0.5", "version": "13.2.4",
"resolved": "https://registry.npmjs.org/yargs/-/yargs-12.0.5.tgz", "resolved": "https://registry.npmjs.org/yargs/-/yargs-13.2.4.tgz",
"integrity": "sha512-Lhz8TLaYnxq/2ObqHDql8dX8CJi97oHxrjUcYtzKbbykPtVW9WB+poxI+NM2UIzsMgNCZTIf0AQwsjK5yMAqZw==", "integrity": "sha512-HG/DWAJa1PAnHT9JAhNa8AbAv3FPaiLzioSjCcmuXXhP8MlpHO5vwls4g4j6n30Z74GVQj8Xa62dWVx1QCGklg==",
"requires": { "requires": {
"cliui": "^4.0.0", "cliui": "^5.0.0",
"decamelize": "^1.2.0",
"find-up": "^3.0.0", "find-up": "^3.0.0",
"get-caller-file": "^1.0.1", "get-caller-file": "^2.0.1",
"os-locale": "^3.0.0", "os-locale": "^3.1.0",
"require-directory": "^2.1.1", "require-directory": "^2.1.1",
"require-main-filename": "^1.0.1", "require-main-filename": "^2.0.0",
"set-blocking": "^2.0.0", "set-blocking": "^2.0.0",
"string-width": "^2.0.0", "string-width": "^3.0.0",
"which-module": "^2.0.0", "which-module": "^2.0.0",
"y18n": "^3.2.1 || ^4.0.0", "y18n": "^4.0.0",
"yargs-parser": "^11.1.1" "yargs-parser": "^13.1.0"
},
"dependencies": {
"ansi-regex": {
"version": "4.1.0",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.0.tgz",
"integrity": "sha512-1apePfXM1UOSqw0o9IiFAovVz9M5S1Dg+4TrDwfMewQ6p/rmMueb7tWZjQ1rx4Loy1ArBggoqGpfqqdI4rondg=="
},
"string-width": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-3.1.0.tgz",
"integrity": "sha512-vafcv6KjVZKSgz06oM/H6GDBrAtz8vdhQakGjFIvNrHA6y3HCF1CInLy+QLq8dTJPQ1b+KDUqDFctkdRW44e1w==",
"requires": {
"emoji-regex": "^7.0.1",
"is-fullwidth-code-point": "^2.0.0",
"strip-ansi": "^5.1.0"
}
},
"strip-ansi": {
"version": "5.2.0",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
"integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
"requires": {
"ansi-regex": "^4.1.0"
}
}
} }
}, },
"yargs-parser": { "yargs-parser": {
"version": "11.1.1", "version": "13.1.1",
"resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-11.1.1.tgz", "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-13.1.1.tgz",
"integrity": "sha512-C6kB/WJDiaxONLJQnF8ccx9SEeoTTLek8RVbaOIsrAUS8VrBEXfmeSnCZxygc+XC2sNMBIwOOnfcxiynjHsVSQ==", "integrity": "sha512-oVAVsHz6uFrg3XQheFII8ESO2ssAf9luWuAd6Wexsu4F3OtIW0o8IribPXYrD4WC24LWtPrJlGy87y5udK+dxQ==",
"requires": { "requires": {
"camelcase": "^5.0.0", "camelcase": "^5.0.0",
"decamelize": "^1.2.0" "decamelize": "^1.2.0"

View File

@@ -25,6 +25,7 @@
"2checkout-node": "0.0.1", "2checkout-node": "0.0.1",
"@sendgrid/mail": "^6.3.1", "@sendgrid/mail": "^6.3.1",
"aws-sdk": "^2.422.0", "aws-sdk": "^2.422.0",
"bluebird": "^3.5.5",
"cheerio": "^1.0.0-rc.2", "cheerio": "^1.0.0-rc.2",
"compression": "^1.7.4", "compression": "^1.7.4",
"dotenv": "^7.0.0", "dotenv": "^7.0.0",
@@ -33,10 +34,11 @@
"express-ejs-layouts": "^2.5.0", "express-ejs-layouts": "^2.5.0",
"express-layout": "^0.1.0", "express-layout": "^0.1.0",
"node-fetch": "^2.3.0", "node-fetch": "^2.3.0",
"node-schedule": "^1.3.2",
"pg": "^7.10.0", "pg": "^7.10.0",
"react-step-wizard": "^5.1.0", "react-step-wizard": "^5.1.0",
"sequelize": "^4.43.2", "sequelize": "^4.43.2",
"sequelize-cli": "^5.4.0" "sequelize-cli": "^5.5.0"
}, },
"devDependencies": { "devDependencies": {
"nodemon": "^1.19.0" "nodemon": "^1.19.0"