diff --git a/.clevercloud/publish-frontend-to-cellar.sh b/.clevercloud/publish-frontend-to-cellar.sh new file mode 100755 index 000000000..645a18bdc --- /dev/null +++ b/.clevercloud/publish-frontend-to-cellar.sh @@ -0,0 +1,23 @@ +#!/usr/bin/env bash + +set -x +set -e + +cd frontend +export REACT_APP_VERSION=$COMMIT_ID +yarn build +cd .. + +curl -O https://downloads.rclone.org/rclone-current-linux-amd64.zip +unzip rclone-current-linux-amd64.zip + +echo "Uploading site to bucket $CELLAR_BUCKET ..." + +export RCLONE_CONFIG_MYS3_ACCESS_KEY_ID=$CELLAR_ADDON_KEY_ID +export RCLONE_CONFIG_MYS3_SECRET_ACCESS_KEY=$CELLAR_ADDON_KEY_SECRET +export RCLONE_CONFIG_MYS3_ENDPOINT=$CELLAR_ADDON_HOST +export RCLONE_CONFIG_MYS3_TYPE="s3" + +./rclone-*-linux-amd64/rclone sync ./frontend/build mys3:$CELLAR_BUCKET --progress --s3-acl=public-read + +echo "Done. Site should be available on $ORIGIN now." \ No newline at end of file diff --git a/.devcontainer/Dockerfile b/.devcontainer/Dockerfile index 50baca3a6..f672c1441 100644 --- a/.devcontainer/Dockerfile +++ b/.devcontainer/Dockerfile @@ -8,4 +8,10 @@ RUN wget --quiet -O - https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo # Add role for user node RUN /etc/init.d/postgresql start \ - && sudo -u postgres createuser -s node \ No newline at end of file + && sudo -u postgres createuser -s node + +# Add role and db for user distributeaid_test +RUN /etc/init.d/postgresql start \ + && sudo -u postgres createuser -s distributeaid_test \ + && sudo -u postgres psql -c "ALTER USER distributeaid_test PASSWORD 'distributeaid_test';" \ + && sudo -u postgres createdb distributeaid_test diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index 226c4c87f..2346247ef 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -3,15 +3,12 @@ "build": { "dockerfile": "Dockerfile" }, - "settings": { - "terminal.integrated.shell.linux": "/bin/zsh" - }, "extensions": [ "esbenp.prettier-vscode", "jpoissonnier.vscode-styled-components", "graphql.vscode-graphql" ], "forwardPorts": [3000], - "postCreateCommand": "script/dev_setup && yarn dev", + "postCreateCommand": "sudo /etc/init.d/postgresql start && yarn run build && npx sequelize-cli --env=test db:migrate", "remoteUser": "node" } diff --git a/.env.example b/.env.example deleted file mode 100644 index cc8125e95..000000000 --- a/.env.example +++ /dev/null @@ -1,6 +0,0 @@ -# AUTH_MODE -# skips authenticating GraphQL requests with Auth0 when set to "SKIP" -# AUTH_MODE=SKIP - -# The URL of the web app, used to restrict CORS requests -CLIENT_URL="http://localhost:8080" \ No newline at end of file diff --git a/.envrc.example b/.envrc.example new file mode 100644 index 000000000..4ded77c64 --- /dev/null +++ b/.envrc.example @@ -0,0 +1,12 @@ +# See https://direnv.net/ about how to use this file. + +# The URL of the web app, used to restrict CORS requests +# CLIENT_URL="http://localhost:8080" + +# Fill in to enable email sending +# SMTP_FROM=user@example.com +# SMTP_SERVER=example.com +# SMTP_USER=user@example.com +# SMTP_PASSWORD=secret +# SMTP_SECURE=false +# SMTP_PORT=587 diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1dc57bd3..b2679d430 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -35,7 +35,7 @@ jobs: - name: Install Node.js uses: actions/setup-node@v2 with: - node-version: 14.15.5 + node-version: '16.x' # Cache node_modules between builds based on the yarn.lock file's hash. - uses: actions/cache@v2 diff --git a/.github/workflows/frontend-unit-tests.yml b/.github/workflows/frontend-unit-tests.yml index e92e8bc4e..76fd0768d 100644 --- a/.github/workflows/frontend-unit-tests.yml +++ b/.github/workflows/frontend-unit-tests.yml @@ -12,6 +12,10 @@ jobs: steps: - uses: actions/checkout@v2 + - name: Install Node.js + uses: actions/setup-node@v2 + with: + node-version: '16.x' - name: Install modules run: yarn --frozen-lockfile - name: Run codegen diff --git a/.gitignore b/.gitignore index c5a22fb0b..bbb773901 100644 --- a/.gitignore +++ b/.gitignore @@ -14,11 +14,7 @@ # misc .DS_Store -.env -.env.local -.env.development.local -.env.test.local -.env.production.local +.envrc npm-debug.log* yarn-debug.log* diff --git a/.node-version b/.node-version deleted file mode 100644 index 5595ae1aa..000000000 --- a/.node-version +++ /dev/null @@ -1 +0,0 @@ -14.17.6 diff --git a/.sequelizerc b/.sequelizerc index 4d9ec2203..362b2e00f 100644 --- a/.sequelizerc +++ b/.sequelizerc @@ -2,7 +2,7 @@ var path = require('path') module.exports = { 'config': path.resolve('db', 'config.json'), - 'migrations-path': path.resolve('db', 'migrations'), + 'migrations-path': path.resolve('dist', 'db', 'migrations'), 'seeders-path': path.resolve('db', 'seeders'), 'models-path': path.resolve('src', 'models') } diff --git a/.tool-versions b/.tool-versions index 1f6451a60..f3134c2d4 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1 @@ -nodejs 14.15.5 +nodejs 16 diff --git a/README.md b/README.md index 1594403e0..f04b47231 100644 --- a/README.md +++ b/README.md @@ -46,7 +46,7 @@ If the pull request does not require additional changes, the reviewer should mer ## Dev setup -Install node.js v14: +Install node.js v16: - Install with [nodenv](https://github.com/nodenv/nodenv) - or [nvm](https://github.com/nvm-sh/nvm) @@ -73,7 +73,6 @@ See [the README in the `frontend` directory](/frontend/README.md) for instructio - [Database Migrations](./docs/migrations.md) - [Graphql Codegen](./docs/codegen.md) -- [Auth0 configuration](./docs/auth0.md) ### Type definitions diff --git a/db/config.json b/db/config.json index 8e2ffb502..10321f7d2 100644 --- a/db/config.json +++ b/db/config.json @@ -14,8 +14,8 @@ "dialect": "postgres" }, "test": { - "username": "distributeaid", - "password": null, + "username": "distributeaid_test", + "password": "distributeaid_test", "database": "distributeaid_test", "host": "127.0.0.1", "dialect": "postgres" @@ -29,5 +29,15 @@ "rejectUnauthorized": false } } + }, + "clevercloud": { + "use_env_variable": "POSTGRESQL_ADDON_URI", + "dialect": "postgres", + "dialectOptions": { + "ssl": { + "require": true, + "rejectUnauthorized": false + } + } } } diff --git a/db/migrations/000-initial.ts b/db/migrations/000-initial.ts new file mode 100644 index 000000000..69d7e940e --- /dev/null +++ b/db/migrations/000-initial.ts @@ -0,0 +1,491 @@ +import { DataTypes, QueryInterface, Sequelize } from 'sequelize' +import { + DangerousGoods, + GroupType, + LineItemCategory, + LineItemContainerType, + LineItemStatus, + OfferStatus, + PalletType, + PaymentStatus, + ShipmentStatus, +} from '../../src/server-internal-types' + +const id = { + type: DataTypes.INTEGER, + primaryKey: true, + allowNull: false, + autoIncrement: true, +} +const createdAt = { + type: DataTypes.DATE, + allowNull: false, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), +} +const updatedAt = { + type: DataTypes.DATE, + allowNull: false, + defaultValue: Sequelize.literal('CURRENT_TIMESTAMP'), + onUpdate: Sequelize.literal('CURRENT_TIMESTAMP') as unknown as string, +} +const autoTimestampFields = { + createdAt, + updatedAt, +} + +export const up = async (queryInterface: QueryInterface) => { + // UserAccount + await queryInterface.createTable(`UserAccounts`, { + id, + email: { + type: DataTypes.STRING, + unique: true, + allowNull: false, + validate: { + isEmail: true, + }, + }, + name: { + type: DataTypes.STRING, + allowNull: false, + validate: { + len: [1, 255], + }, + }, + passwordHash: { + type: DataTypes.STRING, + allowNull: false, + }, + isAdmin: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + isConfirmed: { + type: DataTypes.BOOLEAN, + allowNull: false, + defaultValue: false, + }, + ...autoTimestampFields, + }) + await queryInterface.addIndex(`UserAccounts`, ['email'], { + unique: true, + }) + // VerificationToken + await queryInterface.createTable(`VerificationTokens`, { + id, + userAccountId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: `UserAccounts`, + key: 'id', + }, + }, + token: { + type: DataTypes.STRING, + allowNull: false, + validate: { + len: [6, 6], + }, + }, + ...autoTimestampFields, + }) + await queryInterface.addIndex( + `VerificationTokens`, + ['userAccountId', 'token'], + { + unique: false, + }, + ) + // Group + await queryInterface.createTable(`Groups`, { + id, + name: { + type: DataTypes.STRING, + allowNull: false, + validate: { + len: [1, 255], + }, + }, + captainId: { + type: DataTypes.INTEGER, + allowNull: false, + references: { + model: `UserAccounts`, + key: 'id', + }, + }, + groupType: { + type: DataTypes.STRING, + allowNull: false, + validate: { + isIn: [ + [GroupType.DaHub, GroupType.ReceivingGroup, GroupType.SendingGroup], + ], + }, + }, + primaryLocation: { + type: DataTypes.JSONB, + allowNull: false, + }, + primaryContact: { + type: DataTypes.JSONB, + allowNull: false, + }, + website: { + type: DataTypes.STRING, + validate: { + isUrl: true, + }, + }, + ...autoTimestampFields, + }) + await queryInterface.addIndex(`Groups`, ['captainId'], { + unique: false, + }) + // Shipment + await queryInterface.createTable(`Shipments`, { + id, + shippingRoute: { + allowNull: false, + type: DataTypes.STRING, + }, + labelYear: { + allowNull: false, + type: DataTypes.INTEGER, + }, + labelMonth: { + allowNull: false, + type: DataTypes.INTEGER, + }, + offerSubmissionDeadline: { + type: DataTypes.DATE, + }, + status: { + allowNull: false, + type: DataTypes.STRING, + validate: { + isIn: [ + [ + ShipmentStatus.Draft, + ShipmentStatus.Announced, + ShipmentStatus.Open, + ShipmentStatus.AidMatching, + ShipmentStatus.Staging, + ShipmentStatus.InProgress, + ShipmentStatus.Complete, + ShipmentStatus.Abandoned, + ShipmentStatus.Archived, + ], + ], + }, + }, + pricing: { + type: DataTypes.JSONB, + allowNull: false, + }, + sendingHubs: { + type: DataTypes.INTEGER, + references: { model: `Groups`, key: 'id' }, + allowNull: false, + }, + receivingHubId: { + type: DataTypes.INTEGER, + references: { model: `Groups`, key: 'id' }, + allowNull: false, + }, + statusChangeTime: { + allowNull: false, + type: DataTypes.DATE, + }, + ...autoTimestampFields, + }) + // ShipmentReceivingHub + await queryInterface.createTable(`ShipmentReceivingHubs`, { + id, + shipmentId: { + type: DataTypes.INTEGER, + references: { model: `Shipments`, key: 'id' }, + allowNull: false, + }, + hubId: { + type: DataTypes.INTEGER, + references: { model: `Groups`, key: 'id' }, + allowNull: false, + }, + ...autoTimestampFields, + }) + await queryInterface.addIndex(`ShipmentReceivingHubs`, ['shipmentId'], { + unique: false, + }) + await queryInterface.addIndex(`ShipmentReceivingHubs`, ['hubId'], { + unique: false, + }) + // ShipmentSendingHub + await queryInterface.createTable(`ShipmentSendingHubs`, { + id, + shipmentId: { + type: DataTypes.INTEGER, + references: { model: `Shipments`, key: 'id' }, + allowNull: false, + }, + hubId: { + type: DataTypes.INTEGER, + references: { model: `Groups`, key: 'id' }, + allowNull: false, + }, + ...autoTimestampFields, + }) + await queryInterface.addIndex(`ShipmentSendingHubs`, ['shipmentId'], { + unique: false, + }) + await queryInterface.addIndex(`ShipmentSendingHubs`, ['hubId'], { + unique: false, + }) + // Offer + await queryInterface.createTable(`Offers`, { + id, + status: { + allowNull: false, + type: DataTypes.STRING, + validate: { + isIn: [ + [ + OfferStatus.Draft, + OfferStatus.Proposed, + OfferStatus.BeingReviewed, + OfferStatus.Rejected, + OfferStatus.Accepted, + ], + ], + }, + }, + contact: { + type: DataTypes.JSONB, + }, + photoUris: { + allowNull: false, + type: DataTypes.JSONB, + }, + shipmentId: { + type: DataTypes.INTEGER, + references: { model: `Shipments`, key: 'id' }, + allowNull: false, + }, + sendingGroupId: { + type: DataTypes.INTEGER, + references: { model: `Groups`, key: 'id' }, + allowNull: false, + }, + statusChangeTime: { + allowNull: false, + type: DataTypes.DATE, + }, + ...autoTimestampFields, + }) + await queryInterface.addIndex(`Offers`, ['shipmentId'], { + unique: false, + }) + // Pallets + await queryInterface.createTable(`Pallets`, { + id, + offerId: { + allowNull: false, + type: DataTypes.INTEGER, + references: { model: `Offers`, key: 'id' }, + }, + palletType: { + allowNull: false, + type: DataTypes.STRING, + validate: { + isIn: [[PalletType.Standard, PalletType.Euro, PalletType.Custom]], + }, + }, + paymentStatus: { + allowNull: false, + type: DataTypes.STRING, + validate: { + isIn: [ + [ + PaymentStatus.WontPay, + PaymentStatus.Uninitiated, + PaymentStatus.Invoiced, + PaymentStatus.Paid, + ], + ], + }, + }, + paymentStatusChangeTime: { + allowNull: false, + type: DataTypes.DATE, + }, + ...autoTimestampFields, + }) + await queryInterface.addIndex(`Pallets`, ['offerId'], { + unique: false, + }) + // LineItems + await queryInterface.createTable('LineItems', { + id: { + allowNull: false, + autoIncrement: true, + primaryKey: true, + type: DataTypes.INTEGER, + }, + offerPalletId: { + allowNull: false, + type: DataTypes.INTEGER, + references: { model: `Pallets`, key: 'id' }, + }, + proposedReceivingGroupId: { + type: DataTypes.INTEGER, + references: { model: `Groups`, key: 'id' }, + }, + acceptedReceivingGroupId: { + type: DataTypes.INTEGER, + references: { model: `Groups`, key: 'id' }, + }, + status: { + allowNull: false, + type: DataTypes.STRING, + validate: { + isIn: [ + [ + LineItemStatus.Proposed, + LineItemStatus.AwaitingApproval, + LineItemStatus.Accepted, + LineItemStatus.NotAccepted, + ], + ], + }, + }, + containerType: { + allowNull: false, + type: DataTypes.STRING, + validate: { + isIn: [ + [ + LineItemContainerType.Unset, + LineItemContainerType.BulkBag, + LineItemContainerType.Box, + LineItemContainerType.FullPallet, + ], + ], + }, + }, + category: { + allowNull: false, + type: DataTypes.STRING, + validate: { + isIn: [ + [ + LineItemCategory.Unset, + LineItemCategory.Clothing, + LineItemCategory.Shelter, + LineItemCategory.Hygiene, + LineItemCategory.Food, + LineItemCategory.Games, + LineItemCategory.Electronics, + LineItemCategory.Medical, + LineItemCategory.Ppe, + LineItemCategory.Other, + ], + ], + }, + }, + description: { + type: DataTypes.STRING, + }, + itemCount: { + allowNull: false, + type: DataTypes.INTEGER, + }, + containerCount: { + type: DataTypes.INTEGER, + }, + containerWeightGrams: { + type: DataTypes.INTEGER, + }, + containerLengthCm: { + type: DataTypes.INTEGER, + }, + containerWidthCm: { + type: DataTypes.INTEGER, + }, + containerHeightCm: { + type: DataTypes.INTEGER, + }, + affirmLiability: { + allowNull: false, + type: DataTypes.BOOLEAN, + }, + tosAccepted: { + allowNull: false, + type: DataTypes.BOOLEAN, + }, + dangerousGoods: { + allowNull: false, + type: DataTypes.JSONB, + validate: { + isIn: [ + [ + DangerousGoods.Flammable, + DangerousGoods.Explosive, + DangerousGoods.Medicine, + DangerousGoods.Batteries, + DangerousGoods.Liquids, + DangerousGoods.Other, + ], + ], + }, + }, + photoUris: { + allowNull: false, + type: DataTypes.JSONB, + }, + sendingHubDeliveryDate: { + type: DataTypes.DATE, + }, + statusChangeTime: { + allowNull: false, + type: DataTypes.DATE, + }, + createdAt: { + allowNull: false, + type: DataTypes.DATE, + }, + updatedAt: { + allowNull: false, + type: DataTypes.DATE, + }, + }) + + await queryInterface.addIndex('LineItems', ['offerPalletId'], { + unique: false, + }) + // ShipmentExport + await queryInterface.createTable(`ShipmentExports`, { + id, + contentsCsv: { + type: DataTypes.TEXT, + }, + shipmentId: { + allowNull: false, + type: DataTypes.INTEGER, + references: { model: `Shipments`, key: 'id' }, + }, + userAccountId: { + allowNull: false, + type: DataTypes.INTEGER, + references: { model: `UserAccounts`, key: 'id' }, + }, + createdAt, + }) + await queryInterface.addIndex(`ShipmentExports`, ['shipmentId'], { + unique: false, + }) +} +export const down = async (queryInterface: QueryInterface) => { + await queryInterface.dropAllTables() +} diff --git a/db/migrations/20210221065805-create-group.js b/db/migrations/20210221065805-create-group.js deleted file mode 100644 index 16b0dcca4..000000000 --- a/db/migrations/20210221065805-create-group.js +++ /dev/null @@ -1,27 +0,0 @@ -'use strict' -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('Groups', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - name: { - type: Sequelize.STRING, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }) - }, - down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('Groups') - }, -} diff --git a/db/migrations/20210225043202-create-shipment.js b/db/migrations/20210225043202-create-shipment.js deleted file mode 100644 index c26fabd6f..000000000 --- a/db/migrations/20210225043202-create-shipment.js +++ /dev/null @@ -1,55 +0,0 @@ -'use strict' -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('Shipments', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - shippingRoute: { - allowNull: false, - type: Sequelize.STRING, - }, - labelYear: { - allowNull: false, - type: Sequelize.INTEGER, - }, - labelMonth: { - allowNull: false, - type: Sequelize.INTEGER, - }, - offerSubmissionDeadline: { - type: Sequelize.DATE, - }, - status: { - allowNull: false, - type: Sequelize.STRING, - }, - sendingHubs: { - type: Sequelize.INTEGER, - references: { model: 'Groups', key: 'id' }, - }, - receivingHubId: { - type: Sequelize.INTEGER, - references: { model: 'Groups', key: 'id' }, - }, - statusChangeTime: { - allowNull: false, - type: Sequelize.DATE, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }) - }, - down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('Shipments') - }, -} diff --git a/db/migrations/20210302043126-add-group-type.js b/db/migrations/20210302043126-add-group-type.js deleted file mode 100644 index 3f8fe328d..000000000 --- a/db/migrations/20210302043126-add-group-type.js +++ /dev/null @@ -1,17 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - return await queryInterface.addColumn( - 'Groups', // Table name - 'groupType', // Field name - { - type: Sequelize.DataTypes.STRING, - }, - ) - }, - - down: async (queryInterface, Sequelize) => { - return await queryInterface.removeColumn('Groups', 'groupType') - }, -} diff --git a/db/migrations/20210302222654-update-groups.js b/db/migrations/20210302222654-update-groups.js deleted file mode 100644 index 034fb5e18..000000000 --- a/db/migrations/20210302222654-update-groups.js +++ /dev/null @@ -1,39 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - return queryInterface.sequelize.transaction((t) => { - return Promise.all([ - queryInterface.addColumn('Groups', 'primaryLocation', { - allowNull: false, - type: Sequelize.JSONB, - }), - queryInterface.addColumn('Groups', 'website', { - type: Sequelize.STRING, - }), - queryInterface.addColumn('Groups', 'primaryContact', { - allowNull: false, - type: Sequelize.JSONB, - }), - queryInterface.changeColumn('Groups', 'name', { - allowNull: false, - type: Sequelize.STRING, - }), - ]) - }) - }, - - down: async (queryInterface, Sequelize) => { - return queryInterface.sequelize.transaction((t) => { - return Promise.all([ - queryInterface.removeColumn('Groups', 'primaryLocation'), - queryInterface.removeColumn('Groups', 'primaryContact'), - queryInterface.removeColumn('Groups', 'website'), - queryInterface.changeColumn('Groups', 'name', { - allowNull: true, - type: Sequelize.STRING, - }), - ]) - }) - }, -} diff --git a/db/migrations/20210303082436-create-user-accounts.js b/db/migrations/20210303082436-create-user-accounts.js deleted file mode 100644 index 5081cd818..000000000 --- a/db/migrations/20210303082436-create-user-accounts.js +++ /dev/null @@ -1,34 +0,0 @@ -'use strict' - -const { createInterface } = require('readline') - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('UserAccounts', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - auth0Id: { - allowNull: false, - type: Sequelize.STRING, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }) - - await queryInterface.addIndex('UserAccounts', ['auth0Id'], { unique: true }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('UserAccounts') - }, -} diff --git a/db/migrations/20210308070729-add-captain-to-group.js b/db/migrations/20210308070729-add-captain-to-group.js deleted file mode 100644 index d70e663cf..000000000 --- a/db/migrations/20210308070729-add-captain-to-group.js +++ /dev/null @@ -1,15 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.addColumn('Groups', 'captainId', { - allowNull: false, - type: Sequelize.INTEGER, - references: { model: 'UserAccounts', key: 'id' }, - }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.removeColumn('Groups', 'captainId') - }, -} diff --git a/db/migrations/20210310024724-create-offers.js b/db/migrations/20210310024724-create-offers.js deleted file mode 100644 index 8068e0dc6..000000000 --- a/db/migrations/20210310024724-create-offers.js +++ /dev/null @@ -1,49 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('Offers', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - status: { - allowNull: false, - type: Sequelize.STRING, - }, - contact: { - type: Sequelize.JSONB, - }, - photoUris: { - allowNull: false, - type: Sequelize.JSONB, - }, - shipmentId: { - type: Sequelize.INTEGER, - references: { model: 'Shipments', key: 'id' }, - }, - sendingGroupId: { - type: Sequelize.INTEGER, - references: { model: 'Groups', key: 'id' }, - }, - statusChangeTime: { - allowNull: false, - type: Sequelize.DATE, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('Offers') - }, -} diff --git a/db/migrations/20210314171740-index-groups-on-captain-id.js b/db/migrations/20210314171740-index-groups-on-captain-id.js deleted file mode 100644 index f95daddb4..000000000 --- a/db/migrations/20210314171740-index-groups-on-captain-id.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.addIndex('Groups', ['captainId'], { unique: false }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.removeIndex('Groups', 'captainId') - }, -} diff --git a/db/migrations/20210314172017-index-offers-on-shipment-id.js b/db/migrations/20210314172017-index-offers-on-shipment-id.js deleted file mode 100644 index c4856e3f8..000000000 --- a/db/migrations/20210314172017-index-offers-on-shipment-id.js +++ /dev/null @@ -1,11 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.addIndex('Offers', ['shipmentId'], { unique: false }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.removeIndex('Offers', 'shipmentId') - }, -} diff --git a/db/migrations/20210314222712-create-pallets.js b/db/migrations/20210314222712-create-pallets.js deleted file mode 100644 index b2da94922..000000000 --- a/db/migrations/20210314222712-create-pallets.js +++ /dev/null @@ -1,45 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('Pallets', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - offerId: { - allowNull: false, - type: Sequelize.INTEGER, - references: { model: 'Offers', key: 'id' }, - }, - palletType: { - allowNull: false, - type: Sequelize.STRING, - }, - paymentStatus: { - allowNull: false, - type: Sequelize.STRING, - }, - paymentStatusChangeTime: { - allowNull: false, - type: Sequelize.DATE, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }) - - await queryInterface.addIndex('Pallets', ['offerId'], { unique: false }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('Pallets') - }, -} diff --git a/db/migrations/20210315214429-create-line-items.js b/db/migrations/20210315214429-create-line-items.js deleted file mode 100644 index b250fbcf2..000000000 --- a/db/migrations/20210315214429-create-line-items.js +++ /dev/null @@ -1,100 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('LineItems', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - offerPalletId: { - allowNull: false, - type: Sequelize.INTEGER, - references: { model: 'Pallets', key: 'id' }, - }, - proposedReceivingGroupId: { - type: Sequelize.INTEGER, - references: { model: 'Groups', key: 'id' }, - }, - acceptedReceivingGroupId: { - type: Sequelize.INTEGER, - references: { model: 'Groups', key: 'id' }, - }, - status: { - allowNull: false, - type: Sequelize.STRING, - }, - containerType: { - allowNull: false, - type: Sequelize.STRING, - }, - category: { - allowNull: false, - type: Sequelize.STRING, - }, - description: { - type: Sequelize.STRING, - }, - itemCount: { - allowNull: false, - type: Sequelize.INTEGER, - }, - boxCount: { - type: Sequelize.INTEGER, - }, - boxWeightGrams: { - type: Sequelize.INTEGER, - }, - containerLengthCm: { - type: Sequelize.INTEGER, - }, - containerWidthCm: { - type: Sequelize.INTEGER, - }, - containerHeightCm: { - type: Sequelize.INTEGER, - }, - affirmLiability: { - allowNull: false, - type: Sequelize.BOOLEAN, - }, - tosAccepted: { - allowNull: false, - type: Sequelize.BOOLEAN, - }, - dangerousGoods: { - allowNull: false, - type: Sequelize.JSONB, - }, - photoUris: { - allowNull: false, - type: Sequelize.JSONB, - }, - sendingHubDeliveryDate: { - type: Sequelize.DATE, - }, - statusChangeTime: { - allowNull: false, - type: Sequelize.DATE, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }) - - await queryInterface.addIndex('LineItems', ['offerPalletId'], { - unique: false, - }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('LineItems') - }, -} diff --git a/db/migrations/20210316064400-rename-line-item-box-columns.js b/db/migrations/20210316064400-rename-line-item-box-columns.js deleted file mode 100644 index 9e2eece21..000000000 --- a/db/migrations/20210316064400-rename-line-item-box-columns.js +++ /dev/null @@ -1,21 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.renameColumn('LineItems', 'boxCount', 'containerCount') - await queryInterface.renameColumn( - 'LineItems', - 'boxWeightGrams', - 'containerWeightGrams', - ) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.renameColumn('LineItems', 'containerCount', 'boxCount') - await queryInterface.renameColumn( - 'LineItems', - 'containerWeightGrams', - 'boxWeightGrams', - ) - }, -} diff --git a/db/migrations/20210323060940-add-pricing-to-shipments.js b/db/migrations/20210323060940-add-pricing-to-shipments.js deleted file mode 100644 index 116896358..000000000 --- a/db/migrations/20210323060940-add-pricing-to-shipments.js +++ /dev/null @@ -1,13 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.addColumn('Shipments', 'pricing', { - type: Sequelize.DataTypes.JSONB, - }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.removeColumn('Shipments', 'pricing') - }, -} diff --git a/db/migrations/20210402074831-create-shipment-exports.js b/db/migrations/20210402074831-create-shipment-exports.js deleted file mode 100644 index dfc42e370..000000000 --- a/db/migrations/20210402074831-create-shipment-exports.js +++ /dev/null @@ -1,40 +0,0 @@ -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('ShipmentExports', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - contentsCsv: { - type: Sequelize.TEXT, - }, - shipmentId: { - allowNull: false, - type: Sequelize.INTEGER, - references: { model: 'Shipments', key: 'id' }, - }, - userAccountId: { - allowNull: false, - type: Sequelize.INTEGER, - references: { model: 'UserAccounts', key: 'id' }, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }) - - await queryInterface.addIndex('ShipmentExports', ['shipmentId'], { - unique: false, - }) - }, - - down: async (queryInterface, Sequelize) => { - await queryInterface.removeIndex('ShipmentExports', 'shipmentId') - await queryInterface.dropTable('ShipmentExports') - }, -} diff --git a/db/seeders/20210302050835-demo-groups.js b/db/seeders/20210302050835-demo-groups.js index 72579a5a8..33bd52158 100644 --- a/db/seeders/20210302050835-demo-groups.js +++ b/db/seeders/20210302050835-demo-groups.js @@ -6,7 +6,7 @@ module.exports = { up: async (queryInterface, Sequelize) => { await queryInterface.bulkInsert('UserAccounts', [ { - auth0Id: 'seeded-account-id', + email: 'seeded-account-id@example.com', createdAt: new Date(), updatedAt: new Date(), }, diff --git a/docs/auth0.md b/docs/auth0.md deleted file mode 100644 index 365bb3ee2..000000000 --- a/docs/auth0.md +++ /dev/null @@ -1,7 +0,0 @@ -# Auth0 configuration - -We use Auth0 for authentication and authorization. - -Our Auth0 configuration is stored in GitHub, in the [https://github.com/distributeaid/auth0](distributeaid/auth0) repository. Whenever a change is merge in that repo, the Auth0 configuration is automatically updated. - -See https://github.com/distributeaid/auth0/blob/saga/clients/shipment-tracker.json. diff --git a/docs/authentication.md b/docs/authentication.md new file mode 100644 index 000000000..263c39c9e --- /dev/null +++ b/docs/authentication.md @@ -0,0 +1,20 @@ +# Authentication + +The backend authenticates requests using signed cookies which contains user's id so that it does not have to be fetched for every request. + +Cookies are sent [`secure` and `HttpOnly`](https://developer.mozilla.org/en-US/docs/Web/HTTP/Cookies#restrict_access_to_cookies) when users register their account, or when they log in using email and password. + +Cookies expire after 30 minutes and the client is responsible for renewing cookies by calling the `GET /me/cookie` endpoint before they expire. + +When renewing cookies, the server will re-check if the user still exists and if they haven't changed their password. For this a hash of the user's password hash, email, and id will be generated and included in the cookie. If any of these properties changes, the cookie cannot be renewed and the user has to log-in again. + +## Admin permissions + +Admin permission are granted via the `isAdmin` flag on the `UserAccount` model. + +## Configuration + +These environment variables control the authentication: + +- Backend + - `COOKIE_SECRET`: sets the secret used to sign cookies, default value is a random string diff --git a/docs/migrations.md b/docs/migrations.md index d3d5151a1..60f264f2c 100644 --- a/docs/migrations.md +++ b/docs/migrations.md @@ -31,79 +31,10 @@ npx sequelize-cli db:migrate:undo:all ## Creating migrations -To create a new migration skeleton run: +> **Fun fact:** Sequelize has no support for generating migrations from models. -```sh -npx sequelize-cli migration:create --name -``` - -this will generate a dated file under `db/migrations` with a sequelize migration skeleton inside, which will look something like: - -```javascript -'use strict' - -module.exports = { - up: async (queryInterface, Sequelize) => { - /** - * Add altering commands here. - * - * Example: - * await queryInterface.createTable('users', { id: Sequelize.INTEGER }); - */ - }, - - down: async (queryInterface, Sequelize) => { - /** - * Add reverting commands here. - * - * Example: - * await queryInterface.dropTable('users'); - */ - }, -} -``` - -Alternatively, if you are adding a new table you can use a command similar the following in order to fill in all of the data types for the new table in the migration file: - -```sh -npx sequelize-cli model:generate --name User --attributes name:string,age:number -``` - -The above command would generate the following migration file: - -```javascript -'use strict' -module.exports = { - up: async (queryInterface, Sequelize) => { - await queryInterface.createTable('Users', { - id: { - allowNull: false, - autoIncrement: true, - primaryKey: true, - type: Sequelize.INTEGER, - }, - name: { - type: Sequelize.STRING, - }, - age: { - type: Sequelize.NUMBER, - }, - createdAt: { - allowNull: false, - type: Sequelize.DATE, - }, - updatedAt: { - allowNull: false, - type: Sequelize.DATE, - }, - }) - }, - down: async (queryInterface, Sequelize) => { - await queryInterface.dropTable('Users') - }, -} -``` - -It will also generate a model file under the `src/models` directory, which you will want to delete and replace with a TypeScript file defining your new model. +Create a new TypeScript file following the naming schema in [db/migrations](../db/migrations). You can find more information about sequelize migration files [here](https://sequelize.org/master/manual/migrations.html#migration-skeleton). + +Once you have finished writing your migration, run `npx tsc` to compile the JavaScript version of the migration. diff --git a/frontend/.env b/frontend/.env index 81b11609d..90c2db115 100644 --- a/frontend/.env +++ b/frontend/.env @@ -1,22 +1,2 @@ -# Access the package.json's version through process.env -REACT_APP_VERSION=$npm_package_version - -# The URL where the server runs -REACT_APP_SERVER_URL="http://localhost:3000" - -# The URL where the Apollo server runs -REACT_APP_GRAPHQL_URL="http://localhost:3000/graphql" - -# The URL where the client runs -REACT_APP_CLIENT_URL="http://localhost:8080" - -# Auth0 configuration -## Tenant -REACT_APP_AUTH0_DOMAIN="distributeaid.eu.auth0.com" -## Client ID -REACT_APP_AUTH0_CLIENT_ID="hfNo3Nw2ZrAGv0Vwh7tjU4nJ7lAuPTNH" -## audience -REACT_APP_AUTH0_AUDIENCE="https://da-shipping-tracker-dev" - # See this issue https://github.com/facebook/create-react-app/issues/1795#issuecomment-357353472 SKIP_PREFLIGHT_CHECK=true diff --git a/frontend/.env.development b/frontend/.env.development new file mode 100644 index 000000000..57f73eb12 --- /dev/null +++ b/frontend/.env.development @@ -0,0 +1,2 @@ +# The URL where the server runs +REACT_APP_SERVER_URL="http://localhost:3000" \ No newline at end of file diff --git a/frontend/package.json b/frontend/package.json index f470b30d4..eca4fb511 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -4,7 +4,6 @@ "private": true, "dependencies": { "@apollo/client": "3.3.20", - "@auth0/auth0-react": "1.5.0", "@hookform/error-message": "2.0.0", "@types/body-scroll-lock": "2.6.1", "@types/classnames": "2.3.0", @@ -66,6 +65,5 @@ "last 1 firefox version", "last 1 safari version" ] - }, - "proxy": "http://localhost:3000" + } } diff --git a/frontend/public/index.html b/frontend/public/index.html index cddeca56c..52491ebe8 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -10,6 +10,7 @@ +