diff --git a/.gitignore b/.gitignore
index 6704566..dd46c96 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,3 +1,5 @@
+.zip
+
# Logs
logs
*.log
diff --git a/DIRECTIONS.md b/DIRECTIONS.md
new file mode 100644
index 0000000..d885fc6
--- /dev/null
+++ b/DIRECTIONS.md
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/assets/Directions.jpg b/assets/Directions.jpg
new file mode 100644
index 0000000..4ad5dbf
Binary files /dev/null and b/assets/Directions.jpg differ
diff --git a/events/.env.sample b/events/.env.sample
new file mode 100644
index 0000000..856b6d2
--- /dev/null
+++ b/events/.env.sample
@@ -0,0 +1,7 @@
+DATABASE_URL=postgres://localhost:5432/
+
+MAILGUN_KEY=
+
+MAILGUN_DOMAIN=
+
+TICKET_API=
diff --git a/events/index.js b/events/index.js
new file mode 100644
index 0000000..ba61bad
--- /dev/null
+++ b/events/index.js
@@ -0,0 +1,185 @@
+require('dotenv').config();
+
+const axios = require('axios');
+const Mailgun = require('mailgun.js');
+const formData = require('form-data');
+const { Sequelize, DataTypes } = require('sequelize');
+
+const DATABASE_URL = process.env.DATABASE_URL;
+const db = new Sequelize(DATABASE_URL);
+
+const subs = db.define('Subscriptions', {
+ username: {
+ type: DataTypes.STRING,
+ required: true,
+ },
+ type: {
+ type: DataTypes.ENUM(['weather', 'events']),
+ required: true,
+ },
+});
+
+const users = db.define('Users', {
+ username: {
+ type: DataTypes.STRING,
+ required: true,
+ primaryKey: true,
+ unique: true,
+ },
+ password: {
+ type: DataTypes.STRING,
+ required: true,
+ },
+ role: {
+ type: DataTypes.STRING,
+ required: true,
+ },
+ name: {
+ type: DataTypes.STRING,
+ required: false,
+ },
+ phone: {
+ type: DataTypes.STRING,
+ required: true,
+ },
+ email: {
+ type: DataTypes.STRING,
+ required: true,
+ },
+ zip: {
+ type: DataTypes.STRING,
+ required: true,
+ },
+ city: {
+ type: DataTypes.STRING,
+ required: true,
+ },
+ state: {
+ type: DataTypes.STRING,
+ required: true,
+ },
+});
+
+users.hasMany(subs, {
+ foreignKey: 'username',
+});
+subs.belongsTo(users, {
+ foreignKey: 'username',
+});
+
+const mailgun = new Mailgun(formData);
+const client = mailgun.client({
+ username: 'api',
+ key: process.env.MAILGUN_KEY,
+});
+
+async function getEvents(city, stateCode) {
+ try {
+
+ const currentDate = new Date();
+ const startDateTime = currentDate.toISOString().slice(0, -5) + 'Z';
+
+ const endDateTime = new Date(currentDate.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, -5) + 'Z';
+ console.log(startDateTime);
+ console.log(endDateTime);
+
+
+ let apikey = process.env.TICKET_API;
+ let radius = '20';
+ let unit = 'miles';
+
+
+ const response = await axios.get(
+ `https://app.ticketmaster.com/discovery/v2/events.json?size=5&apikey=${apikey}&startDateTime=${startDateTime}&endDateTime=${endDateTime}&city=${city}&radius=${radius}&unit=${unit}&stateCode=${stateCode}`);
+
+ const concerts = response.data;
+
+
+ const toStandardTime = function (time) {
+
+ const [hours, minutes, seconds] = time.split(':');
+ let hoursFix = parseInt(hours, 10);
+ let minutesFix = parseInt(minutes, 10);
+ let secondsFix = parseInt(seconds, 10);
+
+ if (hoursFix >= 12) {
+
+ hoursFix -= 12;
+
+
+ time = `${hoursFix.toString().padStart(2, '0')}:${minutesFix
+ .toString()
+ .padStart(2, '0')}:${secondsFix.toString().padStart(2, '0')} PM`;
+ } else {
+
+ time = `${hoursFix.toString().padStart(2, '0')}:${minutesFix
+ .toString()
+ .padStart(2, '0')}:${secondsFix.toString().padStart(2, '0')} AM`;
+ }
+
+ return time;
+ };
+ let eventList = '';
+
+ for (let i = 0; i < 5; i++) {
+ const venueData = concerts._embedded.events[i];
+ const venue = venueData._embedded.venues[0].name;
+ const eventName = concerts._embedded.events[i].name;
+ const eventUrl = concerts._embedded.events[i].url;
+ const eventDate = concerts._embedded.events[i].dates;
+ const eventStartTime = eventDate.start.localTime;
+ const eventStartDate = eventDate.start.localDate;
+ const newTime = toStandardTime(eventStartTime);
+
+ eventList += `${eventName} at ${venue} on ${eventStartDate} at ${newTime}, Link: ${eventUrl}`;
+ }
+ return eventList;
+ } catch (error) {
+ console.error(error);
+ }
+}
+
+async function mailgunEvents(eventList, email) {
+
+ const msg = {
+ to: email,
+ from: 'notifyme.us@gmail.com',
+ subject: 'Check Out These 5 events happening this week!',
+ html: `Five Upcoming Events in your Area:
+ `,
+ };
+
+ await client.messages.create(process.env.MAILGUN_DOMAIN, msg);
+ console.log('completed mailgun send');
+}
+
+
+exports.handler = async () => {
+ const subsList = await subs.findAll({
+ where: {
+ type: 'events',
+ },
+ include: [{
+ model: users,
+ required: true,
+ }],
+ });
+ console.log(subsList);
+
+ await Promise.allSettled(subsList.map(async sub => {
+ try {
+ console.log('THIS IS SUB -------------', sub.User.dataValues);
+ const { city, state, email } = sub.User.dataValues;
+
+ const eventList = await getEvents(city, state);
+ await mailgunEvents( eventList, email);
+ console.log(`----- SUCCESS sending events summary to ${email} -----`);
+ } catch (e) {
+ console.log(e.message);
+ }
+ }));
+ console.log(`----- SUCCESS -----`);
+
+};
diff --git a/events/package-lock.json b/events/package-lock.json
new file mode 100644
index 0000000..ba4d647
--- /dev/null
+++ b/events/package-lock.json
@@ -0,0 +1,500 @@
+{
+ "name": "events",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "events",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "axios": "^1.2.1",
+ "dotenv": "^16.0.3",
+ "form-data": "^4.0.0",
+ "mailgun.js": "^8.0.6",
+ "pg": "^8.8.0",
+ "sequelize": "^6.28.0"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
+ "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "0.7.31",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
+ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
+ },
+ "node_modules/@types/node": {
+ "version": "18.11.18",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
+ "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA=="
+ },
+ "node_modules/@types/validator": {
+ "version": "13.7.10",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.10.tgz",
+ "integrity": "sha512-t1yxFAR2n0+VO6hd/FJ9F2uezAZVWHLmpmlJzm1eX03+H7+HsuTAp7L8QJs+2pQCfWkP1+EXsGK9Z9v7o/qPVQ=="
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/axios": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz",
+ "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==",
+ "dependencies": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/base-64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz",
+ "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="
+ },
+ "node_modules/buffer-writer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
+ "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.0.3",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
+ "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/dottie": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz",
+ "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg=="
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
+ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/inflection": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz",
+ "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==",
+ "engines": [
+ "node >= 0.4.0"
+ ]
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/mailgun.js": {
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-8.0.6.tgz",
+ "integrity": "sha512-b+c7QO1T4oFsudEcRB2H7oZKth8ZDeYRW4xjW12QQVNYDSJCVxqSQfps6ofcH8fqcCMJdzc76HVNGdnUZgBPCw==",
+ "dependencies": {
+ "axios": "^0.27.2",
+ "base-64": "^1.0.0",
+ "url-join": "^4.0.1"
+ }
+ },
+ "node_modules/mailgun.js/node_modules/axios": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
+ "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
+ "dependencies": {
+ "follow-redirects": "^1.14.9",
+ "form-data": "^4.0.0"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/moment": {
+ "version": "2.29.4",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
+ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/moment-timezone": {
+ "version": "0.5.40",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz",
+ "integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==",
+ "dependencies": {
+ "moment": ">= 2.9.0"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/packet-reader": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
+ "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
+ },
+ "node_modules/pg": {
+ "version": "8.8.0",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz",
+ "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==",
+ "dependencies": {
+ "buffer-writer": "2.0.0",
+ "packet-reader": "1.0.0",
+ "pg-connection-string": "^2.5.0",
+ "pg-pool": "^3.5.2",
+ "pg-protocol": "^1.5.0",
+ "pg-types": "^2.1.0",
+ "pgpass": "1.x"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ },
+ "peerDependencies": {
+ "pg-native": ">=3.0.1"
+ },
+ "peerDependenciesMeta": {
+ "pg-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pg-connection-string": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
+ "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
+ },
+ "node_modules/pg-int8": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/pg-pool": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz",
+ "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==",
+ "peerDependencies": {
+ "pg": ">=8.0"
+ }
+ },
+ "node_modules/pg-protocol": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
+ "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
+ },
+ "node_modules/pg-types": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+ "dependencies": {
+ "pg-int8": "1.0.1",
+ "postgres-array": "~2.0.0",
+ "postgres-bytea": "~1.0.0",
+ "postgres-date": "~1.0.4",
+ "postgres-interval": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pgpass": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+ "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+ "dependencies": {
+ "split2": "^4.1.0"
+ }
+ },
+ "node_modules/postgres-array": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postgres-bytea": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+ "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-date": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-interval": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "node_modules/retry-as-promised": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.3.tgz",
+ "integrity": "sha512-SEvMa4khHvpU/o6zgh7sK24qm6rxVgKnrSyzb5POeDvZx5N9Bf0s5sQsQ4Fl+HjRp0X+w2UzACGfUnXtx6cJ9Q=="
+ },
+ "node_modules/semver": {
+ "version": "7.3.8",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
+ "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/sequelize": {
+ "version": "6.28.0",
+ "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.28.0.tgz",
+ "integrity": "sha512-+WHqvUQgTp19GLkt+gyQ+F6qg+FIEO2O5F9C0TOYV/PjZ2a/XwWvVkL1NCkS4VSIjVVvAUutiW6Wv9ofveGaVw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/sequelize"
+ }
+ ],
+ "dependencies": {
+ "@types/debug": "^4.1.7",
+ "@types/validator": "^13.7.1",
+ "debug": "^4.3.3",
+ "dottie": "^2.0.2",
+ "inflection": "^1.13.2",
+ "lodash": "^4.17.21",
+ "moment": "^2.29.1",
+ "moment-timezone": "^0.5.34",
+ "pg-connection-string": "^2.5.0",
+ "retry-as-promised": "^7.0.3",
+ "semver": "^7.3.5",
+ "sequelize-pool": "^7.1.0",
+ "toposort-class": "^1.0.1",
+ "uuid": "^8.3.2",
+ "validator": "^13.7.0",
+ "wkx": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ibm_db": {
+ "optional": true
+ },
+ "mariadb": {
+ "optional": true
+ },
+ "mysql2": {
+ "optional": true
+ },
+ "oracledb": {
+ "optional": true
+ },
+ "pg": {
+ "optional": true
+ },
+ "pg-hstore": {
+ "optional": true
+ },
+ "snowflake-sdk": {
+ "optional": true
+ },
+ "sqlite3": {
+ "optional": true
+ },
+ "tedious": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/sequelize-pool": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz",
+ "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/split2": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz",
+ "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
+ "node_modules/toposort-class": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz",
+ "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg=="
+ },
+ "node_modules/url-join": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
+ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="
+ },
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/validator": {
+ "version": "13.7.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
+ "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/wkx": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz",
+ "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "engines": {
+ "node": ">=0.4"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ }
+ }
+}
diff --git a/events/package.json b/events/package.json
new file mode 100644
index 0000000..d2988f7
--- /dev/null
+++ b/events/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "events",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "axios": "^1.2.1",
+ "dotenv": "^16.0.3",
+ "form-data": "^4.0.0",
+ "mailgun.js": "^8.0.6",
+ "pg": "^8.8.0",
+ "sequelize": "^6.28.0"
+ }
+}
diff --git a/events/test.js b/events/test.js
new file mode 100644
index 0000000..e4167d3
--- /dev/null
+++ b/events/test.js
@@ -0,0 +1,6 @@
+const test = require('.').handler;
+
+test().then(() => {
+ console.log('complete');
+});
+
diff --git a/eventsNotification.js b/eventsNotification.js
new file mode 100644
index 0000000..e69de29
diff --git a/external-api copy.js b/external-api copy.js
new file mode 100644
index 0000000..e9ecaaf
--- /dev/null
+++ b/external-api copy.js
@@ -0,0 +1,348 @@
+const router = require('express').Router();
+const axios = require('axios');
+const Mailgun = require('mailgun-js');
+
+require('dotenv').config();
+const mailgun = new Mailgun({
+ apiKey: process.env.MAILGUN_KEY,
+ domain: process.env.MAILGUN_DOMAIN,
+});
+
+const getForecast = socket => ( async () => {
+
+ let zip = '60513';
+
+ try {
+
+ const API_KEY = process.env.WEATHER_KEY;
+
+ let token = process.env.MAPBOX_TOKEN;
+
+
+ const latLon = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/postcode/${zip}.json?access_token=${token}&country=us`);
+
+
+ let lat = latLon.data.features[0].center[1];
+ let lon = latLon.data.features[0].center[0];
+
+ const response = await axios.get(
+ `https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&appid=${API_KEY}`,
+ );
+
+ const getForecastData = function (dayIndex) {
+ const forecast = response.data.list[dayIndex];
+ const weather = forecast.weather[0].description;
+ const temperature = forecast.main.temp;
+ const fahrenheit = (temperature - 273.15) * 9 / 5 + 32;
+ const fahrenheitRounded = Math.round(fahrenheit * 100) / 100;
+ const town = response.data.city.name;
+ return {
+ weather: weather,
+ temp: fahrenheitRounded,
+ city: town,
+ };
+ };
+
+ const forecastData1 = getForecastData(4);
+ const forecastData2 = getForecastData(12);
+ const forecastData3 = getForecastData(20);
+ const forecastData4 = getForecastData(28);
+ const forecastData5 = getForecastData(36);
+
+
+ const currentDate = new Date();
+ const forecastDates = [];
+
+ for (let i = 0; i < 5; i++) {
+ const forecastDate = new Date(currentDate);
+ forecastDate.setDate(currentDate.getDate() + i);
+ forecastDates.push(forecastDate);
+ }
+
+ console.log(response.data);
+
+
+
+ const msg = {
+ to: 'steveo732@gmail.com',
+ from: 'notifyme.us@gmail.com',
+ subject: 'Your 5 Day Weather Forecast',
+ text: `The weather forecast for the next 5 days in ${forecastData1.city} from NotifyMe-US:
+ ${forecastDates[0].toLocaleDateString()}: ${forecastData1.temp} °F with ${forecastData1.weather}
+ ${forecastDates[1].toLocaleDateString()}: ${forecastData2.temp} °F with ${forecastData2.weather}
+ ${forecastDates[2].toLocaleDateString()}: ${forecastData3.temp} °F with ${forecastData3.weather}
+ ${forecastDates[3].toLocaleDateString()}: ${forecastData4.temp} °F with ${forecastData4.weather}
+ ${forecastDates[4].toLocaleDateString()}: ${forecastData5.temp} °F with ${forecastData5.weather}
+ `,
+ html: `The weather forecast for the next 5 days in ${forecastData1.city} from NotifyMe-US:
+
+ -
+ ${forecastDates[0].toLocaleDateString()}: ${forecastData1.temp} °F with ${forecastData1.weather}
+ -
+ ${forecastDates[1].toLocaleDateString()}: ${forecastData2.temp} °F with ${forecastData2.weather}
+ -
+ ${forecastDates[2].toLocaleDateString()}: ${forecastData3.temp} °F with ${forecastData3.weather}
+ -
+ ${forecastDates[3].toLocaleDateString()}: ${forecastData4.temp} °F with ${forecastData4.weather}
+ -
+ ${forecastDates[4].toLocaleDateString()}: ${forecastData5.temp} °F with ${forecastData5.weather}
+
+
`,
+ };
+
+
+ mailgun.messages().send(msg, (error, body) => {
+ if (error) {
+ console.error(error);
+ socket.emit('API_ERROR', 'An error has occured with your request');
+ } else {
+ res.send({ message: 'Email sent successfully', msg });
+ console.log(msg.text);
+ }
+ });
+
+ } catch (error) {
+ console.error(error);
+ socket.emit('API_ERROR', 'An error has occured with your request');
+ }
+});
+
+
+const getWeather = socket => (async () => {
+
+ let zip = '60513';
+ try {
+
+ const API_KEY = process.env.WEATHER_KEY;
+ let token = process.env.MAPBOX_TOKEN;
+
+
+ const latLon = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/postcode/${zip}.json?access_token=${token}&country=us`);
+
+
+ let lat = latLon.data.features[0].center[1];
+ let lon = latLon.data.features[0].center[0];
+
+ const response = await axios.get(
+ `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${API_KEY}`);
+
+
+ const forecast = response.data;
+ const weather = forecast.weather[0].description;
+ const temperatureHigh = forecast.main.temp_max;
+ const fahrenheitHigh = (temperatureHigh - 273.15) * 9 / 5 + 32;
+ const fahrenheitRoundedHigh = Math.round(fahrenheitHigh * 100) / 100;
+ const temperatureLow = forecast.main.temp_min;
+ const fahrenheitLow = (temperatureLow - 273.15) * 9 / 5 + 32;
+ const fahrenheitRoundedLow = Math.round(fahrenheitLow * 100) / 100;
+ const humidity = forecast.main.humidity;
+ const windspeed = forecast.wind.speed;
+ const windMph = windspeed * 2.236936;
+ const windMphFormatted = windMph.toFixed(2);
+ const cloudCoverage = forecast.clouds.all;
+ const town = forecast.name;
+
+ const msg = {
+ to: 'steveo732@gmail.com',
+ from: 'notifyme.us@gmail.com',
+ subject: 'Your Daily Weather Forecast',
+ text: `"Here is your custom weather forecast for today from NotifyMe-US:
+
+ The weather in ${town} is currently ${weather}. The high temperature for today will be ${fahrenheitRoundedHigh} degrees Fahrenheit, and the low temperature will be ${fahrenheitRoundedLow} degrees Fahrenheit. The humidity will be ${humidity}%, and the wind speed will be ${windMphFormatted} mph. There will be ${cloudCoverage}% cloud coverage."`,
+
+
+ html: `
+ Here is your custom weather forecast for today from NotifyMe-US::
+
+ -
+ The weather in ${town} is currently ${weather}. The high temperature for today will be ${fahrenheitRoundedHigh} degrees Fahrenheit, and the low temperature will be ${fahrenheitRoundedLow} degrees Fahrenheit.
+ -
+ The humidity will be ${humidity}%, and the wind speed will be ${windMphFormatted} mph. There will be ${cloudCoverage}% cloud coverage.
+
+
+ `,
+ };
+
+ mailgun.messages().send(msg, (error, body) => {
+ if (error) {
+ console.error(error);
+ socket.emit('API_ERROR', 'An error has occured with your request');
+ } else {
+ console.log(msg.text);
+
+ res.send({ message: 'Email sent successfully', msg });
+ }
+ });
+
+ } catch (error) {
+ console.error(error);
+ socket.emit('API_ERROR', 'An error has occured with your request');
+ }
+});
+
+
+const getDirections = socket => ( async () => {
+ let token = process.env.MAPBOX_TOKEN;
+
+ let addressOne = encodeURIComponent('4026 sunnyside ave 60513');
+ let addressTwo = encodeURIComponent('4255 N Knox Ave 60641');
+
+ try {
+
+ const latLonOne = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/address/${addressOne}.json?types=address%2Cplace&access_token=${token}&country=us`);
+
+
+ const latLonTwo = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/address/${addressTwo}.json?access_token=${token}&country=us`);
+
+
+ let addressOneCoor = latLonOne.data.features[0].center;
+ let addressTwoCoor = latLonTwo.data.features[0].center;
+
+ let coordinates = `${addressOneCoor[0]},${addressOneCoor[1]};${addressTwoCoor[0]},${addressTwoCoor[1]}`;
+
+
+ const response = await axios.get(`https://api.mapbox.com/directions/v5/mapbox/driving-traffic/${coordinates}?geometries=polyline&overview=simplified&steps=true&access_token=${token}`);
+
+ const directions = response.data;
+ const step = directions.routes[0].legs[0].steps;
+
+ const instructionsAndDistance = step.map(step => ({
+ instruction: step.maneuver.instruction,
+ distance: parseFloat((step.distance / 1609.344).toFixed(2)),
+ }));
+
+ const instructions = instructionsAndDistance.map(step => `${step.instruction} (${step.distance} miles)`).join('\n');
+ const distance = instructionsAndDistance.reduce((acc, step) => acc + step.distance, 0);
+ const roundedDistance = distance.toFixed(2);
+
+ const duration = Math.ceil(directions.routes[0].duration / 60);
+
+ const msg = {
+ to: 'steveo732@gmail.com',
+ from: 'notifyme.us@gmail.com',
+ subject: 'Your Daily Commute from NotifyMe-US',
+ text: `Your Daily Commute from NotifyMe-US for ${new Date().toLocaleDateString()}
+ Total distance: ${roundedDistance} miles
+ Total duration: ${duration} minutes,
+
+ Best Route:
+ ${instructions}`,
+
+ html: `Your Daily Commute from NotifyMe-US for ${new Date().toLocaleDateString()}
+ Total distance: ${roundedDistance} miles
+ Total duration: ${duration} minutes
+ Best Route:
+
+
+ ${instructionsAndDistance.map(step => `- ${step.instruction} (${step.distance} miles)
`).join('')}
+
+ `,
+
+ };
+
+
+
+ mailgun.messages().send(msg, (error, body) => {
+ if (error) {
+ console.error(error);
+ socket.emit('API_ERROR', 'An error has occured with your request');
+ } else {
+ console.log(msg.text);
+ res.send({ message: 'Email sent successfully', msg });
+
+ }
+ });
+ } catch (error) {
+ console.error(error);
+ socket.emit('API_ERROR', 'An error has occured with your request');
+ }
+});
+
+const getEvents = socket => ( async () => {
+ try {
+
+ const currentDate = new Date();
+ const startDateTime = currentDate.toISOString().slice(0, -5) + 'Z';
+
+ const endDateTime = new Date(currentDate.getTime() + 7 * 24 * 60 * 60 * 1000).toISOString().slice(0, -5) + 'Z';
+ console.log(startDateTime);
+ console.log(endDateTime);
+
+
+ let apikey = process.env.TICKET_API;
+ let city = 'seattle';
+ let stateCode = 'wa';
+ let radius = '20';
+ let unit = 'miles';
+
+
+ const response = await axios.get(
+ `https://app.ticketmaster.com/discovery/v2/events.json?size=5&apikey=${apikey}&startDateTime=${startDateTime}&endDateTime=${endDateTime}&city=${city}&radius=${radius}&unit=${unit}&stateCode=${stateCode}`);
+
+ const concerts = response.data;
+
+
+ const toStandardTime = function (time) {
+
+ const [hours, minutes, seconds] = time.split(':');
+ let hoursFix = parseInt(hours, 10);
+ let minutesFix = parseInt(minutes, 10);
+ let secondsFix = parseInt(seconds, 10);
+
+ if (hoursFix >= 12) {
+
+ hoursFix -= 12;
+
+
+ time = `${hoursFix.toString().padStart(2, '0')}:${minutesFix
+ .toString()
+ .padStart(2, '0')}:${secondsFix.toString().padStart(2, '0')} PM`;
+ } else {
+
+ time = `${hoursFix.toString().padStart(2, '0')}:${minutesFix
+ .toString()
+ .padStart(2, '0')}:${secondsFix.toString().padStart(2, '0')} AM`;
+ }
+
+ return time;
+ };
+ let eventList = '';
+
+ for (let i = 0; i < 5; i++) {
+ const venueData = concerts._embedded.events[i];
+ const venue = venueData._embedded.venues[0].name;
+ const eventName = concerts._embedded.events[i].name;
+ const eventUrl = concerts._embedded.events[i].url;
+ const eventDate = concerts._embedded.events[i].dates;
+ const eventStartTime = eventDate.start.localTime;
+ const eventStartDate = eventDate.start.localDate;
+ const newTime = toStandardTime(eventStartTime);
+
+ eventList += `${eventName} at ${venue} on ${eventStartDate} at ${newTime}, Link: ${eventUrl}`;
+ }
+
+ const msg = {
+ to: 'steveo732@gmail.com',
+ from: 'notifyme.us@gmail.com',
+ subject: 'Check Out These 5 events happening this week!',
+ text: `Five Upcoming Events in your Area: ${eventList}`,
+ html: `Five Upcoming Events in your Area:
+ `,
+ };
+
+ mailgun.messages().send(msg, (error, body) => {
+ if (error) {
+ console.error(error);
+ socket.emit('API_ERROR', 'An error has occured with your request');
+ } else {
+ console.log(msg.text);
+ socket.emit('API_RESULT', 'success'); // TODO - replace 'success' with actual api result
+ }
+ });
+ } catch (error) {
+ console.error(error);
+ socket.emit('API_ERROR', 'An error has occured with your request');
+ }
+});
diff --git a/weather/.DS_Store b/weather/.DS_Store
new file mode 100644
index 0000000..a3c6fd6
Binary files /dev/null and b/weather/.DS_Store differ
diff --git a/weather/.env.sample b/weather/.env.sample
new file mode 100644
index 0000000..2d2c206
--- /dev/null
+++ b/weather/.env.sample
@@ -0,0 +1,9 @@
+DATABASE_URL=postgres://localhost:5432/
+
+MAILGUN_KEY=
+
+MAILGUN_DOMAIN=
+
+WEATHER_KEY=
+
+MAPBOX_TOKEN=
diff --git a/weather/index.js b/weather/index.js
new file mode 100644
index 0000000..91daded
--- /dev/null
+++ b/weather/index.js
@@ -0,0 +1,230 @@
+require('dotenv').config();
+
+const axios = require('axios');
+const Mailgun = require('mailgun.js');
+const formData = require('form-data');
+const { Sequelize, DataTypes } = require('sequelize');
+
+const DATABASE_URL = process.env.DATABASE_URL;
+const db = new Sequelize(DATABASE_URL);
+
+const subs = db.define('Subscriptions', {
+ username: {
+ type: DataTypes.STRING,
+ required: true,
+ },
+ type: {
+ type: DataTypes.ENUM(['weather', 'events']),
+ required: true,
+ },
+});
+
+const users = db.define('Users', {
+ username: {
+ type: DataTypes.STRING,
+ required: true,
+ primaryKey: true,
+ unique: true,
+ },
+ password: {
+ type: DataTypes.STRING,
+ required: true,
+ },
+ role: {
+ type: DataTypes.STRING,
+ required: true,
+ },
+ name: {
+ type: DataTypes.STRING,
+ required: false,
+ },
+ phone: {
+ type: DataTypes.STRING,
+ required: true,
+ },
+ email: {
+ type: DataTypes.STRING,
+ required: true,
+ },
+ zip: {
+ type: DataTypes.STRING,
+ required: true,
+ },
+});
+
+users.hasMany(subs, {
+ foreignKey: 'username',
+});
+subs.belongsTo(users, {
+ foreignKey: 'username',
+});
+
+const mailgun = new Mailgun(formData);
+const client = mailgun.client({
+ username: 'api',
+ key: process.env.MAILGUN_KEY,
+});
+
+const getCurrentWeather = async (zip) => {
+ try {
+ const API_KEY = process.env.WEATHER_KEY;
+ let token = process.env.MAPBOX_TOKEN;
+
+ const latLon = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/postcode/${zip}.json?access_token=${token}&country=us`);
+
+ let lat = latLon.data.features[0].center[1];
+ let lon = latLon.data.features[0].center[0];
+
+ const response = await axios.get(
+ `https://api.openweathermap.org/data/2.5/weather?lat=${lat}&lon=${lon}&appid=${API_KEY}`);
+
+ const forecast = response.data;
+ const temperatureHigh = forecast.main.temp_max;
+ const fahrenheitHigh = (temperatureHigh - 273.15) * 9 / 5 + 32;
+ const temperatureLow = forecast.main.temp_min;
+ const fahrenheitLow = (temperatureLow - 273.15) * 9 / 5 + 32;
+ const windspeed = forecast.wind.speed;
+ const windMph = windspeed * 2.236936;
+
+ const output = {
+ town: forecast.name,
+ weather: forecast.weather[0].description,
+ fahrenheitRoundedHigh: Math.round(fahrenheitHigh * 100) / 100,
+ fahrenheitRoundedLow: Math.round(fahrenheitLow * 100) / 100,
+ humidity: forecast.main.humidity,
+ windMphFormatted: windMph.toFixed(2),
+ cloudCoverage: forecast.clouds.all,
+ };
+
+ return output;
+ } catch (error) {
+ console.error(error);
+ throw 'An error has occured with your request (current)';
+ }
+};
+
+const getForecast = async (zip) => {
+ try {
+ const API_KEY = process.env.WEATHER_KEY;
+ let token = process.env.MAPBOX_TOKEN;
+
+
+ const latLon = await axios.get(`https://api.mapbox.com/geocoding/v5/mapbox.places/postcode/${zip}.json?access_token=${token}&country=us`);
+
+ let lat = latLon.data.features[0].center[1];
+ let lon = latLon.data.features[0].center[0];
+
+ const response = await axios.get(
+ `https://api.openweathermap.org/data/2.5/forecast?lat=${lat}&lon=${lon}&appid=${API_KEY}`,
+ );
+
+ const getForecastData = function (dayIndex) {
+ const forecast = response.data.list[dayIndex];
+ const weather = forecast.weather[0].description;
+ const temperature = forecast.main.temp;
+ const fahrenheit = (temperature - 273.15) * 9 / 5 + 32;
+ const fahrenheitRounded = Math.round(fahrenheit * 100) / 100;
+ const town = response.data.city.name;
+ return {
+ weather: weather,
+ temp: fahrenheitRounded,
+ city: town,
+ };
+ };
+
+ const forecast = {
+ data: [],
+ dates: [],
+ };
+
+ for (let i = 0; i < 5; i++) {
+ let j = i * 8 + 4;
+ forecast.data[i] = getForecastData(j);
+ const forecastDate = new Date();
+ forecastDate.setDate(forecastDate.getDate() + i);
+ forecast.dates[i] = forecastDate;
+ }
+ return forecast;
+
+ } catch (error) {
+ console.error(error);
+ throw 'An error has occured with your request (forecast)';
+ }
+};
+
+async function mailgunForecast(current, forecast, email) {
+ const { data, dates } = forecast;
+
+ const msg = {
+ to: email,
+ from: 'notifyme.us@gmail.com',
+ subject: 'Your Daily Weather Summary',
+ html: `
+
+ Here is your custom weather forecast for ${current.town} from NotifyMe-US:
+
+ -
+ The weather is currently ${current.weather}. The high temperature for today will be ${current.fahrenheitRoundedHigh} degrees Fahrenheit, and the low temperature will be ${current.fahrenheitRoundedLow} degrees Fahrenheit.
+
+ -
+ The humidity will be ${current.humidity}%, and the wind speed will be ${current.windMphFormatted} mph. There will be ${current.cloudCoverage}% cloud coverage.
+
+
+
+
+
+ The weather forecast for the next 5 days:
+
+ -
+ ${dates[0].toLocaleDateString()}: ${data[0].temp} °F with ${data[0].weather}
+
+ -
+ ${dates[1].toLocaleDateString()}: ${data[1].temp} °F with ${data[1].weather}
+
+ -
+ ${dates[2].toLocaleDateString()}: ${data[2].temp} °F with ${data[2].weather}
+
+ -
+ ${dates[3].toLocaleDateString()}: ${data[3].temp} °F with ${data[3].weather}
+
+ -
+ ${dates[4].toLocaleDateString()}: ${data[4].temp} °F with ${data[4].weather}
+
+
+
+
`,
+ };
+
+
+ await client.messages.create(process.env.MAILGUN_DOMAIN, msg);
+ console.log('completed mailgun send');
+}
+
+
+exports.handler = async () => {
+ const subsList = await subs.findAll({
+ where: {
+ type: 'weather',
+ },
+ include: [{
+ model: users,
+ required: true,
+ }],
+ });
+ console.log(subsList);
+
+ await Promise.allSettled(subsList.map(async sub => {
+ try {
+ console.log('THIS IS SUB -------------', sub.User.dataValues);
+ const { zip, email } = sub.User.dataValues;
+ const currentWeather = await getCurrentWeather(zip);
+ const forecast = await getForecast(zip);
+ await mailgunForecast(currentWeather, forecast, email);
+ console.log(`----- SUCCESS sending weather summary to ${email} -----`);
+ } catch(e) {
+ console.log(e.message);
+ }
+ }));
+ console.log(`----- SUCCESS -----`);
+
+};
diff --git a/weather/package-lock.json b/weather/package-lock.json
new file mode 100644
index 0000000..e6725b7
--- /dev/null
+++ b/weather/package-lock.json
@@ -0,0 +1,500 @@
+{
+ "name": "lambdas",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "lambdas",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "axios": "^1.2.1",
+ "dotenv": "^16.0.3",
+ "form-data": "^4.0.0",
+ "mailgun.js": "^8.0.6",
+ "pg": "^8.8.0",
+ "sequelize": "^6.28.0"
+ }
+ },
+ "node_modules/@types/debug": {
+ "version": "4.1.7",
+ "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.7.tgz",
+ "integrity": "sha512-9AonUzyTjXXhEOa0DnqpzZi6VHlqKMswga9EXjpXnnqxwLtdvPPtlO8evrI5D9S6asFRCQ6v+wpiUKbw+vKqyg==",
+ "dependencies": {
+ "@types/ms": "*"
+ }
+ },
+ "node_modules/@types/ms": {
+ "version": "0.7.31",
+ "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz",
+ "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA=="
+ },
+ "node_modules/@types/node": {
+ "version": "18.11.18",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-18.11.18.tgz",
+ "integrity": "sha512-DHQpWGjyQKSHj3ebjFI/wRKcqQcdR+MoFBygntYOZytCqNfkd2ZC4ARDJ2DQqhjH5p85Nnd3jhUJIXrszFX/JA=="
+ },
+ "node_modules/@types/validator": {
+ "version": "13.7.10",
+ "resolved": "https://registry.npmjs.org/@types/validator/-/validator-13.7.10.tgz",
+ "integrity": "sha512-t1yxFAR2n0+VO6hd/FJ9F2uezAZVWHLmpmlJzm1eX03+H7+HsuTAp7L8QJs+2pQCfWkP1+EXsGK9Z9v7o/qPVQ=="
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/axios": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.2.1.tgz",
+ "integrity": "sha512-I88cFiGu9ryt/tfVEi4kX2SITsvDddTajXTOFmt2uK1ZVA8LytjtdeyefdQWEf5PU8w+4SSJDoYnggflB5tW4A==",
+ "dependencies": {
+ "follow-redirects": "^1.15.0",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/base-64": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/base-64/-/base-64-1.0.0.tgz",
+ "integrity": "sha512-kwDPIFCGx0NZHog36dj+tHiwP4QMzsZ3AgMViUBKI0+V5n4U0ufTCUMhnQ04diaRI8EX/QcPfql7zlhZ7j4zgg=="
+ },
+ "node_modules/buffer-writer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/buffer-writer/-/buffer-writer-2.0.0.tgz",
+ "integrity": "sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "16.0.3",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.0.3.tgz",
+ "integrity": "sha512-7GO6HghkA5fYG9TYnNxi14/7K9f5occMlp3zXAuSxn7CKCxt9xbNWG7yF8hTCSUchlfWSe3uLmlPfigevRItzQ==",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/dottie": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/dottie/-/dottie-2.0.2.tgz",
+ "integrity": "sha512-fmrwR04lsniq/uSr8yikThDTrM7epXHBAAjH9TbeH3rEA8tdCO7mRzB9hdmdGyJCxF8KERo9CITcm3kGuoyMhg=="
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.2",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz",
+ "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/mailgun.js": {
+ "version": "8.0.6",
+ "resolved": "https://registry.npmjs.org/mailgun.js/-/mailgun.js-8.0.6.tgz",
+ "integrity": "sha512-b+c7QO1T4oFsudEcRB2H7oZKth8ZDeYRW4xjW12QQVNYDSJCVxqSQfps6ofcH8fqcCMJdzc76HVNGdnUZgBPCw==",
+ "dependencies": {
+ "axios": "^0.27.2",
+ "base-64": "^1.0.0",
+ "url-join": "^4.0.1"
+ }
+ },
+ "node_modules/mailgun.js/node_modules/axios": {
+ "version": "0.27.2",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.27.2.tgz",
+ "integrity": "sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==",
+ "dependencies": {
+ "follow-redirects": "^1.14.9",
+ "form-data": "^4.0.0"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/moment": {
+ "version": "2.29.4",
+ "resolved": "https://registry.npmjs.org/moment/-/moment-2.29.4.tgz",
+ "integrity": "sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==",
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/moment-timezone": {
+ "version": "0.5.40",
+ "resolved": "https://registry.npmjs.org/moment-timezone/-/moment-timezone-0.5.40.tgz",
+ "integrity": "sha512-tWfmNkRYmBkPJz5mr9GVDn9vRlVZOTe6yqY92rFxiOdWXbjaR0+9LwQnZGGuNR63X456NqmEkbskte8tWL5ePg==",
+ "dependencies": {
+ "moment": ">= 2.9.0"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/packet-reader": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/packet-reader/-/packet-reader-1.0.0.tgz",
+ "integrity": "sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ=="
+ },
+ "node_modules/pg": {
+ "version": "8.8.0",
+ "resolved": "https://registry.npmjs.org/pg/-/pg-8.8.0.tgz",
+ "integrity": "sha512-UXYN0ziKj+AeNNP7VDMwrehpACThH7LUl/p8TDFpEUuSejCUIwGSfxpHsPvtM6/WXFy6SU4E5RG4IJV/TZAGjw==",
+ "dependencies": {
+ "buffer-writer": "2.0.0",
+ "packet-reader": "1.0.0",
+ "pg-connection-string": "^2.5.0",
+ "pg-pool": "^3.5.2",
+ "pg-protocol": "^1.5.0",
+ "pg-types": "^2.1.0",
+ "pgpass": "1.x"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ },
+ "peerDependencies": {
+ "pg-native": ">=3.0.1"
+ },
+ "peerDependenciesMeta": {
+ "pg-native": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pg-connection-string": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.5.0.tgz",
+ "integrity": "sha512-r5o/V/ORTA6TmUnyWZR9nCj1klXCO2CEKNRlVuJptZe85QuhFayC7WeMic7ndayT5IRIR0S0xFxFi2ousartlQ=="
+ },
+ "node_modules/pg-int8": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/pg-int8/-/pg-int8-1.0.1.tgz",
+ "integrity": "sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw==",
+ "engines": {
+ "node": ">=4.0.0"
+ }
+ },
+ "node_modules/pg-pool": {
+ "version": "3.5.2",
+ "resolved": "https://registry.npmjs.org/pg-pool/-/pg-pool-3.5.2.tgz",
+ "integrity": "sha512-His3Fh17Z4eg7oANLob6ZvH8xIVen3phEZh2QuyrIl4dQSDVEabNducv6ysROKpDNPSD+12tONZVWfSgMvDD9w==",
+ "peerDependencies": {
+ "pg": ">=8.0"
+ }
+ },
+ "node_modules/pg-protocol": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/pg-protocol/-/pg-protocol-1.5.0.tgz",
+ "integrity": "sha512-muRttij7H8TqRNu/DxrAJQITO4Ac7RmX3Klyr/9mJEOBeIpgnF8f9jAfRz5d3XwQZl5qBjF9gLsUtMPJE0vezQ=="
+ },
+ "node_modules/pg-types": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/pg-types/-/pg-types-2.2.0.tgz",
+ "integrity": "sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA==",
+ "dependencies": {
+ "pg-int8": "1.0.1",
+ "postgres-array": "~2.0.0",
+ "postgres-bytea": "~1.0.0",
+ "postgres-date": "~1.0.4",
+ "postgres-interval": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/pgpass": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/pgpass/-/pgpass-1.0.5.tgz",
+ "integrity": "sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug==",
+ "dependencies": {
+ "split2": "^4.1.0"
+ }
+ },
+ "node_modules/postgres-array": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-array/-/postgres-array-2.0.0.tgz",
+ "integrity": "sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/postgres-bytea": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/postgres-bytea/-/postgres-bytea-1.0.0.tgz",
+ "integrity": "sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-date": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/postgres-date/-/postgres-date-1.0.7.tgz",
+ "integrity": "sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/postgres-interval": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/postgres-interval/-/postgres-interval-1.2.0.tgz",
+ "integrity": "sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ==",
+ "dependencies": {
+ "xtend": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "node_modules/retry-as-promised": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/retry-as-promised/-/retry-as-promised-7.0.3.tgz",
+ "integrity": "sha512-SEvMa4khHvpU/o6zgh7sK24qm6rxVgKnrSyzb5POeDvZx5N9Bf0s5sQsQ4Fl+HjRp0X+w2UzACGfUnXtx6cJ9Q=="
+ },
+ "node_modules/semver": {
+ "version": "7.3.8",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
+ "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver/node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="
+ },
+ "node_modules/sequelize": {
+ "version": "6.28.0",
+ "resolved": "https://registry.npmjs.org/sequelize/-/sequelize-6.28.0.tgz",
+ "integrity": "sha512-+WHqvUQgTp19GLkt+gyQ+F6qg+FIEO2O5F9C0TOYV/PjZ2a/XwWvVkL1NCkS4VSIjVVvAUutiW6Wv9ofveGaVw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/sequelize"
+ }
+ ],
+ "dependencies": {
+ "@types/debug": "^4.1.7",
+ "@types/validator": "^13.7.1",
+ "debug": "^4.3.3",
+ "dottie": "^2.0.2",
+ "inflection": "^1.13.2",
+ "lodash": "^4.17.21",
+ "moment": "^2.29.1",
+ "moment-timezone": "^0.5.34",
+ "pg-connection-string": "^2.5.0",
+ "retry-as-promised": "^7.0.3",
+ "semver": "^7.3.5",
+ "sequelize-pool": "^7.1.0",
+ "toposort-class": "^1.0.1",
+ "uuid": "^8.3.2",
+ "validator": "^13.7.0",
+ "wkx": "^0.5.0"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ },
+ "peerDependenciesMeta": {
+ "ibm_db": {
+ "optional": true
+ },
+ "mariadb": {
+ "optional": true
+ },
+ "mysql2": {
+ "optional": true
+ },
+ "oracledb": {
+ "optional": true
+ },
+ "pg": {
+ "optional": true
+ },
+ "pg-hstore": {
+ "optional": true
+ },
+ "snowflake-sdk": {
+ "optional": true
+ },
+ "sqlite3": {
+ "optional": true
+ },
+ "tedious": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/sequelize-pool": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/sequelize-pool/-/sequelize-pool-7.1.0.tgz",
+ "integrity": "sha512-G9c0qlIWQSK29pR/5U2JF5dDQeqqHRragoyahj/Nx4KOOQ3CPPfzxnfqFPCSB7x5UgjOgnZ61nSxz+fjDpRlJg==",
+ "engines": {
+ "node": ">= 10.0.0"
+ }
+ },
+ "node_modules/sequelize/node_modules/inflection": {
+ "version": "1.13.4",
+ "resolved": "https://registry.npmjs.org/inflection/-/inflection-1.13.4.tgz",
+ "integrity": "sha512-6I/HUDeYFfuNCVS3td055BaXBwKYuzw7K3ExVMStBowKo9oOAMJIXIHvdyR3iboTCp1b+1i5DSkIZTcwIktuDw==",
+ "engines": [
+ "node >= 0.4.0"
+ ]
+ },
+ "node_modules/split2": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/split2/-/split2-4.1.0.tgz",
+ "integrity": "sha512-VBiJxFkxiXRlUIeyMQi8s4hgvKCSjtknJv/LVYbrgALPwf5zSKmEwV9Lst25AkvMDnvxODugjdl6KZgwKM1WYQ==",
+ "engines": {
+ "node": ">= 10.x"
+ }
+ },
+ "node_modules/toposort-class": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/toposort-class/-/toposort-class-1.0.1.tgz",
+ "integrity": "sha512-OsLcGGbYF3rMjPUf8oKktyvCiUxSbqMMS39m33MAjLTC1DVIH6x3WSt63/M77ihI09+Sdfk1AXvfhCEeUmC7mg=="
+ },
+ "node_modules/url-join": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/url-join/-/url-join-4.0.1.tgz",
+ "integrity": "sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA=="
+ },
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/validator": {
+ "version": "13.7.0",
+ "resolved": "https://registry.npmjs.org/validator/-/validator-13.7.0.tgz",
+ "integrity": "sha512-nYXQLCBkpJ8X6ltALua9dRrZDHVYxjJ1wgskNt1lH9fzGjs3tgojGSCBjmEPwkWS1y29+DrizMTW19Pr9uB2nw==",
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/wkx": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/wkx/-/wkx-0.5.0.tgz",
+ "integrity": "sha512-Xng/d4Ichh8uN4l0FToV/258EjMGU9MGcA0HV2d9B/ZpZB3lqQm7nkOdZdm5GhKtLLhAE7PiVQwN4eN+2YJJUg==",
+ "dependencies": {
+ "@types/node": "*"
+ }
+ },
+ "node_modules/xtend": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz",
+ "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==",
+ "engines": {
+ "node": ">=0.4"
+ }
+ }
+ }
+}
diff --git a/weather/package.json b/weather/package.json
new file mode 100644
index 0000000..8f4c42f
--- /dev/null
+++ b/weather/package.json
@@ -0,0 +1,20 @@
+{
+ "name": "lambdas",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "scripts": {
+ "test": "echo \"Error: no test specified\" && exit 1"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "axios": "^1.2.1",
+ "dotenv": "^16.0.3",
+ "form-data": "^4.0.0",
+ "mailgun.js": "^8.0.6",
+ "pg": "^8.8.0",
+ "sequelize": "^6.28.0"
+ }
+}
diff --git a/weather/test.js b/weather/test.js
new file mode 100644
index 0000000..e4167d3
--- /dev/null
+++ b/weather/test.js
@@ -0,0 +1,6 @@
+const test = require('.').handler;
+
+test().then(() => {
+ console.log('complete');
+});
+