Compare commits
3 Commits
view-ad-ki
...
migrate-to
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
e356f7a177 | ||
|
|
055ca54002 | ||
|
|
3f8e6438b2 |
@@ -1 +0,0 @@
|
||||
{}
|
||||
@@ -1 +0,0 @@
|
||||
SECRET_VARIABLE=mysecretval
|
||||
@@ -1,5 +0,0 @@
|
||||
{
|
||||
"key": "value",
|
||||
"key2": "value2",
|
||||
"other_key": "other_value"
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
{
|
||||
"EventSourceMappings": [
|
||||
{
|
||||
"EventSourceArn": "your event source arn",
|
||||
"StartingPosition": "LATEST",
|
||||
"BatchSize": 100,
|
||||
"Enabled": true
|
||||
}
|
||||
],
|
||||
"ScheduleEvents": [
|
||||
{
|
||||
"ScheduleName": "node-lambda-test-schedule",
|
||||
"ScheduleState": "ENABLED",
|
||||
"ScheduleExpression": "rate(1 hour)",
|
||||
"Input":
|
||||
{
|
||||
"key1": "value",
|
||||
"key2": "value"
|
||||
}
|
||||
}
|
||||
],
|
||||
"S3Events": [{
|
||||
"Bucket": "BUCKET_NAME",
|
||||
"Events": [
|
||||
"s3:ObjectCreated:*"
|
||||
],
|
||||
"Filter": {
|
||||
"Key": {
|
||||
"FilterRules": [{
|
||||
"Name": "prefix",
|
||||
"Value": "STRING_VALUE"
|
||||
}]
|
||||
}
|
||||
}
|
||||
}]
|
||||
}
|
||||
6
ma-api/.gitignore
vendored
Normal file
6
ma-api/.gitignore
vendored
Normal file
@@ -0,0 +1,6 @@
|
||||
# package directories
|
||||
node_modules
|
||||
jspm_packages
|
||||
|
||||
# Serverless directories
|
||||
.serverless
|
||||
6
ma-api/README.md
Normal file
6
ma-api/README.md
Normal file
@@ -0,0 +1,6 @@
|
||||
# How to deploy automatically:
|
||||
|
||||
1. set up aws cli with aws configure
|
||||
2. cd ma-api
|
||||
3. serverless deploy
|
||||
|
||||
83
ma-api/handler.js
Normal file
83
ma-api/handler.js
Normal file
@@ -0,0 +1,83 @@
|
||||
'use strict';
|
||||
const path = require("path");
|
||||
const bodyParser = require("body-parser");
|
||||
const MarketAlert = require("./lib/MarketAlert");
|
||||
const sendNotification = require("./lib/sendnotification");
|
||||
const scrapTheItems = require("./lib/scraptheitems");
|
||||
const sequelize = require("./lib/db.js");
|
||||
const Twocheckout = require("2checkout-node");
|
||||
|
||||
|
||||
module.exports.sendnotification = async (event, context) => {
|
||||
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 } }
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({
|
||||
message: 'Notifications sent',
|
||||
input: event,
|
||||
}),
|
||||
};
|
||||
|
||||
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
|
||||
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
|
||||
};
|
||||
|
||||
|
||||
module.exports.getitems = async (event, context) => {
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({
|
||||
message: 'Get Items',
|
||||
input: event,
|
||||
}),
|
||||
};
|
||||
|
||||
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
|
||||
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
|
||||
};
|
||||
|
||||
module.exports.marketalerts = async (event, context) => {
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({
|
||||
message: 'Market alerts',
|
||||
input: event,
|
||||
}),
|
||||
};
|
||||
|
||||
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
|
||||
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
|
||||
};
|
||||
|
||||
module.exports.payforalert = async (event, context) => {
|
||||
return {
|
||||
statusCode: 200,
|
||||
body: JSON.stringify({
|
||||
message: 'Pay for alert',
|
||||
input: event,
|
||||
}),
|
||||
};
|
||||
|
||||
// Use this code if you don't use the http event with the LAMBDA-PROXY integration
|
||||
// return { message: 'Go Serverless v1.0! Your function executed successfully!', event };
|
||||
};
|
||||
|
||||
17
ma-api/lib/MarketAlert.js
Normal file
17
ma-api/lib/MarketAlert.js
Normal file
@@ -0,0 +1,17 @@
|
||||
const Sequelize = require("sequelize");
|
||||
const sequelize = require("./db.js");
|
||||
const MarketAlert = sequelize.define("market_alert", {
|
||||
id: {
|
||||
type: Sequelize.INTEGER,
|
||||
autoIncrement: true,
|
||||
primaryKey: true
|
||||
},
|
||||
olx_url: Sequelize.STRING,
|
||||
last_date: Sequelize.STRING,
|
||||
email: {
|
||||
type: Sequelize.STRING,
|
||||
allowNull: false
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = MarketAlert;
|
||||
8
ma-api/lib/arethereanynewitems.js
Normal file
8
ma-api/lib/arethereanynewitems.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const convertToDate = require("./convertToDate");
|
||||
function areThereAnyNewItems(lastItemDate, controlDate) {
|
||||
if (!lastItemDate) {
|
||||
return true;
|
||||
}
|
||||
return new Date(controlDate) < convertToDate(lastItemDate);
|
||||
}
|
||||
module.exports = areThereAnyNewItems;
|
||||
23
ma-api/lib/config/config.json
Normal file
23
ma-api/lib/config/config.json
Normal file
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"development": {
|
||||
"username": "javimistaging",
|
||||
"password": "10MinutaSvaki*Dan",
|
||||
"database": "database_development",
|
||||
"host": "javimi-staging.cluster-c52xdqogwrl2.eu-central-1.rds.amazonaws.com",
|
||||
"dialect": "mysql"
|
||||
},
|
||||
"test": {
|
||||
"username": "root",
|
||||
"password": null,
|
||||
"database": "database_test",
|
||||
"host": "127.0.0.1",
|
||||
"dialect": "mysql"
|
||||
},
|
||||
"production": {
|
||||
"username": "root",
|
||||
"password": null,
|
||||
"database": "database_production",
|
||||
"host": "127.0.0.1",
|
||||
"dialect": "mysql"
|
||||
}
|
||||
}
|
||||
13
ma-api/lib/convertToDate.js
Normal file
13
ma-api/lib/convertToDate.js
Normal file
@@ -0,0 +1,13 @@
|
||||
function convertToDate(date) {
|
||||
const [dan, mjesec, godina] = date
|
||||
.split(". u ")[0]
|
||||
.split(".")
|
||||
.map(el => Number(el));
|
||||
const [sati, minute] = date
|
||||
.split(". u ")[1]
|
||||
.split(":")
|
||||
.map(el => Number(el));
|
||||
return new Date(godina, mjesec, dan, sati, minute);
|
||||
}
|
||||
|
||||
module.exports = convertToDate;
|
||||
8
ma-api/lib/db.js
Normal file
8
ma-api/lib/db.js
Normal file
@@ -0,0 +1,8 @@
|
||||
const Sequelize = require("sequelize");
|
||||
const sequelize = new Sequelize("sql7276322", "sql7276322@localhost", "RS53ihYlg9", {
|
||||
host: "sql7.freemysqlhosting.net",
|
||||
dialect: "mysql",
|
||||
operatorsAliases: false
|
||||
});
|
||||
|
||||
module.exports = sequelize;
|
||||
96
ma-api/lib/index.js
Normal file
96
ma-api/lib/index.js
Normal file
@@ -0,0 +1,96 @@
|
||||
let express = require("express");
|
||||
const path = require("path");
|
||||
const bodyParser = require("body-parser");
|
||||
const MarketAlert = require("./MarketAlert");
|
||||
const sendNotification = require("./utils/sendnotification");
|
||||
const scrapTheItems = require("./utils/scraptheitems");
|
||||
const sequelize = require("./db.js");
|
||||
const Twocheckout = require("2checkout-node");
|
||||
|
||||
const app = express();
|
||||
app.use(bodyParser.json());
|
||||
app.use(bodyParser.urlencoded({ extended: true }));
|
||||
|
||||
const port = process.env.PORT || 5000;
|
||||
|
||||
app.get("/sendnotifications", async function(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("/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("/marketalerts", function(req, res) {
|
||||
const { email, last_date, olx_url } = req.body;
|
||||
console.log(email, last_date, olx_url);
|
||||
res.json({ message: "Market Alert Created!" });
|
||||
// sequelize.sync().then(() =>
|
||||
// MarketAlert.create({
|
||||
// olx_url,
|
||||
// last_date,
|
||||
// email
|
||||
// })
|
||||
// );
|
||||
// res.json({ message: "Market Alert Created!" });
|
||||
});
|
||||
|
||||
app.post("/payforalert", function(request, response) {
|
||||
let tco = new Twocheckout({
|
||||
sellerId: "901402692",
|
||||
privateKey: "A28DCE5F-9292-405C-8161-F84D8BB83AFC",
|
||||
sandbox: true
|
||||
});
|
||||
|
||||
let params = {
|
||||
merchantOrderId: "123",
|
||||
token: request.body.token,
|
||||
currency: "USD",
|
||||
total: "2.00",
|
||||
billingAddr: {
|
||||
name: "Testing Tester",
|
||||
addrLine1: "123 Test St",
|
||||
city: "Sarajevo",
|
||||
state: "BiH",
|
||||
zipCode: "71000",
|
||||
country: "BiH",
|
||||
email: request.body.email,
|
||||
phoneNumber: "5555555555"
|
||||
}
|
||||
};
|
||||
|
||||
tco.checkout.authorize(params, function(error, data) {
|
||||
if (error) {
|
||||
response.send(error.message);
|
||||
} else {
|
||||
response.send(data.response.responseMsg);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
app.listen(port, () => console.log(`Example app listening on port ${port}!`));
|
||||
27
ma-api/lib/migrations/20190310042110-create-market-alert.js
Normal file
27
ma-api/lib/migrations/20190310042110-create-market-alert.js
Normal file
@@ -0,0 +1,27 @@
|
||||
'use strict';
|
||||
module.exports = {
|
||||
up: (queryInterface, Sequelize) => {
|
||||
return queryInterface.createTable('MarketAlerts', {
|
||||
id: {
|
||||
allowNull: false,
|
||||
autoIncrement: true,
|
||||
primaryKey: true,
|
||||
type: Sequelize.INTEGER
|
||||
},
|
||||
olx_url: {
|
||||
type: Sequelize.STRING
|
||||
},
|
||||
createdAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
},
|
||||
updatedAt: {
|
||||
allowNull: false,
|
||||
type: Sequelize.DATE
|
||||
}
|
||||
});
|
||||
},
|
||||
down: (queryInterface, Sequelize) => {
|
||||
return queryInterface.dropTable('MarketAlerts');
|
||||
}
|
||||
};
|
||||
37
ma-api/lib/models/index.js
Normal file
37
ma-api/lib/models/index.js
Normal file
@@ -0,0 +1,37 @@
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const Sequelize = require('sequelize');
|
||||
const basename = path.basename(__filename);
|
||||
const env = process.env.NODE_ENV || 'development';
|
||||
const config = require(__dirname + '/../config/config.json')[env];
|
||||
const db = {};
|
||||
|
||||
let sequelize;
|
||||
if (config.use_env_variable) {
|
||||
sequelize = new Sequelize(process.env[config.use_env_variable], config);
|
||||
} else {
|
||||
sequelize = new Sequelize(config.database, config.username, config.password, config);
|
||||
}
|
||||
|
||||
fs
|
||||
.readdirSync(__dirname)
|
||||
.filter(file => {
|
||||
return (file.indexOf('.') !== 0) && (file !== basename) && (file.slice(-3) === '.js');
|
||||
})
|
||||
.forEach(file => {
|
||||
const model = sequelize['import'](path.join(__dirname, file));
|
||||
db[model.name] = model;
|
||||
});
|
||||
|
||||
Object.keys(db).forEach(modelName => {
|
||||
if (db[modelName].associate) {
|
||||
db[modelName].associate(db);
|
||||
}
|
||||
});
|
||||
|
||||
db.sequelize = sequelize;
|
||||
db.Sequelize = Sequelize;
|
||||
|
||||
module.exports = db;
|
||||
10
ma-api/lib/models/marketalert.js
Normal file
10
ma-api/lib/models/marketalert.js
Normal file
@@ -0,0 +1,10 @@
|
||||
'use strict';
|
||||
module.exports = (sequelize, DataTypes) => {
|
||||
const MarketAlert = sequelize.define('MarketAlert', {
|
||||
olx_url: DataTypes.STRING
|
||||
}, {});
|
||||
MarketAlert.associate = function(models) {
|
||||
// associations can be defined here
|
||||
};
|
||||
return MarketAlert;
|
||||
};
|
||||
42
ma-api/lib/scraptheitems.js
Normal file
42
ma-api/lib/scraptheitems.js
Normal file
@@ -0,0 +1,42 @@
|
||||
let fetch = require("node-fetch");
|
||||
let cheerio = require("cheerio");
|
||||
const areThereAnyNewItems = require("./arethereanynewitems");
|
||||
|
||||
async function scrapTheItems(url, controlDate, noNewItems = false) {
|
||||
let items = [];
|
||||
let response = await fetch(url);
|
||||
const body = await response.text();
|
||||
const $ = cheerio.load(body);
|
||||
$("#rezultatipretrage")
|
||||
.find(".listitem")
|
||||
.each(async (index, elem) => {
|
||||
if (noNewItems) return;
|
||||
const itemDate = $(elem)
|
||||
.find(".cijena > .datum > div")
|
||||
.first()
|
||||
.attr("data-cijelidatum");
|
||||
|
||||
if (controlDate && !areThereAnyNewItems(itemDate, controlDate)) {
|
||||
noNewItems = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const id = $(elem)
|
||||
.find("a")
|
||||
.first()
|
||||
.attr("href");
|
||||
const cijena = $(elem)
|
||||
.find(".cijena > .datum > span")
|
||||
.first()
|
||||
.text();
|
||||
const image = $(elem)
|
||||
.find("a > .slika > img")
|
||||
.first()
|
||||
.attr("src");
|
||||
|
||||
items.push({ url: id, price: cijena, image, date: itemDate });
|
||||
});
|
||||
return items;
|
||||
}
|
||||
|
||||
module.exports = scrapTheItems;
|
||||
33
ma-api/lib/sendnotification.js
Normal file
33
ma-api/lib/sendnotification.js
Normal file
@@ -0,0 +1,33 @@
|
||||
const scrapTheItems = require("./scraptheitems");
|
||||
const convertToDate = require("./convertToDate");
|
||||
const sgMail = require("@sendgrid/mail");
|
||||
// should be process.env.SENDGRID_API_KEY
|
||||
sgMail.setApiKey(
|
||||
"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"
|
||||
);
|
||||
|
||||
async function sendNotification(marketAlert) {
|
||||
const { id, email, olx_url, last_date } = marketAlert;
|
||||
let url =
|
||||
"https://www.olx.ba/pretraga?" + olx_url + "&sort_order=desc&sort_po=datum";
|
||||
let newItems = await scrapTheItems(url);
|
||||
let lastDate = newItems.length && newItems[0].date;
|
||||
let message =
|
||||
newItems.length &&
|
||||
newItems.reduce(
|
||||
(mes, item) => mes + `<strong>${item.url} i ${item.price}</strong>`,
|
||||
""
|
||||
);
|
||||
const msg = {
|
||||
to: email,
|
||||
from: "test@example.com",
|
||||
subject: "Market Alert",
|
||||
text: "New items on olx",
|
||||
html: message
|
||||
};
|
||||
if (message) {
|
||||
await sgMail.send(msg);
|
||||
return { id, date: String(convertToDate(lastDate)) };
|
||||
}
|
||||
}
|
||||
module.exports = sendNotification;
|
||||
1918
ma-api/package-lock.json
generated
Normal file
1918
ma-api/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
21
ma-api/package.json
Normal file
21
ma-api/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "backend",
|
||||
"version": "1.0.0",
|
||||
"description": "",
|
||||
"main": "index.js",
|
||||
"scripts": {
|
||||
"test": "echo \"Error: no test specified\" && exit 1"
|
||||
},
|
||||
"author": "Ehvan Gradanin",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"2checkout-node": "0.0.1",
|
||||
"@sendgrid/mail": "^6.3.1",
|
||||
"cheerio": "^1.0.0-rc.2",
|
||||
"express": "^4.16.4",
|
||||
"mysql2": "^1.6.4",
|
||||
"node-fetch": "^2.3.0",
|
||||
"sequelize": "^4.42.0",
|
||||
"sequelize-cli": "^5.4.0"
|
||||
}
|
||||
}
|
||||
109
ma-api/serverless.yml
Normal file
109
ma-api/serverless.yml
Normal file
@@ -0,0 +1,109 @@
|
||||
# Welcome to Serverless!
|
||||
#
|
||||
# This file is the main config file for your service.
|
||||
# It's very minimal at this point and uses default values.
|
||||
# You can always add more config options for more control.
|
||||
# We've included some commented out config examples here.
|
||||
# Just uncomment any of them to get that config option.
|
||||
#
|
||||
# For full config options, check the docs:
|
||||
# docs.serverless.com
|
||||
#
|
||||
# Happy Coding!
|
||||
|
||||
service: ma-api # NOTE: update this with your service name
|
||||
|
||||
# You can pin your service to only deploy with a specific Serverless version
|
||||
# Check out our docs for more details
|
||||
# frameworkVersion: "=X.X.X"
|
||||
|
||||
provider:
|
||||
name: aws
|
||||
runtime: nodejs8.10
|
||||
|
||||
# you can overwrite defaults here
|
||||
# stage: dev
|
||||
# region: us-east-1
|
||||
|
||||
# you can add statements to the Lambda function's IAM Role here
|
||||
# iamRoleStatements:
|
||||
# - Effect: "Allow"
|
||||
# Action:
|
||||
# - "s3:ListBucket"
|
||||
# Resource: { "Fn::Join" : ["", ["arn:aws:s3:::", { "Ref" : "ServerlessDeploymentBucket" } ] ] }
|
||||
# - Effect: "Allow"
|
||||
# Action:
|
||||
# - "s3:PutObject"
|
||||
# Resource:
|
||||
# Fn::Join:
|
||||
# - ""
|
||||
# - - "arn:aws:s3:::"
|
||||
# - "Ref" : "ServerlessDeploymentBucket"
|
||||
# - "/*"
|
||||
|
||||
# you can define service wide environment variables here
|
||||
# environment:
|
||||
# variable1: value1
|
||||
|
||||
# you can add packaging information here
|
||||
#package:
|
||||
# include:
|
||||
# - include-me.js
|
||||
# - include-me-dir/**
|
||||
# exclude:
|
||||
# - exclude-me.js
|
||||
# - exclude-me-dir/**
|
||||
|
||||
functions:
|
||||
sendnotification:
|
||||
handler: handler.sendnotification
|
||||
events:
|
||||
- http:
|
||||
path: notifications/send
|
||||
method: post
|
||||
|
||||
# The following are a few example events you can configure
|
||||
# NOTE: Please make sure to change your handler code to work with those events
|
||||
# Check the event documentation for details
|
||||
# events:
|
||||
# - http:
|
||||
# path: users/create
|
||||
# method: get
|
||||
# - websocket: $connect
|
||||
# - s3: ${env:BUCKET}
|
||||
# - schedule: rate(10 minutes)
|
||||
# - sns: greeter-topic
|
||||
# - stream: arn:aws:dynamodb:region:XXXXXX:table/foo/stream/1970-01-01T00:00:00.000
|
||||
# - alexaSkill: amzn1.ask.skill.xx-xx-xx-xx
|
||||
# - alexaSmartHome: amzn1.ask.skill.xx-xx-xx-xx
|
||||
# - iot:
|
||||
# sql: "SELECT * FROM 'some_topic'"
|
||||
# - cloudwatchEvent:
|
||||
# event:
|
||||
# source:
|
||||
# - "aws.ec2"
|
||||
# detail-type:
|
||||
# - "EC2 Instance State-change Notification"
|
||||
# detail:
|
||||
# state:
|
||||
# - pending
|
||||
# - cloudwatchLog: '/aws/lambda/hello'
|
||||
# - cognitoUserPool:
|
||||
# pool: MyUserPool
|
||||
# trigger: PreSignUp
|
||||
|
||||
# Define function environment variables here
|
||||
# environment:
|
||||
# variable2: value2
|
||||
|
||||
# you can add CloudFormation resource templates here
|
||||
#resources:
|
||||
# Resources:
|
||||
# NewResource:
|
||||
# Type: AWS::S3::Bucket
|
||||
# Properties:
|
||||
# BucketName: my-new-bucket
|
||||
# Outputs:
|
||||
# NewOutput:
|
||||
# Description: "Description for the output"
|
||||
# Value: "Some output value"
|
||||
Reference in New Issue
Block a user