From 9556edd9023902d2ad02e347410df00ac8a0d16f Mon Sep 17 00:00:00 2001 From: michellelin1 <66575725+michellelin1@users.noreply.github.com> Date: Sun, 7 Jan 2024 17:46:10 -0800 Subject: [PATCH] stable v1 * created catalog table * AT - created the published schedule table * fixed enum name * removed brackets around exist conditionals * added create table users statement * added drop table if exists * updated drop table statement * Made CRUD SQL queries for Published Schedule Table * added catalog CRUD queries * Attempt to figure out ENV variable issue with workflows * fix typo * try moving env variables * Revert changes * Make CRUD SQL queries for Users Table (#25) * Added UPDATE and DELETE queries for user table * added Get, Get pending accounts, and Post * implemented changes from pr feedback --------- Co-authored-by: Sean Fong Co-authored-by: Philip Jian * fixed published schedule types * added connection to db * 23-make-backend-routes-for-users (#26) * Create a pull trequest for branch 23-make-backend-routes-for-users * Co-authored-by: cherhchen * Finished backend routes Co-authored-by: cherhchen * Refactor users.js for minor updates and delete users.sql Co-authored-by: cherhchen --------- Co-authored-by: Ethan Ho Co-authored-by: cherhchen * Make backend routes for Catelog (#27) * created catalog.js, made progress * added :id to the router functions, changed delete query * Completed code for routes. Running into db.query is not a function error * Completed Routes for Catelog * removed comments, fixed yarn lock * removed console logs * fixed ? Co-authored-by: liannejl --------- Co-authored-by: Alyssia Tan Co-authored-by: liannejl Co-authored-by: liannejl * Set up the nodeMailer route and the transporter (#28) * nodeMailer.js setup * Added nodeMailer.js code and transporter.js that is called within nodeMailer * Updated package.json to include nodeMailer dependency, added endpoint for emailRouter in app.js, set cors credentials to true so it doesn't block req --------- Co-authored-by: subinqkim * Make Backend Routes for Published Schedule (#29) * Modified GET queries with joins on catalog table * created publishSchedule.js file * Mounted published schedule route on app.js * Added GET and POST route controller functions for published schedule * put and delete, not yet complete * updated publishedSchedule.js * updated to use numeric syntax for sql queries * small fixes in ppublishedSchedule.js * pull request feedback * pull request feedback cont. * fixed misc bugs --------- Co-authored-by: Sean Fong Co-authored-by: Philip Jian Co-authored-by: michellelin1 Co-authored-by: ThatMegamind <92563733+ThatMegamind@users.noreply.github.com> * Minor DB Updates (#32) * Create a pull trequest for branch 31-minor-db-updates * updated db model and queries --------- Co-authored-by: michellelin1 --------- Co-authored-by: Kristen Yee Co-authored-by: Alyssia Tan Co-authored-by: Cheryl Chen Co-authored-by: subinqkim Co-authored-by: chloecheng8 <30807247+chloecheng8@users.noreply.github.com> Co-authored-by: Andrew Lee Co-authored-by: ThatMegamind <92563733+ThatMegamind@users.noreply.github.com> Co-authored-by: ctc-devops <90984711+ctc-devops@users.noreply.github.com> Co-authored-by: Sean Fong Co-authored-by: Philip Jian Co-authored-by: Ethan Ho Co-authored-by: cherhchen Co-authored-by: liannejl Co-authored-by: liannejl --- .github/workflows/CI.yml | 8 +- .github/workflows/scripts/reviewReviewed.js | 6 +- app.js | 18 +++ common/utils.js | 55 +++++++ package.json | 9 +- routes/catalog.js | 93 +++++++++++ routes/nodeMailer.js | 37 +++++ routes/publishedSchedule.js | 139 +++++++++++++++++ routes/users.js | 68 ++++++++ server/db.js | 15 ++ server/schema/catalog.sql | 14 ++ server/schema/published_schedule.sql | 13 ++ server/schema/users.sql | 10 ++ transporter.js | 18 +++ yarn.lock | 164 +++++++++++++++++++- 15 files changed, 656 insertions(+), 11 deletions(-) create mode 100644 common/utils.js create mode 100644 routes/catalog.js create mode 100644 routes/nodeMailer.js create mode 100644 routes/publishedSchedule.js create mode 100644 routes/users.js create mode 100644 server/db.js create mode 100644 server/schema/catalog.sql create mode 100644 server/schema/published_schedule.sql create mode 100644 server/schema/users.sql create mode 100644 transporter.js diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index 318a41d..3d96503 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -2,13 +2,13 @@ name: CI on: [pull_request] - + permissions: read-all jobs: - eslint: + eslint: runs-on: ubuntu-latest - steps: + steps: - uses: actions/checkout@v2 - uses: actions/setup-node@v2 with: @@ -21,7 +21,7 @@ jobs: reporter: github-pr-review fail_on_error: false eslint_flags: '**/*.{js,jsx}' - + detect-secrets: runs-on: ubuntu-latest steps: diff --git a/.github/workflows/scripts/reviewReviewed.js b/.github/workflows/scripts/reviewReviewed.js index 9d83536..9dd2f44 100644 --- a/.github/workflows/scripts/reviewReviewed.js +++ b/.github/workflows/scripts/reviewReviewed.js @@ -40,11 +40,11 @@ const messageAssignee = async ({ context }) => { try { const UserModel = getUserModel(); const slackAssignees = await Promise.allSettled( - githubAssignees.map(assignee => UserModel.findOne({ github: assignee.login })), + githubAssignees.map((assignee) => UserModel.findOne({ github: assignee.login })), ); if (context.payload.review.state === 'approved') { await Promise.all( - slackAssignees.map(assignee => + slackAssignees.map((assignee) => Bot.client.chat.postMessage({ channel: assignee.value?.slackId, text: `One of your pull requests has been APPROVED by ${reviewer}! <${url}|View Review> :shrek::thumbsup:`, @@ -53,7 +53,7 @@ const messageAssignee = async ({ context }) => { ); } else { await Promise.all( - slackAssignees.map(assignee => + slackAssignees.map((assignee) => Bot.client.chat.postMessage({ channel: assignee.value?.slackId, text: `One of your pull requests has been REVIEWED by ${reviewer}! <${url}|View Review> :shrek:`, diff --git a/app.js b/app.js index 7419cb1..20aa7a6 100644 --- a/app.js +++ b/app.js @@ -1,18 +1,36 @@ const express = require('express'); const cors = require('cors'); +const publishedScheduleRouter = require('./routes/publishedSchedule'); require('dotenv').config(); +// routes +const users = require('./routes/users'); + +const email = require('./routes/nodeMailer'); + const app = express(); +const catalogRouter = require('./routes/catalog'); + const PORT = process.env.PORT || 3001; app.use( cors({ origin: `${process.env.REACT_APP_HOST}:${process.env.REACT_APP_PORT}`, + credentials: true, }), ); +// app.use(cors({ origin: 'http://localhost:3000', credentials: true })); + +// add all routes under here +app.use(express.json()); // for req.body +app.use('/published-schedule', publishedScheduleRouter); +app.use('/users', users); +app.use('/catalog', catalogRouter); +app.use('/nodeMailer', email); + app.listen(PORT, () => { console.log(`Server listening on ${PORT}`); }); diff --git a/common/utils.js b/common/utils.js new file mode 100644 index 0000000..14fad66 --- /dev/null +++ b/common/utils.js @@ -0,0 +1,55 @@ +// toCamel, isArray, and isObject are helper functions used within utils only +const toCamel = (s) => { + return s.replace(/([-_][a-z])/g, ($1) => { + return $1.toUpperCase().replace('-', '').replace('_', ''); + }); +}; + +const isArray = (a) => { + return Array.isArray(a); +}; + +const isISODate = (str) => { + try { + const ISOString = str.toISOString(); + if (!/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}.\d{3}Z/.test(ISOString)) return false; + const d = new Date(ISOString); + return d.toISOString() === ISOString; + } catch (err) { + return false; + } +}; + +const isObject = (o) => { + return o === Object(o) && !isArray(o) && typeof o !== 'function' && !isISODate(o); +}; + +// Database columns are in snake case. JavaScript is suppose to be in camel case +// This function converts the keys from the sql query to camel case so it follows JavaScript conventions +const keysToCamel = (data) => { + if (isObject(data)) { + const newData = {}; + Object.keys(data).forEach((key) => { + newData[toCamel(key)] = keysToCamel(data[key]); + }); + return newData; + } + if (isArray(data)) { + return data.map((i) => { + return keysToCamel(i); + }); + } + if ( + typeof data === 'string' && + data.length > 0 && + data[0] === '{' && + data[data.length - 1] === '}' + ) { + let parsedList = data.replaceAll('"', ''); + parsedList = parsedList.slice(1, parsedList.length - 1).split(','); + return parsedList; + } + return data; +}; + +module.exports = { keysToCamel }; diff --git a/package.json b/package.json index c5b52f1..c6ecde7 100644 --- a/package.json +++ b/package.json @@ -23,13 +23,16 @@ "eslint-plugin-import": "^2.25.2", "eslint-plugin-prettier": "^4.0.0", "express": "^4.17.1", + "express-promise-router": "^4.1.1", + "nodemailer": "^6.9.7", "nodemon": "^2.0.14", + "pg": "^8.8.0", + "pg-promise": "^10.12.1", "prettier": "^2.4.1" }, "devDependencies": { "eslint-config-prettier": "^8.3.0", - "lint-staged": "^11.2.6", - "lint-staged": "^11.2.6", - "husky": "^7.0.4" + "husky": "^7.0.4", + "lint-staged": "^11.2.6" } } diff --git a/routes/catalog.js b/routes/catalog.js new file mode 100644 index 0000000..eac9d18 --- /dev/null +++ b/routes/catalog.js @@ -0,0 +1,93 @@ +const express = require('express'); + +const { db } = require('../server/db'); + +const catalogRouter = express.Router(); +const { keysToCamel } = require('../common/utils'); + +// -- GET - Returns all data from the catalog table +catalogRouter.get('/', async (req, res) => { + try { + const allInfo = await db.query(`SELECT * from catalog;`); + res.status(200).json(keysToCamel(allInfo)); + } catch (err) { + res.status(500).send(err.message); + } +}); + +// -- GET/:id - Returns the row that matches the id +catalogRouter.get('/:id', async (req, res) => { + try { + const { id } = req.params; + const allUsers = await db.query(`SELECT * FROM catalog WHERE id = $1;`, [id]); + res.status(200).json(keysToCamel(allUsers)); + } catch (err) { + res.status(500).send(err.message); + } +}); + +// -- POST - Adds a new row to the catalog table +catalogRouter.post('/', async (req, res) => { + const { host, title, eventType, subject, description, year } = req.body; + try { + const returnedData = await db.query( + `INSERT INTO catalog (id, host, title, event_type, subject, description, year) + VALUES (nextval('catalog_id_seq'), $1, $2, $3, $4, $5, $6) + RETURNING id;`, + [host, title, eventType, subject, description, year], + ); + res.status(201).json({ id: returnedData[0].id, status: 'Success' }); + } catch (err) { + res.status(500).json({ + status: 'Failed', + msg: err.message, + }); + } +}); + +// -- PUT - Updates an existing row given an id +// -- All fields are optional +catalogRouter.put('/:id', async (req, res) => { + try { + const { id } = req.params; + const { host, title, eventType, subject, description, year } = req.body; + + const updatedCatalog = await db.query( + `UPDATE catalog SET + ${host ? 'host = $(host), ' : ''} + ${title ? 'title = $(title),' : ''} + ${eventType ? 'event_type = $(eventType), ' : ''} + ${subject ? 'subject = $(subject), ' : ''} + ${description ? 'description = $(description), ' : ''} + ${year ? 'year = $(year), ' : ''} + id = '${id}' + WHERE id = '${id}' + RETURNING *;`, + { + host, + title, + eventType, + subject, + description, + year, + id, + }, + ); + res.status(200).send(keysToCamel(updatedCatalog)); + } catch (err) { + res.status(500).send(err.message); + } +}); + +// -- DELETE - deletes an existing row given an id +catalogRouter.delete('/:id', async (req, res) => { + try { + const { id } = req.params; + const delUser = await db.query(`DELETE FROM catalog WHERE id = $1 RETURNING *;`, [id]); + res.status(200).send(keysToCamel(delUser)); + } catch (err) { + res.status(500).send(err.message); + } +}); + +module.exports = catalogRouter; diff --git a/routes/nodeMailer.js b/routes/nodeMailer.js new file mode 100644 index 0000000..a95fca3 --- /dev/null +++ b/routes/nodeMailer.js @@ -0,0 +1,37 @@ +const express = require('express'); +// const cors = require('cors'); +const transporter = require('../transporter'); +// TODO: add verifyToken + +const emailRouter = express(); +// emailRouter.use( +// cors({ +// origin: 'http://localhost:3000', +// methods: 'GET,HEAD,PUT,PATCH,POST,DELETE', +// credentials: true, +// }), +// ); + +emailRouter.use(express.json()); + +emailRouter.post('/send', (req, res) => { + const { email, messageHtml, subject } = req.body; + console.log('req.body', req.body); + console.log('email', email); + const mail = { + from: `${process.env.REACT_APP_EMAIL_FIRST_NAME} ${process.env.REACT_APP_EMAIL_LAST_NAME} ${process.env.REACT_APP_EMAIL_USERNAME}`, + to: email, + subject, + html: messageHtml, + }; + + transporter.sendMail(mail, (err) => { + if (err) { + res.status(500).send(`Transporter Error: ${err}`); + } else { + res.status(200).send('Transporter Backend Successfully Sent'); + } + }); +}); + +module.exports = emailRouter; diff --git a/routes/publishedSchedule.js b/routes/publishedSchedule.js new file mode 100644 index 0000000..6793999 --- /dev/null +++ b/routes/publishedSchedule.js @@ -0,0 +1,139 @@ +const express = require('express'); +const { db } = require('../server/db'); +const { keysToCamel } = require('../common/utils'); + +const publishedScheduleRouter = express.Router(); + +// GET - Returns all data from the published_schedule table +publishedScheduleRouter.get('/', async (req, res) => { + try { + const allPublishedSchedules = await db.query( + ` + SELECT + PS.id, + C.host, + C.title, + PS.confirmed, + PS.confirmed_on, + PS.start_time, + PS.end_time, + PS.cohort, + PS.notes + FROM + published_schedule PS + LEFT JOIN catalog C ON PS.event_id = C.id; + `, + ); + res.status(200).json(keysToCamel(allPublishedSchedules)); + } catch (err) { + res.status(500).send(err.message); + } +}); + +// GET/:id - returns the rows that match the given id +publishedScheduleRouter.get('/:id', async (req, res) => { + try { + const { id } = req.params; + const publishedScheduleResult = await db.query( + ` + SELECT + PS.id, + C.host, + C.title, + PS.confirmed, + PS.confirmed_on, + PS.start_time, + PS.end_time, + PS.cohort, + PS.notes + FROM + published_schedule PS + LEFT JOIN catalog C ON PS.event_id = C.id + WHERE PS.id = $1; + `, + [id], + ); + res.status(200).json(keysToCamel(publishedScheduleResult)); + } catch (err) { + res.status(500).send(err.message); + } +}); + +// POST - Adds a new row to the published_schedule table +publishedScheduleRouter.post('/', async (req, res) => { + const { eventId, confirmed, confirmedOn, startTime, endTime, cohort, notes } = req.body; + try { + const returnedData = await db.query( + ` + INSERT INTO + published_schedule ( + id, + event_id, + confirmed, + confirmed_on, + start_time, + end_time, + cohort, + notes + ) + VALUES + (nextval('published_schedule_id_seq'), $1, $2, $3, $4, $5, $6, $7) + RETURNING id; + `, + [eventId, confirmed, confirmedOn, startTime, endTime, cohort, notes], + ); + res.status(201).json({ + status: 'Success', + id: returnedData[0].id, + }); + } catch (err) { + res.status(500).send(err.message); + } +}); + +// PUT/:id - Updates an existing row given an id +publishedScheduleRouter.put('/:id', async (req, res) => { + try { + const { id } = req.params; + const { eventId, confirmed, confirmedOn, startTime, endTime, cohort, notes } = req.body; + const updatedPublishedSchedule = await db.query( + ` + UPDATE published_schedule + SET + event_id = COALESCE($1, event_id), + confirmed = COALESCE($2, confirmed), + confirmed_on = COALESCE($3, confirmed_on), + start_time = COALESCE($4, start_time), + end_time = COALESCE($5, end_time), + cohort = COALESCE($6, cohort), + notes = COALESCE($7, notes) + WHERE id = $8 + + RETURNING *; + `, + [eventId, confirmed, confirmedOn, startTime, endTime, cohort, notes, id], + ); + res.status(200).json(keysToCamel(updatedPublishedSchedule)); + } catch (err) { + res.status(500).send(err.message); + } +}); + +// DELETE/:id - deletes an existing row given an id +publishedScheduleRouter.delete('/:id', async (req, res) => { + try { + const { id } = req.params; + const deletedEntry = await db.query( + ` + DELETE FROM published_schedule + WHERE id = $1 RETURNING *; + `, + [id], + ); + res.status(200).send(deletedEntry); + } catch (err) { + res.status(500).send(err.message); + } +}); + +module.exports = publishedScheduleRouter; diff --git a/routes/users.js b/routes/users.js new file mode 100644 index 0000000..eba4bbb --- /dev/null +++ b/routes/users.js @@ -0,0 +1,68 @@ +const express = require('express'); +const { keysToCamel } = require('../common/utils'); +const { db } = require('../server/db'); + +const userRouter = express.Router(); + +userRouter.get('/', async (req, res) => { + try { + const allUsers = await db.query(`SELECT * FROM users;`); + res.status(200).json(keysToCamel(allUsers)); + } catch (err) { + res.status(500).send(err.message); + } +}); + +userRouter.get('/pending-accounts', async (req, res) => { + try { + const pendingAccounts = await db.query(`SELECT * FROM users WHERE approved = FALSE;`); + res.status(200).json(keysToCamel(pendingAccounts)); + } catch (err) { + res.status(500).send(err.message); + } +}); + +userRouter.post('/', async (req, res) => { + try { + const { id, email, type, approved } = req.body; + await db.query(`INSERT INTO users (id, email, "type", approved) VALUES ($1, $2, $3, $4);`, [ + id, + email, + type, + approved, + ]); + res.status(201).json({ + id, + }); + } catch (err) { + res.status(500).json({ + status: 'Failed', + msg: err.message, + }); + } +}); + +userRouter.put('/:uid', async (req, res) => { + try { + const { uid } = req.params; + const updatedApproval = await db.query( + `UPDATE users SET approved = TRUE WHERE id = $1 RETURNING *;`, + [uid], + ); + return res.status(200).send(keysToCamel(updatedApproval)); + } catch (err) { + return res.status(500).send(err.message); + } +}); + +userRouter.delete('/:uid', async (req, res) => { + try { + const { uid } = req.params; + const deletedUser = await db.query(`DELETE FROM users WHERE id = $1 RETURNING *;`, [uid]); + res.status(200).send(keysToCamel(deletedUser)); + } catch (err) { + res.status(500).send(err.message); + } +}); + +module.exports = userRouter; diff --git a/server/db.js b/server/db.js new file mode 100644 index 0000000..07490a8 --- /dev/null +++ b/server/db.js @@ -0,0 +1,15 @@ +const pgp = require('pg-promise')({}); +require('dotenv').config(); + +const db = pgp({ + host: process.env.AWS_HOST, + user: process.env.AWS_USER, + password: process.env.AWS_PASSWORD, + port: process.env.AWS_PORT, + database: process.env.AWS_DB_NAME, + ssl: { + rejectUnauthorized: false, + }, +}); + +module.exports = { db, pgp }; diff --git a/server/schema/catalog.sql b/server/schema/catalog.sql new file mode 100644 index 0000000..d49128c --- /dev/null +++ b/server/schema/catalog.sql @@ -0,0 +1,14 @@ +CREATE TYPE event AS ENUM ('guest speaker', 'study-trip', 'workshop', 'other'); +CREATE TYPE subject AS ENUM ('life skills', 'science', 'technology', 'engineering', 'math', 'college readiness'); +CREATE TYPE year AS ENUM ('junior', 'senior', 'both'); + +DROP TABLE IF EXISTS catalog; +CREATE TABLE catalog ( + id SERIAL PRIMARY KEY, + host VARCHAR(50) NOT NULL, + title VARCHAR(50) NOT NULL, + event_type event NOT NULL, + subject subject NOT NULL, + description VARCHAR(50) NOT NULL, + year year NOT NULL +); diff --git a/server/schema/published_schedule.sql b/server/schema/published_schedule.sql new file mode 100644 index 0000000..7d04a9e --- /dev/null +++ b/server/schema/published_schedule.sql @@ -0,0 +1,13 @@ +DROP TABLE IF EXISTS published_schedule; +CREATE TABLE IF NOT EXISTS published_schedule ( + id serial NOT NULL, + event_id integer NOT NULL, + confirmed boolean NOT NULL, + confirmed_on date NOT NULL, + start_time timestamp NOT NULL, + end_time timestamp NOT NULL, + cohort varchar[] NOT NULL, + notes varchar(100), + FOREIGN KEY (event_id) + REFERENCES catalog (id) +); diff --git a/server/schema/users.sql b/server/schema/users.sql new file mode 100644 index 0000000..b652586 --- /dev/null +++ b/server/schema/users.sql @@ -0,0 +1,10 @@ +CREATE TYPE account_type as ENUM ('superadmin', 'admin'); + +DROP TABLE IF EXISTS users; + +CREATE TABLE users ( + id VARCHAR ( 256 ) PRIMARY KEY, + email VARCHAR ( 50 ) NOT NULL, + type account_type NOT NULL, + approved BOOLEAN NOT NULL +); \ No newline at end of file diff --git a/transporter.js b/transporter.js new file mode 100644 index 0000000..96090bb --- /dev/null +++ b/transporter.js @@ -0,0 +1,18 @@ +const nodemailer = require('nodemailer'); + +require('dotenv').config(); + +// sender information +const transport = { + host: 'smtp.gmail.com', // e.g. smtp.gmail.com + auth: { + user: process.env.REACT_APP_EMAIL_USERNAME, + pass: process.env.REACT_APP_EMAIL_PASSWORD, + }, + from: process.env.REACT_APP_EMAIL_USERNAME, + secure: true, +}; + +const transporter = nodemailer.createTransport(transport); + +module.exports = transporter; diff --git a/yarn.lock b/yarn.lock index bef156e..f4f23de 100644 --- a/yarn.lock +++ b/yarn.lock @@ -198,6 +198,11 @@ array.prototype.flat@^1.2.5: define-properties "^1.1.3" es-abstract "^1.19.0" +assert-options@0.8.0: + version "0.8.0" + resolved "https://registry.yarnpkg.com/assert-options/-/assert-options-0.8.0.tgz#cf71882534d23d3027945bc7462e20d3d3682380" + integrity sha512-qSELrEaEz4sGwTs4Qh+swQkjiHAysC4rot21+jzXU86dJzNG+FDqBzyS3ohSoTRf4ZLA3FSwxQdiuNl5NXUtvA== + astral-regex@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/astral-regex/-/astral-regex-2.0.0.tgz#483143c567aeed4785759c0865786dc77d7d2e31" @@ -258,6 +263,11 @@ braces@^3.0.1, braces@~3.0.2: dependencies: fill-range "^7.0.1" +buffer-writer@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/buffer-writer/-/buffer-writer-2.0.0.tgz#ce7eb81a38f7829db09c873f2fbb792c0c98ec04" + integrity sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw== + bytes@3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.0.tgz#f6cf7933a360e0588fa9fde85651cdc7f805d1f6" @@ -845,6 +855,15 @@ execa@^5.1.1: signal-exit "^3.0.3" strip-final-newline "^2.0.0" +express-promise-router@^4.1.1: + version "4.1.1" + resolved "https://registry.yarnpkg.com/express-promise-router/-/express-promise-router-4.1.1.tgz#8fac102060b9bcc868f84d34fbb12fd8fa494291" + integrity sha512-Lkvcy/ZGrBhzkl3y7uYBHLMtLI4D6XQ2kiFg9dq7fbktBch5gjqJ0+KovX0cvCAvTJw92raWunRLM/OM+5l4fA== + dependencies: + is-promise "^4.0.0" + lodash.flattendeep "^4.0.0" + methods "^1.0.0" + express@^4.17.1: version "4.17.1" resolved "https://registry.yarnpkg.com/express/-/express-4.17.1.tgz#4491fc38605cf51f8629d39c2b5d026f98a4c134" @@ -1354,6 +1373,11 @@ is-path-inside@^3.0.2: resolved "https://registry.yarnpkg.com/is-path-inside/-/is-path-inside-3.0.3.tgz#d231362e53a07ff2b0e0ea7fed049161ffd16283" integrity sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ== +is-promise@^4.0.0: + version "4.0.0" + resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-4.0.0.tgz#42ff9f84206c1991d26debf520dd5c01042dd2f3" + integrity sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ== + is-regex@^1.1.4: version "1.1.4" resolved "https://registry.yarnpkg.com/is-regex/-/is-regex-1.1.4.tgz#eef5663cd59fa4c0ae339505323df6854bb15958" @@ -1528,6 +1552,11 @@ locate-path@^2.0.0: p-locate "^2.0.0" path-exists "^3.0.0" +lodash.flattendeep@^4.0.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.flattendeep/-/lodash.flattendeep-4.4.0.tgz#fb030917f86a3134e5bc9bec0d69e0013ddfedb2" + integrity sha512-uHaJFihxmJcEX3kT4I23ABqKKalJ/zDrDg0lsFtc1h+3uw49SIJ5beyhx5ExVRti3AvKoOJngIj7xz3oylPdWQ== + lodash.merge@^4.6.2: version "4.6.2" resolved "https://registry.yarnpkg.com/lodash.merge/-/lodash.merge-4.6.2.tgz#558aa53b43b661e1925a0afdfa36a9a1085fe57a" @@ -1582,7 +1611,7 @@ merge-stream@^2.0.0: resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" integrity sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w== -methods@~1.1.2: +methods@^1.0.0, methods@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= @@ -1664,6 +1693,11 @@ negotiator@0.6.2: resolved "https://registry.yarnpkg.com/negotiator/-/negotiator-0.6.2.tgz#feacf7ccf525a77ae9634436a64883ffeca346fb" integrity sha512-hZXc7K2e+PgeI1eDBe/10Ard4ekbfrrqG8Ep+8Jmf4JID2bNg7NvCPOZN+kfF574pFQI7mum2AUqDidoKqcTOw== +nodemailer@^6.9.7: + version "6.9.7" + resolved "https://registry.yarnpkg.com/nodemailer/-/nodemailer-6.9.7.tgz#ec2f488f62ba1558e7b19239b62778df4a5c4397" + integrity sha512-rUtR77ksqex/eZRLmQ21LKVH5nAAsVicAtAYudK7JgwenEDZ0UIQ1adUGqErz7sMkWYxWTTU1aeP2Jga6WQyJw== + nodemon@^2.0.14: version "2.0.14" resolved "https://registry.yarnpkg.com/nodemon/-/nodemon-2.0.14.tgz#287c7a2f6cd8a18b07e94cd776ecb6a82e4ba439" @@ -1821,6 +1855,11 @@ package-json@^6.3.0: registry-url "^5.0.0" semver "^6.2.0" +packet-reader@1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/packet-reader/-/packet-reader-1.0.0.tgz#9238e5480dedabacfe1fe3f2771063f164157d74" + integrity sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ== + parent-module@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/parent-module/-/parent-module-1.0.1.tgz#691d2709e78c79fae3a156622452d00762caaaa2" @@ -1873,6 +1912,92 @@ path-type@^4.0.0: resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b" integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw== +pg-cloudflare@^1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/pg-cloudflare/-/pg-cloudflare-1.1.1.tgz#e6d5833015b170e23ae819e8c5d7eaedb472ca98" + integrity sha512-xWPagP/4B6BgFO+EKz3JONXv3YDgvkbVrGw2mTo3D6tVDQRh1e7cqVGvyR3BE+eQgAvx1XhW/iEASj4/jCWl3Q== + +pg-connection-string@^2.5.0, pg-connection-string@^2.6.2: + version "2.6.2" + resolved "https://registry.yarnpkg.com/pg-connection-string/-/pg-connection-string-2.6.2.tgz#713d82053de4e2bd166fab70cd4f26ad36aab475" + integrity sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA== + +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-minify@1.6.2: + version "1.6.2" + resolved "https://registry.yarnpkg.com/pg-minify/-/pg-minify-1.6.2.tgz#055acfe862cfca3ca0a529020846b0f308d68e70" + integrity sha512-1KdmFGGTP6jplJoI8MfvRlfvMiyBivMRP7/ffh4a11RUFJ7kC2J0ZHlipoKiH/1hz+DVgceon9U2qbaHpPeyPg== + +pg-pool@^3.5.2, pg-pool@^3.6.1: + version "3.6.1" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.6.1.tgz#5a902eda79a8d7e3c928b77abf776b3cb7d351f7" + integrity sha512-jizsIzhkIitxCGfPRzJn1ZdcosIt3pz9Sh3V01fm1vZnbnCMgmGl5wvGGdNN2EL9Rmb0EcFoCkixH4Pu+sP9Og== + +pg-promise@^10.12.1: + version "10.15.4" + resolved "https://registry.yarnpkg.com/pg-promise/-/pg-promise-10.15.4.tgz#b8b5055489f375a43e5d3edbff1d41ddb3817b2f" + integrity sha512-BKlHCMCdNUmF6gagVbehRWSEiVcZzPVltEx14OJExR9Iz9/1R6KETDWLLGv2l6yRqYFnEZZy1VDjRhArzeIGrw== + dependencies: + assert-options "0.8.0" + pg "8.8.0" + pg-minify "1.6.2" + spex "3.2.0" + +pg-protocol@^1.5.0, pg-protocol@^1.6.0: + version "1.6.0" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.0.tgz#4c91613c0315349363af2084608db843502f8833" + integrity sha512-M+PDm637OY5WM307051+bsDia5Xej6d9IR4GwJse1qA1DIhiKlksvrneZOYQq42OM+spubpcNYEo2FcKQrDk+Q== + +pg-types@^2.1.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + 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" + +pg@8.8.0: + version "8.8.0" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.8.0.tgz#a77f41f9d9ede7009abfca54667c775a240da686" + 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" + +pg@^8.8.0: + version "8.11.3" + resolved "https://registry.yarnpkg.com/pg/-/pg-8.11.3.tgz#d7db6e3fe268fcedd65b8e4599cda0b8b4bf76cb" + integrity sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g== + dependencies: + buffer-writer "2.0.0" + packet-reader "1.0.0" + pg-connection-string "^2.6.2" + pg-pool "^3.6.1" + pg-protocol "^1.6.0" + pg-types "^2.1.0" + pgpass "1.x" + optionalDependencies: + pg-cloudflare "^1.1.1" + +pgpass@1.x: + version "1.0.5" + resolved "https://registry.yarnpkg.com/pgpass/-/pgpass-1.0.5.tgz#9b873e4a564bb10fa7a7dbd55312728d422a223d" + integrity sha512-FdW9r/jQZhSeohs1Z3sI1yxFQNFvMcnmfuj4WBMUTxOrAyLMaTcE1aAMBiTlbMNaXvBCQuVi0R7hd8udDSP7ug== + dependencies: + split2 "^4.1.0" + picomatch@^2.0.4, picomatch@^2.2.1, picomatch@^2.2.3: version "2.3.0" resolved "https://registry.yarnpkg.com/picomatch/-/picomatch-2.3.0.tgz#f1f061de8f6a4bf022892e2d128234fb98302972" @@ -1892,6 +2017,28 @@ please-upgrade-node@^3.2.0: dependencies: semver-compare "^1.0.0" +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + prelude-ls@^1.2.1: version "1.2.1" resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396" @@ -2167,6 +2314,16 @@ slice-ansi@^4.0.0: astral-regex "^2.0.0" is-fullwidth-code-point "^3.0.0" +spex@3.2.0: + version "3.2.0" + resolved "https://registry.yarnpkg.com/spex/-/spex-3.2.0.tgz#fa4a21922407e112624977b445a6d634578a1127" + integrity sha512-9srjJM7NaymrpwMHvSmpDeIK5GoRMX/Tq0E8aOlDPS54dDnDUIp30DrP9SphMPEETDLzEM9+4qo+KipmbtPecg== + +split2@^4.1.0: + version "4.2.0" + resolved "https://registry.yarnpkg.com/split2/-/split2-4.2.0.tgz#c9c5920904d148bab0b9f67145f245a86aadbfa4" + integrity sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg== + sprintf-js@~1.0.2: version "1.0.3" resolved "https://registry.yarnpkg.com/sprintf-js/-/sprintf-js-1.0.3.tgz#04e6926f662895354f3dd015203633b857297e2c" @@ -2489,6 +2646,11 @@ xdg-basedir@^4.0.0: resolved "https://registry.yarnpkg.com/xdg-basedir/-/xdg-basedir-4.0.0.tgz#4bc8d9984403696225ef83a1573cbbcb4e79db13" integrity sha512-PSNhEJDejZYV7h50BohL09Er9VaIefr2LMAf3OEmpCkjOi34eYyQYAXUTjEQtZJTKcF0E2UKTh+osDLsgNim9Q== +xtend@^4.0.0: + version "4.0.2" + resolved "https://registry.yarnpkg.com/xtend/-/xtend-4.0.2.tgz#bb72779f5fa465186b1f438f674fa347fdb5db54" + integrity sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ== + yallist@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/yallist/-/yallist-4.0.0.tgz#9bb92790d9c0effec63be73519e11a35019a3a72"