From 47a56c31a45fbde67f865dac53dae0c69f42b1b2 Mon Sep 17 00:00:00 2001 From: Gloria V <12786915+ggrossvi@users.noreply.github.com> Date: Wed, 8 May 2024 14:37:39 -0700 Subject: [PATCH 01/25] Create sleepycats.yaml --- .github/workflows/sleepycats.yaml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/sleepycats.yaml diff --git a/.github/workflows/sleepycats.yaml b/.github/workflows/sleepycats.yaml new file mode 100644 index 000000000..263552fb7 --- /dev/null +++ b/.github/workflows/sleepycats.yaml @@ -0,0 +1,26 @@ +name: sleepy-cat +# on key is trigger when pull request is opened or reopened +on: + pull_request: + # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request + types: + - opened + - reopened + +# defining what is going to run when the action is triggered +jobs: + sleepy-cat-pull-request: + # What shows up when it is triggered + name: A fabulous cat to cheer you on! + # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories + runs-on: ubuntu-latest + steps: + - uses: ruairidhwm/action-cats@1.0.1 + with: + # key GITHUB_TOKEN:, expression ${{}}, context secrets.GITHUB_TOKEN - alive for duration of workflow. Enables to copy code to vm + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + + + + From 9b0737df3fcd52759ec11afa3b6266500839061a Mon Sep 17 00:00:00 2001 From: Glenn Piludu Date: Sat, 21 Oct 2023 21:12:58 -0500 Subject: [PATCH 02/25] Added new db migration for constituents and transactions --- package.json | 1 + ...eate_constituent_and_transaction_tables.js | 32 +++++++++++++++++++ 2 files changed, 33 insertions(+) create mode 100644 server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js diff --git a/package.json b/package.json index d5f9b3915..eaa108d24 100644 --- a/package.json +++ b/package.json @@ -28,6 +28,7 @@ "db:create": "npm run db:create:dev && npm run db:create:test", "db:create:dev": "node script/create-db.js --env development", "db:create:test": "node script/drop-db.js --env test && node script/create-db.js --env test", + "db:migrate:create": "knex migrate:make --name", "db:migrate": "npm run db:migrate:dev && npm run db:migrate:test", "db:migrate:dev": "knex migrate:latest --verbose --env development", "db:migrate:test": "knex migrate:latest --verbose --env test", diff --git a/server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js b/server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js new file mode 100644 index 000000000..ccf437635 --- /dev/null +++ b/server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js @@ -0,0 +1,32 @@ +module.exports = { + async up(knex) { + await knex.schema.createTable('constituents', (table) => { + table.increments('id') + table.timestamps() + table.string('first_name') + table.string('last_name') + table.string('address_line_1') + table.string('address_line_2') + table.string('city') + table.string('state') + table.string('zipcode') + table.string('email') + }) + + // TODO: payment status id table + + await knex.schema.createTable('transactions', (table) => { + table.increments('id') + table.timestamps() + table.string('stripe_transaction_id') + table.integer('constituent_id') + table.integer('amount') + table.string('currency') + table.string('status_id') + }) + }, + + async down(knex) { + await knex.schema.doSomethingForReal() + } +} From 50865ffcb89ad0620e01a6c1b0dd3c4e684da4c8 Mon Sep 17 00:00:00 2001 From: Glenn Piludu Date: Sun, 22 Oct 2023 08:53:53 -0500 Subject: [PATCH 03/25] Fixed db migration to UPDATE already existing constituent and transactions tables --- ...eate_constituent_and_transaction_tables.js | 25 ++++++------------- 1 file changed, 7 insertions(+), 18 deletions(-) diff --git a/server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js b/server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js index ccf437635..00cd50c04 100644 --- a/server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js +++ b/server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js @@ -1,32 +1,21 @@ module.exports = { async up(knex) { - await knex.schema.createTable('constituents', (table) => { - table.increments('id') + await knex.schema.alterTable('constituents', (table) => { table.timestamps() - table.string('first_name') - table.string('last_name') - table.string('address_line_1') - table.string('address_line_2') - table.string('city') - table.string('state') - table.string('zipcode') - table.string('email') + table.renameColumn('street_address', 'address_line_1') + table.renameColumn('address_two', 'address_line_2') }) // TODO: payment status id table - await knex.schema.createTable('transactions', (table) => { - table.increments('id') - table.timestamps() - table.string('stripe_transaction_id') + await knex.schema.alterTable('transactions', (table) => { table.integer('constituent_id') - table.integer('amount') - table.string('currency') - table.string('status_id') + table.foreign('constituent_id').references('constituents.id') }) }, async down(knex) { - await knex.schema.doSomethingForReal() + await knex.schema.dropTable('constituents') + await knex.schema.dropTable('transactions') } } From e6a334ed0678dc88f817411983cd94c212dad65d Mon Sep 17 00:00:00 2001 From: Glenn Piludu Date: Sun, 5 Nov 2023 15:01:28 -0600 Subject: [PATCH 04/25] more db stuff --- ...eate_constituent_and_transaction_tables.js | 2 - server/db/models/_base.js | 9 +++- server/db/models/constituent.js | 43 +++++++++++++++++++ server/db/models/transaction.js | 40 +++++++++++++++++ 4 files changed, 91 insertions(+), 3 deletions(-) create mode 100644 server/db/models/constituent.js create mode 100644 server/db/models/transaction.js diff --git a/server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js b/server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js index 00cd50c04..e7ffe4141 100644 --- a/server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js +++ b/server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js @@ -6,8 +6,6 @@ module.exports = { table.renameColumn('address_two', 'address_line_2') }) - // TODO: payment status id table - await knex.schema.alterTable('transactions', (table) => { table.integer('constituent_id') table.foreign('constituent_id').references('constituents.id') diff --git a/server/db/models/_base.js b/server/db/models/_base.js index c89f9b8af..6b9213b1d 100644 --- a/server/db/models/_base.js +++ b/server/db/models/_base.js @@ -1,10 +1,17 @@ -const { Model } = require('objection') +const { Model, snakeCaseMappers } = require('objection') const { createClient } = require('../') class BaseModel extends Model { static get modelPaths() { return [__dirname] } + + // Maps column_name in postgres to columnName in the app. + // Queries must still use snake_case if sql is passed into a query method chain + // For ex. Constituent.query().where('first_name', 'Jennifer') + static get columnNameMappers() { + return snakeCaseMappers() + } } // One-time configuration to link all Objection models derived from this class with Knex diff --git a/server/db/models/constituent.js b/server/db/models/constituent.js new file mode 100644 index 000000000..e7a711e41 --- /dev/null +++ b/server/db/models/constituent.js @@ -0,0 +1,43 @@ +const BaseModel = require('./base') + +class Constituent extends BaseModel { + static get tableName() { + return 'constituents' + } + + static get jsonSchema() { + return { + type: 'object', + required: [ + 'email', + 'first_name', + 'last_name', + 'address_line_1', + 'city', + 'state', + 'zip' + ], + properties: { + email: { type: 'string', minLength: 1, maxLength: 255 }, + first_name: { type: 'string', minLength: 1, maxLength: 255 }, + last_name: { type: 'string', minLength: 1, maxLength: 255 }, + address_line_1: { type: 'string', minLength: 1, maxLength: 255 }, + city: { type: 'string', minLength: 1, maxLength: 255 }, + state: { type: 'string', minLength: 1, maxLength: 255 }, + zip: { type: 'string', minLength: 1, maxLength: 255 } + } + } + } + + /* + static get relationMappings() { + // TODO: Add relation to transactions + } + */ + + fullName() { + return `${this.first_name} ${this.last_name}` + } +} + +module.exports = Constituent diff --git a/server/db/models/transaction.js b/server/db/models/transaction.js new file mode 100644 index 000000000..c9f2c1b4b --- /dev/null +++ b/server/db/models/transaction.js @@ -0,0 +1,40 @@ +// const BaseModel = require('./_base') + +/* +class Transaction extends BaseModel { + static get tableName() { + return 'transactions' + } + + static get jsonSchema() { + return { + type: 'object', + required: [ + 'stripe_transaction_id', 'amount', 'currency', 'email', 'payment_method', 'payment_method_type' + ], + properties: { + stripe_transaction_id: { type: 'string' }, + amount: { type: 'number' }, + currency: { type: 'string' }, + email: { type: 'string' }, + status: { type: 'string' } + } + } + } + + static get relationMappings() { + const Constituent = require('./constituent') + + return { + constituent: { + relation: BaseModel.HasOneRelation, + modelClass: Constituent, + join: { + // from 'transactions.constituent_id', + to: 'constituents.id' + } + } + } + } +} +*/ From abd3bcfebedd0078cd3725c4b7f331e030a0b6f3 Mon Sep 17 00:00:00 2001 From: Glenn Piludu Date: Sun, 12 Nov 2023 10:46:47 -0600 Subject: [PATCH 05/25] Added stripe wrapper to new lib directory. --- server/lib/stripe.js | 68 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 server/lib/stripe.js diff --git a/server/lib/stripe.js b/server/lib/stripe.js new file mode 100644 index 000000000..1b689b86f --- /dev/null +++ b/server/lib/stripe.js @@ -0,0 +1,68 @@ +// Wrappers for Stripe's npm package +require('dotenv').config() + +class StripeError extends Error { + constructor(message) { + super(message) + this.name = 'StripeError' + } +} + +class Stripe { + constructor() { + this.stripeSecret = process.env.STRIPE_SECRET_KEY + this.stripe = require('stripe')(this.stripeSecret) + } + + /** + * Validates that an event actually comes from Stripe. Returns event or throws StripeError. + * @param {string} signature - Stripe signature header. + * @param {object} rawBody - Requests rawBody attribute. + */ + validateEvent(signature, rawBody) { + try { + return this.stripe.webhooks.constructEvent( + rawBody, + signature, + this.stripeSecret + ) + } catch (error) { + throw new StripeError(error.message) + } + } + + /** + * Creates a checkout session and returns the url and session id. + * @param {number} donationAmount - Donation amount (in cents). + * @param {string} customerEmail - For (optionally) pre-filling customer email on checkout page. + */ + async createCheckoutSession(donationAmount, customerEmail = '') { + try { + const session = await this.stripe.checkout.sessions.create({ + line_items: [ + { + price_data: { + currency: 'usd', + product_data: { + name: 'Donation' + }, + unit_amount: donationAmount + }, + quantity: 1, + customer_email: customerEmail + } + ], + mode: 'payment', + allow_promotion_codes: true, + success_url: '', + cancel_url: '' + }) + + return { url: session.url, id: session.id } + } catch (error) { + throw new StripeError(error.message) + } + } +} + +module.exports = { Stripe } From 95b92456e40ec1bf7f57bf733dd2c5a5269dacfd Mon Sep 17 00:00:00 2001 From: Glenn Piludu Date: Wed, 15 Nov 2023 16:19:52 -0600 Subject: [PATCH 06/25] poop migration --- {util => presenters}/format.js | 0 presenters/payment-presenter.js | 56 ++++++++++++++++++++++++++++++++ {util => presenters}/validate.js | 0 server/routes/api/checkout.js | 37 +++++++-------------- 4 files changed, 68 insertions(+), 25 deletions(-) rename {util => presenters}/format.js (100%) create mode 100644 presenters/payment-presenter.js rename {util => presenters}/validate.js (100%) diff --git a/util/format.js b/presenters/format.js similarity index 100% rename from util/format.js rename to presenters/format.js diff --git a/presenters/payment-presenter.js b/presenters/payment-presenter.js new file mode 100644 index 000000000..43a437dba --- /dev/null +++ b/presenters/payment-presenter.js @@ -0,0 +1,56 @@ +// Business logic for donations and payments +require('dotenv').config + +class PaymentPresenterError extends Error { + constructor(message) { + super(message) + this.name = 'PaymentPresenterError' + } +} + +class PaymentPresenter { + constructor() { + this.minimumPayment = process.env.MINIMUM_PAYMENT_AMOUNT + this.maximumPayment = process.env.MAXIMUM_PAYMENT_AMOUNT + } + + /** + * Validates that payments aren't something weird, like NaN or a very large number. + * @param {number} payment + */ + static validatePaymentAmount(payment) { + if (typeof payment !== 'number') { + return false + } + + if (payment < this.minimumPayment || payment > this.maximumPayment) { + return false + } + + return true + } + + /** + * Formats a value to cents, given a number, float, or numerical string + * @param {any} payment + * @returns {number} payment in cents + */ + formatPaymentAmount(payment) { + if (typeof payment == 'string') { + const nonNumerics = new RegExp(/[a-z.,]/, 'g') // Strips alphanumerics, commas, and periods out. + + payment = payment.replace(nonNumerics, '') + payment = Number(payment) + + if (isNaN(payment)) + throw new PaymentPresenterError('Amount is in unexpected format') + } + + if (typeof payment == 'object') + throw new PaymentPresenterError('Unparsable argument') + + return payment * 100 + } +} + +module.exports = { PaymentPresenter } diff --git a/util/validate.js b/presenters/validate.js similarity index 100% rename from util/validate.js rename to presenters/validate.js diff --git a/server/routes/api/checkout.js b/server/routes/api/checkout.js index e82dc1fe3..76cb90830 100644 --- a/server/routes/api/checkout.js +++ b/server/routes/api/checkout.js @@ -3,8 +3,10 @@ const { createClient } = require('../../db') const router = express.Router() const db = createClient() const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY) -const { formatDonationAmount } = require('../../../util/format') -const { validateDonationAmount } = require('../../../util/validate') +const { formatDonationAmount } = require('../../../presenters/format') +const { validateDonationAmount } = require('../../../presenters/validate') +const { Stripe } = require('../../lib/stripe') +const { PaymentPresenter } = require('../../../presenters') router.post('/create-transaction', async (req, res) => { const { sessionId /*, email /*, campaignId, donationId */ } = req.body || {} @@ -45,32 +47,17 @@ router.post('/create-checkout-session', async (req, res) => { const { donationAmount } = req.body || {} const origin = req.get('origin') - const input = formatDonationAmount(donationAmount) - const inputIsValid = validateDonationAmount(input) + const formattedDonation = PaymentPresenter.formatPaymentAmount(donationAmount) + const donationValid = + PaymentPresenter.validatePaymentAmount(formattedDonation) - if (inputIsValid) { - const donationAmountForStripe = input * 100 // Stripe accepts values in cents - let session + if (donationValid) { + const stripe = new Stripe() try { - session = await stripe.checkout.sessions.create({ - line_items: [ - { - price_data: { - currency: 'usd', - product_data: { - name: 'Donation' - }, - unit_amount: donationAmountForStripe - }, - quantity: 1 - } - ], - mode: 'payment', - allow_promotion_codes: true, - success_url: origin + '/complete?session_id={CHECKOUT_SESSION_ID}', - cancel_url: origin - }) + const session = await stripe.createCheckoutSession(donationAmount) + + // TODO: Move redirect logic here } catch (error) { const data = { type: error.type, From bc10da9d5763cde6b2464224f9980bf90b2dd6fc Mon Sep 17 00:00:00 2001 From: Glenn Piludu Date: Wed, 15 Nov 2023 21:16:34 -0600 Subject: [PATCH 07/25] Finished stripe checkout refactor (for now) Tweaked Payment Presenters Fixed postgres timestamps --- server/routes/api/checkout.js | 109 ++++++++++++++-------------------- 1 file changed, 45 insertions(+), 64 deletions(-) diff --git a/server/routes/api/checkout.js b/server/routes/api/checkout.js index 76cb90830..3384e7b87 100644 --- a/server/routes/api/checkout.js +++ b/server/routes/api/checkout.js @@ -1,84 +1,65 @@ const express = require('express') -const { createClient } = require('../../db') const router = express.Router() -const db = createClient() -const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY) -const { formatDonationAmount } = require('../../../presenters/format') -const { validateDonationAmount } = require('../../../presenters/validate') + const { Stripe } = require('../../lib/stripe') -const { PaymentPresenter } = require('../../../presenters') +const { + PaymentPresenter, + PaymentPresenterError +} = require('../../../presenters/payment-presenter') +const Constituent = require('../../db/models/constituent') +const Transaction = require('../../db/models/transaction') -router.post('/create-transaction', async (req, res) => { - const { sessionId /*, email /*, campaignId, donationId */ } = req.body || {} - if (!sessionId /*|| !email*/) { - return res.status(400).send({ error: 'No session ID' }) - } - const session = await stripe.checkout.sessions.retrieve(sessionId) +router.post('/create-checkout-session', async (req, res) => { + const { donationAmount, user } = req.body + const origin = req.get('origin') - const formattedTransaction = { - stripe_transaction_id: sessionId, - amount: session.amount_total, - currency: session.currency, - payment_method: 'something not empty', // Not sure what this is for - payment_method_type: session.payment_method_types[0], - email: session.customer_details.email // to-do: get user email from the server auth, if possible - } + console.log(`origin: ${origin}`) try { - // Expire session? - await db('transactions').insert(formattedTransaction) - res.status(200).send(formattedTransaction) - } catch (error) { - console.log({ error }) - res.status(400).send() - } + const presenter = new PaymentPresenter() - return res.status(200).end() -}) + const formattedDonation = presenter.formatPaymentAmount(donationAmount) -// 1. send a request to `/create-payment-intent` -// with a `donationAmount` as string or integer -// If user doesn't select any particular `donationAmount`, send `1` in the donationAmount -// 2. This API will redirect the client to a Stripe Checkout page -// 3. Once user completes payment, will redirect back to `success_url` with -// a Stripe session_id included in the URL. + // Will throw error if invalid amount is given. + presenter.validatePaymentAmount(formattedDonation) -router.post('/create-checkout-session', async (req, res) => { - const { donationAmount } = req.body || {} - const origin = req.get('origin') + // TODO: Should be strict https but we need to do some deployment fixes first. + const redirectUrl = `http://${origin}/complete?session_id={CHECKOUT_SESSION_ID}` + const cancelUrl = `http://${origin}` - const formattedDonation = PaymentPresenter.formatPaymentAmount(donationAmount) - const donationValid = - PaymentPresenter.validatePaymentAmount(formattedDonation) - - if (donationValid) { const stripe = new Stripe() + const session = await stripe.createCheckoutSession( + formattedDonation, + redirectUrl, + cancelUrl + ) - try { - const session = await stripe.createCheckoutSession(donationAmount) + // TODO: Move Constituent insert to earlier in the cycle. + const constituent = await Constituent.query().insert(user) + console.log(constituent) - // TODO: Move redirect logic here - } catch (error) { - const data = { - type: error.type, - code: error.raw.code, - url: error.raw.doc_url, - message: 'An error occurred with Stripe checkout', - entire_error_object: error - } + const transaction = await Transaction.query().insert({ + stripeTransactionId: session.id, + constituentId: constituent.id, + amount: formattedDonation, + currency: 'USD', + paymentMethod: 'credit_card' + }) + console.log(transaction) - console.log(data) - return res.status(500).json(data) + return res + .status(200) + .json({ url: session.url, sessionId: session.id }) + .end() + } catch (error) { + let statusCode = 500 + + if (error instanceof PaymentPresenterError) { + statusCode = 400 } - // console.log('session:', session) - // the redirection happens within `DonateMoney.vue` - return res.status(200).json({ url: session.url, sessionId: session.id }) - } else { - return res.status(400).send({ - error: 'Bad request: did not create Stripe checkout session', - message: 'Check backend console for possible failing reasons' - }) + // TODO: error logging + return res.status(statusCode).json({ error: error.message }).end() } }) From ad31ebb232a025cd31f269e3d82f85cceadbedb2 Mon Sep 17 00:00:00 2001 From: Glenn Piludu Date: Thu, 16 Nov 2023 19:22:27 -0600 Subject: [PATCH 08/25] Tweaks to Stripe class, payment presenter, etc Added process-transaction route Restored old validate and format files since they're being used in the front end. Will delete later. --- presenters/payment-presenter.js | 18 +++---- ...eate_constituent_and_transaction_tables.js | 23 ++++++-- server/db/models/constituent.js | 8 +-- server/db/models/transaction.js | 17 ++++-- server/lib/stripe.js | 17 +++--- server/routes/api/checkout.js | 52 ++++++++++++++++--- util/format.js | 10 ++++ util/validate.js | 19 +++++++ 8 files changed, 128 insertions(+), 36 deletions(-) create mode 100644 util/format.js create mode 100644 util/validate.js diff --git a/presenters/payment-presenter.js b/presenters/payment-presenter.js index 43a437dba..1ea9774ee 100644 --- a/presenters/payment-presenter.js +++ b/presenters/payment-presenter.js @@ -18,16 +18,14 @@ class PaymentPresenter { * Validates that payments aren't something weird, like NaN or a very large number. * @param {number} payment */ - static validatePaymentAmount(payment) { + validatePaymentAmount(payment) { if (typeof payment !== 'number') { - return false + throw new PaymentPresenterError('Payment is not a number') } if (payment < this.minimumPayment || payment > this.maximumPayment) { - return false + throw new PaymentPresenterError('Payment amount is out of range') } - - return true } /** @@ -42,15 +40,17 @@ class PaymentPresenter { payment = payment.replace(nonNumerics, '') payment = Number(payment) - if (isNaN(payment)) + if (isNaN(payment)) { throw new PaymentPresenterError('Amount is in unexpected format') + } } - if (typeof payment == 'object') + if (typeof payment == 'object') { throw new PaymentPresenterError('Unparsable argument') + } - return payment * 100 + return payment } } -module.exports = { PaymentPresenter } +module.exports = { PaymentPresenter, PaymentPresenterError } diff --git a/server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js b/server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js index e7ffe4141..2c6a0d211 100644 --- a/server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js +++ b/server/db/migrations/20231022014507_create_constituent_and_transaction_tables.js @@ -1,15 +1,28 @@ module.exports = { async up(knex) { await knex.schema.alterTable('constituents', (table) => { - table.timestamps() + table.timestamps(false, true, false) table.renameColumn('street_address', 'address_line_1') table.renameColumn('address_two', 'address_line_2') }) - await knex.schema.alterTable('transactions', (table) => { - table.integer('constituent_id') - table.foreign('constituent_id').references('constituents.id') - }) + await knex.schema + .alterTable('transactions', (table) => { + table.integer('constituent_id') + table.foreign('constituent_id').references('constituents.id') + + // Timestamps are not implemented with proper defaults so we need to drop them here... + table.dropTimestamps() + + table.dropColumn('payment_method_type') + table.dropColumn('email') + }) + .then(() => { + // ...then we add timestamps again here. + return knex.schema.alterTable('transactions', (table) => { + table.timestamps(false, true, false) + }) + }) }, async down(knex) { diff --git a/server/db/models/constituent.js b/server/db/models/constituent.js index e7a711e41..cdb4aad79 100644 --- a/server/db/models/constituent.js +++ b/server/db/models/constituent.js @@ -1,4 +1,4 @@ -const BaseModel = require('./base') +const BaseModel = require('./_base') class Constituent extends BaseModel { static get tableName() { @@ -10,9 +10,9 @@ class Constituent extends BaseModel { type: 'object', required: [ 'email', - 'first_name', - 'last_name', - 'address_line_1', + 'firstName', + 'lastName', + 'addressLine_1', 'city', 'state', 'zip' diff --git a/server/db/models/transaction.js b/server/db/models/transaction.js index c9f2c1b4b..c4615b841 100644 --- a/server/db/models/transaction.js +++ b/server/db/models/transaction.js @@ -1,6 +1,5 @@ -// const BaseModel = require('./_base') +const BaseModel = require('./_base') -/* class Transaction extends BaseModel { static get tableName() { return 'transactions' @@ -10,18 +9,24 @@ class Transaction extends BaseModel { return { type: 'object', required: [ - 'stripe_transaction_id', 'amount', 'currency', 'email', 'payment_method', 'payment_method_type' + 'stripeTransactionId', + 'constituentId', + 'amount', + 'currency', + 'paymentMethod' ], properties: { stripe_transaction_id: { type: 'string' }, + constituent_id: { type: 'number' }, amount: { type: 'number' }, currency: { type: 'string' }, - email: { type: 'string' }, + payment_method: { type: 'string' }, status: { type: 'string' } } } } + /* static get relationMappings() { const Constituent = require('./constituent') @@ -36,5 +41,7 @@ class Transaction extends BaseModel { } } } + */ } -*/ + +module.exports = Transaction diff --git a/server/lib/stripe.js b/server/lib/stripe.js index 1b689b86f..23c82a809 100644 --- a/server/lib/stripe.js +++ b/server/lib/stripe.js @@ -11,6 +11,7 @@ class StripeError extends Error { class Stripe { constructor() { this.stripeSecret = process.env.STRIPE_SECRET_KEY + this.stripeWebhookSecret = process.env.STRIPE_WEBHOOK_SECRET this.stripe = require('stripe')(this.stripeSecret) } @@ -36,7 +37,7 @@ class Stripe { * @param {number} donationAmount - Donation amount (in cents). * @param {string} customerEmail - For (optionally) pre-filling customer email on checkout page. */ - async createCheckoutSession(donationAmount, customerEmail = '') { + async createCheckoutSession(donationAmount, redirectUrl, cancelUrl) { try { const session = await this.stripe.checkout.sessions.create({ line_items: [ @@ -48,17 +49,21 @@ class Stripe { }, unit_amount: donationAmount }, - quantity: 1, - customer_email: customerEmail + quantity: 1 } ], mode: 'payment', allow_promotion_codes: true, - success_url: '', - cancel_url: '' + success_url: redirectUrl, + cancel_url: cancelUrl }) + console.log(session) - return { url: session.url, id: session.id } + return { + url: session.url, + id: session.id, + paymentIntent: session.payment_intent + } } catch (error) { throw new StripeError(error.message) } diff --git a/server/routes/api/checkout.js b/server/routes/api/checkout.js index 3384e7b87..663cb6e21 100644 --- a/server/routes/api/checkout.js +++ b/server/routes/api/checkout.js @@ -1,7 +1,5 @@ const express = require('express') -const router = express.Router() - -const { Stripe } = require('../../lib/stripe') +const { Stripe, StripeError } = require('../../lib/stripe') const { PaymentPresenter, PaymentPresenterError @@ -9,6 +7,8 @@ const { const Constituent = require('../../db/models/constituent') const Transaction = require('../../db/models/transaction') +const router = express.Router() + router.post('/create-checkout-session', async (req, res) => { const { donationAmount, user } = req.body const origin = req.get('origin') @@ -19,6 +19,7 @@ router.post('/create-checkout-session', async (req, res) => { const presenter = new PaymentPresenter() const formattedDonation = presenter.formatPaymentAmount(donationAmount) + console.log(formattedDonation) // Will throw error if invalid amount is given. presenter.validatePaymentAmount(formattedDonation) @@ -36,16 +37,14 @@ router.post('/create-checkout-session', async (req, res) => { // TODO: Move Constituent insert to earlier in the cycle. const constituent = await Constituent.query().insert(user) - console.log(constituent) - const transaction = await Transaction.query().insert({ - stripeTransactionId: session.id, + await Transaction.query().insert({ + stripeTransactionId: session.payment_intent, constituentId: constituent.id, amount: formattedDonation, currency: 'USD', paymentMethod: 'credit_card' }) - console.log(transaction) return res .status(200) @@ -63,4 +62,43 @@ router.post('/create-checkout-session', async (req, res) => { } }) +router.post('/process-transaction', async (req, res) => { + try { + // TODO: Implement webhook secret + const stripe = new Stripe() + + const signature = req.headers['stripe-signature'] + + console.log(signature) + + const event = stripe.validateEvent(signature, req.rawBody) + + const data = event.data + const { id: paymentIntent, amount } = data.object + const [eventType, eventOutcome] = req.body.type.split('.') + + // We are not going to send letters from this endpoint just yet + // so we will record the transaction no matter the outcome. + if (eventType !== 'payment_intent') { + throw new Error('Unexpected event!') + } + + await Transaction.query() + .patch({ amount, status: eventOutcome }) + .where({ stripe_transaction_id: paymentIntent }) + + return res.status(200).end() + } catch (error) { + let statusCode = 500 + + if (error instanceof StripeError) { + // Don't leak Stripe logging. + console.error(error.message) + error.message = 'Payment processing error' + } + + return res.status(statusCode).json({ error: error.message }) + } +}) + module.exports = router diff --git a/util/format.js b/util/format.js new file mode 100644 index 000000000..207b2019b --- /dev/null +++ b/util/format.js @@ -0,0 +1,10 @@ +// format input value +function formatDonationAmount(value) { + // separating parameter assignment and parseFloat operation for consistent outcome + value = parseFloat(value) // outputs: number + value = value.toFixed(2) // outputs: string + value = parseFloat(value) // outputs: number + return value // number +} + +module.exports = { formatDonationAmount } diff --git a/util/validate.js b/util/validate.js new file mode 100644 index 000000000..cda64fa04 --- /dev/null +++ b/util/validate.js @@ -0,0 +1,19 @@ +// validate input value, expects a number as parameter +function validateDonationAmount(value) { + let message = '' + + if (value > 1.49 && value < 10000.01) return true + + if (isNaN(value)) message = 'Please select or enter a valid amount' + if (value < 1.5) message = 'Please enter a donation amount higher than $1.50' + if (value > 10000) + message = + 'Amplify currently only accept donation amounts less than $10,000.00' + + // logs message to help with debugging + if (process.env.NODE_ENV === 'development') console.log(message) + + return false +} + +module.exports = { validateDonationAmount } From 78dbecbe458c1bec1e1c8b3d6062538e1f5cead8 Mon Sep 17 00:00:00 2001 From: Glenn Piludu Date: Thu, 16 Nov 2023 19:33:05 -0600 Subject: [PATCH 09/25] Removed some of the unneeded logic on the ActionComplete page. --- rebuild | 1 + script/rebuild.sh | 3 + src/components/ActionComplete.vue | 92 ++----------------------------- src/components/DonateMoney.vue | 13 +++-- start | 1 + stop | 1 + 6 files changed, 20 insertions(+), 91 deletions(-) create mode 120000 rebuild create mode 100755 script/rebuild.sh create mode 120000 start create mode 120000 stop diff --git a/rebuild b/rebuild new file mode 120000 index 000000000..c2f897577 --- /dev/null +++ b/rebuild @@ -0,0 +1 @@ +./script/rebuild.sh \ No newline at end of file diff --git a/script/rebuild.sh b/script/rebuild.sh new file mode 100755 index 000000000..4ce49efd0 --- /dev/null +++ b/script/rebuild.sh @@ -0,0 +1,3 @@ +#! /bin/bash + +docker-compose -f .docker/docker-compose.yml build amplify \ No newline at end of file diff --git a/src/components/ActionComplete.vue b/src/components/ActionComplete.vue index 9900b2104..37d84215e 100644 --- a/src/components/ActionComplete.vue +++ b/src/components/ActionComplete.vue @@ -1,15 +1,6 @@ diff --git a/src/components/DonateMoney.vue b/src/components/DonateMoney.vue index 9b629f203..6f6a58358 100644 --- a/src/components/DonateMoney.vue +++ b/src/components/DonateMoney.vue @@ -96,8 +96,13 @@ export default { ] } }, - computed: {}, - mounted() {}, + computed: { + userData() { + return this.$store.userData + } + }, + mounted () { + }, methods: { unsetCustomAmountSelection() { this.customAmountSelected = false @@ -121,8 +126,8 @@ export default { return }, createCheckoutSession(donationAmount) { - axios - .post('/api/checkout/create-checkout-session', { donationAmount }) + axios.post('/api/checkout/create-checkout-session', { donationAmount, user: this.userData }) + // TODO: Investigate whether we need to dump user state still. With the new stripe webhook it may not be necessary. .then((response) => { // Dump state to local storage before redirect this.$store.dispatch( diff --git a/start b/start new file mode 120000 index 000000000..b76c00708 --- /dev/null +++ b/start @@ -0,0 +1 @@ +./script/start.sh \ No newline at end of file diff --git a/stop b/stop new file mode 120000 index 000000000..8d8963fd4 --- /dev/null +++ b/stop @@ -0,0 +1 @@ +./script/stop.sh \ No newline at end of file From d6f50e718fed217ebe79c68599cd0dc4c9946dc2 Mon Sep 17 00:00:00 2001 From: Glenn Piludu Date: Sun, 19 Nov 2023 09:09:59 -0600 Subject: [PATCH 10/25] Tweaked donation component and where donation validation happens Decommissioned old validate and format methods in all spots (I think) Moved Presenters to a shared directory so it is obvious that FE/BE can both use it --- server/db/models/constituent.js | 1 + server/routes/api/checkout.js | 11 +-- {presenters => shared/presenters}/format.js | 0 .../presenters}/payment-presenter.js | 0 {presenters => shared/presenters}/validate.js | 0 src/components/DonateMoney.vue | 73 ++++++++----------- 6 files changed, 34 insertions(+), 51 deletions(-) rename {presenters => shared/presenters}/format.js (100%) rename {presenters => shared/presenters}/payment-presenter.js (100%) rename {presenters => shared/presenters}/validate.js (100%) diff --git a/server/db/models/constituent.js b/server/db/models/constituent.js index cdb4aad79..b6632354d 100644 --- a/server/db/models/constituent.js +++ b/server/db/models/constituent.js @@ -22,6 +22,7 @@ class Constituent extends BaseModel { first_name: { type: 'string', minLength: 1, maxLength: 255 }, last_name: { type: 'string', minLength: 1, maxLength: 255 }, address_line_1: { type: 'string', minLength: 1, maxLength: 255 }, + address_line_2: { type: 'string', minLength: 1, maxLength: 255 }, city: { type: 'string', minLength: 1, maxLength: 255 }, state: { type: 'string', minLength: 1, maxLength: 255 }, zip: { type: 'string', minLength: 1, maxLength: 255 } diff --git a/server/routes/api/checkout.js b/server/routes/api/checkout.js index 663cb6e21..5f371fc26 100644 --- a/server/routes/api/checkout.js +++ b/server/routes/api/checkout.js @@ -3,7 +3,7 @@ const { Stripe, StripeError } = require('../../lib/stripe') const { PaymentPresenter, PaymentPresenterError -} = require('../../../presenters/payment-presenter') +} = require('../../../shared/presenters/payment-presenter') const Constituent = require('../../db/models/constituent') const Transaction = require('../../db/models/transaction') @@ -18,11 +18,8 @@ router.post('/create-checkout-session', async (req, res) => { try { const presenter = new PaymentPresenter() - const formattedDonation = presenter.formatPaymentAmount(donationAmount) - console.log(formattedDonation) - // Will throw error if invalid amount is given. - presenter.validatePaymentAmount(formattedDonation) + presenter.validatePaymentAmount(donationAmount) // TODO: Should be strict https but we need to do some deployment fixes first. const redirectUrl = `http://${origin}/complete?session_id={CHECKOUT_SESSION_ID}` @@ -30,7 +27,7 @@ router.post('/create-checkout-session', async (req, res) => { const stripe = new Stripe() const session = await stripe.createCheckoutSession( - formattedDonation, + donationAmount, redirectUrl, cancelUrl ) @@ -41,7 +38,7 @@ router.post('/create-checkout-session', async (req, res) => { await Transaction.query().insert({ stripeTransactionId: session.payment_intent, constituentId: constituent.id, - amount: formattedDonation, + amount: donationAmount, currency: 'USD', paymentMethod: 'credit_card' }) diff --git a/presenters/format.js b/shared/presenters/format.js similarity index 100% rename from presenters/format.js rename to shared/presenters/format.js diff --git a/presenters/payment-presenter.js b/shared/presenters/payment-presenter.js similarity index 100% rename from presenters/payment-presenter.js rename to shared/presenters/payment-presenter.js diff --git a/presenters/validate.js b/shared/presenters/validate.js similarity index 100% rename from presenters/validate.js rename to shared/presenters/validate.js diff --git a/src/components/DonateMoney.vue b/src/components/DonateMoney.vue index 6f6a58358..08e38bd6e 100644 --- a/src/components/DonateMoney.vue +++ b/src/components/DonateMoney.vue @@ -1,4 +1,4 @@ - + + \ No newline at end of file From 091a482a0c8fe1067a3fd92499ab9d9a6916d8c7 Mon Sep 17 00:00:00 2001 From: Jiachun Xiang <18576811+mpa-mxiang@users.noreply.github.com> Date: Wed, 29 May 2024 17:43:16 -0400 Subject: [PATCH 20/25] Create app-policy.hcl --- app-policy.hcl | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 app-policy.hcl diff --git a/app-policy.hcl b/app-policy.hcl new file mode 100644 index 000000000..51899b6db --- /dev/null +++ b/app-policy.hcl @@ -0,0 +1,4 @@ +path "secrets/hashi-corp-hackpod/*" +{ +capabilities = ["read"] +} From 43fc7177dd154e218978dba050592cc5316bd4a1 Mon Sep 17 00:00:00 2001 From: Jiachun Xiang <18576811+mpa-mxiang@users.noreply.github.com> Date: Wed, 29 May 2024 17:51:23 -0400 Subject: [PATCH 21/25] Create integration-tests.yaml --- integration-tests.yaml | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 integration-tests.yaml diff --git a/integration-tests.yaml b/integration-tests.yaml new file mode 100644 index 000000000..bcc91bbc8 --- /dev/null +++ b/integration-tests.yaml @@ -0,0 +1,6 @@ + uses: hashicorp/vault-action@v2.4.0 + with: + url: https://vault-public-vault-22deb760.8ee49bbe.z1.hashicorp.cloud:8200 + role: demo + method: jwt + namespace: "/admin/user5/" \ No newline at end of file From 01809488dd227feca7f967b32e8208c911f08fc7 Mon Sep 17 00:00:00 2001 From: Jiachun Xiang <18576811+mpa-mxiang@users.noreply.github.com> Date: Wed, 29 May 2024 17:53:35 -0400 Subject: [PATCH 22/25] delete two useless slashes --- integration-tests.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration-tests.yaml b/integration-tests.yaml index bcc91bbc8..747766ce7 100644 --- a/integration-tests.yaml +++ b/integration-tests.yaml @@ -3,4 +3,4 @@ url: https://vault-public-vault-22deb760.8ee49bbe.z1.hashicorp.cloud:8200 role: demo method: jwt - namespace: "/admin/user5/" \ No newline at end of file + namespace: "admin/user5" \ No newline at end of file From bef574548e8ad6582cc174f88392eff001e95561 Mon Sep 17 00:00:00 2001 From: Jiachun Xiang <18576811+mpa-mxiang@users.noreply.github.com> Date: Wed, 29 May 2024 18:02:19 -0400 Subject: [PATCH 23/25] delete the wrong integration-tests yaml file and modify the original one --- .github/workflows/integration-tests.yaml | 19 ++++++------------- integration-tests.yaml | 6 ------ 2 files changed, 6 insertions(+), 19 deletions(-) delete mode 100644 integration-tests.yaml diff --git a/.github/workflows/integration-tests.yaml b/.github/workflows/integration-tests.yaml index 1e33c335b..dbe5bf2f0 100644 --- a/.github/workflows/integration-tests.yaml +++ b/.github/workflows/integration-tests.yaml @@ -31,16 +31,9 @@ jobs: run: npm ci - name: Run tests - run: npm test -- server/__tests__/integration/ - env: - # Cicero API key - CICERO_API_KEY: ${{ secrets.TEST_CICERO_API_KEY }} - # Test environment Lob API key - LOB_API_KEY: ${{ secrets.TEST_LOB_API_KEY }} - # Stripe test secret key - STRIPE_SECRET_KEY: ${{ secrets.TEST_STRIPE_SECRET_KEY }} - # Auth0 authentication parameters with nonsensical sample values - SERVER_PORT: 8080 - CLIENT_ORIGIN_URL: http://localhost:8080 - AUTH0_AUDIENCE: your_Auth0_identifier_value - AUTH0_DOMAIN: your_Auth0_domain + uses: hashicorp/vault-action@v2.4.0 + with: + url: https://vault-public-vault-22deb760.8ee49bbe.z1.hashicorp.cloud:8200 + role: demo + method: jwt + namespace: "admin/user5" diff --git a/integration-tests.yaml b/integration-tests.yaml deleted file mode 100644 index 747766ce7..000000000 --- a/integration-tests.yaml +++ /dev/null @@ -1,6 +0,0 @@ - uses: hashicorp/vault-action@v2.4.0 - with: - url: https://vault-public-vault-22deb760.8ee49bbe.z1.hashicorp.cloud:8200 - role: demo - method: jwt - namespace: "admin/user5" \ No newline at end of file From a94210e35439462524a5875b189071754ea73807 Mon Sep 17 00:00:00 2001 From: anaymous Date: Fri, 7 Jun 2024 17:20:39 -0400 Subject: [PATCH 24/25] Create sleepyCat.yaml --- .github/workflows/sleepyCat.yaml | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) create mode 100644 .github/workflows/sleepyCat.yaml diff --git a/.github/workflows/sleepyCat.yaml b/.github/workflows/sleepyCat.yaml new file mode 100644 index 000000000..f149ce690 --- /dev/null +++ b/.github/workflows/sleepyCat.yaml @@ -0,0 +1,22 @@ +name: sleepy-cat +# on key is triggered when pull request is opened or reopened +on: + pull_request: + # Reference: https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request + types: + - opened + - reopened +# defining what is going to run when the action is triggered +jobs: + sleepyCat-pull-request: + # What shows up when it is triggered + name: A sleepy cat with a good happy dream + # Reference: https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories + runs-on: ubuntu-latest + steps: + - uses: ruairidhwm/action-cats@1.0.1 + with: + # GitHub token reference: https://docs.github.com/en/actions/security-guides/automatic-token-authentication + # key GITHUB_TOKEN:, expression ${{}}, context secrets.GITHUB_TOKEN - alive for the duration of the workflow, and enable to copy code to vm + # workflow expression reference: https://docs.github.com/en/actions/security-guides/automatic-token-authentication + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} From e5021f674b98f72dd26f380e8deb1ae23acd9e31 Mon Sep 17 00:00:00 2001 From: Jiachun Xiang <18576811+mpa-mxiang@users.noreply.github.com> Date: Fri, 7 Jun 2024 17:30:08 -0400 Subject: [PATCH 25/25] delete all other actions but add sleepy cat --- .github/workflows/action-cats.yml | 16 --- .github/workflows/build.yml | 39 ------- .github/workflows/cats.yml | 22 ---- .github/workflows/check-formatting.yml | 33 ------ .github/workflows/codeql-analysis.yml | 43 ------- .github/workflows/emojiPR.yml | 27 ----- .github/workflows/finish-hackpod.yml | 36 ------ .github/workflows/get-originaltime.yml | 33 ------ .github/workflows/integration-tests.yaml | 39 ------- .github/workflows/issue-metrics.yml | 50 --------- .github/workflows/labeler.yml | 39 ------- .github/workflows/lint.yml | 33 ------ .github/workflows/pr-metrics.yml | 106 ------------------ .github/workflows/programequity_slack.yml | 31 ----- .github/workflows/scorecards-analysis.yml | 63 ----------- .../{sleepyCat.yaml => sleepy-cat.yaml} | 0 .github/workflows/sleepycats.yaml | 26 ----- .github/workflows/unit-tests.yml | 36 ------ .github/workflows/welcome_message.yml | 21 ---- .github/workflows/workflow-lint.yml | 30 ----- 20 files changed, 723 deletions(-) delete mode 100644 .github/workflows/action-cats.yml delete mode 100644 .github/workflows/build.yml delete mode 100644 .github/workflows/cats.yml delete mode 100644 .github/workflows/check-formatting.yml delete mode 100644 .github/workflows/codeql-analysis.yml delete mode 100644 .github/workflows/emojiPR.yml delete mode 100644 .github/workflows/finish-hackpod.yml delete mode 100644 .github/workflows/get-originaltime.yml delete mode 100644 .github/workflows/integration-tests.yaml delete mode 100644 .github/workflows/issue-metrics.yml delete mode 100644 .github/workflows/labeler.yml delete mode 100644 .github/workflows/lint.yml delete mode 100644 .github/workflows/pr-metrics.yml delete mode 100644 .github/workflows/programequity_slack.yml delete mode 100644 .github/workflows/scorecards-analysis.yml rename .github/workflows/{sleepyCat.yaml => sleepy-cat.yaml} (100%) delete mode 100644 .github/workflows/sleepycats.yaml delete mode 100644 .github/workflows/unit-tests.yml delete mode 100644 .github/workflows/welcome_message.yml delete mode 100644 .github/workflows/workflow-lint.yml diff --git a/.github/workflows/action-cats.yml b/.github/workflows/action-cats.yml deleted file mode 100644 index 4e4925373..000000000 --- a/.github/workflows/action-cats.yml +++ /dev/null @@ -1,16 +0,0 @@ -name: Cats 😺 - -on: - pull_request_target: - types: - - opened - - reopened - -jobs: - aCatForCreatingThePullRequest: - name: A cat for your effort! - runs-on: ubuntu-latest - steps: - - uses: ruairidhwm/action-cats@1.0.2 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml deleted file mode 100644 index 7d981c773..000000000 --- a/.github/workflows/build.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: Build application - -on: - workflow_dispatch: - pull_request: - -permissions: - contents: read - -# This allows a subsequently queued workflow run to interrupt previous runs -concurrency: - group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' - cancel-in-progress: true - -jobs: - build: - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Check out repo - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - - - name: Setup node - uses: actions/setup-node@969bd2663942d722d85b6a8626225850c2f7be4b - with: - node-version-file: '.node-version' - cache: npm - - - name: Install dependencies - run: npm ci - - - name: Build application - run: npm run build - env: - # Auth0 authentication parameters with nonsensical sample values - SERVER_PORT: 8080 - CLIENT_ORIGIN_URL: http://localhost:8080 - AUTH0_AUDIENCE: your_Auth0_identifier_value - AUTH0_DOMAIN: your_Auth0_domain diff --git a/.github/workflows/cats.yml b/.github/workflows/cats.yml deleted file mode 100644 index 64bac52b3..000000000 --- a/.github/workflows/cats.yml +++ /dev/null @@ -1,22 +0,0 @@ -name: Add a cat gif on pr push - -on: - pull_request_target: - types: - - opened - - reopened - -permissions: - contents: read - -jobs: - cats: - name: Cat - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Action Cats - uses: ruairidhwm/action-cats@1.0.2 - with: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/check-formatting.yml b/.github/workflows/check-formatting.yml deleted file mode 100644 index ca706852b..000000000 --- a/.github/workflows/check-formatting.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Check formatting - -on: - workflow_dispatch: - pull_request: - -permissions: - contents: read - -# This allows a subsequently queued workflow run to interrupt previous runs -concurrency: - group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' - cancel-in-progress: true - -jobs: - check: - runs-on: ubuntu-latest - timeout-minutes: 2 - steps: - - name: Check out repo - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - - - name: Setup node - uses: actions/setup-node@969bd2663942d722d85b6a8626225850c2f7be4b - with: - node-version-file: '.node-version' - cache: npm - - - name: Install dependencies - run: npm ci - - - name: Check code style - run: npm run format:check diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml deleted file mode 100644 index f99aa251a..000000000 --- a/.github/workflows/codeql-analysis.yml +++ /dev/null @@ -1,43 +0,0 @@ -name: 'CodeQL' - -on: - push: - branches: - - main - pull_request: - # The PR base branches below must be a subset of the push branches above - branches: - - main - # Only execute on PRs if relevant files changed - paths: - - '**/*.js' - - '.github/workflows/codeql-analysis.yml' - schedule: - - cron: '27 1 * * 0' - -permissions: - actions: read - contents: read - security-events: write - -# This allows a subsequently queued workflow run to interrupt previous runs -concurrency: - group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' - cancel-in-progress: true - -jobs: - analyze: - runs-on: ubuntu-latest - - steps: - - name: Checkout repository - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - - # Initializes the CodeQL tools for scanning. - - name: Initialize CodeQL - uses: github/codeql-action/init@f3feb00acb00f31a6f60280e6ace9ca31d91c76a - with: - languages: javascript - - - name: Perform CodeQL Analysis - uses: github/codeql-action/analyze@f3feb00acb00f31a6f60280e6ace9ca31d91c76a diff --git a/.github/workflows/emojiPR.yml b/.github/workflows/emojiPR.yml deleted file mode 100644 index b305cdd43..000000000 --- a/.github/workflows/emojiPR.yml +++ /dev/null @@ -1,27 +0,0 @@ -# this is an action that will look for keywords in the title of a pull request and add a corresponding emoji to the comment section of the Pull Request so users can quickly identify the intention of a commit by the emoji generated -name: An Emoji for Your Hard Work! -# this action will trigger on every new pull request opened -on: - pull_request: - types: - - opened - -jobs: - # This is the job definition for 'gitemote', which will run as part of the GitHub Actions workflow. - gitemote: - # This job will run on the latest version of Ubuntu - runs-on: ubuntu-latest - # This step checks out a copy of your repository and see if any changes have been made - steps: - - name: Checkout Code - uses: actions/checkout@v2 - # Step 2: Run PR emote generator. - # This custom action is used to generate emotes for pull requests. - # It's referencing to the main branch of an action that I created based off of the original PR Emote Generator action created by @rcmtcristian. - - name: PR emote generator - uses: lingeorge88/gitemotePR_GL@main - with: - # The GITHUB_TOKEN is a special type of secret that is automatically created by GitHub. - # It is used to authenticate in the workflow and perform actions on the repository. - # Here it is passed to the custom action, which uses it to interact with GitHub API. - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/finish-hackpod.yml b/.github/workflows/finish-hackpod.yml deleted file mode 100644 index 89a3bcd2c..000000000 --- a/.github/workflows/finish-hackpod.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Run on Label Assignment (finish hackpod) - -on: - pull_request: - types: - - labeled - -jobs: - run_on_label_assignment: - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@v2 - - - name: Install - run: npm install - working-directory: ./roiScript - - - name: Set Branch Name - id: branch_name - run: echo "::set-output name=issue_branch::${{ github.head_ref }}" - - - name: Get Current User - id: current_user - run: echo "::set-output name=gh_handle::${{ github.actor }}" - - - name: Run script on label assignment - if: contains(github.event.pull_request.labels.*.name, 'completed hackpod issue') # no way to set a particular label workflow trigger -- https://github.com/orgs/community/discussions/26261 - env: - PR_PAYLOAD: ${{ toJson(github.event.pull_request._links.comments.href) }} # was: github # github.event.pull_request._links.comments.href - NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }} - NOTION_DATABASE_ID: ${{ secrets.NOTION_DATABASE_ID }} - BRANCH_NAME: ${{ steps.branch_name.outputs.issue_branch }} - GH_HANDLE: ${{ steps.current_user.outputs.gh_handle }} - run: node ./roiScript/send-metrics.mjs diff --git a/.github/workflows/get-originaltime.yml b/.github/workflows/get-originaltime.yml deleted file mode 100644 index 0a1e3474c..000000000 --- a/.github/workflows/get-originaltime.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Detect Original Time Label - -on: - issues: - types: [labeled] - -jobs: - run_on_label_assignment: - runs-on: ubuntu-latest - - steps: - - name: Checkout Repository - uses: actions/checkout@v2 - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: 16 - - # TODO: fix the unsupported engine error - - name: Install - run: npm install # because of amplify-script@1.0.0 only able to install node: '16.x' - working-directory: ./roiScript - - - name: Send time - if: contains(join(github.event.issue.labels.*.name, ','), 'originaltime-') - env: - LABELS: ${{ toJson(github.event.issue.labels) }} - NOTION_TOKEN: ${{ secrets.NOTION_TOKEN }} - NOTION_DATABASE_ID: ${{ secrets.NOTION_DATABASE_ID }} - ISSUE_NUMBER: ${{ github.event.issue.number }} - GH_HANDLE: ${{ github.actor }} - run: node ./roiScript/send-time.mjs diff --git a/.github/workflows/integration-tests.yaml b/.github/workflows/integration-tests.yaml deleted file mode 100644 index dbe5bf2f0..000000000 --- a/.github/workflows/integration-tests.yaml +++ /dev/null @@ -1,39 +0,0 @@ -name: Integration Tests - -on: - workflow_dispatch: - pull_request: - -permissions: - contents: read - -# This allows a subsequently queued workflow run to interrupt previous runs -concurrency: - group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' - cancel-in-progress: true - -jobs: - test: - if: ${{ github.repository == 'ProgramEquity/amplify' }} - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Check out repo - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - - - name: Setup node - uses: actions/setup-node@969bd2663942d722d85b6a8626225850c2f7be4b - with: - node-version-file: '.node-version' - cache: npm - - - name: Install dependencies - run: npm ci - - - name: Run tests - uses: hashicorp/vault-action@v2.4.0 - with: - url: https://vault-public-vault-22deb760.8ee49bbe.z1.hashicorp.cloud:8200 - role: demo - method: jwt - namespace: "admin/user5" diff --git a/.github/workflows/issue-metrics.yml b/.github/workflows/issue-metrics.yml deleted file mode 100644 index a482b3927..000000000 --- a/.github/workflows/issue-metrics.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Measure issue timestamp - -on: - issues: - types: [assigned] - -jobs: - record_issue_timestamp: - runs-on: ubuntu-latest - if: github.event_name == 'issues' - steps: - - name: Checkout the repo - uses: actions/checkout@v3 - - - name: Create branch linked to the issue - id: create_branch - run: | - issue_number=$(echo "${{ github.event.issue.number }}") - git checkout -b "issue-${issue_number}" - echo "::set-output name=issue_branch::issue-${issue_number}" - - - name: Install - run: npm install - working-directory: ./roiScript - - - name: Run - env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} - ISSUE_NUMBER: ${{ github.event.issue.number }} - run: node ./roiScript/log-branch.mjs - - - name: Log Current Branch (debugging) - run: | - current_branch=$(git rev-parse --abbrev-ref HEAD) - echo "Currently checked out branch: $current_branch" - - - name: Add Issue Timestamp To File - working-directory: roiScript - run: | - issue_num=$(echo "${{ github.event.issue.number }}") - timestamp=$(date +"%Y-%m-%d %H:%M:%S") - echo "$GITHUB_ACTOR, issue #${issue_num}: $timestamp" >> issue_timestamp.log - - - name: Commit Issue Timestamp - run: | - git config --global user.email "Polinka7max@gmail.com" - git config --global user.name "Dunridge" - git add . - git commit -m "Add issue timestamp by $GITHUB_ACTOR" - git push https://${{ secrets.GH_TOKEN }}@github.com/${{ github.repository }}.git diff --git a/.github/workflows/labeler.yml b/.github/workflows/labeler.yml deleted file mode 100644 index de8dfa2ea..000000000 --- a/.github/workflows/labeler.yml +++ /dev/null @@ -1,39 +0,0 @@ -name: label new contributor -on: - issues: - types: [opened] - pull_request_target: - types: [opened] - -jobs: - labeler: - runs-on: ubuntu-latest - steps: - - uses: actions/github-script@v6 - with: - script: | - const eventType = context.eventName; - // Get a list of all issues created by the PR opener - // See: https://octokit.github.io/rest.js/#pagination - const creator = context.payload.sender.login - const opts = github.rest.issues.listForRepo.endpoint.merge({ - ...context.issue, - creator, - state: 'all' - }) - const issues = await github.paginate(opts) - - for (const issue of issues) { - if (issue.number === context.issue.number) { - continue - } - - if (issue.pull_request || issue.user.login === creator) { - return // Creator is already a contributor. - } } - await github.rest.issues.addLabels({ - issue_number: context.issue.number, - owner: context.repo.owner, - repo: context.repo.repo, - labels:["new contributor"] - }); diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml deleted file mode 100644 index 31dc75cef..000000000 --- a/.github/workflows/lint.yml +++ /dev/null @@ -1,33 +0,0 @@ -name: Lint code - -on: - workflow_dispatch: - pull_request: - -permissions: - contents: read - -# This allows a subsequently queued workflow run to interrupt previous runs -concurrency: - group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' - cancel-in-progress: true - -jobs: - lint: - runs-on: ubuntu-latest - timeout-minutes: 2 - steps: - - name: Check out repo - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - - - name: Setup node - uses: actions/setup-node@969bd2663942d722d85b6a8626225850c2f7be4b - with: - node-version-file: '.node-version' - cache: npm - - - name: Install dependencies - run: npm ci - - - name: Run linter - run: npm run lint:check diff --git a/.github/workflows/pr-metrics.yml b/.github/workflows/pr-metrics.yml deleted file mode 100644 index 02394f3ac..000000000 --- a/.github/workflows/pr-metrics.yml +++ /dev/null @@ -1,106 +0,0 @@ -name: ROI PR metrics - -on: - pull_request: - types: [review_requested] - -jobs: - record_pr_timestamp: - runs-on: ubuntu-latest - if: github.event_name == 'pull_request' - outputs: - pr_timestamp: ${{ steps.get_pr_timestamp.outputs.pr_timestamp }} - steps: - - name: Get Current Timestamp for PR - id: get_pr_timestamp - run: | - current_timestamp=$(date -u +'%Y-%m-%dT%H:%M:%SZ') - seconds_since_epoch=$(date -d "${current_timestamp}" +%s) - echo "PR timestamp: ${current_timestamp}" - echo "PR timestamp in seconds: ${seconds_since_epoch}" - echo "::set-output name=pr_timestamp::${seconds_since_epoch}" - - get_issue_timestamp: - needs: record_pr_timestamp - runs-on: ubuntu-latest - outputs: - issue_timestamp: ${{ steps.get_issue_timestamp.outputs.issue_timestamp }} - steps: - - name: Checkout the repository - uses: actions/checkout@v2 - - - name: Checkout Source Branch - run: | - git fetch - echo "Current Branch: $(git branch)" - git checkout ${{github.event.pull_request.head.ref}} - - - name: Get Issue Timestamp - id: get_issue_timestamp - working-directory: roiScript - run: | # timestamp: Dunridge, issue #767: 2023-11-14 16:06:02 - issue_timestamp=$(cat issue_timestamp.log | awk -F ': ' '{print $2}') - seconds_since_epoch=$(date -d "${issue_timestamp}" +"%s") - echo "Issue timestamp from the first step: ${issue_timestamp}" - echo "Issue timestamp in seconds: ${seconds_since_epoch}" - echo "::set-output name=issue_timestamp::${seconds_since_epoch}" - echo "" > issue_timestamp.log - - - name: Commit Timestamp Removal # step + echo "" > issue_timestamp.log -- for clearing the issue_timestamp.log file so that this doesn't run on second reviewer assignment - run: | - git config --global user.email "Polinka7max@gmail.com" - git config --global user.name "Dunridge" - git add . - git commit -m "Remove issue timestamp by $GITHUB_ACTOR" - git push https://${{ secrets.GH_TOKEN }}@github.com/${{ github.repository }}.git - - calculate_time_difference: - needs: - - record_pr_timestamp - - get_issue_timestamp - runs-on: ubuntu-latest - outputs: - time_diff: ${{ steps.time_difference.outputs.time_diff }} - env: - pr_timestamp_input: ${{needs.record_pr_timestamp.outputs.pr_timestamp}} - issue_timestamp_input: ${{needs.get_issue_timestamp.outputs.issue_timestamp}} - steps: - - name: Calculate Time Difference - id: time_difference - run: | - issue_timestamp=$issue_timestamp_input - echo "Issue timestamp (seconds): ${issue_timestamp}" - pr_timestamp=$pr_timestamp_input - echo "PR timestamp (seconds): ${pr_timestamp}" - time_diff=$((pr_timestamp - issue_timestamp)) - echo "Time Difference: ${time_diff}" - echo "::set-output name=time_diff::${time_diff}" - - metric: - needs: calculate_time_difference - runs-on: ubuntu-latest - env: - time_diff_input: ${{needs.calculate_time_difference.outputs.time_diff}} - steps: - - name: Checkout code - uses: actions/checkout@v2 - - - name: Setup Node.js - uses: actions/setup-node@v2 - with: - node-version: 18 # pick the node version that's compatible with libraries - - - name: Output Time Diff - run: | - echo "Time Diff For Output: $time_diff_input" - - - name: Install - run: npm install - working-directory: ./roiScript - - - name: Run - env: - GH_TOKEN: ${{ secrets.GH_TOKEN }} - TIME_DIFF: ${{ needs.calculate_time_difference.outputs.time_diff }} - PR_NUMBER: ${{ github.event.number }} - run: node ./roiScript/issue-metrics.mjs diff --git a/.github/workflows/programequity_slack.yml b/.github/workflows/programequity_slack.yml deleted file mode 100644 index 3c6b40061..000000000 --- a/.github/workflows/programequity_slack.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: Notify mentioned users via Slack - -on: - issues: - types: [opened, edited] - issue_comment: - types: [created, edited] - pull_request_target: - types: [opened, edited, review_requested] - pull_request_review: - types: [submitted] - pull_request_review_comment: - types: [created, edited] - -permissions: - issues: read - pull-requests: read - -jobs: - mention-to-slack: - runs-on: ubuntu-latest - steps: - - name: Run - uses: abeyuya/actions-mention-to-slack@v2 - with: - repo-token: ${{ secrets.GITHUB_TOKEN }} - # Send messages to the 'devs' channel in the ProgramEquity Slack workspace - slack-webhook-url: ${{ secrets.SLACK_WEBHOOK_URL }} - icon-url: https://img.icons8.com/color/256/000000/github-2.png - bot-name: 'Mentioned on GitHub: ${{ github.event_name }}' - run-id: ${{ github.run_id }} diff --git a/.github/workflows/scorecards-analysis.yml b/.github/workflows/scorecards-analysis.yml deleted file mode 100644 index f9b50f65d..000000000 --- a/.github/workflows/scorecards-analysis.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Scorecards supply-chain security - -on: - # Only the default branch is supported. - branch_protection_rule: - schedule: - - cron: '43 10 * * 6' - push: - branches: - - main - -permissions: - contents: read - -jobs: - analysis: - runs-on: ubuntu-latest - - permissions: - actions: read - contents: read - # Used to receive a badge. - id-token: write - # Needed to upload the results to code-scanning dashboard. - security-events: write - - steps: - - name: Check out repo - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - with: - persist-credentials: false - - - name: Run analysis - uses: ossf/scorecard-action@e363bfca00e752f91de7b7d2a77340e2e523cb18 - with: - results_file: results.sarif - results_format: sarif - # Read-only PAT token. To create it, - # follow the steps in https://github.com/ossf/scorecard-action#pat-token-creation. - repo_token: ${{ secrets.SCORECARD_READ_TOKEN }} - # Publish the results to enable scorecard badges. For more details, see - # https://github.com/ossf/scorecard-action#publishing-results. - # For private repositories, `publish_results` will automatically be set to `false`, - # regardless of the value entered here. - publish_results: true - - # Upload the results as artifacts (optional). - - name: Upload artifact - uses: actions/upload-artifact@3cea5372237819ed00197afe530f5a7ea3e805c8 - with: - name: SARIF file - path: results.sarif - retention-days: 5 - - # Upload the results to GitHub's code scanning dashboard. - - name: Upload to code-scanning - uses: github/codeql-action/upload-sarif@f3feb00acb00f31a6f60280e6ace9ca31d91c76a - with: - sarif_file: results.sarif - - # Create a job summary with the OSSF badge. - - name: Output custom summary - run: echo "["'!'"[OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/$GITHUB_REPOSITORY/badge)](https://api.securityscorecards.dev/projects/github.com/$GITHUB_REPOSITORY)" >> $GITHUB_STEP_SUMMARY diff --git a/.github/workflows/sleepyCat.yaml b/.github/workflows/sleepy-cat.yaml similarity index 100% rename from .github/workflows/sleepyCat.yaml rename to .github/workflows/sleepy-cat.yaml diff --git a/.github/workflows/sleepycats.yaml b/.github/workflows/sleepycats.yaml deleted file mode 100644 index 263552fb7..000000000 --- a/.github/workflows/sleepycats.yaml +++ /dev/null @@ -1,26 +0,0 @@ -name: sleepy-cat -# on key is trigger when pull request is opened or reopened -on: - pull_request: - # https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request - types: - - opened - - reopened - -# defining what is going to run when the action is triggered -jobs: - sleepy-cat-pull-request: - # What shows up when it is triggered - name: A fabulous cat to cheer you on! - # https://docs.github.com/en/actions/using-github-hosted-runners/about-github-hosted-runners/about-github-hosted-runners#standard-github-hosted-runners-for-public-repositories - runs-on: ubuntu-latest - steps: - - uses: ruairidhwm/action-cats@1.0.1 - with: - # key GITHUB_TOKEN:, expression ${{}}, context secrets.GITHUB_TOKEN - alive for duration of workflow. Enables to copy code to vm - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - - - diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml deleted file mode 100644 index bea82409d..000000000 --- a/.github/workflows/unit-tests.yml +++ /dev/null @@ -1,36 +0,0 @@ -name: Unit Tests -on: - workflow_dispatch: - pull_request: -permissions: - contents: read -concurrency: - group: >- - ${{ github.workflow }} @ ${{ github.event.pull_request.head.label || - github.head_ref || github.ref }} - cancel-in-progress: true -jobs: - test: - if: ${{ github.repository == 'ProgramEquity/amplify' }} - runs-on: ubuntu-latest - timeout-minutes: 5 - steps: - - name: Check out repo - uses: actions/checkout@v3 - - name: Setup node - uses: actions/setup-node@v3 - with: - node-version-file: .node-version - cache: npm - - name: Install dependencies - run: npm ci - - name: Run tests - run: npm test -- server/__tests__/unit/ - env: - CICERO_API_KEY: '${{ secrets.TEST_CICERO_API_KEY }}' - LOB_API_KEY: '${{ secrets.TEST_LOB_API_KEY }}' - STRIPE_SECRET_KEY: '${{ secrets.TEST_STRIPE_SECRET_KEY }}' - SERVER_PORT: 8080 - CLIENT_ORIGIN_URL: 'http://localhost:8080' - AUTH0_AUDIENCE: your_Auth0_identifier_value - AUTH0_DOMAIN: your_Auth0_domain diff --git a/.github/workflows/welcome_message.yml b/.github/workflows/welcome_message.yml deleted file mode 100644 index b317d7678..000000000 --- a/.github/workflows/welcome_message.yml +++ /dev/null @@ -1,21 +0,0 @@ -name: 'Welcome New Contributors' - -on: - issues: - types: [opened] - pull_request_target: - types: [opened] -permissions: - issues: write - pull-requests: write -jobs: - welcome-new-contributor: - runs-on: ubuntu-latest - timeout-minutes: 2 - steps: - - name: 'Greet the contributor' - uses: garg3133/welcome-new-contributors@a38583ed8282e23d63d7bf919ca2d9fb95300ca6 - with: - token: ${{ secrets.GITHUB_TOKEN }} - issue-message: 'Hello there, thanks for opening your first issue. We welcome you to the community!' - pr-message: 'Hello there, thanks for opening your first Pull Request. Someone will review it soon.' diff --git a/.github/workflows/workflow-lint.yml b/.github/workflows/workflow-lint.yml deleted file mode 100644 index 077ad3f4c..000000000 --- a/.github/workflows/workflow-lint.yml +++ /dev/null @@ -1,30 +0,0 @@ -name: Lint workflows - -on: - workflow_dispatch: - pull_request: - paths: - - '.github/workflows/*.yml' - - '.github/workflows/*.yaml' - -permissions: - contents: read - -# This allows a subsequently queued workflow run to interrupt previous runs -concurrency: - group: '${{ github.workflow }} @ ${{ github.event.pull_request.head.label || github.head_ref || github.ref }}' - cancel-in-progress: true - -jobs: - lint-workflows: - runs-on: ubuntu-latest - timeout-minutes: 2 - steps: - - name: Check out repo - uses: actions/checkout@2541b1294d2704b0964813337f33b291d3f8596b - - - name: Run linter - uses: cschleiden/actions-linter@caffd707beda4fc6083926a3dff48444bc7c24aa - with: - # ".github/workflows/scorecards-analysis.yml" is an exception as `on: branch_protection_rule` is not recognized yet - workflows: '[".github/workflows/*.yml", ".github/workflows/*.yaml", "!.github/workflows/scorecards-analysis.yml"]'