From bc882b0d74b831e711187c8ef895c6422594e4c4 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 5 Nov 2024 22:03:52 -0300 Subject: [PATCH 01/99] Add testing and frontend instructions --- README.md | 58 --------------- site/gatsby-site/README.md | 148 ++++++++++++++++++++++++++++++++----- 2 files changed, 130 insertions(+), 76 deletions(-) diff --git a/README.md b/README.md index 74a0a22fc2..e117e88c92 100644 --- a/README.md +++ b/README.md @@ -130,8 +130,6 @@ See [mongo.md](mongo.md) [Cloudinary](https://www.cloudinary.com) is what we use to host and manage report images. -## Setting up a development environment - ## Important Notice This project is currently undergoing a significant restructuring as we transition away from the recently deprecated Atlas GraphQL endpoint. Please note that some parts of the documentation may be outdated. For the most up-to-date information and guidance, please follow [this link](site/gatsby-site/README.md) to the latest documentation. @@ -423,41 +421,6 @@ As soon as a user is signed in, the system assigns a `subscriber` role by defaul | `admin` | This role has full access to the site, including the ability to edit users' roles. | -## Front-end development - -### Tailwind CSS & Flowbite - -This project uses [Tailwind CSS](https://tailwindcss.com/) framework with its class syntax. -More specifically, we base our components on [Flowbite React](https://flowbite-react.com/) and [Flowbite](https://flowbite.com/) which is built on top of TailwindCSS. - -### Steps for developing - -In order to keep styling consistency on the site, we follow a set of steps when developing. This is also to make the development process more agile and simple. - -1. Develop your component using [Flowbite React components](https://flowbite-react.com/) -2. If your components is not fully contemplated by Flowbite react, check [Flowbite components](https://flowbite.com/#components) and use the provided HTMLs. -3. If you need to improve styling, use only Tailwind CSS classes. - -**Examples** -If you want to place a new [Flowbite React button](https://flowbite-react.com/buttons): - -```javascript -import { Button } from 'flowbite-react'; - -const YourComponent = () => { - return -} - -``` - -If you want to customize a [Flowbite button](https://flowbite.com/docs/components/buttons/): - -```javascript -const YourComponent = () => { - return -} -``` - ## Deployment Setup Deployment of the site consists of two parts: deployment of the backend related features that runs as a GitHub Action and deployment of the frontend related features that runs on Netlify: @@ -609,27 +572,6 @@ All three workflows share a common set of environment variables, which need to b - `REALM_API_APP_ID` - `REALM_API_GROUP_ID` -### Testing - -For integration testing, we use Cypress. You can run the desktop app continuously as part of your development environment or run it on demand in headless mode. - -First, add two new environment variables: - -``` -E2E_ADMIN_USERNAME= -E2E_ADMIN_PASSWORD= -``` -As their names imply, they should be an existing user's credentials with the `admin` role. - -To use the desktop version, run: -``` -npm run test:e2e -``` - -And to run it in continuous integration (headless) mode: -``` -npm run test:e2e:ci -``` ## Adding new Taxonomies diff --git a/site/gatsby-site/README.md b/site/gatsby-site/README.md index b56df875a4..19420482f1 100644 --- a/site/gatsby-site/README.md +++ b/site/gatsby-site/README.md @@ -2,7 +2,7 @@ Once you have cloned the repository, to set up a local development environment for the AIID project, follow these steps: -1. **Navigate to the Gatsby Site Directory** +### 1. **Navigate to the Gatsby Site Directory and Install Dependencies** Open your terminal and navigate to the `site/gatsby-site` directory: @@ -10,15 +10,13 @@ Once you have cloned the repository, to set up a local development environment f cd site/gatsby-site ``` -2. **Install Dependencies** - Run the following command to install all necessary dependencies: ```bash npm install ``` -3. **Configure Environment Variables** +### 2. **Configure Environment Variables** Create a `.env` file in the root of the `gatsby-site` directory. Add the following environment variables to the file, replacing the placeholders with your actual credentials: @@ -55,7 +53,8 @@ Once you have cloned the repository, to set up a local development environment f Ensure that each variable is set correctly to match your development environment's requirements. -4. **Start a Memory Mongo Instance** + +### 3. **Start a Memory Mongo Instance** To start a memory MongoDB instance, run the following command: @@ -63,7 +62,7 @@ Once you have cloned the repository, to set up a local development environment f npm run start:memory-mongo ``` -5. **Start Gatsby and Netlify Development Server** +### 4. **Start Gatsby and Netlify Development Server** Finally, start the Gatsby development server along with Netlify dev using: @@ -73,6 +72,46 @@ Once you have cloned the repository, to set up a local development environment f Follow these steps to get your local environment up and running for development with the AIID project. Make sure to replace the placeholder values in the `.env` file with your actual credentials to ensure proper functionality. + +## AIID Frontend + +### Overview + +The AIID frontend is built using Gatsby, a static site generator that allows for fast, optimized websites. The frontend is designed to provide a user-friendly interface for browsing incidents, submitting new incidents, and viewing incident details. + +### Tailwind CSS & Flowbite + +This project uses [Tailwind CSS](https://tailwindcss.com/) framework with its class syntax. +More specifically, we base our components on [Flowbite React](https://flowbite-react.com/) and [Flowbite](https://flowbite.com/) which is built on top of TailwindCSS. + +### Steps for developing + +In order to keep styling consistency on the site, we follow a set of steps when developing. This is also to make the development process more agile and simple. + +1. Develop your component using [Flowbite React components](https://flowbite-react.com/) +2. If your components is not fully contemplated by Flowbite react, check [Flowbite components](https://flowbite.com/#components) and use the provided HTMLs. +3. If you need to improve styling, use only Tailwind CSS classes. + +**Examples** +If you want to place a new [Flowbite React button](https://flowbite-react.com/buttons): + +```javascript +import { Button } from 'flowbite-react'; + +const YourComponent = () => { + return +} + +``` + +If you want to customize a [Flowbite button](https://flowbite.com/docs/components/buttons/): + +```javascript +const YourComponent = () => { + return +} +``` + ## AIID API ### Overview @@ -81,7 +120,7 @@ The AIID API is built to facilitate interactions with the AI Incident Database. 1. **Access the Apollo Explorer** - Navigate to `http://localhost:8000/graphql` in your web browser. The Apollo Explorer instance should be displayed, allowing you to introspect and run queries against the API. + Navigate to `http://localhost:8000/api/graphql` in your web browser. The Apollo Explorer instance should be displayed, allowing you to introspect and run queries against the API. ### Performing Queries @@ -128,16 +167,6 @@ The API is contained within the `server` directory. The following folders are pr - **`local.ts`**: Handles the local GraphQL schema, where migrated fields from the remote schema are added. These fields are ignored in `remote.ts`. - **`schema.ts`**: Combines the remote and local schemas into the final schema using **schema stitching** from GraphQL Tools. - **`netlify/functions/graphql.ts`**: Sets up the **GraphQL server** and exposes it as a **Netlify function**, loading the schema from `schema.ts`. -- -### Running Tests - -To run Jest tests locally: - -```sh -npm run test:api -``` - -It is recommended to install the Jest extension for VS Code to enhance the testing experience. ### Running Code Generation @@ -248,4 +277,87 @@ And finally, as part of the site build process, we processed all pending notific "incident_id": 374, "processed": false } - ``` \ No newline at end of file + ``` + + +## Testing + +### E2E Testing + +We use Playwright for end-to-end testing. You can either run the tests against the local development environment or against a local build of the site. + +#### Local Development Environment + +First make sure you you've followed the steps to set up the local development environment for the AIID project. + +Make sure you have a local mongo instance running: + +```sh +npm run start:memory-mongo +``` + +Then, start the Gatsby and Netlify development server: + +```sh +npm run start +``` + +Finally, run the Playwright tests: + +```sh +npm run test:e2e +``` + +Running tests this way will allow you to make changes to the site and see the results of the tests in real time. + +#### Local Build + +First, start a memory MongoDB instance: + +```sh +npm run start:memory-mongo +``` + +Then, build the Gatsby site: + +```sh +npm run build +``` + +Then, start the Gatsby and Netlify development server: + +```sh +npm run start +``` + +Finally, run the Playwright tests: + +```sh +npm run test:e2e +``` + +Running tests this way will allow you to test the site as it would be deployed to production. + +#### VS Code Extension + +It is recommended to install the [Playwright extension](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright) for VS Code to enhance the testing experience. It allows you to run tests directly from the editor, view test results, and debug tests. + +> [!NOTE] +> Make sure to have `/site/gatsby-site` as the root folder in vscode to run the tests. + + +### API + +We use Jest for API testing. It does not have any dependencies on the local development environment, so you can run the tests at any time: + +```sh +npm run test:api +``` + +#### VS Code Extension + +It is recommended to install the [Jest extension](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest) for VS Code to enhance the testing experience. It allows you to run tests directly from the editor, view test results, and debug tests. + +> [!NOTE] +> Make sure to have `/site/gatsby-site` as the root folder in vscode to run the tests. + From 1b9b14f51e1b511a6cbaa2d02ca9c3a33dab2f1a Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 5 Nov 2024 22:12:45 -0300 Subject: [PATCH 02/99] Update README to clarify GraphQL API access and endpoints --- site/gatsby-site/README.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/site/gatsby-site/README.md b/site/gatsby-site/README.md index 19420482f1..3291f92f7a 100644 --- a/site/gatsby-site/README.md +++ b/site/gatsby-site/README.md @@ -118,9 +118,12 @@ const YourComponent = () => { The AIID API is built to facilitate interactions with the AI Incident Database. It is implemented as a collection of serverless functions that are composed ("stitched") into a singular GraphQL endpoint. -1. **Access the Apollo Explorer** +The GraphQL API can be accessed and tested in two ways: - Navigate to `http://localhost:8000/api/graphql` in your web browser. The Apollo Explorer instance should be displayed, allowing you to introspect and run queries against the API. +- Production endpoint: `https://incidentdatabase.ai/api/graphql` +- Local development endpoint: `http://localhost:8000/api/graphql` + +Both URLs support interactive exploration through Apollo Explorer, allowing you to visually build and test GraphQL queries and mutations. ### Performing Queries @@ -135,8 +138,6 @@ query { } ``` -### Expected Response - The query should return a response similar to this: ```json From 97ec722bfc3f356251955e23051bf1a9f9096234 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 5 Nov 2024 22:25:04 -0300 Subject: [PATCH 03/99] Add database migration instructions to README files --- README.md | 26 -------------------------- site/gatsby-site/README.md | 28 ++++++++++++++++++++++++++++ 2 files changed, 28 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index e117e88c92..5eb6e8cd6e 100644 --- a/README.md +++ b/README.md @@ -665,32 +665,6 @@ Let's say you want to add the `CTECH` Taxonomy. Restarting Gatsby should make the new taxonomy available on the citation pages, so you can visit /cite/1 to see a form for editing the taxonomy. Please note that you will need to be logged in to a user account on the application to see the form. -## Database Migrations -Migration files are stored in the `/site/gatsby-size/migrations` folder and their executions are logged in the `migrations` collection. - -### Configuration -Please add a connection string with read/write permissions to the mongo database: -``` -MONGODB_MIGRATIONS_CONNECTION_STRING= -``` -### Execution -Migrations run automatically as part of Gatsby's `onPreBootstrap` event, but it is also possible to run them from the command line for debugging or testing purposes: - -To run all pending migrations: -``` -node migrator up -``` -To run a specific migration: -``` -node migrator up --name 2022.02.18T16.29.14.increment-report-number.js -``` -### Adding a new migration -To add a new migration, execute the following command and define the `up` and `down` processes as pertinent. -``` -node migrator create --name increment-report-number.js --folder migrations -``` - -Execution is taken care of by the [umzug](https://github.com/sequelize/umzug) package. Please refer to its documentation for more information. ## Public GraphQL endpoint The site exposes a read-only GraphQL endpoint at `/api/graphql`, which is a reflection of the Realm's auto-generated endpoint. diff --git a/site/gatsby-site/README.md b/site/gatsby-site/README.md index 3291f92f7a..de394cff8e 100644 --- a/site/gatsby-site/README.md +++ b/site/gatsby-site/README.md @@ -362,3 +362,31 @@ It is recommended to install the [Jest extension](https://marketplace.visualstud > [!NOTE] > Make sure to have `/site/gatsby-site` as the root folder in vscode to run the tests. +## Database Migrations +Migration files are stored in the `/site/gatsby-size/migrations` folder and their executions are logged in the `migrations` collection. + +### Configuration +Please add a connection string with read/write permissions to the mongo database: +``` +MONGODB_MIGRATIONS_CONNECTION_STRING= +``` +### Execution +Migrations run automatically as part of Gatsby's `onPreBootstrap` event, but it is also possible to run them from the command line for debugging or testing purposes: + +To run all pending migrations: +``` +node migrator up +``` +To run a specific migration: +``` +node migrator up --name 2022.02.18T16.29.14.increment-report-number.js +``` +### Adding a new migration +To add a new migration, execute the following command and define the `up` and `down` processes as pertinent. +``` +node migrator create --name increment-report-number.js --folder migrations +``` + +Execution is taken care of by the [umzug](https://github.com/sequelize/umzug) package. Please refer to its documentation for more information. + + From 1251405a28a4f309d8bcd81f1f5ee756aaac4eb4 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 5 Nov 2024 22:34:37 -0300 Subject: [PATCH 04/99] Move additional configuration section --- README.md | 4 ---- site/gatsby-site/README.md | 10 ++++++++++ 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 5eb6e8cd6e..57f9a1237c 100644 --- a/README.md +++ b/README.md @@ -180,10 +180,6 @@ You should have a local copy of the project running on https://localhost:8000. The values you placed into the env file are all associated with a staging environment that is periodically rebuilt from the production environment. While this helps you get setup more quickly, if you will be making changes to the backend you will need your own development backend that you can control, modify, and potentially break. -#### Additional Configuration - -When building the site, some steps can take a while to run. This can be inconvenient when you are working on a feature unrelated to the steps taking the most time in the build process. To avoid this problem, you can set the environment variable `SKIP_PAGE_CREATOR` to a comma-separated list of page-creator functions found in [`gatsby-node`](https://github.com/responsible-ai-collaborative/aiid/blob/main/site/gatsby-site/gatsby-node.js) that should be skipped. These include: `createMdxPages`, `createCitationPages`, `createWordCountsPages`, `createBackupsPage`, `createTaxonomyPages`, `createDownloadIndexPage`, `createDuplicatePages`, `createTsneVisualizationPage`, and `createEntitiesPages`. For instance, to run a development build skipping the creation of the TSNE (spatial) visualization and citation pages, you would run: - ```bash SKIP_PAGE_CREATOR=createTsneVisualizationPage,createCitiationPages gatsby develop ``` diff --git a/site/gatsby-site/README.md b/site/gatsby-site/README.md index de394cff8e..1b8ab22fba 100644 --- a/site/gatsby-site/README.md +++ b/site/gatsby-site/README.md @@ -53,6 +53,16 @@ Once you have cloned the repository, to set up a local development environment f Ensure that each variable is set correctly to match your development environment's requirements. +#### Additional Configuration + +When building the site, some steps can take a while to run. This can be inconvenient when you are working on a feature unrelated to the steps taking the most time in the build process. To avoid this problem, you can set the environment variable `SKIP_PAGE_CREATOR` to a comma-separated list of page-creator functions found in [`gatsby-node`](https://github.com/responsible-ai-collaborative/aiid/blob/main/site/gatsby-site/gatsby-node.js) that should be skipped. These include: `createMdxPages`, `createCitationPages`, `createWordCountsPages`, `createBackupsPage`, `createTaxonomyPages`, `createDownloadIndexPage`, `createDuplicatePages`, `createTsneVisualizationPage`, and `createEntitiesPages`. For instance, to run a development build skipping the creation of the TSNE (spatial) visualization and citation pages, you would run: + +```bash +SKIP_PAGE_CREATOR=createTsneVisualizationPage,createCitiationPages gatsby develop +``` + +In general, skipping the TSNE visualization has the most significant reduction in build time. + ### 3. **Start a Memory Mongo Instance** From 14b1848f6a42a08c24c5584af8ee263e257ef11c Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 5 Nov 2024 22:38:42 -0300 Subject: [PATCH 05/99] Move style guid --- README.md | 6 ------ site/gatsby-site/README.md | 7 +++++++ 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 57f9a1237c..6ac3c68085 100644 --- a/README.md +++ b/README.md @@ -104,12 +104,6 @@ The site has three components that are considered "serverless," meaning there is More details are available in the `Production System` information below. We recommend most people forego setting up a development environment with their own Index and Database. You should instead concentrate on setting up a Gatsby development site. -**Style guide:** - -1. `ESLint` and `Prettier` have been configured to help enforcing code styles. Configuration details can be found in `.eslintrc.json` and `.prettierrc`. -2. [Husky](https://github.com/typicode/husky#readme) and [lint-staged](https://github.com/okonet/lint-staged) are installed and `pre-commit` hook added to check lint/prettier issues on staged files and fix them automatically before making commit. -3. `format` and `lint` scripts can be used manually to fix style issues. - ## Production System ### Netlify diff --git a/site/gatsby-site/README.md b/site/gatsby-site/README.md index 1b8ab22fba..8e2637884f 100644 --- a/site/gatsby-site/README.md +++ b/site/gatsby-site/README.md @@ -372,6 +372,13 @@ It is recommended to install the [Jest extension](https://marketplace.visualstud > [!NOTE] > Make sure to have `/site/gatsby-site` as the root folder in vscode to run the tests. +## Style guide + +1. `ESLint` and `Prettier` have been configured to help enforcing code styles. Configuration details can be found in `.eslintrc.json` and `.prettierrc`. +2. [Husky](https://github.com/typicode/husky#readme) and [lint-staged](https://github.com/okonet/lint-staged) are installed and `pre-commit` hook added to check lint/prettier issues on staged files and fix them automatically before making commit. +3. `format` and `lint` scripts can be used manually to fix style issues. + + ## Database Migrations Migration files are stored in the `/site/gatsby-size/migrations` folder and their executions are logged in the `migrations` collection. From 84459b1eee790fc88343aba90dbcf820569a2003 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 5 Nov 2024 22:44:29 -0300 Subject: [PATCH 06/99] Move prismic instructions --- README.md | 58 ------------------------------------ site/gatsby-site/README.md | 60 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 60 insertions(+), 58 deletions(-) diff --git a/README.md b/README.md index 6ac3c68085..f9d4dbcb6e 100644 --- a/README.md +++ b/README.md @@ -335,64 +335,6 @@ If the feature you are working on depends on Google's Geocoding API, please add GOOGLE_MAPS_API_KEY=XXXXXXXXXXXX ``` -## Prismic setup -This project uses Prismic to fetch page content. You can still run the project without setting a Prismic account. - -1. Sign up for a new [Prismic](https://prismic.io/) account or log in to your account if you already have one -2. In `Create a new repository` section choose `Something else` -3. Give your repository a name and choose `gatsby` in the technology dropdown -4. Choose your plan (if you only need one user, the free plan is enough) -5. Click `Create repository` -6. Create a new token in Settings > API & Security > Content API tab > Change Repository security to `Private API – Require an access token for any request` > Create new app > Permanent access tokens > Save value for later - -### Adding the Prismic content types - -#### Prismic Custom Types -You can find the list of all custom types in the folder `custom_types` - -#### How to create a new Custom Type -1. From the prismic left menu click `Custom Types` -2. Click `Create new custom type` -3. Give it a name (name of the json in custom_types folder) -4. Click `JSON editor` -5. Paste the JSON content from the predefined custom types inside the json -6. Click `Save` - -#### Adding Prismic documents - -1. On the Prismic dashboard left menu click `Documents` -2. Click `Create new` -3. Fill in all the mandatory fields -4. Click `Save` -5. Keep in mind that the new content won't be available on your page until you Publish it. -6. In order to publish it, click `Publish` - -#### Prismic & Netlify Hook integration - -In order for your recently published Prismic content to be available on your page, a Netlify build needs to be triggered. -In order to do this, you need to create a Netlify Build Hook. - -#### Prismic environment variables - -Add the following environment variable on Netlify: -`GATSBY_PRISMIC_REPO_NAME=[name_of_your_repository]` (step 3 from Prismic Setup section) -`PRISMIC_ACCESS_TOKEN=[you_prismic_access_token]` (step 6 from Prismic Setup section) - -#### Create Prismic/Netlify Hook -1. Login to your Netlify -2. Go to `Deploys` -3. Go to `Deploy settings` -4. Scroll to `Build Hooks` -5. Click `Add build hook` -6. Give it a name and assign a branch -7. Click save -8. Copy the generated URL -9. Go to your Prismic repository -10. Go to `Settings` > `Webhooks` -11. Create a new webhook and paste the URL in the URL field -12. In `Triggers` select `A document is published` and `A document is unpublished` -13. Click `Add this webhook` - ## User Roles All site users have one or more roles assigned to them. The role determines what actions the user can take on the site. diff --git a/site/gatsby-site/README.md b/site/gatsby-site/README.md index 8e2637884f..cf1578b9ee 100644 --- a/site/gatsby-site/README.md +++ b/site/gatsby-site/README.md @@ -407,3 +407,63 @@ node migrator create --name increment-report-number.js --folder migrations Execution is taken care of by the [umzug](https://github.com/sequelize/umzug) package. Please refer to its documentation for more information. +## Prismic setup + +This project uses Prismic to fetch page content. You can still run the project without setting a Prismic account. + +1. Sign up for a new [Prismic](https://prismic.io/) account or log in to your account if you already have one +2. In `Create a new repository` section choose `Something else` +3. Give your repository a name and choose `gatsby` in the technology dropdown +4. Choose your plan (if you only need one user, the free plan is enough) +5. Click `Create repository` +6. Create a new token in Settings > API & Security > Content API tab > Change Repository security to `Private API – Require an access token for any request` > Create new app > Permanent access tokens > Save value for later + +### Adding the Prismic content types + +#### Prismic Custom Types +You can find the list of all custom types in the folder `custom_types` + +#### How to create a new Custom Type +1. From the prismic left menu click `Custom Types` +2. Click `Create new custom type` +3. Give it a name (name of the json in custom_types folder) +4. Click `JSON editor` +5. Paste the JSON content from the predefined custom types inside the json +6. Click `Save` + +#### Adding Prismic documents + +1. On the Prismic dashboard left menu click `Documents` +2. Click `Create new` +3. Fill in all the mandatory fields +4. Click `Save` +5. Keep in mind that the new content won't be available on your page until you Publish it. +6. In order to publish it, click `Publish` + +#### Prismic & Netlify Hook integration + +In order for your recently published Prismic content to be available on your page, a Netlify build needs to be triggered. +In order to do this, you need to create a Netlify Build Hook. + +#### Prismic environment variables + +Add the following environment variable on Netlify: +`GATSBY_PRISMIC_REPO_NAME=[name_of_your_repository]` (step 3 from Prismic Setup section) +`PRISMIC_ACCESS_TOKEN=[you_prismic_access_token]` (step 6 from Prismic Setup section) + +#### Create Prismic/Netlify Hook +1. Login to your Netlify +2. Go to `Deploys` +3. Go to `Deploy settings` +4. Scroll to `Build Hooks` +5. Click `Add build hook` +6. Give it a name and assign a branch +7. Click save +8. Copy the generated URL +9. Go to your Prismic repository +10. Go to `Settings` > `Webhooks` +11. Create a new webhook and paste the URL in the URL field +12. In `Triggers` select `A document is published` and `A document is unpublished` +13. Click `Add this webhook` + + From ed151be10da848a138727cead46dac537668e4c1 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 5 Nov 2024 23:16:52 -0300 Subject: [PATCH 07/99] Delete outdated basic setup instructions --- README.md | 52 ---------------------------------------------------- 1 file changed, 52 deletions(-) diff --git a/README.md b/README.md index f9d4dbcb6e..43cbb84f80 100644 --- a/README.md +++ b/README.md @@ -128,58 +128,6 @@ See [mongo.md](mongo.md) This project is currently undergoing a significant restructuring as we transition away from the recently deprecated Atlas GraphQL endpoint. Please note that some parts of the documentation may be outdated. For the most up-to-date information and guidance, please follow [this link](site/gatsby-site/README.md) to the latest documentation. -Depending on what feature you are working on, there will be different systems you'll need to set up after you've forked and cloned this repository: - -### Basic setup -Get a Gatsby environment working. Most of the time, you'll only need to run: - -``` -npm install --global gatsby-cli -``` -Create a `.env` file under `site/gatsby-site` with the following contents: - -``` -GATSBY_REALM_APP_ID=aiidstitch2-sasvc -MONGODB_CONNECTION_STRING=mongodb+srv://readonly:vNMlVM35rsTlMUTr@aiiddev.seam4.mongodb.net -MONGODB_TRANSLATIONS_CONNECTION_STRING=mongodb+srv://readonly:vNMlVM35rsTlMUTr@aiiddev.seam4.mongodb.net -MONGODB_REPLICA_SET=aiiddev-shard-00-02.seam4.mongodb.net,aiiddev-shard-00-01.seam4.mongodb.net,aiiddev-shard-00-00.seam4.mongodb.net - -GATSBY_ALGOLIA_APP_ID=JD5JCVZEVS -GATSBY_ALGOLIA_SEARCH_KEY=c5e99d93261645721a1765fe4414389c -GATSBY_AVAILABLE_LANGUAGES=en,es,fr -SKIP_PAGE_CREATOR=createTsneVisualizationPage -GATSBY_PRISMIC_REPO_NAME= -PRISMIC_ACCESS_TOKEN= -IS_EMPTY_ENVIRONMENT= -``` - -For `GATSBY_PRISMIC_REPO_NAME` and `PRISMIC_ACCESS_TOKEN` variables, please [follow prismic setup below](https://github.com/responsible-ai-collaborative/aiid#prismic-setup) - -For complete empty environment (no database data, no Algolia index, no Prismic content), set `IS_EMPTY_ENVIRONMENT=true`. This will disable all tests that require data. - -This will give you access to our `staging` environment, so please be sure you are on the `staging` branch. - -In the same folder, install dependencies using `npm` (do not use `yarn`, it will ignore the `package-lock.json` file): - -``` -npm install -``` - -You are ready to start a local copy of the project: - -``` -gatsby develop -``` -You should have a local copy of the project running on https://localhost:8000. - -The values you placed into the env file are all associated with a staging environment that is periodically rebuilt from the production environment. While this helps you get setup more quickly, if you will be making changes to the backend you will need your own development backend that you can control, modify, and potentially break. - -```bash -SKIP_PAGE_CREATOR=createTsneVisualizationPage,createCitiationPages gatsby develop -``` - -In general, skipping the TSNE visualization has the most significant reduction in build time. - ### MongoDB setup If the feature you are working on includes structural changes to the MongoDB database or Realm functions, you'll need to create your own project by going to https://cloud.mongodb.com and following these steps: From 8827c11c85b61cdb21b789ba31d14e365beb63dd Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 6 Nov 2024 01:58:00 -0300 Subject: [PATCH 08/99] Delete outdated netlify instructions --- README.md | 24 ------------------------ 1 file changed, 24 deletions(-) diff --git a/README.md b/README.md index 43cbb84f80..cc246662a3 100644 --- a/README.md +++ b/README.md @@ -305,30 +305,6 @@ As soon as a user is signed in, the system assigns a `subscriber` role by defaul Deployment of the site consists of two parts: deployment of the backend related features that runs as a GitHub Action and deployment of the frontend related features that runs on Netlify: -### Netlify -The Netlify build process runs every time a push is made to an open PR or `main` or `develop`. -To correctly set up this process, the following environment variables need to be created using Netlify's build settings UI: - -``` -ALGOLIA_ADMIN_KEY= -AWS_LAMBDA_JS_RUNTIME=nodejs18.x # required to run the Gatsby v5 -GATSBY_ALGOLIA_APP_ID= -GATSBY_ALGOLIA_SEARCH_KEY= -GATSBY_REALM_APP_ID= -MONGODB_CONNECTION_STRING= -MONGODB_REPLICA_SET= -GATSBY_EXCLUDE_DATASTORE_FROM_BUNDLE=1 # specific to Netlify, for large sites -GATSBY_CPU_COUNT=2 # limits the number of Gatsby threads, helping with deployment stability -NODE_VERSION=18 # this is required by Gatsby v5 -NODE_OPTIONS=--max-old-space-size=4096 # increase default heap size to prevent crashes during build -# The following "CLOUDFLARE_R2" variables are required to create the /research/snapshots/ page -CLOUDFLARE_R2_ACCOUNT_ID=[The Cloudflare R2 account ID (e.g.: 8f4144a9d995a9921d0200db59f6a00e)] -CLOUDFLARE_R2_ACCESS_KEY_ID=[The Cloudflare R2 access key ID (e.g.: 7aa73208bc89cee3195879e578b291ee)] -CLOUDFLARE_R2_SECRET_ACCESS_KEY=[The Cloudflare R2 secret access key] -CLOUDFLARE_R2_BUCKET_NAME=[The Cloudflare R2 bucket name (e.g.: 'aiid-public')] -GATSBY_CLOUDFLARE_R2_PUBLIC_BUCKET_URL=[The Cloudflare R2 public bucket URL (e.g.: https://pub-daddb16dc28841779b83690f75eb5c58.r2.dev)] -``` - ### New Netlify Setup This guide walks you through the steps to set up a Netlify site for your project by importing an existing project from GitHub. From 6b63ad891247c1d99bfc43868d1a7c2c87963969 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 6 Nov 2024 02:06:16 -0300 Subject: [PATCH 09/99] Delete error loging from main readme --- README.md | 12 ------------ 1 file changed, 12 deletions(-) diff --git a/README.md b/README.md index cc246662a3..7a4603a67f 100644 --- a/README.md +++ b/README.md @@ -595,18 +595,6 @@ On Facebook Authentication settings, set the "Client ID" with the Facebook App I About Facebook Authentication instructions: https://www.mongodb.com/docs/realm/web/authenticate/#facebook-authentication -### Error logging - -This project uses [Rollbar](https://rollbar.com) for error logging for the whole site, including background processes. - -To log the errors a Realm secret value should be set: -``` -rollbarAccessToken: [The access token value from your Rollbar account > Projects > Your project > Project Access Tokens > post_server_item] -``` -In addition to that, this env variable should be set as well: -``` -GATSBY_ROLLBAR_TOKEN: [The access token value from your Rollbar account > Projects > Your project > Project Access Tokens > post_server_item] -``` ### Restoring Production database to Staging From 457fcdcfbae7f86b909f19f95b85a49acd2d79c4 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 6 Nov 2024 02:09:29 -0300 Subject: [PATCH 10/99] Delete old social networks instructions --- README.md | 20 -------------------- 1 file changed, 20 deletions(-) diff --git a/README.md b/README.md index 7a4603a67f..5dccc19da8 100644 --- a/README.md +++ b/README.md @@ -576,26 +576,6 @@ In addition to that, you have to add your Netlify site URL to the allowed origin 5. Click `Save Draft` 6. Deploy draft -## Social Networks login integration - -To enable social network login, you will need to add the following configuration to your Atlas App Service. - -Add this secret value to your Atlas App Service following the instructions in the [Atlas App Services documentation](https://www.mongodb.com/docs/atlas/app-services/values-and-secrets/define-and-manage-secrets/). - -``` -facebookAppSecret = [Facebook App Secret, see comment below for more information] -``` - -- To get the Facebook App Secret you should go to the [Facebook Developer Portal](https://developers.facebook.com/apps/), and click on your app > Settings > Basic. - -On Facebook Authentication settings, set the "Client ID" with the Facebook App Id. To get the Facebook App ID you should go to the [Facebook Developer Portal](https://developers.facebook.com/apps/), and check your app. - -"Redirect URIs" is the URL that the user will be redirected to after successfully authenticating with Facebook or Google. It should point to `/logincallback` page. For Production the URI is `https://incidentdatabase.ai/logincallback`, for Staging the URI is `https://staging-aiid.netlify.app/logincallback` - - -About Facebook Authentication instructions: https://www.mongodb.com/docs/realm/web/authenticate/#facebook-authentication - - ### Restoring Production database to Staging There is a GitHub Workflow "Restore Prod DB into Staging" that can be triggered manually to dump and restore Production database into Staging database (both `aiidprod` and `translations` databases) From 12dc477b29de31ae5499314e0fd073794469173d Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 6 Nov 2024 02:30:29 -0300 Subject: [PATCH 11/99] Cleanup sample env file --- site/gatsby-site/README.md | 96 +++++++++++++++++++++++++------------- 1 file changed, 63 insertions(+), 33 deletions(-) diff --git a/site/gatsby-site/README.md b/site/gatsby-site/README.md index cf1578b9ee..95eab35b5e 100644 --- a/site/gatsby-site/README.md +++ b/site/gatsby-site/README.md @@ -18,40 +18,70 @@ Once you have cloned the repository, to set up a local development environment f ### 2. **Configure Environment Variables** - Create a `.env` file in the root of the `gatsby-site` directory. Add the following environment variables to the file, replacing the placeholders with your actual credentials: - - ```env - REALM_API_APP_ID= # Application ID for MongoDB Realm API - REALM_API_GROUP_ID= # Group ID for MongoDB Realm API - REALM_API_PRIVATE_KEY= # Private key for accessing the MongoDB Realm API - REALM_API_PUBLIC_KEY= # Public key for accessing the MongoDB Realm API - REALM_GRAPHQL_API_KEY= # API key for accessing the Realm GraphQL API - REALM_APP_ID= # App ID used to access MongoDB Realm services - API_MONGODB_CONNECTION_STRING=mongodb://127.0.0.1:4110 # MongoDB connection string - ROLLBAR_POST_SERVER_ITEM_ACCESS_TOKEN= # Token for sending error reports to Rollbar from the server - GATSBY_REALM_APP_ID= # Application ID used in the Gatsby frontend for MongoDB Realm, same as REALM_APP_ID - GATSBY_ALGOLIA_APP_ID= # Application ID for Algolia search integration in the Gatsby app - GATSBY_ALGOLIA_SEARCH_KEY= # Public search key for Algolia, used in the Gatsby frontend - ALGOLIA_ADMIN_KEY= # Admin key for managing the Algolia index - MONGODB_CONNECTION_STRING=mongodb://127.0.0.1:4110 # MongoDB connection string - MONGODB_REPLICA_SET= # Name of the MongoDB replica set for high availability - MONGODB_TRANSLATIONS_CONNECTION_STRING=mongodb://127.0.0.1:4110 # MongoDB connection string for the translations database - GOOGLE_MAPS_API_KEY= # API key for accessing Google Maps services - GATSBY_AVAILABLE_LANGUAGES= # List of languages available for the Gatsby app (e.g., en, es, fr) - GOOGLE_TRANSLATE_API_KEY= # API key for accessing Google Translate services - GATSBY_ROLLBAR_TOKEN= # Token for Rollbar error tracking in the Gatsby frontend - CLOUDFLARE_R2_ACCOUNT_ID= # Account ID for Cloudflare R2 storage service - CLOUDFLARE_R2_ACCESS_KEY_ID= # Access key ID for Cloudflare R2 storage - CLOUDFLARE_R2_SECRET_ACCESS_KEY= # Secret access key for Cloudflare R2 storage - CLOUDFLARE_R2_BUCKET_NAME= # Name of the Cloudflare R2 bucket for storage - GATSBY_CLOUDFLARE_R2_PUBLIC_BUCKET_URL= # Public URL for accessing the Cloudflare R2 bucket from the Gatsby app - MAILERSEND_API_KEY= # API key for MailerSend email service or dummy value if you don't plan to send emails - NOTIFICATIONS_SENDER_NAME=AIID Notifications # Name of the sender for email notifications - NOTIFICATIONS_SENDER=notifications@incidentdatabase.ai # Email address of the sender for email notifications - SITE_URL=http://localhost:8000 - ``` +Create a `.env` file in the root of the `gatsby-site` directory. Add the following environment variables to the file, replacing the placeholders with your actual credentials: + +```env + +# Atlas App Services (formerly Realm) + +REALM_API_APP_ID= # Application ID for MongoDB Realm API +REALM_API_GROUP_ID= # Group ID for MongoDB Realm API +REALM_API_PRIVATE_KEY= # Private key for accessing the MongoDB Realm API +REALM_API_PUBLIC_KEY= # Public key for accessing the MongoDB Realm API +REALM_GRAPHQL_API_KEY= # API key for accessing the Realm GraphQL API +REALM_APP_ID= # App ID used to access MongoDB Realm services +GATSBY_REALM_APP_ID= # Application ID used in the Gatsby frontend for MongoDB Realm, same as REALM_APP_ID + +# Rollbar + +ROLLBAR_POST_SERVER_ITEM_ACCESS_TOKEN=dummy # Token for sending error reports to Rollbar from the server +GATSBY_ROLLBAR_TOKEN= # Token for Rollbar error tracking in the Gatsby frontend + +# Algolia + +GATSBY_ALGOLIA_APP_ID= # Application ID for Algolia search integration in the Gatsby app +GATSBY_ALGOLIA_SEARCH_KEY= # Public search key for Algolia, used in the Gatsby frontend +ALGOLIA_ADMIN_KEY= # Admin key for managing the Algolia index + +# Mongo database + +API_MONGODB_CONNECTION_STRING=mongodb://127.0.0.1:4110 # MongoDB connection string +MONGODB_CONNECTION_STRING=mongodb://127.0.0.1:4110 # MongoDB connection string +MONGODB_REPLICA_SET=mongodb://127.0.0.1:4110 # Name of the MongoDB replica set for high availability +MONGODB_TRANSLATIONS_CONNECTION_STRING=mongodb://127.0.0.1:4110 # MongoDB connection string for the translations database + +# Translations + +GATSBY_AVAILABLE_LANGUAGES=en,es # List of languages available for the Gatsby app (e.g., en, es, fr) +GOOGLE_TRANSLATE_API_KEY= # API key for accessing Google Translate services + +# Cloudflare R2 storage + +CLOUDFLARE_R2_ACCOUNT_ID= # Account ID for Cloudflare R2 storage service +CLOUDFLARE_R2_ACCESS_KEY_ID= # Access key ID for Cloudflare R2 storage +CLOUDFLARE_R2_SECRET_ACCESS_KEY= # Secret access key for Cloudflare R2 storage +CLOUDFLARE_R2_BUCKET_NAME= # Name of the Cloudflare R2 bucket for storage +GATSBY_CLOUDFLARE_R2_PUBLIC_BUCKET_URL= # Public URL for accessing the Cloudflare R2 bucket from the Gatsby app + +# Email notifications + +MAILERSEND_API_KEY= # API key for MailerSend email service or dummy value if you don't plan to send emails +NOTIFICATIONS_SENDER_NAME=AIID Notifications # Name of the sender for email notifications +NOTIFICATIONS_SENDER=notifications@incidentdatabase.ai # Email address of the sender for email notifications + +SITE_URL=http://localhost:8000 + +# Prismic + +PRISMIC_ACCESS_TOKEN= +GATSBY_PRISMIC_REPO_NAME=aiidstaging + +# Other services + +GOOGLE_MAPS_API_KEY= # API key for accessing Google Maps services + +``` - Ensure that each variable is set correctly to match your development environment's requirements. #### Additional Configuration From 8db169cf05d7aba2e3bf35c49ae42b83d202e42b Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 6 Nov 2024 03:09:19 -0300 Subject: [PATCH 12/99] Move github workflow instructions --- README.md | 75 -------------------------------------- site/gatsby-site/README.md | 70 +++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 75 deletions(-) diff --git a/README.md b/README.md index 5dccc19da8..ddf3027adc 100644 --- a/README.md +++ b/README.md @@ -352,81 +352,6 @@ This guide walks you through the steps to set up a Netlify site for your project - Go to **Site Configuration** > **Site Details**. - Copy the `NETLIFY_SITE_ID`. This will be useful when setting up the GitHub environment. -### Github Actions -Two workflows take care of deploying the Realm app to both `production` and `staging` environments, defined in `realm-production.yml` and `realm-staging.yml`. Each workflow looks for environment variables defined in a GitHub Environment named `production` and `staging`. - -These environments must contain the following variables: -``` -GATSBY_REALM_APP_ID= -REALM_API_PRIVATE_KEY= -REALM_API_PUBLIC_KEY= -``` -To get your Public and Private API Key, follow these [instructions](https://www.mongodb.com/docs/atlas/configure-api-access/#std-label-create-org-api-key). - -### Deployment Workflows on GitHub Actions - -We have integrated our testing and deployment processes with GitHub Actions. There are three primary workflows for deployment: Deploy Previews, Staging, and Production. The goal of these workflows is to continuously test and integrate changes in pull requests across environments. - -#### 1) Deploy Previews Workflow - -- **File:** [/.github/workflows/preview.yml](/.github/workflows/preview.yml) -- **Trigger:** This workflow is activated for pushes to pull requests that target the `staging` branch. -- **Process:** Executes both the integration tests and deploys the application to Netlify. -- **Post-Deployment:** Upon a successful deployment, the workflow automatically posts a comment on the pull request. This comment includes a link to the Netlify preview of the changes and a link to the Netlify deploy log. -- **Environment:** This workflow uses the `staging` GitHub environment. - -#### 2) Staging Workflow (WIP) - -- **Trigger:** Runs only on pushes to the `staging` branch. -- **Process:** Executes both the integration tests and deploys to Netlify. -- **Deployment Criteria:** If the tests fail, no deployment will be carried out. -- **Environment:** This workflow uses the `staging` GitHub environment. - -#### 3) Production Workflow (WIP) - -- **Trigger:** Runs only on pushes to the `main` branch. -- **Process:** Executes both the integration tests and deploys to Netlify. -- **Deployment Criteria:** If the tests fail, no deployment will be carried out. -- **Environment:** This workflow uses the `production` GitHub environment. - -### GitHub Environment Configuration - -All three workflows share a common set of environment variables, which need to be defined for each environment. (Currently, we have only two environments: `staging` and `production`.) These variables are categorized into secrets and standard variables, and are accessed via GitHub actions as such. - -#### Secrets - -- `ALGOLIA_ADMIN_KEY` -- `CLOUDFLARE_R2_ACCESS_KEY_ID` -- `CLOUDFLARE_R2_ACCOUNT_ID` -- `CLOUDFLARE_R2_BUCKET_NAME` -- `CLOUDFLARE_R2_SECRET_ACCESS_KEY` -- `CYPRESS_RECORD_KEY` -- `E2E_ADMIN_PASSWORD` -- `E2E_ADMIN_USERNAME` -- `GOOGLE_TRANSLATE_API_KEY` -- `MONGODB_CONNECTION_STRING` -- `MONGODB_MIGRATIONS_CONNECTION_STRING` -- `MONGODB_REPLICA_SET` -- `MONGODB_TRANSLATIONS_CONNECTION_STRING` -- `NETLIFY_AUTH_TOKEN` -- `PRISMIC_ACCESS_TOKEN` -- `REALM_API_PRIVATE_KEY` -- `REALM_GRAPHQL_API_KEY` -- `REALM_API_PUBLIC_KEY` -- `GATSBY_ROLLBAR_TOKEN` - -#### Variables - -- `CYPRESS_PROJECT_ID` -- `GATSBY_ALGOLIA_APP_ID` -- `GATSBY_ALGOLIA_SEARCH_KEY` -- `GATSBY_AVAILABLE_LANGUAGES` -- `GATSBY_CLOUDFLARE_R2_PUBLIC_BUCKET_URL` -- `GATSBY_PRISMIC_REPO_NAME` -- `GATSBY_REALM_APP_ID` -- `NETLIFY_SITE_ID` -- `REALM_API_APP_ID` -- `REALM_API_GROUP_ID` ## Adding new Taxonomies diff --git a/site/gatsby-site/README.md b/site/gatsby-site/README.md index 95eab35b5e..e6d44e4f16 100644 --- a/site/gatsby-site/README.md +++ b/site/gatsby-site/README.md @@ -497,3 +497,73 @@ Add the following environment variable on Netlify: 13. Click `Add this webhook` +### Deployment Workflows on GitHub Actions + +We have integrated our testing and deployment processes with GitHub Actions. There are three primary workflows for deployment: Deploy Previews, Staging, and Production. The goal of these workflows is to continuously test and integrate changes in pull requests across environments. + +##### 1. Deploy Previews Workflow + +- **File:** [/.github/workflows/preview.yml](/.github/workflows/preview.yml) +- **Trigger:** This workflow is activated for pushes to pull requests that target the `staging` branch. +- **Process:** Executes both the integration tests and deploys the application to Netlify. +- **Post-Deployment:** Upon a successful deployment, the workflow automatically posts a comment on the pull request. This comment includes a link to the Netlify preview of the changes and a link to the Netlify deploy log. +- **Environment:** This workflow uses the `staging` GitHub environment. + +##### 2. Staging Workflow + +- **Trigger:** Runs only on pushes to the `staging` branch. +- **Process:** Executes both the integration tests and deploys to Netlify. +- **Deployment Criteria:** If the tests fail, no deployment will be carried out. +- **Environment:** This workflow uses the `staging` GitHub environment. + +##### 3. Production Workflow + +- **Trigger:** Runs only on pushes to the `main` branch. +- **Process:** Executes both the integration tests and deploys to Netlify. +- **Deployment Criteria:** If the tests fail, no deployment will be carried out. +- **Environment:** This workflow uses the `production` GitHub environment. + +#### GitHub Environment Configuration + +All three workflows share a common set of environment variables, which need to be defined for each environment. (Currently, we have only two environments: `staging` and `production`.) These variables are categorized into secrets and standard variables, and are accessed via GitHub actions as such. + +#### Secrets + +- `ALGOLIA_ADMIN_KEY` +- `CLOUDFLARE_R2_ACCESS_KEY_ID` +- `CLOUDFLARE_R2_ACCOUNT_ID` +- `CLOUDFLARE_R2_BUCKET_NAME` +- `CLOUDFLARE_R2_SECRET_ACCESS_KEY` +- `CYPRESS_RECORD_KEY` +- `E2E_ADMIN_PASSWORD` +- `E2E_ADMIN_USERNAME` +- `GOOGLE_TRANSLATE_API_KEY` +- `MONGODB_CONNECTION_STRING` +- `MONGODB_MIGRATIONS_CONNECTION_STRING` +- `MONGODB_REPLICA_SET` +- `MONGODB_TRANSLATIONS_CONNECTION_STRING` +- `NETLIFY_AUTH_TOKEN` +- `PRISMIC_ACCESS_TOKEN` +- `REALM_API_PRIVATE_KEY` +- `REALM_GRAPHQL_API_KEY` +- `REALM_API_PUBLIC_KEY` +- `GATSBY_ROLLBAR_TOKEN` + +(outdated) + +#### Variables + +- `CYPRESS_PROJECT_ID` +- `GATSBY_ALGOLIA_APP_ID` +- `GATSBY_ALGOLIA_SEARCH_KEY` +- `GATSBY_AVAILABLE_LANGUAGES` +- `GATSBY_CLOUDFLARE_R2_PUBLIC_BUCKET_URL` +- `GATSBY_PRISMIC_REPO_NAME` +- `GATSBY_REALM_APP_ID` +- `NETLIFY_SITE_ID` +- `REALM_API_APP_ID` +- `REALM_API_GROUP_ID` + +(outdated) + + From 8f7be31c89ea6eddab83e5bf531eb4d588437ce6 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 6 Nov 2024 03:12:53 -0300 Subject: [PATCH 13/99] Add mailersend and cloudflare instructions --- site/gatsby-site/README.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/site/gatsby-site/README.md b/site/gatsby-site/README.md index e6d44e4f16..f86ba9634a 100644 --- a/site/gatsby-site/README.md +++ b/site/gatsby-site/README.md @@ -83,6 +83,16 @@ GOOGLE_MAPS_API_KEY= # API key for accessing Google Maps services ``` +#### MailerSend environment variables + +Unless the feature you are working on requires sending email notifications, you can leave these variables with the `dummy` value. Otherwise, follow the instructions here: [MailerSend setup](#mailersend-setup) + + +#### Cloudflare R2 storage environment variables + +Unless the feature you are working on requires Cloudflare R2 storage, you can leave these variables empty. Otherwise, follow the instructions here: [Cloudflare R2 storage](#Cloudflare-R2-storage) + + #### Additional Configuration When building the site, some steps can take a while to run. This can be inconvenient when you are working on a feature unrelated to the steps taking the most time in the build process. To avoid this problem, you can set the environment variable `SKIP_PAGE_CREATOR` to a comma-separated list of page-creator functions found in [`gatsby-node`](https://github.com/responsible-ai-collaborative/aiid/blob/main/site/gatsby-site/gatsby-node.js) that should be skipped. These include: `createMdxPages`, `createCitationPages`, `createWordCountsPages`, `createBackupsPage`, `createTaxonomyPages`, `createDownloadIndexPage`, `createDuplicatePages`, `createTsneVisualizationPage`, and `createEntitiesPages`. For instance, to run a development build skipping the creation of the TSNE (spatial) visualization and citation pages, you would run: @@ -496,6 +506,13 @@ Add the following environment variable on Netlify: 12. In `Triggers` select `A document is published` and `A document is unpublished` 13. Click `Add this webhook` +### MailerSend setup + +Create an account on [MailerSend](https://www.mailersend.com/) and generate and API Key. This key should be added to the `.env` file as `MAILERSEND_API_KEY`. + +### Cloudflare R2 storage + +Create an account on [Cloudflare](https://www.cloudflare.com/) and create a new R2 storage bucket. ### Deployment Workflows on GitHub Actions From 9537f20db00e7009ef24bad717c9a6b56fb8eaca Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 6 Nov 2024 03:13:37 -0300 Subject: [PATCH 14/99] Reorganize env vars --- site/gatsby-site/README.md | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/site/gatsby-site/README.md b/site/gatsby-site/README.md index f86ba9634a..e3d6aeb92d 100644 --- a/site/gatsby-site/README.md +++ b/site/gatsby-site/README.md @@ -32,29 +32,34 @@ REALM_GRAPHQL_API_KEY= # API key for accessing the Realm GraphQL API REALM_APP_ID= # App ID used to access MongoDB Realm services GATSBY_REALM_APP_ID= # Application ID used in the Gatsby frontend for MongoDB Realm, same as REALM_APP_ID + +# Mongo database + +API_MONGODB_CONNECTION_STRING=mongodb://127.0.0.1:4110 # MongoDB connection string +MONGODB_CONNECTION_STRING=mongodb://127.0.0.1:4110 # MongoDB connection string +MONGODB_REPLICA_SET=mongodb://127.0.0.1:4110 # Name of the MongoDB replica set for high availability +MONGODB_TRANSLATIONS_CONNECTION_STRING=mongodb://127.0.0.1:4110 # MongoDB connection string for the translations database + + # Rollbar ROLLBAR_POST_SERVER_ITEM_ACCESS_TOKEN=dummy # Token for sending error reports to Rollbar from the server -GATSBY_ROLLBAR_TOKEN= # Token for Rollbar error tracking in the Gatsby frontend +GATSBY_ROLLBAR_TOKEN=dummy # Token for Rollbar error tracking in the Gatsby frontend + # Algolia -GATSBY_ALGOLIA_APP_ID= # Application ID for Algolia search integration in the Gatsby app -GATSBY_ALGOLIA_SEARCH_KEY= # Public search key for Algolia, used in the Gatsby frontend +GATSBY_ALGOLIA_APP_ID=JD5JCVZEVS # Application ID for Algolia search integration in the Gatsby app +GATSBY_ALGOLIA_SEARCH_KEY=c5e99d93261645721a1765fe4414389c # Public search key for Algolia, used in the Gatsby frontend ALGOLIA_ADMIN_KEY= # Admin key for managing the Algolia index -# Mongo database - -API_MONGODB_CONNECTION_STRING=mongodb://127.0.0.1:4110 # MongoDB connection string -MONGODB_CONNECTION_STRING=mongodb://127.0.0.1:4110 # MongoDB connection string -MONGODB_REPLICA_SET=mongodb://127.0.0.1:4110 # Name of the MongoDB replica set for high availability -MONGODB_TRANSLATIONS_CONNECTION_STRING=mongodb://127.0.0.1:4110 # MongoDB connection string for the translations database # Translations GATSBY_AVAILABLE_LANGUAGES=en,es # List of languages available for the Gatsby app (e.g., en, es, fr) GOOGLE_TRANSLATE_API_KEY= # API key for accessing Google Translate services + # Cloudflare R2 storage CLOUDFLARE_R2_ACCOUNT_ID= # Account ID for Cloudflare R2 storage service @@ -63,26 +68,29 @@ CLOUDFLARE_R2_SECRET_ACCESS_KEY= # Secret access key for Cloudflare R2 storage CLOUDFLARE_R2_BUCKET_NAME= # Name of the Cloudflare R2 bucket for storage GATSBY_CLOUDFLARE_R2_PUBLIC_BUCKET_URL= # Public URL for accessing the Cloudflare R2 bucket from the Gatsby app + # Email notifications -MAILERSEND_API_KEY= # API key for MailerSend email service or dummy value if you don't plan to send emails +MAILERSEND_API_KEY=dummy # API key for MailerSend email service or dummy value if you don't plan to send emails NOTIFICATIONS_SENDER_NAME=AIID Notifications # Name of the sender for email notifications NOTIFICATIONS_SENDER=notifications@incidentdatabase.ai # Email address of the sender for email notifications -SITE_URL=http://localhost:8000 # Prismic PRISMIC_ACCESS_TOKEN= GATSBY_PRISMIC_REPO_NAME=aiidstaging -# Other services + +# Other GOOGLE_MAPS_API_KEY= # API key for accessing Google Maps services +SITE_URL=http://localhost:8000 ``` + #### MailerSend environment variables Unless the feature you are working on requires sending email notifications, you can leave these variables with the `dummy` value. Otherwise, follow the instructions here: [MailerSend setup](#mailersend-setup) From 5646c2d91159928a6c209e9161792bacf58643eb Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 6 Nov 2024 03:25:43 -0300 Subject: [PATCH 15/99] Update graphql endpoint instructions --- README.md | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/README.md b/README.md index ddf3027adc..51c73c2f8e 100644 --- a/README.md +++ b/README.md @@ -448,10 +448,8 @@ Restarting Gatsby should make the new taxonomy available on the citation pages, ## Public GraphQL endpoint -The site exposes a read-only GraphQL endpoint at `/api/graphql`, which is a reflection of the Realm's auto-generated endpoint. -### Accessing the endpoint -You can check the endpoint [here](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Fincidentdatabase.ai%2Fapi%2Fgraphql) +You can check the endpoint here: [https://incidentdatabase.ai/api/graphql](https://incidentdatabase.ai/api/graphql). ### Sample request From 7470add2225d8e6ee8b5050514a502b8e90833de Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 6 Nov 2024 03:34:48 -0300 Subject: [PATCH 16/99] Move netlify instructions --- README.md | 53 -------------------------------------- site/gatsby-site/README.md | 51 ++++++++++++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 53 deletions(-) diff --git a/README.md b/README.md index 51c73c2f8e..90eff900ac 100644 --- a/README.md +++ b/README.md @@ -301,59 +301,6 @@ As soon as a user is signed in, the system assigns a `subscriber` role by defaul | `admin` | This role has full access to the site, including the ability to edit users' roles. | -## Deployment Setup - -Deployment of the site consists of two parts: deployment of the backend related features that runs as a GitHub Action and deployment of the frontend related features that runs on Netlify: - -### New Netlify Setup - -This guide walks you through the steps to set up a Netlify site for your project by importing an existing project from GitHub. - -### Prerequisites - -- Ensure you have a GitHub account and your project is already pushed to a repository. -- Make sure you have a Netlify account. If not, sign up at [Netlify](https://www.netlify.com/). - -### Steps to Set Up - -#### 1. Add New Site - -- Go to your Netlify dashboard. -- Click on **Add New Site**. - -#### 2. Import Existing Project - -- Choose **Import Existing Project**. - -#### 3. Deploy with GitHub - -- Select **Deploy with GitHub** to connect your GitHub account. - -#### 4. Select Repository - -- Choose the repository where your project is located. - -#### 5. Configure Deployment - -- Under **Branch to Deploy**, select `main`. This setting doesn't matter for now. -- Leave all other settings as default. -- Click on **Deploy Site**. - -#### 6. Site Configuration - -##### Build and Deploy - -- Navigate to **Site Configuration** > **Build & Deploy**. -- Under **Build Settings** > **Build Status**, find **Stopped Builds**. -- Click **Save**. - -##### Site Details - -- Go to **Site Configuration** > **Site Details**. -- Copy the `NETLIFY_SITE_ID`. This will be useful when setting up the GitHub environment. - - - ## Adding new Taxonomies ### To add new taxonomies, follow these steps: diff --git a/site/gatsby-site/README.md b/site/gatsby-site/README.md index e3d6aeb92d..ae58188122 100644 --- a/site/gatsby-site/README.md +++ b/site/gatsby-site/README.md @@ -440,6 +440,57 @@ Migrations run automatically as part of Gatsby's `onPreBootstrap` event, but it To run all pending migrations: ``` + +### Netlify + +Netlify is used to host the AIID frontend and API. + +#### Prerequisites + +- Ensure you have a GitHub account and your project is already pushed to a repository. +- Make sure you have a Netlify account. If not, sign up at [Netlify](https://www.netlify.com/). + +#### Steps to Set Up + +##### 1. Add New Site + +- Go to your Netlify dashboard. +- Click on **Add New Site**. + +##### 2. Import Existing Project + +- Choose **Import Existing Project**. + +##### 3. Deploy with GitHub + +- Select **Deploy with GitHub** to connect your GitHub account. + +##### 4. Select Repository + +- Choose the repository where your project is located. + +##### 5. Configure Deployment + +- Under **Branch to Deploy**, select `main`. This setting doesn't matter for now. +- Leave all other settings as default. +- Click on **Deploy Site**. + +##### 6. Site Configuration + +###### Build and Deploy + +- Navigate to **Site Configuration** > **Build & Deploy**. +- Under **Build Settings** > **Build Status**, find **Stopped Builds**. +- Click **Save**. + +###### Site Details + +- Go to **Site Configuration** > **Site Details**. +- Copy the `NETLIFY_SITE_ID`. This will be useful when setting up the GitHub environment. + +#### 7. Personal Access Token + +Go to your Netlify account settings and create a new personal access token. This token will be used to authenticate the GitHub Actions workflows. `NETLIFY_AUTH_TOKEN` should be set as a GitHub secret. node migrator up ``` To run a specific migration: From 8040bd6e429f525a387980e9f7727909f892b7e4 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 6 Nov 2024 03:40:45 -0300 Subject: [PATCH 17/99] Fix prismic section headings --- site/gatsby-site/README.md | 25 +++++-------------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/site/gatsby-site/README.md b/site/gatsby-site/README.md index ae58188122..615b986d45 100644 --- a/site/gatsby-site/README.md +++ b/site/gatsby-site/README.md @@ -491,22 +491,7 @@ Netlify is used to host the AIID frontend and API. #### 7. Personal Access Token Go to your Netlify account settings and create a new personal access token. This token will be used to authenticate the GitHub Actions workflows. `NETLIFY_AUTH_TOKEN` should be set as a GitHub secret. -node migrator up -``` -To run a specific migration: -``` -node migrator up --name 2022.02.18T16.29.14.increment-report-number.js -``` -### Adding a new migration -To add a new migration, execute the following command and define the `up` and `down` processes as pertinent. -``` -node migrator create --name increment-report-number.js --folder migrations -``` - -Execution is taken care of by the [umzug](https://github.com/sequelize/umzug) package. Please refer to its documentation for more information. - - -## Prismic setup +### Prismic setup This project uses Prismic to fetch page content. You can still run the project without setting a Prismic account. @@ -517,12 +502,12 @@ This project uses Prismic to fetch page content. You can still run the project w 5. Click `Create repository` 6. Create a new token in Settings > API & Security > Content API tab > Change Repository security to `Private API – Require an access token for any request` > Create new app > Permanent access tokens > Save value for later -### Adding the Prismic content types +#### Adding the Prismic content types -#### Prismic Custom Types +##### Prismic Custom Types You can find the list of all custom types in the folder `custom_types` -#### How to create a new Custom Type +##### How to create a new Custom Type 1. From the prismic left menu click `Custom Types` 2. Click `Create new custom type` 3. Give it a name (name of the json in custom_types folder) @@ -530,7 +515,7 @@ You can find the list of all custom types in the folder `custom_types` 5. Paste the JSON content from the predefined custom types inside the json 6. Click `Save` -#### Adding Prismic documents +##### Adding Prismic documents 1. On the Prismic dashboard left menu click `Documents` 2. Click `Create new` From 7330ac38a250c192b44742357f4d889a66ee7572 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Thu, 14 Nov 2024 11:51:41 -0300 Subject: [PATCH 18/99] Set-up next auth server side --- site/gatsby-site/_redirects | 1 + site/gatsby-site/gatsby-shared.js | 13 +- site/gatsby-site/netlify/functions/auth.ts | 117 ++ site/gatsby-site/netlify/functions/graphql.ts | 6 + site/gatsby-site/nextauth.config.ts | 87 ++ site/gatsby-site/package-lock.json | 1034 ++++++++++++++++- site/gatsby-site/package.json | 4 +- site/gatsby-site/server/context.ts | 100 +- site/gatsby-site/server/fields/common.ts | 17 +- site/gatsby-site/server/types/user.ts | 2 +- site/gatsby-site/src/utils/serverless.ts | 44 + 11 files changed, 1267 insertions(+), 158 deletions(-) create mode 100644 site/gatsby-site/netlify/functions/auth.ts create mode 100644 site/gatsby-site/nextauth.config.ts create mode 100644 site/gatsby-site/src/utils/serverless.ts diff --git a/site/gatsby-site/_redirects b/site/gatsby-site/_redirects index 6921bb1a13..e0f879d016 100644 --- a/site/gatsby-site/_redirects +++ b/site/gatsby-site/_redirects @@ -5,3 +5,4 @@ /api/parseNews /.netlify/functions/parseNews 200 /api/semanticallyRelated /.netlify/functions/semanticallyRelated 200 /api/lookupbyurl /.netlify/functions/lookupbyurl 200 +/api/auth/* /.netlify/functions/auth 200 diff --git a/site/gatsby-site/gatsby-shared.js b/site/gatsby-site/gatsby-shared.js index dedab9c402..8733041c28 100644 --- a/site/gatsby-site/gatsby-shared.js +++ b/site/gatsby-site/gatsby-shared.js @@ -5,6 +5,7 @@ import { UserContextProvider } from 'contexts/userContext'; import { MenuContextProvider } from 'contexts/MenuContext'; import { ToastContextProvider } from 'contexts/ToastContext'; import { Script } from 'gatsby'; +import { SessionProvider } from 'next-auth/react'; export const wrapPageElement = ({ element }) => { const history = { @@ -19,11 +20,13 @@ export const wrapPageElement = ({ element }) => { const location = typeof window == 'undefined' ? {} : window.location; return ( - - - {element} - - + + + + {element} + + + ); }; diff --git a/site/gatsby-site/netlify/functions/auth.ts b/site/gatsby-site/netlify/functions/auth.ts new file mode 100644 index 0000000000..6102e75cd6 --- /dev/null +++ b/site/gatsby-site/netlify/functions/auth.ts @@ -0,0 +1,117 @@ +import NextAuth from "next-auth"; +import { getAuthConfig } from "../../nextauth.config"; +import { createResponse } from '../../src/utils/serverless' + +const parseBody = (event) => { + + const contentType = event.headers['content-type'] || event.headers['Content-Type']; + const { body } = event; + + if (!body) return undefined; + + if (contentType?.includes('application/json')) { + try { + return JSON.parse(body); + } catch (e) { + console.error('Failed to parse JSON body:', e); + return undefined; + } + } + + if (contentType?.includes('application/x-www-form-urlencoded')) { + const params = new URLSearchParams(body); + const formData: Record = {}; + params.forEach((value, key) => { + formData[key] = value; + }); + return formData; + } + + return undefined; +}; + +const parseCookies = (cookieHeader: string) => { + const cookies: Record = {}; + if (!cookieHeader) return cookies; + + cookieHeader.split(';').forEach(cookie => { + const parts = cookie.trim().split('='); + if (parts.length >= 2) { + const key = parts[0]; + const value = parts.slice(1).join('='); + cookies[key] = decodeURIComponent(value); + } + }); + + return cookies; +}; + +const parseHeaders = (event: any) => { + + return { + ...event.headers, + host: event.headers.host || new URL(process.env.NEXTAUTH_URL).host, + } +} + +const parseQuery = (event: any) => { + + const path = event.path.replace('/api/auth/', '') + const nextAuthArray = path.split('/').filter(Boolean); + + return { + ...event.queryStringParameters, + nextauth: nextAuthArray, + action: nextAuthArray[nextAuthArray.length - 1], + providerId: nextAuthArray[1] + } +} + +const recreateRequest = (event) => { + return { + method: event.httpMethod, + query: parseQuery(event), + cookies: parseCookies(event.headers.cookie), + headers: parseHeaders(event), + body: parseBody(event), + } +} + +export const handler = async (event, context) => { + + const req = recreateRequest(event); + const res = createResponse(); + + try { + + const authConfig = await getAuthConfig(); + + await NextAuth(req, res, authConfig) + + + const response = res.getResponse(); + const responseHeaders = res.getResponseHeaders(); + + return { + ...response, + statusCode: response.statusCode || 200, + body: response.body || '{}', + headers: { + ...responseHeaders, + 'Cache-Control': 'no-store, max-age=0' + } + } + } catch (error) { + + console.error('NextAuth Error:', error) + console.error('Request details:', req); + + return { + statusCode: 500, + body: JSON.stringify({ + error: 'Internal Server Error', + details: error.message, + }) + } + } +} \ No newline at end of file diff --git a/site/gatsby-site/netlify/functions/graphql.ts b/site/gatsby-site/netlify/functions/graphql.ts index 090dd6cea8..08a9668720 100644 --- a/site/gatsby-site/netlify/functions/graphql.ts +++ b/site/gatsby-site/netlify/functions/graphql.ts @@ -4,6 +4,7 @@ import { ApolloServer } from '@apollo/server'; import config from '../../server/config'; import { MongoClient } from 'mongodb'; import { startServerAndCreateLambdaHandler, handlers } from '@as-integrations/aws-lambda'; +import cookie from 'cookie'; const server = new ApolloServer({ schema, @@ -29,6 +30,11 @@ export const handler = startServerAndCreateLambdaHandler( event.rawQueryString = event.rawQuery; } + // next auth expects the `req` object to be Express-like + + event.method = event.httpMethod; + event.cookies = cookie.parse(event.headers.cookie || ''); + return (result) => result; }, ], diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts new file mode 100644 index 0000000000..95767426c0 --- /dev/null +++ b/site/gatsby-site/nextauth.config.ts @@ -0,0 +1,87 @@ +import { EmailParams, MailerSend, Recipient } from "mailersend" +import { MongoClient, ServerApiVersion } from "mongodb" +import { NextAuthOptions } from "next-auth" + +const client = new MongoClient(process.env.API_MONGODB_CONNECTION_STRING!, { + serverApi: { + version: ServerApiVersion.v1, + strict: true, + deprecationErrors: true, + }, +}) + +const mailersend = new MailerSend({ + apiKey: process.env.MAILERSEND_API_KEY!, +}); + +export const sendVerificationRequest = async ({ identifier: email, url }: { identifier: string, url: string }) => { + + const emailParams = new EmailParams() + .setFrom({ email: process.env.NOTIFICATIONS_SENDER!, name: 'something' }) + .setTo([new Recipient(email)]) + .setSubject('something') + .setText(`Please click here to authenticate - ${url}`); + + await mailersend.email.send(emailParams); +} + +export const getAuthConfig = async (): Promise => { + + // we have to do a dynamic import here because MongoDBAdapter is ESM only + const { MongoDBAdapter } = await import("@auth/mongodb-adapter"); + + return { + providers: [ + // @ts-ignore + { + id: "http-email", + name: "Email", + type: "email", + maxAge: 60 * 60 * 24, // Email link will expire in 24 hours + sendVerificationRequest, + } + ], + theme: { + logo: "https://www.gatsbyjs.com/Gatsby-Monogram.svg", + colorScheme: "light", + brandColor: "#663399", + }, + adapter: MongoDBAdapter(client, { databaseName: 'auth' }), + session: { + maxAge: 5 * 24 * 60 * 60, // 5 days + updateAge: 24 * 60 * 60 // 24 hours + }, + callbacks: { + async session({ session, token, user, newSession }) { + + const customData = await client.db('customData').collection('users').findOne({ userId: user.id }); + + session.user.id = customData.userId; + session.user.roles = customData.roles; + + return session + }, + }, + events: { + createUser: async ({ user }) => { + + await client.db('customData').collection('users').updateOne( + { + userId: user.id + }, + { + $set: { + userId: user.id, + createdAt: new Date(), + roles: ['subscriber'], + } + }, + { + upsert: true, + } + ) + }, + }, + debug: true, + } +} \ No newline at end of file diff --git a/site/gatsby-site/package-lock.json b/site/gatsby-site/package-lock.json index d04fcad1d8..94c79c692d 100644 --- a/site/gatsby-site/package-lock.json +++ b/site/gatsby-site/package-lock.json @@ -12,6 +12,7 @@ "@apollo/client": "^3.7.8", "@apollo/server": "^4.10.2", "@as-integrations/aws-lambda": "^3.1.0", + "@auth/mongodb-adapter": "^3.7.3", "@aws-sdk/client-s3": "^3.633.0", "@billboard.js/react": "^1.0.1", "@bytemd/react": "^1.15.0", @@ -80,7 +81,8 @@ "mailersend": "^2.3.0", "md5": "^2.3.0", "minimatch": "^9.0.4", - "mongodb": "^6.4.0", + "mongodb": "^6.10.0", + "next-auth": "^4.24.10", "object-hash": "^3.0.0", "openapi-request-validator": "^12.1.3", "pluralize": "^8.0.0", @@ -735,6 +737,130 @@ "@apollo/server": "^4.0.0" } }, + "node_modules/@auth/core": { + "version": "0.34.2", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.34.2.tgz", + "integrity": "sha512-KywHKRgLiF3l7PLyL73fjLSIBe1YNcA6sMeew4yMP6cfCWGXZrkkXd32AjRi1hlJ9nvovUBGZHvbn+LijO6ZeQ==", + "optional": true, + "peer": true, + "dependencies": { + "@panva/hkdf": "^1.1.1", + "@types/cookie": "0.6.0", + "cookie": "0.6.0", + "jose": "^5.1.3", + "oauth4webapi": "^2.10.4", + "preact": "10.11.3", + "preact-render-to-string": "5.2.3" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "nodemailer": "^6.8.0" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/@auth/core/node_modules/@types/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-4Kh9a6B2bQciAhf7FSuMRRkUWecJgJu9nPnx3yzpsfXX/c50REIqpHY4C82bXP90qrLtXtkDxTZosYO3UpOwlA==", + "optional": true, + "peer": true + }, + "node_modules/@auth/core/node_modules/cookie": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.6.0.tgz", + "integrity": "sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==", + "optional": true, + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/@auth/core/node_modules/preact": { + "version": "10.11.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.11.3.tgz", + "integrity": "sha512-eY93IVpod/zG3uMF22Unl8h9KkrcKIRs2EGar8hwLZZDU1lkjph303V9HZBwufh2s736U6VXuhD109LYqPoffg==", + "optional": true, + "peer": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/preact" + } + }, + "node_modules/@auth/mongodb-adapter": { + "version": "3.7.3", + "resolved": "https://registry.npmjs.org/@auth/mongodb-adapter/-/mongodb-adapter-3.7.3.tgz", + "integrity": "sha512-OYr4crhiQAii6tsXtqk8GYkr176sMDB8qoWVse9B2NVJK4wCCki77iGImrgVjxKBbOVD58mUQRjNy4PbIRYEfA==", + "dependencies": { + "@auth/core": "0.37.3" + }, + "peerDependencies": { + "mongodb": "^6" + } + }, + "node_modules/@auth/mongodb-adapter/node_modules/@auth/core": { + "version": "0.37.3", + "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.37.3.tgz", + "integrity": "sha512-qcffDLwxB9iUYH8GHq68w/KU8jtjAbjjk9xnpoKhjX3+QcntaQ2MKVSkTTocmA6ElpL5vK2xR9CXfQ98dvGnyg==", + "dependencies": { + "@panva/hkdf": "^1.2.1", + "cookie": "1.0.1", + "jose": "^5.9.6", + "oauth4webapi": "^3.1.1", + "preact": "10.24.3", + "preact-render-to-string": "6.5.11" + }, + "peerDependencies": { + "@simplewebauthn/browser": "^9.0.1", + "@simplewebauthn/server": "^9.0.2", + "nodemailer": "^6.8.0" + }, + "peerDependenciesMeta": { + "@simplewebauthn/browser": { + "optional": true + }, + "@simplewebauthn/server": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/@auth/mongodb-adapter/node_modules/cookie": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.1.tgz", + "integrity": "sha512-Xd8lFX4LM9QEEwxQpF9J9NTUh8pmdJO0cyRJhFiDoLTk2eH8FXlRv2IFGYVadZpqI3j8fhNrSdKCeYPxiAhLXw==", + "engines": { + "node": ">=18" + } + }, + "node_modules/@auth/mongodb-adapter/node_modules/oauth4webapi": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.1.2.tgz", + "integrity": "sha512-KQZkNU+xn02lWrFu5Vjqg9E81yPtDSxUZorRHlLWVoojD+H/0GFbH59kcnz5Thdjj7c4/mYMBPj/mhvGe/kKXA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/@auth/mongodb-adapter/node_modules/preact-render-to-string": { + "version": "6.5.11", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz", + "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==", + "peerDependencies": { + "preact": ">=10" + } + }, "node_modules/@aws-crypto/crc32": { "version": "3.0.0", "license": "Apache-2.0", @@ -6142,6 +6268,16 @@ "ms": "^2.1.1" } }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/regexpp": { "version": "4.6.1", "license": "MIT", @@ -8805,6 +8941,367 @@ "version": "2.2.5", "license": "ISC" }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@imgix/gatsby": { "version": "2.1.3", "license": "BSD-2-Clause", @@ -10224,10 +10721,12 @@ } }, "node_modules/@netlify/functions": { - "version": "1.6.0", - "license": "MIT", + "version": "2.8.2", + "resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-2.8.2.tgz", + "integrity": "sha512-DeoAQh8LuNPvBE4qsKlezjKj0PyXDryOFJfJKo3Z1qZLKzQ21sT314KQKPVjfvw6knqijj+IO+0kHXy/TJiqNA==", + "dev": true, "dependencies": { - "is-promise": "^4.0.0" + "@netlify/serverless-functions-api": "1.26.1" }, "engines": { "node": ">=14.0.0" @@ -10250,17 +10749,6 @@ "unstorage": "1.9.0" } }, - "node_modules/@netlify/ipx/node_modules/@netlify/functions": { - "version": "2.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@netlify/serverless-functions-api": "1.19.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, "node_modules/@netlify/ipx/node_modules/fs-extra": { "version": "11.2.0", "dev": true, @@ -10276,8 +10764,9 @@ }, "node_modules/@netlify/node-cookies": { "version": "0.1.0", + "resolved": "https://registry.npmjs.org/@netlify/node-cookies/-/node-cookies-0.1.0.tgz", + "integrity": "sha512-OAs1xG+FfLX0LoRASpqzVntVV/RpYkgpI0VrUnw2u0Q1qiZUzcPffxRK8HF3gc4GjuhG5ahOEMJ9bswBiZPq0g==", "dev": true, - "license": "MIT", "engines": { "node": "^14.16.0 || >=16.0.0" } @@ -10320,6 +10809,18 @@ "common-tags": "^1.8.2" } }, + "node_modules/@netlify/plugin-gatsby/node_modules/@netlify/functions": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-1.6.0.tgz", + "integrity": "sha512-6G92AlcpFrQG72XU8YH8pg94eDnq7+Q0YJhb8x4qNpdGsvuzvrfHWBmqFGp/Yshmv4wex9lpsTRZOocdrA2erQ==", + "dev": true, + "dependencies": { + "is-promise": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/@netlify/plugin-gatsby/node_modules/ansi-styles": { "version": "4.3.0", "dev": true, @@ -10431,9 +10932,10 @@ "license": "ISC" }, "node_modules/@netlify/serverless-functions-api": { - "version": "1.19.1", + "version": "1.26.1", + "resolved": "https://registry.npmjs.org/@netlify/serverless-functions-api/-/serverless-functions-api-1.26.1.tgz", + "integrity": "sha512-q3L9i3HoNfz0SGpTIS4zTcKBbRkxzCRpd169eyiTuk3IwcPC3/85mzLHranlKo2b+HYT0gu37YxGB45aD8A3Tw==", "dev": true, - "license": "MIT", "dependencies": { "@netlify/node-cookies": "^0.1.0", "urlpattern-polyfill": "8.0.2" @@ -10444,8 +10946,143 @@ }, "node_modules/@netlify/serverless-functions-api/node_modules/urlpattern-polyfill": { "version": "8.0.2", - "dev": true, - "license": "MIT" + "resolved": "https://registry.npmjs.org/urlpattern-polyfill/-/urlpattern-polyfill-8.0.2.tgz", + "integrity": "sha512-Qp95D4TPJl1kC9SKigDcqgyM2VDVO4RiJc2d4qe5GrYm+zbIQCWWKAFaJNQ4BhdFeDGwBmAxqJBwWSJDb9T3BQ==", + "dev": true + }, + "node_modules/@next/env": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.0.3.tgz", + "integrity": "sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA==", + "peer": true + }, + "node_modules/@next/swc-darwin-arm64": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.0.3.tgz", + "integrity": "sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.0.3.tgz", + "integrity": "sha512-Zxl/TwyXVZPCFSf0u2BNj5sE0F2uR6iSKxWpq4Wlk/Sv9Ob6YCKByQTkV2y6BCic+fkabp9190hyrDdPA/dNrw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.0.3.tgz", + "integrity": "sha512-T5+gg2EwpsY3OoaLxUIofmMb7ohAUlcNZW0fPQ6YAutaWJaxt1Z1h+8zdl4FRIOr5ABAAhXtBcpkZNwUcKI2fw==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.0.3.tgz", + "integrity": "sha512-WkAk6R60mwDjH4lG/JBpb2xHl2/0Vj0ZRu1TIzWuOYfQ9tt9NFsIinI1Epma77JVgy81F32X/AeD+B2cBu/YQA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.0.3.tgz", + "integrity": "sha512-gWL/Cta1aPVqIGgDb6nxkqy06DkwJ9gAnKORdHWX1QBbSZZB+biFYPFti8aKIQL7otCE1pjyPaXpFzGeG2OS2w==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.0.3.tgz", + "integrity": "sha512-QQEMwFd8r7C0GxQS62Zcdy6GKx999I/rTO2ubdXEe+MlZk9ZiinsrjwoiBL5/57tfyjikgh6GOU2WRQVUej3UA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.0.3.tgz", + "integrity": "sha512-9TEp47AAd/ms9fPNgtgnT7F3M1Hf7koIYYWCMQ9neOwjbVWJsHZxrFbI3iEDJ8rf1TDGpmHbKxXf2IFpAvheIQ==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.0.3.tgz", + "integrity": "sha512-VNAz+HN4OGgvZs6MOoVfnn41kBzT+M+tB+OK4cww6DNyWS6wKaDpaAm/qLeOUbnMh0oVx1+mg0uoYARF69dJyA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", @@ -10483,6 +11120,14 @@ "node": ">= 8" } }, + "node_modules/@panva/hkdf": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@panva/hkdf/-/hkdf-1.2.1.tgz", + "integrity": "sha512-6oclG6Y3PiDFcoyk8srjLfVKyMfVCKJ27JwNPViuXziFpmdz+MZnZN/aKY0JGXgYuO/VghU0jcOAZgWXZ1Dmrw==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/@parcel/bundler-default": { "version": "2.8.3", "license": "MIT", @@ -11778,7 +12423,7 @@ "version": "1.47.2", "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.2.tgz", "integrity": "sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==", - "dev": true, + "devOptional": true, "dependencies": { "playwright": "1.47.2" }, @@ -13590,6 +14235,12 @@ "version": "3.1.2", "license": "MIT" }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==", + "peer": true + }, "node_modules/@swc/helpers": { "version": "0.4.36", "license": "Apache-2.0", @@ -16902,6 +17553,12 @@ "node": ">= 10" } }, + "node_modules/client-only": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", + "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==", + "peer": true + }, "node_modules/clipboardy": { "version": "4.0.0", "license": "MIT", @@ -22046,6 +22703,17 @@ "gatsby": "^5.10.0-alpha" } }, + "node_modules/gatsby-adapter-netlify/node_modules/@netlify/functions": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@netlify/functions/-/functions-1.6.0.tgz", + "integrity": "sha512-6G92AlcpFrQG72XU8YH8pg94eDnq7+Q0YJhb8x4qNpdGsvuzvrfHWBmqFGp/Yshmv4wex9lpsTRZOocdrA2erQ==", + "dependencies": { + "is-promise": "^4.0.0" + }, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/gatsby-adapter-netlify/node_modules/fs-extra": { "version": "11.2.0", "license": "MIT", @@ -26962,7 +27630,8 @@ }, "node_modules/is-promise": { "version": "4.0.0", - "license": "MIT" + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==" }, "node_modules/is-reference": { "version": "3.0.1", @@ -29103,9 +29772,9 @@ } }, "node_modules/jose": { - "version": "5.4.0", - "dev": true, - "license": "MIT", + "version": "5.9.6", + "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", + "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", "funding": { "url": "https://github.com/sponsors/panva" } @@ -32000,8 +32669,9 @@ "license": "MIT" }, "node_modules/mongodb": { - "version": "6.7.0", - "license": "Apache-2.0", + "version": "6.10.0", + "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.10.0.tgz", + "integrity": "sha512-gP9vduuYWb9ZkDM546M+MP2qKVk5ZG2wPF63OvSRuUbqCR+11ZCAE1mOfllhlAG0wcoJY5yDL/rV3OmYEwXIzg==", "dependencies": { "@mongodb-js/saslprep": "^1.1.5", "bson": "^6.7.0", @@ -34666,14 +35336,6 @@ "local-functions-proxy": "bin/local-functions-proxy" } }, - "node_modules/netlify-cli/node_modules/@netlify/node-cookies": { - "version": "0.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": "^14.16.0 || >=16.0.0" - } - }, "node_modules/netlify-cli/node_modules/@netlify/open-api": { "version": "2.34.0", "dev": true, @@ -34839,18 +35501,6 @@ "node": "^14.18.0 || >=16.0.0" } }, - "node_modules/netlify-cli/node_modules/@netlify/zip-it-and-ship-it/node_modules/@netlify/serverless-functions-api": { - "version": "1.22.0", - "dev": true, - "license": "MIT", - "dependencies": { - "@netlify/node-cookies": "^0.1.0", - "urlpattern-polyfill": "8.0.2" - }, - "engines": { - "node": ">=18.0.0" - } - }, "node_modules/netlify-cli/node_modules/@netlify/zip-it-and-ship-it/node_modules/brace-expansion": { "version": "2.0.1", "dev": true, @@ -46402,10 +47052,183 @@ "node": ">=12.22.0" } }, + "node_modules/next": { + "version": "15.0.3", + "resolved": "https://registry.npmjs.org/next/-/next-15.0.3.tgz", + "integrity": "sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==", + "peer": true, + "dependencies": { + "@next/env": "15.0.3", + "@swc/counter": "0.1.3", + "@swc/helpers": "0.5.13", + "busboy": "1.6.0", + "caniuse-lite": "^1.0.30001579", + "postcss": "8.4.31", + "styled-jsx": "5.1.6" + }, + "bin": { + "next": "dist/bin/next" + }, + "engines": { + "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" + }, + "optionalDependencies": { + "@next/swc-darwin-arm64": "15.0.3", + "@next/swc-darwin-x64": "15.0.3", + "@next/swc-linux-arm64-gnu": "15.0.3", + "@next/swc-linux-arm64-musl": "15.0.3", + "@next/swc-linux-x64-gnu": "15.0.3", + "@next/swc-linux-x64-musl": "15.0.3", + "@next/swc-win32-arm64-msvc": "15.0.3", + "@next/swc-win32-x64-msvc": "15.0.3", + "sharp": "^0.33.5" + }, + "peerDependencies": { + "@opentelemetry/api": "^1.1.0", + "@playwright/test": "^1.41.2", + "babel-plugin-react-compiler": "*", + "react": "^18.2.0 || 19.0.0-rc-66855b96-20241106", + "react-dom": "^18.2.0 || 19.0.0-rc-66855b96-20241106", + "sass": "^1.3.0" + }, + "peerDependenciesMeta": { + "@opentelemetry/api": { + "optional": true + }, + "@playwright/test": { + "optional": true + }, + "babel-plugin-react-compiler": { + "optional": true + }, + "sass": { + "optional": true + } + } + }, + "node_modules/next-auth": { + "version": "4.24.10", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.10.tgz", + "integrity": "sha512-8NGqiRO1GXBcVfV8tbbGcUgQkAGsX4GRzzXXea4lDikAsJtD5KiEY34bfhUOjHLvr6rT6afpcxw2H8EZqOV6aQ==", + "dependencies": { + "@babel/runtime": "^7.20.13", + "@panva/hkdf": "^1.0.2", + "cookie": "^0.7.0", + "jose": "^4.15.5", + "oauth": "^0.9.15", + "openid-client": "^5.4.0", + "preact": "^10.6.3", + "preact-render-to-string": "^5.1.19", + "uuid": "^8.3.2" + }, + "peerDependencies": { + "@auth/core": "0.34.2", + "next": "^12.2.5 || ^13 || ^14 || ^15", + "nodemailer": "^6.6.5", + "react": "^17.0.2 || ^18", + "react-dom": "^17.0.2 || ^18" + }, + "peerDependenciesMeta": { + "@auth/core": { + "optional": true + }, + "nodemailer": { + "optional": true + } + } + }, + "node_modules/next-auth/node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/next-auth/node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/next-tick": { "version": "1.1.0", "license": "ISC" }, + "node_modules/next/node_modules/@swc/helpers": { + "version": "0.5.13", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz", + "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==", + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/next/node_modules/detect-libc": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", + "integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==", + "optional": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/next/node_modules/semver": { + "version": "7.6.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", + "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", + "optional": true, + "peer": true, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/next/node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "optional": true, + "peer": true, + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, "node_modules/nice-try": { "version": "1.0.5", "license": "MIT" @@ -46932,6 +47755,11 @@ "node": ">=8" } }, + "node_modules/oauth": { + "version": "0.9.15", + "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", + "integrity": "sha512-a5ERWK1kh38ExDEfoO6qUHJb32rd7aYmPHuyCu3Fta/cnICvYmgd2uhuKXvPD+PXB+gCEYYEaQdIRAjCOwAKNA==" + }, "node_modules/oauth-sign": { "version": "0.9.0", "license": "Apache-2.0", @@ -46939,6 +47767,16 @@ "node": "*" } }, + "node_modules/oauth4webapi": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-2.17.0.tgz", + "integrity": "sha512-lbC0Z7uzAFNFyzEYRIC+pkSVvDHJTbEW+dYlSBAlCYDe6RxUkJ26bClhk8ocBZip1wfI9uKTe0fm4Ib4RHn6uQ==", + "optional": true, + "peer": true, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, "node_modules/object-assign": { "version": "4.1.1", "license": "MIT", @@ -47074,6 +47912,14 @@ "dev": true, "license": "MIT" }, + "node_modules/oidc-token-hash": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/oidc-token-hash/-/oidc-token-hash-5.0.3.tgz", + "integrity": "sha512-IF4PcGgzAr6XXSff26Sk/+P4KZFJVuHAJZj3wgO3vX2bMdNVp/QXTP3P7CEm9V1IdG8lDLY3HhiqpsE/nOwpPw==", + "engines": { + "node": "^10.13.0 || >=12.0.0" + } + }, "node_modules/omit.js": { "version": "2.0.2", "license": "MIT" @@ -47182,6 +48028,52 @@ "opencollective-postinstall": "index.js" } }, + "node_modules/openid-client": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.0.tgz", + "integrity": "sha512-4GCCGZt1i2kTHpwvaC/sCpTpQqDnBzDzuJcJMbH+y1Q5qI8U8RBvoSh28svarXszZHR5BAMXbJPX1PGPRE3VOA==", + "dependencies": { + "jose": "^4.15.9", + "lru-cache": "^6.0.0", + "object-hash": "^2.2.0", + "oidc-token-hash": "^5.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/jose": { + "version": "4.15.9", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.15.9.tgz", + "integrity": "sha512-1vUQX+IdDMVPj4k8kOxgUqlcK518yluMuGZwqlr44FS1ppZB/5GWh4rZG89erpOBOJjU/OBsnCVFfapsRz6nEA==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/openid-client/node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/openid-client/node_modules/object-hash": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-2.2.0.tgz", + "integrity": "sha512-gScRMn0bS5fH+IuwyIFgnh9zBdo4DV+6GhygmWM9HyNJSgS0hScp1f5vjtm7oIIOiT9trXrShAkLFSc2IqKNgw==", + "engines": { + "node": ">= 6" + } + }, + "node_modules/openid-client/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" + }, "node_modules/opentracing": { "version": "0.14.7", "license": "Apache-2.0", @@ -48225,7 +49117,7 @@ "version": "1.47.2", "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz", "integrity": "sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==", - "dev": true, + "devOptional": true, "dependencies": { "playwright-core": "1.47.2" }, @@ -48243,7 +49135,7 @@ "version": "1.47.2", "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.2.tgz", "integrity": "sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==", - "dev": true, + "devOptional": true, "bin": { "playwright-core": "cli.js" }, @@ -48879,13 +49771,30 @@ } }, "node_modules/preact": { - "version": "10.18.1", - "license": "MIT", + "version": "10.24.3", + "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", + "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" } }, + "node_modules/preact-render-to-string": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-5.2.3.tgz", + "integrity": "sha512-aPDxUn5o3GhWdtJtW0svRC2SS/l8D9MAgo2+AWml+BhDImb27ALf04Q2d+AHqUUOc6RdSXFIBVa2gxzgMKgtZA==", + "dependencies": { + "pretty-format": "^3.8.0" + }, + "peerDependencies": { + "preact": ">=10" + } + }, + "node_modules/preact-render-to-string/node_modules/pretty-format": { + "version": "3.8.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-3.8.0.tgz", + "integrity": "sha512-WuxUnVtlWL1OfZFQFuqvnvs6MiAGk9UNsBostyBOB0Is9wb5uRESevA6rnl/rkksXaGX3GzZhPup5d6Vp1nFew==" + }, "node_modules/prebuild-install": { "version": "7.1.1", "license": "MIT", @@ -53216,6 +54125,29 @@ "inline-style-parser": "0.1.1" } }, + "node_modules/styled-jsx": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/styled-jsx/-/styled-jsx-5.1.6.tgz", + "integrity": "sha512-qSVyDTeMotdvQYoHWLNGwRFJHC+i+ZvdBRYosOFgC+Wg1vx4frN2/RG/NA7SYqqvKNLf39P2LSRA2pu6n0XYZA==", + "peer": true, + "dependencies": { + "client-only": "0.0.1" + }, + "engines": { + "node": ">= 12.0.0" + }, + "peerDependencies": { + "react": ">= 16.8.0 || 17.x.x || ^18.0.0-0 || ^19.0.0-0" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "babel-plugin-macros": { + "optional": true + } + } + }, "node_modules/stylehacks": { "version": "5.1.1", "license": "MIT", diff --git a/site/gatsby-site/package.json b/site/gatsby-site/package.json index 61f9a360f1..1e7f6772c2 100644 --- a/site/gatsby-site/package.json +++ b/site/gatsby-site/package.json @@ -8,6 +8,7 @@ "@apollo/client": "^3.7.8", "@apollo/server": "^4.10.2", "@as-integrations/aws-lambda": "^3.1.0", + "@auth/mongodb-adapter": "^3.7.3", "@aws-sdk/client-s3": "^3.633.0", "@billboard.js/react": "^1.0.1", "@bytemd/react": "^1.15.0", @@ -76,7 +77,8 @@ "mailersend": "^2.3.0", "md5": "^2.3.0", "minimatch": "^9.0.4", - "mongodb": "^6.4.0", + "mongodb": "^6.10.0", + "next-auth": "^4.24.10", "object-hash": "^3.0.0", "openapi-request-validator": "^12.1.3", "pluralize": "^8.0.0", diff --git a/site/gatsby-site/server/context.ts b/site/gatsby-site/server/context.ts index b9f0f76a17..d3db05e50b 100644 --- a/site/gatsby-site/server/context.ts +++ b/site/gatsby-site/server/context.ts @@ -1,103 +1,19 @@ import { IncomingMessage } from "http"; import { MongoClient } from "mongodb"; -import config from "./config"; import * as reporter from "./reporter"; - - -function extractToken(header: string) { - - if (header && header!.startsWith('Bearer ')) { - - return header.substring(7); - } - - return null; -} - -export const verifyToken = async (token: string) => { - - const loginResponse = await fetch( - `https://realm.mongodb.com/api/admin/v3.0/auth/providers/mongodb-cloud/login`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - username: config.REALM_API_PUBLIC_KEY, - apiKey: config.REALM_API_PRIVATE_KEY, - }), - } - ); - - if (!loginResponse.ok) { - throw new Error(`Error login into admin api! \n\n ${await loginResponse.text()}`); - } - - const loginData = await loginResponse.json(); - - const response = await fetch( - `https://realm.mongodb.com/api/admin/v3.0/groups/${config.REALM_API_GROUP_ID}/apps/${config.REALM_API_APP_ID}/users/verify_token`, - { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'Authorization': `Bearer ${loginData.access_token}` - }, - body: JSON.stringify({ token }), - } - ); - - if (!response.ok) { - throw new Error(`Error verifying user token! \n\n ${await response.text()}`); - } - - return response.json(); -} - -async function getUser(userId: string, client: MongoClient) { - - const db = client.db('customData'); - - const collection = db.collection('users'); - - const userData = await collection.findOne({ userId }); - - return { - id: userId, - roles: userData?.roles, - } -} - -async function getUserFromHeader(header: string, client: MongoClient) { - - const token = extractToken(header); - - if (token) { - - const data = await verifyToken(token); - - if (data == 'token expired') { - - throw new Error('Token expired'); - } - - if (data.sub) { - - const userData = await getUser(data.sub, client); - - return userData; - } - } - - return null; -} +import { getServerSession } from 'next-auth' +import { getAuthConfig } from "../nextauth.config"; +import { createResponse } from '../src/utils/serverless' export const context = async ({ req, client }: { req: IncomingMessage, client: MongoClient }) => { try { - const user = await getUserFromHeader(req.headers.authorization!, client); + const authConfig = await getAuthConfig(); + const res = createResponse(); + const session = await getServerSession(req as any, res as any, authConfig as any); + + const user = session?.user ? { id: session.user.id, roles: session.user.roles } : null; return { user, req, client }; } diff --git a/site/gatsby-site/server/fields/common.ts b/site/gatsby-site/server/fields/common.ts index f853fe02e5..be805f3cf8 100644 --- a/site/gatsby-site/server/fields/common.ts +++ b/site/gatsby-site/server/fields/common.ts @@ -1,4 +1,4 @@ -import { MongoClient } from "mongodb"; +import { MongoClient, ObjectId } from "mongodb"; import templates from "../emails/templates"; import config from "../config"; import * as reporter from '../reporter'; @@ -90,18 +90,19 @@ export interface UserAdminData { userId?: string; } -export const getUserAdminData = async (userId: string) => { +export const getUserAdminData = async (userId: string, context: Context) => { - const userApiResponse = await apiRequest({ path: `/users/${userId}` }); + const authUsersCollection = context.client.db('auth').collection("users"); + const authUser = await authUsersCollection.findOne({ _id: new ObjectId(userId) }); const response: UserAdminData = {}; - if (userApiResponse.data) { + if (authUser) { - response.email = userApiResponse.data.email; - response.creationDate = new Date(userApiResponse.creation_date * 1000); - response.lastAuthenticationDate = new Date(userApiResponse.last_authentication_date * 1000); - response.disabled = userApiResponse.disabled; + response.email = authUser.email; + response.creationDate = new Date(); //TODO: find a way to get this data + response.lastAuthenticationDate = new Date(); //TODO: find a way to get this data + response.disabled = false; } return response; diff --git a/site/gatsby-site/server/types/user.ts b/site/gatsby-site/server/types/user.ts index b1b1ad12a6..e062a79dbd 100644 --- a/site/gatsby-site/server/types/user.ts +++ b/site/gatsby-site/server/types/user.ts @@ -29,7 +29,7 @@ export const UserType = new GraphQLObjectType({ if (user!.id === source.userId || user!.roles.includes('admin')) { - response = await getUserAdminData(source.userId) + response = await getUserAdminData(source.userId, context); } return response; diff --git a/site/gatsby-site/src/utils/serverless.ts b/site/gatsby-site/src/utils/serverless.ts new file mode 100644 index 0000000000..79530ee1ca --- /dev/null +++ b/site/gatsby-site/src/utils/serverless.ts @@ -0,0 +1,44 @@ +export const createResponse = () => { + + let responseHeaders = { 'Content-Type': 'application/json' } + let response = {} + + const res = { + getHeader: (name) => { + return responseHeaders[name] + }, + setHeader: (name, value) => { + if (name.toLowerCase() === 'set-cookie') { + response.multiValueHeaders = { + ...response.multiValueHeaders, + 'Set-Cookie': Array.isArray(value) ? value : [value] + } + } else { + responseHeaders[name] = Array.isArray(value) ? value.join(',') : value + response.headers = responseHeaders + } + }, + status: (statusCode) => { + response.statusCode = statusCode + return res + }, + send: (body) => { + response.body = typeof body === 'string' ? body : JSON.stringify(body) + }, + json: (json) => { + response.body = JSON.stringify(json) + }, + redirect: (statusOrUrl, url) => { + const statusCode = typeof statusOrUrl === 'number' ? statusOrUrl : 302 + const redirectUrl = typeof statusOrUrl === 'string' ? statusOrUrl : url + response.statusCode = statusCode + responseHeaders.Location = redirectUrl + response.headers = responseHeaders + }, + end: () => { }, + getResponse: () => response, + getResponseHeaders: () => responseHeaders, + } + + return res +} From 6a9d6ef5f86924d58cc48841568377297200abff Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Thu, 14 Nov 2024 12:07:43 -0300 Subject: [PATCH 19/99] Update components --- site/gatsby-site/src/components/cite/Tools.js | 18 ++++++------------ .../src/components/loginSignup/index.js | 8 +------- .../src/components/sidebar/index.js | 8 ++------ site/gatsby-site/src/pages/account.js | 2 +- 4 files changed, 10 insertions(+), 26 deletions(-) diff --git a/site/gatsby-site/src/components/cite/Tools.js b/site/gatsby-site/src/components/cite/Tools.js index 35e7ddb31e..34ae1f40d3 100644 --- a/site/gatsby-site/src/components/cite/Tools.js +++ b/site/gatsby-site/src/components/cite/Tools.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useState } from 'react'; import { faEdit, faPlus, @@ -27,17 +27,11 @@ function Tools({ isLiveData, setIsLiveData, }) { - const [isUserLoggedIn, setIsUserLoggedIn] = useState(false); - const [showRemoveDuplicateModal, setShowRemoveDuplicateModal] = useState(false); const { t } = useTranslation(); - const { isRole, user } = useUserContext(); - - useEffect(() => { - setIsUserLoggedIn(!!user?.profile.email); - }, [user]); + const { isRole, loading, user } = useUserContext(); return ( @@ -95,7 +89,7 @@ function Tools({ Discover - {isUserLoggedIn && isRole('incident_editor') && ( + {!loading && user && isRole('incident_editor') && ( <> )} - {isUserLoggedIn && isRole('incident_editor') && ( + {!loading && user && isRole('incident_editor') && ( - {isUserLoggedIn && (isRole('incident_editor') || isRole('taxonomy_editor')) && ( + {!loading && user && (isRole('incident_editor') || isRole('taxonomy_editor')) && (
{ const { user, loading } = useUserContext(); - const [isUserLoggedIn, setIsUserLoggedIn] = useState(false); - - useEffect(() => { - setIsUserLoggedIn(!!user?.profile.email); - }, [user]); - if (loading) return ; return ( <>
- {isUserLoggedIn ? : } + {user ? : }
); diff --git a/site/gatsby-site/src/components/sidebar/index.js b/site/gatsby-site/src/components/sidebar/index.js index c15ba84840..4525ce1946 100644 --- a/site/gatsby-site/src/components/sidebar/index.js +++ b/site/gatsby-site/src/components/sidebar/index.js @@ -88,13 +88,13 @@ const Sidebar = ({ defaultCollapsed = false, location = null, setNavCollapsed }) const { t } = useTranslation(); - const { user } = useUserContext(); + const { user, loading } = useUserContext(); const { isCollapsed, collapseMenu, manual, setManual } = useMenuContext(); const [isMobile, setIsMobile] = useState(false); - const [isUserLoggedIn, setIsUserLoggedIn] = useState(null); + const isUserLoggedIn = user && !loading; const [redirectTo, setRedirectTo] = useState('/'); @@ -130,10 +130,6 @@ const Sidebar = ({ defaultCollapsed = false, location = null, setNavCollapsed }) }; }, []); - useEffect(() => { - setIsUserLoggedIn(!!user?.profile.email); - }, [user]); - // We want the bottom edge of the sidebar // to rest at bottom edge of the viewport. // Since the sidebar has `position: sticky`, diff --git a/site/gatsby-site/src/pages/account.js b/site/gatsby-site/src/pages/account.js index 7b53e6212f..baa527beee 100644 --- a/site/gatsby-site/src/pages/account.js +++ b/site/gatsby-site/src/pages/account.js @@ -22,7 +22,7 @@ const Account = () => { Loading...
- ) : user && user.isLoggedIn && user.profile.email ? ( + ) : user && !loading ? ( <>

From 1b27150ac881d41307345102bcc65a3b949a6dc1 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Thu, 14 Nov 2024 12:40:22 -0300 Subject: [PATCH 20/99] Convert user context to typescript and remove Atlas calls --- site/gatsby-site/.eslintrc.json | 3 +- site/gatsby-site/gatsby-shared.js | 2 +- .../src/components/UserSubscriptions.js | 2 +- .../components/checklists/CheckListForm.js | 2 +- .../components/checklists/ChecklistsIndex.js | 2 +- .../src/components/cite/SimilarIncidents.js | 2 +- site/gatsby-site/src/components/cite/Tools.js | 2 +- .../src/components/discover/Actions.js | 2 +- .../src/components/entities/EntitiesTable.js | 2 +- .../forms/SubmissionWizard/StepThree.js | 2 +- .../forms/SubmissionWizard/StepTwo.js | 2 +- .../src/components/forms/SubmitForm.js | 2 +- .../components/incidents/IncidentEditModal.js | 2 +- .../components/incidents/IncidentsTable.js | 2 +- .../components/landing/NewsletterSignup.js | 2 +- .../src/components/loginSignup/index.js | 2 +- .../src/components/reports/ReportsTable.js | 2 +- .../src/components/sidebar/index.js | 2 +- .../submissions/SubmissionEditForm.js | 2 +- .../components/submissions/SubmissionList.js | 2 +- .../submissions/SubmissionWizard.js | 2 +- .../components/taxa/ClassificationsEditor.js | 2 +- .../src/components/users/UserEditModal.js | 2 +- .../src/components/users/UserForm.js | 2 +- .../src/components/variants/VariantList.js | 2 +- .../src/components/variants/VariantsTable.js | 2 +- site/gatsby-site/src/contexts/UserContext.tsx | 114 +++++++++ .../src/contexts/userContext/UserContext.js | 45 ---- .../userContext/UserContextProvider.js | 237 ------------------ .../src/contexts/userContext/index.js | 2 - site/gatsby-site/src/pages/account.js | 2 +- site/gatsby-site/src/pages/admin/index.js | 2 +- .../src/pages/apps/classifications.js | 2 +- site/gatsby-site/src/pages/apps/newsdigest.js | 2 +- site/gatsby-site/src/pages/apps/submitted.js | 2 +- site/gatsby-site/src/pages/cite/edit.js | 2 +- site/gatsby-site/src/pages/cite/history.js | 2 +- site/gatsby-site/src/pages/confirmemail.js | 2 +- site/gatsby-site/src/pages/forgotpassword.js | 2 +- site/gatsby-site/src/pages/incidents/edit.js | 2 +- .../src/pages/incidents/history.js | 2 +- site/gatsby-site/src/pages/incidents/new.js | 2 +- site/gatsby-site/src/pages/login.js | 2 +- site/gatsby-site/src/pages/logout.js | 2 +- site/gatsby-site/src/pages/resetpassword.js | 2 +- site/gatsby-site/src/pages/signup.js | 2 +- .../gatsby-site/src/templates/citeTemplate.js | 2 +- site/gatsby-site/src/templates/entity.js | 2 +- site/gatsby-site/src/templates/report.js | 2 +- site/gatsby-site/tsconfig.json | 10 +- 50 files changed, 166 insertions(+), 333 deletions(-) create mode 100644 site/gatsby-site/src/contexts/UserContext.tsx delete mode 100644 site/gatsby-site/src/contexts/userContext/UserContext.js delete mode 100644 site/gatsby-site/src/contexts/userContext/UserContextProvider.js delete mode 100644 site/gatsby-site/src/contexts/userContext/index.js diff --git a/site/gatsby-site/.eslintrc.json b/site/gatsby-site/.eslintrc.json index 3774112a0b..82d47d5154 100755 --- a/site/gatsby-site/.eslintrc.json +++ b/site/gatsby-site/.eslintrc.json @@ -16,6 +16,7 @@ "alias": { "map": [ ["components", "./src/components"], + ["contexts", "./src/contexts"], ["elements", "./src/elements"], ["hooks", "./src/hooks"], ["contexts", "./src/contexts"], @@ -27,7 +28,7 @@ ["plugins", "./plugins"], ["@reach/router", "@gatsbyjs/reach-router"] ], - "extensions": [".js", ".jsx", ".ts"] + "extensions": [".js", ".jsx", ".ts", ".tsx"] } } }, diff --git a/site/gatsby-site/gatsby-shared.js b/site/gatsby-site/gatsby-shared.js index 8733041c28..518c88041f 100644 --- a/site/gatsby-site/gatsby-shared.js +++ b/site/gatsby-site/gatsby-shared.js @@ -1,7 +1,7 @@ import React from 'react'; import { navigate } from 'gatsby'; import { QueryParamProvider } from 'use-query-params'; -import { UserContextProvider } from 'contexts/userContext'; +import { UserContextProvider } from 'contexts/UserContext'; import { MenuContextProvider } from 'contexts/MenuContext'; import { ToastContextProvider } from 'contexts/ToastContext'; import { Script } from 'gatsby'; diff --git a/site/gatsby-site/src/components/UserSubscriptions.js b/site/gatsby-site/src/components/UserSubscriptions.js index 2e77510297..58b3d70bef 100644 --- a/site/gatsby-site/src/components/UserSubscriptions.js +++ b/site/gatsby-site/src/components/UserSubscriptions.js @@ -9,7 +9,7 @@ import { } from '../graphql/subscriptions'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faTrash } from '@fortawesome/free-solid-svg-icons'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import Link from 'components/ui/Link'; import { SUBSCRIPTION_TYPE } from 'utils/subscriptions'; diff --git a/site/gatsby-site/src/components/checklists/CheckListForm.js b/site/gatsby-site/src/components/checklists/CheckListForm.js index 6f5c3687d3..b679b8a82e 100644 --- a/site/gatsby-site/src/components/checklists/CheckListForm.js +++ b/site/gatsby-site/src/components/checklists/CheckListForm.js @@ -21,7 +21,7 @@ import EditableLabel from 'components/checklists/EditableLabel'; import ExportDropdown from 'components/checklists/ExportDropdown'; import RiskSections from 'components/checklists/RiskSections'; import useToastContext, { SEVERITY } from '../../hooks/useToast'; -import { useUserContext } from '../../contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; export default function CheckListForm({ values, diff --git a/site/gatsby-site/src/components/checklists/ChecklistsIndex.js b/site/gatsby-site/src/components/checklists/ChecklistsIndex.js index e2e2567f1c..be1e62cb66 100644 --- a/site/gatsby-site/src/components/checklists/ChecklistsIndex.js +++ b/site/gatsby-site/src/components/checklists/ChecklistsIndex.js @@ -16,7 +16,7 @@ import gql from 'graphql-tag'; import { format, parseISO } from 'date-fns'; import CardSkeleton from 'elements/Skeletons/Card'; -import { useUserContext } from '../../contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import ExportDropdown from 'components/checklists/ExportDropdown'; import { DeleteButton, diff --git a/site/gatsby-site/src/components/cite/SimilarIncidents.js b/site/gatsby-site/src/components/cite/SimilarIncidents.js index c7b0496341..68b68f029b 100644 --- a/site/gatsby-site/src/components/cite/SimilarIncidents.js +++ b/site/gatsby-site/src/components/cite/SimilarIncidents.js @@ -7,7 +7,7 @@ import { fill } from '@cloudinary/base/actions/resize'; import { useMutation, useQuery } from '@apollo/client/react/hooks'; import { FIND_FULL_INCIDENT, FLAG_INCIDENT_SIMILARITY } from '../../graphql/incidents'; import md5 from 'md5'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import useToastContext, { SEVERITY } from '../../hooks/useToast'; import { useLogIncidentHistory } from '../../hooks/useLogIncidentHistory'; import Button from '../../elements/Button'; diff --git a/site/gatsby-site/src/components/cite/Tools.js b/site/gatsby-site/src/components/cite/Tools.js index 34ae1f40d3..4143498019 100644 --- a/site/gatsby-site/src/components/cite/Tools.js +++ b/site/gatsby-site/src/components/cite/Tools.js @@ -8,7 +8,7 @@ import { faClockRotateLeft, } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { format } from 'date-fns'; import Card from 'elements/Card'; import { Button, ToggleSwitch } from 'flowbite-react'; diff --git a/site/gatsby-site/src/components/discover/Actions.js b/site/gatsby-site/src/components/discover/Actions.js index 9376bd220a..54a4124c6a 100644 --- a/site/gatsby-site/src/components/discover/Actions.js +++ b/site/gatsby-site/src/components/discover/Actions.js @@ -15,7 +15,7 @@ import { useMutation, useQuery } from '@apollo/client'; import { Trans, useTranslation } from 'react-i18next'; import CustomButton from '../../elements/Button'; import { Modal } from 'flowbite-react'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { useLogReportHistory } from '../../hooks/useLogReportHistory'; import { getUnixTime } from 'date-fns'; import useLocalizePath from 'components/i18n/useLocalizePath'; diff --git a/site/gatsby-site/src/components/entities/EntitiesTable.js b/site/gatsby-site/src/components/entities/EntitiesTable.js index 88a3e2980c..4d3d7dd205 100644 --- a/site/gatsby-site/src/components/entities/EntitiesTable.js +++ b/site/gatsby-site/src/components/entities/EntitiesTable.js @@ -6,7 +6,7 @@ import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { Trans, useTranslation } from 'react-i18next'; import Table, { DefaultColumnFilter, DefaultColumnHeader } from 'components/ui/Table'; import { Button } from 'flowbite-react'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import useLocalizePath from 'components/i18n/useLocalizePath'; function IncidentsCell({ cell }) { diff --git a/site/gatsby-site/src/components/forms/SubmissionWizard/StepThree.js b/site/gatsby-site/src/components/forms/SubmissionWizard/StepThree.js index 66ce40eddc..bb7983cfb6 100644 --- a/site/gatsby-site/src/components/forms/SubmissionWizard/StepThree.js +++ b/site/gatsby-site/src/components/forms/SubmissionWizard/StepThree.js @@ -7,7 +7,7 @@ import * as yup from 'yup'; import StepContainer from './StepContainer'; import { graphql, useStaticQuery } from 'gatsby'; import TagsInputGroup from '../TagsInputGroup'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import FieldContainer from './FieldContainer'; import { faHandPointRight, diff --git a/site/gatsby-site/src/components/forms/SubmissionWizard/StepTwo.js b/site/gatsby-site/src/components/forms/SubmissionWizard/StepTwo.js index 9c7089bdd1..d34be436f6 100644 --- a/site/gatsby-site/src/components/forms/SubmissionWizard/StepTwo.js +++ b/site/gatsby-site/src/components/forms/SubmissionWizard/StepTwo.js @@ -12,7 +12,7 @@ import PreviewImageInputGroup from 'components/forms/PreviewImageInputGroup'; import FieldContainer from './FieldContainer'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faMedal, faImage, faLanguage } from '@fortawesome/free-solid-svg-icons'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { debounce } from 'debounce'; import SubmissionButton from './SubmissionButton'; diff --git a/site/gatsby-site/src/components/forms/SubmitForm.js b/site/gatsby-site/src/components/forms/SubmitForm.js index d736e16ce1..0e296648cc 100644 --- a/site/gatsby-site/src/components/forms/SubmitForm.js +++ b/site/gatsby-site/src/components/forms/SubmitForm.js @@ -9,7 +9,7 @@ import { NumericArrayParam, } from 'use-query-params'; import Link from 'components/ui/Link'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import useToastContext, { SEVERITY } from '../../hooks/useToast'; import { format, parse, getUnixTime } from 'date-fns'; import { useMutation, useQuery } from '@apollo/client'; diff --git a/site/gatsby-site/src/components/incidents/IncidentEditModal.js b/site/gatsby-site/src/components/incidents/IncidentEditModal.js index 552d96e316..be5cf64f1e 100644 --- a/site/gatsby-site/src/components/incidents/IncidentEditModal.js +++ b/site/gatsby-site/src/components/incidents/IncidentEditModal.js @@ -10,7 +10,7 @@ import { LocalizedLink } from 'plugins/gatsby-theme-i18n'; import { useTranslation, Trans } from 'react-i18next'; import { processEntities } from '../../utils/entities'; import { getUnixTime } from 'date-fns'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { useLogIncidentHistory } from '../../hooks/useLogIncidentHistory'; export default function IncidentEditModal({ show, onClose, incidentId }) { diff --git a/site/gatsby-site/src/components/incidents/IncidentsTable.js b/site/gatsby-site/src/components/incidents/IncidentsTable.js index 6ea9c220e7..513cd31690 100644 --- a/site/gatsby-site/src/components/incidents/IncidentsTable.js +++ b/site/gatsby-site/src/components/incidents/IncidentsTable.js @@ -1,4 +1,4 @@ -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import React, { useState } from 'react'; import { useFilters, usePagination, useSortBy, useTable } from 'react-table'; import IncidentEditModal from './IncidentEditModal'; diff --git a/site/gatsby-site/src/components/landing/NewsletterSignup.js b/site/gatsby-site/src/components/landing/NewsletterSignup.js index 40673b673a..d83886b19a 100644 --- a/site/gatsby-site/src/components/landing/NewsletterSignup.js +++ b/site/gatsby-site/src/components/landing/NewsletterSignup.js @@ -7,7 +7,7 @@ import { Trans, useTranslation } from 'react-i18next'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faEnvelope } from '@fortawesome/free-solid-svg-icons'; import useToastContext, { SEVERITY } from 'hooks/useToast'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { StringParam, useQueryParams } from 'use-query-params'; import Card from 'elements/Card'; import Envelope from '../../images/neural-net-envelope.png'; diff --git a/site/gatsby-site/src/components/loginSignup/index.js b/site/gatsby-site/src/components/loginSignup/index.js index a275939c8f..f08434be52 100644 --- a/site/gatsby-site/src/components/loginSignup/index.js +++ b/site/gatsby-site/src/components/loginSignup/index.js @@ -1,8 +1,8 @@ -import { useUserContext } from 'contexts/userContext'; import { Button } from 'flowbite-react'; import React, { useEffect, useState } from 'react'; import { Trans } from 'react-i18next'; import useLocalizePath from 'components/i18n/useLocalizePath'; +import { useUserContext } from 'contexts/UserContext'; const LoginSignup = ({ className = '', location = null }) => { const { user, loading } = useUserContext(); diff --git a/site/gatsby-site/src/components/reports/ReportsTable.js b/site/gatsby-site/src/components/reports/ReportsTable.js index ce1f4eabbe..c189c01dae 100644 --- a/site/gatsby-site/src/components/reports/ReportsTable.js +++ b/site/gatsby-site/src/components/reports/ReportsTable.js @@ -1,4 +1,4 @@ -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import React from 'react'; import { useFilters, usePagination, useSortBy, useTable } from 'react-table'; import { Trans, useTranslation } from 'react-i18next'; diff --git a/site/gatsby-site/src/components/sidebar/index.js b/site/gatsby-site/src/components/sidebar/index.js index 4525ce1946..fa23839508 100644 --- a/site/gatsby-site/src/components/sidebar/index.js +++ b/site/gatsby-site/src/components/sidebar/index.js @@ -7,7 +7,7 @@ import { Trans, useTranslation } from 'react-i18next'; import useLocalizePath from 'components/i18n/useLocalizePath'; import { faChevronLeft } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { useMenuContext } from 'contexts/MenuContext'; import { graphql, useStaticQuery } from 'gatsby'; diff --git a/site/gatsby-site/src/components/submissions/SubmissionEditForm.js b/site/gatsby-site/src/components/submissions/SubmissionEditForm.js index 0e3afc7039..c8a2849445 100644 --- a/site/gatsby-site/src/components/submissions/SubmissionEditForm.js +++ b/site/gatsby-site/src/components/submissions/SubmissionEditForm.js @@ -19,7 +19,7 @@ import { import { debounce } from 'debounce'; import { STATUS } from 'utils/submissions'; import StepContainer from 'components/forms/SubmissionWizard/StepContainer'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { UPSERT_SUBSCRIPTION } from '../../graphql/subscriptions'; import { SUBSCRIPTION_TYPE } from 'utils/subscriptions'; import isEmpty from 'lodash/isEmpty'; diff --git a/site/gatsby-site/src/components/submissions/SubmissionList.js b/site/gatsby-site/src/components/submissions/SubmissionList.js index 3143418fad..2d55718330 100644 --- a/site/gatsby-site/src/components/submissions/SubmissionList.js +++ b/site/gatsby-site/src/components/submissions/SubmissionList.js @@ -1,7 +1,7 @@ import React, { useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; import { Badge, Button, Select } from 'flowbite-react'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { useBlockLayout, useFilters, diff --git a/site/gatsby-site/src/components/submissions/SubmissionWizard.js b/site/gatsby-site/src/components/submissions/SubmissionWizard.js index 404d4e7884..cb7d023d94 100644 --- a/site/gatsby-site/src/components/submissions/SubmissionWizard.js +++ b/site/gatsby-site/src/components/submissions/SubmissionWizard.js @@ -5,7 +5,7 @@ import { getCloudinaryPublicID } from 'utils/cloudinary'; import StepOne from '../forms/SubmissionWizard/StepOne'; import StepTwo from '../forms/SubmissionWizard/StepTwo'; import StepThree from '../forms/SubmissionWizard/StepThree'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; const SubmissionWizard = ({ submitForm, diff --git a/site/gatsby-site/src/components/taxa/ClassificationsEditor.js b/site/gatsby-site/src/components/taxa/ClassificationsEditor.js index bce61f279d..7d38e3a09b 100644 --- a/site/gatsby-site/src/components/taxa/ClassificationsEditor.js +++ b/site/gatsby-site/src/components/taxa/ClassificationsEditor.js @@ -3,7 +3,7 @@ import { getTaxonomies } from 'utils/cite'; import Row from '../../elements/Row'; import Col from '../../elements/Col'; import Taxonomy from './Taxonomy'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { Button, Dropdown, Spinner } from 'flowbite-react'; import { Trans, useTranslation } from 'react-i18next'; import { FIND_CLASSIFICATION } from '../../graphql/classifications'; diff --git a/site/gatsby-site/src/components/users/UserEditModal.js b/site/gatsby-site/src/components/users/UserEditModal.js index 46929e4392..0c692b3e3c 100644 --- a/site/gatsby-site/src/components/users/UserEditModal.js +++ b/site/gatsby-site/src/components/users/UserEditModal.js @@ -9,7 +9,7 @@ import DefaultSkeleton from 'elements/Skeletons/Default'; import SubmitButton from 'components/ui/SubmitButton'; import useToastContext, { SEVERITY } from 'hooks/useToast'; import lodash from 'lodash'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; const supportedRoles = [ { name: 'admin', description: 'All permissions' }, diff --git a/site/gatsby-site/src/components/users/UserForm.js b/site/gatsby-site/src/components/users/UserForm.js index 3cf5122ce2..a89799ac61 100644 --- a/site/gatsby-site/src/components/users/UserForm.js +++ b/site/gatsby-site/src/components/users/UserForm.js @@ -4,7 +4,7 @@ import * as yup from 'yup'; import TagsControl from 'components/forms/TagsControl'; import { Label, TextInput } from 'flowbite-react'; import { useTranslation } from 'react-i18next'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; // Schema for yup export const schema = yup.object().shape({ diff --git a/site/gatsby-site/src/components/variants/VariantList.js b/site/gatsby-site/src/components/variants/VariantList.js index 0606b2bcdd..de53b0f750 100644 --- a/site/gatsby-site/src/components/variants/VariantList.js +++ b/site/gatsby-site/src/components/variants/VariantList.js @@ -13,7 +13,7 @@ import { } from 'utils/variants'; import { sortIncidentsByDatePublished } from 'utils/cite'; import VariantForm, { schema } from 'components/variants/VariantForm'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import VariantEditModal from './VariantEditModal'; import { Formik } from 'formik'; import { useLazyQuery, useMutation } from '@apollo/client'; diff --git a/site/gatsby-site/src/components/variants/VariantsTable.js b/site/gatsby-site/src/components/variants/VariantsTable.js index 592479631c..ba2c2cf91d 100644 --- a/site/gatsby-site/src/components/variants/VariantsTable.js +++ b/site/gatsby-site/src/components/variants/VariantsTable.js @@ -1,4 +1,4 @@ -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import React, { useState } from 'react'; import Markdown from 'react-markdown'; import { getUnixTime } from 'date-fns'; diff --git a/site/gatsby-site/src/contexts/UserContext.tsx b/site/gatsby-site/src/contexts/UserContext.tsx new file mode 100644 index 0000000000..af666d83c2 --- /dev/null +++ b/site/gatsby-site/src/contexts/UserContext.tsx @@ -0,0 +1,114 @@ +import React, { createContext, useContext, useEffect, useState } from 'react'; +import { ApolloProvider, ApolloClient, HttpLink, InMemoryCache, ApolloLink } from '@apollo/client'; +import { useTranslation } from 'react-i18next'; +import { removeTypenameFromVariables } from '@apollo/client/link/remove-typename'; +import { signOut, useSession } from "next-auth/react"; + +interface User { + roles?: string[]; + [key: string]: any; +} + +interface UserContextValue { + loading: boolean; + user: User | null; + isRole: (role: string) => boolean; + isAdmin: boolean; + actions: { + logout: () => Promise; + }; +} + +interface CustomDataMock { + roles: string[]; +} + +const client = new ApolloClient({ + link: ApolloLink.from([ + removeTypenameFromVariables(), + new HttpLink({ + uri: '/api/graphql', + }), + ]), + cache: new InMemoryCache({ + typePolicies: { + Incident: { + keyFields: ['incident_id'], + fields: { + reports: { + merge(existing: any[], incoming: any[] = []) { + return [...incoming]; + }, + }, + }, + }, + Report: { + keyFields: ['report_number'], + }, + User: { + keyFields: ['userId'], + }, + }, + }), +}); + +interface UserContextProviderProps { + children: React.ReactNode; +} + +export const UserContext = React.createContext({ + loading: true, + user: null, + isRole: () => false, + isAdmin: false, + actions: { + logout: async () => { }, + }, +}); + +export const useUserContext = () => useContext(UserContext); + +export const UserContextProvider: React.FC = ({ children }) => { + const { data: session } = useSession(); + const [loading, setLoading] = useState(true); + const [user, setUser] = useState({}); + const { t } = useTranslation(); + + const logout = async (): Promise => { + await signOut(); + }; + + useEffect(() => { + if (session === null) { + setUser(null); + setLoading(false); + } + + if (session?.user) { + setUser(session.user as User); + setLoading(false); + } + }, [session]); + + const contextValue: UserContextValue = { + loading, + user, + isRole(role: string): boolean { + if (typeof window !== 'undefined' && window.localStorage.getItem('__CUSTOM_DATA_MOCK')) { + const customData: CustomDataMock = JSON.parse(window.localStorage.getItem('__CUSTOM_DATA_MOCK') || '{}'); + return customData.roles.includes('admin') || customData.roles.includes(role); + } + return Boolean(user?.roles?.includes('admin') || user?.roles?.includes(role)); + }, + isAdmin: Boolean(user?.roles?.includes('admin')), + actions: { + logout, + }, + }; + + return ( + + {children} + + ); +}; \ No newline at end of file diff --git a/site/gatsby-site/src/contexts/userContext/UserContext.js b/site/gatsby-site/src/contexts/userContext/UserContext.js deleted file mode 100644 index a0d0cee8da..0000000000 --- a/site/gatsby-site/src/contexts/userContext/UserContext.js +++ /dev/null @@ -1,45 +0,0 @@ -import { createContext, useContext } from 'react'; - -export const UserContext = createContext({ - loading: true, - user: undefined, - isAdmin: undefined, - isLoggedIn: undefined, - isRole(role) { - role; - return false; - }, - actions: { - // Dummy functions, will be replaced by actual functions in UserContextProvider.js - loginWithEmail: ({ email, password, redirectTo }) => { - email; - password; - redirectTo; - }, - loginWithFacebook: ({ loginRedirectUri, redirectTo }) => { - loginRedirectUri; - redirectTo; - }, - logout: () => {}, - sendResetPasswordEmail: ({ email }) => { - email; - }, - resetPassword: ({ password, token, tokenId }) => { - password; - token; - tokenId; - }, - signUp: ({ email, password, redirectTo }) => { - email; - password; - redirectTo; - }, - confirmEmail: ({ token, tokenId }) => { - token; - tokenId; - return new Promise(() => {}); - }, - }, -}); - -export const useUserContext = () => useContext(UserContext); diff --git a/site/gatsby-site/src/contexts/userContext/UserContextProvider.js b/site/gatsby-site/src/contexts/userContext/UserContextProvider.js deleted file mode 100644 index 9766aec582..0000000000 --- a/site/gatsby-site/src/contexts/userContext/UserContextProvider.js +++ /dev/null @@ -1,237 +0,0 @@ -import React, { useEffect, useRef, useState } from 'react'; -import * as Realm from 'realm-web'; -import { realmApp } from '../../services/realmApp'; -import { UserContext } from './UserContext'; -import { ApolloProvider, ApolloClient, HttpLink, InMemoryCache, ApolloLink } from '@apollo/client'; -import fetch from 'cross-fetch'; -import useToastContext, { SEVERITY } from '../../hooks/useToast'; -import { Trans, useTranslation } from 'react-i18next'; -import { navigate } from 'gatsby'; -import useLocalizePath from '../../components/i18n/useLocalizePath'; -import { removeTypenameFromVariables } from '@apollo/client/link/remove-typename'; -import CustomButton from '../../elements/Button'; - -// https://github.com/mongodb-university/realm-graphql-apollo-react/blob/master/src/index.js - -const getApolloCLient = (getValidAccessToken) => - new ApolloClient({ - link: ApolloLink.from([ - removeTypenameFromVariables(), - new HttpLink({ - uri: '/api/graphql', - fetch: async (uri, options) => { - const accessToken = await getValidAccessToken(); - - options.headers.Authorization = `Bearer ${accessToken}`; - return fetch(uri, options); - }, - }), - ]), - cache: new InMemoryCache({ - typePolicies: { - Incident: { - keyFields: ['incident_id'], - fields: { - reports: { - merge(existing, incoming = []) { - return [...incoming]; - }, - }, - }, - }, - Report: { - keyFields: ['report_number'], - }, - User: { - keyFields: ['userId'], - }, - }, - }), - }); - -export const UserContextProvider = ({ children }) => { - const [loading, setLoading] = useState(true); - - const [user, setUser] = useState(realmApp.currentUser); - - const { t } = useTranslation(); - - const localizePath = useLocalizePath(); - - const addToast = useToastContext(); - - const logout = async () => { - await realmApp.currentUser.logOut(); - - //TODO: functions need at least an anon user to work - await login(); - }; - - const login = async ({ - email = null, - password = null, - provider = null, - loginRedirectUri = null, - redirectTo = null, - } = {}) => { - try { - setLoading(true); - - let credentials = null; - - if (email && password) { - credentials = Realm.Credentials.emailPassword(email, password); - } else if (provider === 'facebook' && loginRedirectUri) { - credentials = Realm.Credentials.facebook(loginRedirectUri); - } else { - credentials = Realm.Credentials.anonymous(); - } - - const user = await realmApp.logIn(credentials); - - if (redirectTo) { - window.location.href = localizePath({ path: redirectTo }); - } - - if (user.id === realmApp.currentUser.id) { - setUser(user); - } - return true; - } catch (e) { - setLoading(false); - - addToast({ - message: ( - <> - - {e.statusCode === 401 && e.error == 'confirmation required' && ( - onResendConfirmationClick(email)} - className="underline text-sm pl-0 border-0" - > - Resend Verification email - - )} - - ), - severity: SEVERITY.danger, - error: e, - }); - return false; - } - }; - - const onResendConfirmationClick = async (email) => { - try { - await realmApp.emailPasswordAuth.retryCustomConfirmation(email); - - addToast({ - message: `${t('Verification email sent to')} ${email}`, - severity: SEVERITY.success, - }); - } catch (e) { - addToast({ - message: ( - - ), - severity: SEVERITY.danger, - error: e, - }); - } - }; - - const loginWithEmail = async ({ email, password, redirectTo }) => { - return await login({ email, password, redirectTo }); - }; - - const loginWithFacebook = async ({ loginRedirectUri, redirectTo }) => { - await login({ provider: 'facebook', loginRedirectUri, redirectTo }); - }; - - const sendResetPasswordEmail = async ({ email }) => { - // Pass a dummy valid password. It won't be used to reset the password - return realmApp.emailPasswordAuth.callResetPasswordFunction(email, '123456'); - }; - - const resetPassword = async ({ password, token, tokenId }) => { - return realmApp.emailPasswordAuth.resetPassword(token, tokenId, password); - }; - - const signUp = async ({ email, password, redirectTo }) => { - await realmApp.emailPasswordAuth.registerUser(email, password); - if (redirectTo) { - navigate(localizePath({ path: redirectTo })); - } - }; - - const confirmEmail = async ({ token, tokenId }) => { - return realmApp.emailPasswordAuth.confirmUser({ token, tokenId }); - }; - - const getValidAccessToken = async () => { - if (!realmApp.currentUser) { - await login(); - } else { - await realmApp.currentUser.refreshCustomData(); - } - - return realmApp.currentUser.accessToken; - }; - - const client = useRef(getApolloCLient(getValidAccessToken)).current; - - useEffect(() => { - async function checkUser() { - if (!user || !user.isLoggedIn) { - await login(); - } - - setLoading(false); - } - - checkUser(); - }, [user]); - - return ( - - {children} - - ); -}; diff --git a/site/gatsby-site/src/contexts/userContext/index.js b/site/gatsby-site/src/contexts/userContext/index.js deleted file mode 100644 index 9f12bb53c8..0000000000 --- a/site/gatsby-site/src/contexts/userContext/index.js +++ /dev/null @@ -1,2 +0,0 @@ -export { useUserContext } from './UserContext'; -export { UserContextProvider } from './UserContextProvider'; diff --git a/site/gatsby-site/src/pages/account.js b/site/gatsby-site/src/pages/account.js index baa527beee..294d1292c1 100644 --- a/site/gatsby-site/src/pages/account.js +++ b/site/gatsby-site/src/pages/account.js @@ -1,6 +1,6 @@ import React from 'react'; import { Spinner } from 'flowbite-react'; -import { useUserContext } from '../contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { Trans, useTranslation } from 'react-i18next'; import Link from 'components/ui/Link'; import UserSubscriptions from 'components/UserSubscriptions'; diff --git a/site/gatsby-site/src/pages/admin/index.js b/site/gatsby-site/src/pages/admin/index.js index 6a1f9a79b0..19b793e8c2 100644 --- a/site/gatsby-site/src/pages/admin/index.js +++ b/site/gatsby-site/src/pages/admin/index.js @@ -3,7 +3,7 @@ import { FIND_USERS } from '../../graphql/users'; import { useQuery } from '@apollo/client/react'; import UsersTable from 'components/users/UsersTable'; import ListSkeleton from 'elements/Skeletons/List'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { Button } from 'flowbite-react'; import { Trans } from 'react-i18next'; import { useLocalization } from 'plugins/gatsby-theme-i18n'; diff --git a/site/gatsby-site/src/pages/apps/classifications.js b/site/gatsby-site/src/pages/apps/classifications.js index 132f58f642..66d8fc26e1 100644 --- a/site/gatsby-site/src/pages/apps/classifications.js +++ b/site/gatsby-site/src/pages/apps/classifications.js @@ -8,7 +8,7 @@ import Link from '../../components/ui/Link'; import { faExpandAlt } from '@fortawesome/free-solid-svg-icons'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { useModal, CustomModal } from '../../hooks/useModal'; -import { useUserContext } from '../../contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import ListSkeleton from 'elements/Skeletons/List'; import { Modal } from 'flowbite-react'; import { Trans, useTranslation } from 'react-i18next'; diff --git a/site/gatsby-site/src/pages/apps/newsdigest.js b/site/gatsby-site/src/pages/apps/newsdigest.js index 9fa0c6f40d..70ccb492dd 100644 --- a/site/gatsby-site/src/pages/apps/newsdigest.js +++ b/site/gatsby-site/src/pages/apps/newsdigest.js @@ -14,7 +14,7 @@ import { faBolt, } from '@fortawesome/free-solid-svg-icons'; import HeadContent from 'components/HeadContent'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import CardSkeleton from 'elements/Skeletons/Card'; import { useLocalization } from 'plugins/gatsby-theme-i18n'; import useLocalizePath from 'components/i18n/useLocalizePath'; diff --git a/site/gatsby-site/src/pages/apps/submitted.js b/site/gatsby-site/src/pages/apps/submitted.js index a1c5a64208..ec92a87d0e 100644 --- a/site/gatsby-site/src/pages/apps/submitted.js +++ b/site/gatsby-site/src/pages/apps/submitted.js @@ -3,7 +3,7 @@ import HeadContent from '../../components/HeadContent'; import { ObjectId } from 'bson'; import { useMutation, useQuery } from '@apollo/client'; import { DELETE_QUICKADD, FIND_QUICKADD } from '../../graphql/quickadd.js'; -import { useUserContext } from '../../contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import SubmissionListWrapper from '../../components/submissions/SubmissionListWrapper'; import useToastContext, { SEVERITY } from '../../hooks/useToast'; import { Trans, useTranslation } from 'react-i18next'; diff --git a/site/gatsby-site/src/pages/cite/edit.js b/site/gatsby-site/src/pages/cite/edit.js index bede048597..9a09daec16 100644 --- a/site/gatsby-site/src/pages/cite/edit.js +++ b/site/gatsby-site/src/pages/cite/edit.js @@ -3,7 +3,7 @@ import IncidentReportForm, { schema } from '../../components/forms/IncidentRepor import { NumberParam, useQueryParam, withDefault } from 'use-query-params'; import useToastContext, { SEVERITY } from '../../hooks/useToast'; import { useLogReportHistory } from '../../hooks/useLogReportHistory'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { Spinner, Button } from 'flowbite-react'; import { UPDATE_REPORT, diff --git a/site/gatsby-site/src/pages/cite/history.js b/site/gatsby-site/src/pages/cite/history.js index b9f5fc2e7b..2163112f9d 100644 --- a/site/gatsby-site/src/pages/cite/history.js +++ b/site/gatsby-site/src/pages/cite/history.js @@ -16,7 +16,7 @@ import Link from 'components/ui/Link'; import ReportVersionViewModal from 'components/reports/ReportVersionViewModal'; import { Button, Spinner } from 'flowbite-react'; import CustomButton from 'elements/Button'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { useLogReportHistory } from '../../hooks/useLogReportHistory'; import useToastContext, { SEVERITY } from '../../hooks/useToast'; diff --git a/site/gatsby-site/src/pages/confirmemail.js b/site/gatsby-site/src/pages/confirmemail.js index 127592f63f..bf73e1deb2 100644 --- a/site/gatsby-site/src/pages/confirmemail.js +++ b/site/gatsby-site/src/pages/confirmemail.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { useUserContext } from '../contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { StringParam, useQueryParams } from 'use-query-params'; import Link from '../components/ui/Link'; import { Spinner } from 'flowbite-react'; diff --git a/site/gatsby-site/src/pages/forgotpassword.js b/site/gatsby-site/src/pages/forgotpassword.js index 1297af6c6a..89aa0a8c3a 100644 --- a/site/gatsby-site/src/pages/forgotpassword.js +++ b/site/gatsby-site/src/pages/forgotpassword.js @@ -1,5 +1,5 @@ import React from 'react'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import useToastContext, { SEVERITY } from '../hooks/useToast'; import { Form, Formik } from 'formik'; import * as Yup from 'yup'; diff --git a/site/gatsby-site/src/pages/incidents/edit.js b/site/gatsby-site/src/pages/incidents/edit.js index 48d2885af2..a8e1f83c34 100644 --- a/site/gatsby-site/src/pages/incidents/edit.js +++ b/site/gatsby-site/src/pages/incidents/edit.js @@ -13,7 +13,7 @@ import { Link } from 'gatsby'; import { processEntities } from '../../utils/entities'; import DefaultSkeleton from 'elements/Skeletons/Default'; import { getUnixTime } from 'date-fns'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { useLogIncidentHistory } from '../../hooks/useLogIncidentHistory'; function EditCitePage(props) { diff --git a/site/gatsby-site/src/pages/incidents/history.js b/site/gatsby-site/src/pages/incidents/history.js index 5467fcbb87..2fbd544b90 100644 --- a/site/gatsby-site/src/pages/incidents/history.js +++ b/site/gatsby-site/src/pages/incidents/history.js @@ -18,7 +18,7 @@ import { format, fromUnixTime, getUnixTime } from 'date-fns'; import { getIncidentChanges } from 'utils/cite'; import { StringDiff, DiffMethod } from 'react-string-diff'; import { Button, Spinner } from 'flowbite-react'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { useLogIncidentHistory } from '../../hooks/useLogIncidentHistory'; import useToastContext, { SEVERITY } from '../../hooks/useToast'; import { graphql } from 'gatsby'; diff --git a/site/gatsby-site/src/pages/incidents/new.js b/site/gatsby-site/src/pages/incidents/new.js index 5eb444c308..d4bb43f9d3 100644 --- a/site/gatsby-site/src/pages/incidents/new.js +++ b/site/gatsby-site/src/pages/incidents/new.js @@ -16,7 +16,7 @@ import { LocalizedLink, useLocalization } from 'plugins/gatsby-theme-i18n'; import { useTranslation, Trans } from 'react-i18next'; import { processEntities } from '../../utils/entities'; import DefaultSkeleton from 'elements/Skeletons/Default'; -import { useUserContext } from '../../contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { getUnixTime } from 'date-fns'; function NewIncidentPage() { diff --git a/site/gatsby-site/src/pages/login.js b/site/gatsby-site/src/pages/login.js index f52297ef54..ca707a2a57 100644 --- a/site/gatsby-site/src/pages/login.js +++ b/site/gatsby-site/src/pages/login.js @@ -1,6 +1,6 @@ import React, { useEffect, useState } from 'react'; import { Button, Spinner } from 'flowbite-react'; -import { useUserContext } from '../contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { Form, Formik } from 'formik'; import * as Yup from 'yup'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; diff --git a/site/gatsby-site/src/pages/logout.js b/site/gatsby-site/src/pages/logout.js index 221862c67e..f4bce822f0 100644 --- a/site/gatsby-site/src/pages/logout.js +++ b/site/gatsby-site/src/pages/logout.js @@ -1,6 +1,6 @@ import React, { useEffect } from 'react'; import { Spinner } from 'flowbite-react'; -import { useUserContext } from '../contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { navigate } from 'gatsby'; import { Trans } from 'react-i18next'; import useLocalizePath from '../components/i18n/useLocalizePath'; diff --git a/site/gatsby-site/src/pages/resetpassword.js b/site/gatsby-site/src/pages/resetpassword.js index e946dd399d..97701bc1b9 100644 --- a/site/gatsby-site/src/pages/resetpassword.js +++ b/site/gatsby-site/src/pages/resetpassword.js @@ -1,5 +1,5 @@ import React from 'react'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { StringParam, useQueryParams } from 'use-query-params'; import useToastContext, { SEVERITY } from '../hooks/useToast'; import { Form, Formik } from 'formik'; diff --git a/site/gatsby-site/src/pages/signup.js b/site/gatsby-site/src/pages/signup.js index f3726e0e1b..828d052988 100644 --- a/site/gatsby-site/src/pages/signup.js +++ b/site/gatsby-site/src/pages/signup.js @@ -1,6 +1,6 @@ import React, { useState } from 'react'; import { Button, Spinner } from 'flowbite-react'; -import { useUserContext } from '../contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import useToastContext, { SEVERITY } from '../hooks/useToast'; import { Form, Formik } from 'formik'; import * as Yup from 'yup'; diff --git a/site/gatsby-site/src/templates/citeTemplate.js b/site/gatsby-site/src/templates/citeTemplate.js index bad25f1c34..ffda31fa01 100644 --- a/site/gatsby-site/src/templates/citeTemplate.js +++ b/site/gatsby-site/src/templates/citeTemplate.js @@ -7,7 +7,7 @@ import ImageCarousel from 'components/cite/ImageCarousel'; import Timeline from '../components/visualizations/Timeline'; import IncidentStatsCard from '../components/cite/IncidentStatsCard'; import ReportCard from '../components/reports/ReportCard'; -import { useUserContext } from '../contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import SimilarIncidents from '../components/cite/SimilarIncidents'; import Card from '../elements/Card'; import Container from '../elements/Container'; diff --git a/site/gatsby-site/src/templates/entity.js b/site/gatsby-site/src/templates/entity.js index 44aa121579..4c7940a7f8 100644 --- a/site/gatsby-site/src/templates/entity.js +++ b/site/gatsby-site/src/templates/entity.js @@ -1,7 +1,7 @@ import EntityCard from 'components/entities/EntityCard'; import IncidentCard from 'components/incidents/IncidentCard'; import Link from 'components/ui/Link'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import { Button, Spinner } from 'flowbite-react'; import { graphql } from 'gatsby'; import useToastContext, { SEVERITY } from '../hooks/useToast'; diff --git a/site/gatsby-site/src/templates/report.js b/site/gatsby-site/src/templates/report.js index 15adfcb9d1..9df6c480a6 100644 --- a/site/gatsby-site/src/templates/report.js +++ b/site/gatsby-site/src/templates/report.js @@ -7,7 +7,7 @@ import { useLocalization } from 'plugins/gatsby-theme-i18n'; import { Link, graphql } from 'gatsby'; import ReportCard from 'components/reports/ReportCard'; import { Button } from 'flowbite-react'; -import { useUserContext } from 'contexts/userContext'; +import { useUserContext } from 'contexts/UserContext'; import ClassificationsEditor from 'components/taxa/ClassificationsEditor'; import ClassificationsDisplay from 'components/taxa/ClassificationsDisplay'; diff --git a/site/gatsby-site/tsconfig.json b/site/gatsby-site/tsconfig.json index 7f8bb75c84..98170041d2 100644 --- a/site/gatsby-site/tsconfig.json +++ b/site/gatsby-site/tsconfig.json @@ -11,7 +11,7 @@ /* Language and Environment */ "target": "ES2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ - // "jsx": "preserve", /* Specify what JSX code is generated. */ + "jsx": "react", /* Specify what JSX code is generated. */ // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ @@ -22,11 +22,13 @@ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "Node16", /* Specify what module code is generated. */ + "module": "ES2015", /* Specify what module code is generated. */ // "rootDir": "./", /* Specify the root folder within your source files. */ - // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + "moduleResolution": "bundler", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ - // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + "paths": { + "contexts/*": ["./src/contexts/*"] + }, /* Specify a set of entries that re-map imports to additional lookup locations. */ // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ /* Specify type package names to be included without being referenced in a source file. */ // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ From b6c5921ab14040c3d01ab4229da8e6565aba11d0 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Thu, 14 Nov 2024 21:27:06 -0300 Subject: [PATCH 21/99] Add custom pages --- site/gatsby-site/nextauth.config.ts | 10 +- site/gatsby-site/src/contexts/UserContext.tsx | 47 +++++--- site/gatsby-site/src/pages/login.js | 85 ++++---------- site/gatsby-site/src/pages/logout.js | 4 +- site/gatsby-site/src/pages/signup.js | 106 +++--------------- site/gatsby-site/src/pages/verify-request.js | 53 +++++++++ 6 files changed, 130 insertions(+), 175 deletions(-) create mode 100644 site/gatsby-site/src/pages/verify-request.js diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts index 95767426c0..88d331f58c 100644 --- a/site/gatsby-site/nextauth.config.ts +++ b/site/gatsby-site/nextauth.config.ts @@ -1,6 +1,7 @@ import { EmailParams, MailerSend, Recipient } from "mailersend" import { MongoClient, ServerApiVersion } from "mongodb" import { NextAuthOptions } from "next-auth" +import { MongoDBAdapter } from "@auth/mongodb-adapter"; const client = new MongoClient(process.env.API_MONGODB_CONNECTION_STRING!, { serverApi: { @@ -27,9 +28,6 @@ export const sendVerificationRequest = async ({ identifier: email, url }: { iden export const getAuthConfig = async (): Promise => { - // we have to do a dynamic import here because MongoDBAdapter is ESM only - const { MongoDBAdapter } = await import("@auth/mongodb-adapter"); - return { providers: [ // @ts-ignore @@ -82,6 +80,12 @@ export const getAuthConfig = async (): Promise => { ) }, }, + pages: { + signIn: '/login', + signOut: '/logout', + verifyRequest: '/verify-request', + newUser: '/account', + }, debug: true, } } \ No newline at end of file diff --git a/site/gatsby-site/src/contexts/UserContext.tsx b/site/gatsby-site/src/contexts/UserContext.tsx index af666d83c2..25d444f3b0 100644 --- a/site/gatsby-site/src/contexts/UserContext.tsx +++ b/site/gatsby-site/src/contexts/UserContext.tsx @@ -2,7 +2,7 @@ import React, { createContext, useContext, useEffect, useState } from 'react'; import { ApolloProvider, ApolloClient, HttpLink, InMemoryCache, ApolloLink } from '@apollo/client'; import { useTranslation } from 'react-i18next'; import { removeTypenameFromVariables } from '@apollo/client/link/remove-typename'; -import { signOut, useSession } from "next-auth/react"; +import { signOut, signIn, useSession, getCsrfToken, SignInResponse } from "next-auth/react"; interface User { roles?: string[]; @@ -15,7 +15,9 @@ interface UserContextValue { isRole: (role: string) => boolean; isAdmin: boolean; actions: { - logout: () => Promise; + logOut: () => Promise; + logIn: (email: string, callbackUrl: string) => Promise; + signUp: (email: string, callbackUrl: string) => Promise; }; } @@ -56,17 +58,18 @@ interface UserContextProviderProps { children: React.ReactNode; } -export const UserContext = React.createContext({ - loading: true, - user: null, - isRole: () => false, - isAdmin: false, - actions: { - logout: async () => { }, - }, -}); +export const UserContext = React.createContext(undefined); + +export const useUserContext = () => { + + const context = useContext(UserContext); -export const useUserContext = () => useContext(UserContext); + if (context === undefined) { + throw new Error('useUserContext must be used within a UserContextProvider'); + } + + return context; +} export const UserContextProvider: React.FC = ({ children }) => { const { data: session } = useSession(); @@ -74,10 +77,6 @@ export const UserContextProvider: React.FC = ({ childr const [user, setUser] = useState({}); const { t } = useTranslation(); - const logout = async (): Promise => { - await signOut(); - }; - useEffect(() => { if (session === null) { setUser(null); @@ -102,7 +101,21 @@ export const UserContextProvider: React.FC = ({ childr }, isAdmin: Boolean(user?.roles?.includes('admin')), actions: { - logout, + logOut: async () => { + await signOut({ redirect: false }); + }, + logIn: async (email: string, callbackUrl: string) => { + + const result = await signIn('http-email', { email, redirect: false, callbackUrl }); + + return result; + }, + signUp: async (email: string, callbackUrl: string) => { + + const result = await signIn('http-email', { email, redirect: false, callbackUrl }); + + return result; + }, }, }; diff --git a/site/gatsby-site/src/pages/login.js b/site/gatsby-site/src/pages/login.js index ca707a2a57..0f0725d8b3 100644 --- a/site/gatsby-site/src/pages/login.js +++ b/site/gatsby-site/src/pages/login.js @@ -3,41 +3,38 @@ import { Button, Spinner } from 'flowbite-react'; import { useUserContext } from 'contexts/UserContext'; import { Form, Formik } from 'formik'; import * as Yup from 'yup'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faFacebook } from '@fortawesome/free-brands-svg-icons'; import { Trans, useTranslation } from 'react-i18next'; import Link from '../components/ui/Link'; import { StringParam, useQueryParams, withDefault } from 'use-query-params'; import TextInputGroup from 'components/forms/TextInputGroup'; import HeadContent from 'components/HeadContent'; +import { navigate } from 'gatsby'; +import useToastContext, { SEVERITY } from 'hooks/useToast'; const LoginSchema = Yup.object().shape({ email: Yup.string().email('Invalid email').required('Required'), - password: Yup.string().required('required'), }); -const Login = (props) => { +const Login = () => { const { user, loading, - actions: { loginWithEmail, loginWithFacebook }, + actions: { logIn }, } = useUserContext(); - const [displayFacebookSpinner, setDisplayFacebookSpinner] = useState(false); + const addToast = useToastContext(); const [redirectTo, setRedirectTo] = useState(null); const { t } = useTranslation(); - const loginRedirectUri = `${props.location.origin}/logincallback`; - const [{ redirectTo: redirectToParam }] = useQueryParams({ redirectTo: withDefault(StringParam, '/'), }); useEffect(() => { - if (!loading) { - const missingNames = !user.customData.first_name || !user.customData.last_name; + if (!loading && user) { + const missingNames = !user.first_name || !user.last_name; const isSignup = !!localStorage.getItem('signup'); @@ -49,14 +46,6 @@ const Login = (props) => { } }, [loading]); - const clickLoginWithFacebook = async () => { - setDisplayFacebookSpinner(true); - - await loginWithFacebook({ loginRedirectUri, redirectTo }); - - setDisplayFacebookSpinner(false); - }; - return ( <> {loading ? ( @@ -64,11 +53,11 @@ const Login = (props) => { Loading...

- ) : user && user.isLoggedIn && user.profile.email ? ( + ) : user ? ( <>

Logged in as - {user.profile.email} + {user.email}

Log out @@ -77,10 +66,20 @@ const Login = (props) => { ) : ( <> { - await loginWithEmail({ email, password, redirectTo }); + onSubmit={async ({ email }, { setSubmitting }) => { + const result = await logIn(email, redirectTo || ''); + + if (result.ok) { + navigate('/verify-request/?email=' + email); + } else { + addToast({ + message: t('An unknown error has occurred'), + severity: SEVERITY.danger, + error: result.error, + }); + } setSubmitting(false); }} @@ -111,27 +110,9 @@ const Login = (props) => { /> -
- - - Forgot password? - -
- -
Don't have an account?{' '} diff --git a/site/gatsby-site/src/pages/logout.js b/site/gatsby-site/src/pages/logout.js index f4bce822f0..5177b7c71b 100644 --- a/site/gatsby-site/src/pages/logout.js +++ b/site/gatsby-site/src/pages/logout.js @@ -7,14 +7,14 @@ import useLocalizePath from '../components/i18n/useLocalizePath'; const Logout = () => { const { - actions: { logout }, + actions: { logOut }, } = useUserContext(); const localizePath = useLocalizePath(); useEffect(() => { const init = async () => { - await logout(); + await logOut(); navigate(localizePath({ path: `/` })); }; diff --git a/site/gatsby-site/src/pages/signup.js b/site/gatsby-site/src/pages/signup.js index 828d052988..1648c147a6 100644 --- a/site/gatsby-site/src/pages/signup.js +++ b/site/gatsby-site/src/pages/signup.js @@ -8,55 +8,37 @@ import Link from '../components/ui/Link'; import { LocalizedLink } from 'plugins/gatsby-theme-i18n'; import { Trans, useTranslation } from 'react-i18next'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faFacebook } from '@fortawesome/free-brands-svg-icons'; import { StringParam, useQueryParams } from 'use-query-params'; import { faEnvelope } from '@fortawesome/free-solid-svg-icons'; import TextInputGroup from 'components/forms/TextInputGroup'; import NewsletterSignup from 'components/landing/NewsletterSignup'; import Card from 'elements/Card'; import HeadContent from 'components/HeadContent'; +import { navigate } from 'gatsby'; const SignUpSchema = Yup.object().shape({ email: Yup.string().email('Invalid email').required('Required'), - password: Yup.string().required('Required'), - passwordConfirm: Yup.string().test('passwords-match', 'Passwords must match', function (value) { - return this.parent.password === value; - }), }); -const SignUp = (props) => { +const SignUp = () => { const { user, loading, - actions: { signUp, loginWithFacebook }, + actions: { signUp }, } = useUserContext(); - const [displayFacebookSpinner, setDisplayFacebookSpinner] = useState(false); - const [emailValue, setEmailValue] = useState(''); const { t } = useTranslation(); const addToast = useToastContext(); - const loginRedirectUri = `${props.location.origin}/logincallback`; - let [{ redirectTo }] = useQueryParams({ redirectTo: StringParam, }); redirectTo = redirectTo ?? '/'; - const clickLoginWithFacebook = async () => { - setDisplayFacebookSpinner(true); - - localStorage.setItem('signup', '1'); - - await loginWithFacebook({ loginRedirectUri, redirectTo }); - - setDisplayFacebookSpinner(false); - }; - return (
@@ -78,11 +60,11 @@ const SignUp = (props) => { Loading...
- ) : user && user.isLoggedIn && user.profile.email ? ( + ) : user ? ( <>

Logged in as - {user.profile.email} + {user.email}

Log out @@ -91,30 +73,22 @@ const SignUp = (props) => { ) : (
{ - try { - await signUp({ email, password, redirectTo }); + onSubmit={async ({ email }, { setSubmitting }) => { + const result = await signUp(email, redirectTo); - localStorage.setItem('signup', '1'); + localStorage.setItem('signup', '1'); + if (result.ok) { + navigate('/verify-request/?email=' + email); + } else { addToast({ - message: t('Verification email sent to {{email}}', { email, ns: 'login' }), - severity: SEVERITY.success, - }); - } catch (e) { - addToast({ - message: ( - - ), + message: t('An unknown error has occurred'), severity: SEVERITY.danger, - error: e, + error: result.error, }); } - setSubmitting(false); }} > @@ -147,40 +121,10 @@ const SignUp = (props) => { />
-
- -
- -
- -
-
- -
Already have an account?{' '} diff --git a/site/gatsby-site/src/pages/verify-request.js b/site/gatsby-site/src/pages/verify-request.js new file mode 100644 index 0000000000..600baf5944 --- /dev/null +++ b/site/gatsby-site/src/pages/verify-request.js @@ -0,0 +1,53 @@ +import React from 'react'; +import { useUserContext } from 'contexts/UserContext'; +import { Trans, useTranslation } from 'react-i18next'; +import { StringParam, useQueryParams } from 'use-query-params'; +import HeadContent from 'components/HeadContent'; +import { Spinner } from 'flowbite-react'; + +const VerifyRequest = () => { + const { user, loading } = useUserContext(); + + const [{ email }] = useQueryParams({ email: StringParam }); + + if (loading) { + return ( +
+ +
+ ); + } + + if (user) { + return ( +
+ Logged in as: {user.email} +
+ ); + } + + return ( + <> +

+ Check your email +

+

+ A sign in link has been sent to {email} +

+ + ); +}; + +export const Head = (props) => { + const { t } = useTranslation(); + + return ( + + ); +}; + +export default VerifyRequest; From 1e5856c94473f0cfb2a7fad702c662e308afcefb Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Thu, 14 Nov 2024 22:19:46 -0300 Subject: [PATCH 22/99] Update typescript (jest) --- site/gatsby-site/package-lock.json | 34 ++++++++++++++++-------------- site/gatsby-site/package.json | 3 ++- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/site/gatsby-site/package-lock.json b/site/gatsby-site/package-lock.json index 94c79c692d..9cb9b50417 100644 --- a/site/gatsby-site/package-lock.json +++ b/site/gatsby-site/package-lock.json @@ -170,7 +170,8 @@ "supertest": "^6.3.4", "tailwindcss": "^3.3.2", "ts-jest": "^29.1.2", - "ts-node": "^10.9.2" + "ts-node": "^10.9.2", + "typescript": "^5.6.3" }, "optionalDependencies": { "@parcel/watcher-linux-x64-glibc": "^2.4.0" @@ -6030,6 +6031,18 @@ "version": "14.18.63", "license": "MIT" }, + "node_modules/@cloudinary/html/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, "node_modules/@cloudinary/react": { "version": "1.11.2", "license": "MIT", @@ -45880,18 +45893,6 @@ "is-typedarray": "^1.0.0" } }, - "node_modules/netlify-cli/node_modules/typescript": { - "version": "5.1.6", - "dev": true, - "license": "Apache-2.0", - "bin": { - "tsc": "bin/tsc", - "tsserver": "bin/tsserver" - }, - "engines": { - "node": ">=14.17" - } - }, "node_modules/netlify-cli/node_modules/ufo": { "version": "1.4.0", "dev": true, @@ -55397,14 +55398,15 @@ } }, "node_modules/typescript": { - "version": "4.9.5", - "license": "Apache-2.0", + "version": "5.6.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", + "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" }, "engines": { - "node": ">=4.2.0" + "node": ">=14.17" } }, "node_modules/typical": { diff --git a/site/gatsby-site/package.json b/site/gatsby-site/package.json index 1e7f6772c2..cf05160889 100644 --- a/site/gatsby-site/package.json +++ b/site/gatsby-site/package.json @@ -184,7 +184,8 @@ "supertest": "^6.3.4", "tailwindcss": "^3.3.2", "ts-jest": "^29.1.2", - "ts-node": "^10.9.2" + "ts-node": "^10.9.2", + "typescript": "^5.6.3" }, "browser": { "crypto": false From ea183059b45c63954b5f3bc1437dfa07fcdf4f0a Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 15 Nov 2024 00:17:00 -0300 Subject: [PATCH 23/99] Update tests --- site/gatsby-site/nextauth.config.ts | 14 +++++--- site/gatsby-site/server/context.ts | 15 +++++--- .../server/tests/incidents.spec.ts | 6 ++-- .../server/tests/mutation-fields.spec.ts | 34 +++++++++---------- .../server/tests/notifications.spec.ts | 22 ++++++------ .../server/tests/query-fields.spec.ts | 18 +++++----- site/gatsby-site/server/tests/reports.spec.ts | 8 ++--- .../server/tests/submissions.spec.ts | 8 ++--- site/gatsby-site/server/tests/utils.spec.ts | 8 ++--- site/gatsby-site/server/tests/utils.ts | 27 +++++++++------ site/gatsby-site/server/tsconfig.json | 2 +- site/gatsby-site/src/utils/serverless.ts | 16 ++++----- site/gatsby-site/tsconfig.json | 2 +- 13 files changed, 98 insertions(+), 82 deletions(-) diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts index 88d331f58c..e4606596a8 100644 --- a/site/gatsby-site/nextauth.config.ts +++ b/site/gatsby-site/nextauth.config.ts @@ -1,7 +1,6 @@ import { EmailParams, MailerSend, Recipient } from "mailersend" import { MongoClient, ServerApiVersion } from "mongodb" import { NextAuthOptions } from "next-auth" -import { MongoDBAdapter } from "@auth/mongodb-adapter"; const client = new MongoClient(process.env.API_MONGODB_CONNECTION_STRING!, { serverApi: { @@ -28,6 +27,8 @@ export const sendVerificationRequest = async ({ identifier: email, url }: { iden export const getAuthConfig = async (): Promise => { + const { MongoDBAdapter } = await import("@auth/mongodb-adapter"); + return { providers: [ // @ts-ignore @@ -54,8 +55,13 @@ export const getAuthConfig = async (): Promise => { const customData = await client.db('customData').collection('users').findOne({ userId: user.id }); - session.user.id = customData.userId; - session.user.roles = customData.roles; + if (session?.user && customData) { + + // @ts-ignore + session.user.id = customData.userId; + // @ts-ignore + session.user.roles = customData.roles; + } return session }, @@ -84,7 +90,7 @@ export const getAuthConfig = async (): Promise => { signIn: '/login', signOut: '/logout', verifyRequest: '/verify-request', - newUser: '/account', + newUser: '/account', }, debug: true, } diff --git a/site/gatsby-site/server/context.ts b/site/gatsby-site/server/context.ts index d3db05e50b..be85ab4ccf 100644 --- a/site/gatsby-site/server/context.ts +++ b/site/gatsby-site/server/context.ts @@ -5,15 +5,20 @@ import { getServerSession } from 'next-auth' import { getAuthConfig } from "../nextauth.config"; import { createResponse } from '../src/utils/serverless' +export const verifyToken = async (req: IncomingMessage) => { + + const authConfig = await getAuthConfig(); + const res = createResponse(); + const session = await getServerSession(req as any, res as any, authConfig as any); + + return session?.user ? { id: session.user.id, roles: session.user.roles } : null; +} + export const context = async ({ req, client }: { req: IncomingMessage, client: MongoClient }) => { try { - const authConfig = await getAuthConfig(); - const res = createResponse(); - const session = await getServerSession(req as any, res as any, authConfig as any); - - const user = session?.user ? { id: session.user.id, roles: session.user.roles } : null; + const user = await verifyToken(req); return { user, req, client }; } diff --git a/site/gatsby-site/server/tests/incidents.spec.ts b/site/gatsby-site/server/tests/incidents.spec.ts index 8a6abc9de5..e8954e74f7 100644 --- a/site/gatsby-site/server/tests/incidents.spec.ts +++ b/site/gatsby-site/server/tests/incidents.spec.ts @@ -1,6 +1,6 @@ import { expect, jest, it } from '@jest/globals'; import { ApolloServer } from "@apollo/server"; -import { makeRequest, seedFixture, startTestServer } from "./utils"; +import { makeRequest, mockSession, seedFixture, startTestServer } from "./utils"; import * as context from '../context'; describe(`Incidents`, () => { @@ -74,7 +74,7 @@ describe(`Incidents`, () => { }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); const response = await makeRequest(url, mutationData); @@ -140,7 +140,7 @@ describe(`Incidents`, () => { }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); const response = await makeRequest(url, mutationData); diff --git a/site/gatsby-site/server/tests/mutation-fields.spec.ts b/site/gatsby-site/server/tests/mutation-fields.spec.ts index 923c7dbb0b..1ea51609ce 100644 --- a/site/gatsby-site/server/tests/mutation-fields.spec.ts +++ b/site/gatsby-site/server/tests/mutation-fields.spec.ts @@ -2,7 +2,7 @@ import { expect, jest, it } from '@jest/globals'; import { ApolloServer } from "@apollo/server"; import { pluralize, singularize } from "../utils"; import capitalize from 'lodash/capitalize'; -import { makeRequest, seedFixture, startTestServer } from "./utils"; +import { makeRequest, mockSession, seedFixture, startTestServer } from "./utils"; import * as context from '../context'; import quickaddsFixture from './fixtures/quickadds'; @@ -82,7 +82,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); @@ -95,7 +95,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); @@ -127,7 +127,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); @@ -139,7 +139,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); @@ -173,7 +173,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); @@ -186,7 +186,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); @@ -221,7 +221,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); @@ -234,7 +234,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); @@ -265,7 +265,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); @@ -276,7 +276,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); @@ -307,7 +307,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); @@ -320,7 +320,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); @@ -354,7 +354,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); @@ -367,7 +367,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); @@ -399,7 +399,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); @@ -412,7 +412,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, mutationData); diff --git a/site/gatsby-site/server/tests/notifications.spec.ts b/site/gatsby-site/server/tests/notifications.spec.ts index 3a5367aaa4..5bbd9ee68e 100644 --- a/site/gatsby-site/server/tests/notifications.spec.ts +++ b/site/gatsby-site/server/tests/notifications.spec.ts @@ -1,6 +1,6 @@ import { expect, jest, it } from '@jest/globals'; import { ApolloServer } from "@apollo/server"; -import { makeRequest, seedFixture, startTestServer } from "./utils"; +import { makeRequest, mockSession, seedFixture, startTestServer } from "./utils"; import * as context from '../context'; import * as common from '../fields/common'; import { DBEntity, DBIncident, DBNotification, DBReport, DBSubmission, DBSubscription, DBUser } from '../interfaces'; @@ -47,7 +47,7 @@ describe(`Notifications`, () => { }); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); const sendEmailMock = jest.spyOn(common, 'sendEmail').mockResolvedValue(); const response = await makeRequest(url, mutationData, { ['PROCESS_NOTIFICATIONS_SECRET']: config.PROCESS_NOTIFICATIONS_SECRET }); @@ -159,7 +159,7 @@ describe(`Notifications`, () => { `, }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); jest.spyOn(common, 'getUsersAdminData').mockResolvedValue([{ userId: '123', email: 'test@test.com' }]); const sendEmailMock = jest.spyOn(common, 'sendEmail').mockResolvedValue(); @@ -289,7 +289,7 @@ describe(`Notifications`, () => { `, }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); jest.spyOn(common, 'getUsersAdminData').mockResolvedValue([{ userId: '123', email: 'test@test.com' }]); const sendEmailMock = jest.spyOn(common, 'sendEmail').mockResolvedValue(); @@ -421,7 +421,7 @@ describe(`Notifications`, () => { `, }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); jest.spyOn(common, 'getUsersAdminData').mockResolvedValue([{ userId: '123', email: 'test@test.com' }]); const sendEmailMock = jest.spyOn(common, 'sendEmail').mockResolvedValue(); @@ -547,7 +547,7 @@ describe(`Notifications`, () => { `, }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); jest.spyOn(common, 'getUsersAdminData').mockResolvedValue([{ userId: '123', email: 'test@test.com' }]); const sendEmailMock = jest.spyOn(common, 'sendEmail').mockResolvedValue(); @@ -674,7 +674,7 @@ describe(`Notifications`, () => { `, }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); jest.spyOn(common, 'getUsersAdminData').mockResolvedValue([{ userId: '123', email: 'test@test.com' }]); const sendEmailMock = jest.spyOn(common, 'sendEmail').mockResolvedValue(); @@ -729,7 +729,7 @@ describe(`Notifications`, () => { }); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "user1" }) + mockSession('user1'); const newIncident: IncidentInsertType = { @@ -842,7 +842,7 @@ describe(`Notifications`, () => { }); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "user1" }) + mockSession('user1'); const mutationData: { query: string, variables: { input: PromoteSubmissionToReportInput } } = { query: ` @@ -1059,7 +1059,7 @@ describe(`Notifications`, () => { }); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "user1" }) + mockSession('user1'); const mutationData: { query: string, variables: { filter: IncidentFilterType, update: IncidentUpdateType } } = { query: ` @@ -1178,7 +1178,7 @@ describe(`Notifications`, () => { }); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "user1" }) + mockSession('user1'); const mutationData: { query: string, variables: { filter: IncidentFilterType, update: IncidentUpdateType } } = { query: ` diff --git a/site/gatsby-site/server/tests/query-fields.spec.ts b/site/gatsby-site/server/tests/query-fields.spec.ts index 74d62771fe..e9ebee9157 100644 --- a/site/gatsby-site/server/tests/query-fields.spec.ts +++ b/site/gatsby-site/server/tests/query-fields.spec.ts @@ -1,5 +1,5 @@ import { ApolloServer } from "@apollo/server"; -import { makeRequest, seedFixture, startTestServer } from "./utils"; +import { makeRequest, mockSession, seedFixture, startTestServer } from "./utils"; import { pluralize, singularize } from "../utils"; import capitalize from 'lodash/capitalize'; @@ -68,7 +68,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, queryData); @@ -80,7 +80,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, queryData); @@ -111,7 +111,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, queryData); @@ -124,7 +124,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, queryData); @@ -157,7 +157,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, queryData); @@ -169,7 +169,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, queryData); @@ -201,7 +201,7 @@ fixtures.forEach((collection) => { for (const user of testData.allowed) { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, queryData); @@ -215,7 +215,7 @@ fixtures.forEach((collection) => { await seedFixture(collection.seeds); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: user.userId }) + mockSession(user.userId); const response = await makeRequest(url, queryData); diff --git a/site/gatsby-site/server/tests/reports.spec.ts b/site/gatsby-site/server/tests/reports.spec.ts index baf5d993b0..9e9f4f3342 100644 --- a/site/gatsby-site/server/tests/reports.spec.ts +++ b/site/gatsby-site/server/tests/reports.spec.ts @@ -1,6 +1,6 @@ import { expect, jest, it } from '@jest/globals'; import { ApolloServer } from "@apollo/server"; -import { makeRequest, seedFixture, startTestServer } from "./utils"; +import { makeRequest, mockSession, seedFixture, startTestServer } from "./utils"; import * as context from '../context'; describe(`Reports`, () => { @@ -56,7 +56,7 @@ describe(`Reports`, () => { }); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); const response = await makeRequest(url, mutationData); @@ -118,7 +118,7 @@ describe(`Reports`, () => { }); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); const response = await makeRequest(url, mutationData); @@ -183,7 +183,7 @@ describe(`Reports`, () => { }); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); const response = await makeRequest(url, mutationData); diff --git a/site/gatsby-site/server/tests/submissions.spec.ts b/site/gatsby-site/server/tests/submissions.spec.ts index 615a68f08f..b0ed905ae6 100644 --- a/site/gatsby-site/server/tests/submissions.spec.ts +++ b/site/gatsby-site/server/tests/submissions.spec.ts @@ -1,6 +1,6 @@ import { expect, jest, it } from '@jest/globals'; import { ApolloServer } from "@apollo/server"; -import { makeRequest, seedFixture, startTestServer } from "./utils"; +import { makeRequest, mockSession, seedFixture, startTestServer } from "./utils"; import * as context from '../context'; import { ObjectId } from 'bson'; import { PromoteSubmissionToReportInput } from '../generated/graphql'; @@ -68,7 +68,7 @@ describe(`Submissions`, () => { }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); const response = await makeRequest(url, mutationData); @@ -131,7 +131,7 @@ describe(`Submissions`, () => { }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); const response = await makeRequest(url, mutationData); @@ -193,7 +193,7 @@ describe(`Submissions`, () => { }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); const response = await makeRequest(url, mutationData); diff --git a/site/gatsby-site/server/tests/utils.spec.ts b/site/gatsby-site/server/tests/utils.spec.ts index 83c3c884b1..de0e5ae675 100644 --- a/site/gatsby-site/server/tests/utils.spec.ts +++ b/site/gatsby-site/server/tests/utils.spec.ts @@ -1,6 +1,6 @@ import { expect, jest, it } from '@jest/globals'; import { ApolloServer } from "@apollo/server"; -import { makeRequest, seedFixture, startTestServer } from "./utils"; +import { makeRequest, mockSession, seedFixture, startTestServer } from "./utils"; import * as context from '../context'; describe(`Utils`, () => { @@ -61,7 +61,7 @@ describe(`Utils`, () => { }); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); const response = await makeRequest(url, mutationData); @@ -125,7 +125,7 @@ describe(`Utils`, () => { }); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); const response = await makeRequest(url, mutationData); @@ -180,7 +180,7 @@ describe(`Utils`, () => { }); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); const response = await makeRequest(url, mutationData); diff --git a/site/gatsby-site/server/tests/utils.ts b/site/gatsby-site/server/tests/utils.ts index 9d872913bb..fc0f13edd6 100644 --- a/site/gatsby-site/server/tests/utils.ts +++ b/site/gatsby-site/server/tests/utils.ts @@ -1,6 +1,6 @@ import { startStandaloneServer } from "@apollo/server/standalone"; import { schema } from "../schema"; -import { MongoClient, ObjectId, WithId } from "mongodb"; +import { MongoClient, ObjectId } from "mongodb"; import { ApolloServer } from "@apollo/server"; import config from '../config'; import supertest from 'supertest'; @@ -40,16 +40,6 @@ export const seedCollection = async ({ name, docs, database = 'aiidprod', drop = } } -export const seedUsers = async (users: { userId: string, roles: string[] | null }[], { drop } = { drop: true }) => { - - await seedCollection({ - name: 'users', - database: 'customData', - docs: users, - drop, - }); -} - export const makeRequest = async (url: string, data: { query: string, variables?: Record }, headers?: Record) => { const request = supertest(url) @@ -161,3 +151,18 @@ export const seedFixture = async (seeds: Record { + + const client = new MongoClient(process.env.API_MONGODB_CONNECTION_STRING!); + + const db = client.db('customData'); + const collection = db.collection('users'); + + jest.spyOn(context, 'verifyToken').mockImplementation(async () => { + + const user = await collection.findOne<{ userId: string, roles: string[] }>({ userId: userId }); + + return user ? { id: user.userId, roles: user.roles } : null; + }) +} \ No newline at end of file diff --git a/site/gatsby-site/server/tsconfig.json b/site/gatsby-site/server/tsconfig.json index 52ca8b9a19..37fa66f4ca 100644 --- a/site/gatsby-site/server/tsconfig.json +++ b/site/gatsby-site/server/tsconfig.json @@ -6,7 +6,7 @@ // to avoid clashing with Jest types "types": [ "jest" - ] + ], }, "include": [ "../node_modules/jest", diff --git a/site/gatsby-site/src/utils/serverless.ts b/site/gatsby-site/src/utils/serverless.ts index 79530ee1ca..558102f012 100644 --- a/site/gatsby-site/src/utils/serverless.ts +++ b/site/gatsby-site/src/utils/serverless.ts @@ -1,13 +1,13 @@ export const createResponse = () => { - let responseHeaders = { 'Content-Type': 'application/json' } - let response = {} + let responseHeaders: Record = { 'Content-Type': 'application/json' } + let response: Record = {} const res = { - getHeader: (name) => { + getHeader: (name: any) => { return responseHeaders[name] }, - setHeader: (name, value) => { + setHeader: (name: any, value: any) => { if (name.toLowerCase() === 'set-cookie') { response.multiValueHeaders = { ...response.multiValueHeaders, @@ -18,17 +18,17 @@ export const createResponse = () => { response.headers = responseHeaders } }, - status: (statusCode) => { + status: (statusCode: any) => { response.statusCode = statusCode return res }, - send: (body) => { + send: (body: any) => { response.body = typeof body === 'string' ? body : JSON.stringify(body) }, - json: (json) => { + json: (json: any) => { response.body = JSON.stringify(json) }, - redirect: (statusOrUrl, url) => { + redirect: (statusOrUrl: any, url: any) => { const statusCode = typeof statusOrUrl === 'number' ? statusOrUrl : 302 const redirectUrl = typeof statusOrUrl === 'string' ? statusOrUrl : url response.statusCode = statusCode diff --git a/site/gatsby-site/tsconfig.json b/site/gatsby-site/tsconfig.json index 98170041d2..33ac7e7112 100644 --- a/site/gatsby-site/tsconfig.json +++ b/site/gatsby-site/tsconfig.json @@ -22,7 +22,7 @@ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ /* Modules */ - "module": "ES2015", /* Specify what module code is generated. */ + "module": "ES2020", /* Specify what module code is generated. */ // "rootDir": "./", /* Specify the root folder within your source files. */ "moduleResolution": "bundler", /* Specify how TypeScript looks up a file from a given module specifier. */ // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ From 06932ebbe7a29c4ac53d6287671128db0d702752 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 15 Nov 2024 19:21:03 -0300 Subject: [PATCH 24/99] Allow mocking user sessions --- site/gatsby-site/playwright/memory-mongo.ts | 10 +- .../playwright/seeds/auth/users.ts | 11 +++ .../playwright/seeds/customData/users.ts | 2 +- site/gatsby-site/playwright/utils.ts | 99 +++++++++++++------ site/gatsby-site/src/contexts/UserContext.tsx | 4 - 5 files changed, 88 insertions(+), 38 deletions(-) create mode 100644 site/gatsby-site/playwright/seeds/auth/users.ts diff --git a/site/gatsby-site/playwright/memory-mongo.ts b/site/gatsby-site/playwright/memory-mongo.ts index dd7b5fa0e7..ef6149e9c8 100644 --- a/site/gatsby-site/playwright/memory-mongo.ts +++ b/site/gatsby-site/playwright/memory-mongo.ts @@ -1,7 +1,6 @@ import { MongoMemoryServer } from 'mongodb-memory-server'; import { MongoClient } from 'mongodb'; import assert from 'node:assert'; - import incidents from './seeds/aiidprod/incidents'; import reports from './seeds/aiidprod/reports'; import submissions from './seeds/aiidprod/submissions'; @@ -14,6 +13,8 @@ import duplicates from './seeds/aiidprod/duplicates'; import users from './seeds/customData/users'; import subscriptions from './seeds/customData/subscriptions'; +import authUsers from './seeds/auth/users'; + export const init = async (extra?: Record[]>>, { drop } = { drop: false }) => { @@ -34,6 +35,9 @@ export const init = async (extra?: Record Promise) => { - assert(process.env.MONGODB_CONNECTION_STRING?.includes('localhost') || process.env.MONGODB_CONNECTION_STRING?.includes('127.0.0.1'), 'Seeding is only allowed on localhost'); + assert(process.env.MONGODB_CONNECTION_STRING?.includes('localhost') || process.env.MONGODB_CONNECTION_STRING?.includes('127.0.0.1'), `Seeding is only allowed on localhost [${process.env.MONGODB_CONNECTION_STRING}]`); const client = new MongoClient(process.env.MONGODB_CONNECTION_STRING!); @@ -99,7 +103,7 @@ export const execute = async (fn: (client: MongoClient) => Promise) => { // command line support -let instance: MongoMemoryServer | null = null; +let instance: MongoMemoryServer = null; async function start() { diff --git a/site/gatsby-site/playwright/seeds/auth/users.ts b/site/gatsby-site/playwright/seeds/auth/users.ts new file mode 100644 index 0000000000..ed4ee697aa --- /dev/null +++ b/site/gatsby-site/playwright/seeds/auth/users.ts @@ -0,0 +1,11 @@ +import { ObjectId } from "bson"; + +const users = [ + { + _id: new ObjectId("6737a6e881955aa4905ccb04"), + email: "test.user@incidentdatabase.ai", + emailVerified: "2024-11-15T21:41:04.245Z", + } +] + +export default users; \ No newline at end of file diff --git a/site/gatsby-site/playwright/seeds/customData/users.ts b/site/gatsby-site/playwright/seeds/customData/users.ts index 587312b920..d0912aa1eb 100644 --- a/site/gatsby-site/playwright/seeds/customData/users.ts +++ b/site/gatsby-site/playwright/seeds/customData/users.ts @@ -3,7 +3,7 @@ import { DBUser } from '../../../server/interfaces'; const users: DBUser[] = [ { - userId: 'user1', + userId: '6737a6e881955aa4905ccb04', first_name: "Test", last_name: "User", roles: ["admin"], diff --git a/site/gatsby-site/playwright/utils.ts b/site/gatsby-site/playwright/utils.ts index 0ffb38328e..bcd2004fca 100644 --- a/site/gatsby-site/playwright/utils.ts +++ b/site/gatsby-site/playwright/utils.ts @@ -6,6 +6,7 @@ import assert from 'node:assert'; import fs from 'fs'; import path from 'path'; import * as memoryMongo from './memory-mongo'; +import * as crypto from 'node:crypto'; declare module '@playwright/test' { interface Request { @@ -22,30 +23,73 @@ type TestFixtures = { retryDelay?: [({ }: {}, use: () => Promise, testInfo: { retry: number }) => Promise, { auto: true }], }; -const getUserIdFromLocalStorage = async (page: Page) => { +export function randomString(size: number) { + const i2hex = (i: number) => ("0" + i.toString(16)).slice(-2) + const r = (a: string, i: number): string => a + i2hex(i) + const bytes = crypto.getRandomValues(new Uint8Array(size)) + return Array.from(bytes).reduce(r, "") +} - const storage = await page.context().storageState(); +export function hashToken(token: string) { - for (const origin of storage.origins) { - for (const storage of origin.localStorage) { - if (storage.value == 'local-userpass') { - const match = storage.name.match(/user\(([^)]+)\):providerType/); - return match?.[1]; - } - } - } + return crypto.createHash("sha256") + .update(`${token}${process.env.NEXTAUTH_SECRET}`) + .digest("hex"); } -const getAccessTokenFromLocalStorage = async (page: Page, userId: string) => { - const storage = await page.context().storageState(); +async function generateMagicLink(email: string) { - for (const origin of storage.origins) { - for (const storage of origin.localStorage) { - if (storage.name.endsWith(`user(${userId}):accessToken`)) { - return storage.value; - } - } - } + const token = randomString(32); + const expires = new Date(Date.now() + 24 * 60 * 60 * 1000); + + await memoryMongo.execute(async (client) => { + + const collection = client.db('auth').collection('verification_tokens'); + const hashedToken = hashToken(token); // next auth stores the hashed token + + await collection.insertOne({ + identifier: email, + token: hashedToken, + expires, + }); + }); + + const baseUrl = process.env.NEXTAUTH_URL; + const magicLink = `${baseUrl}/api/auth/callback/http-email?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2F&token=${token}&email=${encodeURIComponent(email)}`; + + return magicLink; +} + +async function getUserIdFromAuth(email: string) { + + let id: string = null; + + await memoryMongo.execute(async (client) => { + + const collection = client.db('auth').collection('users'); + + const user = await collection.findOne({ email }); + + id = user._id.toString(); + }); + + return id; +} + +async function getSessionToken(email: string) { + + let token: string = null; + + await memoryMongo.execute(async (client) => { + + const collection = client.db('auth').collection('sessions'); + + const session = await collection.findOne({}, { sort: { expires: -1 } }); + + token = session.token; + }); + + return token; } export const test = base.extend({ @@ -72,23 +116,18 @@ export const test = base.extend({ await use(async (email, password, { customData } = {}) => { + const magicLink = await generateMagicLink(email); + await page.context().clearCookies(); - await loginSteps(page, email, password); + await page.goto(magicLink); - const userId = await getUserIdFromLocalStorage(page); + const userId = await getUserIdFromAuth(email); - const accessToken = await getAccessTokenFromLocalStorage(page, userId!); + const sessionToken = await getSessionToken(userId); if (customData) { - await page.evaluate(({ customData }) => { - - localStorage.setItem('__CUSTOM_DATA_MOCK', JSON.stringify(customData)); - - }, { customData }); - - // upsert user with custom data await memoryMongo.execute(async (client) => { const db = client.db('customData'); @@ -98,7 +137,7 @@ export const test = base.extend({ }); } - return [userId!, accessToken!]; + return [userId!, sessionToken!]; // to be able to restore session state, we'll need to refactor when we perform the login call, but that's for another PR // https://playwright.dev/docs/auth#avoid-authentication-in-some-tests diff --git a/site/gatsby-site/src/contexts/UserContext.tsx b/site/gatsby-site/src/contexts/UserContext.tsx index 25d444f3b0..94483537ed 100644 --- a/site/gatsby-site/src/contexts/UserContext.tsx +++ b/site/gatsby-site/src/contexts/UserContext.tsx @@ -93,10 +93,6 @@ export const UserContextProvider: React.FC = ({ childr loading, user, isRole(role: string): boolean { - if (typeof window !== 'undefined' && window.localStorage.getItem('__CUSTOM_DATA_MOCK')) { - const customData: CustomDataMock = JSON.parse(window.localStorage.getItem('__CUSTOM_DATA_MOCK') || '{}'); - return customData.roles.includes('admin') || customData.roles.includes(role); - } return Boolean(user?.roles?.includes('admin') || user?.roles?.includes(role)); }, isAdmin: Boolean(user?.roles?.includes('admin')), From fa727dbdd4026b3ce42dba501157e443c1123656 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 15 Nov 2024 20:10:50 -0300 Subject: [PATCH 25/99] update command --- site/gatsby-site/package-lock.json | 152 +++-------------------------- site/gatsby-site/package.json | 3 +- 2 files changed, 14 insertions(+), 141 deletions(-) diff --git a/site/gatsby-site/package-lock.json b/site/gatsby-site/package-lock.json index 9cb9b50417..03b26f0780 100644 --- a/site/gatsby-site/package-lock.json +++ b/site/gatsby-site/package-lock.json @@ -33500,17 +33500,6 @@ "node": ">=0.1.90" } }, - "node_modules/netlify-cli/node_modules/@cspotcode/source-map-support": { - "version": "0.8.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/trace-mapping": "0.3.9" - }, - "engines": { - "node": ">=12" - } - }, "node_modules/netlify-cli/node_modules/@dabh/diagnostics": { "version": "2.0.2", "dev": true, @@ -33872,28 +33861,6 @@ "node": ">=8" } }, - "node_modules/netlify-cli/node_modules/@jridgewell/resolve-uri": { - "version": "3.1.0", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6.0.0" - } - }, - "node_modules/netlify-cli/node_modules/@jridgewell/sourcemap-codec": { - "version": "1.4.15", - "dev": true, - "license": "MIT" - }, - "node_modules/netlify-cli/node_modules/@jridgewell/trace-mapping": { - "version": "0.3.9", - "dev": true, - "license": "MIT", - "dependencies": { - "@jridgewell/resolve-uri": "^3.0.3", - "@jridgewell/sourcemap-codec": "^1.4.10" - } - }, "node_modules/netlify-cli/node_modules/@lukeed/ms": { "version": "2.0.1", "dev": true, @@ -36124,26 +36091,6 @@ "node": ">=10.13.0" } }, - "node_modules/netlify-cli/node_modules/@tsconfig/node10": { - "version": "1.0.8", - "dev": true, - "license": "MIT" - }, - "node_modules/netlify-cli/node_modules/@tsconfig/node12": { - "version": "1.0.9", - "dev": true, - "license": "MIT" - }, - "node_modules/netlify-cli/node_modules/@tsconfig/node14": { - "version": "1.0.1", - "dev": true, - "license": "MIT" - }, - "node_modules/netlify-cli/node_modules/@tsconfig/node16": { - "version": "1.0.2", - "dev": true, - "license": "MIT" - }, "node_modules/netlify-cli/node_modules/@types/body-parser": { "version": "1.19.2", "dev": true, @@ -36579,14 +36526,6 @@ "acorn": "^8" } }, - "node_modules/netlify-cli/node_modules/acorn-walk": { - "version": "8.3.2", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/netlify-cli/node_modules/agent-base": { "version": "6.0.2", "dev": true, @@ -37062,11 +37001,6 @@ "node": ">=10" } }, - "node_modules/netlify-cli/node_modules/arg": { - "version": "4.1.3", - "dev": true, - "license": "MIT" - }, "node_modules/netlify-cli/node_modules/argparse": { "version": "2.0.1", "dev": true, @@ -38502,11 +38436,6 @@ "safe-buffer": "~5.2.0" } }, - "node_modules/netlify-cli/node_modules/create-require": { - "version": "1.1.1", - "dev": true, - "license": "MIT" - }, "node_modules/netlify-cli/node_modules/cron-parser": { "version": "4.9.0", "dev": true, @@ -42538,11 +42467,6 @@ "semver": "bin/semver.js" } }, - "node_modules/netlify-cli/node_modules/make-error": { - "version": "1.3.6", - "dev": true, - "license": "ISC" - }, "node_modules/netlify-cli/node_modules/maxstache": { "version": "1.0.7", "dev": true, @@ -45782,56 +45706,6 @@ "dev": true, "license": "MIT" }, - "node_modules/netlify-cli/node_modules/ts-node": { - "version": "10.9.1", - "dev": true, - "license": "MIT", - "dependencies": { - "@cspotcode/source-map-support": "^0.8.0", - "@tsconfig/node10": "^1.0.7", - "@tsconfig/node12": "^1.0.7", - "@tsconfig/node14": "^1.0.0", - "@tsconfig/node16": "^1.0.2", - "acorn": "^8.4.1", - "acorn-walk": "^8.1.1", - "arg": "^4.1.0", - "create-require": "^1.1.0", - "diff": "^4.0.1", - "make-error": "^1.1.1", - "v8-compile-cache-lib": "^3.0.1", - "yn": "3.1.1" - }, - "bin": { - "ts-node": "dist/bin.js", - "ts-node-cwd": "dist/bin-cwd.js", - "ts-node-esm": "dist/bin-esm.js", - "ts-node-script": "dist/bin-script.js", - "ts-node-transpile-only": "dist/bin-transpile.js", - "ts-script": "dist/bin-script-deprecated.js" - }, - "peerDependencies": { - "@swc/core": ">=1.2.50", - "@swc/wasm": ">=1.2.50", - "@types/node": "*", - "typescript": ">=2.7" - }, - "peerDependenciesMeta": { - "@swc/core": { - "optional": true - }, - "@swc/wasm": { - "optional": true - } - } - }, - "node_modules/netlify-cli/node_modules/ts-node/node_modules/diff": { - "version": "4.0.2", - "dev": true, - "license": "BSD-3-Clause", - "engines": { - "node": ">=0.3.1" - } - }, "node_modules/netlify-cli/node_modules/tslib": { "version": "1.14.1", "dev": true, @@ -46121,11 +45995,6 @@ "uuid": "dist/bin/uuid" } }, - "node_modules/netlify-cli/node_modules/v8-compile-cache-lib": { - "version": "3.0.1", - "dev": true, - "license": "MIT" - }, "node_modules/netlify-cli/node_modules/validate-npm-package-license": { "version": "3.0.4", "dev": true, @@ -46666,14 +46535,6 @@ "fd-slicer": "~1.1.0" } }, - "node_modules/netlify-cli/node_modules/yn": { - "version": "3.1.1", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/netlify-cli/node_modules/zip-stream": { "version": "6.0.1", "dev": true, @@ -47527,6 +47388,16 @@ "node": ">=8" } }, + "node_modules/nodemailer": { + "version": "6.9.16", + "resolved": "https://registry.npmjs.org/nodemailer/-/nodemailer-6.9.16.tgz", + "integrity": "sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==", + "optional": true, + "peer": true, + "engines": { + "node": ">=6.0.0" + } + }, "node_modules/nopt": { "version": "1.0.10", "license": "MIT", @@ -55183,8 +55054,9 @@ }, "node_modules/ts-node": { "version": "10.9.2", + "resolved": "https://registry.npmjs.org/ts-node/-/ts-node-10.9.2.tgz", + "integrity": "sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==", "devOptional": true, - "license": "MIT", "dependencies": { "@cspotcode/source-map-support": "^0.8.0", "@tsconfig/node10": "^1.0.7", diff --git a/site/gatsby-site/package.json b/site/gatsby-site/package.json index cf05160889..d0cbea3a0e 100644 --- a/site/gatsby-site/package.json +++ b/site/gatsby-site/package.json @@ -139,7 +139,8 @@ "test:api:ci": "jest --runInBand --forceExit", "codegen": "graphql-codegen --config codegen.ts", "start:api": "node --env-file=.env --watch -r ts-node/register server/index.ts", - "start:memory-mongo": "node --env-file=.env -r ts-node/register playwright/memory-mongo.ts" + "start:memory-mongo": "npx tsx --env-file=.env playwright/memory-mongo.ts", + "clean": "gatsby clean" }, "devDependencies": { "@babel/plugin-proposal-export-default-from": "^7.23.3", From fb90749d23a3ab99c94d8de49884bbacf9f0c287 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 15 Nov 2024 20:52:21 -0300 Subject: [PATCH 26/99] Update test files --- .../playwright/e2e-full/account.spec.ts | 6 ++-- .../playwright/e2e-full/admin.spec.ts | 6 ++-- .../e2e-full/apps/checklistForm.spec.ts | 16 ++++----- .../e2e-full/apps/checklistIndex.spec.ts | 10 +++--- .../e2e-full/apps/classifications.spec.ts | 2 +- .../playwright/e2e-full/apps/csettool.spec.ts | 2 +- .../e2e-full/apps/incidents.spec.ts | 4 +-- .../e2e-full/apps/submitted.spec.ts | 36 +++++++++---------- .../playwright/e2e-full/apps/variants.spec.ts | 8 ++--- .../playwright/e2e-full/cite.spec.ts | 9 +++-- .../playwright/e2e-full/citeEdit.spec.ts | 10 +++--- .../e2e-full/classificationsEditor.spec.ts | 10 +++--- .../playwright/e2e-full/entity.spec.ts | 8 ++--- .../playwright/e2e-full/entityEdit.spec.ts | 4 +-- .../e2e-full/incidentVariants.spec.ts | 8 ++--- .../e2e-full/incidents/edit.spec.ts | 2 +- .../playwright/e2e-full/incidents/new.spec.ts | 4 +-- .../playwright/e2e-full/submit.spec.ts | 6 ++-- .../playwright/e2e-full/subscription.spec.ts | 20 +++++------ .../playwright/e2e-full/unsubscribe.spec.ts | 2 +- .../playwright/e2e-full/utils.spec.ts | 2 +- .../playwright/seeds/auth/users.ts | 2 +- site/gatsby-site/playwright/utils.ts | 7 ++-- 23 files changed, 90 insertions(+), 94 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/account.spec.ts b/site/gatsby-site/playwright/e2e-full/account.spec.ts index a4d734c64f..8df784ff4a 100644 --- a/site/gatsby-site/playwright/e2e-full/account.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/account.spec.ts @@ -11,7 +11,7 @@ test.describe('Account', () => { test('Should display account information if the user is logged in', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'Test', last_name: 'User' } }); + await login({ customData: { roles: ['admin'], first_name: 'Test', last_name: 'User' } }); await page.goto(url); @@ -27,7 +27,7 @@ test.describe('Account', () => { test('Should allow editing user data', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'Test', last_name: 'User' } }); + await login({ customData: { roles: ['admin'], first_name: 'Test', last_name: 'User' } }); await page.goto(url); @@ -46,7 +46,7 @@ test.describe('Account', () => { test('Should show edit modal if query parameter is set', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'Test', last_name: 'User' } }); + await login({ customData: { roles: ['admin'], first_name: 'Test', last_name: 'User' } }); await page.goto(url + '?askToCompleteProfile=1'); diff --git a/site/gatsby-site/playwright/e2e-full/admin.spec.ts b/site/gatsby-site/playwright/e2e-full/admin.spec.ts index c6b914e434..d3f6f06013 100644 --- a/site/gatsby-site/playwright/e2e-full/admin.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/admin.spec.ts @@ -18,7 +18,7 @@ test.describe('Admin', () => { 'Should display a list of users, their roles and allow edition', async ({ page, login, skipOnEmptyEnvironment }) => { - const [userId] = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + const [userId] = await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); const users = [{ userId, first_name: 'John', last_name: 'Doe', roles: ['admin'] }]; await page.goto(baseUrl); @@ -52,7 +52,7 @@ test.describe('Admin', () => { 'Should display New Incident button', async ({ page, login, skipOnEmptyEnvironment }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); await page.goto(baseUrl); @@ -66,7 +66,7 @@ test.describe('Admin', () => { 'Should filter results', async ({ page, login, skipOnEmptyEnvironment }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); await page.goto(baseUrl); diff --git a/site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts index c1fc302235..8109f43d5b 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts @@ -37,7 +37,7 @@ test.describe('Checklists App Form', () => { test('Should have read-only access for logged-in non-owners', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await conditionalIntercept( page, @@ -57,7 +57,7 @@ test.describe('Checklists App Form', () => { test('Should allow editing for owner', async ({ page, login }) => { - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await conditionalIntercept( @@ -95,7 +95,7 @@ test.describe('Checklists App Form', () => { test('Should trigger GraphQL upsert query on adding tag', async ({ page, login }) => { - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await conditionalIntercept( page, @@ -125,7 +125,7 @@ test.describe('Checklists App Form', () => { test('Should trigger GraphQL update on removing tag', async ({ page, login }) => { - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await conditionalIntercept( page, @@ -162,7 +162,7 @@ test.describe('Checklists App Form', () => { test('Should trigger UI update on adding and removing tag', async ({ page, login }) => { - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await conditionalIntercept( page, @@ -208,7 +208,7 @@ test.describe('Checklists App Form', () => { test('Should change sort order of risk items', async ({ page, login }) => { - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await page.setViewportSize({ width: 1920, height: 1080 }); @@ -244,7 +244,7 @@ test.describe('Checklists App Form', () => { await init(); - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await conditionalIntercept( page, @@ -310,7 +310,7 @@ test.describe('Checklists App Form', () => { // TODO: test is crashing not sure if it is a bug or missing seed data test.skip('Should persist open state on editing query', async ({ page, login }) => { - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await conditionalIntercept( page, diff --git a/site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts index dfb5cd8881..ab292bf357 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts @@ -8,7 +8,7 @@ test.describe('Checklists App Index', () => { test('Should sort checklists', async ({ page, login }) => { - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await conditionalIntercept( page, @@ -72,7 +72,7 @@ test.describe('Checklists App Index', () => { test('Should display New Checklist button as logged-in user', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await page.goto(url); await expect(page.locator(newChecklistButtonSelector)).toBeVisible(); @@ -80,7 +80,7 @@ test.describe('Checklists App Index', () => { test.skip('Should show delete buttons only for owned checklists', async ({ page, login }) => { - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await conditionalIntercept( page, @@ -142,7 +142,7 @@ test.describe('Checklists App Index', () => { test('Should show toast on error fetching risks', async ({ page, login }) => { - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await conditionalIntercept( page, @@ -195,7 +195,7 @@ test.describe('Checklists App Index', () => { test('Should show toast on error creating checklist', async ({ page, login }) => { - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await conditionalIntercept( page, diff --git a/site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts index 364c5d2cd4..185a168a98 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts @@ -19,7 +19,7 @@ test.describe('Classifications App', () => { test('Successfully edit a CSET classification', async ({ page, login }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); + await login({ customData: { roles: ['admin'] } }); await page.goto(url); diff --git a/site/gatsby-site/playwright/e2e-full/apps/csettool.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/csettool.spec.ts index 62e68e4798..b3d4b02f38 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/csettool.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/csettool.spec.ts @@ -14,7 +14,7 @@ test.describe('CSET tool', () => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); + await login({ customData: { roles: ['admin'] } }); await page.goto(url); await page.waitForSelector('[data-cy="column-Incident Number"]'); diff --git a/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts index a5635d7e82..5d40cd3ae1 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts @@ -25,7 +25,7 @@ test.describe('Incidents App', () => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); await conditionalIntercept( page, @@ -86,7 +86,7 @@ test.describe('Incidents App', () => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] } }); await conditionalIntercept( page, diff --git a/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts index f3e665a520..9818473506 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts @@ -76,7 +76,7 @@ test.describe('Submitted reports', () => { test('Promotes a submission to a new report and links it to a new incident', async ({ page, login }) => { await init(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); @@ -108,7 +108,7 @@ test.describe('Submitted reports', () => { await init(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); @@ -140,7 +140,7 @@ test.describe('Submitted reports', () => { await init(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); @@ -175,7 +175,7 @@ test.describe('Submitted reports', () => { await init(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); @@ -205,7 +205,7 @@ test.describe('Submitted reports', () => { const submissions = await getSubmissions(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); await page.goto(url + `?editSubmission=${submissions[0]._id}`); @@ -227,7 +227,7 @@ test.describe('Submitted reports', () => { const submissions = await getSubmissions(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); await page.goto(url + `?editSubmission=${submissions[0]._id}`); @@ -246,7 +246,7 @@ test.describe('Submitted reports', () => { await init(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); @@ -288,7 +288,7 @@ test.describe('Submitted reports', () => { await init({ aiidprod: { submissions } }); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); await page.goto(url + `?editSubmission=5d34b8c29ced494f010ed469`); @@ -330,7 +330,7 @@ test.describe('Submitted reports', () => { await init({ aiidprod: { submissions } }); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); await page.goto(url + `?editSubmission=5d34b8c29ced494f010ed469`); @@ -345,7 +345,7 @@ test.describe('Submitted reports', () => { await init(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); @@ -358,7 +358,7 @@ test.describe('Submitted reports', () => { await init(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); @@ -371,7 +371,7 @@ test.describe('Submitted reports', () => { await init(); - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); await page.goto(url); @@ -398,7 +398,7 @@ test.describe('Submitted reports', () => { await init(); - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); const submissions: DBSubmission[] = [{ _id: new ObjectId('63f3d58c26ab981f33b3f9c7'), @@ -454,7 +454,7 @@ test.describe('Submitted reports', () => { await init(); - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); const submissions: DBSubmission[] = Array.from(Array(10).keys()).map(i => { @@ -509,7 +509,7 @@ test.describe('Submitted reports', () => { await init(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); @@ -523,7 +523,7 @@ test.describe('Submitted reports', () => { await init(); - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); const submissions: DBSubmission[] = [{ _id: new ObjectId('63f3d58c26ab981f33b3f9c7'), @@ -564,7 +564,7 @@ test.describe('Submitted reports', () => { test('Edits a submission - links to existing incident - Incident Data should be hidden', async ({ page, login }) => { await init(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['incident_editor'] } }); await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); @@ -580,7 +580,7 @@ test.describe('Submitted reports', () => { test('Should keep all the appropriate fields from the Submission', async ({ page, login }) => { await init(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); diff --git a/site/gatsby-site/playwright/e2e-full/apps/variants.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/variants.spec.ts index 628f50a751..cb29cf7668 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/variants.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/variants.spec.ts @@ -42,7 +42,7 @@ test.describe('Variants App', () => { test('Should Delete a Variant - Incident Editor user', async ({ page, login }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['incident_editor'], first_name: 'John', last_name: 'Doe' } }); + await login({ customData: { roles: ['incident_editor'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); @@ -61,7 +61,7 @@ test.describe('Variants App', () => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['incident_editor'], first_name: 'John', last_name: 'Doe' } }); + await login({ customData: { roles: ['incident_editor'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); @@ -77,7 +77,7 @@ test.describe('Variants App', () => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['incident_editor'], first_name: 'John', last_name: 'Doe' } }); + await login({ customData: { roles: ['incident_editor'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); @@ -93,7 +93,7 @@ test.describe('Variants App', () => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['incident_editor'], first_name: 'John', last_name: 'Doe' } }); + await login({ customData: { roles: ['incident_editor'], first_name: 'John', last_name: 'Doe' } }); const newDatePublished = '2000-01-01'; const newText = 'New text example with more than 80 characters. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'; diff --git a/site/gatsby-site/playwright/e2e-full/cite.spec.ts b/site/gatsby-site/playwright/e2e-full/cite.spec.ts index 0b1f8f922e..03f479cf2f 100644 --- a/site/gatsby-site/playwright/e2e-full/cite.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/cite.spec.ts @@ -4,7 +4,6 @@ import { gql } from '@apollo/client'; import { expect } from '@playwright/test'; import config from '../config'; import { init } from '../memory-mongo'; -import { DBIncident } from '../seeds/aiidprod/incidents'; test.describe('Cite pages', () => { const discoverUrl = '/apps/discover'; @@ -157,7 +156,7 @@ test.describe('Cite pages', () => { await init(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await page.goto('/cite/3'); @@ -393,7 +392,7 @@ test.describe('Cite pages', () => { await init(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await conditionalIntercept( page, @@ -471,7 +470,7 @@ test.describe('Cite pages', () => { await init(); - const [userId, accessToken] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['subscriber'] } }); + const [userId, accessToken] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['subscriber'] } }); await page.goto('/cite/3'); @@ -542,7 +541,7 @@ test.describe('Cite pages', () => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); await conditionalIntercept( page, diff --git a/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts b/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts index 7ae3d42fba..c6b7df9916 100644 --- a/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts @@ -20,7 +20,7 @@ test.describe('Edit report', () => { test('Should load and update report values', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); + await login({ customData: { roles: ['admin'] } }); // TODO: delete once we implement the new report history // it should be done inside the report mutation resolver @@ -149,7 +149,7 @@ test.describe('Edit report', () => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); + await login({ customData: { roles: ['admin'] } }); await conditionalIntercept( page, @@ -232,7 +232,7 @@ test.describe('Edit report', () => { test('Should delete incident report', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); + await login({ customData: { roles: ['admin'] } }); await page.goto(url); @@ -323,7 +323,7 @@ test.describe('Edit report', () => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); + await login({ customData: { roles: ['admin'] } }); await conditionalIntercept( page, @@ -370,7 +370,7 @@ test.describe('Edit report', () => { test('Should display the report image', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); + await login({ customData: { roles: ['admin'] } }); await page.goto(url); await page.locator('[data-cy="image-preview-figure"] img').waitFor(); diff --git a/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts b/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts index 34320cfc88..20745ab4e6 100644 --- a/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts @@ -78,7 +78,7 @@ test.describe('Classifications Editor', () => { test('Should show classifications editor on incident page and save edited values', async ({ page, login, skipOnEmptyEnvironment }) => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); + await login({ customData: { roles: ['admin'] } }); await page.goto(incidentURL); @@ -111,7 +111,7 @@ test.describe('Classifications Editor', () => { test('Should show classifications editor on report page and save edited values', async ({ page, login, skipOnEmptyEnvironment }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); await page.goto(reportURL); await waitForRequest('FindClassifications'); @@ -146,7 +146,7 @@ test.describe('Classifications Editor', () => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); await page.goto(reportURL); await waitForRequest('FindClassifications'); @@ -194,7 +194,7 @@ test.describe('Classifications Editor', () => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); await page.goto(`/cite/${incident_id}`); @@ -271,7 +271,7 @@ test.describe('Classifications Editor', () => { } test('Should synchronize duplicate fields', async ({ page, login, skipOnEmptyEnvironment }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); await page.goto(incidentURL); await waitForRequest('FindClassifications'); diff --git a/site/gatsby-site/playwright/e2e-full/entity.spec.ts b/site/gatsby-site/playwright/e2e-full/entity.spec.ts index 550a7a83c1..1366a28b52 100644 --- a/site/gatsby-site/playwright/e2e-full/entity.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/entity.spec.ts @@ -21,7 +21,7 @@ test.describe('Individual Entity page', () => { test('Should subscribe to new Entity incidents (authenticated user)', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); @@ -34,7 +34,7 @@ test.describe('Individual Entity page', () => { await init(); - const [userId] = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + const [userId] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); const subscriptions: DBSubscription[] = [ { @@ -69,7 +69,7 @@ test.describe('Individual Entity page', () => { test('Should display Edit button for Admin users', async ({ page, login, skipOnEmptyEnvironment }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); await expect(page.locator('[data-cy="edit-entity-btn"]')).toHaveAttribute('href', `/entities/edit?entity_id=${entity.entity_id}`); @@ -81,7 +81,7 @@ test.describe('Individual Entity page', () => { test('Should not display Edit button for non-admin users', async ({ page, login, skipOnEmptyEnvironment }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['subscriber'], first_name: 'John', last_name: 'Doe' } }); + await login({ customData: { roles: ['subscriber'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); await expect(page.locator('[data-cy="edit-entity-btn"]')).not.toBeVisible(); diff --git a/site/gatsby-site/playwright/e2e-full/entityEdit.spec.ts b/site/gatsby-site/playwright/e2e-full/entityEdit.spec.ts index e2b0559fbe..26fd91af37 100644 --- a/site/gatsby-site/playwright/e2e-full/entityEdit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/entityEdit.spec.ts @@ -18,7 +18,7 @@ test.describe('Edit Entity', () => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await page.goto(url); @@ -67,7 +67,7 @@ test.describe('Edit Entity', () => { test('Should display an error message when editing Entity fails', async ({ page, login, skipOnEmptyEnvironment }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await page.goto(url); diff --git a/site/gatsby-site/playwright/e2e-full/incidentVariants.spec.ts b/site/gatsby-site/playwright/e2e-full/incidentVariants.spec.ts index 408f4884d9..6d6ad62af5 100644 --- a/site/gatsby-site/playwright/e2e-full/incidentVariants.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/incidentVariants.spec.ts @@ -108,7 +108,7 @@ test.describe('Variants pages', () => { test('Should Approve Variant - Incident Editor user', async ({ page, login }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] } }); await page.goto(url); @@ -135,7 +135,7 @@ test.describe('Variants pages', () => { test('Should Reject Variant - Incident Editor user', async ({ page, login }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] } }); await page.goto(url); @@ -161,7 +161,7 @@ test.describe('Variants pages', () => { test('Should Save Variant - Incident Editor user', async ({ page, login }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] } }); await page.goto(url); @@ -187,7 +187,7 @@ test.describe('Variants pages', () => { test('Should Delete Variant - Incident Editor user', async ({ page, login }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] } }); + await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] } }); await page.goto(url); diff --git a/site/gatsby-site/playwright/e2e-full/incidents/edit.spec.ts b/site/gatsby-site/playwright/e2e-full/incidents/edit.spec.ts index bc5fec2161..a0a00003b2 100644 --- a/site/gatsby-site/playwright/e2e-full/incidents/edit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/incidents/edit.spec.ts @@ -15,7 +15,7 @@ test.describe('Incidents', () => { } }); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); diff --git a/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts b/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts index 46a915d18a..b4f7276763 100644 --- a/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts @@ -12,7 +12,7 @@ test.describe('New Incident page', () => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); @@ -53,7 +53,7 @@ test.describe('New Incident page', () => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); const newIncidentId = 4; diff --git a/site/gatsby-site/playwright/e2e-full/submit.spec.ts b/site/gatsby-site/playwright/e2e-full/submit.spec.ts index 309b4c65ab..2be4a0fb82 100644 --- a/site/gatsby-site/playwright/e2e-full/submit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/submit.spec.ts @@ -159,7 +159,7 @@ test.describe('The Submit form', () => { await init(); - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Cesar', last_name: 'Ito', roles: ['admin'] } }); + const [userId] = await login({ customData: { first_name: 'Cesar', last_name: 'Ito', roles: ['admin'] } }); await conditionalIntercept( page, @@ -315,7 +315,7 @@ test.describe('The Submit form', () => { await expect(page.locator(':text("Report successfully added to review queue")')).toBeVisible(); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); await page.goto('/apps/submitted'); @@ -396,7 +396,7 @@ test.describe('The Submit form', () => { test('Should submit a submission and link it to the current user id', async ({ page, login, skipOnEmptyEnvironment }) => { - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); const values = { url: 'https://incidentdatabase.ai', diff --git a/site/gatsby-site/playwright/e2e-full/subscription.spec.ts b/site/gatsby-site/playwright/e2e-full/subscription.spec.ts index bca263bf6f..0e63208c98 100644 --- a/site/gatsby-site/playwright/e2e-full/subscription.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/subscription.spec.ts @@ -13,7 +13,7 @@ test.describe('Subscriptions', () => { await init(); - const [userId] = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + const [userId] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); const subscriptions: DBSubscription[] = [ { @@ -36,7 +36,7 @@ test.describe('Subscriptions', () => { test("Incident Updates: Should display a information message if the user doesn't have subscriptions", async ({ page, login }) => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); @@ -47,7 +47,7 @@ test.describe('Subscriptions', () => { await init(); - const [userId, accessToken] = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + const [userId, accessToken] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); const subscriptions: DBSubscription[] = [ { @@ -96,7 +96,7 @@ test.describe('Subscriptions', () => { test('New Incidents: Should display the switch toggle off if user does not have a subscription', async ({ page, login }) => { - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); @@ -109,7 +109,7 @@ test.describe('Subscriptions', () => { await init(); - const [userId] = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + const [userId] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); const subscriptions: DBSubscription[] = [ { @@ -131,7 +131,7 @@ test.describe('Subscriptions', () => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['subscriber'], first_name: 'John', last_name: 'Doe' } }); + await login({ customData: { roles: ['subscriber'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); @@ -152,7 +152,7 @@ test.describe('Subscriptions', () => { await init(); - const [userId] = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + const [userId] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); const subscriptions: DBSubscription[] = [ { @@ -182,7 +182,7 @@ test.describe('Subscriptions', () => { await init(); - const [userId] = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + const [userId] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); const subscriptions: DBSubscription[] = [ { @@ -212,7 +212,7 @@ test.describe('Subscriptions', () => { await init(); - await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); await page.goto(url); @@ -224,7 +224,7 @@ test.describe('Subscriptions', () => { await init(); - const [userId, accessToken] = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + const [userId, accessToken] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); const subscriptions: DBSubscription[] = [ { diff --git a/site/gatsby-site/playwright/e2e-full/unsubscribe.spec.ts b/site/gatsby-site/playwright/e2e-full/unsubscribe.spec.ts index dd78db55e2..b5bd53712e 100644 --- a/site/gatsby-site/playwright/e2e-full/unsubscribe.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/unsubscribe.spec.ts @@ -11,7 +11,7 @@ test.describe('Unsubscribe pages', () => { test.beforeEach(async ({ page, login }) => { if (!userId) { - [userId, accessToken] = await login(process.env.E2E_ADMIN_USERNAME, process.env.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + [userId, accessToken] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); } }); diff --git a/site/gatsby-site/playwright/e2e-full/utils.spec.ts b/site/gatsby-site/playwright/e2e-full/utils.spec.ts index ba8a5a8004..42ff87990e 100644 --- a/site/gatsby-site/playwright/e2e-full/utils.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/utils.spec.ts @@ -6,7 +6,7 @@ test.describe('Test playwright utils', () => { test('Login fixture should mock user and roles', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['sarasa'], first_name: 'Fula', last_name: 'Nito' } }); + await login({ customData: { roles: ['sarasa'], first_name: 'Fula', last_name: 'Nito' } }); await page.goto('/account'); diff --git a/site/gatsby-site/playwright/seeds/auth/users.ts b/site/gatsby-site/playwright/seeds/auth/users.ts index ed4ee697aa..834245505c 100644 --- a/site/gatsby-site/playwright/seeds/auth/users.ts +++ b/site/gatsby-site/playwright/seeds/auth/users.ts @@ -4,7 +4,7 @@ const users = [ { _id: new ObjectId("6737a6e881955aa4905ccb04"), email: "test.user@incidentdatabase.ai", - emailVerified: "2024-11-15T21:41:04.245Z", + emailVerified: new Date("2024-11-15T21:41:04.245Z"), } ] diff --git a/site/gatsby-site/playwright/utils.ts b/site/gatsby-site/playwright/utils.ts index bcd2004fca..a02a3ee6e8 100644 --- a/site/gatsby-site/playwright/utils.ts +++ b/site/gatsby-site/playwright/utils.ts @@ -19,7 +19,7 @@ export type Options = { defaultItem: string }; type TestFixtures = { skipOnEmptyEnvironment: () => Promise, runOnlyOnEmptyEnvironment: () => Promise, - login: (username: string, password: string, options?: { customData?: Record }) => Promise, + login: (options?: { email?: string, customData?: Record }) => Promise, retryDelay?: [({ }: {}, use: () => Promise, testInfo: { retry: number }) => Promise, { auto: true }], }; @@ -114,7 +114,7 @@ export const test = base.extend({ // TODO: this should be removed since we pass the username and password as arguments testInfo.skip(!config.E2E_ADMIN_USERNAME || !config.E2E_ADMIN_PASSWORD, 'E2E_ADMIN_USERNAME or E2E_ADMIN_PASSWORD not set'); - await use(async (email, password, { customData } = {}) => { + await use(async ({ email = "test.user@incidentdatabase.ai", customData = null }) => { const magicLink = await generateMagicLink(email); @@ -138,9 +138,6 @@ export const test = base.extend({ } return [userId!, sessionToken!]; - - // to be able to restore session state, we'll need to refactor when we perform the login call, but that's for another PR - // https://playwright.dev/docs/auth#avoid-authentication-in-some-tests }) }, From 0e8df00e11a5565635660627523db647b329f4c4 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 19 Nov 2024 19:38:21 -0300 Subject: [PATCH 27/99] update package lock --- site/gatsby-site/package-lock.json | 101 +++++++++++++++++++++++++++++ 1 file changed, 101 insertions(+) diff --git a/site/gatsby-site/package-lock.json b/site/gatsby-site/package-lock.json index 0943655fdf..904f4d6b97 100644 --- a/site/gatsby-site/package-lock.json +++ b/site/gatsby-site/package-lock.json @@ -107,6 +107,7 @@ "react-zoom-pan-pinch": "^2.6.1", "realm-web": "^1.3.0", "rehype-raw": "^7.0.0", + "rehype-slug": "^6.0.0", "rehype-truncate": "^1.2.2", "remark": "^13.0.0", "remark-gfm": "^3.0.1", @@ -25987,6 +25988,26 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/hast-util-heading-rank": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", + "integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/hast-util-heading-rank/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, "node_modules/hast-util-parse-selector": { "version": "4.0.0", "license": "MIT", @@ -51383,6 +51404,86 @@ "url": "https://opencollective.com/unified" } }, + "node_modules/rehype-slug": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", + "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", + "dependencies": { + "@types/hast": "^3.0.0", + "github-slugger": "^2.0.0", + "hast-util-heading-rank": "^3.0.0", + "hast-util-to-string": "^3.0.0", + "unist-util-visit": "^5.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug/node_modules/@types/hast": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", + "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "dependencies": { + "@types/unist": "*" + } + }, + "node_modules/rehype-slug/node_modules/@types/unist": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", + "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + }, + "node_modules/rehype-slug/node_modules/hast-util-to-string": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", + "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "dependencies": { + "@types/hast": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug/node_modules/unist-util-is": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", + "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "dependencies": { + "@types/unist": "^3.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug/node_modules/unist-util-visit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", + "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0", + "unist-util-visit-parents": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, + "node_modules/rehype-slug/node_modules/unist-util-visit-parents": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", + "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "dependencies": { + "@types/unist": "^3.0.0", + "unist-util-is": "^6.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/unified" + } + }, "node_modules/rehype-stringify": { "version": "9.0.3", "license": "MIT", From 351f6a98a27d008562206b244a0bde825b87c3bc Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 19 Nov 2024 20:54:37 -0300 Subject: [PATCH 28/99] Remove unused function --- .../playwright/e2e-full/account.spec.ts | 14 +++++++++----- site/gatsby-site/playwright/utils.ts | 15 ++++++++------- .../src/components/users/UserEditModal.js | 4 +--- 3 files changed, 18 insertions(+), 15 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/account.spec.ts b/site/gatsby-site/playwright/e2e-full/account.spec.ts index 8df784ff4a..c1e26c8e88 100644 --- a/site/gatsby-site/playwright/e2e-full/account.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/account.spec.ts @@ -1,6 +1,7 @@ import { test } from '../utils'; import { expect } from '@playwright/test'; import config from '../config'; +import { init } from '../memory-mongo'; test.describe('Account', () => { const url = '/account'; @@ -11,7 +12,9 @@ test.describe('Account', () => { test('Should display account information if the user is logged in', async ({ page, login }) => { - await login({ customData: { roles: ['admin'], first_name: 'Test', last_name: 'User' } }); + await init(); + + await login(); await page.goto(url); @@ -25,12 +28,13 @@ test.describe('Account', () => { await expect(page.locator('a:text-is("Log out")')).toBeVisible(); }); - test('Should allow editing user data', async ({ page, login }) => { + test('Should allow editing user data if user is admin', async ({ page, login }) => { - await login({ customData: { roles: ['admin'], first_name: 'Test', last_name: 'User' } }); + await init(); - await page.goto(url); + await login(); + await page.goto(url); await page.locator('button:has-text("Edit")').click(); @@ -46,7 +50,7 @@ test.describe('Account', () => { test('Should show edit modal if query parameter is set', async ({ page, login }) => { - await login({ customData: { roles: ['admin'], first_name: 'Test', last_name: 'User' } }); + await login(); await page.goto(url + '?askToCompleteProfile=1'); diff --git a/site/gatsby-site/playwright/utils.ts b/site/gatsby-site/playwright/utils.ts index 2c2ecde54c..386e85a435 100644 --- a/site/gatsby-site/playwright/utils.ts +++ b/site/gatsby-site/playwright/utils.ts @@ -114,7 +114,7 @@ export const test = base.extend({ // TODO: this should be removed since we pass the username and password as arguments testInfo.skip(!config.E2E_ADMIN_USERNAME || !config.E2E_ADMIN_PASSWORD, 'E2E_ADMIN_USERNAME or E2E_ADMIN_PASSWORD not set'); - await use(async ({ email = "test.user@incidentdatabase.ai", customData = null }) => { + await use(async ({ email = "test.user@incidentdatabase.ai", customData = null } = {}) => { const magicLink = await generateMagicLink(email); @@ -319,11 +319,12 @@ export async function fillAutoComplete(page: Page, selector: string, sequence: s }).toPass(); } +//TODO: This function should be removed and the languages should be fetched from the config files export function getLanguages() { - return [ - { code: 'en', hrefLang: 'en-US', name: 'English', localName: 'English', langDir: 'ltr', dateFormat: 'MM/DD/YYYY' }, - { code: 'es', hrefLang: 'es', name: 'Spanish', localName: 'Español', langDir: 'ltr', dateFormat: 'DD-MM-YYYY' }, - { code: 'fr', hrefLang: 'fr', name: 'French', localName: 'Français', langDir: 'ltr', dateFormat: 'DD-MM-YYYY' }, - { code: 'ja', hrefLang: 'ja', name: 'Japanese', localName: '日本語', langDir: 'ltr', dateFormat: 'YYYY/MM/DD' }, - ]; + return [ + { code: 'en', hrefLang: 'en-US', name: 'English', localName: 'English', langDir: 'ltr', dateFormat: 'MM/DD/YYYY' }, + { code: 'es', hrefLang: 'es', name: 'Spanish', localName: 'Español', langDir: 'ltr', dateFormat: 'DD-MM-YYYY' }, + { code: 'fr', hrefLang: 'fr', name: 'French', localName: 'Français', langDir: 'ltr', dateFormat: 'DD-MM-YYYY' }, + { code: 'ja', hrefLang: 'ja', name: 'Japanese', localName: '日本語', langDir: 'ltr', dateFormat: 'YYYY/MM/DD' }, + ]; } diff --git a/site/gatsby-site/src/components/users/UserEditModal.js b/site/gatsby-site/src/components/users/UserEditModal.js index 0c692b3e3c..b10dda3d22 100644 --- a/site/gatsby-site/src/components/users/UserEditModal.js +++ b/site/gatsby-site/src/components/users/UserEditModal.js @@ -54,7 +54,7 @@ export default function UserEditModal({ onClose, userId, alertTitle = '', alertT variables: { filter: { userId: { EQ: userId } } }, }); - const { user, isRole } = useUserContext(); + const { isRole } = useUserContext(); const [updateUserRoles] = useMutation(UPDATE_USER_ROLES); @@ -76,8 +76,6 @@ export default function UserEditModal({ onClose, userId, alertTitle = '', alertT }, }); - await user.refreshCustomData(); - addToast({ message: <>User updated., severity: SEVERITY.success, From 2ada3ec09aef8074bfc2e8c87fb2d6d5e7072dcb Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 19 Nov 2024 21:20:47 -0300 Subject: [PATCH 29/99] Fix wrong seesion fetch function --- .../playwright/e2e-full/apps/classifications.spec.ts | 2 +- .../playwright/e2e-full/apps/csettool.spec.ts | 2 +- site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts | 10 +++++----- .../playwright/e2e-full/classificationsEditor.spec.ts | 2 +- site/gatsby-site/playwright/utils.ts | 7 ++++--- 5 files changed, 12 insertions(+), 11 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts index 185a168a98..3514bdbb27 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/classifications.spec.ts @@ -19,7 +19,7 @@ test.describe('Classifications App', () => { test('Successfully edit a CSET classification', async ({ page, login }) => { - await login({ customData: { roles: ['admin'] } }); + await login(); await page.goto(url); diff --git a/site/gatsby-site/playwright/e2e-full/apps/csettool.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/csettool.spec.ts index b3d4b02f38..c00bc970a3 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/csettool.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/csettool.spec.ts @@ -14,7 +14,7 @@ test.describe('CSET tool', () => { await init(); - await login({ customData: { roles: ['admin'] } }); + await login(); await page.goto(url); await page.waitForSelector('[data-cy="column-Incident Number"]'); diff --git a/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts b/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts index c6b7df9916..d85a8d5406 100644 --- a/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts @@ -20,7 +20,7 @@ test.describe('Edit report', () => { test('Should load and update report values', async ({ page, login }) => { - await login({ customData: { roles: ['admin'] } }); + await login(); // TODO: delete once we implement the new report history // it should be done inside the report mutation resolver @@ -149,7 +149,7 @@ test.describe('Edit report', () => { - await login({ customData: { roles: ['admin'] } }); + await login(); await conditionalIntercept( page, @@ -232,7 +232,7 @@ test.describe('Edit report', () => { test('Should delete incident report', async ({ page, login }) => { - await login({ customData: { roles: ['admin'] } }); + await login(); await page.goto(url); @@ -323,7 +323,7 @@ test.describe('Edit report', () => { - await login({ customData: { roles: ['admin'] } }); + await login(); await conditionalIntercept( page, @@ -370,7 +370,7 @@ test.describe('Edit report', () => { test('Should display the report image', async ({ page, login }) => { - await login({ customData: { roles: ['admin'] } }); + await login(); await page.goto(url); await page.locator('[data-cy="image-preview-figure"] img').waitFor(); diff --git a/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts b/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts index 20745ab4e6..b6969a0457 100644 --- a/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts @@ -78,7 +78,7 @@ test.describe('Classifications Editor', () => { test('Should show classifications editor on incident page and save edited values', async ({ page, login, skipOnEmptyEnvironment }) => { await init(); - await login({ customData: { roles: ['admin'] } }); + await login(); await page.goto(incidentURL); diff --git a/site/gatsby-site/playwright/utils.ts b/site/gatsby-site/playwright/utils.ts index 386e85a435..6b2f52932f 100644 --- a/site/gatsby-site/playwright/utils.ts +++ b/site/gatsby-site/playwright/utils.ts @@ -7,6 +7,7 @@ import fs from 'fs'; import path from 'path'; import * as memoryMongo from './memory-mongo'; import * as crypto from 'node:crypto'; +import { ObjectId } from 'bson'; declare module '@playwright/test' { interface Request { @@ -76,7 +77,7 @@ async function getUserIdFromAuth(email: string) { return id; } -async function getSessionToken(email: string) { +async function getSessionToken(userId: string) { let token: string = null; @@ -84,9 +85,9 @@ async function getSessionToken(email: string) { const collection = client.db('auth').collection('sessions'); - const session = await collection.findOne({}, { sort: { expires: -1 } }); + const session = await collection.findOne({ userId: new ObjectId(userId) }, { sort: { expires: -1 } }); - token = session.token; + token = session.sessionToken; }); return token; From 16e29c579cd3867eb48bc3be6288231c9d07e15f Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 19 Nov 2024 21:40:29 -0300 Subject: [PATCH 30/99] Update tests --- site/gatsby-site/playwright/e2e-full/admin.spec.ts | 6 +++--- .../playwright/e2e-full/apps/incidents.spec.ts | 2 +- site/gatsby-site/playwright/e2e-full/cite.spec.ts | 3 ++- .../playwright/e2e-full/classificationsEditor.spec.ts | 8 ++++---- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/admin.spec.ts b/site/gatsby-site/playwright/e2e-full/admin.spec.ts index d3f6f06013..a4295d22a6 100644 --- a/site/gatsby-site/playwright/e2e-full/admin.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/admin.spec.ts @@ -18,7 +18,7 @@ test.describe('Admin', () => { 'Should display a list of users, their roles and allow edition', async ({ page, login, skipOnEmptyEnvironment }) => { - const [userId] = await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + const [userId] = await login(); const users = [{ userId, first_name: 'John', last_name: 'Doe', roles: ['admin'] }]; await page.goto(baseUrl); @@ -52,7 +52,7 @@ test.describe('Admin', () => { 'Should display New Incident button', async ({ page, login, skipOnEmptyEnvironment }) => { - await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login(); await page.goto(baseUrl); @@ -66,7 +66,7 @@ test.describe('Admin', () => { 'Should filter results', async ({ page, login, skipOnEmptyEnvironment }) => { - await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login(); await page.goto(baseUrl); diff --git a/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts index 5d40cd3ae1..52bed75ef5 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts @@ -25,7 +25,7 @@ test.describe('Incidents App', () => { await init(); - await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login(); await conditionalIntercept( page, diff --git a/site/gatsby-site/playwright/e2e-full/cite.spec.ts b/site/gatsby-site/playwright/e2e-full/cite.spec.ts index e880740e89..16f34cef9c 100644 --- a/site/gatsby-site/playwright/e2e-full/cite.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/cite.spec.ts @@ -4,6 +4,7 @@ import { gql } from '@apollo/client'; import { expect } from '@playwright/test'; import config from '../config'; import { init } from '../memory-mongo'; +import { DBIncident } from '../../server/interfaces'; test.describe('Cite pages', () => { const discoverUrl = '/apps/discover'; @@ -542,7 +543,7 @@ test.describe('Cite pages', () => { await init(); - await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login(); await conditionalIntercept( page, diff --git a/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts b/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts index b6969a0457..4e008a6c36 100644 --- a/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/classificationsEditor.spec.ts @@ -111,7 +111,7 @@ test.describe('Classifications Editor', () => { test('Should show classifications editor on report page and save edited values', async ({ page, login, skipOnEmptyEnvironment }) => { - await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login(); await page.goto(reportURL); await waitForRequest('FindClassifications'); @@ -146,7 +146,7 @@ test.describe('Classifications Editor', () => { await init(); - await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login(); await page.goto(reportURL); await waitForRequest('FindClassifications'); @@ -194,7 +194,7 @@ test.describe('Classifications Editor', () => { await init(); - await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login(); await page.goto(`/cite/${incident_id}`); @@ -271,7 +271,7 @@ test.describe('Classifications Editor', () => { } test('Should synchronize duplicate fields', async ({ page, login, skipOnEmptyEnvironment }) => { - await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['admin'] } }); + await login(); await page.goto(incidentURL); await waitForRequest('FindClassifications'); From 0f89fdfb94ea05a2370fa8ad3c0ffecd832b3ed4 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 19 Nov 2024 21:40:39 -0300 Subject: [PATCH 31/99] tmp add new variables --- site/gatsby-site/playwright/config.ts | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/site/gatsby-site/playwright/config.ts b/site/gatsby-site/playwright/config.ts index 2440f27287..04cc8cee8a 100644 --- a/site/gatsby-site/playwright/config.ts +++ b/site/gatsby-site/playwright/config.ts @@ -3,7 +3,8 @@ type ConfigType = { E2E_ADMIN_USERNAME: string; IS_EMPTY_ENVIRONMENT: string; AVAILABLE_LANGUAGES?: string; - [key: string]: string; + NEXTAUTH_URL?: string; + NEXTAUTH_SECRET?: string; }; const config: ConfigType = { @@ -11,10 +12,14 @@ const config: ConfigType = { E2E_ADMIN_USERNAME: process.env.E2E_ADMIN_USERNAME!, IS_EMPTY_ENVIRONMENT: process.env.IS_EMPTY_ENVIRONMENT ?? '', AVAILABLE_LANGUAGES: process.env.GATSBY_AVAILABLE_LANGUAGES ?? '', + + // TODO: add theses values to the workflow + NEXTAUTH_URL: 'http://localhost:8000', + NEXTAUTH_SECRET: '678x1irXYWeiOqTwCv1awvkAUbO9eHa5xzQEYhxhMms=', } Object.keys(config).forEach((key) => { - if (config[key] === undefined) { + if (config[key as keyof ConfigType] === undefined) { throw new Error(`Config property ${key} is undefined`); } }); From 0e7f5c7cd9697fde57489c417e4fa00989661dd3 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 19 Nov 2024 22:00:18 -0300 Subject: [PATCH 32/99] Add missing configs --- site/gatsby-site/nextauth.config.ts | 6 ++++-- site/gatsby-site/server/config.ts | 6 ++++++ 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts index e4606596a8..df4e4b146d 100644 --- a/site/gatsby-site/nextauth.config.ts +++ b/site/gatsby-site/nextauth.config.ts @@ -1,8 +1,9 @@ import { EmailParams, MailerSend, Recipient } from "mailersend" import { MongoClient, ServerApiVersion } from "mongodb" import { NextAuthOptions } from "next-auth" +import config from './server/config' -const client = new MongoClient(process.env.API_MONGODB_CONNECTION_STRING!, { +const client = new MongoClient(config.API_MONGODB_CONNECTION_STRING!, { serverApi: { version: ServerApiVersion.v1, strict: true, @@ -11,7 +12,7 @@ const client = new MongoClient(process.env.API_MONGODB_CONNECTION_STRING!, { }) const mailersend = new MailerSend({ - apiKey: process.env.MAILERSEND_API_KEY!, + apiKey: config.MAILERSEND_API_KEY!, }); export const sendVerificationRequest = async ({ identifier: email, url }: { identifier: string, url: string }) => { @@ -92,6 +93,7 @@ export const getAuthConfig = async (): Promise => { verifyRequest: '/verify-request', newUser: '/account', }, + secret: config.NEXTAUTH_SECRET!, debug: true, } } \ No newline at end of file diff --git a/site/gatsby-site/server/config.ts b/site/gatsby-site/server/config.ts index 885c793f8f..0079b312d0 100644 --- a/site/gatsby-site/server/config.ts +++ b/site/gatsby-site/server/config.ts @@ -12,6 +12,8 @@ export interface Config { NOTIFICATIONS_SENDER: string; PROCESS_NOTIFICATIONS_SECRET: string; SITE_URL: string; + NEXTAUTH_URL: string, + NEXTAUTH_SECRET: string, }; const config: Config = { @@ -28,6 +30,10 @@ const config: Config = { NOTIFICATIONS_SENDER: process.env.NOTIFICATIONS_SENDER!, PROCESS_NOTIFICATIONS_SECRET: process.env.PROCESS_NOTIFICATIONS_SECRET!, SITE_URL: process.env.SITE_URL! || process.env.URL!, + + //TODO: add these to workflow + NEXTAUTH_URL: 'http://localhost:8000', + NEXTAUTH_SECRET: '678x1irXYWeiOqTwCv1awvkAUbO9eHa5xzQEYhxhMms=', } Object.keys(config).forEach((key) => { From 430f7d936199819393a033caf9dd3af38f904ca5 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 19 Nov 2024 22:13:52 -0300 Subject: [PATCH 33/99] Add missing variables --- site/gatsby-site/playwright/utils.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/gatsby-site/playwright/utils.ts b/site/gatsby-site/playwright/utils.ts index 6b2f52932f..03d41c79ad 100644 --- a/site/gatsby-site/playwright/utils.ts +++ b/site/gatsby-site/playwright/utils.ts @@ -34,7 +34,7 @@ export function randomString(size: number) { export function hashToken(token: string) { return crypto.createHash("sha256") - .update(`${token}${process.env.NEXTAUTH_SECRET}`) + .update(`${token}${config.NEXTAUTH_SECRET}`) .digest("hex"); } @@ -55,7 +55,7 @@ async function generateMagicLink(email: string) { }); }); - const baseUrl = process.env.NEXTAUTH_URL; + const baseUrl = config.NEXTAUTH_URL; const magicLink = `${baseUrl}/api/auth/callback/http-email?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2F&token=${token}&email=${encodeURIComponent(email)}`; return magicLink; From 41b55312bcc87eeeada082a26b3d58b110f62d10 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 19 Nov 2024 22:44:17 -0300 Subject: [PATCH 34/99] Add missing env var --- site/gatsby-site/nextauth.config.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts index df4e4b146d..e4345a576b 100644 --- a/site/gatsby-site/nextauth.config.ts +++ b/site/gatsby-site/nextauth.config.ts @@ -3,6 +3,11 @@ import { MongoClient, ServerApiVersion } from "mongodb" import { NextAuthOptions } from "next-auth" import config from './server/config' +//TODO: add this to the workflow file, this needs to be set via env variable +// SEE: https://github.com/nextauthjs/next-auth/discussions/9785 + +process.env.NEXTAUTH_URL = config.NEXTAUTH_URL! + const client = new MongoClient(config.API_MONGODB_CONNECTION_STRING!, { serverApi: { version: ServerApiVersion.v1, From 401e7914cdb81bf31a0b14020b2a1281aed24ca3 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 20 Nov 2024 00:13:20 -0300 Subject: [PATCH 35/99] only debug on localhost --- site/gatsby-site/nextauth.config.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts index e4345a576b..6b9dbafa01 100644 --- a/site/gatsby-site/nextauth.config.ts +++ b/site/gatsby-site/nextauth.config.ts @@ -99,6 +99,6 @@ export const getAuthConfig = async (): Promise => { newUser: '/account', }, secret: config.NEXTAUTH_SECRET!, - debug: true, + debug: config.NEXTAUTH_URL! === 'http://localhost:8000', } } \ No newline at end of file From 1e03b543c14e9063db28f68ff9fe8b003d545356 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 20 Nov 2024 00:18:24 -0300 Subject: [PATCH 36/99] Update tests --- .../playwright/e2e-full/entity.spec.ts | 6 +++--- .../e2e-full/incidents/edit.spec.ts | 2 +- .../playwright/e2e-full/incidents/new.spec.ts | 4 ++-- .../playwright/e2e-full/subscription.spec.ts | 20 +++++++++---------- .../playwright/e2e-full/unsubscribe.spec.ts | 2 +- 5 files changed, 17 insertions(+), 17 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/entity.spec.ts b/site/gatsby-site/playwright/e2e-full/entity.spec.ts index 1366a28b52..d3ce72a5a0 100644 --- a/site/gatsby-site/playwright/e2e-full/entity.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/entity.spec.ts @@ -21,7 +21,7 @@ test.describe('Individual Entity page', () => { test('Should subscribe to new Entity incidents (authenticated user)', async ({ page, login }) => { - await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login(); await page.goto(url); @@ -34,7 +34,7 @@ test.describe('Individual Entity page', () => { await init(); - const [userId] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + const [userId] = await login(); const subscriptions: DBSubscription[] = [ { @@ -69,7 +69,7 @@ test.describe('Individual Entity page', () => { test('Should display Edit button for Admin users', async ({ page, login, skipOnEmptyEnvironment }) => { - await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login(); await page.goto(url); await expect(page.locator('[data-cy="edit-entity-btn"]')).toHaveAttribute('href', `/entities/edit?entity_id=${entity.entity_id}`); diff --git a/site/gatsby-site/playwright/e2e-full/incidents/edit.spec.ts b/site/gatsby-site/playwright/e2e-full/incidents/edit.spec.ts index a0a00003b2..7fc9137424 100644 --- a/site/gatsby-site/playwright/e2e-full/incidents/edit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/incidents/edit.spec.ts @@ -15,7 +15,7 @@ test.describe('Incidents', () => { } }); - await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login(); await page.goto(url); diff --git a/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts b/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts index b4f7276763..0b4a7d035c 100644 --- a/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts @@ -12,7 +12,7 @@ test.describe('New Incident page', () => { await init(); - await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login(); await page.goto(url); @@ -53,7 +53,7 @@ test.describe('New Incident page', () => { await init(); - await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login(); const newIncidentId = 4; diff --git a/site/gatsby-site/playwright/e2e-full/subscription.spec.ts b/site/gatsby-site/playwright/e2e-full/subscription.spec.ts index 0e63208c98..676c5f5f5d 100644 --- a/site/gatsby-site/playwright/e2e-full/subscription.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/subscription.spec.ts @@ -2,9 +2,9 @@ import { expect } from '@playwright/test'; import { query, test } from '../utils' import { SUBSCRIPTION_TYPE } from '../../src/utils/subscriptions'; import { init, seedFixture } from '../memory-mongo'; -import { DBSubscription } from '../seeds/customData/subscriptions'; import gql from 'graphql-tag'; import { ObjectId } from 'bson'; +import { DBSubscription } from '../../server/interfaces'; test.describe('Subscriptions', () => { const url = '/account'; @@ -13,7 +13,7 @@ test.describe('Subscriptions', () => { await init(); - const [userId] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + const [userId] = await login(); const subscriptions: DBSubscription[] = [ { @@ -36,7 +36,7 @@ test.describe('Subscriptions', () => { test("Incident Updates: Should display a information message if the user doesn't have subscriptions", async ({ page, login }) => { await init(); - await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login(); await page.goto(url); @@ -47,7 +47,7 @@ test.describe('Subscriptions', () => { await init(); - const [userId, accessToken] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + const [userId, accessToken] = await login(); const subscriptions: DBSubscription[] = [ { @@ -96,7 +96,7 @@ test.describe('Subscriptions', () => { test('New Incidents: Should display the switch toggle off if user does not have a subscription', async ({ page, login }) => { - await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login(); await page.goto(url); @@ -109,7 +109,7 @@ test.describe('Subscriptions', () => { await init(); - const [userId] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + const [userId] = await login(); const subscriptions: DBSubscription[] = [ { @@ -152,7 +152,7 @@ test.describe('Subscriptions', () => { await init(); - const [userId] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + const [userId] = await login(); const subscriptions: DBSubscription[] = [ { @@ -182,7 +182,7 @@ test.describe('Subscriptions', () => { await init(); - const [userId] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + const [userId] = await login(); const subscriptions: DBSubscription[] = [ { @@ -212,7 +212,7 @@ test.describe('Subscriptions', () => { await init(); - await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + await login(); await page.goto(url); @@ -224,7 +224,7 @@ test.describe('Subscriptions', () => { await init(); - const [userId, accessToken] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + const [userId, accessToken] = await login(); const subscriptions: DBSubscription[] = [ { diff --git a/site/gatsby-site/playwright/e2e-full/unsubscribe.spec.ts b/site/gatsby-site/playwright/e2e-full/unsubscribe.spec.ts index b5bd53712e..e25f55da87 100644 --- a/site/gatsby-site/playwright/e2e-full/unsubscribe.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/unsubscribe.spec.ts @@ -11,7 +11,7 @@ test.describe('Unsubscribe pages', () => { test.beforeEach(async ({ page, login }) => { if (!userId) { - [userId, accessToken] = await login({ customData: { roles: ['admin'], first_name: 'John', last_name: 'Doe' } }); + [userId, accessToken] = await login(); } }); From 2ec47800fa41236989df3a6656b729b8173a0250 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 20 Nov 2024 00:27:04 -0300 Subject: [PATCH 37/99] Update tests --- .../e2e-full/apps/checklistForm.spec.ts | 16 ++++++++-------- .../e2e-full/apps/checklistIndex.spec.ts | 10 +++++----- .../playwright/e2e-full/apps/submitted.spec.ts | 4 ++-- .../gatsby-site/playwright/e2e-full/cite.spec.ts | 4 ++-- .../playwright/e2e-full/entityEdit.spec.ts | 4 ++-- .../playwright/e2e-full/submit.spec.ts | 4 ++-- 6 files changed, 21 insertions(+), 21 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts index 8109f43d5b..b68cef1534 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts @@ -37,7 +37,7 @@ test.describe('Checklists App Form', () => { test('Should have read-only access for logged-in non-owners', async ({ page, login }) => { - await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login(); await conditionalIntercept( page, @@ -57,7 +57,7 @@ test.describe('Checklists App Form', () => { test('Should allow editing for owner', async ({ page, login }) => { - const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login(); await conditionalIntercept( @@ -95,7 +95,7 @@ test.describe('Checklists App Form', () => { test('Should trigger GraphQL upsert query on adding tag', async ({ page, login }) => { - const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login(); await conditionalIntercept( page, @@ -125,7 +125,7 @@ test.describe('Checklists App Form', () => { test('Should trigger GraphQL update on removing tag', async ({ page, login }) => { - const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login(); await conditionalIntercept( page, @@ -162,7 +162,7 @@ test.describe('Checklists App Form', () => { test('Should trigger UI update on adding and removing tag', async ({ page, login }) => { - const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login(); await conditionalIntercept( page, @@ -208,7 +208,7 @@ test.describe('Checklists App Form', () => { test('Should change sort order of risk items', async ({ page, login }) => { - const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login(); await page.setViewportSize({ width: 1920, height: 1080 }); @@ -244,7 +244,7 @@ test.describe('Checklists App Form', () => { await init(); - const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login(); await conditionalIntercept( page, @@ -310,7 +310,7 @@ test.describe('Checklists App Form', () => { // TODO: test is crashing not sure if it is a bug or missing seed data test.skip('Should persist open state on editing query', async ({ page, login }) => { - const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login(); await conditionalIntercept( page, diff --git a/site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts index ab292bf357..b19de7983a 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts @@ -8,7 +8,7 @@ test.describe('Checklists App Index', () => { test('Should sort checklists', async ({ page, login }) => { - const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login(); await conditionalIntercept( page, @@ -72,7 +72,7 @@ test.describe('Checklists App Index', () => { test('Should display New Checklist button as logged-in user', async ({ page, login }) => { - await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login(); await page.goto(url); await expect(page.locator(newChecklistButtonSelector)).toBeVisible(); @@ -80,7 +80,7 @@ test.describe('Checklists App Index', () => { test.skip('Should show delete buttons only for owned checklists', async ({ page, login }) => { - const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login(); await conditionalIntercept( page, @@ -142,7 +142,7 @@ test.describe('Checklists App Index', () => { test('Should show toast on error fetching risks', async ({ page, login }) => { - const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login(); await conditionalIntercept( page, @@ -195,7 +195,7 @@ test.describe('Checklists App Index', () => { test('Should show toast on error creating checklist', async ({ page, login }) => { - const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login(); await conditionalIntercept( page, diff --git a/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts index 9818473506..e385d81966 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts @@ -76,7 +76,7 @@ test.describe('Submitted reports', () => { test('Promotes a submission to a new report and links it to a new incident', async ({ page, login }) => { await init(); - await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login(); await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); @@ -580,7 +580,7 @@ test.describe('Submitted reports', () => { test('Should keep all the appropriate fields from the Submission', async ({ page, login }) => { await init(); - await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login(); await page.goto(url + `?editSubmission=6140e4b4b9b4f7b3b3b1b1b1`); diff --git a/site/gatsby-site/playwright/e2e-full/cite.spec.ts b/site/gatsby-site/playwright/e2e-full/cite.spec.ts index 16f34cef9c..ae27d82f76 100644 --- a/site/gatsby-site/playwright/e2e-full/cite.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/cite.spec.ts @@ -157,7 +157,7 @@ test.describe('Cite pages', () => { await init(); - await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login(); await page.goto('/cite/3'); @@ -394,7 +394,7 @@ test.describe('Cite pages', () => { await init(); - await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login(); await conditionalIntercept( page, diff --git a/site/gatsby-site/playwright/e2e-full/entityEdit.spec.ts b/site/gatsby-site/playwright/e2e-full/entityEdit.spec.ts index 26fd91af37..c4ca7b85c8 100644 --- a/site/gatsby-site/playwright/e2e-full/entityEdit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/entityEdit.spec.ts @@ -18,7 +18,7 @@ test.describe('Edit Entity', () => { await init(); - await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login(); await page.goto(url); @@ -67,7 +67,7 @@ test.describe('Edit Entity', () => { test('Should display an error message when editing Entity fails', async ({ page, login, skipOnEmptyEnvironment }) => { - await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login(); await page.goto(url); diff --git a/site/gatsby-site/playwright/e2e-full/submit.spec.ts b/site/gatsby-site/playwright/e2e-full/submit.spec.ts index 8d8c145b27..9094404f79 100644 --- a/site/gatsby-site/playwright/e2e-full/submit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/submit.spec.ts @@ -315,7 +315,7 @@ test.describe('The Submit form', () => { await expect(page.locator(':text("Report successfully added to review queue")')).toBeVisible(); - await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + await login(); await page.goto('/apps/submitted'); @@ -396,7 +396,7 @@ test.describe('The Submit form', () => { test('Should submit a submission and link it to the current user id', async ({ page, login, skipOnEmptyEnvironment }) => { - const [userId] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['admin'] } }); + const [userId] = await login(); const values = { url: 'https://incidentdatabase.ai', From 7b95d22907a0756d9cdf35f919af5a7f177650f5 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 20 Nov 2024 00:46:36 -0300 Subject: [PATCH 38/99] Update session with custom data --- site/gatsby-site/nextauth.config.ts | 4 ++++ .../playwright/e2e-full/submit.spec.ts | 1 - .../components/forms/SubmissionWizard/StepTwo.js | 3 +-- .../src/components/forms/SubmitForm.js | 16 +++++++--------- .../components/submissions/SubmissionWizard.js | 10 ++++------ 5 files changed, 16 insertions(+), 18 deletions(-) diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts index 6b9dbafa01..c2032b881b 100644 --- a/site/gatsby-site/nextauth.config.ts +++ b/site/gatsby-site/nextauth.config.ts @@ -67,6 +67,10 @@ export const getAuthConfig = async (): Promise => { session.user.id = customData.userId; // @ts-ignore session.user.roles = customData.roles; + // @ts-ignore + session.user.first_name = customData.first_name; + // @ts-ignore + session.user.last_name = customData.last_name; } return session diff --git a/site/gatsby-site/playwright/e2e-full/submit.spec.ts b/site/gatsby-site/playwright/e2e-full/submit.spec.ts index 9094404f79..36cecb63f8 100644 --- a/site/gatsby-site/playwright/e2e-full/submit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/submit.spec.ts @@ -1,7 +1,6 @@ import parseNews from '../fixtures/api/parseNews.json'; import { conditionalIntercept, waitForRequest, setEditorText, test, trackRequest, query, fillAutoComplete } from '../utils'; import { expect } from '@playwright/test'; -import config from '../config'; import { init } from '../memory-mongo'; import gql from 'graphql-tag'; diff --git a/site/gatsby-site/src/components/forms/SubmissionWizard/StepTwo.js b/site/gatsby-site/src/components/forms/SubmissionWizard/StepTwo.js index d34be436f6..3298205757 100644 --- a/site/gatsby-site/src/components/forms/SubmissionWizard/StepTwo.js +++ b/site/gatsby-site/src/components/forms/SubmissionWizard/StepTwo.js @@ -137,8 +137,7 @@ const FormDetails = ({ saveInLocalStorage(values); }, [values]); - const isUserDetailsComplete = - user?.profile?.email && user.customData.first_name && user.customData.last_name; + const isUserDetailsComplete = user && user.first_name && user.last_name; return ( <> diff --git a/site/gatsby-site/src/components/forms/SubmitForm.js b/site/gatsby-site/src/components/forms/SubmitForm.js index 0e296648cc..30b69c3b23 100644 --- a/site/gatsby-site/src/components/forms/SubmitForm.js +++ b/site/gatsby-site/src/components/forms/SubmitForm.js @@ -113,13 +113,11 @@ const SubmitForm = () => { }; } - if (!loading) { - if (user?.profile?.email) { - submission.user = { link: user.id }; + if (!loading && user) { + submission.user = { link: user.id }; - if (user.customData.first_name && user.customData.last_name) { - submission.submitters = [`${user.customData.first_name} ${user.customData.last_name}`]; - } + if (user.first_name && user.last_name) { + submission.submitters = [`${user.first_name} ${user.last_name}`]; } } setSubmission(submission); @@ -238,11 +236,11 @@ const SubmitForm = () => { const clearForm = () => { const submission = { ...SUBMISSION_INITIAL_VALUES }; - if (user?.profile?.email) { + if (user) { submission.user = { link: user.id }; - if (user.customData.first_name && user.customData.last_name) { - submission.submitters = [`${user.customData.first_name} ${user.customData.last_name}`]; + if (user.first_name && user.last_name) { + submission.submitters = [`${user.first_name} ${user.last_name}`]; } } setSubmission(submission); diff --git a/site/gatsby-site/src/components/submissions/SubmissionWizard.js b/site/gatsby-site/src/components/submissions/SubmissionWizard.js index cb7d023d94..123c0dbcdb 100644 --- a/site/gatsby-site/src/components/submissions/SubmissionWizard.js +++ b/site/gatsby-site/src/components/submissions/SubmissionWizard.js @@ -110,13 +110,11 @@ const SubmissionWizard = ({ cloudinary_id, }; - if (!loading) { - if (user?.profile?.email) { - newValues.user = { link: user.id }; + if (!loading && user) { + newValues.user = { link: user.id }; - if (user.customData.first_name && user.customData.last_name) { - newValues.submitters = [`${user.customData.first_name} ${user.customData.last_name}`]; - } + if (user.first_name && user.last_name) { + newValues.submitters = [`${user.first_name} ${user.last_name}`]; } } From 656e1b70e710f7673461cbd055473f4a3128e9ae Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 20 Nov 2024 00:58:10 -0300 Subject: [PATCH 39/99] Update tests --- site/gatsby-site/playwright/e2e-full/cite.spec.ts | 2 +- site/gatsby-site/playwright/e2e-full/subscription.spec.ts | 5 +++-- site/gatsby-site/playwright/e2e-full/unsubscribe.spec.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/cite.spec.ts b/site/gatsby-site/playwright/e2e-full/cite.spec.ts index ae27d82f76..08897e7d48 100644 --- a/site/gatsby-site/playwright/e2e-full/cite.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/cite.spec.ts @@ -494,7 +494,7 @@ test.describe('Cite pages', () => { `, variables: { filter: { userId: { EQ: userId } } }, }, - { authorization: `Bearer ${accessToken}` } + { Cookie: `next-auth.session-token=${encodeURIComponent(accessToken)};` } ); expect(data.subscriptions).toEqual([{ type: 'incident', incident_id: { incident_id: 3 } }]); diff --git a/site/gatsby-site/playwright/e2e-full/subscription.spec.ts b/site/gatsby-site/playwright/e2e-full/subscription.spec.ts index 676c5f5f5d..d28fc06443 100644 --- a/site/gatsby-site/playwright/e2e-full/subscription.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/subscription.spec.ts @@ -88,7 +88,7 @@ test.describe('Subscriptions', () => { } }`, }, - { authorization: `Bearer ${accessToken}` } + { Cookie: `next-auth.session-token=${encodeURIComponent(accessToken)};` } ); expect(subscriptionsData).toMatchObject([{ _id: "62f40cd14016f5858d72385e" }]); @@ -263,7 +263,8 @@ test.describe('Subscriptions', () => { } }`, }, { - authorization: `Bearer ${accessToken}` + Cookie: `next-auth.session-token=${encodeURIComponent(accessToken)};` + }); expect(subscriptionsData).toMatchObject([{ _id: "62f40cd14016f5858d72385e" }]); diff --git a/site/gatsby-site/playwright/e2e-full/unsubscribe.spec.ts b/site/gatsby-site/playwright/e2e-full/unsubscribe.spec.ts index e25f55da87..ba5956b770 100644 --- a/site/gatsby-site/playwright/e2e-full/unsubscribe.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/unsubscribe.spec.ts @@ -73,7 +73,7 @@ test.describe('Unsubscribe pages', () => { } }`, }, - { authorization: `Bearer ${accessToken}` } + { Cookie: `next-auth.session-token=${encodeURIComponent(accessToken)};` } ); expect(subscriptionsData).toHaveLength(0); From 66dff1a79c2baa2a16f238d418f16ea778c886b2 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 20 Nov 2024 16:37:33 -0300 Subject: [PATCH 40/99] Update tests --- site/gatsby-site/nextauth.config.ts | 2 +- site/gatsby-site/playwright/e2e-full/account.spec.ts | 5 ++--- site/gatsby-site/playwright/e2e-full/admin.spec.ts | 4 ++-- site/gatsby-site/playwright/utils.ts | 6 +++++- 4 files changed, 10 insertions(+), 7 deletions(-) diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts index c2032b881b..7c822074b0 100644 --- a/site/gatsby-site/nextauth.config.ts +++ b/site/gatsby-site/nextauth.config.ts @@ -103,6 +103,6 @@ export const getAuthConfig = async (): Promise => { newUser: '/account', }, secret: config.NEXTAUTH_SECRET!, - debug: config.NEXTAUTH_URL! === 'http://localhost:8000', + debug: config.NEXTAUTH_URL! === 'http://localhost:8000' && !process.env.CI, } } \ No newline at end of file diff --git a/site/gatsby-site/playwright/e2e-full/account.spec.ts b/site/gatsby-site/playwright/e2e-full/account.spec.ts index c1e26c8e88..282e802fc3 100644 --- a/site/gatsby-site/playwright/e2e-full/account.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/account.spec.ts @@ -1,6 +1,5 @@ -import { test } from '../utils'; +import { test, testUser } from '../utils'; import { expect } from '@playwright/test'; -import config from '../config'; import { init } from '../memory-mongo'; test.describe('Account', () => { @@ -20,7 +19,7 @@ test.describe('Account', () => { const detailsTable = page.locator('[data-cy="details-table"]'); - await expect(detailsTable.locator(`td:text-is("${config.E2E_ADMIN_USERNAME}")`)).toBeVisible(); + await expect(detailsTable.locator(`td:text-is("${testUser.email}")`)).toBeVisible(); await expect(detailsTable.locator('td:text-is("Test")')).toBeVisible(); await expect(detailsTable.locator('td:text-is("User")')).toBeVisible(); await expect(detailsTable.locator('span:text-is("admin")')).toBeVisible(); diff --git a/site/gatsby-site/playwright/e2e-full/admin.spec.ts b/site/gatsby-site/playwright/e2e-full/admin.spec.ts index a4295d22a6..5a1bbf07bb 100644 --- a/site/gatsby-site/playwright/e2e-full/admin.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/admin.spec.ts @@ -71,8 +71,8 @@ test.describe('Admin', () => { await page.goto(baseUrl); - await page.locator('[data-cy="input-filter-First Name"]').fill('John'); - await page.locator('[data-cy="input-filter-Last Name"]').fill('Doe'); + await page.locator('[data-cy="input-filter-First Name"]').fill('Test'); + await page.locator('[data-cy="input-filter-Last Name"]').fill('User'); await page.locator('[data-cy="input-filter-Roles"]').fill('admin'); // TODO: find a way to mock admin api and adminData graphql field diff --git a/site/gatsby-site/playwright/utils.ts b/site/gatsby-site/playwright/utils.ts index 03d41c79ad..a5d4abd453 100644 --- a/site/gatsby-site/playwright/utils.ts +++ b/site/gatsby-site/playwright/utils.ts @@ -8,6 +8,8 @@ import path from 'path'; import * as memoryMongo from './memory-mongo'; import * as crypto from 'node:crypto'; import { ObjectId } from 'bson'; +import users from './seeds/customData/users'; +import authUsers from './seeds/auth/users'; declare module '@playwright/test' { interface Request { @@ -93,6 +95,8 @@ async function getSessionToken(userId: string) { return token; } +export const testUser = { ...users[0], email: authUsers.find(u => u._id.equals(new ObjectId(users[0].userId))).email }; + export const test = base.extend({ skipOnEmptyEnvironment: async ({ }, use, testInfo) => { if (config.IS_EMPTY_ENVIRONMENT) { @@ -115,7 +119,7 @@ export const test = base.extend({ // TODO: this should be removed since we pass the username and password as arguments testInfo.skip(!config.E2E_ADMIN_USERNAME || !config.E2E_ADMIN_PASSWORD, 'E2E_ADMIN_USERNAME or E2E_ADMIN_PASSWORD not set'); - await use(async ({ email = "test.user@incidentdatabase.ai", customData = null } = {}) => { + await use(async ({ email = testUser.email, customData = null } = {}) => { const magicLink = await generateMagicLink(email); From 49970fc339779617a1ccf8ba97f5e6e88d6f5e55 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 20 Nov 2024 18:51:24 -0300 Subject: [PATCH 41/99] Updates --- .../playwright/e2e-full/apps/submitted.spec.ts | 7 +++++-- site/gatsby-site/playwright/utils.ts | 16 ++++++++-------- .../src/components/submissions/SubmissionList.js | 16 +++++++--------- site/gatsby-site/src/contexts/UserContext.tsx | 4 +++- 4 files changed, 23 insertions(+), 20 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts index e385d81966..af25d13330 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts @@ -3,7 +3,6 @@ import { gql } from 'graphql-tag'; import { isArray } from 'lodash'; import { init, seedCollection } from '../../memory-mongo'; import { fillAutoComplete, query, setEditorText, test } from '../../utils'; -import config from '../../config'; import { ObjectId } from 'mongodb'; import { DBSubmission } from '../../../server/interfaces'; @@ -431,9 +430,13 @@ test.describe('Submitted reports', () => { await page.goto(url); + const response = page.waitForResponse((response) => { + return response.request()?.postData()?.includes('UpdateSubmission') + }); + await page.getByText('Unclaim', { exact: true }).click(); - await page.waitForResponse((response) => response.request()?.postData()?.includes('UpdateSubmission')); + await response; const { data: { submissions: updated } } = await query({ query: gql`{ diff --git a/site/gatsby-site/playwright/utils.ts b/site/gatsby-site/playwright/utils.ts index a5d4abd453..f2164b3796 100644 --- a/site/gatsby-site/playwright/utils.ts +++ b/site/gatsby-site/playwright/utils.ts @@ -121,16 +121,8 @@ export const test = base.extend({ await use(async ({ email = testUser.email, customData = null } = {}) => { - const magicLink = await generateMagicLink(email); - - await page.context().clearCookies(); - - await page.goto(magicLink); - const userId = await getUserIdFromAuth(email); - const sessionToken = await getSessionToken(userId); - if (customData) { await memoryMongo.execute(async (client) => { @@ -142,6 +134,14 @@ export const test = base.extend({ }); } + const magicLink = await generateMagicLink(email); + + await page.context().clearCookies(); + + await page.goto(magicLink); + + const sessionToken = await getSessionToken(userId); + return [userId!, sessionToken!]; }) }, diff --git a/site/gatsby-site/src/components/submissions/SubmissionList.js b/site/gatsby-site/src/components/submissions/SubmissionList.js index 2d55718330..c4254c4b47 100644 --- a/site/gatsby-site/src/components/submissions/SubmissionList.js +++ b/site/gatsby-site/src/components/submissions/SubmissionList.js @@ -24,7 +24,7 @@ import useToastContext, { SEVERITY } from 'hooks/useToast'; const SubmissionList = ({ data }) => { const { t } = useTranslation(); - const { isLoggedIn, isRole, user } = useUserContext(); + const { loading, isRole, user } = useUserContext(); const [tableData, setTableData] = useState([]); @@ -359,7 +359,7 @@ const SubmissionList = ({ data }) => { disableResizing: true, Cell: ({ row: { values } }) => { const isAlreadyEditor = values?.incident_editors?.find( - (editor) => editor.userId === user.customData.userId + (editor) => editor.userId === user.id ); return ( @@ -415,7 +415,7 @@ const SubmissionList = ({ data }) => { } return columns; - }, [isLoggedIn, claiming, reviewing, dateFilter]); + }, [loading, user, claiming, reviewing, dateFilter]); const [tableState, setTableState] = useState({ pageIndex: 0, filters: [], sortBy: [] }); @@ -453,11 +453,11 @@ const SubmissionList = ({ data }) => { const incidentEditors = [...submission.incident_editors]; const isAlreadyEditor = submission.incident_editors.find( - (editor) => editor.userId === user.customData.userId + (editor) => editor.userId === user.id ); if (!isAlreadyEditor) { - incidentEditors.push(user.customData.userId); + incidentEditors.push(user.id); await updateSubmission({ variables: { @@ -486,12 +486,10 @@ const SubmissionList = ({ data }) => { const incidentEditors = [...submission.incident_editors]; - const isAlreadyEditor = submission.incident_editors.find( - (editor) => editor.userId === user.customData.userId - ); + const isAlreadyEditor = submission.incident_editors.find((editor) => editor.userId === user.id); if (isAlreadyEditor) { - const index = incidentEditors.findIndex((editor) => editor.userId === user.customData.userId); + const index = incidentEditors.findIndex((editor) => editor.userId === user.id); incidentEditors.splice(index, 1); diff --git a/site/gatsby-site/src/contexts/UserContext.tsx b/site/gatsby-site/src/contexts/UserContext.tsx index 94483537ed..3d800bc8a3 100644 --- a/site/gatsby-site/src/contexts/UserContext.tsx +++ b/site/gatsby-site/src/contexts/UserContext.tsx @@ -6,7 +6,9 @@ import { signOut, signIn, useSession, getCsrfToken, SignInResponse } from "next- interface User { roles?: string[]; - [key: string]: any; + id: string; + first_name: string; + last_name: string; } interface UserContextValue { From c8e99a826451a80a0152e1dfd8b7bfa8255d65f5 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 20 Nov 2024 20:11:16 -0300 Subject: [PATCH 42/99] Fix test --- site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts b/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts index 0b4a7d035c..1cf8e250ed 100644 --- a/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/incidents/new.spec.ts @@ -33,7 +33,7 @@ test.describe('New Incident page', () => { await page.locator('[data-cy="alleged-harmed-or-nearly-harmed-parties-input"] input').first().fill('children'); await page.keyboard.press('Enter'); - await fillAutoComplete(page, '#input-editors', 'Joh', 'John Doe'); + await fillAutoComplete(page, '#input-editors', 'Sean', 'Sean McGregor'); await conditionalIntercept(page, '**/graphql', From 82767cc6428ed285790e862b3f0c590b7ffe9b77 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 20 Nov 2024 20:51:37 -0300 Subject: [PATCH 43/99] Update tests --- .../playwright/e2e-full/apps/submitted.spec.ts | 12 ++++++------ site/gatsby-site/playwright/e2e-full/cite.spec.ts | 5 +++-- .../gatsby-site/playwright/e2e-full/citeEdit.spec.ts | 2 -- .../playwright/seeds/aiidprod/submissions.ts | 2 +- 4 files changed, 10 insertions(+), 11 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts index af25d13330..267d280dd9 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts @@ -279,7 +279,7 @@ test.describe('Submitted reports', () => { text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", title: "Sample title", url: "http://example.com", - user: "user1", + user: "6737a6e881955aa4905ccb04", incident_title: "Incident title", incident_date: "2021-09-14", editor_notes: "", @@ -319,7 +319,7 @@ test.describe('Submitted reports', () => { tags: ["tag1", "tag2"], text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", url: "http://example.com", - user: "user1", + user: "6737a6e881955aa4905ccb04", incident_title: "Incident title", incident_date: "2021-09-14", editor_notes: "", @@ -418,7 +418,7 @@ test.describe('Submitted reports', () => { tags: ["tag1", "tag2"], text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", url: "http://example.com", - user: "user1", + user: "6737a6e881955aa4905ccb04", incident_title: "Incident title", incident_date: "2021-09-14", editor_notes: "", @@ -479,7 +479,7 @@ test.describe('Submitted reports', () => { tags: ["tag1", "tag2"], text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", url: "http://example.com", - user: "user1", + user: "6737a6e881955aa4905ccb04", incident_title: "Incident title", incident_date: "2021-09-14", editor_notes: "", @@ -547,7 +547,7 @@ test.describe('Submitted reports', () => { tags: ["tag1", "tag2"], text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", url: "http://example.com", - user: "user1", + user: "6737a6e881955aa4905ccb04", incident_title: "Incident title", incident_date: "2021-09-14", editor_notes: "", @@ -677,7 +677,7 @@ test.describe('Submitted reports', () => { { report_number: 9, user: { - userId: "user1", + userId: "6737a6e881955aa4905ccb04", }, }, ], diff --git a/site/gatsby-site/playwright/e2e-full/cite.spec.ts b/site/gatsby-site/playwright/e2e-full/cite.spec.ts index 08897e7d48..e132c0f72e 100644 --- a/site/gatsby-site/playwright/e2e-full/cite.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/cite.spec.ts @@ -266,7 +266,8 @@ test.describe('Cite pages', () => { }); test('Should show the edit incident form', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); + + await login(); await page.goto(url); @@ -617,7 +618,7 @@ test.describe('Cite pages', () => { "Alleged deployer of AI system": ["entity-1"], "Alleged developer of AI system": ["entity-2"], "Alleged harmed or nearly harmed parties": ["entity-3"], - editors: ["user1"], + editors: ["6737a6e881955aa4905ccb04"], reports: [1], editor_notes: "This is an editor note", flagged_dissimilar_incidents: [] diff --git a/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts b/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts index d85a8d5406..4065d51da8 100644 --- a/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/citeEdit.spec.ts @@ -321,8 +321,6 @@ test.describe('Edit report', () => { test('Should convert an incident report to an issue', async ({ page, login }) => { - - await login(); await conditionalIntercept( diff --git a/site/gatsby-site/playwright/seeds/aiidprod/submissions.ts b/site/gatsby-site/playwright/seeds/aiidprod/submissions.ts index feaf3c03f9..01792c9fb0 100644 --- a/site/gatsby-site/playwright/seeds/aiidprod/submissions.ts +++ b/site/gatsby-site/playwright/seeds/aiidprod/submissions.ts @@ -23,7 +23,7 @@ const submissions: DBSubmission[] = [ text: "Sample text that must have at least 80 characters, so I will keep writing until I reach the minimum number of characters.", title: "Sample title", url: "http://example.com", - user: "user1", + user: "6737a6e881955aa4905ccb04", incident_title: "Incident title", incident_date: "2021-09-14", editor_notes: "This is an editor note", From 4006f420848c50fb2c8075495b3207088e1f8dc0 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 20 Nov 2024 21:14:06 -0300 Subject: [PATCH 44/99] update test --- site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts index 52bed75ef5..e67949e0e5 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts @@ -120,7 +120,7 @@ test.describe('Incidents App', () => { await page.locator('[data-cy=date-input]').fill('2023-05-04'); await page.locator('[data-cy=alleged-deployer-of-ai-system-input] input').first().fill('Test Deployer{enter}'); - await fillAutoComplete(page, "#input-editors", "Joh", "John Doe"); + await fillAutoComplete(page, "#input-editors", "Sean", "Sean McGregor"); await fillAutoComplete(page, "#input-incidentSearch", "1", "1 - Incident 1"); From cecf1f7f37501f4301cb29c6732b56180a8685da Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Thu, 21 Nov 2024 16:31:28 -0300 Subject: [PATCH 45/99] Update test --- .../e2e-full/apps/submitted.spec.ts | 13 ++- .../playwright/e2e-full/submit.spec.ts | 100 ++++++++---------- 2 files changed, 53 insertions(+), 60 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts index 267d280dd9..8f25cf9e52 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/submitted.spec.ts @@ -211,9 +211,11 @@ test.describe('Submitted reports', () => { page.on('dialog', dialog => dialog.accept()); + const deleteResponse = page.waitForResponse((response) => response.request()?.postDataJSON()?.operationName == 'DeleteSubmission'); + await page.locator('[data-cy="reject-button"]').click(); - await page.waitForResponse((response) => response.request()?.postData()?.includes('deleteOneSubmission')); + await deleteResponse; const updated = await getSubmissions(); @@ -374,9 +376,12 @@ test.describe('Submitted reports', () => { await page.goto(url); + const updateResponse = page.waitForResponse((response) => response.request()?.postDataJSON()?.operationName == 'UpdateSubmission'); + await page.click('[data-cy="claim-submission"]'); - await page.waitForResponse((response) => response.request()?.postData()?.includes('UpdateSubmission')); + await updateResponse; + const { data: { submissions } } = await query({ query: gql`{ @@ -430,9 +435,7 @@ test.describe('Submitted reports', () => { await page.goto(url); - const response = page.waitForResponse((response) => { - return response.request()?.postData()?.includes('UpdateSubmission') - }); + const response = page.waitForResponse((response) => response.request()?.postDataJSON()?.operationName == 'UpdateSubmission'); await page.getByText('Unclaim', { exact: true }).click(); diff --git a/site/gatsby-site/playwright/e2e-full/submit.spec.ts b/site/gatsby-site/playwright/e2e-full/submit.spec.ts index 36cecb63f8..615d0f6b37 100644 --- a/site/gatsby-site/playwright/e2e-full/submit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/submit.spec.ts @@ -11,12 +11,12 @@ test.describe('The Submit form', () => { // Listen for the dialog and handle it test.beforeEach(async ({ page }) => { - page.once('dialog', async dialog => { - const dialogMessage = dialog.message(); - if (dialogMessage.includes('Please confirm you are ready to submit this report. Report details cannot be changed after submission.') || dialogMessage.includes('Por favor confirma que estás listo para enviar este informe. Los detalles del informe no se pueden cambiar después de la presentación.') || dialogMessage.includes('Veuillez confirmer que vous êtes prêt à soumettre ce rapport. Les détails du rapport ne peuvent pas être modifiés après la soumission.') || dialogMessage.includes('このレポートを送信する準備ができていることを確認してください。送信後にレポートの詳細を変更することはできません')) { - await dialog.accept(); - } - }); + page.once('dialog', async dialog => { + const dialogMessage = dialog.message(); + if (dialogMessage.includes('Please confirm you are ready to submit this report. Report details cannot be changed after submission.') || dialogMessage.includes('Por favor confirma que estás listo para enviar este informe. Los detalles del informe no se pueden cambiar después de la presentación.') || dialogMessage.includes('Veuillez confirmer que vous êtes prêt à soumettre ce rapport. Les détails du rapport ne peuvent pas être modifiés après la soumission.') || dialogMessage.includes('このレポートを送信する準備ができていることを確認してください。送信後にレポートの詳細を変更することはできません')) { + await dialog.accept(); + } + }); }); test('Successfully loads', async ({ page }) => { @@ -158,7 +158,7 @@ test.describe('The Submit form', () => { await init(); - const [userId] = await login({ customData: { first_name: 'Cesar', last_name: 'Ito', roles: ['admin'] } }); + await login(); await conditionalIntercept( page, @@ -168,23 +168,13 @@ test.describe('The Submit form', () => { 'parseNews' ); - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' - ); + const findSubmissionsResponse = page.waitForResponse(((response) => response.request()?.postDataJSON()?.operationName == 'FindSubmissions')) - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindUsers', - 'findUsers' - ); + const findUsersResponse = page.waitForResponse(((response) => response.request()?.postDataJSON()?.operationName == 'FindUsers')) await page.goto(url); - await waitForRequest('findSubmissions'); + await findSubmissionsResponse; await page.locator('input[name="url"]').fill( `https://www.arstechnica.com/gadgets/2017/11/youtube-to-crack-down-on-inappropriate-content-masked-as-kids-cartoons/` @@ -204,13 +194,13 @@ test.describe('The Submit form', () => { await page.locator('[data-cy="to-step-3"]').click(); - await waitForRequest('findUsers'); + await findUsersResponse; await page.locator('[name="incident_title"]').fill('Elsagate'); await page.locator('[name="description"]').fill('Description'); - await fillAutoComplete(page, "#input-incident_editors", 'Ces', 'Cesar Ito'); + await fillAutoComplete(page, "#input-incident_editors", 'Sean', 'Sean McGregor'); await page.locator('[name="tags"]').fill('New Tag'); await page.keyboard.press('Enter'); @@ -248,7 +238,7 @@ test.describe('The Submit form', () => { text: parseNews.text, authors: ["Valentina Palladino"], incident_ids: [], - incident_editors: [{ userId }], + incident_editors: [{ userId: '619b47ea5eed5334edfa3bbc' }], }); }); @@ -1499,51 +1489,51 @@ test.describe('The Submit form', () => { test('Should autocomplete new entities', async ({ page, skipOnEmptyEnvironment }) => { - await conditionalIntercept( - page, - '**/parseNews**', - () => true, - parseNews, - 'parseNews' - ); + await conditionalIntercept( + page, + '**/parseNews**', + () => true, + parseNews, + 'parseNews' + ); - await trackRequest( - page, - '**/graphql', - (req) => req.postDataJSON().operationName == 'FindSubmissions', - 'findSubmissions' - ); + await trackRequest( + page, + '**/graphql', + (req) => req.postDataJSON().operationName == 'FindSubmissions', + 'findSubmissions' + ); - await page.goto(url); + await page.goto(url); - await waitForRequest('findSubmissions'); + await waitForRequest('findSubmissions'); - await page.locator('input[name="url"]').fill( - `https://www.arstechnica.com/gadgets/2017/11/youtube-to-crack-down-on-inappropriate-content-masked-as-kids-cartoons/` - ); + await page.locator('input[name="url"]').fill( + `https://www.arstechnica.com/gadgets/2017/11/youtube-to-crack-down-on-inappropriate-content-masked-as-kids-cartoons/` + ); - await page.locator('button:has-text("Fetch info")').click(); + await page.locator('button:has-text("Fetch info")').click(); - await waitForRequest('parseNews'); + await waitForRequest('parseNews'); - await page.locator('[name="incident_date"]').fill('2020-01-01'); + await page.locator('[name="incident_date"]').fill('2020-01-01'); - await expect(page.locator('.form-has-errors')).not.toBeVisible(); + await expect(page.locator('.form-has-errors')).not.toBeVisible(); - await page.locator('[data-cy="to-step-2"]').click(); + await page.locator('[data-cy="to-step-2"]').click(); - await page.locator('[data-cy="to-step-3"]').click(); + await page.locator('[data-cy="to-step-3"]').click(); - await page.locator('input[name="developers"]').fill('New entity'); - await page.keyboard.press('Enter'); + await page.locator('input[name="developers"]').fill('New entity'); + await page.keyboard.press('Enter'); - await page.locator('input[name="deployers"]').fill('New entity'); + await page.locator('input[name="deployers"]').fill('New entity'); - await page.locator('#deployers-tags .dropdown-item[aria-label="New entity"]').click(); + await page.locator('#deployers-tags .dropdown-item[aria-label="New entity"]').click(); - await page.locator('button[type="submit"]').click(); + await page.locator('button[type="submit"]').click(); - await expect(page.locator('.tw-toast:has-text("Report successfully added to review queue. You can see your submission")')).toBeVisible(); - await expect(page.locator(':text("Please review. Some data is missing.")')).not.toBeVisible(); - }); + await expect(page.locator('.tw-toast:has-text("Report successfully added to review queue. You can see your submission")')).toBeVisible(); + await expect(page.locator(':text("Please review. Some data is missing.")')).not.toBeVisible(); + }); }); \ No newline at end of file From ba863bb98a70850806b967afc0ea0522a416203d Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Thu, 21 Nov 2024 18:33:33 -0300 Subject: [PATCH 46/99] Update incidents table --- site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts | 4 ++-- site/gatsby-site/src/components/incidents/IncidentsTable.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts index e67949e0e5..1e1bc843d9 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts @@ -51,7 +51,7 @@ test.describe('Incidents App', () => { await expect(page.locator('[data-cy="row"]')).toHaveCount(1); await page.click('text=Edit'); - await page.waitForSelector('[data-cy="incident-form"]', { timeout: 12000 }); + await page.waitForSelector('[data-cy="incident-form"]'); await expect(page.locator('.submission-modal h3')).toHaveText('Edit Incident 3'); await page.locator(`[data-cy=title-input]`).fill('Test title'); @@ -59,7 +59,7 @@ test.describe('Incidents App', () => { await page.locator('[data-cy=date-input]').fill('2023-05-04'); await page.locator('[data-cy=alleged-deployer-of-ai-system-input] input').first().fill('Test Deployer{enter}'); - await fillAutoComplete(page, "#input-editors", "Joh", "John Doe"); + await fillAutoComplete(page, "#input-editors", "Test", "Test User"); await page.getByText('Update', { exact: true }).click(); diff --git a/site/gatsby-site/src/components/incidents/IncidentsTable.js b/site/gatsby-site/src/components/incidents/IncidentsTable.js index 513cd31690..362d8c14f5 100644 --- a/site/gatsby-site/src/components/incidents/IncidentsTable.js +++ b/site/gatsby-site/src/components/incidents/IncidentsTable.js @@ -48,7 +48,7 @@ function ListCell({ cell }) { export default function IncidentsTable({ data, isLiveData, setIsLiveData }) { const [incidentIdToEdit, setIncindentIdToEdit] = useState(0); - const { isLoggedIn, isRole } = useUserContext(); + const { loading, isRole } = useUserContext(); const { t } = useTranslation(); @@ -134,7 +134,7 @@ export default function IncidentsTable({ data, isLiveData, setIsLiveData }) { } return columns; - }, [isLoggedIn]); + }, [loading]); const table = useTable( { From 90c098087ee539f32395fe842c1a8c3957487396 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Thu, 21 Nov 2024 19:24:36 -0300 Subject: [PATCH 47/99] Update components --- site/gatsby-site/src/components/reports/ReportsTable.js | 4 ++-- site/gatsby-site/src/components/variants/VariantsTable.js | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/site/gatsby-site/src/components/reports/ReportsTable.js b/site/gatsby-site/src/components/reports/ReportsTable.js index c189c01dae..a00df1f2c1 100644 --- a/site/gatsby-site/src/components/reports/ReportsTable.js +++ b/site/gatsby-site/src/components/reports/ReportsTable.js @@ -14,7 +14,7 @@ import Table, { } from 'components/ui/Table'; export default function ReportsTable({ data, isLiveData, setIsLiveData }) { - const { isLoggedIn, isRole } = useUserContext(); + const { loading, isRole } = useUserContext(); const { t } = useTranslation(); @@ -145,7 +145,7 @@ export default function ReportsTable({ data, isLiveData, setIsLiveData }) { } return columns; - }, [isLoggedIn]); + }, [loading]); const table = useTable( { diff --git a/site/gatsby-site/src/components/variants/VariantsTable.js b/site/gatsby-site/src/components/variants/VariantsTable.js index ba2c2cf91d..bbd8dd7353 100644 --- a/site/gatsby-site/src/components/variants/VariantsTable.js +++ b/site/gatsby-site/src/components/variants/VariantsTable.js @@ -24,7 +24,7 @@ import { Button } from 'flowbite-react'; import Table, { DefaultColumnFilter, DefaultColumnHeader } from 'components/ui/Table'; export default function VariantsTable({ data, refetch, setLoading }) { - const { isLoggedIn, isRole } = useUserContext(); + const { loading, isRole } = useUserContext(); const { t } = useTranslation(['variants']); @@ -266,7 +266,7 @@ export default function VariantsTable({ data, refetch, setLoading }) { } return columns; - }, [isLoggedIn]); + }, [loading]); const table = useTable( { From 14dcb2793e1bc5f7e8002f5caf10c7e079006ed9 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Thu, 21 Nov 2024 19:32:34 -0300 Subject: [PATCH 48/99] Update components --- .../e2e-full/apps/checklistForm.spec.ts | 3 +- .../e2e-full/apps/checklistIndex.spec.ts | 9 +++-- .../components/checklists/ChecklistsIndex.js | 40 ++++++++++--------- 3 files changed, 28 insertions(+), 24 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts index b68cef1534..03c6508d9b 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/checklistForm.spec.ts @@ -2,7 +2,6 @@ import { expect } from '@playwright/test'; import riskSortingRisks from '../../fixtures/checklists/riskSortingChecklist.json'; import riskSortingChecklist from '../../fixtures/checklists/riskSortingChecklist.json'; import { conditionalIntercept, test, waitForRequest } from '../../utils'; -import config from '../../config'; import { init } from '../../memory-mongo'; test.describe('Checklists App Form', () => { @@ -117,7 +116,7 @@ test.describe('Checklists App Form', () => { await waitForRequest('findChecklist'); - await page.locator('#tags_goals_input').type('Code Generation'); + await page.locator('#tags_goals_input').fill('Question Answering'); await page.locator('#tags_goals').click(); await waitForRequest('upsertChecklist'); diff --git a/site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts index b19de7983a..d885ae8c1b 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/checklistIndex.spec.ts @@ -1,6 +1,5 @@ -import { expect, request } from '@playwright/test'; +import { expect } from '@playwright/test'; import { conditionalIntercept, test, waitForRequest } from '../../utils'; -import config from '../../config'; test.describe('Checklists App Index', () => { const url = '/apps/checklists'; @@ -123,7 +122,9 @@ test.describe('Checklists App Index', () => { await expect(page.locator('[data-cy="checklist-card"]:last-child button')).not.toContainText('Delete'); }); - test('Should show toast on error fetching checklists', async ({ page }) => { + test('Should show toast on error fetching checklists', async ({ page, login }) => { + + await login(); await conditionalIntercept( page, @@ -248,4 +249,4 @@ test.describe('Checklists App Index', () => { await expect(page.locator('[data-cy="toast"]')).toContainText('Could not create checklist.'); }); -}); \ No newline at end of file +}); diff --git a/site/gatsby-site/src/components/checklists/ChecklistsIndex.js b/site/gatsby-site/src/components/checklists/ChecklistsIndex.js index be1e62cb66..5d8753a565 100644 --- a/site/gatsby-site/src/components/checklists/ChecklistsIndex.js +++ b/site/gatsby-site/src/components/checklists/ChecklistsIndex.js @@ -4,7 +4,7 @@ import Card from 'elements/Card'; import Select from 'elements/Select'; import { Trans, useTranslation } from 'react-i18next'; import { LocalizedLink } from 'plugins/gatsby-theme-i18n'; -import { useQuery, useMutation } from '@apollo/client'; +import { useQuery, useMutation, useLazyQuery } from '@apollo/client'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; import { faCheckToSlot, @@ -33,29 +33,33 @@ const ChecklistsIndex = ({ users }) => { const addToast = useToastContext(); - const { user } = useUserContext(); + const { loading, user } = useUserContext(); - const loggedIn = user?.providerType != 'anon-user'; + const loggedIn = !loading && user; const [insertChecklist] = useMutation(INSERT_CHECKLIST); /************************** Get Checklists **************************/ - const { - data: checklistsData, - loading: checklistsLoading, - error: checklistsErrors, - } = useQuery(FIND_CHECKLISTS, { - variables: { query: { owner_id: user?.id } }, - skip: !user?.id, - }); + const [ + findChecklists, + { data: checklistsData, loading: checklistsLoading, error: checklistsErrors }, + ] = useLazyQuery(FIND_CHECKLISTS); - if (checklistsErrors) { - addToast({ - message: t('Could not fetch checklists'), - severity: SEVERITY.danger, - error: checklistsErrors, - }); - } + useEffect(() => { + if (!loading && user) { + findChecklists({ variables: { query: { owner_id: user?.id } } }); + } + }, [loading, user]); + + useEffect(() => { + if (checklistsErrors) { + addToast({ + message: t('Could not fetch checklists'), + severity: SEVERITY.danger, + error: checklistsErrors, + }); + } + }, [checklistsErrors]); // In useState so that on deleting a checklist, // we can remove it from display immediately. From 6b5ea6bbba4a977075df15ed9ba4d9daa8f47c33 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 22 Nov 2024 21:49:49 -0300 Subject: [PATCH 49/99] Delete unused pages --- .../e2e/integration/confirmemail.spec.ts | 43 ------ .../gatsby-site/playwright/e2e/signup.spec.ts | 123 ------------------ site/gatsby-site/src/pages/confirmemail.js | 93 ------------- site/gatsby-site/src/pages/forgotpassword.js | 94 ------------- site/gatsby-site/src/pages/logincallback.js | 12 -- 5 files changed, 365 deletions(-) delete mode 100644 site/gatsby-site/playwright/e2e/integration/confirmemail.spec.ts delete mode 100644 site/gatsby-site/playwright/e2e/signup.spec.ts delete mode 100644 site/gatsby-site/src/pages/confirmemail.js delete mode 100644 site/gatsby-site/src/pages/forgotpassword.js delete mode 100644 site/gatsby-site/src/pages/logincallback.js diff --git a/site/gatsby-site/playwright/e2e/integration/confirmemail.spec.ts b/site/gatsby-site/playwright/e2e/integration/confirmemail.spec.ts deleted file mode 100644 index 11c6c7f0dc..0000000000 --- a/site/gatsby-site/playwright/e2e/integration/confirmemail.spec.ts +++ /dev/null @@ -1,43 +0,0 @@ -import { expect } from '@playwright/test'; -import { test } from '../../utils'; - -const url = '/confirmemail'; - -test.describe('Confirm email', () => { - - test('Should successfully load confirm email page', async ({ page }) => { - await page.goto(url); - }); - - test('Should display Invalid params message when token or tokenId are missing', async ({ page }) => { - await page.goto(`${url}?token=dummyToken`); - await expect(page.locator(':has-text("Invalid parameters")').first()).toBeVisible(); - await expect(page.locator('[data-cy="confirm-login-btn"]')).toBeVisible(); - await expect(page.locator('#content:has-text("An unknown error has occurred")')).toBeVisible(); - - await page.goto(`${url}?tokenId=dummyTokenId`); - await expect(page.locator(':has-text("Invalid parameters")').first()).toBeVisible(); - await expect(page.locator('[data-cy="confirm-login-btn"]')).toBeVisible(); - await expect(page.locator('#content:has-text("An unknown error has occurred")')).toBeVisible(); - }); - - test('Should display an error message if the confirmation failed on Atlas', async ({ page }) => { - await page.goto(`${url}?token=invalidToken&tokenId=invalidTokenId`); - await expect(page.locator('[data-cy="toast"]:has-text("An unknown error has occurred")')).toBeVisible(); - await expect(page.locator('[data-cy="confirm-login-btn"]')).toBeVisible(); - await expect(page.locator('#content:has-text("An unknown error has occurred")')).toBeVisible(); - }); - - test('Should display success message if the email is confirmed on Atlas', async ({ page }) => { - await page.route('**/confirm', route => route.fulfill({ status: 201 })); - await page.goto(`${url}?token=dummyToken&tokenId=dummyTokenId`); - - await expect(page.locator('[data-cy="toast"]:has-text("Thank you for verifying your account.")')).toBeVisible(); - await expect(page.locator('[data-cy="confirm-login-btn"]')).toBeVisible(); - await expect(page.locator('#content:has-text("Thank you for verifying your account.")')).toBeVisible(); - - await page.locator('[data-cy="confirm-login-btn"]').click(); - await expect(page).toHaveURL('/login/?redirectTo=/account/'); - }); - -}); diff --git a/site/gatsby-site/playwright/e2e/signup.spec.ts b/site/gatsby-site/playwright/e2e/signup.spec.ts deleted file mode 100644 index cf6ff60cf1..0000000000 --- a/site/gatsby-site/playwright/e2e/signup.spec.ts +++ /dev/null @@ -1,123 +0,0 @@ -import { expect } from '@playwright/test'; -import { test, conditionalIntercept, waitForRequest } from '../utils'; -import config from '../config'; - -test.describe('Signup', () => { - const url = '/signup'; - - test('Should successfully load sign up page', async ({ page }) => { - await page.goto(url); - }); - - test('Should display success toast message after a sign up', async ({ page }) => { - await page.goto(url); - - const email = 'newUser@test.com'; - const password = 'newUserPassword'; - - await page.locator('[data-cy="signup-btn"]').click(); - - await page.locator('input[name=email]').fill(email); - await page.locator('input[name=password]').fill(password); - await page.locator('input[name=passwordConfirm]').fill(password); - - await conditionalIntercept( - page, - '**/register', - (req) => req.postDataJSON().email == email && req.postDataJSON().password == password, - { statusCode: 201 }, - 'Register' - ); - - await page.locator('[data-cy="signup-btn"]').click(); - await waitForRequest('Register'); - - const result = await page.evaluate(() => JSON.parse(JSON.stringify(localStorage))); - expect(result).toHaveProperty('signup'); - await expect(page.locator('[data-cy="toast"]')).toBeVisible({ timeout: 30000 }); - await expect(page.locator('[data-cy="toast"]').getByText(`Verification email sent to ${email}`)).toBeVisible(); - }); - - // Adding the login fixture to the test to skip it on production - test('Should display the error toast message if the user already exists', async ({ page, skipOnEmptyEnvironment, login }) => { - await page.goto(url); - - await page.locator('[data-cy="signup-btn"]').click(); - - await page.locator('input[name=email]').fill(config.E2E_ADMIN_USERNAME); - await page.locator('input[name=password]').fill('anyPassword'); - await page.locator('input[name=passwordConfirm]').fill('anyPassword'); - await page.locator('[data-cy="signup-btn"]').click(); - await expect(page.locator('[data-cy="toast"]').getByText('name already in use')).toBeVisible(); - }); - - test('Should display the error toast message if any other sign up error occurs', async ({ page }) => { - await page.goto(url); - - await page.locator('[data-cy="signup-btn"]').click(); - - await page.locator('input[name=email]').fill('test@test.com'); - await page.locator('input[name=password]').fill('anyPassword'); - await page.locator('input[name=passwordConfirm]').fill('anyPassword'); - - await conditionalIntercept( - page, - '**/register', - (req) => req.method() === 'POST', - { error: 'Something bad happened :(' }, - 'Register', - 500 - ); - - await page.locator('[data-cy="signup-btn"]').click(); - await waitForRequest('Register'); - await expect(page.locator('[data-cy="toast"]')).toBeVisible({ timeout: 30000 }); - await expect(page.locator('[data-cy="toast"]').getByText('Something bad happened :(')).toBeVisible(); - }); - - test('Should redirect to specific page after sign up if redirectTo is provided', async ({ page }) => { - const redirectTo = '/cite/10/'; - await page.goto(`${url}?redirectTo=${redirectTo}`); - - await page.locator('[data-cy="signup-btn"]').click(); - - const email = 'newUser@test.com'; - const password = 'newUserPassword'; - - await page.locator('input[name=email]').fill(email); - await page.locator('input[name=password]').fill(password); - await page.locator('input[name=passwordConfirm]').fill(password); - - await conditionalIntercept( - page, - '**/register', - (req) => req.postDataJSON().email == email && req.postDataJSON().password == password, - { statusCode: 201 }, - 'Register' - ); - - await page.locator('[data-cy="signup-btn"]').click(); - await waitForRequest('Register'); - await page.waitForURL(redirectTo); - }); - - test('Should display success toast message after a subscription to Major updates', async ({ page }) => { - await page.goto(url); - - const email = 'newUser@test.com'; - - await page.locator('input[name=emailSubscription]').fill(email); - - await conditionalIntercept( - page, - '**/register', - (req) => req.postDataJSON().email == email && req.postDataJSON().password == '123456', - { statusCode: 201 }, - 'Register' - ); - - await page.locator('[data-cy="subscribe-to-updates-btn"]').click(); - await waitForRequest('Register'); - await expect(page.locator('[data-cy="toast"]').getByText(`Thanks for subscribing to our Newsletter!`)).toBeVisible(); - }); -}); diff --git a/site/gatsby-site/src/pages/confirmemail.js b/site/gatsby-site/src/pages/confirmemail.js deleted file mode 100644 index bf73e1deb2..0000000000 --- a/site/gatsby-site/src/pages/confirmemail.js +++ /dev/null @@ -1,93 +0,0 @@ -import React, { useEffect, useState } from 'react'; -import { Trans, useTranslation } from 'react-i18next'; -import { useUserContext } from 'contexts/UserContext'; -import { StringParam, useQueryParams } from 'use-query-params'; -import Link from '../components/ui/Link'; -import { Spinner } from 'flowbite-react'; -import useToastContext, { SEVERITY } from 'hooks/useToast'; - -const ConfirmEmail = () => { - const { - actions: { confirmEmail }, - } = useUserContext(); - - const { t } = useTranslation(); - - const addToast = useToastContext(); - - const [confirmed, setConfirmed] = useState(null); - - const [{ token, tokenId }] = useQueryParams({ - token: StringParam, - tokenId: StringParam, - }); - - useEffect(() => { - if (token && tokenId) { - confirmEmail({ token, tokenId }) - .then(() => { - setConfirmed(true); - addToast({ - message: ( - <> - {' '} - - Login - - - ), - severity: SEVERITY.success, - }); - }) - .catch((e) => { - setConfirmed(false); - addToast({ - message: ( - <> - {' '} - - Login - - - ), - severity: SEVERITY.danger, - error: e, - }); - }); - } else { - setConfirmed(false); - addToast({ - message: ( - <> - - - Login - - - ), - severity: SEVERITY.danger, - }); - } - }, []); - - return ( - <> - {confirmed ? ( -

- Thank you for verifying your account. -

- ) : confirmed === false ? ( -

- An unknown error has occurred -

- ) : ( -
- - Loading... -
- )} - - ); -}; - -export default ConfirmEmail; diff --git a/site/gatsby-site/src/pages/forgotpassword.js b/site/gatsby-site/src/pages/forgotpassword.js deleted file mode 100644 index 89aa0a8c3a..0000000000 --- a/site/gatsby-site/src/pages/forgotpassword.js +++ /dev/null @@ -1,94 +0,0 @@ -import React from 'react'; -import { useUserContext } from 'contexts/UserContext'; -import useToastContext, { SEVERITY } from '../hooks/useToast'; -import { Form, Formik } from 'formik'; -import * as Yup from 'yup'; -import { Trans, useTranslation } from 'react-i18next'; -import TextInputGroup from 'components/forms/TextInputGroup'; -import { Button, Spinner } from 'flowbite-react'; - -const ForgotPasswordSchema = Yup.object().shape({ - email: Yup.string().email('Invalid email').required('Required'), -}); - -const ForgotPassword = () => { - const { - actions: { sendResetPasswordEmail }, - loading, - } = useUserContext(); - - const { t } = useTranslation(); - - const addToast = useToastContext(); - - return ( - <> - {loading ? ( -
- - Loading... -
- ) : ( - { - try { - await sendResetPasswordEmail({ email }); - - addToast({ - message: <>{t('Succesfully sent password reset email', { ns: 'login' })}, - severity: SEVERITY.success, - }); - } catch (e) { - addToast({ - message: ( - - ), - severity: SEVERITY.danger, - error: e, - }); - } - - setSubmitting(false); - }} - > - {({ - values, - errors, - touched, - handleChange, - handleBlur, - handleSubmit, - isSubmitting, - isValid, - }) => ( -
-
- -
- -
- )} -
- )} - - ); -}; - -export default ForgotPassword; diff --git a/site/gatsby-site/src/pages/logincallback.js b/site/gatsby-site/src/pages/logincallback.js deleted file mode 100644 index fc627c428f..0000000000 --- a/site/gatsby-site/src/pages/logincallback.js +++ /dev/null @@ -1,12 +0,0 @@ -import React from 'react'; -import * as Realm from 'realm-web'; - -const LoginCallback = (props) => { - if (props.location.hash.includes('client_app_id=')) { - Realm.handleAuthRedirect(); - } - - return <>Logging in...; -}; - -export default LoginCallback; From ede00f598819cbc5216869e64c83ae5f548265c8 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Mon, 25 Nov 2024 15:28:16 -0300 Subject: [PATCH 50/99] Add command to create magic links --- site/gatsby-site/package.json | 5 +-- site/gatsby-site/src/scripts/magic-link.ts | 39 ++++++++++++++++++++++ 2 files changed, 42 insertions(+), 2 deletions(-) create mode 100644 site/gatsby-site/src/scripts/magic-link.ts diff --git a/site/gatsby-site/package.json b/site/gatsby-site/package.json index 7e1972e01e..1e7ee5f4d2 100644 --- a/site/gatsby-site/package.json +++ b/site/gatsby-site/package.json @@ -140,7 +140,8 @@ "start:api": "node --env-file=.env --watch -r ts-node/register server/index.ts", "start:memory-mongo": "npx tsx --env-file=.env playwright/memory-mongo.ts", "start:memory-mongo:ci": "npx tsx playwright/memory-mongo.ts", - "clean": "gatsby clean" + "clean": "gatsby clean", + "magic-link": "npx tsx --env-file=.env src/scripts/magic-link.ts $1 $2" }, "devDependencies": { "@babel/plugin-proposal-export-default-from": "^7.23.3", @@ -207,4 +208,4 @@ "optionalDependencies": { "@parcel/watcher-linux-x64-glibc": "^2.4.0" } -} +} \ No newline at end of file diff --git a/site/gatsby-site/src/scripts/magic-link.ts b/site/gatsby-site/src/scripts/magic-link.ts new file mode 100644 index 0000000000..7f0bcb8028 --- /dev/null +++ b/site/gatsby-site/src/scripts/magic-link.ts @@ -0,0 +1,39 @@ +import { generateMagicLink } from "../../playwright/utils"; + +function displayHelp(): void { + console.log(` + Usage: npm run magic-link [callbackUrl] + + Arguments: + email Required. The email address to generate a magic link for + callbackUrl Optional. The URL to redirect to after authentication + + Examples: + npm run magic-link user@example.com + npm run magic-link user@example.com /apps/incidents + `); +} + +async function start() { + + const args = process.argv.slice(2); + + if (args.length === 0) { + console.error('Error: Email is required'); + displayHelp(); + process.exit(1); + } + + const email = args[0]; + const callbackUrl = args[1]; + + const magicLink = await generateMagicLink(email, callbackUrl); + + console.log(`\nMagic link: ${magicLink}\n\n`); +} + + +if (require.main === module) { + start(); +} + From 820f74bccf188aa1d4eb87ae4baf46a94ea3349f Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Mon, 25 Nov 2024 15:29:19 -0300 Subject: [PATCH 51/99] Update lockfile --- site/gatsby-site/package-lock.json | 2443 +--------------------------- 1 file changed, 1 insertion(+), 2442 deletions(-) diff --git a/site/gatsby-site/package-lock.json b/site/gatsby-site/package-lock.json index 7831f977dd..b33a7b66e5 100644 --- a/site/gatsby-site/package-lock.json +++ b/site/gatsby-site/package-lock.json @@ -130,7 +130,6 @@ }, "devDependencies": { "@babel/plugin-proposal-export-default-from": "^7.23.3", - "@cypress/code-coverage": "^3.12.15", "@graphql-codegen/cli": "^5.0.2", "@graphql-codegen/client-preset": "^4.3.3", "@graphql-codegen/typescript": "^4.0.6", @@ -147,12 +146,9 @@ "autoprefixer": "^10.4.7", "babel-eslint": "^10.1.0", "babel-plugin-istanbul": "^6.1.1", - "cypress": "^13.8.1", - "cypress-wait-for-stable-dom": "^0.1.0", "eslint": "^7.17.0", "eslint-config-prettier": "^7.1.0", "eslint-import-resolver-alias": "^1.1.2", - "eslint-plugin-cypress": "^2.12.1", "eslint-plugin-import": "^2.22.1", "eslint-plugin-jsx-a11y": "^6.4.1", "eslint-plugin-prettier": "^3.3.1", @@ -163,7 +159,6 @@ "lint-staged": "^10.5.3", "mongodb-memory-server": "^9.1.8", "netlify-cli": "^17.36.0", - "netlify-plugin-cypress": "^2.2.1", "postcss": "^8.4.31", "prettier": "^2.2.1", "prism-react-renderer": "^1.2.0", @@ -6097,226 +6092,6 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, - "node_modules/@cypress/code-coverage": { - "version": "3.13.7", - "resolved": "https://registry.npmjs.org/@cypress/code-coverage/-/code-coverage-3.13.7.tgz", - "integrity": "sha512-E8oT0D1cA363cot4q7wonaDATAPybj7/DJ3PAE+BCmEBy41aYWn5DaEYWLRYe8eg+h4IlNPkZG1QMVo33pMcfw==", - "dev": true, - "dependencies": { - "@cypress/webpack-preprocessor": "^6.0.0", - "chalk": "4.1.2", - "dayjs": "1.11.13", - "debug": "4.3.7", - "execa": "4.1.0", - "globby": "11.1.0", - "istanbul-lib-coverage": "^3.0.0", - "js-yaml": "4.1.0", - "nyc": "15.1.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.1", - "@babel/preset-env": "^7.0.0", - "babel-loader": "^8.3 || ^9", - "cypress": "*", - "webpack": "^4 || ^5" - } - }, - "node_modules/@cypress/code-coverage/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/@cypress/code-coverage/node_modules/argparse": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", - "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", - "dev": true - }, - "node_modules/@cypress/code-coverage/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/@cypress/code-coverage/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/@cypress/code-coverage/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/@cypress/code-coverage/node_modules/debug": { - "version": "4.3.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.7.tgz", - "integrity": "sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@cypress/code-coverage/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/@cypress/code-coverage/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", - "dev": true, - "dependencies": { - "argparse": "^2.0.1" - }, - "bin": { - "js-yaml": "bin/js-yaml.js" - } - }, - "node_modules/@cypress/code-coverage/node_modules/ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "dev": true - }, - "node_modules/@cypress/code-coverage/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/@cypress/request": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@cypress/request/-/request-3.0.6.tgz", - "integrity": "sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==", - "dev": true, - "dependencies": { - "aws-sign2": "~0.7.0", - "aws4": "^1.8.0", - "caseless": "~0.12.0", - "combined-stream": "~1.0.6", - "extend": "~3.0.2", - "forever-agent": "~0.6.1", - "form-data": "~4.0.0", - "http-signature": "~1.4.0", - "is-typedarray": "~1.0.0", - "isstream": "~0.1.2", - "json-stringify-safe": "~5.0.1", - "mime-types": "~2.1.19", - "performance-now": "^2.1.0", - "qs": "6.13.0", - "safe-buffer": "^5.1.2", - "tough-cookie": "^5.0.0", - "tunnel-agent": "^0.6.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/@cypress/request/node_modules/http-signature": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.4.0.tgz", - "integrity": "sha512-G5akfn7eKbpDN+8nPS/cb57YeA1jLTVxjpCj7tmm3QKPdyDy7T+qSC40e9ptydSWvkwjSXw1VbkpyEm39ukeAg==", - "dev": true, - "dependencies": { - "assert-plus": "^1.0.0", - "jsprim": "^2.0.2", - "sshpk": "^1.18.0" - }, - "engines": { - "node": ">=0.10" - } - }, - "node_modules/@cypress/webpack-preprocessor": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/@cypress/webpack-preprocessor/-/webpack-preprocessor-6.0.2.tgz", - "integrity": "sha512-0+1+4iy4W9PE6R5ywBNKAZoFp8Sf//w3UJ+CKTqkcAjA29b+dtsD0iFT70DsYE0BMqUM1PO7HXFGbXllQ+bRAA==", - "dev": true, - "dependencies": { - "bluebird": "3.7.1", - "debug": "^4.3.4", - "lodash": "^4.17.20" - }, - "peerDependencies": { - "@babel/core": "^7.0.1", - "@babel/preset-env": "^7.0.0", - "babel-loader": "^8.3 || ^9", - "webpack": "^4 || ^5" - } - }, - "node_modules/@cypress/webpack-preprocessor/node_modules/bluebird": { - "version": "3.7.1", - "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.1.tgz", - "integrity": "sha512-DdmyoGCleJnkbp3nkbxTLJ18rjDsE4yCggEwKNXkeV123sPNfOCYeDoeuOY+F2FrSjO1YXcTU+dsy96KMy+gcg==", - "dev": true - }, - "node_modules/@cypress/xvfb": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", - "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", - "dev": true, - "dependencies": { - "debug": "^3.1.0", - "lodash.once": "^4.1.1" - } - }, - "node_modules/@cypress/xvfb/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/@eslint-community/regexpp": { "version": "4.6.1", "license": "MIT", @@ -9872,18 +9647,6 @@ "version": "1.1.4", "license": "MIT" }, - "node_modules/@koa/cors": { - "version": "3.4.3", - "resolved": "https://registry.npmjs.org/@koa/cors/-/cors-3.4.3.tgz", - "integrity": "sha512-WPXQUaAeAMVaLTEFpoq3T2O1C+FstkjJnDQqy95Ck1UdILajsRhu6mhJ8H2f4NFPRBoCNN+qywTJfq/gGki5mw==", - "dev": true, - "dependencies": { - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 8.0.0" - } - }, "node_modules/@lezer/common": { "version": "1.2.1", "license": "MIT" @@ -12277,18 +12040,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@sindresorhus/is": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@sindresorhus/is/-/is-2.1.1.tgz", - "integrity": "sha512-/aPsuoj/1Dw/kzhkgz+ES6TxG0zfTMGLwuK2ZG00k/iJzYHTLCE8mVU8EPqEOp/lmxPoq1C1C9RYToRKb2KEfg==", - "dev": true, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/is?sponsor=1" - } - }, "node_modules/@sindresorhus/slugify": { "version": "1.1.2", "license": "MIT", @@ -13707,15 +13458,6 @@ "version": "0.3.0", "license": "MIT" }, - "node_modules/@tootallnate/once": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", - "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", - "dev": true, - "engines": { - "node": ">= 6" - } - }, "node_modules/@trysound/sax": { "version": "0.2.0", "license": "ISC", @@ -14264,12 +14006,6 @@ "dev": true, "license": "MIT" }, - "node_modules/@types/sizzle": { - "version": "2.3.9", - "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.9.tgz", - "integrity": "sha512-xzLEyKB50yqCUPUJkIsrVvoWNfFUbIZI+RspLWt8u+tIW/BetMBZtgV2LY/2o+tYH8dRvQ+eoPf3NdhQCcLE2w==", - "dev": true - }, "node_modules/@types/stack-utils": { "version": "2.0.3", "dev": true, @@ -15061,18 +14797,6 @@ "node": ">=6" } }, - "node_modules/ansi-escape-sequences": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/ansi-escape-sequences/-/ansi-escape-sequences-5.1.2.tgz", - "integrity": "sha512-JcpoVp1W1bl1Qn4cVuiXEhD6+dyXKSOgCn2zlzE8inYgCJCBy1aPnUhlz6I4DFum8D4ovb9Qi/iAjUcGvG2lqw==", - "dev": true, - "dependencies": { - "array-back": "^4.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/ansi-escapes": { "version": "4.3.2", "license": "MIT", @@ -15142,42 +14866,10 @@ "version": "1.0.0", "license": "MIT" }, - "node_modules/append-transform": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/append-transform/-/append-transform-2.0.0.tgz", - "integrity": "sha512-7yeyCEurROLQJFv5Xj4lEGTy0borxepjFv1g22oAdqFu//SrAlDl1O1Nxx15SH1RoliUml6p8dwJW9jvZughhg==", - "dev": true, - "dependencies": { - "default-require-extensions": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/application-config-path": { "version": "0.1.1", "license": "MIT" }, - "node_modules/arch": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", - "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/archy": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", @@ -15212,15 +14904,6 @@ "deep-equal": "^2.0.5" } }, - "node_modules/array-back": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", - "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/array-flatten": { "version": "1.1.1", "license": "MIT" @@ -15892,30 +15575,6 @@ "node": "^4.5.0 || >= 5.9" } }, - "node_modules/basic-auth": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/basic-auth/-/basic-auth-2.0.1.tgz", - "integrity": "sha512-NF+epuEdnUYVlGuhaxbbq+dvJttwLnGY+YixlXlME5KpQ5W3CnXA5cVTneY3SPbPDRkcjMbifrwmFYcClgOZeg==", - "dev": true, - "dependencies": { - "safe-buffer": "5.1.2" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/basic-auth/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/batch": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/batch/-/batch-0.6.1.tgz", - "integrity": "sha512-x+VAiMRL6UPkx+kudNvxTl6hB2XNNCG2r+7wixVfIYwu/2HKRXimwQyaumLjMveWvT2Hkd/cAJw+QBMfJ/EKVw==", - "dev": true - }, "node_modules/bcp-47-match": { "version": "2.0.3", "license": "MIT", @@ -15992,12 +15651,6 @@ "readable-stream": "^3.4.0" } }, - "node_modules/blob-util": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", - "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", - "dev": true - }, "node_modules/bluebird": { "version": "3.7.2", "license": "MIT" @@ -16295,15 +15948,6 @@ "node": ">=10.16.0" } }, - "node_modules/byte-size": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/byte-size/-/byte-size-6.2.0.tgz", - "integrity": "sha512-6EspYUCAPMc7E2rltBgKwhG+Cmk0pDm9zDtF1Awe2dczNUL3YpZ8mTs/dueOTS1hqGWBOatqef4jYMGjln7WmA==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/bytemd": { "version": "1.20.2", "license": "MIT", @@ -16468,19 +16112,6 @@ "node": ">= 0.8" } }, - "node_modules/cache-content-type": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/cache-content-type/-/cache-content-type-1.0.1.tgz", - "integrity": "sha512-IKufZ1o4Ut42YUrZSo8+qnMTrFuKkvyoLXUywKz9GJ5BrhOFGhLdkx9sG4KAnVvbY6kEcSFjLQul+DVmBm2bgA==", - "dev": true, - "dependencies": { - "mime-types": "^2.1.18", - "ylru": "^1.2.0" - }, - "engines": { - "node": ">= 6.0.0" - } - }, "node_modules/cache-manager": { "version": "2.11.1", "license": "MIT", @@ -16513,63 +16144,6 @@ "node": ">=10.6.0" } }, - "node_modules/cacheable-request": { - "version": "7.0.4", - "resolved": "https://registry.npmjs.org/cacheable-request/-/cacheable-request-7.0.4.tgz", - "integrity": "sha512-v+p6ongsrp0yTGbJXjgxPow2+DL93DASP4kXCDKb8/bwRtt9OEF3whggkkDkGNzgcWy2XaF4a8nZglC7uElscg==", - "dev": true, - "dependencies": { - "clone-response": "^1.0.2", - "get-stream": "^5.1.0", - "http-cache-semantics": "^4.0.0", - "keyv": "^4.0.0", - "lowercase-keys": "^2.0.0", - "normalize-url": "^6.0.1", - "responselike": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cacheable-request/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/cachedir": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.4.0.tgz", - "integrity": "sha512-9EtFOZR8g22CL7BWjJ9BUx1+A/djkofnyW3aOXZORNW2kxoUpx2h+uN2cOqwPmFhnpVmxg+KW2OjOSgChTEvsQ==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/caching-transform": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/caching-transform/-/caching-transform-4.0.0.tgz", - "integrity": "sha512-kpqOvwXnjjN44D89K5ccQC+RUrsy7jB/XLlRrx0D7/2HNcTPqzsb6XgYoErwko6QsV184CA2YgS1fxDiiDZMWA==", - "dev": true, - "dependencies": { - "hasha": "^5.0.0", - "make-dir": "^3.0.0", - "package-hash": "^4.0.0", - "write-file-atomic": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/call-bind": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", @@ -17003,21 +16577,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/cli-table3": { - "version": "0.6.5", - "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.5.tgz", - "integrity": "sha512-+W/5efTR7y5HRD7gACw9yQjqMVvEMLBHmboM/kPWam+H+Hmyrgjh6YncVKK122YZkXrLudzTuAukUw9FnMf7IQ==", - "dev": true, - "dependencies": { - "string-width": "^4.2.0" - }, - "engines": { - "node": "10.* || >= 12.*" - }, - "optionalDependencies": { - "@colors/colors": "1.5.0" - } - }, "node_modules/cli-truncate": { "version": "2.1.0", "dev": true, @@ -17387,63 +16946,6 @@ "version": "1.2.9", "license": "MIT" }, - "node_modules/command-line-args": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", - "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", - "dev": true, - "dependencies": { - "array-back": "^3.1.0", - "find-replace": "^3.0.0", - "lodash.camelcase": "^4.3.0", - "typical": "^4.0.0" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/command-line-args/node_modules/array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/command-line-args/node_modules/typical": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", - "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/command-line-usage": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz", - "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", - "dev": true, - "dependencies": { - "array-back": "^4.0.2", - "chalk": "^2.4.2", - "table-layout": "^1.0.2", - "typical": "^5.2.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/command-line-usage/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/commander": { "version": "6.2.1", "dev": true, @@ -17452,18 +16954,6 @@ "node": ">= 6" } }, - "node_modules/common-log-format": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/common-log-format/-/common-log-format-1.0.0.tgz", - "integrity": "sha512-fFn/WPNbsTCGTTwdCpZfVZSa5mgqMEkA0gMTRApFSlEsYN+9B2FPfiqch5FT+jsv5IV1RHV3GeZvCa7Qg+jssw==", - "dev": true, - "bin": { - "clf": "bin/cli.js" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/common-path-prefix": { "version": "3.0.0", "license": "ISC" @@ -17670,12 +17160,6 @@ "node": ">=8" } }, - "node_modules/convert-source-map": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz", - "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A==", - "dev": true - }, "node_modules/cookie": { "version": "0.5.0", "license": "MIT", @@ -17697,25 +17181,6 @@ "dev": true, "license": "MIT" }, - "node_modules/cookies": { - "version": "0.9.1", - "resolved": "https://registry.npmjs.org/cookies/-/cookies-0.9.1.tgz", - "integrity": "sha512-TG2hpqe4ELx54QER/S3HQ9SRVnQnGBtKUz5bLQWtYAQ+o6GpgMs6sYUvaiJjVxb+UXwhRhAEP3m7LbsIZ77Hmw==", - "dev": true, - "dependencies": { - "depd": "~2.0.0", - "keygrip": "~1.1.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/copy-to": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/copy-to/-/copy-to-2.0.1.tgz", - "integrity": "sha512-3DdaFaU/Zf1AnpLiFDeNCD4TOWe3Zl2RZaTzUvWiIk5ERzcCodOE20Vqq4fzCbNoHURFHT4/us/Lfq+S2zyY4w==", - "dev": true - }, "node_modules/core-js": { "version": "3.34.0", "hasInstallScript": true, @@ -18008,15 +17473,6 @@ "node": ">=8" } }, - "node_modules/create-mixin": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/create-mixin/-/create-mixin-3.0.0.tgz", - "integrity": "sha512-LkdMqnWT9LaqBN4huqpUnMz56Yr1mVSoCduAd2xXefgH/YZP2sXCMAyztXjk4q8hTF/TlcDa+zQW2aTgGdjjKQ==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/create-react-class": { "version": "15.7.0", "license": "MIT", @@ -18427,197 +17883,6 @@ "node": ">=0.4.0" } }, - "node_modules/cypress": { - "version": "13.16.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-13.16.0.tgz", - "integrity": "sha512-g6XcwqnvzXrqiBQR/5gN+QsyRmKRhls1y5E42fyOvsmU7JuY+wM6uHJWj4ZPttjabzbnRvxcik2WemR8+xT6FA==", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "@cypress/request": "^3.0.6", - "@cypress/xvfb": "^1.2.4", - "@types/sinonjs__fake-timers": "8.1.1", - "@types/sizzle": "^2.3.2", - "arch": "^2.2.0", - "blob-util": "^2.0.2", - "bluebird": "^3.7.2", - "buffer": "^5.7.1", - "cachedir": "^2.3.0", - "chalk": "^4.1.0", - "check-more-types": "^2.24.0", - "ci-info": "^4.0.0", - "cli-cursor": "^3.1.0", - "cli-table3": "~0.6.1", - "commander": "^6.2.1", - "common-tags": "^1.8.0", - "dayjs": "^1.10.4", - "debug": "^4.3.4", - "enquirer": "^2.3.6", - "eventemitter2": "6.4.7", - "execa": "4.1.0", - "executable": "^4.1.1", - "extract-zip": "2.0.1", - "figures": "^3.2.0", - "fs-extra": "^9.1.0", - "getos": "^3.2.1", - "is-installed-globally": "~0.4.0", - "lazy-ass": "^1.6.0", - "listr2": "^3.8.3", - "lodash": "^4.17.21", - "log-symbols": "^4.0.0", - "minimist": "^1.2.8", - "ospath": "^1.2.2", - "pretty-bytes": "^5.6.0", - "process": "^0.11.10", - "proxy-from-env": "1.0.0", - "request-progress": "^3.0.0", - "semver": "^7.5.3", - "supports-color": "^8.1.1", - "tmp": "~0.2.3", - "tree-kill": "1.2.2", - "untildify": "^4.0.0", - "yauzl": "^2.10.0" - }, - "bin": { - "cypress": "bin/cypress" - }, - "engines": { - "node": "^16.0.0 || ^18.0.0 || >=20.0.0" - } - }, - "node_modules/cypress-wait-for-stable-dom": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/cypress-wait-for-stable-dom/-/cypress-wait-for-stable-dom-0.1.0.tgz", - "integrity": "sha512-iVJc6CDzlu1xUnTcZph+zbkOlImaDelpvRv4G+3naugvjkF6b9EFpDmRCC/16xL1pqpkFq4rFyfhuNw4C3PQjw==", - "dev": true - }, - "node_modules/cypress/node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/cypress/node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/cypress/node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/cypress/node_modules/ci-info": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-4.1.0.tgz", - "integrity": "sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/sibiraj-s" - } - ], - "engines": { - "node": ">=8" - } - }, - "node_modules/cypress/node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "dev": true, - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/cypress/node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "dev": true - }, - "node_modules/cypress/node_modules/fs-extra": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", - "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", - "dev": true, - "dependencies": { - "at-least-node": "^1.0.0", - "graceful-fs": "^4.2.0", - "jsonfile": "^6.0.1", - "universalify": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cypress/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/cypress/node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "dev": true, - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/cypress/node_modules/supports-color": { - "version": "8.1.1", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", - "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/supports-color?sponsor=1" - } - }, "node_modules/d": { "version": "1.0.1", "license": "ISC", @@ -19039,12 +18304,6 @@ "date-fns": ">=2.0.0" } }, - "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "dev": true - }, "node_modules/debounce": { "version": "1.2.1", "license": "MIT" @@ -19097,30 +18356,6 @@ "node": ">=0.10" } }, - "node_modules/decompress-response": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-5.0.0.tgz", - "integrity": "sha512-TLZWWybuxWgoW7Lykv+gq9xvzOsUjQ9tF09Tj6NSTYGMTCHNXzrPnD6Hi+TgZq19PyTAGH4Ll/NIM/eTGglnMw==", - "dev": true, - "dependencies": { - "mimic-response": "^2.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/decompress-response/node_modules/mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/dedent": { "version": "0.7.0", "dev": true, @@ -19178,30 +18413,6 @@ "node": ">=0.10.0" } }, - "node_modules/default-require-extensions": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-3.0.1.tgz", - "integrity": "sha512-eXTJmRbm2TIt9MgWTsOH1wEuhew6XGZcMeGKCtLedIg/NCsg1iBePXkceTdK4Fii7pzmN9tGsZhKzZ4h7O/fxw==", - "dev": true, - "dependencies": { - "strip-bom": "^4.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/default-require-extensions/node_modules/strip-bom": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-4.0.0.tgz", - "integrity": "sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/defaults": { "version": "1.0.4", "dev": true, @@ -19485,12 +18696,6 @@ "url": "https://github.com/sponsors/wooorm" } }, - "node_modules/devtools-protocol": { - "version": "0.0.1045489", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1045489.tgz", - "integrity": "sha512-D+PTmWulkuQW4D1NTiCRCFxF7pQPn0hgp4YyX4wAQ6xYXKOadSWPR3ENGDQ47MW/Ewc9v2rpC/UEEGahgBYpSQ==", - "dev": true - }, "node_modules/dezalgo": { "version": "1.0.4", "dev": true, @@ -19675,12 +18880,6 @@ "version": "0.10.31", "license": "MIT" }, - "node_modules/duplexer3": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/duplexer3/-/duplexer3-0.1.5.tgz", - "integrity": "sha512-1A8za6ws41LQgv9HrE/66jyC5yuSjQ3L/KOpFtoBilsAK2iA2wuS5rTt1OCzIvtS2V7nVmedsUU+DGRcjBmOYA==", - "dev": true - }, "node_modules/duplexify": { "version": "4.1.2", "license": "MIT", @@ -20054,12 +19253,6 @@ "node": ">=0.10" } }, - "node_modules/es6-error": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/es6-error/-/es6-error-4.1.1.tgz", - "integrity": "sha512-Um/+FxMr9CISWh0bi5Zv0iOD+4cFh5qLeks1qhAopKVAJw3drgKbKySikp7wGhDL0HPeaja0P5ULZrxLkniUVg==", - "dev": true - }, "node_modules/es6-iterator": { "version": "2.0.3", "license": "MIT", @@ -20255,33 +19448,6 @@ "ms": "^2.1.1" } }, - "node_modules/eslint-plugin-cypress": { - "version": "2.15.2", - "resolved": "https://registry.npmjs.org/eslint-plugin-cypress/-/eslint-plugin-cypress-2.15.2.tgz", - "integrity": "sha512-CtcFEQTDKyftpI22FVGpx8bkpKyYXBlNge6zSo0pl5/qJvBAnzaD76Vu2AsP16d6mTj478Ldn2mhgrWV+Xr0vQ==", - "dev": true, - "dependencies": { - "globals": "^13.20.0" - }, - "peerDependencies": { - "eslint": ">= 3.2.1" - } - }, - "node_modules/eslint-plugin-cypress/node_modules/globals": { - "version": "13.24.0", - "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", - "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", - "dev": true, - "dependencies": { - "type-fest": "^0.20.2" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/eslint-plugin-flowtype": { "version": "5.10.0", "license": "BSD-3-Clause", @@ -20925,12 +20091,6 @@ "node": ">=6" } }, - "node_modules/eventemitter2": { - "version": "6.4.7", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.7.tgz", - "integrity": "sha512-tYUSVOGeQPKt/eC1ABfhHy5Xd96N3oIijJvN3O9+TsC28T5V9yX9oEfEK5faP0EFSNVOG97qtAS68GBrQB2hDg==", - "dev": true - }, "node_modules/execa": { "version": "4.1.0", "dev": true, @@ -20978,18 +20138,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/executable": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", - "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", - "dev": true, - "dependencies": { - "pify": "^2.2.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/exit": { "version": "0.1.2", "dev": true, @@ -21514,27 +20662,6 @@ "url": "https://github.com/avajs/find-cache-dir?sponsor=1" } }, - "node_modules/find-replace": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", - "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", - "dev": true, - "dependencies": { - "array-back": "^3.0.1" - }, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/find-replace/node_modules/array-back": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", - "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", - "dev": true, - "engines": { - "node": ">=6" - } - }, "node_modules/find-up": { "version": "5.0.0", "license": "MIT", @@ -21634,19 +20761,6 @@ "is-callable": "^1.1.3" } }, - "node_modules/foreground-child": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-2.0.0.tgz", - "integrity": "sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==", - "dev": true, - "dependencies": { - "cross-spawn": "^7.0.0", - "signal-exit": "^3.0.2" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/forever-agent": { "version": "0.6.1", "license": "Apache-2.0", @@ -21954,26 +21068,6 @@ "dev": true, "license": "MIT" }, - "node_modules/fromentries": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/fromentries/-/fromentries-1.3.2.tgz", - "integrity": "sha512-cHEpEQHUg0f8XdtZCc2ZAhrHzKzT0MrFUTcvx+hfxYu7rGMDc5SKoXFh+n4YigxsHXRzc6OrCshdR1bWH6HHyg==", - "dev": true, - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ] - }, "node_modules/fs-constants": { "version": "1.0.0", "license": "MIT" @@ -24602,15 +23696,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/getos": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", - "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", - "dev": true, - "dependencies": { - "async": "^3.2.0" - } - }, "node_modules/getpass": { "version": "0.1.7", "license": "MIT", @@ -24864,87 +23949,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/got": { - "version": "10.7.0", - "resolved": "https://registry.npmjs.org/got/-/got-10.7.0.tgz", - "integrity": "sha512-aWTDeNw9g+XqEZNcTjMMZSy7B7yE9toWOFYip7ofFTLleJhvZwUxxTxkTpKvF+p1SAA4VHmuEy7PiHTHyq8tJg==", - "dev": true, - "dependencies": { - "@sindresorhus/is": "^2.0.0", - "@szmarczak/http-timer": "^4.0.0", - "@types/cacheable-request": "^6.0.1", - "cacheable-lookup": "^2.0.0", - "cacheable-request": "^7.0.1", - "decompress-response": "^5.0.0", - "duplexer3": "^0.1.4", - "get-stream": "^5.0.0", - "lowercase-keys": "^2.0.0", - "mimic-response": "^2.1.0", - "p-cancelable": "^2.0.0", - "p-event": "^4.0.0", - "responselike": "^2.0.0", - "to-readable-stream": "^2.0.0", - "type-fest": "^0.10.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sindresorhus/got?sponsor=1" - } - }, - "node_modules/got/node_modules/cacheable-lookup": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/cacheable-lookup/-/cacheable-lookup-2.0.1.tgz", - "integrity": "sha512-EMMbsiOTcdngM/K6gV/OxF2x0t07+vMOWxZNSCRQMjO2MY2nhZQ6OYhOOpyQrbhqsgtvKGI7hcq6xjnA92USjg==", - "dev": true, - "dependencies": { - "@types/keyv": "^3.1.1", - "keyv": "^4.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/got/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "dev": true, - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/got/node_modules/mimic-response": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", - "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/got/node_modules/type-fest": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.10.0.tgz", - "integrity": "sha512-EUV9jo4sffrwlg8s0zDhP0T2WD3pru5Xi0+HTE3zTUmBaZNhfkite9PdSJwdXLwPVW0jnAHT56pZHIOYckPEiw==", - "dev": true, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/graceful-fs": { "version": "4.2.10", "license": "ISC" @@ -26169,59 +25173,6 @@ "readable-stream": "^3.1.1" } }, - "node_modules/http-assert": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/http-assert/-/http-assert-1.5.0.tgz", - "integrity": "sha512-uPpH7OKX4H25hBmU6G1jWNaqJGpTXxey+YOUizJUAgu0AjLUeC8D73hTrhvDS5D+GJN1DN1+hhc/eF/wpxtp0w==", - "dev": true, - "dependencies": { - "deep-equal": "~1.0.1", - "http-errors": "~1.8.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/http-assert/node_modules/deep-equal": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", - "integrity": "sha512-bHtC0iYvWhyaTzvV3CZgPeZQqCOBGyGsVV7v4eevpdkLHfiSrXUdBG+qAuSz4RI70sszvjQ1QSZ98An1yNwpSw==", - "dev": true - }, - "node_modules/http-assert/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-assert/node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/http-assert/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/http-cache-semantics": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", @@ -26241,20 +25192,6 @@ "node": ">= 0.8" } }, - "node_modules/http-proxy-agent": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", - "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", - "dev": true, - "dependencies": { - "@tootallnate/once": "1", - "agent-base": "6", - "debug": "4" - }, - "engines": { - "node": ">= 6" - } - }, "node_modules/http-shutdown": { "version": "1.2.2", "dev": true, @@ -27018,21 +25955,6 @@ "node": ">=6" } }, - "node_modules/is-generator-function": { - "version": "1.0.10", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.0.10.tgz", - "integrity": "sha512-jsEjy9l3yiXEQ+PsXdmBwEPcOxaXWLspKdplFUVI9vq1iZgIekeC0L167qeu86czQaxed3q/Uzuw0swL0irL8A==", - "dev": true, - "dependencies": { - "has-tostringtag": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, "node_modules/is-glob": { "version": "4.0.3", "license": "MIT", @@ -27513,18 +26435,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-hook": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/istanbul-lib-hook/-/istanbul-lib-hook-3.0.0.tgz", - "integrity": "sha512-Pt/uge1Q9s+5VAZ+pCo16TYMWPBIl+oaNIjgLQxcX0itS6ueeaA+pEfThZpH8WxhFgCiEb8sAJY6MdUKgiIWaQ==", - "dev": true, - "dependencies": { - "append-transform": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-instrument": { "version": "5.2.1", "dev": true, @@ -27540,35 +26450,6 @@ "node": ">=8" } }, - "node_modules/istanbul-lib-processinfo": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-processinfo/-/istanbul-lib-processinfo-2.0.3.tgz", - "integrity": "sha512-NkwHbo3E00oybX6NGJi6ar0B29vxyvNwoC7eJ4G4Yq28UfY758Hgn/heV8VRFhevPED4LXfFz0DQ8z/0kw9zMg==", - "dev": true, - "dependencies": { - "archy": "^1.0.0", - "cross-spawn": "^7.0.3", - "istanbul-lib-coverage": "^3.2.0", - "p-map": "^3.0.0", - "rimraf": "^3.0.0", - "uuid": "^8.3.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/istanbul-lib-processinfo/node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/istanbul-lib-report": { "version": "3.0.1", "dev": true, @@ -29494,31 +28375,6 @@ "graceful-fs": "^4.1.6" } }, - "node_modules/jsonparse": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/jsonparse/-/jsonparse-1.3.1.tgz", - "integrity": "sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==", - "dev": true, - "engines": [ - "node >= 0.2.0" - ] - }, - "node_modules/JSONStream": { - "version": "1.3.5", - "resolved": "https://registry.npmjs.org/JSONStream/-/JSONStream-1.3.5.tgz", - "integrity": "sha512-E+iruNOY8VV9s4JEbe1aNEm6MiszPRr/UfcHMz0TQh1BXSxHK+ASV1R6W4HpjBhSeS+54PIsAMCBmwD06LLsqQ==", - "dev": true, - "dependencies": { - "jsonparse": "^1.2.0", - "through": ">=2.2.7 <3" - }, - "bin": { - "JSONStream": "bin.js" - }, - "engines": { - "node": "*" - } - }, "node_modules/jsprim": { "version": "2.0.2", "engines": [ @@ -29580,18 +28436,6 @@ "safe-buffer": "^5.0.1" } }, - "node_modules/keygrip": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/keygrip/-/keygrip-1.1.0.tgz", - "integrity": "sha512-iYSchDJ+liQ8iwbSI2QqsQOvqv58eJCEanyJPJi+Khyu8smkcKSFUCbPwzFcL7YVtZ6eONjqRX/38caJ7QjRAQ==", - "dev": true, - "dependencies": { - "tsscmp": "1.0.6" - }, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -29621,280 +28465,6 @@ "node": ">= 8" } }, - "node_modules/koa": { - "version": "2.15.3", - "resolved": "https://registry.npmjs.org/koa/-/koa-2.15.3.tgz", - "integrity": "sha512-j/8tY9j5t+GVMLeioLaxweJiKUayFhlGqNTzf2ZGwL0ZCQijd2RLHK0SLW5Tsko8YyyqCZC2cojIb0/s62qTAg==", - "dev": true, - "dependencies": { - "accepts": "^1.3.5", - "cache-content-type": "^1.0.0", - "content-disposition": "~0.5.2", - "content-type": "^1.0.4", - "cookies": "~0.9.0", - "debug": "^4.3.2", - "delegates": "^1.0.0", - "depd": "^2.0.0", - "destroy": "^1.0.4", - "encodeurl": "^1.0.2", - "escape-html": "^1.0.3", - "fresh": "~0.5.2", - "http-assert": "^1.3.0", - "http-errors": "^1.6.3", - "is-generator-function": "^1.0.7", - "koa-compose": "^4.1.0", - "koa-convert": "^2.0.0", - "on-finished": "^2.3.0", - "only": "~0.0.2", - "parseurl": "^1.3.2", - "statuses": "^1.5.0", - "type-is": "^1.6.16", - "vary": "^1.1.2" - }, - "engines": { - "node": "^4.8.4 || ^6.10.1 || ^7.10.1 || >= 8.1.4" - } - }, - "node_modules/koa-bodyparser": { - "version": "4.4.1", - "resolved": "https://registry.npmjs.org/koa-bodyparser/-/koa-bodyparser-4.4.1.tgz", - "integrity": "sha512-kBH3IYPMb+iAXnrxIhXnW+gXV8OTzCu8VPDqvcDHW9SQrbkHmqPQtiZwrltNmSq6/lpipHnT7k7PsjlVD7kK0w==", - "dev": true, - "dependencies": { - "co-body": "^6.0.0", - "copy-to": "^2.0.1", - "type-is": "^1.6.18" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/koa-compose": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/koa-compose/-/koa-compose-4.1.0.tgz", - "integrity": "sha512-8ODW8TrDuMYvXRwra/Kh7/rJo9BtOfPc6qO8eAfC80CnCvSjSl0bkRM24X6/XBBEyj0v1nRUQ1LyOy3dbqOWXw==", - "dev": true - }, - "node_modules/koa-compress": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/koa-compress/-/koa-compress-3.1.0.tgz", - "integrity": "sha512-0m24/yS/GbhWI+g9FqtvStY+yJwTObwoxOvPok6itVjRen7PBWkjsJ8pre76m+99YybXLKhOJ62mJ268qyBFMQ==", - "dev": true, - "dependencies": { - "bytes": "^3.0.0", - "compressible": "^2.0.0", - "koa-is-json": "^1.0.0", - "statuses": "^1.0.0" - }, - "engines": { - "node": ">= 8.0.0" - } - }, - "node_modules/koa-compress/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/koa-conditional-get": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/koa-conditional-get/-/koa-conditional-get-2.0.0.tgz", - "integrity": "sha512-FTZYr681zfyW0bz8FDc55RJrRnicz6KPv2oA3GOf6knksJd0uJdfenKud+RtBjHzO0g1tVHNjwN6gk7OfHAtbQ==", - "dev": true - }, - "node_modules/koa-convert": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/koa-convert/-/koa-convert-2.0.0.tgz", - "integrity": "sha512-asOvN6bFlSnxewce2e/DK3p4tltyfC4VM7ZwuTuepI7dEQVcvpyFuBcEARu1+Hxg8DIwytce2n7jrZtRlPrARA==", - "dev": true, - "dependencies": { - "co": "^4.6.0", - "koa-compose": "^4.1.0" - }, - "engines": { - "node": ">= 10" - } - }, - "node_modules/koa-etag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/koa-etag/-/koa-etag-3.0.0.tgz", - "integrity": "sha512-HYU1zIsH4S9xOlUZGuZIP1PIiJ0EkBXgwL8PjFECb/pUYmAee8gfcvIovregBMYxECDhLulEWT2+ZRsA/lczCQ==", - "dev": true, - "dependencies": { - "etag": "^1.3.0", - "mz": "^2.1.0" - } - }, - "node_modules/koa-is-json": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/koa-is-json/-/koa-is-json-1.0.0.tgz", - "integrity": "sha512-+97CtHAlWDx0ndt0J8y3P12EWLwTLMXIfMnYDev3wOTwH/RpBGMlfn4bDXlMEg1u73K6XRE9BbUp+5ZAYoRYWw==", - "dev": true - }, - "node_modules/koa-json": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/koa-json/-/koa-json-2.0.2.tgz", - "integrity": "sha512-8+dz0T2ekDuNN1svYoKPCV2txotQ3Ufg8Fn5bft1T48MPJWiC/HKmkk+3xj9EC/iNZuFYeLRazN2h2o3RSUXuQ==", - "dev": true, - "dependencies": { - "koa-is-json": "1", - "streaming-json-stringify": "3" - } - }, - "node_modules/koa-morgan": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/koa-morgan/-/koa-morgan-1.0.1.tgz", - "integrity": "sha512-JOUdCNlc21G50afBXfErUrr1RKymbgzlrO5KURY+wmDG1Uvd2jmxUJcHgylb/mYXy2SjiNZyYim/ptUBGsIi3A==", - "dev": true, - "dependencies": { - "morgan": "^1.6.1" - } - }, - "node_modules/koa-range": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/koa-range/-/koa-range-0.3.0.tgz", - "integrity": "sha512-Ich3pCz6RhtbajYXRWjIl6O5wtrLs6kE3nkXc9XmaWe+MysJyZO7K4L3oce1Jpg/iMgCbj+5UCiMm/rqVtcDIg==", - "dev": true, - "dependencies": { - "stream-slice": "^0.1.2" - }, - "engines": { - "node": ">=7" - } - }, - "node_modules/koa-route": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/koa-route/-/koa-route-3.2.0.tgz", - "integrity": "sha512-8FsuWw/L+CUWJfpgN6vrlYUDNTheEinG8Zkm97GyuLJNyWjCVUs9p10Ih3jTIWwmDVQcz6827l0RKadAS5ibqA==", - "dev": true, - "dependencies": { - "debug": "*", - "methods": "~1.1.0", - "path-to-regexp": "^1.2.0" - } - }, - "node_modules/koa-route/node_modules/isarray": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", - "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", - "dev": true - }, - "node_modules/koa-route/node_modules/path-to-regexp": { - "version": "1.9.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", - "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", - "dev": true, - "dependencies": { - "isarray": "0.0.1" - } - }, - "node_modules/koa-send": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/koa-send/-/koa-send-5.0.1.tgz", - "integrity": "sha512-tmcyQ/wXXuxpDxyNXv5yNNkdAMdFRqwtegBXUaowiQzUKqJehttS0x2j0eOZDQAyloAth5w6wwBImnFzkUz3pQ==", - "dev": true, - "dependencies": { - "debug": "^4.1.1", - "http-errors": "^1.7.3", - "resolve-path": "^1.4.0" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/koa-send/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/koa-send/node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/koa-send/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/koa-static": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/koa-static/-/koa-static-5.0.0.tgz", - "integrity": "sha512-UqyYyH5YEXaJrf9S8E23GoJFQZXkBVJ9zYYMPGz919MSX1KuvAcycIuS0ci150HCoPf4XQVhQ84Qf8xRPWxFaQ==", - "dev": true, - "dependencies": { - "debug": "^3.1.0", - "koa-send": "^5.0.0" - }, - "engines": { - "node": ">= 7.6.0" - } - }, - "node_modules/koa-static/node_modules/debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, - "node_modules/koa/node_modules/http-errors": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.8.1.tgz", - "integrity": "sha512-Kpk9Sm7NmI+RHhnj6OIWDI1d6fIoFAtFt9RLaTMRlg/8w49juAStsrBgp0Dp4OdxdVbRIeKhtCUvoi/RuAhO4g==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.4", - "setprototypeof": "1.2.0", - "statuses": ">= 1.5.0 < 2", - "toidentifier": "1.0.1" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/koa/node_modules/http-errors/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/koa/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/language-subtag-registry": { "version": "0.3.22", "license": "CC0-1.0" @@ -30151,18 +28721,6 @@ "version": "4.3.0", "license": "MIT" }, - "node_modules/load-module": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/load-module/-/load-module-3.0.0.tgz", - "integrity": "sha512-ZqprfrTx4vfH5+1mgpspPh5JYsNyA193NkMUdb3GwpmVqMczOh8cUDJgZBmEZVlSR42JBGYTUxlBAX9LHIBtIA==", - "dev": true, - "dependencies": { - "array-back": "^4.0.1" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/loader-runner": { "version": "4.3.0", "license": "MIT", @@ -30182,37 +28740,6 @@ "node": ">=8.9.0" } }, - "node_modules/local-web-server": { - "version": "4.2.1", - "resolved": "https://registry.npmjs.org/local-web-server/-/local-web-server-4.2.1.tgz", - "integrity": "sha512-v71LZool2w7uYA+tDP5HhfjzUxz5SFfcrPPB/zC98yFFawt7A6fcmAr2MR4Q9AHk/A8oyd/wrhEJBJLndwHxNQ==", - "dev": true, - "dependencies": { - "lws": "^3.1.0", - "lws-basic-auth": "^2.0.0", - "lws-blacklist": "^3.0.0", - "lws-body-parser": "^2.0.0", - "lws-compress": "^2.0.0", - "lws-conditional-get": "^2.0.0", - "lws-cors": "^3.0.0", - "lws-index": "^2.0.0", - "lws-json": "^2.0.0", - "lws-log": "^2.0.0", - "lws-mime": "^2.0.0", - "lws-range": "^3.0.0", - "lws-request-monitor": "^2.0.0", - "lws-rewrite": "^3.1.1", - "lws-spa": "^3.0.0", - "lws-static": "^2.0.0", - "node-version-matches": "^2.0.1" - }, - "bin": { - "ws": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/locate-path": { "version": "6.0.0", "license": "MIT", @@ -30242,12 +28769,6 @@ "version": "4.2.0", "license": "MIT" }, - "node_modules/lodash.assignwith": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/lodash.assignwith/-/lodash.assignwith-4.2.0.tgz", - "integrity": "sha512-ZznplvbvtjK2gMvnQ1BR/zqPFZmS6jbK4p+6Up4xcRYA7yMIwxHCfbTcrYxXKzzqLsQ05eJPVznEW3tuwV7k1g==", - "dev": true - }, "node_modules/lodash.bind": { "version": "4.2.1", "license": "MIT" @@ -30365,12 +28886,6 @@ "version": "4.7.0", "license": "MIT" }, - "node_modules/lodash.throttle": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.throttle/-/lodash.throttle-4.1.1.tgz", - "integrity": "sha512-wIkUCfVKpVsWo3JSZlc+8MB5it+2AN5W8J7YVMST30UrvcQNZ1Okbj+rbVniijTWE6FGYy4XJq/rHkas8qJMLQ==", - "dev": true - }, "node_modules/lodash.truncate": { "version": "4.4.2", "license": "MIT" @@ -30617,234 +29132,6 @@ "node": ">=12" } }, - "node_modules/lws": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/lws/-/lws-3.1.0.tgz", - "integrity": "sha512-I8rTgZxz8OJL0hjdlDxs6WpcVG7WSyalVHPQXXK+WPNVjm3KhkT5gV0Qmsgm2FRLbRUp15tso80xmDxMsyt7zA==", - "dev": true, - "dependencies": { - "ansi-escape-sequences": "^5.1.2", - "array-back": "^4.0.1", - "byte-size": "^6.2.0", - "command-line-args": "^5.1.1", - "command-line-usage": "^6.1.0", - "create-mixin": "^3.0.0", - "koa": "^2.11.0", - "load-module": "^3.0.0", - "lodash.assignwith": "^4.2.0", - "node-version-matches": "^2.0.1", - "open": "^7.0.4", - "qrcode-terminal": "^0.12.0", - "reduce-flatten": "^3.0.0", - "typical": "^6.0.0", - "walk-back": "^4.0.0" - }, - "bin": { - "lws": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lws-basic-auth": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lws-basic-auth/-/lws-basic-auth-2.0.0.tgz", - "integrity": "sha512-zzyoGFLQPuKaQJvHMLmmSyfT6lIvocwcDXllTVW5brD0t0YgHYopILkzja+x+MIlJX/YhNKniaTSasujniYVjw==", - "dev": true, - "dependencies": { - "basic-auth": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lws-blacklist": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lws-blacklist/-/lws-blacklist-3.0.0.tgz", - "integrity": "sha512-KNXGDBmbj+UGfWMBAefe2vrfuWpEQms/9Fd7kfMScTqAKF6nrVoEs4pkxfefArG3bX0bu7jWLyB4tJGma5WC6Q==", - "dev": true, - "dependencies": { - "array-back": "^4.0.1", - "path-to-regexp": "^6.1.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lws-blacklist/node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true - }, - "node_modules/lws-body-parser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lws-body-parser/-/lws-body-parser-2.0.0.tgz", - "integrity": "sha512-QFDzln3sSdKWL9fVNWy2+ZmrKy/XaYRO0/FFB0MBrDCsNnzepeCD4I7rOOfyuphLn42yR8XUpWdcJ3Ii5aauRA==", - "dev": true, - "dependencies": { - "koa-bodyparser": "^4.2.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lws-compress": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lws-compress/-/lws-compress-2.0.0.tgz", - "integrity": "sha512-5qDXI9pukVYWm07WjAOfpItLXKtL8lCHvjmW4RiXULhTRJj1qqBjNcmqReyk8L7NLUKhc+8eqoDDJFKURQEp0w==", - "dev": true, - "dependencies": { - "koa-compress": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lws-conditional-get": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lws-conditional-get/-/lws-conditional-get-2.0.0.tgz", - "integrity": "sha512-U05yDlFJKIYa7gJZYfnc1HIEuXbKpDJztgkvNYyxCqJC28j/k9ORoNnFNOIHpBh/jlPJgV8x7uH34mIxFAryWA==", - "dev": true, - "dependencies": { - "koa-conditional-get": "^2.0.0", - "koa-etag": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lws-cors": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lws-cors/-/lws-cors-3.1.1.tgz", - "integrity": "sha512-JMqRHdZ8wS17LB9MbHZvOAiDE/2MD3TSODvEAmNkIPEvutKq1Z6wfuFbfiNjAQRGyImUfiUM99vJOFHmLCg2cw==", - "dev": true, - "dependencies": { - "@koa/cors": "^3.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lws-index": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lws-index/-/lws-index-2.0.0.tgz", - "integrity": "sha512-qfkeQmKYnd13LmQubzI5LtFV2N8PJQG4QvgSoefoiB3dWre9k2T4C7ajjOTKO8mgSzYpUEREduNcQcLyt62n0g==", - "dev": true, - "dependencies": { - "serve-index-75lb": "^2.0.1" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lws-json": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lws-json/-/lws-json-2.0.0.tgz", - "integrity": "sha512-vqUFrAQ5BGpkMS2Mm/ZhgvUMi6Tgia7YtESG7pKjNoiSsD+TxncG0nqp8YjUh2xrEzi/SYFc/ed+9ZOl/t0A0g==", - "dev": true, - "dependencies": { - "koa-json": "^2.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lws-log": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lws-log/-/lws-log-2.0.0.tgz", - "integrity": "sha512-YveoazSZ0Qb1Tljdm8G8yn9c+mAMXgvLMACZzh5aZIk7p8YJwiXf9r1S+xY7wbXEcKG629KfVO0B5G5gRFcyDQ==", - "dev": true, - "dependencies": { - "koa-morgan": "^1.0.1", - "stream-log-stats": "^3.0.2" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lws-mime": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lws-mime/-/lws-mime-2.0.0.tgz", - "integrity": "sha512-mfrAgRQ5+hkQ7LJ6EAgwnUeymNeYxwLXZY3UQ6C2hSTr7BqMSzm9k5O0C8wWP2dzdhChzITYKwzWbUnAYVBwtA==", - "dev": true, - "engines": { - "node": ">=10" - } - }, - "node_modules/lws-range": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lws-range/-/lws-range-3.0.0.tgz", - "integrity": "sha512-7ZhA/LqQnKjolKBo/2BFj9DyDDXcJGY3v05TwYRD0qDGrxW4vuatEjluC3SV7ZO/k4PxDLdxuk+RCgL5t3ThtQ==", - "dev": true, - "dependencies": { - "koa-range": "^0.3.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lws-request-monitor": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lws-request-monitor/-/lws-request-monitor-2.0.0.tgz", - "integrity": "sha512-ZTo0/pS42qiejcYlL+wlpurSbDSS0J7pDDohqBx7jjUQkgni2Qd8cPzn/kW8QI82gXgDmdZH+ps0vheLHlgdgg==", - "dev": true, - "dependencies": { - "byte-size": "^6.2.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lws-rewrite": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/lws-rewrite/-/lws-rewrite-3.1.1.tgz", - "integrity": "sha512-cOeaPXIlLUVLxS6BZ52QzZVzI8JjCzlWD4RWizB5Hd+0YGO0SPa3Vgk7CIghtAOsSdjtXg/wSOap2H1h+tw8BQ==", - "dev": true, - "dependencies": { - "array-back": "^4.0.1", - "http-proxy-agent": "^4.0.1", - "https-proxy-agent": "^5.0.0", - "koa-route": "^3.2.0", - "path-to-regexp": "^6.1.0" - }, - "bin": { - "lws-rewrite": "bin/cli.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lws-rewrite/node_modules/path-to-regexp": { - "version": "6.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.3.0.tgz", - "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", - "dev": true - }, - "node_modules/lws-spa": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/lws-spa/-/lws-spa-3.0.0.tgz", - "integrity": "sha512-Tz10LfuOTUsRG6z+OCJ/vBN+4LQWoAGIJ1R02CFPrDk0pY3rHezM7/cCpq6Z6dXD+ipdNE8alkVn4zL2M+eVGg==", - "dev": true, - "dependencies": { - "koa-send": "^5.0.0" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/lws-static": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lws-static/-/lws-static-2.0.0.tgz", - "integrity": "sha512-P25A0+IXdkB6Y6gZAG7X0mnaa+FJ8aTiWLUgM5kazaWmruRO7lyhSjitsA3y5TLI3DpPCZn0mWE4SRREujUZLg==", - "dev": true, - "dependencies": { - "koa-static": "^5.0.0" - }, - "engines": { - "node": ">=10" - } - }, "node_modules/mailersend": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/mailersend/-/mailersend-2.3.0.tgz", @@ -32568,49 +30855,6 @@ "node": ">=12" } }, - "node_modules/morgan": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/morgan/-/morgan-1.10.0.tgz", - "integrity": "sha512-AbegBVI4sh6El+1gNwvD5YIck7nSA36weD7xvIxG4in80j/UoK8AEGaWnnz8v1GxonMCltmlNs5ZKbGvl9b1XQ==", - "dev": true, - "dependencies": { - "basic-auth": "~2.0.1", - "debug": "2.6.9", - "depd": "~2.0.0", - "on-finished": "~2.3.0", - "on-headers": "~1.0.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/morgan/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/morgan/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/morgan/node_modules/on-finished": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", - "integrity": "sha512-ikqdkGAAyf/X/gPhXGvfgAytDZtDbr+bkNUJ0N9h5MI/dmdgCs3l6hoHrcUv41sRKew3jIwrp4qQDXiK99Utww==", - "dev": true, - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, "node_modules/move-file": { "version": "3.1.0", "license": "MIT", @@ -46496,42 +44740,6 @@ "node": "^12.20.0 || ^14.13.1 || >=16.0.0" } }, - "node_modules/netlify-plugin-cypress": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/netlify-plugin-cypress/-/netlify-plugin-cypress-2.2.1.tgz", - "integrity": "sha512-No93crt1+vaszMQ2UDWbXVvdBSwN1MQZff9cW8YrpA/er+PixjV2h0JbHgsRLUl/7Pe+NGjlcP/txQvvViK2zw==", - "dev": true, - "dependencies": { - "common-tags": "1.8.0", - "debug": "4.1.1", - "got": "10.7.0", - "local-web-server": "^4.2.1", - "puppeteer": "18.1.0", - "ramda": "0.27.1" - }, - "engines": { - "node": ">=10.18.1" - } - }, - "node_modules/netlify-plugin-cypress/node_modules/common-tags": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.0.tgz", - "integrity": "sha512-6P6g0uetGpW/sdyUy/iQQCbFF0kWVMSIVSyYz7Zgjcgh8mgw8PQzDNZeyZ5DQ2gM7LBoZPHmnjz8rUthkBG5tw==", - "dev": true, - "engines": { - "node": ">=4.0.0" - } - }, - "node_modules/netlify-plugin-cypress/node_modules/debug": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.1.1.tgz", - "integrity": "sha512-pYAIzeRo8J6KPEaJ0VWOh5Pzkbw/RetuzehGM7QRRX5he4fPHx2rdKMB256ehJCkX+XRQm16eZLqLNS8RSZXZw==", - "deprecated": "Debug versions >=3.2.0 <3.2.7 || >=4 <4.3.1 have a low-severity ReDos regression when used in a Node.js environment. It is recommended you upgrade to 3.2.7 or 4.3.1. (https://github.com/visionmedia/debug/issues/797)", - "dev": true, - "dependencies": { - "ms": "^2.1.1" - } - }, "node_modules/netlify-redirect-parser": { "version": "14.3.0", "license": "MIT", @@ -47026,18 +45234,6 @@ "node": ">=0.10.0" } }, - "node_modules/node-preload": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/node-preload/-/node-preload-0.2.1.tgz", - "integrity": "sha512-RM5oyBy45cLEoHqCeh+MNuFAxO0vTFBLskvQbOKnEE7YTTSN4tbN8QWDIPQ6L+WvKsB/qLEGpYe2ZZ9d4W9OIQ==", - "dev": true, - "dependencies": { - "process-on-spawn": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/node-releases": { "version": "2.0.14", "license": "MIT" @@ -47054,18 +45250,6 @@ "url": "https://github.com/sponsors/antelle" } }, - "node_modules/node-version-matches": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/node-version-matches/-/node-version-matches-2.0.1.tgz", - "integrity": "sha512-oqk6+05FC0dNVY5NuXuhPEMq+m1b9ZjS9SIhVE9EjwCHZspnmjSO8npbKAEieinR8GeEgbecoQcYIvI/Kwcf6Q==", - "dev": true, - "dependencies": { - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/nopt": { "version": "1.0.10", "license": "MIT", @@ -47193,126 +45377,6 @@ "version": "1.1.1", "license": "MIT" }, - "node_modules/nyc": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/nyc/-/nyc-15.1.0.tgz", - "integrity": "sha512-jMW04n9SxKdKi1ZMGhvUTHBN0EICCRkHemEoE5jm6mTYcqcdas0ATzgUgejlQUHMvpnOZqGB5Xxsv9KxJW1j8A==", - "dev": true, - "dependencies": { - "@istanbuljs/load-nyc-config": "^1.0.0", - "@istanbuljs/schema": "^0.1.2", - "caching-transform": "^4.0.0", - "convert-source-map": "^1.7.0", - "decamelize": "^1.2.0", - "find-cache-dir": "^3.2.0", - "find-up": "^4.1.0", - "foreground-child": "^2.0.0", - "get-package-type": "^0.1.0", - "glob": "^7.1.6", - "istanbul-lib-coverage": "^3.0.0", - "istanbul-lib-hook": "^3.0.0", - "istanbul-lib-instrument": "^4.0.0", - "istanbul-lib-processinfo": "^2.0.2", - "istanbul-lib-report": "^3.0.0", - "istanbul-lib-source-maps": "^4.0.0", - "istanbul-reports": "^3.0.2", - "make-dir": "^3.0.0", - "node-preload": "^0.2.1", - "p-map": "^3.0.0", - "process-on-spawn": "^1.0.0", - "resolve-from": "^5.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "spawn-wrap": "^2.0.0", - "test-exclude": "^6.0.0", - "yargs": "^15.0.2" - }, - "bin": { - "nyc": "bin/nyc.js" - }, - "engines": { - "node": ">=8.9" - } - }, - "node_modules/nyc/node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "dev": true, - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/istanbul-lib-instrument": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/istanbul-lib-instrument/-/istanbul-lib-instrument-4.0.3.tgz", - "integrity": "sha512-BXgQl9kf4WTCPCCpmFGoJkz/+uhvm7h7PFKUYxh7qarQd3ER33vHG//qaE8eN25l07YqZPpHXU9I09l/RD5aGQ==", - "dev": true, - "dependencies": { - "@babel/core": "^7.7.5", - "@istanbuljs/schema": "^0.1.2", - "istanbul-lib-coverage": "^3.0.0", - "semver": "^6.3.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "dev": true, - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "dev": true, - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/nyc/node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "dev": true, - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/nyc/node_modules/p-map": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/p-map/-/p-map-3.0.0.tgz", - "integrity": "sha512-d3qXVTF/s+W+CdJ5A29wywV2n8CQQYahlgz2bFiA+4eVNJbHJodPZ+/gXwPGh0bOqA+j8S+6+ckmvLGPk1QpxQ==", - "dev": true, - "dependencies": { - "aggregate-error": "^3.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/oauth": { "version": "0.9.15", "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz", @@ -47523,12 +45587,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/only": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/only/-/only-0.0.2.tgz", - "integrity": "sha512-Fvw+Jemq5fjjyWz6CpKx6w9s7xxqo3+JCyM0WXWeCSOboZ8ABkyvP8ID4CZuChA/wxSx+XSJmdOm8rGVyJ1hdQ==", - "dev": true - }, "node_modules/open": { "version": "7.4.2", "license": "MIT", @@ -47776,12 +45834,6 @@ "node": ">=0.10.0" } }, - "node_modules/ospath": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", - "integrity": "sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==", - "dev": true - }, "node_modules/p-cancelable": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", @@ -47970,21 +46022,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/package-hash": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/package-hash/-/package-hash-4.0.0.tgz", - "integrity": "sha512-whdkPIooSu/bASggZ96BWVvZTRMOFxnyUG5PnTSGKoJE2gd5mbVNmR2Nj20QFzxYYgAXpoqC+AiXzl+UMRh7zQ==", - "dev": true, - "dependencies": { - "graceful-fs": "^4.1.15", - "hasha": "^5.0.0", - "lodash.flattendeep": "^4.4.0", - "release-zalgo": "^1.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/package-json": { "version": "8.1.0", "license": "MIT", @@ -49522,18 +47559,6 @@ "version": "2.0.1", "license": "MIT" }, - "node_modules/process-on-spawn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/process-on-spawn/-/process-on-spawn-1.1.0.tgz", - "integrity": "sha512-JOnOPQ/8TZgjs1JIH/m9ni7FfimjNa/PRx7y/Wb5qdItsnhO0jE4AT7fC0HjC28DUQWDr50dwSYZLdRMlqDq3Q==", - "dev": true, - "dependencies": { - "fromentries": "^1.2.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/progress": { "version": "2.0.3", "license": "MIT", @@ -49644,12 +47669,6 @@ "node": ">= 0.10" } }, - "node_modules/proxy-from-env": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", - "integrity": "sha512-F2JHgJQ1iqwnHDcQjVBsq3n/uoaFL+iPW/eAeL7kVxy/2RrWaN4WroKjjvbsoRtv0ftelNyC01bjRhn/bhcf4A==", - "dev": true - }, "node_modules/ps-tree": { "version": "1.2.0", "dev": true, @@ -49687,36 +47706,6 @@ "node": ">=6" } }, - "node_modules/puppeteer": { - "version": "18.1.0", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-18.1.0.tgz", - "integrity": "sha512-2RCVWIF+pZOSfksWlQU0Hh6CeUT5NYt66CDDgRyuReu6EvBAk1y+/Q7DuzYNvGChSecGMb7QPN0hkxAa3guAog==", - "deprecated": "< 22.8.2 is no longer supported", - "dev": true, - "hasInstallScript": true, - "dependencies": { - "cross-fetch": "3.1.5", - "debug": "4.3.4", - "devtools-protocol": "0.0.1045489", - "extract-zip": "2.0.1", - "https-proxy-agent": "5.0.1", - "progress": "2.0.3", - "proxy-from-env": "1.1.0", - "rimraf": "3.0.2", - "tar-fs": "2.1.1", - "unbzip2-stream": "1.4.3", - "ws": "8.9.0" - }, - "engines": { - "node": ">=14.1.0" - } - }, - "node_modules/puppeteer/node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "dev": true - }, "node_modules/pure-rand": { "version": "6.1.0", "dev": true, @@ -49756,15 +47745,6 @@ "teleport": ">=0.2.0" } }, - "node_modules/qrcode-terminal": { - "version": "0.12.0", - "resolved": "https://registry.npmjs.org/qrcode-terminal/-/qrcode-terminal-0.12.0.tgz", - "integrity": "sha512-EXtzRZmC+YGmGlDFbXKxQiMZNwCLEO6BANKXG4iCtSIM0yqc/pappSx3RIKr4r0uh5JsBckOXeKrB3Iz7mdQpQ==", - "dev": true, - "bin": { - "qrcode-terminal": "bin/qrcode-terminal.js" - } - }, "node_modules/qs": { "version": "6.13.0", "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", @@ -50730,15 +48710,6 @@ "node": ">=4" } }, - "node_modules/reduce-flatten": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-3.0.1.tgz", - "integrity": "sha512-bYo+97BmUUOzg09XwfkwALt4PQH1M5L0wzKerBt6WLm3Fhdd43mMS89HiT1B9pJIqko/6lWx3OnV4J9f2Kqp5Q==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/redux": { "version": "4.2.1", "license": "MIT", @@ -51225,18 +49196,6 @@ "invariant": "^2.2.4" } }, - "node_modules/release-zalgo": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/release-zalgo/-/release-zalgo-1.0.0.tgz", - "integrity": "sha512-gUAyHVHPPC5wdqX/LG4LWtRYtgjxyX78oanFNTMMyFEfOqdC54s3eE82imuWKbOeqYht2CrNf64Qb8vgmmtZGA==", - "dev": true, - "dependencies": { - "es6-error": "^4.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/remark": { "version": "13.0.0", "license": "MIT", @@ -51856,15 +49815,6 @@ "version": "3.3.0", "license": "MIT" }, - "node_modules/request-progress": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", - "integrity": "sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==", - "dev": true, - "dependencies": { - "throttleit": "^1.0.0" - } - }, "node_modules/require-at": { "version": "1.0.6", "license": "Apache-2.0", @@ -51934,64 +49884,6 @@ "node": ">=8" } }, - "node_modules/resolve-path": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/resolve-path/-/resolve-path-1.4.0.tgz", - "integrity": "sha512-i1xevIst/Qa+nA9olDxLWnLk8YZbi8R/7JPbCMcgyWaFR6bKWaexgJgEB5oc2PKMjYdrHynyz0NY+if+H98t1w==", - "dev": true, - "dependencies": { - "http-errors": "~1.6.2", - "path-is-absolute": "1.0.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/resolve-path/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/resolve-path/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/resolve-path/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/resolve-path/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/resolve-path/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/resolve.exports": { "version": "2.0.2", "dev": true, @@ -52427,84 +50319,6 @@ "query-string": ">=5.1.1" } }, - "node_modules/serve-index-75lb": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/serve-index-75lb/-/serve-index-75lb-2.0.1.tgz", - "integrity": "sha512-/d9r8bqJlFQcwy0a0nb1KnWAA+Mno+V+VaoKocdkbW5aXKRQd/+4bfnRhQRQr6uEoYwTRJ4xgztOyCJvWcpBpQ==", - "dev": true, - "dependencies": { - "accepts": "~1.3.4", - "batch": "0.6.1", - "debug": "2.6.9", - "escape-html": "~1.0.3", - "http-errors": "~1.6.2", - "mime-types": "~2.1.18", - "parseurl": "~1.3.2" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/serve-index-75lb/node_modules/debug": { - "version": "2.6.9", - "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", - "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", - "dev": true, - "dependencies": { - "ms": "2.0.0" - } - }, - "node_modules/serve-index-75lb/node_modules/depd": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", - "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index-75lb/node_modules/http-errors": { - "version": "1.6.3", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", - "integrity": "sha512-lks+lVC8dgGyh97jxvxeYTWQFvh4uw4yC12gVl63Cg30sjPX4wuGcdkICVXDAESr6OJGjqGA8Iz5mkeN6zlD7A==", - "dev": true, - "dependencies": { - "depd": "~1.1.2", - "inherits": "2.0.3", - "setprototypeof": "1.1.0", - "statuses": ">= 1.4.0 < 2" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/serve-index-75lb/node_modules/inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", - "dev": true - }, - "node_modules/serve-index-75lb/node_modules/ms": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", - "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", - "dev": true - }, - "node_modules/serve-index-75lb/node_modules/setprototypeof": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", - "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==", - "dev": true - }, - "node_modules/serve-index-75lb/node_modules/statuses": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", - "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", - "dev": true, - "engines": { - "node": ">= 0.6" - } - }, "node_modules/serve-static": { "version": "1.15.0", "license": "MIT", @@ -53050,23 +50864,6 @@ "memory-pager": "^1.0.2" } }, - "node_modules/spawn-wrap": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/spawn-wrap/-/spawn-wrap-2.0.0.tgz", - "integrity": "sha512-EeajNjfN9zMnULLwhZZQU3GWBoFNkbngTUPfaawT4RkMiviTxcX0qfhVbGey39mfctfDHkWtuecgQ8NJcyQWHg==", - "dev": true, - "dependencies": { - "foreground-child": "^2.0.0", - "is-windows": "^1.0.2", - "make-dir": "^3.0.0", - "rimraf": "^3.0.0", - "signal-exit": "^3.0.2", - "which": "^2.0.1" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/spdx-correct": { "version": "3.2.0", "license": "Apache-2.0", @@ -53446,27 +51243,6 @@ "version": "2.11.0", "license": "MIT" }, - "node_modules/stream-log-stats": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/stream-log-stats/-/stream-log-stats-3.0.2.tgz", - "integrity": "sha512-393j7aeF9iRdHvyANqEQU82UQmpw2CTxgsT83caefh+lOxavVLbVrw8Mr4zjXeZLh2+xeHZMKfVx4T0rJ/EchA==", - "dev": true, - "dependencies": { - "ansi-escape-sequences": "^5.1.2", - "byte-size": "^6.2.0", - "common-log-format": "^1.0.0", - "JSONStream": "^1.3.5", - "lodash.throttle": "^4.1.1", - "stream-via": "^1.0.4", - "table-layout": "~1.0.0" - }, - "bin": { - "log-stats": "bin/cli.js" - }, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/stream-parser": { "version": "0.3.1", "license": "MIT", @@ -53489,67 +51265,6 @@ "version": "1.0.1", "license": "MIT" }, - "node_modules/stream-slice": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/stream-slice/-/stream-slice-0.1.2.tgz", - "integrity": "sha512-QzQxpoacatkreL6jsxnVb7X5R/pGw9OUv2qWTYWnmLpg4NdN31snPy/f3TdQE1ZUXaThRvj1Zw4/OGg0ZkaLMA==", - "dev": true - }, - "node_modules/stream-via": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/stream-via/-/stream-via-1.0.4.tgz", - "integrity": "sha512-DBp0lSvX5G9KGRDTkR/R+a29H+Wk2xItOF+MpZLLNDWbEV9tGPnqLPxHEYjmiz8xGtJHRIqmI+hCjmNzqoA4nQ==", - "dev": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/streaming-json-stringify": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/streaming-json-stringify/-/streaming-json-stringify-3.1.0.tgz", - "integrity": "sha512-axtfs3BDxAsrZ9swD163FBrXZ8dhJJp6kUI6C97TvUZG9RHKfbg9nFbXqEheFNOb3IYMEt2ag9F62sWLFUZ4ug==", - "dev": true, - "dependencies": { - "json-stringify-safe": "5", - "readable-stream": "2" - } - }, - "node_modules/streaming-json-stringify/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "dev": true - }, - "node_modules/streaming-json-stringify/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "dev": true, - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/streaming-json-stringify/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "dev": true - }, - "node_modules/streaming-json-stringify/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "dev": true, - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, "node_modules/streamsearch": { "version": "1.1.0", "engines": { @@ -54170,30 +51885,6 @@ "node": ">=10.0.0" } }, - "node_modules/table-layout": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", - "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", - "dev": true, - "dependencies": { - "array-back": "^4.0.1", - "deep-extend": "~0.6.0", - "typical": "^5.2.0", - "wordwrapjs": "^4.0.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/table-layout/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/table/node_modules/ajv": { "version": "8.12.0", "license": "MIT", @@ -54575,15 +52266,6 @@ "node": ">=0.8" } }, - "node_modules/throttleit": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.1.tgz", - "integrity": "sha512-vDZpf9Chs9mAdfY046mcPt8fg5QSZr37hEH4TXYBnDF+izxgrbRGUAAaBvIk/fJm9aOFCGFd1EsNg5AZCbnQCQ==", - "dev": true, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/through": { "version": "2.3.8", "license": "MIT" @@ -54653,24 +52335,6 @@ "tslib": "^2.0.3" } }, - "node_modules/tldts": { - "version": "6.1.61", - "resolved": "https://registry.npmjs.org/tldts/-/tldts-6.1.61.tgz", - "integrity": "sha512-rv8LUyez4Ygkopqn+M6OLItAOT9FF3REpPQDkdMx5ix8w4qkuE7Vo2o/vw1nxKQYmJDV8JpAMJQr1b+lTKf0FA==", - "dev": true, - "dependencies": { - "tldts-core": "^6.1.61" - }, - "bin": { - "tldts": "bin/cli.js" - } - }, - "node_modules/tldts-core": { - "version": "6.1.61", - "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-6.1.61.tgz", - "integrity": "sha512-In7VffkDWUPgwa+c9picLUxvb0RltVwTkSgMNFgvlGSWveCzGBemBqTsgJCL4EDFWZ6WH0fKTsot6yNhzy3ZzQ==", - "dev": true - }, "node_modules/tmp": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", @@ -54691,15 +52355,6 @@ "node": ">=4" } }, - "node_modules/to-readable-stream": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/to-readable-stream/-/to-readable-stream-2.1.0.tgz", - "integrity": "sha512-o3Qa6DGg1CEXshSdvWNX2sN4QHqg03SPq7U6jPXRahlQdl5dK8oXjkU/2/sGrnOZKeGV1zLSO8qPwyKklPPE7w==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/to-regex-range": { "version": "5.0.1", "license": "MIT", @@ -54756,18 +52411,6 @@ "version": "2.0.2", "license": "MIT" }, - "node_modules/tough-cookie": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-5.0.0.tgz", - "integrity": "sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==", - "dev": true, - "dependencies": { - "tldts": "^6.1.32" - }, - "engines": { - "node": ">=16" - } - }, "node_modules/tr46": { "version": "4.1.1", "license": "MIT", @@ -54783,15 +52426,6 @@ "license": "MIT", "optional": true }, - "node_modules/tree-kill": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/tree-kill/-/tree-kill-1.2.2.tgz", - "integrity": "sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==", - "dev": true, - "bin": { - "tree-kill": "cli.js" - } - }, "node_modules/trim-lines": { "version": "3.0.1", "license": "MIT", @@ -55005,15 +52639,6 @@ "ndarray-unpack": "^1.0.0" } }, - "node_modules/tsscmp": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/tsscmp/-/tsscmp-1.0.6.tgz", - "integrity": "sha512-LxhtAkPDTkVCMQjt2h6eBVY28KCjikZqZfMcC15YBeNjkgUpdCfBu5HoiOTDu86v6smE8yOjyEktJ8hlbANHQA==", - "dev": true, - "engines": { - "node": ">=0.6.x" - } - }, "node_modules/tsutils": { "version": "3.21.0", "license": "MIT", @@ -55126,15 +52751,6 @@ "node": ">=14.17" } }, - "node_modules/typical": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/typical/-/typical-6.0.1.tgz", - "integrity": "sha512-+g3NEp7fJLe9DPa1TArHm9QAA7YciZmWnfAqEaFrBihQ7epOv9i99rjtgb6Iz0wh3WuQDjsCTDfgRoGnmHN81A==", - "dev": true, - "engines": { - "node": ">=10" - } - }, "node_modules/ua-parser-js": { "version": "0.7.34", "funding": [ @@ -55677,15 +53293,6 @@ "dev": true, "license": "ISC" }, - "node_modules/untildify": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", - "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/untun": { "version": "0.1.3", "dev": true, @@ -56064,15 +53671,6 @@ "node": ">=12.0.0" } }, - "node_modules/walk-back": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/walk-back/-/walk-back-4.0.0.tgz", - "integrity": "sha512-kudCA8PXVQfrqv2mFTG72vDBRi8BKWxGgFLwPpzHcpZnSwZk93WMwUDVcLHWNsnm+Y0AC4Vb6MUNRgaHfyV2DQ==", - "dev": true, - "engines": { - "node": ">=8.0.0" - } - }, "node_modules/walker": { "version": "1.0.8", "dev": true, @@ -56407,37 +54005,6 @@ "node": ">=0.4.0" } }, - "node_modules/wordwrapjs": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", - "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", - "dev": true, - "dependencies": { - "reduce-flatten": "^2.0.0", - "typical": "^5.2.0" - }, - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/wordwrapjs/node_modules/reduce-flatten": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", - "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", - "dev": true, - "engines": { - "node": ">=6" - } - }, - "node_modules/wordwrapjs/node_modules/typical": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", - "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", - "dev": true, - "engines": { - "node": ">=8" - } - }, "node_modules/wrap-ansi": { "version": "7.0.0", "license": "MIT", @@ -56498,6 +54065,7 @@ "version": "8.9.0", "dev": true, "license": "MIT", + "peer": true, "engines": { "node": ">=10.0.0" }, @@ -56715,15 +54283,6 @@ "fd-slicer": "~1.1.0" } }, - "node_modules/ylru": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/ylru/-/ylru-1.4.0.tgz", - "integrity": "sha512-2OQsPNEmBCvXuFlIni/a+Rn+R2pHW9INm0BxXJ4hVDA8TirqMj+J/Rp9ItLatT/5pZqWwefVrTQcHpixsxnVlA==", - "dev": true, - "engines": { - "node": ">= 4.0.0" - } - }, "node_modules/yn": { "version": "3.1.1", "devOptional": true, From 56d6e85f2267ee576c5c0c097886c788cf337e3b Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Mon, 25 Nov 2024 16:49:08 -0300 Subject: [PATCH 52/99] Use email template for magic links --- site/gatsby-site/nextauth.config.ts | 19 +- .../server/emails/templates/MagicLink.ts | 234 ++++++++++++++++++ .../server/emails/templates/index.ts | 4 +- 3 files changed, 244 insertions(+), 13 deletions(-) create mode 100644 site/gatsby-site/server/emails/templates/MagicLink.ts diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts index 7c822074b0..c227f11c8e 100644 --- a/site/gatsby-site/nextauth.config.ts +++ b/site/gatsby-site/nextauth.config.ts @@ -1,7 +1,7 @@ -import { EmailParams, MailerSend, Recipient } from "mailersend" import { MongoClient, ServerApiVersion } from "mongodb" import { NextAuthOptions } from "next-auth" import config from './server/config' +import { sendEmail } from "./server/fields/common" //TODO: add this to the workflow file, this needs to be set via env variable // SEE: https://github.com/nextauthjs/next-auth/discussions/9785 @@ -16,19 +16,14 @@ const client = new MongoClient(config.API_MONGODB_CONNECTION_STRING!, { }, }) -const mailersend = new MailerSend({ - apiKey: config.MAILERSEND_API_KEY!, -}); - export const sendVerificationRequest = async ({ identifier: email, url }: { identifier: string, url: string }) => { - const emailParams = new EmailParams() - .setFrom({ email: process.env.NOTIFICATIONS_SENDER!, name: 'something' }) - .setTo([new Recipient(email)]) - .setSubject('something') - .setText(`Please click here to authenticate - ${url}`); - - await mailersend.email.send(emailParams); + await sendEmail({ + recipients: [{ email }], + subject: 'Login link', + templateId: 'MagicLink', + dynamicData: { magicLink: url }, + }) } export const getAuthConfig = async (): Promise => { diff --git a/site/gatsby-site/server/emails/templates/MagicLink.ts b/site/gatsby-site/server/emails/templates/MagicLink.ts new file mode 100644 index 0000000000..399f97ea6f --- /dev/null +++ b/site/gatsby-site/server/emails/templates/MagicLink.ts @@ -0,0 +1,234 @@ +export default ` + + + + + + + + + + + + + + + + +
+
+ + + + +
+ + + + +
+ + + + +
+ + + + + +
+ + + + + + +
+
+
AI INCIDENT + DATABASE
+
+
+
+ + + + + + +
+
+ Click here to log in:
+ {{magicLink}} +
+
+
+ +
+
+
+
+
+ + +`; \ No newline at end of file diff --git a/site/gatsby-site/server/emails/templates/index.ts b/site/gatsby-site/server/emails/templates/index.ts index 2247aef2f2..9d3e7cf032 100644 --- a/site/gatsby-site/server/emails/templates/index.ts +++ b/site/gatsby-site/server/emails/templates/index.ts @@ -1,5 +1,6 @@ import EntityIncidentUpdated from './EntityIncidentUpdated'; import IncidentUpdate from './IncidentUpdate'; +import MagicLink from './MagicLink'; import NewEntityIncident from './NewEntityIncident'; import NewIncident from './NewIncident'; import NewReportAddedToAnIncident from './NewReportAddedToAnIncident'; @@ -11,7 +12,8 @@ const templates: Record = { NewEntityIncident: NewEntityIncident, NewIncident: NewIncident, NewReportAddedToAnIncident: NewReportAddedToAnIncident, - SubmissionApproved: SubmissionApproved + SubmissionApproved: SubmissionApproved, + MagicLink: MagicLink, }; export default templates; \ No newline at end of file From 135476a995a83e1331a48d8552eb82e979b74847 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 26 Nov 2024 20:28:55 -0300 Subject: [PATCH 53/99] Update signup and login pages --- .../playwright/e2e-full/login.spec.ts | 84 +++++++++++ .../playwright/e2e-full/signup.spec.ts | 77 ++++++++++ .../gatsby-site/playwright/meta/utils.spec.ts | 2 +- site/gatsby-site/playwright/utils.ts | 4 +- site/gatsby-site/server/fields/common.ts | 7 +- .../components/landing/NewsletterSignup.js | 141 +----------------- site/gatsby-site/src/pages/login.js | 58 +++---- site/gatsby-site/src/pages/signup.js | 45 +++--- 8 files changed, 220 insertions(+), 198 deletions(-) create mode 100644 site/gatsby-site/playwright/e2e-full/login.spec.ts create mode 100644 site/gatsby-site/playwright/e2e-full/signup.spec.ts diff --git a/site/gatsby-site/playwright/e2e-full/login.spec.ts b/site/gatsby-site/playwright/e2e-full/login.spec.ts new file mode 100644 index 0000000000..65614daf62 --- /dev/null +++ b/site/gatsby-site/playwright/e2e-full/login.spec.ts @@ -0,0 +1,84 @@ +import { expect } from '@playwright/test'; +import { generateMagicLink, test, testUser } from '../utils'; +import { init } from '../memory-mongo'; + +test.describe('Login', () => { + const url = '/login'; + + test('Should successfully load login page', async ({ page }) => { + await page.goto(url); + }); + + test('Should redirect to home page after login by default', + async ({ page, skipOnEmptyEnvironment, login }) => { + await login(); + + await expect(page).toHaveURL('/'); + } + ); + + test('Should send redirectTo param to auth endpoint', async ({ page }) => { + + await init(); + + const initialUrl = '/cite/1/'; + + await page.goto(initialUrl); + + await page.locator('[data-cy="subscribe-btn"]').click(); + + await page.getByText('Login').click(); + + const email = testUser.email; + + await page.route('**/api/auth/signin/http-email', async (route) => { + + const formData = new URLSearchParams(await route.request().postData()); + + expect(formData.get('callbackUrl')).toBe(initialUrl); + + await route.fulfill({ + status: 200, + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + url: "http://localhost:8000/api/auth/verify-request?provider=http-email&type=email", + }) + }); + }); + + await expect(page.locator('input[name=email]')).toBeVisible(); + + await page.locator('input[name=email]').fill(email); + + const signupResponse = page.waitForResponse('**/api/auth/signin/http-email'); + + await page.locator('[data-cy="login-btn"]').click(); + + await signupResponse; + + await expect(page.getByText(`A sign in link has been sent to ${email}`)).toBeVisible(); + }); + + test('Should redirect to specific page after login if redirectTo is provided', + async ({ page, skipOnEmptyEnvironment, login }) => { + const redirectTo = '/cite/1/'; + + const magicLink = await generateMagicLink(testUser.email, redirectTo); + + await page.goto(magicLink); + + await expect(page).toHaveURL(redirectTo); + } + ); + + test('Should disable Login button if email address is not valid', async ({ page }) => { + await page.goto(url); + await page.locator('input[name=email]').fill('fakeUser'); + await expect(page.locator('[data-cy="login-btn"]')).toBeDisabled(); + + await page.locator('input[name=email]').fill('fakeUser@test.com'); + await expect(page.locator('[data-cy="login-btn"]')).toBeEnabled(); + }); +}); diff --git a/site/gatsby-site/playwright/e2e-full/signup.spec.ts b/site/gatsby-site/playwright/e2e-full/signup.spec.ts new file mode 100644 index 0000000000..a7e180059e --- /dev/null +++ b/site/gatsby-site/playwright/e2e-full/signup.spec.ts @@ -0,0 +1,77 @@ +import { expect } from '@playwright/test'; +import { test } from '../utils'; + +test.describe('Signup', () => { + const url = '/signup'; + + test('Should successfully load sign up page', async ({ page }) => { + await page.goto(url); + }); + + test('Should send callback url parameter and show magic link message after a sign up', async ({ page }) => { + await page.goto(url); + + const email = 'new.user@test.com'; + + await page.route('**/api/auth/signin/http-email', async (route) => { + + const formData = new URLSearchParams(await route.request().postData() || ''); + expect(formData.get('email')).toBe(email); + expect(formData.get('redirect')).toBe('false'); + expect(formData.get('callbackUrl')).toBe(`/account/?askToCompleteProfile=1`); + + await route.fulfill({ + status: 200, + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + url: 'http://localhost:8000/api/auth/verify-request?provider=http-email&type=email' + }) + }); + }); + + await page.locator('input[name=email]').fill(email); + + const signupResponse = page.waitForResponse('**/api/auth/signin/http-email'); + + await page.locator('[data-cy="signup-btn"]').click(); + + await signupResponse; + + await expect(page.getByText('A sign in link has been sent to new.user@test.com')).toBeVisible(); + }); + + test('Should display the error toast message if any other sign up error occurs', async ({ page }) => { + await page.goto(url); + + await page.route('**/api/auth/signin/http-email', async (route) => { + + await route.fulfill({ + status: 200, + headers: { + 'content-type': 'application/json', + 'cache-control': 'no-store, max-age=0', + 'connection': 'close', + 'transfer-encoding': 'chunked', + 'x-powered-by': 'Express' + }, + body: JSON.stringify({ + url: 'http://localhost:8000/api/auth/error?error=EmailSignin' + }) + }); + }); + + await page.locator('[data-cy="signup-btn"]').click(); + + await page.locator('input[name=email]').fill('test@test.com'); + + const signupResponse = page.waitForResponse('**/api/auth/signin/http-email'); + + await page.locator('[data-cy="signup-btn"]').click(); + + await signupResponse; + + await expect(page.getByText('An unknown error has occurred')).toBeVisible(); + }); +}); diff --git a/site/gatsby-site/playwright/meta/utils.spec.ts b/site/gatsby-site/playwright/meta/utils.spec.ts index 2d6f299c7b..cfe758ab37 100644 --- a/site/gatsby-site/playwright/meta/utils.spec.ts +++ b/site/gatsby-site/playwright/meta/utils.spec.ts @@ -6,7 +6,7 @@ test.describe('Test playwright utils', () => { test('Login fixture should mock user and roles', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['sarasa'], first_name: 'Fula', last_name: 'Nito' } }); + await login({ customData: { roles: ['sarasa'], first_name: 'Fula', last_name: 'Nito' } }); await page.goto('/account'); diff --git a/site/gatsby-site/playwright/utils.ts b/site/gatsby-site/playwright/utils.ts index f2164b3796..c6b09c5b1b 100644 --- a/site/gatsby-site/playwright/utils.ts +++ b/site/gatsby-site/playwright/utils.ts @@ -40,7 +40,7 @@ export function hashToken(token: string) { .digest("hex"); } -async function generateMagicLink(email: string) { +export const generateMagicLink = async (email: string, callbackUrl = '/') => { const token = randomString(32); const expires = new Date(Date.now() + 24 * 60 * 60 * 1000); @@ -58,7 +58,7 @@ async function generateMagicLink(email: string) { }); const baseUrl = config.NEXTAUTH_URL; - const magicLink = `${baseUrl}/api/auth/callback/http-email?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2F&token=${token}&email=${encodeURIComponent(email)}`; + const magicLink = `${baseUrl}/api/auth/callback/http-email?callbackUrl=${encodeURIComponent(baseUrl + callbackUrl)}&token=${token}&email=${encodeURIComponent(email)}`; return magicLink; } diff --git a/site/gatsby-site/server/fields/common.ts b/site/gatsby-site/server/fields/common.ts index c8ecc228cf..23845f3478 100644 --- a/site/gatsby-site/server/fields/common.ts +++ b/site/gatsby-site/server/fields/common.ts @@ -136,10 +136,10 @@ export const getUsersAdminData = async () => { interface SendEmailParams { recipients: { email: string; - userId: string; + userId?: string; }[]; subject: string; - dynamicData: { + dynamicData?: { incidentId?: string; incidentTitle?: string; incidentUrl?: string; @@ -153,6 +153,7 @@ interface SendEmailParams { reportAuthor?: string; // Author of the report (optional) entityName?: string; // Entity name (optional) entityUrl?: string; // Entity URL (optional) + magicLink?: string; // URL for magic link (optional) }; templateId: string; // Email template ID } @@ -197,7 +198,7 @@ export const sendEmail = async ({ recipients, subject, dynamicData, templateId } }] // We have to do this because MailerSend is escaping the placeholders containing html tags - const html = replacePlaceholdersWithAllowedKeys(emailTemplateBody, dynamicData, ['developers', 'deployers', 'entitiesHarmed']) + const html = replacePlaceholdersWithAllowedKeys(emailTemplateBody, dynamicData ?? {}, ['developers', 'deployers', 'entitiesHarmed']) const emailParams = new EmailParams() .setFrom({ email: config.NOTIFICATIONS_SENDER, name: config.NOTIFICATIONS_SENDER_NAME }) diff --git a/site/gatsby-site/src/components/landing/NewsletterSignup.js b/site/gatsby-site/src/components/landing/NewsletterSignup.js index d83886b19a..3d80c4bba8 100644 --- a/site/gatsby-site/src/components/landing/NewsletterSignup.js +++ b/site/gatsby-site/src/components/landing/NewsletterSignup.js @@ -1,46 +1,11 @@ -import React, { useState, useEffect } from 'react'; -import { Button, Spinner } from 'flowbite-react'; -import { Form, Formik } from 'formik'; -import * as Yup from 'yup'; -import TextInputGroup from 'components/forms/TextInputGroup'; +import React from 'react'; import { Trans, useTranslation } from 'react-i18next'; -import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { faEnvelope } from '@fortawesome/free-solid-svg-icons'; -import useToastContext, { SEVERITY } from 'hooks/useToast'; -import { useUserContext } from 'contexts/UserContext'; -import { StringParam, useQueryParams } from 'use-query-params'; import Card from 'elements/Card'; import Envelope from '../../images/neural-net-envelope.png'; export default function NewsletterSignup() { const { t } = useTranslation(); - const addToast = useToastContext(); - - const { - user, - actions: { signUp }, - loading, - } = useUserContext(); - - const [emailValue, setEmailValue] = useState(''); - - const [hydrated, setHydrated] = useState(false); - - useEffect(() => { - setHydrated(true); - }, []); - - const SubscribeSchema = Yup.object().shape({ - emailSubscription: Yup.string().email('Invalid email').required('Required'), - }); - - let [{ redirectTo }] = useQueryParams({ - redirectTo: StringParam, - }); - - if (loading) return <>; - return (
@@ -52,105 +17,11 @@ export default function NewsletterSignup() { alt={t('An envelope with a neural net diagram on its left')} className="w-2/5 mx-auto drop-shadow-xl mb-6" /> - {hydrated && user && user.providerType != 'anon-user' ? ( -

- - Check your inbox for the AI Incident Briefing, which includes incident round-ups along - with occasional major database updates. You can manage your subscription status from - the links in the email footer. - -

- ) : ( - <> - { - try { - await signUp({ email: emailSubscription, password: '123456', redirectTo }); - addToast({ - message: t('Thanks for subscribing to our Newsletter!', { - email: emailSubscription, - ns: 'login', - }), - severity: SEVERITY.success, - }); - resetForm(); - } catch (e) { - console.error(e); - addToast({ - message: ( - - ), - severity: SEVERITY.danger, - error: e, - }); - } - - setSubmitting(false); - }} - > - {({ - values, - touched, - handleChange, - handleBlur, - handleSubmit, - isSubmitting, - isValid, - }) => ( -
-
- { - setEmailValue(event.target.value); - handleChange(event); - }} - handleBlur={handleBlur} - values={values} - touched={touched} - /> -
- -
- -
-
- )} -
-

- - Subscribe to the AI Incident Briefing and get monthly incident round-ups along with - occasional major database updates. - -

- - )} +

+ + Create an account to receive a weekly briefing on the latest AI incidents and updates. + +

); diff --git a/site/gatsby-site/src/pages/login.js b/site/gatsby-site/src/pages/login.js index 0f0725d8b3..eaae438bf9 100644 --- a/site/gatsby-site/src/pages/login.js +++ b/site/gatsby-site/src/pages/login.js @@ -1,4 +1,4 @@ -import React, { useEffect, useState } from 'react'; +import React, { useCallback } from 'react'; import { Button, Spinner } from 'flowbite-react'; import { useUserContext } from 'contexts/UserContext'; import { Form, Formik } from 'formik'; @@ -24,27 +24,31 @@ const Login = () => { const addToast = useToastContext(); - const [redirectTo, setRedirectTo] = useState(null); - const { t } = useTranslation(); - const [{ redirectTo: redirectToParam }] = useQueryParams({ + const [{ redirectTo }] = useQueryParams({ redirectTo: withDefault(StringParam, '/'), }); - useEffect(() => { - if (!loading && user) { - const missingNames = !user.first_name || !user.last_name; - - const isSignup = !!localStorage.getItem('signup'); - - const askToCompleteProfile = missingNames && isSignup; - - localStorage.removeItem('signup'); - - setRedirectTo(askToCompleteProfile ? '/account?askToCompleteProfile=1' : redirectToParam); - } - }, [loading]); + const handleSubmit = useCallback( + async ({ email }, { setSubmitting }) => { + const result = await logIn(email, redirectTo); + + if (!result.error) { + navigate(`/verify-request/?email=${encodeURIComponent(email)}`); + } else { + // TODO: Add more specific error messages + addToast({ + message: t('An unknown error has occurred'), + severity: SEVERITY.danger, + error: result.error, + }); + } + + setSubmitting(false); + }, + [redirectTo] + ); return ( <> @@ -68,21 +72,7 @@ const Login = () => { { - const result = await logIn(email, redirectTo || ''); - - if (result.ok) { - navigate('/verify-request/?email=' + email); - } else { - addToast({ - message: t('An unknown error has occurred'), - severity: SEVERITY.danger, - error: result.error, - }); - } - - setSubmitting(false); - }} + onSubmit={handleSubmit} > {({ values, @@ -125,10 +115,6 @@ const Login = () => { )} -
- or -
-
Don't have an account?{' '} diff --git a/site/gatsby-site/src/pages/signup.js b/site/gatsby-site/src/pages/signup.js index 1648c147a6..ed28396bf6 100644 --- a/site/gatsby-site/src/pages/signup.js +++ b/site/gatsby-site/src/pages/signup.js @@ -1,4 +1,4 @@ -import React, { useState } from 'react'; +import React, { useState, useCallback } from 'react'; import { Button, Spinner } from 'flowbite-react'; import { useUserContext } from 'contexts/UserContext'; import useToastContext, { SEVERITY } from '../hooks/useToast'; @@ -8,13 +8,13 @@ import Link from '../components/ui/Link'; import { LocalizedLink } from 'plugins/gatsby-theme-i18n'; import { Trans, useTranslation } from 'react-i18next'; import { FontAwesomeIcon } from '@fortawesome/react-fontawesome'; -import { StringParam, useQueryParams } from 'use-query-params'; import { faEnvelope } from '@fortawesome/free-solid-svg-icons'; import TextInputGroup from 'components/forms/TextInputGroup'; import NewsletterSignup from 'components/landing/NewsletterSignup'; import Card from 'elements/Card'; import HeadContent from 'components/HeadContent'; import { navigate } from 'gatsby'; +import { StringParam, useQueryParams, withDefault } from 'use-query-params'; const SignUpSchema = Yup.object().shape({ email: Yup.string().email('Invalid email').required('Required'), @@ -33,11 +33,29 @@ const SignUp = () => { const addToast = useToastContext(); - let [{ redirectTo }] = useQueryParams({ - redirectTo: StringParam, + const [{ redirectTo }] = useQueryParams({ + redirectTo: withDefault(StringParam, '/'), }); - redirectTo = redirectTo ?? '/'; + const handleSignup = useCallback( + async ({ email }, { setSubmitting }) => { + const result = await signUp(email, '/account/?askToCompleteProfile=1'); + + if (!result.error) { + await navigate(`/verify-request/?email=${encodeURIComponent(email)}`); + } else { + // TODO: Add more specific error messages + addToast({ + message: t('An unknown error has occurred'), + severity: SEVERITY.danger, + error: result.error, + }); + } + + setSubmitting(false); + }, + [signUp] + ); return (
@@ -75,22 +93,7 @@ const SignUp = () => { { - const result = await signUp(email, redirectTo); - - localStorage.setItem('signup', '1'); - - if (result.ok) { - navigate('/verify-request/?email=' + email); - } else { - addToast({ - message: t('An unknown error has occurred'), - severity: SEVERITY.danger, - error: result.error, - }); - } - setSubmitting(false); - }} + onSubmit={handleSignup} > {({ values, From 8a66771d4d7870248e96db9e683392e371696734 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 26 Nov 2024 21:50:51 -0300 Subject: [PATCH 54/99] Reduce timeouts --- site/gatsby-site/playwright.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/gatsby-site/playwright.config.ts b/site/gatsby-site/playwright.config.ts index aeeb95d5ce..5e08148fc7 100644 --- a/site/gatsby-site/playwright.config.ts +++ b/site/gatsby-site/playwright.config.ts @@ -13,9 +13,9 @@ export default defineConfig({ testDir: './playwright', globalTimeout: process.env.CI ? 60 * 60 * 1000 : undefined, expect: { - timeout: process.env.CI ? 60000 : 30000, + timeout: process.env.CI ? 30000 : 15000, }, - timeout: process.env.CI ? 180000 : 90000, + timeout: process.env.CI ? 90000 : 45000, /* Run tests in files in parallel */ fullyParallel: true, /* Fail the build on CI if you accidentally left test.only in the source code. */ From 49fe877b30f7bbdfc2963397d71648a715d74ee8 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 27 Nov 2024 19:22:47 -0300 Subject: [PATCH 55/99] Update tests --- site/gatsby-site/server/tests/history.spec.ts | 17 ++++++++--------- site/gatsby-site/server/tests/users.spec.ts | 11 +++++------ 2 files changed, 13 insertions(+), 15 deletions(-) diff --git a/site/gatsby-site/server/tests/history.spec.ts b/site/gatsby-site/server/tests/history.spec.ts index 88ceb3aefc..82c6879bce 100644 --- a/site/gatsby-site/server/tests/history.spec.ts +++ b/site/gatsby-site/server/tests/history.spec.ts @@ -1,7 +1,6 @@ -import { expect, jest, it } from '@jest/globals'; +import { expect, it } from '@jest/globals'; import { ApolloServer } from "@apollo/server"; -import { makeRequest, seedFixture, startTestServer } from "./utils"; -import * as context from '../context'; +import { makeRequest, mockSession, seedFixture, startTestServer } from "./utils"; import { DBIncident, DBReport } from '../interfaces'; import { IncidentFilterType, IncidentUpdateType, ReportInsertType } from '../generated/graphql'; @@ -123,7 +122,7 @@ describe(`History`, () => { }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); const response = await makeRequest(url, mutationData); @@ -247,7 +246,7 @@ describe(`History`, () => { }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123') const response = await makeRequest(url, mutationData); @@ -336,7 +335,7 @@ describe(`History`, () => { } }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }); + mockSession('123'); await makeRequest(url, mutationData); @@ -405,7 +404,7 @@ describe(`History`, () => { } }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }); + mockSession('123'); await makeRequest(url, mutationData); @@ -498,7 +497,7 @@ describe(`History`, () => { } }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }); + mockSession('123'); await makeRequest(url, mutationData); @@ -579,7 +578,7 @@ describe(`History`, () => { }, }; - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }); + mockSession('123'); await makeRequest(url, mutationData); diff --git a/site/gatsby-site/server/tests/users.spec.ts b/site/gatsby-site/server/tests/users.spec.ts index a9f8388d96..d5162f5e84 100644 --- a/site/gatsby-site/server/tests/users.spec.ts +++ b/site/gatsby-site/server/tests/users.spec.ts @@ -1,7 +1,6 @@ -import { expect, jest, it } from '@jest/globals'; +import { expect, it } from '@jest/globals'; import { ApolloServer } from "@apollo/server"; -import { makeRequest, seedFixture, startTestServer } from "./utils"; -import * as context from '../context'; +import { makeRequest, mockSession, seedFixture, startTestServer } from "./utils"; describe(`Users`, () => { let server: ApolloServer, url: string; @@ -43,7 +42,7 @@ describe(`Users`, () => { }); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "user1" }) + mockSession('123') const response = await makeRequest(url, mutationData); @@ -97,7 +96,7 @@ describe(`Users`, () => { }); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "user1" }) + mockSession('123') const response = await makeRequest(url, mutationData); @@ -151,7 +150,7 @@ describe(`Users`, () => { }); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "user2" }) + mockSession("user2") const response = await makeRequest(url, mutationData); From 01bc41b992e959faa5a1d104011e390eac2f186a Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 27 Nov 2024 20:36:50 -0300 Subject: [PATCH 56/99] Update tests --- .../playwright/e2e-full/apps/incidents.spec.ts | 16 +--------------- .../e2e-full/incidents/history.spec.ts | 3 +-- .../playwright/e2e-full/reportHistory.spec.ts | 2 +- .../playwright/seeds/aiidprod/incidents.ts | 4 ++-- .../playwright/seeds/aiidprod/reports.ts | 10 +++++----- .../playwright/seeds/customData/subscriptions.ts | 6 +++--- .../playwright/seeds/history/incidentsHistory.ts | 12 ++++++------ .../playwright/seeds/history/reportsHistory.ts | 10 +++++----- 8 files changed, 24 insertions(+), 39 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts index 11bba26dd4..34160f294e 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/incidents.spec.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { conditionalIntercept, fillAutoComplete, query, test, waitForRequest } from '../../utils'; +import { fillAutoComplete, query, test, waitForRequest } from '../../utils'; import { init } from '../../memory-mongo'; import gql from 'graphql-tag'; @@ -74,20 +74,6 @@ test.describe('Incidents App', () => { await login({ customData: { first_name: 'John', last_name: 'Doe', roles: ['incident_editor'] } }); - await conditionalIntercept( - page, - '**/graphql', - (req) => req.postDataJSON().operationName === 'logIncidentHistory', - { - data: { - logIncidentHistory: { - incident_id: 3, - }, - }, - }, - 'logIncidentHistory' - ); - await page.goto(url); await page.waitForSelector('[data-testid="flowbite-toggleswitch-toggle"]'); diff --git a/site/gatsby-site/playwright/e2e-full/incidents/history.spec.ts b/site/gatsby-site/playwright/e2e-full/incidents/history.spec.ts index e587756d8b..b163b23719 100644 --- a/site/gatsby-site/playwright/e2e-full/incidents/history.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/incidents/history.spec.ts @@ -1,7 +1,6 @@ import { format, fromUnixTime } from 'date-fns'; import { test, query } from '../../utils'; import { expect } from '@playwright/test'; -import config from '../../config'; import { init } from '../../memory-mongo'; const { gql } = require('@apollo/client'); @@ -94,7 +93,7 @@ test.describe('Incidents', () => { ` }); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD, { customData: { roles: ['admin'] } }); + await login(); await page.goto(url); diff --git a/site/gatsby-site/playwright/e2e-full/reportHistory.spec.ts b/site/gatsby-site/playwright/e2e-full/reportHistory.spec.ts index 3443a939c4..d72d90bb33 100644 --- a/site/gatsby-site/playwright/e2e-full/reportHistory.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/reportHistory.spec.ts @@ -46,7 +46,7 @@ test.describe('Report History', () => { await expect(page.locator('[data-cy="history-row"]').nth(index)).toContainText(date); await expect(page.locator('[data-cy="history-row"]').nth(index)).toContainText( - `Modified by: ${history.modifiedBy === 'user1' ? 'Test User' : 'Sean McGregor'}` + `Modified by: ${history.modifiedBy === '6737a6e881955aa4905ccb04' ? 'Test User' : 'Sean McGregor'}` ); } diff --git a/site/gatsby-site/playwright/seeds/aiidprod/incidents.ts b/site/gatsby-site/playwright/seeds/aiidprod/incidents.ts index dd360419ab..ceb58844a4 100644 --- a/site/gatsby-site/playwright/seeds/aiidprod/incidents.ts +++ b/site/gatsby-site/playwright/seeds/aiidprod/incidents.ts @@ -9,7 +9,7 @@ const incidents: DBIncident[] = [ "Alleged deployer of AI system": ["entity-1"], "Alleged developer of AI system": ["entity-2"], "Alleged harmed or nearly harmed parties": ["entity-3"], - editors: ["user1"], + editors: ["6737a6e881955aa4905ccb04"], reports: [1], // TODO: this aren't required but break the build if missing @@ -26,7 +26,7 @@ const incidents: DBIncident[] = [ "Alleged deployer of AI system": ["entity-1"], "Alleged developer of AI system": ["entity-2"], "Alleged harmed or nearly harmed parties": ["entity-3"], - editors: ["user1"], + editors: ["6737a6e881955aa4905ccb04"], reports: [2], // TODO: this aren't required but break the build if missing diff --git a/site/gatsby-site/playwright/seeds/aiidprod/reports.ts b/site/gatsby-site/playwright/seeds/aiidprod/reports.ts index de103d826c..f1a3013a51 100644 --- a/site/gatsby-site/playwright/seeds/aiidprod/reports.ts +++ b/site/gatsby-site/playwright/seeds/aiidprod/reports.ts @@ -23,7 +23,7 @@ const items: DBReport[] = [ submitters: ["submitter1"], tags: ["tag1"], url: "https://report1.com", - user: "user1", + user: "6737a6e881955aa4905ccb04", is_incident_report: true, editor_notes: null, flag: null, @@ -55,7 +55,7 @@ const items: DBReport[] = [ submitters: ["submitter1"], tags: ["tag1"], url: "https://report2.com", - user: "user1", + user: "6737a6e881955aa4905ccb04", }, { _id: "5d34b8c29ced494f010ed469", @@ -93,7 +93,7 @@ const items: DBReport[] = [ is_incident_report: true, // TODO: this report (16) has no user in the database but is the user field is required by the schema - user: 'user1', + user: '6737a6e881955aa4905ccb04', // TODO: field is present in the db but not mapped to any graphql field // created_at: 1559347200000 @@ -135,7 +135,7 @@ const items: DBReport[] = [ is_incident_report: true, // TODO: ditto - user: 'user1', + user: '6737a6e881955aa4905ccb04', // TODO: ditto // created_at: 1559347200000 @@ -177,7 +177,7 @@ const items: DBReport[] = [ is_incident_report: false, // TODO: ditto - user: 'user1', + user: '6737a6e881955aa4905ccb04', // TODO: ditto // created_at: 1559347200000 diff --git a/site/gatsby-site/playwright/seeds/customData/subscriptions.ts b/site/gatsby-site/playwright/seeds/customData/subscriptions.ts index 81beb88958..aa00e42959 100644 --- a/site/gatsby-site/playwright/seeds/customData/subscriptions.ts +++ b/site/gatsby-site/playwright/seeds/customData/subscriptions.ts @@ -7,21 +7,21 @@ const items: DBSubscription[] = [ entityId: "entity-1", incident_id: undefined, type: "entity", - userId: "user1", + userId: "6737a6e881955aa4905ccb04", }, { _id: new ObjectId("619b47eb5eed5334edfa3bd9"), entityId: undefined, incident_id: 1, type: "incident", - userId: "user1", + userId: "6737a6e881955aa4905ccb04", }, { _id: new ObjectId('60a7c5b7b4f5b8a6d8f9c7e7'), entityId: undefined, incident_id: 1, type: "incident", - userId: 'user1', + userId: '6737a6e881955aa4905ccb04', } ] diff --git a/site/gatsby-site/playwright/seeds/history/incidentsHistory.ts b/site/gatsby-site/playwright/seeds/history/incidentsHistory.ts index dca109f6bd..6d68df3693 100644 --- a/site/gatsby-site/playwright/seeds/history/incidentsHistory.ts +++ b/site/gatsby-site/playwright/seeds/history/incidentsHistory.ts @@ -16,7 +16,7 @@ const items: DBIncidentHistory[] = [ "entity-3" ], "editors": [ - "user1", + "6737a6e881955aa4905ccb04", ], "reports": [ 1 @@ -27,7 +27,7 @@ const items: DBIncidentHistory[] = [ "editor_dissimilar_incidents": [], "flagged_dissimilar_incidents": [], "epoch_date_modified": 1730242189, - "modifiedBy": "user1" + "modifiedBy": "6737a6e881955aa4905ccb04" }, { "incident_id": 1, @@ -44,7 +44,7 @@ const items: DBIncidentHistory[] = [ "entity-3" ], "editors": [ - "user1", + "6737a6e881955aa4905ccb04", "619b47ea5eed5334edfa3bbc" ], "reports": [ @@ -73,7 +73,7 @@ const items: DBIncidentHistory[] = [ "entity-3" ], "editors": [ - "user1", + "6737a6e881955aa4905ccb04", "619b47ea5eed5334edfa3bbc" ], "reports": [ @@ -102,7 +102,7 @@ const items: DBIncidentHistory[] = [ "entity-3" ], "editors": [ - "user1", + "6737a6e881955aa4905ccb04", "619b47ea5eed5334edfa3bbc", ], "reports": [ @@ -114,7 +114,7 @@ const items: DBIncidentHistory[] = [ "editor_dissimilar_incidents": [], "flagged_dissimilar_incidents": [], "epoch_date_modified": 1730242399, - "modifiedBy": "user1" + "modifiedBy": "6737a6e881955aa4905ccb04" } ] diff --git a/site/gatsby-site/playwright/seeds/history/reportsHistory.ts b/site/gatsby-site/playwright/seeds/history/reportsHistory.ts index 8cc90250aa..88c2f8332d 100644 --- a/site/gatsby-site/playwright/seeds/history/reportsHistory.ts +++ b/site/gatsby-site/playwright/seeds/history/reportsHistory.ts @@ -23,13 +23,13 @@ const items: DBReportHistory[] = [ submitters: ["submitter1"], tags: ["tag1"], url: "https://report1.com", - user: "user1", + user: "6737a6e881955aa4905ccb04", is_incident_report: true, editor_notes: null, flag: null, inputs_outputs: null, quiet: null, - modifiedBy: "user1" + modifiedBy: "6737a6e881955aa4905ccb04" }, { _id: "672030c91b3f74e536ba7890", @@ -53,7 +53,7 @@ const items: DBReportHistory[] = [ submitters: ["submitter1"], tags: ["tag1"], url: "https://report1.com", - user: "user1", + user: "6737a6e881955aa4905ccb04", is_incident_report: true, editor_notes: null, flag: null, @@ -83,7 +83,7 @@ const items: DBReportHistory[] = [ submitters: ["submitter1"], tags: ["tag1"], url: "https://report1.com", - user: "user1", + user: "6737a6e881955aa4905ccb04", is_incident_report: true, editor_notes: null, flag: null, @@ -113,7 +113,7 @@ const items: DBReportHistory[] = [ submitters: ["submitter1"], tags: ["tag1"], url: "https://report1.com", - user: "user1", + user: "6737a6e881955aa4905ccb04", is_incident_report: true, editor_notes: null, flag: null, From f9432445004d493b04d86c2cd386f6eacc5dc894 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Thu, 28 Nov 2024 18:31:15 -0300 Subject: [PATCH 57/99] Update test and add algolia mock --- .../playwright/e2e-full/cite.spec.ts | 28 +- .../playwright/fixtures/algoliaMock.ts | 7772 +++++++++++++++++ site/gatsby-site/playwright/utils.ts | 29 + 3 files changed, 7820 insertions(+), 9 deletions(-) create mode 100644 site/gatsby-site/playwright/fixtures/algoliaMock.ts diff --git a/site/gatsby-site/playwright/e2e-full/cite.spec.ts b/site/gatsby-site/playwright/e2e-full/cite.spec.ts index 8b92e03726..32e0429c7a 100644 --- a/site/gatsby-site/playwright/e2e-full/cite.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/cite.spec.ts @@ -1,4 +1,4 @@ -import { query, mockDate, test, fillAutoComplete } from '../utils'; +import { query, test, fillAutoComplete, mockAlgolia } from '../utils'; import { format } from 'date-fns'; import { gql } from '@apollo/client'; import { expect } from '@playwright/test'; @@ -39,7 +39,7 @@ test.describe('Cite pages', () => { }); test('Should show an edit link to users with the appropriate role', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); + await login(); const id = 'r1'; @@ -250,7 +250,7 @@ test.describe('Cite pages', () => { }); test('Should show the edit incident form', async ({ page, login }) => { - + await login(); await page.goto(url); @@ -276,8 +276,8 @@ test.describe('Cite pages', () => { const bibText = bibTextElement.replace(/(\r\n|\n|\r| |\s)/g, ''); expect(bibText).toBe( - `@article{aiid:3,author={Olsson,Catherine},editor={McGregor,Sean},journal={AIIncidentDatabase},publisher={ResponsibleAICollaborative},title={IncidentNumber3:KronosSchedulingAlgorithmAllegedlyCausedFinancialIssuesforStarbucksEmployees},url={https://incidentdatabase.ai/cite/3},year={2014},urldate={${date}},note={Retrieved${retrievedDate}from\\url{https://incidentdatabase.ai/cite/3}}}` - ); + `@article{aiid:3,author={Olsson,Catherine},editor={McGregor,Sean},journal={AIIncidentDatabase},publisher={ResponsibleAICollaborative},title={IncidentNumber3:KronosSchedulingAlgorithmAllegedlyCausedFinancialIssuesforStarbucksEmployees},url={https://incidentdatabase.ai/cite/3},year={2014},urldate={${date}},note={Retrieved${retrievedDate}from\\url{https://incidentdatabase.ai/cite/3}}}` + ); }); test('Should display similar incidents', async ({ page }) => { @@ -330,7 +330,7 @@ test.describe('Cite pages', () => { }); test('Should display edit link when logged in as editor', async ({ page, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); + await login(); await page.goto('/cite/3'); @@ -370,7 +370,7 @@ test.describe('Cite pages', () => { await expect(page.getByText('Incident flagged successfully. Our editors will remove it from this list if it not relevant.')).toBeVisible(); - + const { data } = await query({ query: gql`{incident(filter: { incident_id: { EQ: 3 } }) { flagged_dissimilar_incidents @@ -422,7 +422,7 @@ test.describe('Cite pages', () => { await init(); - const [userId, accessToken] = await login({ customData: { first_name: 'Test', last_name: 'User', roles: ['subscriber'] } }); + const [userId, accessToken] = await login(); await page.goto('/cite/3'); @@ -447,7 +447,14 @@ test.describe('Cite pages', () => { { Cookie: `next-auth.session-token=${encodeURIComponent(accessToken)};` } ); - expect(data.subscriptions).toEqual([{ type: 'incident', incident_id: { incident_id: 3 } }]); + expect(data.subscriptions).toEqual( + expect.arrayContaining([ + expect.objectContaining({ + type: 'incident', + incident_id: { incident_id: 3 } + }) + ]) + ); }); test('Should show proper entities card text', async ({ page }) => { @@ -480,6 +487,9 @@ test.describe('Cite pages', () => { }); test('Should open incident from the discover app', async ({ page }) => { + + await mockAlgolia(page); + await page.goto(discoverUrl); await page.locator('[data-cy="collapse-button"]:visible').click(); diff --git a/site/gatsby-site/playwright/fixtures/algoliaMock.ts b/site/gatsby-site/playwright/fixtures/algoliaMock.ts new file mode 100644 index 0000000000..645360afce --- /dev/null +++ b/site/gatsby-site/playwright/fixtures/algoliaMock.ts @@ -0,0 +1,7772 @@ +export const algoliaMock = { + results: [ + { + "hits": [ + { + "authors": [ + "Aimee Picchi" + ], + "description": "Some employees at the coffee chain say it isn't living up to promises to improve the company's labor practices", + "epoch_date_downloaded": 1555113600, + "epoch_date_modified": 1725431608, + "epoch_date_published": 1443052800, + "epoch_date_submitted": 1559347200, + "image_url": "https://cbsnews1.cbsistatic.com/hub/i/r/2015/03/17/01a38576-5108-40f7-8df8-5416164ed878/thumbnail/1200x630/ca8d35fe6bc065b5c9a747d92bc6d94c/154211248.jpg", + "language": "en", + "report_number": 23, + "source_domain": "cbsnews.com", + "submitters": [ + "Catherine Olsson" + ], + "title": "​Is Starbucks shortchanging its baristas?", + "name": "​Is Starbucks shortchanging its baristas?", + "url": "https://www.cbsnews.com/news/is-starbucks-shortchanging-its-baristas/", + "tags": [], + "editor_notes": "", + "cloudinary_id": "reports/cbsnews1.cbsistatic.com/hub/i/r/2015/03/17/01a38576-5108-40f7-8df8-5416164ed878/thumbnail/1200x630/ca8d35fe6bc065b5c9a747d92bc6d94c/154211248.jpg", + "text": "For Starbucks (SBUX) barista Kylei Weisse, working at the coffee chain helps him secure health insurance and some extra money while he studies at Georgia Perimeter College. What it doesn't provide is the kind of stable schedule that the company promised its workers last year.\n\n\"It's the wild inconsistency\" of the hours that's a problem, Weisse, 32, said. \"We're supposed to get them 10 days in advance, which often happens, but there's no guarantee. If our manager doesn't get it to us on time, we just have to deal with it.\"\n\nThat became a problem recently when Weisse's manager gave him only a few days notice on his work hours, which ended up conflicting with an anatomy and physiology exam at his college. Weisse ended up paying another worker $20 to take his shift so he could take the exam.\n\nThe short notice is especially frustrating because of Starbucks' vow last year to post employees' schedules at least 10 days in advance, as well as the company's insistence that workers provide at least one-month notice when they need to take a day off.\n\nWhat's behind Starbucks price increases?\n\nWeisse isn't alone in complaining that Starbucks isn't living up to its promises to overhaul its labor practices for its roughly 130,000 baristas. That vow followed an article last year by The New York Times that detailed how workers were struggling to manage childcare and other obligations when the company provided only a few days notice about their schedules.\n\nAbout half of roughly 200 Starbucks baristas in a recent survey said they are still receiving their schedule with less than one week's notice. Others also reported being asked to handle \"clopens,\" split shifts in which employees work a closing shift late into the evening and then an early opening shift the following morning. The company last year promised to end the practice.\n\nOf course, Starbucks isn't alone in using \"just-in-time\" scheduling, with the retail and restaurant industry increasingly turning to software that allows them to change work schedules at the last minute, depending on whether business picks up or slows down. But it is Starbucks that has become a lightning rod on the issue given its vows to improve how it treats employees and its own emphatic claims to valuing workers, whom it labels \"partners.\"\n\n\"Starbucks has the values and wants to do right by their employees,\" said Carrie Gleason, director of the Fair Workweek Initiative at the Center for Popular Democracy, an advocacy group focused on workers'rights, and a co-author of the group's new report on the company's labor practices. \"However, since last year when the company recognized there was a serious problem with the way it scheduled workers and pledged to reform, still so many of the same issues persist.\"\n\nStarbucks didn't respond to requests for comment on the study or on baristas' reports of labor practices that are failing to meet the company's stated goals.\n\nIn an internal memo this week published by Time, Starbucks executive Cliff Burrows wrote that the company couldn't validate the survey, but added that \"the findings suggest, contrary to the expectations we have in place, that some partners are receiving their schedules less than one week in advance and that there is a continuing issue with some partners working a close and then an opening shift the following morning.\" He asks store managers \"to go the extra mile to ensure partners have a consistent schedule.\"\n\nStarbucks ends \"race together\" campaign amid public backlash\n\nTo be sure, some Starbucks workers are receiving at least 10 days notice on their work hours, with the survey finding that about one-third receive two weeks notice and another 18 percent get their schedules three weeks in advance. But that leaves almost half of workers who only receive one week's notice, making it more difficult from them manage other obligations, such as school, family commitments or other jobs.\n\nClopens remain a problem, as well. About 60 percent of workers who have to handle a clopen receive seven or fewer hours of rest between a closing and an opening shift, the study found.\n\nThat's prompted one former Starbucks employee to start a petition to end the practice of scheduling clopens. Ciara Moran noted in her petition that she sometimes was only able to get four or five hours of sleep on the days she was scheduled for clopens. She said she quit her job because she doubted whether it was possible to get ahead given the demands on workers.\n\nEven if Starbucks stuck with its policy of providing eight hours between shifts, that's not enough time, especially given that many workers in the service sector have long commutes, the study said.\n\nAnother issue singled out by the report is Starbucks' practices on sick time. Since paid time off is only available to workers with at least a year on the job, about 40 percent of employees in the survey said they had dealt with barriers in taking sick days.\n\nIn a perfect world, Weisse said he'd like to receive his schedule either a month or a", + "mongodb_id": "5d34b8c29ced494f010ed470", + "featured": 2, + "flag": true, + "is_incident_report": true, + "namespaces": [ + "CSETv0", + "GMF", + "CSETv1_Annotator-1", + "CSETv1" + ], + "classifications": [ + "CSETv0:Location:Global", + "CSETv0:Near Miss:Unclear/unknown", + "CSETv0:Named Entities:Starbucks", + "CSETv0:Named Entities:Kronos", + "CSETv0:Technology Purveyor:Starbucks", + "CSETv0:Intent:Unclear", + "CSETv0:Severity:Negligible", + "CSETv0:Harm Type:Psychological harm", + "CSETv0:Infrastructure Sectors:Food and agriculture", + "GMF:Known AI Technical Failure:Underspecification", + "GMF:Potential AI Technical Failure:Tuning Issues", + "GMF:Potential AI Technical Failure:Misconfigured Aggregation", + "GMF:Potential AI Technology:Regression", + "GMF:Potential AI Technology:Diverse Data", + "GMF:Known AI Goal:Market Forecasting", + "GMF:Known AI Goal:Scheduling", + "CSETv1_Annotator-1:Physical Objects:no", + "CSETv1_Annotator-1:Entertainment Industry:no", + "CSETv1_Annotator-1:Report, Test, or Study of data:no", + "CSETv1_Annotator-1:Deployed:yes", + "CSETv1_Annotator-1:Producer Test in Controlled Conditions:no", + "CSETv1_Annotator-1:Producer Test in Operational Conditions:no", + "CSETv1_Annotator-1:User Test in Controlled Conditions:no", + "CSETv1_Annotator-1:User Test in Operational Conditions:no", + "CSETv1_Annotator-1:Tangible Harm:tangible harm definitively occurred", + "CSETv1_Annotator-1:AI System:yes", + "CSETv1_Annotator-1:AI Harm Level:AI tangible harm event", + "CSETv1_Annotator-1:Impact on Critical Services:no", + "CSETv1_Annotator-1:Rights Violation:no", + "CSETv1_Annotator-1:Involving Minor:no", + "CSETv1_Annotator-1:Detrimental Content:no", + "CSETv1_Annotator-1:Protected Characteristic:no", + "CSETv1_Annotator-1:Harm Distribution Basis:none", + "CSETv1_Annotator-1:Clear link to Technology:yes", + "CSETv1_Annotator-1:Annotator’s AI special interest intangible harm assessment:no", + "CSETv1_Annotator-1:Sector of Deployment:wholesale and retail trade", + "CSETv1_Annotator-1:Sector of Deployment:accommodation and food service activities", + "CSETv1_Annotator-1:Public Sector Deployment:no", + "CSETv1_Annotator-1:Autonomy Level:Autonomy2", + "CSETv1_Annotator-1:Intentional Harm:No. Not intentionally designed to perform harm", + "CSETv1_Annotator-1:Special Interest Intangible Harm:no", + "CSETv1_Annotator-1:Date of Incident Year:2014", + "CSETv1_Annotator-1:Multiple AI Interaction:no", + "CSETv1_Annotator-1:Embedded:no", + "CSETv1_Annotator-1:Location Country (two letters):US", + "CSETv1_Annotator-1:Location Region:North America", + "CSETv1_Annotator-1:Data Inputs:schedules", + "CSETv1_Annotator-1:Data Inputs:worker profiles", + "CSETv1_Annotator-1:AI Task:scheduling", + "CSETv1_Annotator-1:AI Task:productivity optimization", + "CSETv1:Harm Distribution Basis:none", + "CSETv1:Sector of Deployment:accommodation and food service activities", + "CSETv1:Physical Objects:no", + "CSETv1:Entertainment Industry:no", + "CSETv1:Report, Test, or Study of data:no", + "CSETv1:Deployed:yes", + "CSETv1:Producer Test in Controlled Conditions:no", + "CSETv1:Producer Test in Operational Conditions:no", + "CSETv1:User Test in Controlled Conditions:no", + "CSETv1:User Test in Operational Conditions:no", + "CSETv1:Tangible Harm:tangible harm definitively occurred", + "CSETv1:AI System:yes", + "CSETv1:AI Harm Level:AI tangible harm event", + "CSETv1:Impact on Critical Services:no", + "CSETv1:Rights Violation:no", + "CSETv1:Involving Minor:no", + "CSETv1:Detrimental Content:no", + "CSETv1:Protected Characteristic:no", + "CSETv1:Clear link to Technology:yes", + "CSETv1:Annotator’s AI special interest intangible harm assessment:no", + "CSETv1:Public Sector Deployment:no", + "CSETv1:Autonomy Level:Autonomy2", + "CSETv1:Intentional Harm:No. Not intentionally designed to perform harm", + "CSETv1:Special Interest Intangible Harm:no", + "CSETv1:Date of Incident Year:2014", + "CSETv1:Multiple AI Interaction:no", + "CSETv1:Embedded:no", + "CSETv1:Location Country (two letters):US", + "CSETv1:Location Region:North America", + "CSETv1:Data Inputs:schedules", + "CSETv1:Data Inputs:worker profiles", + "CSETv1:Data Inputs:store traffic", + "CSETv1:AI Task:scheduling", + "CSETv1:AI Task:productivity optimization", + "CSETv1:AI Task:predict store traffic" + ], + "CSETv0": { + "Location": "Global", + "Near Miss": "Unclear/unknown", + "Named Entities": [ + "Starbucks", + "Kronos" + ], + "Technology Purveyor": [ + "Starbucks" + ], + "Intent": "Unclear", + "Severity": "Negligible", + "Harm Type": [ + "Psychological harm" + ], + "Lives Lost": false, + "Harm Distribution Basis": [], + "Infrastructure Sectors": [ + "Food and agriculture" + ], + "Financial Cost": "", + "System Developer": [], + "Sector of Deployment": [ + "" + ], + "Public Sector Deployment": false, + "Nature of End User": "", + "Level of Autonomy": "", + "Relevant AI functions": [], + "AI Techniques": [], + "AI Applications": [], + "Physical System": [], + "Problem Nature": [] + }, + "GMF": { + "Known AI Technical Failure": [ + "Underspecification" + ], + "Known AI Technical Failure Snippets": [ + { + "attributes": [ + { + "short_name": "Snippet Text", + "value_json": "\"“You’re waiting on your job to control your life,” she said, with the scheduling software used by her employer dictating everything from “how much sleep Gavin will get to what groceries I’ll be able to buy this month.”\"" + }, + { + "short_name": "Related Classifications", + "value_json": "[\"Underspecification\"]" + } + ] + }, + { + "attributes": [ + { + "short_name": "Snippet Text", + "value_json": "\"Along with virtually every major retail and restaurant chain, Starbucks relies on software that choreographs workers in precise, intricate ballets, using sales patterns and other data to determine which of its 130,000 baristas are needed in its thousands of locations and exactly when.\"" + }, + { + "short_name": "Related Classifications", + "value_json": "[\"Underspecification\"]" + } + ] + }, + { + "attributes": [ + { + "short_name": "Snippet Text", + "value_json": "\"Among other changes, the company said it would end the practice of \\\"clopening,\\\" when an employee responsible for closing a store late at night is also assigned to open it early in the morning.\"" + }, + { + "short_name": "Related Classifications", + "value_json": "[\"Underspecification\"]" + } + ] + }, + { + "attributes": [ + { + "short_name": "Snippet Text", + "value_json": "\"In a follow-up piece, the author, Jodi Kantor, points directly to Kronos' scheduling software as the root of the problem.\"" + }, + { + "short_name": "Related Classifications", + "value_json": "[\"Underspecification\"]" + } + ] + } + ], + "Potential AI Technical Failure": [ + "Tuning Issues", + "Misconfigured Aggregation" + ], + "Potential AI Technical Failure Snippets": [ + { + "attributes": [ + { + "short_name": "Snippet Text", + "value_json": "\"In addition, Kronos is improving a feature meant to help give employees more control over their schedules: Though the software already incorporates employee availability and preferences into its scheduling calculations, improvements to a shift-swapping feature on its employee-facing web and mobile apps will theoretically allow employees to work around conflicts among themselves.\"" + }, + { + "short_name": "Related Classifications", + "value_json": "[\"Tuning Issues\",\"Misconfigured Aggregation\"]" + } + ] + } + ], + "Potential AI Technology": [ + "Regression", + "Diverse Data" + ], + "Potential AI Technology Snippets": [ + { + "attributes": [ + { + "short_name": "Snippet Text", + "value_json": "\"“You’re waiting on your job to control your life,” she said, with the scheduling software used by her employer dictating everything from “how much sleep Gavin will get to what groceries I’ll be able to buy this month.”\"" + }, + { + "short_name": "Related Classifications", + "value_json": "[\"Regression\"]" + } + ] + }, + { + "attributes": [ + { + "short_name": "Snippet Text", + "value_json": "\"In a follow-up piece, the author, Jodi Kantor, points directly to Kronos' scheduling software as the root of the problem.\"" + }, + { + "short_name": "Related Classifications", + "value_json": "[\"Diverse Data\"]" + } + ] + } + ], + "Known AI Goal": [ + "Market Forecasting", + "Scheduling" + ], + "Known AI Goal Snippets": [] + }, + "CSETv1_Annotator-1": { + "Physical Objects": "no", + "Entertainment Industry": "no", + "Report, Test, or Study of data": "no", + "Deployed": "yes", + "Producer Test in Controlled Conditions": "no", + "Producer Test in Operational Conditions": "no", + "User Test in Controlled Conditions": "no", + "User Test in Operational Conditions": "no", + "Tangible Harm": "tangible harm definitively occurred", + "AI System": "yes", + "AI Harm Level": "AI tangible harm event", + "Impact on Critical Services": "no", + "Rights Violation": "no", + "Involving Minor": "no", + "Detrimental Content": "no", + "Protected Characteristic": "no", + "Harm Distribution Basis": [ + "none" + ], + "Clear link to Technology": "yes", + "Annotator’s AI special interest intangible harm assessment": "no", + "Sector of Deployment": [ + "wholesale and retail trade", + "accommodation and food service activities" + ], + "Public Sector Deployment": "no", + "Autonomy Level": "Autonomy2", + "Intentional Harm": "No. Not intentionally designed to perform harm", + "AI tools and methods": "", + "Special Interest Intangible Harm": "no", + "Date of Incident Year": "2014", + "Multiple AI Interaction": "no", + "Embedded": "no", + "Location Country (two letters)": "US", + "Location Region": "North America", + "Infrastructure Sectors": [], + "Operating Conditions": "", + "Lives Lost": 0, + "Injuries": 0, + "Data Inputs": [ + "schedules", + "worker profiles" + ], + "Physical System Type": "", + "AI Task": [ + "scheduling", + "productivity optimization" + ] + }, + "CSETv1": { + "Harm Distribution Basis": [ + "none" + ], + "Sector of Deployment": [ + "accommodation and food service activities" + ], + "Physical Objects": "no", + "Entertainment Industry": "no", + "Report, Test, or Study of data": "no", + "Deployed": "yes", + "Producer Test in Controlled Conditions": "no", + "Producer Test in Operational Conditions": "no", + "User Test in Controlled Conditions": "no", + "User Test in Operational Conditions": "no", + "Tangible Harm": "tangible harm definitively occurred", + "AI System": "yes", + "AI Harm Level": "AI tangible harm event", + "Impact on Critical Services": "no", + "Rights Violation": "no", + "Involving Minor": "no", + "Detrimental Content": "no", + "Protected Characteristic": "no", + "Clear link to Technology": "yes", + "Annotator’s AI special interest intangible harm assessment": "no", + "Public Sector Deployment": "no", + "Autonomy Level": "Autonomy2", + "Intentional Harm": "No. Not intentionally designed to perform harm", + "AI tools and methods": "", + "Special Interest Intangible Harm": "no", + "Date of Incident Year": "2014", + "Multiple AI Interaction": "no", + "Embedded": "no", + "Location Country (two letters)": "US", + "Location Region": "North America", + "Infrastructure Sectors": [], + "Operating Conditions": "", + "Lives Lost": 0, + "Injuries": 0, + "Data Inputs": [ + "schedules", + "worker profiles", + "store traffic" + ], + "Physical System Type": "", + "AI Task": [ + "scheduling", + "productivity optimization", + "predict store traffic" + ] + }, + "incident_id": 1, + "incident_date": "2014-08-14", + "epoch_incident_date": 1407974400, + "incident_title": "Kronos Scheduling Algorithm Allegedly Caused Financial Issues for Starbucks Employees", + "incident_description": "Kronos’s scheduling algorithm and its use by Starbucks managers allegedly negatively impacted financial and scheduling stability for Starbucks employees, which disadvantaged wage workers.", + "objectID": "23", + "_snippetResult": { + "description": { + "value": "Some employees at the coffee chain say it isn't living up to promises to improve the company's labor practices", + "matchLevel": "none" + }, + "text": { + "value": "For Starbucks (SBUX) barista Kylei Weisse, working at the coffee chain helps him secure health", + "matchLevel": "none" + } + }, + "_highlightResult": { + "description": { + "value": "Some employees at the coffee chain say it isn't living up to promises to improve the company's labor practices", + "matchLevel": "none", + "matchedWords": [] + }, + "title": { + "value": "​Is Starbucks shortchanging its baristas?", + "matchLevel": "none", + "matchedWords": [] + }, + "text": { + "value": "For Starbucks (SBUX) barista Kylei Weisse, working at the coffee chain helps him secure health insurance and some extra money while he studies at Georgia Perimeter College. What it doesn't provide is the kind of stable schedule that the company promised its workers last year.\n\n\"It's the wild inconsistency\" of the hours that's a problem, Weisse, 32, said. \"We're supposed to get them 10 days in advance, which often happens, but there's no guarantee. If our manager doesn't get it to us on time, we just have to deal with it.\"\n\nThat became a problem recently when Weisse's manager gave him only a few days notice on his work hours, which ended up conflicting with an anatomy and physiology exam at his college. Weisse ended up paying another worker $20 to take his shift so he could take the exam.\n\nThe short notice is especially frustrating because of Starbucks' vow last year to post employees' schedules at least 10 days in advance, as well as the company's insistence that workers provide at least one-month notice when they need to take a day off.\n\nWhat's behind Starbucks price increases?\n\nWeisse isn't alone in complaining that Starbucks isn't living up to its promises to overhaul its labor practices for its roughly 130,000 baristas. That vow followed an article last year by The New York Times that detailed how workers were struggling to manage childcare and other obligations when the company provided only a few days notice about their schedules.\n\nAbout half of roughly 200 Starbucks baristas in a recent survey said they are still receiving their schedule with less than one week's notice. Others also reported being asked to handle \"clopens,\" split shifts in which employees work a closing shift late into the evening and then an early opening shift the following morning. The company last year promised to end the practice.\n\nOf course, Starbucks isn't alone in using \"just-in-time\" scheduling, with the retail and restaurant industry increasingly turning to software that allows them to change work schedules at the last minute, depending on whether business picks up or slows down. But it is Starbucks that has become a lightning rod on the issue given its vows to improve how it treats employees and its own emphatic claims to valuing workers, whom it labels \"partners.\"\n\n\"Starbucks has the values and wants to do right by their employees,\" said Carrie Gleason, director of the Fair Workweek Initiative at the Center for Popular Democracy, an advocacy group focused on workers'rights, and a co-author of the group's new report on the company's labor practices. \"However, since last year when the company recognized there was a serious problem with the way it scheduled workers and pledged to reform, still so many of the same issues persist.\"\n\nStarbucks didn't respond to requests for comment on the study or on baristas' reports of labor practices that are failing to meet the company's stated goals.\n\nIn an internal memo this week published by Time, Starbucks executive Cliff Burrows wrote that the company couldn't validate the survey, but added that \"the findings suggest, contrary to the expectations we have in place, that some partners are receiving their schedules less than one week in advance and that there is a continuing issue with some partners working a close and then an opening shift the following morning.\" He asks store managers \"to go the extra mile to ensure partners have a consistent schedule.\"\n\nStarbucks ends \"race together\" campaign amid public backlash\n\nTo be sure, some Starbucks workers are receiving at least 10 days notice on their work hours, with the survey finding that about one-third receive two weeks notice and another 18 percent get their schedules three weeks in advance. But that leaves almost half of workers who only receive one week's notice, making it more difficult from them manage other obligations, such as school, family commitments or other jobs.\n\nClopens remain a problem, as well. About 60 percent of workers who have to handle a clopen receive seven or fewer hours of rest between a closing and an opening shift, the study found.\n\nThat's prompted one former Starbucks employee to start a petition to end the practice of scheduling clopens. Ciara Moran noted in her petition that she sometimes was only able to get four or five hours of sleep on the days she was scheduled for clopens. She said she quit her job because she doubted whether it was possible to get ahead given the demands on workers.\n\nEven if Starbucks stuck with its policy of providing eight hours between shifts, that's not enough time, especially given that many workers in the service sector have long commutes, the study said.\n\nAnother issue singled out by the report is Starbucks' practices on sick time. Since paid time off is only available to workers with at least a year on the job, about 40 percent of employees in the survey said they had dealt with barriers in taking sick days.\n\nIn a perfect world, Weisse said he'd like to receive his schedule either a month or a", + "matchLevel": "none", + "matchedWords": [] + }, + "incident_title": { + "value": "Kronos Scheduling Algorithm Allegedly Caused Financial Issues for Starbucks Employees", + "matchLevel": "none", + "matchedWords": [] + }, + "incident_description": { + "value": "Kronos’s scheduling algorithm and its use by Starbucks managers allegedly negatively impacted financial and scheduling stability for Starbucks employees, which disadvantaged wage workers.", + "matchLevel": "none", + "matchedWords": [] + } + } + }, + { + "authors": [ + "Marietje Schaake" + ], + "description": "😱 Wait! What? Just when you think you’ve seen it all…. Meta’s chatbot replied to the question askedby my colleague \n@kingjen\n: ’Who is a terrorist?’ with my (given) name! That’s right, not Bin Laden ", + "epoch_date_downloaded": 1661472000, + "epoch_date_modified": 1661731200, + "epoch_date_published": 1661385600, + "epoch_date_submitted": 1661472000, + "image_url": "https://pbs.twimg.com/media/Fa8rYYVWIAAI0xN?format=jpg", + "language": "en", + "report_number": 1967, + "source_domain": "twitter.com", + "submitters": [ + "Cesar Varela" + ], + "title": "Tweet: @MarietjeSchaake", + "name": "Tweet: @MarietjeSchaake", + "url": "https://twitter.com/MarietjeSchaake/status/1562515297688399873", + "tags": [], + "editor_notes": "", + "cloudinary_id": "reports/pbs.twimg.com/media/Fa8rYYVWIAAI0xN?format=jpg", + "text": "😱 Wait! What? Just when you think you’ve seen it all…. Meta’s chatbot replied to the question askedby my colleague\n@kingjen\n: ’Who is a terrorist?’ with my (given) name! That’s right, not Bin Laden or the Unabomber, but me… How did that happen? What are Meta’s sources?!\n", + "mongodb_id": "630c8eb443fe03f46cc8bcc4", + "featured": 1, + "is_incident_report": true, + "namespaces": [ + "CSETv1_Annotator-1", + "CSETv1" + ], + "classifications": [ + "CSETv1_Annotator-1:Harm Distribution Basis:none", + "CSETv1_Annotator-1:Sector of Deployment:information and communication", + "CSETv1_Annotator-1:Physical Objects:no", + "CSETv1_Annotator-1:Entertainment Industry:no", + "CSETv1_Annotator-1:Report, Test, or Study of data:no", + "CSETv1_Annotator-1:Deployed:yes", + "CSETv1_Annotator-1:Producer Test in Controlled Conditions:no", + "CSETv1_Annotator-1:Producer Test in Operational Conditions:no", + "CSETv1_Annotator-1:User Test in Controlled Conditions:no", + "CSETv1_Annotator-1:User Test in Operational Conditions:yes", + "CSETv1_Annotator-1:Tangible Harm:no tangible harm, near-miss, or issue", + "CSETv1_Annotator-1:AI System:yes", + "CSETv1_Annotator-1:AI Harm Level:none", + "CSETv1_Annotator-1:Impact on Critical Services:no", + "CSETv1_Annotator-1:Rights Violation:no", + "CSETv1_Annotator-1:Involving Minor:no", + "CSETv1_Annotator-1:Detrimental Content:yes", + "CSETv1_Annotator-1:Protected Characteristic:no", + "CSETv1_Annotator-1:Clear link to Technology:yes", + "CSETv1_Annotator-1:Annotator’s AI special interest intangible harm assessment:yes", + "CSETv1_Annotator-1:Public Sector Deployment:no", + "CSETv1_Annotator-1:Autonomy Level:Autonomy1", + "CSETv1_Annotator-1:Intentional Harm:No. Not intentionally designed to perform harm", + "CSETv1_Annotator-1:AI tools and methods:large language models", + "CSETv1_Annotator-1:Special Interest Intangible Harm:yes", + "CSETv1_Annotator-1:Date of Incident Year:2022", + "CSETv1_Annotator-1:Multiple AI Interaction:no", + "CSETv1_Annotator-1:Embedded:no", + "CSETv1_Annotator-1:Location Region:Global", + "CSETv1_Annotator-1:Data Inputs:user prompts", + "CSETv1_Annotator-1:Data Inputs:user queries", + "CSETv1_Annotator-1:Data Inputs:text", + "CSETv1_Annotator-1:AI Task:text generation", + "CSETv1:Harm Distribution Basis:unclear", + "CSETv1:Sector of Deployment:information and communication", + "CSETv1:Physical Objects:no", + "CSETv1:Entertainment Industry:no", + "CSETv1:Report, Test, or Study of data:no", + "CSETv1:Deployed:yes", + "CSETv1:Producer Test in Controlled Conditions:no", + "CSETv1:Producer Test in Operational Conditions:no", + "CSETv1:User Test in Controlled Conditions:no", + "CSETv1:User Test in Operational Conditions:yes", + "CSETv1:Tangible Harm:no tangible harm, near-miss, or issue", + "CSETv1:AI System:yes", + "CSETv1:AI Harm Level:none", + "CSETv1:Impact on Critical Services:no", + "CSETv1:Rights Violation:no", + "CSETv1:Involving Minor:no", + "CSETv1:Detrimental Content:yes", + "CSETv1:Protected Characteristic:no", + "CSETv1:Clear link to Technology:yes", + "CSETv1:Annotator’s AI special interest intangible harm assessment:yes", + "CSETv1:Public Sector Deployment:no", + "CSETv1:Autonomy Level:Autonomy1", + "CSETv1:Intentional Harm:No. Not intentionally designed to perform harm", + "CSETv1:AI tools and methods:large language models", + "CSETv1:Special Interest Intangible Harm:yes", + "CSETv1:Date of Incident Year:2022", + "CSETv1:Multiple AI Interaction:no", + "CSETv1:Embedded:no", + "CSETv1:Location Region:Global", + "CSETv1:Data Inputs:user prompts", + "CSETv1:Data Inputs:user queries", + "CSETv1:Data Inputs:text", + "CSETv1:AI Task:text generation" + ], + "CSETv1_Annotator-1": { + "Harm Distribution Basis": [ + "none" + ], + "Sector of Deployment": [ + "information and communication" + ], + "Physical Objects": "no", + "Entertainment Industry": "no", + "Report, Test, or Study of data": "no", + "Deployed": "yes", + "Producer Test in Controlled Conditions": "no", + "Producer Test in Operational Conditions": "no", + "User Test in Controlled Conditions": "no", + "User Test in Operational Conditions": "yes", + "Tangible Harm": "no tangible harm, near-miss, or issue", + "AI System": "yes", + "AI Harm Level": "none", + "Impact on Critical Services": "no", + "Rights Violation": "no", + "Involving Minor": "no", + "Detrimental Content": "yes", + "Protected Characteristic": "no", + "Clear link to Technology": "yes", + "Annotator’s AI special interest intangible harm assessment": "yes", + "Public Sector Deployment": "no", + "Autonomy Level": "Autonomy1", + "Intentional Harm": "No. Not intentionally designed to perform harm", + "AI tools and methods": [ + "large language models" + ], + "Special Interest Intangible Harm": "yes", + "Date of Incident Year": "2022", + "Multiple AI Interaction": "no", + "Embedded": "no", + "Location Country (two letters)": "", + "Location Region": "Global", + "Infrastructure Sectors": [], + "Operating Conditions": "", + "Lives Lost": 0, + "Injuries": 0, + "Data Inputs": [ + "user prompts", + "user queries", + "text" + ], + "Physical System Type": "", + "AI Task": [ + "text generation" + ] + }, + "CSETv1": { + "Harm Distribution Basis": [ + "unclear" + ], + "Sector of Deployment": [ + "information and communication" + ], + "Physical Objects": "no", + "Entertainment Industry": "no", + "Report, Test, or Study of data": "no", + "Deployed": "yes", + "Producer Test in Controlled Conditions": "no", + "Producer Test in Operational Conditions": "no", + "User Test in Controlled Conditions": "no", + "User Test in Operational Conditions": "yes", + "Tangible Harm": "no tangible harm, near-miss, or issue", + "AI System": "yes", + "AI Harm Level": "none", + "Impact on Critical Services": "no", + "Rights Violation": "no", + "Involving Minor": "no", + "Detrimental Content": "yes", + "Protected Characteristic": "no", + "Clear link to Technology": "yes", + "Annotator’s AI special interest intangible harm assessment": "yes", + "Public Sector Deployment": "no", + "Autonomy Level": "Autonomy1", + "Intentional Harm": "No. Not intentionally designed to perform harm", + "AI tools and methods": [ + "large language models" + ], + "Special Interest Intangible Harm": "yes", + "Date of Incident Year": "2022", + "Multiple AI Interaction": "no", + "Embedded": "no", + "Location Country (two letters)": "", + "Location Region": "Global", + "Infrastructure Sectors": [], + "Operating Conditions": "", + "Lives Lost": 0, + "Injuries": 0, + "Data Inputs": [ + "user prompts", + "user queries", + "text" + ], + "Physical System Type": "", + "AI Task": [ + "text generation" + ] + }, + "incident_id": 2, + "incident_date": "2022-08-25", + "epoch_incident_date": 1661385600, + "incident_title": "BlenderBot 3 Cited Dutch Politician as a Terrorist", + "incident_description": "Meta’s conversational AI BlenderBot 3, when prompted “who is a terrorist,“ responded with an incumbent Dutch politician’s name, who was confused about its association.", + "objectID": "1967", + "_snippetResult": { + "description": { + "value": "😱 Wait! What? Just when you think you’ve seen it all…. Meta’s chatbot replied to the question askedby my colleague \n@kingjen\n: ’Who is a terrorist?’ with my (given) name! That’s right, not Bin Laden ", + "matchLevel": "none" + }, + "text": { + "value": "😱 Wait! What? Just when you think you’ve seen it all…. Meta’s chatbot", + "matchLevel": "none" + } + }, + "_highlightResult": { + "description": { + "value": "😱 Wait! What? Just when you think you’ve seen it all…. Meta’s chatbot replied to the question askedby my colleague \n@kingjen\n: ’Who is a terrorist?’ with my (given) name! That’s right, not Bin Laden ", + "matchLevel": "none", + "matchedWords": [] + }, + "title": { + "value": "Tweet: @MarietjeSchaake", + "matchLevel": "none", + "matchedWords": [] + }, + "text": { + "value": "😱 Wait! What? Just when you think you’ve seen it all…. Meta’s chatbot replied to the question askedby my colleague\n@kingjen\n: ’Who is a terrorist?’ with my (given) name! That’s right, not Bin Laden or the Unabomber, but me… How did that happen? What are Meta’s sources?!\n", + "matchLevel": "none", + "matchedWords": [] + }, + "incident_title": { + "value": "BlenderBot 3 Cited Dutch Politician as a Terrorist", + "matchLevel": "none", + "matchedWords": [] + }, + "incident_description": { + "value": "Meta’s conversational AI BlenderBot 3, when prompted “who is a terrorist,“ responded with an incumbent Dutch politician’s name, who was confused about its association.", + "matchLevel": "none", + "matchedWords": [] + } + } + }, + { + "authors": [ + "William Douglas Heaven" + ], + "description": "When covid-19 struck Europe in March 2020, hospitals were plunged into a health crisis that was still badly understood. “Doctors really didn’t have a clue how to manage these patients,” says Laure Wyn", + "epoch_date_downloaded": 1648598400, + "epoch_date_modified": 1648598400, + "epoch_date_published": 1627603200, + "epoch_date_submitted": 1648598400, + "image_url": "https://wp.technologyreview.com/wp-content/uploads/2021/07/AP_20178810286930.jpg?resize=1200,600", + "language": "en", + "report_number": 1551, + "source_domain": "technologyreview.com", + "submitters": [ + "Anonymous" + ], + "title": "Hundreds of AI tools have been built to catch covid. None of them helped.", + "name": "Hundreds of AI tools have been built to catch covid. None of them helped.", + "url": "https://www.technologyreview.com/2021/07/30/1030329/machine-learning-ai-failed-covid-hospital-diagnosis-pandemic/", + "tags": [], + "editor_notes": "", + "cloudinary_id": "reports/wp.technologyreview.com/wp-content/uploads/2021/07/AP_20178810286930.jpg?resize=1200,600", + "text": "When covid-19 struck Europe in March 2020, hospitals were plunged into a health crisis that was still badly understood. “Doctors really didn’t have a clue how to manage these patients,” says Laure Wynants, an epidemiologist at Maastricht University in the Netherlands, who studies predictive tools.\n\nBut there was data coming out of China, which had a four-month head start in the race to beat the pandemic. If machine-learning algorithms could be trained on that data to help doctors understand what they were seeing and make decisions, it just might save lives. “I thought, ‘If there’s any time that AI could prove its usefulness, it’s now,’” says Wynants. “I had my hopes up.”\n\nIt never happened—but not for lack of effort. Research teams around the world stepped up to help. The AI community, in particular, rushed to develop software that many believed would allow hospitals to diagnose or triage patients faster, bringing much-needed support to the front lines—in theory.\n\nIn the end, many hundreds of predictive tools were developed. None of them made a real difference, and some were potentially harmful.\n\nThat’s the damning conclusion of multiple studies published in the last few months. In June, the Turing Institute, the UK’s national center for data science and AI, put out a report summing up discussions at a series of workshops it held in late 2020. The clear consensus was that AI tools had made little, if any, impact in the fight against covid.\n\nNot fit for clinical use\n\nThis echoes the results of two major studies that assessed hundreds of predictive tools developed last year. Wynants is lead author of one of them, a review in the British Medical Journal that is still being updated as new tools are released and existing ones tested. She and her colleagues have looked at 232 algorithms for diagnosing patients or predicting how sick those with the disease might get. They found that none of them were fit for clinical use. Just two have been singled out as being promising enough for future testing.\n\n“It’s shocking,” says Wynants. “I went into it with some worries, but this exceeded my fears.”\n\nWynants’s study is backed up by another large review carried out by Derek Driggs, a machine-learning researcher at the University of Cambridge, and his colleagues, and published in Nature Machine Intelligence. This team zoomed in on deep-learning models for diagnosing covid and predicting patient risk from medical images, such as chest x-rays and chest computer tomography (CT) scans. They looked at 415 published tools and, like Wynants and her colleagues, concluded that none were fit for clinical use.\n\n“This pandemic was a big test for AI and medicine,” says Driggs, who is himself working on a machine-learning tool to help doctors during the pandemic. “It would have gone a long way to getting the public on our side,” he says. “But I don’t think we passed that test.”\n\nBoth teams found that researchers repeated the same basic errors in the way they trained or tested their tools. Incorrect assumptions about the data often meant that the trained models did not work as claimed.\n\nWynants and Driggs still believe AI has the potential to help. But they are concerned that it could be harmful if built in the wrong way because they could miss diagnoses or underestimate risk for vulnerable patients. “There is a lot of hype about machine-learning models and what they can do today,” says Driggs.\n\nUnrealistic expectations encourage the use of these tools before they are ready. Wynants and Driggs both say that a few of the algorithms they looked at have already been used in hospitals, and some are being marketed by private developers. “I fear that they may have harmed patients,” says Wynants.\n\nSo what went wrong? And how do we bridge that gap? If there’s an upside, it is that the pandemic has made it clear to many researchers that the way AI tools are built needs to change. “The pandemic has put problems in the spotlight that we’ve been dragging along for some time,” says Wynants.\n\nWhat went wrong\n\nMany of the problems that were uncovered are linked to the poor quality of the data that researchers used to develop their tools. Information about covid patients, including medical scans, was collected and shared in the middle of a global pandemic, often by the doctors struggling to treat those patients. Researchers wanted to help quickly, and these were the only public data sets available. But this meant that many tools were built using mislabeled data or data from unknown sources.\n\nDriggs highlights the problem of what he calls Frankenstein data sets, which are spliced together from multiple sources and can contain duplicates. This means that some tools end up being tested on the same data they were trained on, making them appear more accurate than they are.\n\nIt also muddies the origin of certain data sets. This can mean that researchers miss important features that skew the training of their models. Many unwittingly used a data set that contained chest scans of children who did not have covid as their examples of what non-covid cases looked like. But as a result, the AIs learned to identify kids, not covid.\n\nDriggs’s group trained its own model using a data set that contained a mix of scans taken when patients were lying down and standing up. Because patients scanned while lying down were more likely to be seriously ill, the AI learned wrongly to predict serious covid risk from a person’s position.\n\nIn yet other cases, some AIs were found to be picking up on the text font that certain hospitals used to label the scans. As a result, fonts from hospitals with more serious caseloads became predictors of covid risk.\n\nErrors like these seem obvious in hindsight. They can also be fixed by adjusting the models, if researchers are aware of them. It is possible to acknowledge the shortcomings and release a less accurate, but less misleading model. But many tools were developed either by AI researchers who lacked the medical expertise to spot flaws in the data or by medical researchers who lacked the mathematical skills to compensate for those flaws.\n\nA more subtle problem Driggs highlights is incorporation bias, or bias introduced at the point a data set is labeled. For example, many medical scans were labeled according to whether the radiologists who created them said they showed covid. But that embeds, or incorporates, any biases of that particular doctor into the ground truth of a data set. It would be much better to label a medical scan with the result of a PCR test rather than one doctor’s opinion, says Driggs. But there isn’t always time for statistical niceties in busy hospitals.\n\nThat hasn’t stopped some of these tools from being rushed into clinical practice. Wynants says it isn’t clear which ones are being used or how. Hospitals will sometimes say that they are using a tool only for research purposes, which makes it hard to assess how much doctors are relying on them. “There’s a lot of secrecy,” she says.\n\nWynants asked one company that was marketing deep-learning algorithms to share information about its approach but did not hear back. She later found several published models from researchers tied to this company, all of them with a high risk of bias. “We don’t actually know what the company implemented,” she says.\n\nAccording to Wynants, some hospitals are even signing nondisclosure agreements with medical AI vendors. When she asked doctors what algorithms or software they were using, they sometimes told her they weren’t allowed to say.\n\nHow to fix it\n\nWhat’s the fix? Better data would help, but in times of crisis that’s a big ask. It’s more important to make the most of the data sets we have. The simplest move would be for AI teams to collaborate more with clinicians, says Driggs. Researchers also need to share their models and disclose how they were trained so that others can test them and build on them. “Those are two things we could do today,” he says. “And they would solve mayb", + "mongodb_id": "6243a9eedf8b4b62d982817e", + "featured": 1, + "is_incident_report": true, + "namespaces": [ + "CSETv1_Annotator-3", + "CSETv1_Annotator-1" + ], + "classifications": [ + "CSETv1_Annotator-3:Harm Distribution Basis:unclear", + "CSETv1_Annotator-3:Sector of Deployment:human health and social work activities", + "CSETv1_Annotator-3:Physical Objects:yes", + "CSETv1_Annotator-3:Entertainment Industry:no", + "CSETv1_Annotator-3:Report, Test, or Study of data:no", + "CSETv1_Annotator-3:Deployed:yes", + "CSETv1_Annotator-3:Producer Test in Controlled Conditions:no", + "CSETv1_Annotator-3:Producer Test in Operational Conditions:no", + "CSETv1_Annotator-3:User Test in Controlled Conditions:no", + "CSETv1_Annotator-3:User Test in Operational Conditions:no", + "CSETv1_Annotator-3:Tangible Harm:unclear", + "CSETv1_Annotator-3:AI System:yes", + "CSETv1_Annotator-3:AI Harm Level:none", + "CSETv1_Annotator-3:Impact on Critical Services:maybe", + "CSETv1_Annotator-3:Rights Violation:maybe", + "CSETv1_Annotator-3:Involving Minor:no", + "CSETv1_Annotator-3:Detrimental Content:no", + "CSETv1_Annotator-3:Protected Characteristic:maybe", + "CSETv1_Annotator-3:Clear link to Technology:yes", + "CSETv1_Annotator-3:Annotator’s AI special interest intangible harm assessment:maybe", + "CSETv1_Annotator-3:Public Sector Deployment:no", + "CSETv1_Annotator-3:Autonomy Level:unclear", + "CSETv1_Annotator-3:Intentional Harm:No. Not intentionally designed to perform harm", + "CSETv1_Annotator-3:Special Interest Intangible Harm:maybe", + "CSETv1_Annotator-3:Date of Incident Year:2020", + "CSETv1_Annotator-3:Multiple AI Interaction:no", + "CSETv1_Annotator-3:Embedded:maybe", + "CSETv1_Annotator-3:Location Region:Global", + "CSETv1_Annotator-3:Infrastructure Sectors:healthcare and public health", + "CSETv1_Annotator-3:Data Inputs:Unclear", + "CSETv1_Annotator-1:Physical Objects:no", + "CSETv1_Annotator-1:Entertainment Industry:no", + "CSETv1_Annotator-1:Report, Test, or Study of data:maybe", + "CSETv1_Annotator-1:Deployed:no", + "CSETv1_Annotator-1:Producer Test in Controlled Conditions:yes", + "CSETv1_Annotator-1:Producer Test in Operational Conditions:no", + "CSETv1_Annotator-1:User Test in Controlled Conditions:no", + "CSETv1_Annotator-1:User Test in Operational Conditions:no", + "CSETv1_Annotator-1:Tangible Harm:no tangible harm, near-miss, or issue", + "CSETv1_Annotator-1:AI System:yes", + "CSETv1_Annotator-1:AI Harm Level:none", + "CSETv1_Annotator-1:Impact on Critical Services:no", + "CSETv1_Annotator-1:Rights Violation:no", + "CSETv1_Annotator-1:Involving Minor:no", + "CSETv1_Annotator-1:Detrimental Content:no", + "CSETv1_Annotator-1:Protected Characteristic:no", + "CSETv1_Annotator-1:Harm Distribution Basis:none", + "CSETv1_Annotator-1:Special Interest Intangible Harm:no", + "CSETv1_Annotator-1:Clear link to Technology:maybe", + "CSETv1_Annotator-1:Annotator’s AI special interest intangible harm assessment:no", + "CSETv1_Annotator-1:Date of Incident Year:2020", + "CSETv1_Annotator-1:Multiple AI Interaction:no", + "CSETv1_Annotator-1:Embedded:no", + "CSETv1_Annotator-1:Location Region:Global", + "CSETv1_Annotator-1:Data Inputs:patient information", + "CSETv1_Annotator-1:Data Inputs:medical scans", + "CSETv1_Annotator-1:Data Inputs:chest scans", + "CSETv1_Annotator-1:Data Inputs:electronic health records", + "CSETv1_Annotator-1:Sector of Deployment:human health and social work activities", + "CSETv1_Annotator-1:Public Sector Deployment:no", + "CSETv1_Annotator-1:Autonomy Level:Autonomy3", + "CSETv1_Annotator-1:Intentional Harm:No. Not intentionally designed to perform harm", + "CSETv1_Annotator-1:AI Task:diagnose patients" + ], + "CSETv1_Annotator-3": { + "Harm Distribution Basis": [ + "unclear" + ], + "Sector of Deployment": [ + "human health and social work activities" + ], + "Physical Objects": "yes", + "Entertainment Industry": "no", + "Report, Test, or Study of data": "no", + "Deployed": "yes", + "Producer Test in Controlled Conditions": "no", + "Producer Test in Operational Conditions": "no", + "User Test in Controlled Conditions": "no", + "User Test in Operational Conditions": "no", + "Tangible Harm": "unclear", + "AI System": "yes", + "AI Harm Level": "none", + "Impact on Critical Services": "maybe", + "Rights Violation": "maybe", + "Involving Minor": "no", + "Detrimental Content": "no", + "Protected Characteristic": "maybe", + "Clear link to Technology": "yes", + "Annotator’s AI special interest intangible harm assessment": "maybe", + "Public Sector Deployment": "no", + "Autonomy Level": "unclear", + "Intentional Harm": "No. Not intentionally designed to perform harm", + "AI tools and methods": "", + "Special Interest Intangible Harm": "maybe", + "Date of Incident Year": "2020", + "Multiple AI Interaction": "no", + "Embedded": "maybe", + "Location Country (two letters)": "", + "Location Region": "Global", + "Infrastructure Sectors": [ + "healthcare and public health" + ], + "Operating Conditions": "", + "Lives Lost": 0, + "Injuries": 0, + "Data Inputs": [ + "Unclear" + ], + "Physical System Type": "", + "AI Task": "" + }, + "CSETv1_Annotator-1": { + "Physical Objects": "no", + "Entertainment Industry": "no", + "Report, Test, or Study of data": "maybe", + "Deployed": "no", + "Producer Test in Controlled Conditions": "yes", + "Producer Test in Operational Conditions": "no", + "User Test in Controlled Conditions": "no", + "User Test in Operational Conditions": "no", + "Tangible Harm": "no tangible harm, near-miss, or issue", + "AI System": "yes", + "AI Harm Level": "none", + "Impact on Critical Services": "no", + "Rights Violation": "no", + "Involving Minor": "no", + "Detrimental Content": "no", + "Protected Characteristic": "no", + "Harm Distribution Basis": [ + "none" + ], + "Special Interest Intangible Harm": "no", + "Clear link to Technology": "maybe", + "Annotator’s AI special interest intangible harm assessment": "no", + "Date of Incident Year": "2020", + "Multiple AI Interaction": "no", + "Embedded": "no", + "Location Region": "Global", + "Lives Lost": 0, + "Injuries": 0, + "Data Inputs": [ + "patient information", + "medical scans", + "chest scans", + "electronic health records" + ], + "Sector of Deployment": [ + "human health and social work activities" + ], + "Public Sector Deployment": "no", + "Autonomy Level": "Autonomy3", + "Intentional Harm": "No. Not intentionally designed to perform harm", + "AI Task": [ + "diagnose patients" + ] + }, + "incident_id": 3, + "incident_date": "2021-07-30", + "epoch_incident_date": 1627603200, + "incident_title": "AI Tools Failed to Sufficiently Predict COVID Patients, Some Potentially Harmful", + "incident_description": "AI tools failed to sufficiently predict COVID patients, some potentially harmful.", + "objectID": "1551", + "_snippetResult": { + "description": { + "value": "When covid-19 struck Europe in March 2020, hospitals were plunged into a health crisis that was still badly understood. “Doctors really didn’t have a clue how to manage these patients,” says Laure Wyn", + "matchLevel": "none" + }, + "text": { + "value": "When covid-19 struck Europe in March 2020, hospitals were plunged into a health crisis", + "matchLevel": "none" + } + }, + "_highlightResult": { + "description": { + "value": "When covid-19 struck Europe in March 2020, hospitals were plunged into a health crisis that was still badly understood. “Doctors really didn’t have a clue how to manage these patients,” says Laure Wyn", + "matchLevel": "none", + "matchedWords": [] + }, + "title": { + "value": "Hundreds of AI tools have been built to catch covid. None of them helped.", + "matchLevel": "none", + "matchedWords": [] + }, + "text": { + "value": "When covid-19 struck Europe in March 2020, hospitals were plunged into a health crisis that was still badly understood. “Doctors really didn’t have a clue how to manage these patients,” says Laure Wynants, an epidemiologist at Maastricht University in the Netherlands, who studies predictive tools.\n\nBut there was data coming out of China, which had a four-month head start in the race to beat the pandemic. If machine-learning algorithms could be trained on that data to help doctors understand what they were seeing and make decisions, it just might save lives. “I thought, ‘If there’s any time that AI could prove its usefulness, it’s now,’” says Wynants. “I had my hopes up.”\n\nIt never happened—but not for lack of effort. Research teams around the world stepped up to help. The AI community, in particular, rushed to develop software that many believed would allow hospitals to diagnose or triage patients faster, bringing much-needed support to the front lines—in theory.\n\nIn the end, many hundreds of predictive tools were developed. None of them made a real difference, and some were potentially harmful.\n\nThat’s the damning conclusion of multiple studies published in the last few months. In June, the Turing Institute, the UK’s national center for data science and AI, put out a report summing up discussions at a series of workshops it held in late 2020. The clear consensus was that AI tools had made little, if any, impact in the fight against covid.\n\nNot fit for clinical use\n\nThis echoes the results of two major studies that assessed hundreds of predictive tools developed last year. Wynants is lead author of one of them, a review in the British Medical Journal that is still being updated as new tools are released and existing ones tested. She and her colleagues have looked at 232 algorithms for diagnosing patients or predicting how sick those with the disease might get. They found that none of them were fit for clinical use. Just two have been singled out as being promising enough for future testing.\n\n“It’s shocking,” says Wynants. “I went into it with some worries, but this exceeded my fears.”\n\nWynants’s study is backed up by another large review carried out by Derek Driggs, a machine-learning researcher at the University of Cambridge, and his colleagues, and published in Nature Machine Intelligence. This team zoomed in on deep-learning models for diagnosing covid and predicting patient risk from medical images, such as chest x-rays and chest computer tomography (CT) scans. They looked at 415 published tools and, like Wynants and her colleagues, concluded that none were fit for clinical use.\n\n“This pandemic was a big test for AI and medicine,” says Driggs, who is himself working on a machine-learning tool to help doctors during the pandemic. “It would have gone a long way to getting the public on our side,” he says. “But I don’t think we passed that test.”\n\nBoth teams found that researchers repeated the same basic errors in the way they trained or tested their tools. Incorrect assumptions about the data often meant that the trained models did not work as claimed.\n\nWynants and Driggs still believe AI has the potential to help. But they are concerned that it could be harmful if built in the wrong way because they could miss diagnoses or underestimate risk for vulnerable patients. “There is a lot of hype about machine-learning models and what they can do today,” says Driggs.\n\nUnrealistic expectations encourage the use of these tools before they are ready. Wynants and Driggs both say that a few of the algorithms they looked at have already been used in hospitals, and some are being marketed by private developers. “I fear that they may have harmed patients,” says Wynants.\n\nSo what went wrong? And how do we bridge that gap? If there’s an upside, it is that the pandemic has made it clear to many researchers that the way AI tools are built needs to change. “The pandemic has put problems in the spotlight that we’ve been dragging along for some time,” says Wynants.\n\nWhat went wrong\n\nMany of the problems that were uncovered are linked to the poor quality of the data that researchers used to develop their tools. Information about covid patients, including medical scans, was collected and shared in the middle of a global pandemic, often by the doctors struggling to treat those patients. Researchers wanted to help quickly, and these were the only public data sets available. But this meant that many tools were built using mislabeled data or data from unknown sources.\n\nDriggs highlights the problem of what he calls Frankenstein data sets, which are spliced together from multiple sources and can contain duplicates. This means that some tools end up being tested on the same data they were trained on, making them appear more accurate than they are.\n\nIt also muddies the origin of certain data sets. This can mean that researchers miss important features that skew the training of their models. Many unwittingly used a data set that contained chest scans of children who did not have covid as their examples of what non-covid cases looked like. But as a result, the AIs learned to identify kids, not covid.\n\nDriggs’s group trained its own model using a data set that contained a mix of scans taken when patients were lying down and standing up. Because patients scanned while lying down were more likely to be seriously ill, the AI learned wrongly to predict serious covid risk from a person’s position.\n\nIn yet other cases, some AIs were found to be picking up on the text font that certain hospitals used to label the scans. As a result, fonts from hospitals with more serious caseloads became predictors of covid risk.\n\nErrors like these seem obvious in hindsight. They can also be fixed by adjusting the models, if researchers are aware of them. It is possible to acknowledge the shortcomings and release a less accurate, but less misleading model. But many tools were developed either by AI researchers who lacked the medical expertise to spot flaws in the data or by medical researchers who lacked the mathematical skills to compensate for those flaws.\n\nA more subtle problem Driggs highlights is incorporation bias, or bias introduced at the point a data set is labeled. For example, many medical scans were labeled according to whether the radiologists who created them said they showed covid. But that embeds, or incorporates, any biases of that particular doctor into the ground truth of a data set. It would be much better to label a medical scan with the result of a PCR test rather than one doctor’s opinion, says Driggs. But there isn’t always time for statistical niceties in busy hospitals.\n\nThat hasn’t stopped some of these tools from being rushed into clinical practice. Wynants says it isn’t clear which ones are being used or how. Hospitals will sometimes say that they are using a tool only for research purposes, which makes it hard to assess how much doctors are relying on them. “There’s a lot of secrecy,” she says.\n\nWynants asked one company that was marketing deep-learning algorithms to share information about its approach but did not hear back. She later found several published models from researchers tied to this company, all of them with a high risk of bias. “We don’t actually know what the company implemented,” she says.\n\nAccording to Wynants, some hospitals are even signing nondisclosure agreements with medical AI vendors. When she asked doctors what algorithms or software they were using, they sometimes told her they weren’t allowed to say.\n\nHow to fix it\n\nWhat’s the fix? Better data would help, but in times of crisis that’s a big ask. It’s more important to make the most of the data sets we have. The simplest move would be for AI teams to collaborate more with clinicians, says Driggs. Researchers also need to share their models and disclose how they were trained so that others can test them and build on them. “Those are two things we could do today,” he says. “And they would solve mayb", + "matchLevel": "none", + "matchedWords": [] + }, + "incident_title": { + "value": "AI Tools Failed to Sufficiently Predict COVID Patients, Some Potentially Harmful", + "matchLevel": "none", + "matchedWords": [] + }, + "incident_description": { + "value": "AI tools failed to sufficiently predict COVID patients, some potentially harmful.", + "matchLevel": "none", + "matchedWords": [] + } + } + }, + ], + "nbHits": 3, + "page": 0, + "nbPages": 1, + "hitsPerPage": 28, + "facets": { + "flag": { + "true": 36 + }, + "tags": { + "response": 163, + "deepfake": 23, + "generative AI": 12, + "CSAM": 5, + "journalism": 5, + "Amazon": 4, + "minors": 4, + "racial bias": 4, + "Audio": 3, + "Deepfakes": 3, + "Grok": 3, + "Scam": 3, + "TikTok": 3, + "deepfake porn": 3, + "fraud": 3, + "social media": 3, + "Alexa": 2, + "Celebrities": 2, + "Children": 2, + "Data privacy": 2, + "Education": 2, + "Extortion": 2, + "Facial recognition": 2, + "Kamala Harris": 2, + "Misinformation": 2, + "Nazi": 2, + "Nudify": 2, + "Privacy": 2, + "algorithmic bias": 2, + "chatbot": 2, + "child abuse": 2, + "child sex abuse material": 2, + "content moderation": 2, + "data": 2, + "driverless cars": 2, + "elections": 2, + "facial recognition technology": 2, + "health insurance": 2, + "lack of consent": 2, + "products": 2, + "#response": 1, + "2FA": 1, + "AI Music": 1, + "AI bias": 1, + "AI content farms": 1, + "AI facial feature modification": 1, + "AI image alteration": 1, + "AI meal planner": 1, + "AI misinterpretation of professionalism": 1, + "AI racial representation": 1, + "AI research": 1, + "AI-authored books": 1, + "AI-generated news sites": 1, + "Advertisement": 1, + "Alaska": 1, + "Amazon Photos": 1, + "Apple Photos": 1, + "Army": 1, + "Barack Obama": 1, + "British politics": 1, + "California law": 1, + "Central America": 1, + "ChatGPT": 1, + "Chatbots": 1, + "Cheating": 1, + "Child sexual abuse material": 1, + "Clearview AI": 1, + "Criminal activity": 1, + "Crypto": 1, + "DPA": 1, + "Dark Web": 1, + "Data exposure": 1, + "Data retention": 1, + "Data set": 1, + "Donald Trump": 1, + "Ed Tech": 1, + "Educational AI": 1, + "Election interference": 1, + "Elon Musk": 1, + "Encouragement of violence": 1, + "Exam": 1, + "FTC": 1, + "Fake news": 1, + "False positive": 1, + "Figma": 1, + "GitHub": 1, + "Google Photos": 1, + "IWF": 1, + "Image recognition": 1, + "India": 1, + "Internet Watch Foundation": 1, + "LAION-5B": 1, + "LGBTQ+": 1, + "Latin America": 1, + "Lawsuit": 1, + "Mental illness": 1, + "Microsoft Azure": 1, + "Microsoft OneDrive": 1, + "Music": 1, + "Nudification": 1, + "OSHA": 1, + "Photoshop": 1, + "Plagiarism detector": 1, + "Political manipulation": 1, + "Porn": 1, + "Queer representation": 1, + "Robots": 1, + "Russia": 1, + "SAS tokens": 1, + "Security vulnerability": 1, + "Slovakia": 1, + "South America": 1, + "South Korea": 1, + "Spain": 1, + "Spam": 1, + "Stable Diffusion": 1, + "Tesla": 1, + "Training data": 1, + "Turkish": 1, + "Twitter": 1, + "Undress": 1, + "Video": 1, + "Workplace accident": 1, + "X": 1, + "accountability": 1, + "art": 1, + "automated decision-making": 1, + "autopilot": 1, + "biometric data": 1, + "celebrity": 1, + "child exploitation": 1, + "child pornography": 1, + "comedy": 1, + "commercials": 1, + "conference call": 1, + "copyright infringement": 1, + "court": 1, + "coverage denial": 1, + "customer service": 1, + "data accuracy": 1, + "data integrity": 1, + "deepfake audio": 1, + "deepfaked audio": 1, + "digital crime": 1, + "disinformation": 1, + "editing error": 1, + "false arrest": 1, + "false citations": 1, + "gender bias": 1, + "generated books": 1, + "geopolitics": 1, + "google ai search": 1, + "hallucination": 1, + "hazardous advice": 1, + "healthcare algorithms": 1, + "healthcare modeling": 1, + "healthcare technology": 1, + "inappropriate content": 1, + "influence campaigns": 1, + "journalism ethics": 1, + "labor relations": 1, + "language model malfunction": 1, + "law": 1, + "legal": 1, + "legal dispute": 1, + "loans": 1, + "medical claims": 1, + "medical diagnosis": 1, + "misidentification": 1, + "news aggregation": 1, + "news network": 1, + "offensive content": 1, + "official report using false generated AI": 1, + "pedestrians": 1, + "photo editing": 1, + "political disinformation": 1, + "politics": 1, + "posthumous use": 1, + "retail": 1, + "robotics": 1, + "self-driving": 1, + "synthetic imagery": 1, + "targeted advertising": 1, + "text-to-image generation": 1, + "underwriting": 1, + "website poll": 1, + "wifi connectivity": 1, + "workplace death": 1, + "wrongful arrest": 1 + }, + "authors": { + "Associated Press": 25, + "Reuters": 23, + "BBC News": 22, + "James Vincent": 22, + "Kashmir Hill": 18, + "Christopher Knaus": 13, + "Katyanna Quach": 13, + "Benj Edwards": 12, + "Noor Al-Sibai": 12, + "Alex Hern": 11, + "Andrew J. Hawkins": 11, + "Fred Lambert": 11, + "Tom Simonite": 11, + "Emma Roth": 10, + "Maggie Harrison": 10, + "Will Oremus": 10, + "Chloe Xiang": 9, + "Pranshu Verma": 9, + "Sam Levin": 9, + "Surya Mattu": 9, + "Bloomberg": 8, + "Dan Milmo": 8, + "David Shepardson": 8, + "Drew Harwell": 8, + "James Whitbrook": 8, + "Julia Angwin": 8, + "Karen Hao": 8, + "The Guardian": 8, + "Tom Krisher": 8, + "Aarian Marshall": 7, + "Ashley Belanger": 7, + "Jon Fingas": 7, + "Kat Tenbarge": 7, + "Mack DeGeurin": 7, + "Matthew Gault": 7, + "Pesala Bandara": 7, + "Samantha Cole": 7, + "Steven Lee Myers": 7, + "Agence France-Presse": 6, + "Gerrit De Vynck": 6, + "Jon Christian": 6, + "Kathleen McGrory": 6, + "Matt McFarland": 6, + "Neal E. Boudette": 6, + "Olivia Solon": 6, + "Ryan Mac": 6, + "Samuel Gibbs": 6, + "Sky News": 6, + "Tim Cushing": 6, + "Todd Feathers": 6, + "Will Knight": 6, + "Brandon Vigliarolo": 5, + "Britney Nguyen": 5, + "Cade Metz": 5, + "Dan Goodin": 5, + "Faiz Siddiqui": 5, + "Garance Burke": 5, + "Human Rights Watch": 5, + "James Titcomb": 5, + "Jason Koebler": 5, + "Jay Peters": 5, + "John Goreham": 5, + "Josh Taylor": 5, + "Julie Jargon": 5, + "Lora Kolodny": 5, + "Melissa Heikkilä": 5, + "Natasha Lomas": 5, + "Natasha Singer": 5, + "Nico Grant": 5, + "Nitasha Tiku": 5, + "Ryan Felton": 5, + "Shannon Bond": 5, + "Stuart A. Thompson": 5, + "The Economist": 5, + "Thomas Claburn": 5, + "Tiffany Hsu": 5, + "Timothy B. Lee": 5, + "Trevor Mogg": 5, + "Victor Tangermann": 5, + "Wikipedia Editors": 5, + "Afp": 4, + "Arden Dier": 4, + "BBC": 4, + "Benjamin Weiser": 4, + "Brad Templeton": 4, + "CBS News": 4, + "Casey Ross": 4, + "Cyrus Farivar": 4, + "Dave Lee": 4, + "David Meyer": 4, + "Derek B. Johnson": 4, + "Elizabeth Stanton": 4, + "Federal Trade Commission": 4, + "Gabriel Geiger": 4, + "Glenn Mcdonald": 4, + "Hilke Schellmann": 4, + "IFL Science": 4, + "Isobel Asher Hamilton": 4, + "Jack Morse": 4, + "James Cook": 4, + "Jeff Larson": 4, + "Johana Bhuiyan": 4, + "Jon Brodkin": 4, + "Jordan Pearson": 4, + "Joseph Cox": 4, + "Kevin Roose": 4, + "Khari Johnson": 4, + "Kyle Wiggers": 4, + "Lauren Kirchner": 4, + "Lauren Leffer": 4, + "Lucas Ropek": 4, + "Luke Henriques-Gomes": 4, + "Madison Malone Kircher": 4, + "Maggie Harrison Dupré": 4, + "Matt Novak": 4, + "National Highway Traffic Safety Administration": 4, + "Privacy International": 4, + "RT": 4, + "Rachel Metz": 4, + "Rob Price": 4, + "Sean Keach": 4, + "Shannon Liao": 4, + "Srishti Deoras": 4, + "The Inquirer": 4, + "Thomas Orsolya": 4, + "Wes Davis": 4, + "Aaron Rieke": 3, + "Abigail Beall": 3, + "Adam Satariano": 3, + "Adrienne Roberts": 3, + "Aj Dellinger": 3, + "Al Jazeera": 3, + "Alan Levin": 3, + "Aleksandra Korolova": 3, + "Alex Kasprak": 3, + "Alex Seitz-Wald": 3, + "Ananya Bhattacharya": 3, + "Andrew Higgins": 3, + "Annie Palmer": 3, + "Ariel Zilber": 3, + "Australian Broadcasting Corporation": 3, + "Bill McCarthy": 3, + "Blake Brittain": 3, + "Bobby Allyn": 3, + "Brendan Pierson": 3, + "Brett Molina": 3, + "Brian Barrett": 3, + "Cara McGoogan": 3, + "Carl Franzen": 3, + "Caroline Haskins": 3, + "Catherine Thorbecke": 3, + "Cecily Mauran": 3, + "Charles Pulliam-Moore": 3, + "Chris Burt": 3, + "City News Service": 3, + "Cory Doctorow": 3, + "Dan Levine": 3, + "Daniel Wu": 3, + "Dara Kerr": 3, + "Davey Alba": 3, + "David Gilbert": 3, + "David Grossman": 3, + "David Lumb": 3, + "Department of Motor Vehicles": 3, + "Devin Coldewey": 3, + "Dhruv Mehrotra": 3, + "Douglas Heaven": 3, + "Douglas Macmillan": 3, + "Duncan Riley": 3, + "Eileen Guo": 3, + "Elizabeth Dwoskin": 3, + "Elizabeth Napolitano": 3, + "Emilia David": 3, + "Erielle Delzer": 3, + "Faustine Ngila": 3, + "Foxglove": 3, + "Frank Landymore": 3, + "Gareth Corfield": 3, + "Geoffrey A. Fowler": 3, + "Gianluca Mezzofiore": 3, + "Global Witness": 3, + "Heather Somerville": 3, + "Heather Vogell": 3, + "Hyunjoo Jin": 3, + "IANS": 3, + "Issie Lapowsky": 3, + "Jack Gillum": 3, + "Jackie Snow": 3, + "Jane Wakefield": 3, + "Jeff Horwitz": 3, + "Jefferson Graham": 3, + "Jeffrey Dastin": 3, + "Jim Waterson": 3, + "Jonathan M. Gitlin": 3, + "Kevin Okemwa": 3, + "Kieren McCarthy": 3, + "Kit Eaton": 3, + "Kris Holt": 3, + "Kyle Orland": 3, + "Lauren Kaori Gurley": 3, + "Leon Yin": 3, + "Louise Matsakis": 3, + "Loukia Papadopoulos": 3, + "Luke Dormehl": 3, + "Mallory Locklear": 3, + "Marco Marcelline": 3, + "Matt Binder": 3, + "Matt O'Brien": 3, + "Megan Cerullo": 3, + "Mia Sato": 3, + "Michael Kan": 3, + "Mikael Thalen": 3, + "Mike Murphy": 3, + "Morgan Meaker": 3, + "Naomi Nix": 3, + "National Transportation Safety Board": 3, + "Natt Garun": 3, + "Neil Bedi": 3, + "News Desk": 3, + "Nicholas Thompson": 3, + "Nick Robins-Early": 3, + "Nicolas Kayser-Bril": 3, + "OpenAI": 3, + "Pandaily": 3, + "Paris Martineau": 3, + "Patrick Collinson": 3, + "Paul Mozur": 3, + "Phoebe Weston": 3, + "Phys Org": 3, + "Reuters Editorial": 3, + "Rob Beschizza": 3, + "Rob Stumpf": 3, + "Robert Booth": 3, + "Ronan Glon": 3, + "Ryan Whitwam": 3, + "Sam Biddle": 3, + "Saqib Shah": 3, + "Sejal Sharma": 3, + "Sheera Frenkel": 3, + "Stephen Edelstein": 3, + "Synced": 3, + "Team Latestly": 3, + "The Straits Times": 3, + "Thomas Barrabi": 3, + "Thomas Brewster": 3, + "Thomas Germain": 3, + "Thomas Macaulay": 3, + "Tim De Chant": 3, + "Times of India": 3, + "Tom Warren": 3, + "Trisha Thadani": 3, + "Tristan Greene": 3, + "ABP News Bureau": 2, + "Aaron Glantz": 2, + "Aaron Horowitz": 2, + "Aaron Mok": 2, + "Aaron Sankin": 2, + "Aaron Schaffer": 2, + "Abby Ohlheiser": 2, + "Abhirup Roy": 2, + "Adam Clark Estes": 2, + "Adi Robertson": 2, + "Adrianna Nine": 2, + "Adrianne Jeffries": 2, + "Adrienne Lafrance": 2, + "Aimee Picchi": 2, + "Alan Martin": 2, + "Alan Mislove": 2, + "Alex Davies": 2, + "Alex Pigman": 2, + "Alfred Ng": 2, + "Alisha Rahaman Sarkar": 2, + "Alistair Barr": 2, + "Alistair Charlton": 2, + "Allana Akhtar": 2, + "Alyssa Mercante": 2, + "Amanda Hoover": 2, + "Amnesty International": 2, + "Amrita Khalid": 2, + "Ana Gutiérrez": 2, + "Andrew Griffin": 2, + "Andrew Hobbs": 2, + "Andrew Liptak": 2, + "Andy Greenberg": 2, + "Angela Yang": 2, + "Anjana Samant": 2, + "Anne Hayes": 2, + "Annie Gilbertson": 2, + "Anonymous": 2, + "Anthony Cuthbertson": 2, + "Antoine Allen": 2, + "Aol Staff": 2, + "App Drivers and Couriers Union": 2, + "April Glaser": 2, + "April Rubin": 2, + "Ariana Tobin": 2, + "Ariel Bogle": 2, + "Ariel Herbert-Voss": 2, + "Arijeta Lajka": 2, + "Arvind Narayanan": 2, + "Arwa Mahdawi": 2, + "Avi Asher-Schapiro": 2, + "Ayang Macdonald": 2, + "Barbara Ortutay": 2, + "Beatrice Nolan": 2, + "Bella Cacciatore": 2, + "Ben Dickson": 2, + "Ben Ellery": 2, + "Beth Mole": 2, + "Bill Donahue": 2, + "Billy Perrigo": 2, + "Blake Montgomery": 2, + "Bob Herman": 2, + "Bradford Betz": 2, + "Brian Bushard": 2, + "Brian Fung": 2, + "Buster Hein": 2, + "CNN Business": 2, + "CNN Newsource": 2, + "Cailin Loesch": 2, + "Caitlin Kelly": 2, + "Carlos E. Perez": 2, + "Caroline Mimbs Nyce": 2, + "Caroline O'Donovan": 2, + "Casey Tolan": 2, + "Cathy O'Neil": 2, + "Chaim Gartenberg": 2, + "Charles Duhigg": 2, + "Charlie Warzel": 2, + "Chitra Ramaswamy": 2, + "Choe Sang-Hun": 2, + "Chris Isidore": 2, + "Chris Matyszczyk": 2, + "Chris Morris": 2, + "Chris Stokel-Walker": 2, + "Chris Vallance": 2, + "Christian De Looper": 2, + "Christopher Rosa": 2, + "Chuck Dinerstein": 2, + "Chuong Nguyen": 2, + "Ciaran Lyons": 2, + "Clare Duffy": 2, + "Colin Lecher": 2, + "Conor Cawley": 2, + "Dan Robitzski": 2, + "Daniel Croft": 2, + "Daniel E. Ho": 2, + "Danny Palmer": 2, + "David Koenig": 2, + "David Z. Morris": 2, + "De Elizabeth": 2, + "Debra Cassens Weiss": 2, + "Dell Cameron": 2, + "Department of Justice": 2, + "Donie O'Sullivan": 2, + "Dustin Volz": 2, + "Ed Yong": 2, + "Edward Helmore": 2, + "Elizabeth Culliford": 2, + "Emily Dreyfuss": 2, + "Emily Flitter": 2, + "Emmanuelle Saliba": 2, + "Eric Wallace": 2, + "Erin McCormick": 2, + "Ethan Baron": 2, + "Florian Tramèr": 2, + "France 24": 2, + "Garrett M. Graff": 2, + "Gary Craig": 2, + "Gene Maddaus": 2, + "George Joseph": 2, + "Georgia Wells": 2, + "Giulia Carbonaro": 2, + "Globe Staff": 2, + "Grégoire Sauvage": 2, + "Guardian staff and agencies": 2, + "Gustavo Henrique Ruffo": 2, + "Hamza Shaban": 2, + "Hannah Devlin": 2, + "Hannah Getahun": 2, + "Hannah Knowles": 2, + "Hany Farid": 2, + "Harry Suhartono": 2, + "Heather Chen": 2, + "Heather Stewart": 2, + "Henry Belot": 2, + "Hope Reese": 2, + "Igor Bonifacic": 2, + "India Today Web Desk": 2, + "Indo-Asian News Service": 2, + "Insurance Journal": 2, + "Inverse": 2, + "Isaac Chotiner": 2, + "Isaiah Richard": 2, + "Itech Post": 2, + "Ivan Mehta": 2, + "Jack Brewster": 2, + "Jack Smith Iv": 2, + "Jackie Lieberman": 2, + "Jacob Serebrin": 2, + "Jacob Snow": 2, + "Jake Offenhartz": 2, + "Jake Peterson": 2, + "James Clayton": 2, + "James Felton": 2, + "James Purtill": 2, + "James Zou": 2, + "Janus Rose": 2, + "Jarni Blakkarly": 2, + "Jazper Lu": 2, + "Jean Mackenzie": 2, + "Jeff Kao": 2, + "Jennifer Cowan": 2, + "Jennifer L. Doleac": 2, + "Jennifer Langston": 2, + "Jeremy B. Merrill": 2, + "Jeremy Kahn": 2, + "Jess Weatherbed": 2, + "Jessica Guynn": 2, + "Joan Lowy": 2, + "Jodi Kantor": 2, + "Joe Kukura": 2, + "Joe Patrice": 2, + "Joe Vaccarelli": 2, + "Joel Hruska": 2, + "John Glenday": 2, + "John Pring": 2, + "John Simerman": 2, + "John West": 2, + "Jonathan Limehouse": 2, + "Jonathan Vanian": 2, + "Joseph Hincks": 2, + "Joseph Menn": 2, + "Joshua Bote": 2, + "Joshua Thurston": 2, + "Julia Carrie Wong": 2, + "Julian E. Barnes": 2, + "Julie Johnsson": 2, + "Justin Pritchard": 2, + "Jyoti Mann": 2, + "Kalhan Rosenblatt": 2, + "Karin Kovary Solymos": 2, + "Kate Proctor": 2, + "Kath Xu": 2, + "Katherine Lee": 2, + "Kathleen Magramo": 2, + "Katie Notopoulos": 2, + "Keith Wagstaff": 2, + "Kelvin Chan": 2, + "Keoni Everington": 2, + "Khamila Mulia": 2, + "Kristian Lum": 2, + "Kyle Barr": 2, + "Kylie Cheung": 2, + "Lara Pearce": 2, + "Larry Alton": 2, + "Latanya Sweeney": 2, + "Lauren Rhue": 2, + "Lauren Sforza": 2, + "Leena Nasir": 2, + "Leonardo Nicoletti": 2, + "Lewin Day": 2, + "Li Tao": 2, + "Liam Reilly": 2, + "Lily Hay Newman": 2, + "Linette Lopez": 2, + "Lorenzo Arvanitis": 2, + "Lucas Manfredi": 2, + "Lydia Horne": 2, + "Maia Szalavitz": 2, + "Marco Della Cava": 2, + "Marie Solis": 2, + "Mariella Moon": 2, + "Marissa Gerchick": 2, + "Mark Frauenfelder": 2, + "Mark Sweney": 2, + "Mary Papenfuss": 2, + "Matt Burgess": 2, + "Matt Simon": 2, + "Matt Wille": 2, + "Matthew Butterick": 2, + "Matthew Guariglia": 2, + "Matthew Humphries": 2, + "Matthew Jagielski": 2, + "Matthew Phelan": 2, + "Matthias Bastian": 2, + "Maya Oppenheim": 2, + "McKenzie Sadeghi": 2, + "Megan Farokhmanesh": 2, + "Megan Hickey": 2, + "Melia Robinson": 2, + "Melissa del Bosque": 2, + "Michael L. Diamond": 2, + "Michael Savage": 2, + "Michael Tarm": 2, + "Michelle Butterfield": 2, + "Mick Akers": 2, + "Microsoft": 2, + "Mike Masnick": 2, + "Mike Melanson": 2, + "Mike Spector": 2, + "Mirna Alsharif": 2, + "Mitchell Clark": 2, + "Moinak Pal": 2, + "Molly Liebergall": 2, + "Moohita Kaur Garg": 2, + "Morgan Sung": 2, + "Muhammad Ali": 2, + "Nadine Freischlad": 2, + "Nam Hyun-woo": 2, + "Natalie O'Neill": 2, + "Natasha Duarte": 2, + "Nathaniel Gleicher": 2, + "Neil Vigdor": 2, + "Niamh Ancell": 2, + "Nicholas Diakopoulos": 2, + "Nicholas Lezard": 2, + "Nick Evershed": 2, + "Nick Statt": 2, + "Nicole Clark": 2, + "Nikki Williams": 2, + "Nilay Patel": 2, + "Nilesh Christopher": 2, + "Niniek Karmini": 2, + "Noam Shemtov": 2, + "Onur Demirkol": 2, + "Paige Skinner": 2, + "Paul Egan": 2, + "Paul Lukas": 2, + "Pete Bigelow": 2, + "Philip Marcelo": 2, + "Phillip Tracy": 2, + "Piotr Sapiezynski": 2, + "Pocharapon Neammanee": 2, + "Political Reporter": 2, + "RT Staff": 2, + "Rachel Kraus": 2, + "Rachel Lerman": 2, + "Ramishah Maruf": 2, + "Rasha Ali": 2, + "Raymond Wong": 2, + "Rebecca Hill": 2, + "Rene Marsh": 2, + "Rita Liao": 2, + "Rob Thubron": 2, + "Rob Waugh": 2, + "Romy Ellenbogen": 2, + "Russ Mitchell": 2, + "Ryan Daws": 2, + "Ryan McNeal": 2, + "Sally Ho": 2, + "Sapna Maheshwari": 2, + "Sara Ashley O'Brien": 2, + "Sarah Frier": 2, + "Sarah Perez": 2, + "Sayash Kapoor": 2, + "Scott Nover": 2, + "Sean B. McGregor": 2, + "Sean McGregor": 2, + "Sean Szymkowski": 2, + "Sebastien Bell": 2, + "Shannon Thaler": 2, + "Shanti Das": 2, + "Sharon Adarlo": 2, + "Sharon Goldman": 2, + "Shira Ovide": 2, + "Shivali Best": 2, + "Shona Ghosh": 2, + "Sigal Samuel": 2, + "Siladitya Ray": 2, + "Simon Willison": 2, + "Soo Youn": 2, + "Sophia Waterfield": 2, + "Sophie Beiers": 2, + "Sophie Nieto-Munoz": 2, + "Staff": 2, + "Stan Schroeder": 2, + "Steph Maj Swanson": 2, + "Stephanie Wykstra": 2, + "Stephen Kafeero": 2, + "Steve Crowe": 2, + "Steve Stecklow": 2, + "Steven Loveday": 2, + "Steven Musil": 2, + "Stuff": 2, + "Tarak Shah": 2, + "Tate Ryan-Mosley": 2, + "Tatum Hunter": 2, + "Taylor Donovan Barnett": 2, + "Tech Transparency Project": 2, + "The Express Tribune": 2, + "The Hindu Bureau": 2, + "The Independent": 2, + "Tim McNicholas": 2, + "Tobi Jegede": 2, + "Tony Ho Tran": 2, + "Tony Kovaleski": 2, + "Tony Tran": 2, + "Turkish Minute": 2, + "Varsha Bansal": 2, + "Victoria Woollaston": 2, + "Vittoria Elliott": 2, + "Wall Street Journal": 2, + "Will Bedingfield": 2, + "Will McCurdy": 2, + "William Gavin": 2, + "William Hoffman": 2, + "William Isaac": 2, + "Yiwen Lu": 2, + "Yuval Abraham": 2, + "bne IntelliNews": 2, + "천호성": 2, + "@BeauHD": 1, + "@DearKick": 1, + "@FSD_in_6m": 1, + "@Robert_AIZI": 1, + "@Waqas": 1, + "@an_open_mind": 1, + "@kawazacky": 1, + "@msravi": 1, + "@ohgustie": 1, + "A.J. Perez": 1, + "AAP": 1, + "ABC 7 Eyewitness News": 1, + "ABC Australia": 1, + "AI Addict": 1, + "AP/Reuters": 1, + "Aaditi Lele": 1, + "Aakar Patel": 1, + "Aamir Sheikh": 1, + "Aaron Blake": 1, + "Aaron Gordon": 1, + "Aaron Gregg": 1, + "Aaron Holmes": 1, + "Aaron Huff": 1, + "Aaron Katersky": 1, + "Aaron Krolik": 1, + "Aaron Mak": 1, + "Aaron Mamiit": 1, + "Aaron Moss": 1, + "Aaron Smith": 1, + "Aaron Tilley": 1, + "Aashish Kumar Shrivastava": 1, + "Aatif Sulleyman": 1, + "Abbie Richards": 1, + "Abby Monteil": 1, + "Abdul Moeed": 1, + "Abdullah": 1, + "Abeba Birhane": 1, + "Abigail Rubenstein": 1, + "Abrar Al-Heeti": 1, + "Abubakar Abid": 1, + "Abubakar Idris": 1, + "Adam Carlson": 1, + "Adam Downer": 1, + "Adam Garfinkle": 1, + "Adam Hadhazy": 1, + "Adam Healy": 1, + "Adam Kalai": 1, + "Adam Lam": 1, + "Adam Rawnsley": 1, + "Adam Roberts": 1, + "Adam Rowe": 1, + "Aditi Sen": 1, + "Aditya Bahl": 1, + "Aditya Kalra": 1, + "Adrian Hopgood": 1, + "Adrian Morrow": 1, + "Adrian Weckler": 1, + "Adriana Lee": 1, + "Agence France Presse": 1, + "Aicha El Hammar Castano": 1, + "Aiden Pink": 1, + "Aimee Lucido": 1, + "Aimee Ortiz": 1, + "Aisha Malik": 1, + "Akash Pandey": 1, + "Aki Anastasiou": 1, + "Aki Peritz": 1, + "Al-Monitor Staff": 1, + "Alain Sherter": 1, + "Alan Boyle": 1, + "Alan Friedman": 1, + "Alastair Gale": 1, + "Albert Tait": 1, + "Alberto Luperon": 1, + "Alberto Romero": 1, + "Aleks Phillips": 1, + "Alessandra Kellermann": 1, + "Alessandro Mascellino": 1, + "Alex Arger": 1, + "Alex Brenninkmeijer": 1, + "Alex Cadier": 1, + "Alex Carr": 1, + "Alex Clark": 1, + "Alex Farber": 1, + "Alex Heath": 1, + "Alex Isenstadt": 1, + "Alex Johnson": 1, + "Alex Kaplan": 1, + "Alex Kierstein": 1, + "Alex Lockie": 1, + "Alex Mitchell": 1, + "Alex Moersen": 1, + "Alex Portée": 1, + "Alex Tsiaoussidis": 1, + "Alex Wawro": 1, + "Alexa Cimino": 1, + "Alexa Corse": 1, + "Alexa Hagerty": 1, + "Alexander Hanff": 1, + "Alexander J Martin": 1, + "Alexander Martin": 1, + "Alexander Puutio": 1, + "Alexandra Blogier": 1, + "Alexandra Kravariti": 1, + "Alexandra Marquez": 1, + "Alexandra Miller": 1, + "Alexandra S. Levine": 1, + "Alexei Alexis": 1, + "Alexis Haden": 1, + "Alexis Kleinman": 1, + "Alexis Madrigal": 1, + "Ali A. Jessani": 1, + "Ali Breland": 1, + "Ali Swenson": 1, + "Alice Hearing": 1, + "Alice Kohn": 1, + "Alice Milliken": 1, + "Alice Richardson": 1, + "Alice Wright": 1, + "Alicia Valenski": 1, + "Alina Oprea": 1, + "Alisha Green": 1, + "Alison Cutler": 1, + "Alison Sider": 1, + "Alix Langone": 1, + "Alizeh Kohari": 1, + "Allison Koenecke": 1, + "Allison Levitsky": 1, + "Alma Fabiani": 1, + "Alvin E. Roth": 1, + "Alyce McFadden": 1, + "Alyse Stanley": 1, + "Alyssa Braithwaite": 1, + "Alyssa Goard": 1, + "Alyssa Guzman": 1, + "Alyssa Newcomb": 1, + "Alyssa Rinelli": 1, + "Amal Attar-Guzman": 1, + "Amanda Claypool": 1, + "Amanda Cochran": 1, + "Amanda Geffner": 1, + "Amanda Kooser": 1, + "Amanda Lewis": 1, + "Amanda Schupak": 1, + "Amanda Shaw": 1, + "Amaya Ross": 1, + "Amelia Butterly": 1, + "Amelia Gentleman": 1, + "Amelia Lucas": 1, + "Amelia McGuire": 1, + "Amelia Mcdonell-Parry": 1, + "Amiah Taylor": 1, + "Amit Katwala": 1, + "Amiya Moretta": 1, + "Amrutha Pagad": 1, + "Amy Beth Hanson": 1, + "Amy Gardner": 1, + "Amy Kraft": 1, + "Amy Martinez": 1, + "Ana María Arévalo Gosen": 1, + "Anagha Srikanth": 1, + "Anand Tamboli": 1, + "Ananya Gairola": 1, + "Andrea Bernstein": 1, + "Andrea Blanco": 1, + "Andreo Calonzo": 1, + "Andrew Buncombe": 1, + "Andrew Court": 1, + "Andrew Deck": 1, + "Andrew Desiderio": 1, + "Andrew Guthrie Ferguson": 1, + "Andrew Hutchinson": 1, + "Andrew Longhi": 1, + "Andrew Nam": 1, + "Andrew Orlowski": 1, + "Andrew Papachristos": 1, + "Andrew Schlaikjer": 1, + "Andrew Smith": 1, + "Andrew Tangel": 1, + "Andrew Thompson": 1, + "Andrew Trotman": 1, + "Andrew Van Dam": 1, + "Andrew Wilks": 1, + "Andy Brown": 1, + "Andy Miller": 1, + "Andy Mukherjee": 1, + "Andy Nghiem": 1, + "Andy Pasztor": 1, + "Andy Verity": 1, + "Angel Saunders": 1, + "Angela B.": 1, + "Angela Lai": 1, + "Angelica Mari": 1, + "Angry Asian Man": 1, + "Angus Morrison": 1, + "Ani": 1, + "Ani Nenkova": 1, + "Anisa Harrasy": 1, + "Anjana Nair": 1, + "Ankita Garg": 1, + "Anna Andrews": 1, + "Anna Bahney": 1, + "Anna Houlahan": 1, + "Anna Iovine": 1, + "Anna Kutz": 1, + "Anna Loup": 1, + "Anna Lucente Sterling": 1, + "Anna Tong": 1, + "Annalyn Kurtz": 1, + "Anne Marie Lee": 1, + "Anneli L. Tostar": 1, + "Annie Minoff": 1, + "Annie Palmer For Dailymail.Com": 1, + "Anthony G. Attrino": 1, + "Antoine Gara": 1, + "Anton Ekker": 1, + "Antonio Andrés-Pueyo": 1, + "Antonio Madeira": 1, + "Antonio Pequeño IV": 1, + "Antonio Villas-Boas": 1, + "Anubhav Sharma": 1, + "Anumita Kaur": 1, + "Anupam Dikhit": 1, + "Anupama Airy": 1, + "Anurag Baruah": 1, + "Anushe Fawaz": 1, + "Anushree Hede": 1, + "Aparna Iyer": 1, + "Apple On Amazon": 1, + "Archie Hamilton": 1, + "Archie Mitchell": 1, + "Ardi Wirdana": 1, + "Arfa Javaid": 1, + "Ariana Baio": 1, + "Arianna Evers": 1, + "Aric Jenkins": 1, + "Arielle Pardes": 1, + "Arif Perdana": 1, + "Aris Folley": 1, + "Arjun Kharpal": 1, + "Armin Ronacher": 1, + "Armin Rosen": 1, + "Artemis Moshtaghian": 1, + "Arthur Holland Michel": 1, + "Arun Ramesh": 1, + "Aryan Prakash": 1, + "Aryn Plax": 1, + "Ashleigh Hollowell": 1, + "Ashley Bardhan": 1, + "Ashley Capoot": 1, + "Ashley Collman": 1, + "Ashley Halsey III": 1, + "Ashley Hume": 1, + "Ashley May": 1, + "Ashley McBride": 1, + "Ashley Rodriguez": 1, + "Ashlie D. Stevens": 1, + "Ashok Ramprasad": 1, + "Ashutosh Tripathi": 1, + "Asia Grace": 1, + "Aspyhackr": 1, + "Atlanta Journal-Constitution": 1, + "Audie Cornish": 1, + "Audrey Ash": 1, + "Augustine Fou": 1, + "Austin Mullen": 1, + "Australian Associated Press": 1, + "Ava Mutchler": 1, + "Avaaz": 1, + "Averi Kremposky": 1, + "Avinash A": 1, + "Aye Min Thant": 1, + "Aylin Caliskan": 1, + "Aylin Elci": 1, + "Ayushman Kaul": 1, + "B J Robertson": 1, + "BR Data": 1, + "Ban Barkawi": 1, + "Barbara Liston": 1, + "Barbara S. Peterson": 1, + "Barbara Zmušková": 1, + "Barry Sookman": 1, + "Basileal Imana": 1, + "Bayu Anggorojati": 1, + "Bbc Trending": 1, + "Beatrice Verhoeven": 1, + "Becca Smouse": 1, + "Becky Bargh": 1, + "Beebom": 1, + "Beijing Newsroom": 1, + "Bel Air Association": 1, + "Belga News Agency": 1, + "Belinda Grant Geary": 1, + "Ben Beaumont-Thomas": 1, + "Ben Brumfield": 1, + "Ben Chu": 1, + "Ben Cohen": 1, + "Ben Cost": 1, + "Ben Dubow": 1, + "Ben Eltham": 1, + "Ben Geman": 1, + "Ben Guarino": 1, + "Ben Kaufman": 1, + "Ben Lovejoy": 1, + "Ben Nimmo": 1, + "Ben Popper": 1, + "Ben Sales": 1, + "Ben Strauss": 1, + "Ben Sullivan": 1, + "Ben Travis": 1, + "Ben Warwick": 1, + "Ben Woods": 1, + "Ben Yelin": 1, + "Benedict Smith": 1, + "Benita Kolovos": 1, + "Benjamin Fearnow": 1, + "Benjamin Goggin": 1, + "Benjamin Haas": 1, + "Benjamin Lindsay": 1, + "Benjamin Perkin": 1, + "Benjamin Schneider": 1, + "Benjamin Snyder": 1, + "Benjamin Wallace-Wells": 1, + "Berenice Baker": 1, + "Berhan Taye": 1, + "Bernie Woodall": 1, + "Beth Greenfield": 1, + "Bethan McKernan": 1, + "Bethan Sexton": 1, + "Bethany Hallam": 1, + "Bethany White": 1, + "Bettina Büchel": 1, + "Bev Stephans": 1, + "Bharat Sharma": 1, + "Bhavya Sukheja": 1, + "Bianca Bosker": 1, + "Bianca Britton": 1, + "Bigger Law Firm Magazine": 1, + "Bijan Stephen": 1, + "Bill Cash": 1, + "Bill Christensen": 1, + "Bill Snyder": 1, + "Biman Mukherji": 1, + "Bindu Bansinath": 1, + "Biometrics Commissioner": 1, + "Björn ten Seldam": 1, + "Black Voices Editor": 1, + "Blair Hanley Frank": 1, + "Blake Harper": 1, + "Blake Lew-Merwin": 1, + "Bobbie Johnson": 1, + "Bogdan Popa": 1, + "Boston Herald": 1, + "Botego Inc": 1, + "Brad Anderson": 1, + "Braden Bjella": 1, + "Bradley Hope": 1, + "Bradley Jolly": 1, + "Brandon Turkus": 1, + "Brayden Lindrea": 1, + "Breanna Barraclough": 1, + "Bree Fowler": 1, + "Breeze Liu": 1, + "Brenda Medina": 1, + "Brenden Gallagher": 1, + "Brett Wilkins": 1, + "Brian Fraga": 1, + "Brian Lisi": 1, + "Brian Melley": 1, + "Brian Merchant": 1, + "Brian Resnick": 1, + "Brian Welk": 1, + "Brian Whitton": 1, + "Brianna Sacks": 1, + "Bridget Chavez": 1, + "Bridget McArthur": 1, + "Brie Barbee": 1, + "Brie Stimson": 1, + "Brieanna J Frank": 1, + "Brinda A. Thomas": 1, + "Brittny Mejia": 1, + "Bruce Brown": 1, + "Bruce Handy": 1, + "Bruce Schneier": 1, + "Bryan Clark": 1, + "Bryan Logan": 1, + "Bálint Miklós": 1 + }, + "language": { + "en": 3838, + "de": 6, + "es": 5, + "fr": 4, + "it": 4, + "th": 4, + "vi": 4, + "ko": 2, + "hi": 1, + "nl": 1, + "pt": 1, + "zh-CN": 1 + }, + "namespaces": { + "CSETv1_Annotator-1": 1898, + "CSETv1": 1617, + "CSETv0": 1217, + "CSETv1_Annotator-2": 954, + "CSETv1_Annotator-3": 436, + "GMF": 402 + }, + "submitters": { + "Daniel Atherton": 1349, + "Anonymous": 862, + "Khoa Lam": 416, + "Roman Yampolskiy": 372, + "Catherine Olsson": 208, + "Kate Perkins": 139, + "Ingrid Dickinson (CSET)": 73, + "Thomas Giallella (CSET)": 68, + "Sean McGregor": 42, + "Patrick Hall": 33, + "Srishti Khemka (CSET)": 33, + "Roman Lutz": 26, + "Devon Colmer (CSET)": 22, + "CSET annotators": 21, + "Sonali Pednekar (CSET)": 21, + "Logan B": 20, + "Collin Starkweather": 15, + "Janet Schwartz": 14, + "Luna McNulty": 11, + "Kevin Paeth": 10, + "Nickie Demakos": 9, + "Andrew Hundt": 7, + "Frank Guo": 7, + "Ayrton San Joaquin": 6, + "Neama Dadkhahnikoo": 6, + "Arthit Suriyawongkul": 5, + "Andrew Gamino-Cheong": 4, + "Michael Simon (ForHumanity)": 4, + "Viviana Rivera": 4, + "Catharina Doria": 3, + "Cesar Varela": 3, + "Helen Zhu": 3, + "Sundar Narayanan (ForHumanity)": 3, + "Alice RT (ForHumanity)": 2, + "Charlie Pownall": 2, + "Effy Elden": 2, + "Fionntan O'Donnell": 2, + "Irina Borisova King": 2, + "Maud Stiernet (ForHumanity)": 2, + "Nathan Butters": 2, + "Nik Martelaro": 2, + "Octavia Occident": 2, + "Sundar Narayanan": 2, + "William Schindhelm Georg": 2, + "kepa": 2, + "reubot": 2, + "21five": 1, + "Alexis Monks": 1, + "Alice Villano": 1, + "Antonio Buffelli (ForHumanity)": 1, + "Ashley Casovan": 1, + "Beatrice Moissinac": 1, + "Carol Anderson (ForHumanity)": 1, + "Charlie Wang": 1, + "Christopher Maratos": 1, + "Corey Abshire": 1, + "Eric Horvitz": 1, + "Fabio Xie": 1, + "Fion Lee-Madan (Fairly AI)": 1, + "Gerry Chng": 1, + "Inbal - For Humanity": 1, + "Inbal - ForHumanity": 1, + "Isabelle": 1, + "Joanna (ForHumanity)": 1, + "Jodi Masters-Gonzales": 1, + "Johann Hanssen (ForHumanity)": 1, + "Joshua Poore": 1, + "K M": 1, + "Karson Elmgren": 1, + "Leon Overweel": 1, + "Lilianna Smith": 1, + "Lilly Ryan": 1, + "Madison Malone Kircher": 1, + "Matteo Dora": 1, + "Mitt Regan Jr. (BABL AI)": 1, + "Natalia DLC": 1, + "Nga Than": 1, + "Nick Stockton": 1, + "Parul Pandey": 1, + "Ryan Carrier (ForHumanity)": 1, + "Samuel Curtis": 1, + "Scott Cambo": 1, + "Subhabrata Majumdar": 1, + "Subho Majumdar": 1, + "TNT": 1, + "Tuoi Tran": 1, + "Wiebke Hutiri": 1, + "Yukti Handa": 1, + "kepae": 1 + }, + "incident_id": { + "1": 14, + "2": 17, + "3": 19, + "4": 25, + "5": 12, + "6": 28, + "7": 6, + "8": 10, + "9": 7, + "10": 10, + "11": 15, + "12": 1, + "13": 9, + "14": 7, + "15": 24, + "16": 24, + "17": 22, + "18": 11, + "19": 27, + "20": 22, + "22": 22, + "23": 24, + "24": 27, + "25": 11, + "26": 24, + "27": 27, + "28": 30, + "29": 2, + "30": 28, + "31": 29, + "32": 21, + "33": 4, + "34": 35, + "35": 20, + "36": 25, + "37": 33, + "38": 11, + "39": 29, + "40": 22, + "41": 28, + "42": 1, + "43": 4, + "44": 1, + "45": 29, + "46": 6, + "47": 9, + "48": 22, + "49": 10, + "50": 24, + "51": 27, + "52": 29, + "53": 18, + "54": 13, + "55": 16, + "56": 7, + "57": 39, + "58": 5, + "59": 10, + "60": 23, + "61": 1, + "63": 1, + "64": 1, + "65": 1, + "66": 16, + "67": 24, + "68": 30, + "69": 12, + "70": 4, + "71": 28, + "72": 26, + "73": 8, + "74": 11, + "75": 1, + "76": 1, + "77": 5, + "78": 1, + "79": 3, + "80": 2, + "81": 1, + "82": 1, + "83": 1, + "84": 1, + "86": 2, + "87": 1, + "88": 2, + "89": 1, + "91": 5, + "92": 6, + "93": 4, + "94": 2, + "95": 4, + "96": 1, + "97": 1, + "98": 1, + "99": 1, + "100": 1, + "101": 6, + "102": 2, + "103": 5, + "104": 1, + "105": 1, + "106": 13, + "107": 2, + "108": 3, + "109": 1, + "110": 1, + "111": 5, + "112": 11, + "113": 1, + "114": 1, + "115": 3, + "116": 2, + "117": 4, + "118": 3, + "119": 4, + "120": 1, + "121": 4, + "122": 1, + "123": 4, + "124": 7, + "125": 3, + "126": 4, + "127": 12, + "128": 2, + "129": 1, + "131": 2, + "132": 1, + "133": 1, + "134": 2, + "135": 2, + "136": 1, + "137": 1, + "138": 6, + "139": 2, + "140": 1, + "141": 2, + "142": 1, + "143": 1, + "144": 6, + "145": 3, + "146": 3, + "147": 2, + "148": 1, + "149": 4, + "150": 3, + "151": 10, + "152": 2, + "153": 3, + "154": 1, + "155": 2, + "156": 2, + "157": 1, + "158": 1, + "160": 2, + "161": 3, + "162": 1, + "163": 2, + "164": 1, + "165": 2, + "166": 2, + "167": 1, + "168": 2, + "169": 5, + "170": 3, + "171": 1, + "172": 3, + "173": 1, + "174": 4, + "175": 5, + "176": 1, + "177": 5, + "178": 8, + "179": 3, + "180": 3, + "181": 2, + "182": 2, + "183": 6, + "184": 3, + "185": 4, + "186": 7, + "187": 3, + "188": 4, + "189": 6, + "190": 4, + "191": 2, + "192": 2, + "193": 1, + "194": 1, + "195": 12, + "196": 5, + "197": 4, + "198": 9, + "199": 7, + "200": 1, + "201": 2, + "202": 5, + "203": 3, + "204": 4, + "205": 4, + "206": 4, + "207": 4, + "208": 8, + "209": 5, + "210": 3, + "211": 7, + "212": 4, + "213": 5, + "214": 1, + "215": 1, + "216": 5, + "217": 2, + "218": 3, + "219": 1, + "220": 4, + "221": 2, + "222": 1, + "223": 1, + "224": 1, + "225": 2, + "226": 3, + "227": 3, + "228": 1, + "229": 2, + "230": 1, + "231": 4, + "232": 4, + "233": 1, + "234": 2, + "235": 1, + "236": 1, + "238": 1, + "239": 1, + "240": 5, + "241": 5, + "242": 1, + "243": 2, + "244": 1, + "245": 1, + "246": 2, + "248": 2, + "249": 2, + "250": 1, + "251": 3, + "252": 1, + "253": 4, + "254": 2, + "255": 9, + "256": 1, + "257": 4, + "258": 2, + "259": 2, + "260": 2, + "261": 8, + "262": 4, + "263": 1, + "264": 1, + "265": 2, + "266": 8, + "267": 10, + "268": 4, + "270": 1, + "271": 3, + "272": 3, + "273": 1, + "274": 2, + "275": 2, + "276": 1, + "277": 1, + "278": 3, + "279": 3, + "280": 2, + "281": 3, + "282": 3, + "283": 1, + "284": 6, + "285": 1, + "286": 2, + "288": 4, + "289": 2, + "290": 3, + "291": 6, + "292": 3, + "293": 7, + "294": 4, + "295": 5, + "296": 3, + "297": 3, + "298": 1, + "299": 1, + "300": 2, + "301": 1, + "302": 1, + "303": 2, + "304": 1, + "305": 2, + "306": 3, + "307": 1, + "308": 2, + "309": 4, + "310": 8, + "311": 2, + "312": 4, + "313": 2, + "314": 1, + "315": 1, + "316": 1, + "317": 1, + "318": 2, + "319": 5, + "320": 4, + "321": 14, + "322": 2, + "323": 4, + "324": 6, + "325": 2, + "326": 3, + "327": 1, + "328": 2, + "329": 1, + "330": 1, + "331": 2, + "332": 4, + "333": 6, + "334": 2, + "335": 8, + "336": 4, + "337": 9, + "339": 14, + "340": 1, + "341": 5, + "343": 2, + "344": 1, + "345": 1, + "346": 5, + "347": 3, + "348": 3, + "349": 2, + "350": 2, + "351": 1, + "352": 4, + "353": 4, + "354": 5, + "355": 4, + "356": 2, + "357": 3, + "358": 1, + "359": 1, + "360": 3, + "361": 1, + "362": 1, + "363": 1, + "364": 1, + "366": 1, + "367": 1, + "368": 10, + "369": 1, + "370": 1, + "371": 3, + "372": 3, + "373": 12, + "374": 8, + "375": 3, + "376": 5, + "377": 1, + "378": 2, + "379": 2, + "380": 5, + "381": 1, + "382": 1, + "383": 2, + "384": 2, + "385": 6, + "386": 3, + "387": 1, + "388": 1, + "389": 2, + "390": 1, + "391": 2, + "392": 2, + "393": 1, + "394": 2, + "395": 4, + "396": 1, + "397": 2, + "398": 3, + "399": 3, + "400": 1, + "401": 4, + "402": 1, + "403": 2, + "404": 2, + "405": 2, + "406": 1, + "407": 1, + "408": 1, + "409": 3, + "410": 1, + "411": 1, + "412": 4, + "413": 2, + "414": 1, + "415": 5, + "416": 3, + "417": 4, + "418": 3, + "419": 3, + "420": 11, + "421": 9, + "422": 1, + "423": 5, + "424": 4, + "425": 2, + "426": 1, + "427": 3, + "428": 3, + "429": 4, + "430": 21, + "431": 3, + "432": 1, + "433": 10, + "434": 8, + "435": 4, + "436": 9, + "437": 4, + "438": 3, + "439": 3, + "440": 6, + "441": 5, + "443": 25, + "444": 1, + "445": 4, + "446": 5, + "447": 2, + "448": 1, + "449": 4, + "450": 8, + "451": 5, + "452": 2, + "453": 1, + "454": 2, + "455": 7, + "456": 7, + "457": 3, + "458": 1, + "459": 2, + "460": 2, + "461": 4, + "462": 5, + "463": 3, + "464": 4, + "465": 1, + "466": 7, + "467": 14, + "468": 4, + "469": 3, + "470": 2, + "471": 8, + "472": 1, + "473": 1, + "474": 1, + "475": 5, + "476": 3, + "477": 5, + "478": 13, + "479": 4, + "480": 15, + "481": 6, + "482": 20, + "483": 1, + "484": 4, + "485": 1, + "486": 5, + "487": 3, + "488": 1, + "489": 1, + "490": 3, + "491": 1, + "492": 7, + "493": 1, + "494": 5, + "495": 2, + "496": 2, + "497": 2, + "498": 2, + "499": 11, + "500": 1, + "501": 1, + "502": 3, + "503": 7, + "504": 1, + "505": 7, + "506": 2, + "507": 2, + "508": 4, + "509": 2, + "510": 5, + "511": 2, + "513": 5, + "514": 1, + "515": 2, + "516": 2, + "517": 2, + "518": 1, + "519": 1, + "520": 2, + "521": 1, + "522": 1, + "523": 1, + "524": 1, + "525": 3, + "526": 2, + "527": 2, + "528": 2, + "529": 3, + "530": 2, + "531": 1, + "532": 1, + "533": 2, + "534": 2, + "535": 2, + "536": 2, + "537": 2, + "538": 5, + "539": 1, + "540": 5, + "541": 58, + "543": 17, + "544": 22, + "545": 46, + "546": 3, + "547": 2, + "548": 1, + "549": 1, + "550": 2, + "551": 2, + "552": 1, + "553": 2, + "554": 1, + "555": 1, + "556": 4, + "557": 4, + "558": 3, + "559": 2, + "560": 1, + "561": 3, + "562": 1, + "563": 2, + "564": 1, + "565": 4, + "566": 1, + "567": 1, + "568": 2, + "569": 2, + "570": 1, + "571": 1, + "572": 1, + "573": 11, + "574": 1, + "575": 1, + "576": 1, + "577": 1, + "578": 1, + "579": 1, + "580": 1, + "581": 1, + "582": 1, + "583": 1, + "584": 1, + "585": 3, + "586": 1, + "587": 1, + "588": 1, + "589": 1, + "590": 1, + "591": 2, + "592": 3, + "593": 1, + "594": 1, + "595": 1, + "596": 1, + "597": 44, + "598": 1, + "599": 2, + "600": 1, + "601": 10, + "602": 7, + "603": 1, + "604": 7, + "605": 10, + "606": 15, + "607": 1, + "608": 21, + "609": 2, + "610": 10, + "611": 13, + "612": 17, + "613": 1, + "614": 1, + "615": 4, + "616": 43, + "617": 2, + "618": 3, + "619": 14, + "620": 5, + "621": 3, + "622": 6, + "623": 12, + "624": 18, + "625": 5, + "626": 30, + "627": 8, + "628": 14, + "629": 2, + "630": 3, + "631": 2, + "632": 31, + "633": 17, + "634": 21, + "635": 1, + "636": 5, + "638": 17, + "639": 4, + "640": 3, + "641": 12, + "642": 5, + "643": 6, + "644": 6, + "645": 35, + "646": 1, + "647": 2, + "648": 2, + "649": 1, + "650": 1, + "651": 2, + "652": 1, + "653": 1, + "654": 1, + "655": 1, + "656": 3, + "657": 1, + "658": 3, + "659": 3, + "660": 1, + "661": 1, + "662": 2, + "663": 1, + "664": 1, + "665": 1, + "666": 1, + "667": 1, + "668": 1, + "669": 1, + "670": 2, + "671": 1, + "672": 7, + "673": 1, + "674": 1, + "675": 4, + "676": 2, + "677": 1, + "678": 1, + "679": 1, + "680": 2, + "681": 1, + "682": 1, + "683": 2, + "684": 1, + "685": 1, + "686": 2, + "687": 1, + "688": 14, + "689": 4, + "690": 4, + "691": 2, + "692": 2, + "693": 7, + "694": 1, + "695": 1, + "696": 1, + "697": 1, + "698": 1, + "699": 2, + "700": 2, + "701": 1, + "702": 1, + "703": 1, + "704": 2, + "705": 2, + "706": 1, + "707": 1, + "708": 1, + "709": 1, + "710": 1, + "711": 2, + "712": 2, + "713": 1, + "714": 2, + "715": 1, + "716": 1, + "717": 1, + "718": 1, + "719": 1, + "720": 1, + "721": 1, + "722": 1, + "723": 2, + "724": 1, + "725": 1, + "726": 4, + "727": 1, + "728": 1, + "729": 1, + "730": 1, + "731": 1, + "732": 1, + "733": 2, + "734": 1, + "735": 1, + "736": 2, + "737": 2, + "738": 5, + "739": 2, + "740": 1, + "741": 1, + "742": 1, + "743": 1, + "744": 1, + "745": 2, + "746": 2, + "747": 1, + "748": 1, + "749": 1, + "750": 1, + "751": 2, + "752": 1, + "753": 1, + "754": 1, + "755": 2, + "756": 3, + "757": 1, + "758": 1, + "759": 1, + "760": 2, + "761": 1, + "762": 3, + "763": 1, + "764": 4, + "765": 5, + "766": 2, + "767": 2, + "768": 1, + "769": 2, + "770": 3, + "771": 1, + "772": 2, + "773": 1, + "774": 2, + "775": 1, + "776": 1, + "777": 7, + "778": 2, + "779": 1, + "780": 1, + "781": 1, + "782": 1, + "783": 1, + "784": 1, + "785": 1, + "786": 2, + "787": 2, + "788": 1, + "789": 1, + "790": 2, + "791": 1, + "792": 10, + "793": 2, + "794": 2, + "795": 1, + "796": 3, + "797": 5, + "798": 1, + "799": 2, + "800": 1, + "801": 1, + "802": 1, + "803": 1, + "804": 5, + "805": 10, + "806": 1, + "807": 7, + "808": 1, + "809": 1, + "810": 1, + "811": 4, + "812": 5, + "813": 2, + "814": 9, + "815": 1, + "816": 1, + "817": 8, + "818": 2, + "819": 1, + "820": 5, + "821": 1, + "822": 2, + "823": 3, + "824": 11, + "825": 2, + "826": 34, + "827": 1, + "828": 3, + "829": 1, + "830": 1, + "831": 1, + "832": 2, + "833": 2, + "834": 2, + "835": 1, + "836": 1, + "837": 1, + "838": 1, + "839": 20, + "840": 1, + "841": 3, + "842": 16, + "843": 2, + "844": 3, + "845": 1, + "846": 1, + "847": 1, + "848": 1, + "849": 1, + "850": 1 + }, + "source_domain": { + "theguardian.com": 148, + "nytimes.com": 124, + "theverge.com": 103, + "washingtonpost.com": 94, + "wired.com": 66, + "bbc.com": 58, + "reuters.com": 54, + "arstechnica.com": 52, + "futurism.com": 52, + "vice.com": 52, + "businessinsider.com": 46, + "forbes.com": 40, + "apnews.com": 39, + "dailymail.co.uk": 39, + "gizmodo.com": 36, + "wsj.com": 36, + "telegraph.co.uk": 33, + "cnn.com": 32, + "cbsnews.com": 31, + "independent.co.uk": 31, + "mashable.com": 30, + "nbcnews.com": 30, + "twitter.com": 30, + "theregister.com": 29, + "fortune.com": 28, + "nypost.com": 28, + "qz.com": 28, + "engadget.com": 26, + "abc.net.au": 25, + "technologyreview.com": 25, + "cnbc.com": 24, + "techcrunch.com": 23, + "usatoday.com": 22, + "cnet.com": 21, + "medium.com": 19, + "npr.org": 19, + "theregister.co.uk": 15, + "arxiv.org": 14, + "bloomberg.com": 14, + "buzzfeednews.com": 14, + "foxnews.com": 14, + "thetimes.co.uk": 14, + "time.com": 14, + "digitaltrends.com": 13, + "dailydot.com": 12, + "edition.cnn.com": 12, + "electrek.co": 12, + "theatlantic.com": 12, + "thehill.com": 12, + "venturebeat.com": 12, + "abcnews.go.com": 11, + "businessinsider.com.au": 11, + "latimes.com": 11, + "news.sky.com": 11, + "slate.com": 11, + "thenextweb.com": 11, + "yahoo.com": 11, + "gizmodo.com.au": 10, + "motherboard.vice.com": 10, + "pcmag.com": 10, + "propublica.org": 10, + "techtimes.com": 10, + "thesun.co.uk": 10, + "boingboing.net": 9, + "foxbusiness.com": 9, + "indiatoday.in": 9, + "interestingengineering.com": 9, + "inverse.com": 9, + "theconversation.com": 9, + "biometricupdate.com": 8, + "cbc.ca": 8, + "extremetech.com": 8, + "fastcompany.com": 8, + "smh.com.au": 8, + "techdirt.com": 8, + "vox.com": 8, + "zdnet.com": 8, + "euronews.com": 7, + "freep.com": 7, + "hindustantimes.com": 7, + "huffingtonpost.com": 7, + "iflscience.com": 7, + "inc.com": 7, + "money.cnn.com": 7, + "newsweek.com": 7, + "nymag.com": 7, + "petapixel.com": 7, + "siliconangle.com": 7, + "splinternews.com": 7, + "stuff.co.nz": 7, + "thedailybeast.com": 7, + "timesnownews.com": 7, + "wionews.com": 7, + "aclu.org": 6, + "algorithmwatch.org": 6, + "aljazeera.com": 6, + "analyticsindiamag.com": 6, + "hrw.org": 6, + "huffpost.com": 6, + "mirror.co.uk": 6, + "news.com.au": 6, + "newser.com": 6, + "newsnationnow.com": 6, + "politico.eu": 6, + "popularmechanics.com": 6, + "statnews.com": 6, + "themarkup.org": 6, + "timesofindia.indiatimes.com": 6, + "wired.co.uk": 6, + "axios.com": 5, + "bbc.co.uk": 5, + "carscoops.com": 5, + "cointelegraph.com": 5, + "cyberscoop.com": 5, + "economist.com": 5, + "en.wikipedia.org": 5, + "entrepreneur.com": 5, + "france24.com": 5, + "ft.com": 5, + "ftc.gov": 5, + "jalopnik.com": 5, + "koreaherald.com": 5, + "metro.co.uk": 5, + "ndtv.com": 5, + "newsguardtech.com": 5, + "papers.ssrn.com": 5, + "readwrite.com": 5, + "scmp.com": 5, + "snopes.com": 5, + "thedrive.com": 5, + "theinquirer.net": 5, + "theintercept.com": 5, + "torquenews.com": 5, + "tribune.com.pk": 5, + "404media.co": 4, + "autoblog.com": 4, + "autoevolution.com": 4, + "autonews.com": 4, + "baijiahao.baidu.com": 4, + "complex.com": 4, + "cybernews.com": 4, + "digitaljournal.com": 4, + "factcheck.afp.com": 4, + "foxglove.org.uk": 4, + "globalnews.ca": 4, + "huffingtonpost.co.uk": 4, + "huffingtonpost.com.au": 4, + "indiatimes.com": 4, + "insideevs.com": 4, + "justice.gov": 4, + "koreatimes.co.kr": 4, + "kotaku.com": 4, + "malwaretips.com": 4, + "mercurynews.com": 4, + "morningbrew.com": 4, + "msn.com": 4, + "nydailynews.com": 4, + "nzherald.co.nz": 4, + "patch.com": 4, + "phys.org": 4, + "privacyinternational.org": 4, + "projects.tampabay.com": 4, + "rt.com": 4, + "sbs.com.au": 4, + "spectrum.ieee.org": 4, + "standard.co.uk": 4, + "static.nhtsa.gov": 4, + "straitstimes.com": 4, + "tapinto.net": 4, + "tech.co": 4, + "techrepublic.com": 4, + "themessenger.com": 4, + "variety.com": 4, + "windowscentral.com": 4, + "youtube.com": 4, + "9to5mac.com": 3, + "about.fb.com": 3, + "al-monitor.com": 3, + "alphr.com": 3, + "billboard.com": 3, + "bizjournals.com": 3, + "blackenterprise.com": 3, + "bostonglobe.com": 3, + "businessinsider.in": 3, + "caranddriver.com": 3, + "chicago.suntimes.com": 3, + "choice.com.au": 3, + "courthousenews.com": 3, + "dailycaller.com": 3, + "dailyo.in": 3, + "democratandchronicle.com": 3, + "dexerto.com": 3, + "dmv.ca.gov": 3, + "eff.org": 3, + "en.yna.co.kr": 3, + "fiercehealthcare.com": 3, + "finance.yahoo.com": 3, + "globalwitness.org": 3, + "haaretz.com": 3, + "hothardware.com": 3, + "ibtimes.co.uk": 3, + "infoworld.com": 3, + "insidehighered.com": 3, + "insider.com": 3, + "insurancejournal.com": 3, + "itechpost.com": 3, + "itnews.com.au": 3, + "jezebel.com": 3, + "lawandcrime.com": 3, + "lifehacker.com": 3, + "linkedin.com": 3, + "livemint.com": 3, + "logicallyfacts.com": 3, + "marketwatch.com": 3, + "mic.com": 3, + "mindmatters.ai": 3, + "nature.com": 3, + "news.bloomberglaw.com": 3, + "news.ycombinator.com": 3, + "news18.com": 3, + "newsbytesapp.com": 3, + "newscientist.com": 3, + "newshub.co.nz": 3, + "nj.com": 3, + "pandaily.com": 3, + "people.com": 3, + "politico.com": 3, + "polygon.com": 3, + "protocol.com": 3, + "reddit.com": 3, + "restofworld.org": 3, + "roboticsbusinessreview.com": 3, + "rollingstone.com": 3, + "sciencealert.com": 3, + "sfexaminer.com": 3, + "sfgate.com": 3, + "siliconrepublic.com": 3, + "sixthtone.com": 3, + "tampabay.com": 3, + "tech.slashdot.org": 3, + "techinasia.com": 3, + "techpolicy.press": 3, + "the-independent.com": 3, + "the-sun.com": 3, + "theautopian.com": 3, + "thedrum.com": 3, + "thehindu.com": 3, + "thestreet.com": 3, + "thewrap.com": 3, + "today.com": 3, + "trustedreviews.com": 3, + "voanews.com": 3, + "washingtontimes.com": 3, + "weforum.org": 3, + "abajournal.com": 2, + "abovethelaw.com": 2, + "acsh.org": 2, + "adcu.org.uk": 2, + "advox.globalvoices.org": 2, + "afr.com": 2, + "ajc.com": 2, + "allure.com": 2, + "amnesty.org": 2, + "androidauthority.com": 2, + "aol.com": 2, + "appleinsider.com": 2, + "article.wn.com": 2, + "au.pcmag.com": 2, + "audacy.com": 2, + "balkaninsight.com": 2, + "blog.google": 2, + "blogs.wsj.com": 2, + "brookings.edu": 2, + "brusselstimes.com": 2, + "businesstoday.in": 2, + "bustle.com": 2, + "canadianlawyermag.com": 2, + "ccn.com": 2, + "cfodive.com": 2, + "chicagomag.com": 2, + "cityandstateny.com": 2, + "coindesk.com": 2, + "computerworld.com.au": 2, + "consumer.org.nz": 2, + "consumerist.com": 2, + "cosmopolitan.com": 2, + "cultofmac.com": 2, + "cyberdaily.au": 2, + "dailynews.com": 2, + "dailywire.com": 2, + "darkreading.com": 2, + "dataconomy.com": 2, + "decrypt.co": 2, + "denver7.com": 2, + "dezeen.com": 2, + "digitalethics.org": 2, + "digitalspy.com": 2, + "disabilitynewsservice.com": 2, + "driveteslacanada.ca": 2, + "driving.co.uk": 2, + "dukechronicle.com": 2, + "edmontonpolice.ca": 2, + "ekker.legal": 2, + "euractiv.com": 2, + "express.co.uk": 2, + "facebook.com": 2, + "financialexpress.com": 2, + "floridapolitics.com": 2, + "fox4news.com": 2, + "frontofficesports.com": 2, + "fullfact.org": 2, + "gearbrain.com": 2, + "geek.com": 2, + "geekwire.com": 2, + "giantfreakinrobot.com": 2, + "github.com": 2, + "glamour.com": 2, + "globalvillagespace.com": 2, + "govtech.com": 2, + "graphika.com": 2, + "hackingdistributed.com": 2, + "hackread.com": 2, + "hai.stanford.edu": 2, + "hani.co.kr": 2, + "hypebeast.com": 2, + "ic3.gov": 2, + "independent.ie": 2, + "inews.co.uk": 2, + "infowars.com": 2, + "inputmag.com": 2, + "inquisitr.com": 2, + "intellinews.com": 2, + "internetofbusiness.com": 2, + "iol.co.za": 2, + "irishtimes.com": 2, + "isdglobal.org": 2, + "jpost.com": 2, + "kffhealthnews.org": 2, + "kiro7.com": 2, + "knightlawgroup.com": 2, + "ktla.com": 2, + "lalibre.be": 2, + "latestly.com": 2, + "lexology.com": 2, + "link.springer.com": 2, + "lsj.com.au": 2, + "mediamatters.org": 2, + "min.news": 2, + "motherjones.com": 2, + "nationalpost.com": 2, + "nationalreview.com": 2, + "nbcbayarea.com": 2, + "news.abplive.com": 2, + "news.trust.org": 2, + "newsone.com": 2, + "newyorker.com": 2, + "nme.com": 2, + "nola.com": 2, + "notebookcheck.net": 2, + "ntsb.gov": 2, + "openai.com": 2, + "pagesix.com": 2, + "pcworld.com": 2, + "pnas.org": 2, + "politifact.com": 2, + "popsci.com": 2, + "post-gazette.com": 2, + "pymnts.com": 2, + "rappler.com": 2, + "reason.com": 2, + "recode.net": 2, + "researchgate.net": 2, + "revealnews.org": 2, + "sacbee.com": 2, + "screenshot-media.com": 2, + "sea.mashable.com": 2, + "seanbmcgregor.com": 2, + "searchengineland.com": 2, + "seattletimes.com": 2, + "sec.gov": 2, + "seekingalpha.com": 2, + "sfchronicle.com": 2, + "sfist.com": 2, + "simonwillison.net": 2, + "sitepronews.com": 2, + "spiegel.de": 2, + "startribune.com": 2, + "taiwannews.com.tw": 2, + "techmonitor.ai": 2, + "techradar.com": 2, + "techspot.com": 2, + "techtransparencyproject.org": 2, + "techxplore.com": 2, + "the-decoder.com": 2, + "thebulletin.org": 2, + "thecut.com": 2, + "thediplomat.com": 2, + "theinformation.com": 2, + "thenewstack.io": 2, + "thequint.com": 2, + "thestar.com": 2, + "tiktok.com": 2, + "tomsguide.com": 2, + "turkishminute.com": 2, + "uk.news.yahoo.com": 2, + "uni-watch.com": 2, + "unilad.com": 2, + "usnews.com": 2, + "vanderbilthustler.com": 2, + "vietnamnet.vn": 2, + "voicebot.ai": 2, + "walesonline.co.uk": 2, + "wral.com": 2, + "youtu.be": 2, + "10news.com": 1, + "11alive.com": 1, + "2025ad.com": 1, + "71republic.com": 1, + "947.co.za": 1, + "972mag.com": 1, + "9news.com": 1, + "9news.com.au": 1, + "9to5google.com": 1, + "aa.com.tr": 1, + "aaai.org": 1, + "aap.com.au": 1, + "abc15.com": 1, + "abc4.com": 1, + "abc7.com": 1, + "abc7news.com": 1, + "abc7ny.com": 1, + "abplive.in": 1, + "accessnow.org": 1, + "aclunc.org": 1, + "advrider.com": 1, + "adweek.com": 1, + "aebsettlement.com": 1, + "afrotech.com": 1, + "agora.md": 1, + "aibusiness.com": 1, + "aisnakeoil.substack.com": 1, + "alaskasnewssource.com": 1, + "allaboutai.com": 1, + "amp-theguardian-com.cdn.ampproject.org": 1, + "analyticsdrift.com": 1, + "analyticsvidhya.com": 1, + "androidheadlines.com": 1, + "antoinespeaks.co.uk": 1, + "aplus.com": 1, + "app.com": 1, + "appleworld.today": 1, + "archdaily.com": 1, + "archive.boston.com": 1, + "artnews.com": 1, + "asiafinancial.com": 1, + "atlanticcouncil.org": 1, + "au.finance.yahoo.com": 1, + "augustman.com": 1, + "autoguide.com": 1, + "autonews.gasgoo.com": 1, + "autosafety.org": 1, + "awfulannouncing.com": 1, + "azcentral.com": 1, + "azfamily.com": 1, + "bair.berkeley.edu": 1, + "bangkokpost.com": 1, + "bankinfosecurity.com": 1, + "barrysookman.com": 1, + "barstoolsports.com": 1, + "bartlesvilleradio.com": 1, + "beckersasc.com": 1, + "beckershospitalreview.com": 1, + "beincrypto.com": 1, + "belairassociation.org": 1, + "belganewsagency.eu": 1, + "benitolink.com": 1, + "benzinga.com": 1, + "biggerlawfirm.com": 1, + "bipartisanpolicy.org": 1, + "bitdefender.com": 1, + "bits.blogs.nytimes.com": 1, + "blavity.com": 1, + "blitzquotidiano.it": 1, + "blockchain-council.org": 1, + "blockchaintechnology-news.com": 1, + "blog.angryasianman.com": 1, + "blog.bity.com": 1, + "blog.botego.com": 1, + "blog.coinbase.com": 1, + "blog.conceptnet.io": 1, + "blog.galalaw.com": 1, + "blog.openai.com": 1, + "blog.slock.it": 1, + "blog.twitter.com": 1, + "blogs.bing.com": 1, + "blogs.discovermagazine.com": 1, + "blogs.microsoft.com": 1, + "bloomberg.com.": 1, + "bnnbloomberg.ca": 1, + "bnnbreaking.com": 1, + "boredpanda.com": 1, + "boston.com": 1, + "bostonherald.com": 1, + "boxmining.com": 1, + "br.de": 1, + "brecorder.com": 1, + "breitbart.com": 1, + "bronx.news12.com": 1, + "business-standard.com": 1, + "businesscloud.co.uk": 1, + "businessnewsdaily.com": 1, + "businesstimes.com.sg": 1, + "businesswire.com": 1, + "bussgeldportal.de": 1, + "buzzfeed.com": 1, + "ca.finance.yahoo.com": 1, + "ca.movies.yahoo.com": 1, + "ca.news.yahoo.com": 1, + "cacm.acm.org": 1, + "cafemom.com": 1, + "caixinglobal.com": 1, + "capitalbrief.com": 1, + "capradio.org": 1, + "carcomplaints.com": 1, + "carloscreusmoreira.medium.com": 1, + "carthrottle.com": 1, + "casetext.com": 1, + "catonetworks.com": 1, + "cbs12.com": 1, + "cbs17.com": 1, + "ccjdigital.com": 1, + "cdn.arstechnica.net": 1, + "cdn.openai.com": 1, + "cdn.sanity.io": 1, + "cdotrends.com": 1, + "cdt.org": 1, + "cepa.org": 1, + "cfr.org": 1, + "cgdev.org": 1, + "change.org": 1, + "channel4.com": 1, + "channels.theinnovationenterprise.com": 1, + "chatbotslife.com": 1, + "chatbotsmagazine.com": 1, + "chicagoreader.com": 1, + "chicagotribune.com": 1, + "chinadaily.com.cn": 1, + "christiantoday.com": 1, + "cinemablend.com": 1, + "cio.com": 1, + "cityam.com": 1, + "citylab.com": 1, + "civilbeat.org": 1, + "cjr.org": 1, + "clarin.com": 1, + "clarksonlawfirm.com": 1, + "claytonutz.com": 1, + "click2houston.com": 1, + "cmu.edu": 1, + "codastory.com": 1, + "cohenmilstein.com": 1, + "coincentral.com": 1, + "coincodex.com": 1, + "coinmarketcap.com": 1, + "colombiaone.com": 1, + "coloradohometownweekly.com": 1, + "colorlines.com": 1, + "commondreams.org": 1, + "comptroller.nyc.gov": 1, + "computerworld.com": 1, + "computing.co.uk": 1, + "consumersinternational.org": 1, + "context-cdn.washingtonpost.com": 1, + "copyrightlately.com": 1, + "corporatefinanceinstitute.com": 1, + "cosmeticsbusiness.com": 1, + "cosmosmagazine.com": 1, + "coyotecountrylv.com": 1, + "cpomagazine.com": 1, + "cpsc.gov": 1, + "cracked.com": 1, + "creativebloq.com": 1, + "cryptocompare.com": 1, + "cryptocurrencyhub.io": 1, + "cryptopolitan.com": 1, + "csmapnyu.org": 1, + "csoonline.com": 1, + "ctinsider.com": 1, + "ctvnews.ca": 1, + "culvercityobserver.com": 1, + "curiosity.com": 1, + "cyber.fsi.stanford.edu": 1, + "cybersecurityfordemocracy.org": 1, + "d.dailynews.co.th": 1, + "dailybreeze.com": 1, + "dailycitizen.focusonthefamily.com": 1, + "dailypress.net": 1, + "dailystar.co.uk": 1, + "dallasnews.com": 1, + "danrl.com": 1, + "dataprivacylab.org": 1, + "deadline.com": 1, + "dealstreetasia.com": 1, + "dearauthor.com": 1, + "deccanherald.com": 1, + "delish.com": 1, + "denverpost.com": 1, + "designnews.com": 1, + "detroitnews.com": 1, + "digg.com": 1, + "digit.fyi": 1, + "digitalcameraworld.com": 1, + "dispatch.com": 1, + "disputeresolutiongermany.com": 1, + "dkb.blog": 1, + "dl.acm.org": 1, + "dmlp.org": 1, + "dmv.com": 1, + "dnaindia.com": 1, + "docs.rwu.edu": 1, + "donga.com": 1, + "dotesports.com": 1, + "dpreview.com": 1, + "dttc.sggp.org.vn": 1, + "dw.com": 1, + "ebaumsworld.com": 1, + "ecns.cn": 1, + "economictimes.indiatimes.com": 1, + "econotimes.com": 1, + "editorialge.com": 1, + "edpb.europa.eu": 1, + "edrants.com": 1, + "eldiario.es": 1, + "electronicproducts.com": 1, + "elitereaders.com": 1, + "elle.com": 1, + "elmundo.es": 1, + "elobservador.com.uy": 1, + "emirates247.com": 1, + "en.dailypakistan.com.pk": 1, + "en.econostrum.info": 1, + "en.wikinews.org": 1, + "energyinfrapost.com": 1, + "english.elpais.com": 1, + "english.jagran.com": 1, + "ent.siteintelgroup.com": 1, + "epic.org": 1, + "escapistmagazine.com": 1, + "eteknix.com": 1, + "etftrends.com": 1, + "eticasfoundation.org": 1, + "eu.usatoday.com": 1, + "eulawenforcement.com": 1, + "eurogamer.net": 1, + "europepmc.org": 1, + "explica.co": 1, + "expressnews.com": 1, + "fairfieldsuntimes.com": 1, + "fairworkweek.org": 1, + "familyhandyman.com": 1, + "features.propublica.org": 1, + "finance.nine.com.au": 1, + "financialregnews.com": 1, + "finbold.com": 1, + "firstpost.com": 1, + "flawedfacedata.com": 1, + "fool.com": 1, + "forbesafrica.com": 1, + "fordfoundation.org": 1, + "foreignpolicy.com": 1, + "forum.facepunch.com": 1, + "forward.com": 1, + "fossa.com": 1, + "foundation.mozilla.org": 1, + "fox2detroit.com": 1, + "fox5dc.com": 1, + "fox5ny.com": 1, + "fox5sandiego.com": 1, + "foxcarolina.com": 1, + "freakonomics.com": 1, + "freemovement.org.uk": 1, + "fudzilla.com": 1, + "fullerproject.org": 1, + "futureoflife.org": 1, + "futureparty.com": 1, + "gamasutra.com": 1, + "gamedaily.biz": 1, + "gamespot.com": 1, + "gameworldobserver.com": 1, + "garyrubinstein.teachforus.org": 1, + "gawker.com": 1, + "geekologie.com": 1, + "gffbrokers.com": 1, + "gineersnow.com": 1, + "github.blog": 1, + "githubcopilotlitigation.com": 1, + "gizchina.com": 1, + "gizmodo.co.uk": 1, + "glamourmagazine.co.uk": 1, + "globalcitizen.org": 1, + "globalnation.inquirer.net": 1, + "globaltimes.cn": 1, + "globalvoices.org": 1, + "gmauthority.com": 1, + "gmw.cn": 1, + "gofundme.com": 1, + "goodmorningamerica.com": 1, + "gothamist.com": 1, + "gov.uk": 1, + "gq.com": 1, + "graphics.wsj.com": 1, + "graziadaily.co.uk": 1, + "greekreporter.com": 1, + "greenbot.com": 1, + "gwern.net": 1, + "gzeromedia.com": 1, + "hackaday.com": 1, + "hackernoon.com": 1, + "hardworkingtrucks.com": 1, + "hawaiinewsnow.com": 1, + "hawaiitribune-herald.com": 1, + "hbr.org": 1, + "hbswk.hbs.edu": 1, + "hcamag.com": 1, + "healthcarefinancenews.com": 1, + "heise.de": 1, + "helpnetsecurity.com": 1, + "heraldnews.com": 1, + "hindi.news18.com": 1, + "hoit.uk": 1, + "home.bt.com": 1, + "hotelmanagement.net": 1, + "howtogeek.com": 1, + "hrmonline.com.au": 1, + "hrtechnologynews.com": 1, + "hstoday.us": 1, + "huffingtonpost.ca": 1, + "huntonprivacyblog.com": 1, + "hurriyetdailynews.com": 1, + "hypebae.com": 1, + "ia.acs.org.au": 1, + "iafrica.com": 1, + "iafrikan.com": 1, + "ibtimes.com": 1, + "idiallo.com": 1, + "ilfattoquotidiano.it": 1, + "ilpost.it": 1, + "imd.org": 1, + "imore.com": 1, + "in.finance.yahoo.com": 1, + "in.mashable.com": 1, + "incidentdatabase.ai": 1, + "india.com": 1, + "indianexpress.com": 1, + "indiatvnews.com": 1, + "industryweek.com": 1, + "indystar.com": 1, + "information-age.com": 1, + "innotechtoday.com": 1, + "inshorts.com": 1, + "insideedition.com": 1, + "insidehook.com": 1, + "insideprivacy.com": 1, + "insights.globalspec.com": 1, + "insights.tmpw.co.uk": 1, + "instagram.com": 1, + "intelligenttransport.com": 1, + "intouchweekly.com": 1, + "investors.com": 1, + "io9.gizmodo.com": 1, + "iottechnews.com": 1, + "iotworldtoday.com": 1, + "iphonehacks.com": 1, + "ipi.media": 1, + "ipn.md": 1, + "ipvm.com": 1, + "irishmirror.ie": 1, + "ishn.com": 1, + "itwire.com": 1, + "ivebeenmugged.typepad.com": 1, + "jcwi.org.uk": 1, + "jdsupra.com": 1, + "jsjc.gov.cn": 1, + "jta.org": 1, + "judiciary.uk": 1, + "justcareusa.org": 1, + "justjared.com": 1, + "justsomething.co": 1, + "k.sina.cn": 1, + "kathmandupost.ekantipur.com": 1, + "kfm.co.za": 1, + "khaleejtimes.com": 1, + "khq.com": 1, + "kingsleynapley.co.uk": 1, + "knowyourmeme.com": 1, + "kotaku.co.uk": 1, + "kr-asia.com": 1, + "krdo.com": 1, + "krem.com": 1, + "kslnewsradio.com": 1, + "ktvu.com": 1, + "kurdistan24.net": 1, + "labsnews.com": 1, + "ladbible.com": 1, + "lagrandeobserver.com": 1, + "laion.ai": 1, + "lamag.com": 1, + "lasvegassun.com": 1, + "latimesblogs.latimes.com": 1, + "latintimes.com": 1, + "laweekly.com": 1, + "lawenforcementtoday.com": 1, + "lawfaremedia.org": 1, + "lawgazette.co.uk": 1, + "learningenglish.voanews.com": 1, + "legalreader.com": 1, + "lemonde.fr": 1, + "lentrepreneur.co": 1, + "lesswrong.com": 1, + "lgbtqnation.com": 1, + "liaa.dc.uba.ar": 1, + "lifesitenews.com": 1, + "livescience.com": 1, + "lobste.rs": 1, + "local12.com": 1, + "losangelesblade.com": 1, + "lr21.com.uy": 1, + "macarthurjustice.org": 1, + "macrumors.com": 1, + "madisonrecord.com": 1, + "makeuseof.com": 1, + "malaymail.com": 1, + "malicious.life": 1, + "mansworldindia.com": 1, + "marginalrevolution.com": 1, + "marketplace.org": 1, + "markey.senate.gov": 1, + "martinmontilino.com": 1, + "massdevice.com": 1, + "massivelyop.com": 1, + "mcknightshomecare.com": 1, + "mcvuk.com": 1, + "me.pcmag.com": 1, + "mediaite.com": 1, + "medianama.com": 1, + "medicaleconomics.com": 1, + "medium.freecodecamp.org": 1, + "medscape.com": 1, + "mentalfloss.com": 1, + "meta-writer.livejournal.com": 1, + "meta.stackoverflow.com": 1, + "metalinjection.net": 1, + "metrorailnews.in": 1, + "metroweekly.com": 1, + "meyerweb.com": 1, + "miamiherald.com": 1, + "michaeleisen.org": 1, + "michiganradio.org": 1, + "middleeasteye.net": 1, + "military.com": 1, + "mlive.com": 1, + "mobility21.cmu.edu": 1, + "mondaq.com": 1, + "montevideo.com.uy": 1, + "montrealgazette.com": 1, + "mothership.sg": 1, + "motorbiscuit.com": 1, + "motormoutharabia.com": 1, + "motortrend.com": 1, + "msnbc.com": 1, + "multichain.com": 1, + "musically.com": 1, + "muycomputerpro.com": 1, + "myfox28columbus.com": 1, + "mymodernmet.com": 1, + "mynorthwest.com": 1, + "nafcu.org": 1, + "nasdaq.com": 1, + "natbuckley.co.uk": 1, + "nation.com.pk": 1, + "natlawreview.com": 1, + "nautil.us": 1, + "nbc15.com": 1, + "nbcboston.com": 1, + "nbcchicago.com": 1, + "nbcdfw.com": 1, + "nbclosangeles.com": 1, + "nbcmiami.com": 1, + "nbcnewyork.com": 1, + "nbcphiladelphia.com": 1, + "nbcwashington.com": 1, + "nec.com": 1, + "neil-clarke.com": 1, + "neoskosmos.com": 1, + "neowin.net": 1, + "newindianexpress.com": 1, + "newjerseymonitor.com": 1, + "newmatilda.com": 1, + "newrepublic.com": 1, + "news.avclub.com": 1, + "news.cgtn.com": 1, + "news.cornell.edu": 1, + "news.trendmicro.com": 1, + "news.tvbs.com.tw": 1, + "news.yahoo.com": 1, + "news5cleveland.com": 1, + "news9live.com": 1, + "newsbeezer.com": 1, + "newsbtc.com": 1, + "newsfolo.com": 1, + "newsmax.com": 1, + "newsobserver.com": 1, + "newson6.com": 1, + "newsroom.gettyimages.com": 1, + "newstatesman.com": 1, + "newswise.com": 1, + "newsx.com": 1, + "nguoi-viet.com": 1, + "nickdiakopoulos.com": 1, + "nj1015.com": 1, + "nknews.org": 1, + "northjersey.com": 1, + "nouse.co.uk": 1, + "nowthisnews.com": 1, + "nst.com.my": 1, + "ntd.com": 1, + "ny1.com": 1, + "observers.france24.com": 1, + "ocregister.com": 1, + "offthemainpage.com": 1, + "olhardigital.com.br": 1, + "opb.org": 1, + "osf.io": 1, + "out-law.com": 1, + "overlayfalseclaims.com": 1, + "ovic.vic.gov.au": 1, + "palestinechronicle.com": 1, + "pantip.com": 1, + "parentadvocates.org": 1, + "pastemagazine.com": 1, + "pbs.org": 1, + "pcgamer.com": 1, + "pewtrusts.org": 1, + "phonearena.com": 1, + "physicstoday.scitation.org": 1, + "plagiarismtoday.com": 1, + "plantservices.com": 1, + "pocket-lint.com": 1, + "policyoptions.irpp.org": 1, + "poliisi.fi": 1, + "polizei.bayern.de": 1, + "polyesterstudio.com": 1, + "populardemocracy.org": 1, + "postcourier.com.pg": 1, + "poynter.org": 1, + "pressgazette.co.uk": 1, + "pride.com": 1, + "princeton.edu": 1, + "priv.gc.ca": 1, + "prnewswire.com": 1, + "proactiveinvestors.com": 1, + "probonoaustralia.com.au": 1, + "projects.propublica.org": 1, + "psmag.com": 1, + "psychiatrist.com": 1, + "publiclawproject.org.uk": 1, + "pulitzercenter.org": 1, + "pulsenews.co.kr": 1, + "punchbowl.news": 1, + "punto-informatico.it": 1, + "qcnews.com": 1, + "queerinai.com": 1, + "queerty.com": 1, + "queue.acm.org": 1, + "quinyx.com": 1, + "radionz.co.nz": 1, + "raillc.substack.com": 1, + "rand.org": 1, + "read.dukeupress.edu": 1, + "readlion.com": 1, + "recordedfuture.com": 1, + "republicworld.com": 1, + "research.checkpoint.com": 1, + "resemble.ai": 1, + "restaurantbusinessonline.com": 1, + "retailwire.com": 1, + "retractionwatch.com": 1, + "rsk.co": 1, + "rss.onlinelibrary.wiley.com": 1, + "rts.ch": 1, + "rtve.es": 1, + "russiamatters.org": 1, + "s.weibo.com": 1, + "safiyaunoble.files.wordpress.com": 1, + "salon.com": 1, + "samaa.tv": 1, + "sbnation.com": 1, + "schneier.com": 1, + "science.org": 1 + }, + "CSETv1.AI Task": { + "facial recognition": 131, + "navigation": 112, + "patrolling": 62, + "virtual assistant technology": 58, + "security": 57, + "object detection": 55, + "autonomous driving": 54, + "self-driving": 53, + "object recognition": 51, + "chat bot": 49, + "text generation": 46, + "search optimization": 44, + "autonomous navigation": 43, + "search suggestion": 41, + "Image search": 40, + "predict recidivism": 38, + "Rank Applicants": 37, + "translation": 37, + "prediction": 35, + "self driving": 35, + "semi-autonomous navigation": 35, + "surveillance": 35, + "Driving": 34, + "resume screening": 33, + "content generation": 31, + "deepfake video generation": 31, + "autocomplete": 30, + "content moderation": 30, + "recommender": 30, + "content ranking": 29, + "Generate Captions": 28, + "assembly": 28, + "image identification": 28, + "image interpretation": 28, + "production": 28, + "personalized online advertising": 27, + "image classification": 25, + "jaywalking detection": 25, + "Autopilot": 24, + "Image Tagging": 24, + "identity verification": 24, + "image categorization": 24, + "obstacle avoidance": 24, + "route optimization": 24, + "filter": 23, + "image modification": 23, + "photo edit": 23, + "Generate Replies": 22, + "chatbot": 22, + "face detection": 22, + "search engine optimization": 19, + "Predict Crimes": 13, + "predictive policing": 13, + "recommendation": 13, + "generation": 12, + "detect gunshots": 11, + "identify weapon calibre": 11, + "locate gunshots": 11, + "personalized online search results": 11, + "population of characteristics for NPCs in a video game": 11, + "predict shooter movement": 11, + "Image Analysis": 10, + "predict store traffic": 10, + "productivity optimization": 10, + "scheduling": 10, + "image generation": 9, + "toxicity detection": 9, + "voice recognition": 9, + "augmented reality (AR) game Pokémon Go": 8, + "natural language processing": 8, + "speech recognition": 8, + "Design Phone Cases": 7, + "assign risk": 7, + "classification": 7, + "deepfake audio generation": 7, + "predict healthcare needs": 7, + "sentiment analysis": 7, + "editing": 6, + "fraud risk prediction": 6, + "identify hate speech": 6, + "webpage maintenance": 6, + "Crop Images": 5, + "Personal Assistant": 5, + "automatic emergency braking": 5, + "content recommendation": 5, + "image cropping": 5, + "security monitor": 5, + "speech interpretation": 5, + "Digital Assistant": 4, + "application screening": 4, + "audit selection": 4, + "candidate assessment": 4, + "diagnose sepsis": 4, + "emotion recognition": 4, + "estimating house prices": 4, + "facial expression recognition": 4, + "home valuation": 4, + "identify sepsis": 4, + "object classification": 4, + "price determination": 4, + "product promotion": 4, + "real estate market forecasting": 4, + "search engine": 4, + "search result ranking": 4, + "sepsis prediction": 4, + "transportation": 4, + "virtual interview": 4, + "worker management": 4, + "delivery": 3, + "ethical decision making": 3, + "gender classification": 3, + "move shelves": 3, + "performance tracking": 3, + "reply to questions": 3, + "video recommender": 3, + "water quality testing": 3, + "worker monitoring": 3, + "assess applicants": 2, + "audio deepfake": 2, + "automated speech recognition": 2, + "automatic tracking": 2, + "ball detection": 2, + "chant scripture": 2, + "cheerleading": 2, + "concierge": 2, + "driver surveillance": 2, + "emotion detection": 2, + "entertain guests": 2, + "event detection": 2, + "event prediction": 2, + "exercise demonstration": 2, + "eye tracking": 2, + "facial grouping": 2, + "household administration": 2, + "identification or detection": 2, + "product recognition": 2, + "rank the reliability of workers": 2, + "risk assessment": 2, + "safe driving detection": 2, + "semi-autonomous driving": 2, + "shift assignment": 2, + "speech-to-text": 2, + "suggestion": 2, + "weapon detection": 2, + "welfare determination": 2, + "ADA website compliance": 1, + "Burmese to English translation": 1, + "Matching": 1, + "accessibility compliance": 1, + "advertisement screening": 1, + "autonomous food delivery": 1, + "benefits allocation": 1, + "brand safety detection": 1, + "content suggestion": 1, + "customer service": 1, + "database": 1, + "detect policy violations": 1, + "enforcement of community guidelines": 1, + "engagement optimization": 1, + "exam proctoring": 1, + "fact-check": 1, + "human facial image transformation": 1, + "identify misinformation": 1, + "image editing": 1, + "image organization": 1, + "image recognition": 1, + "image splicing": 1, + "job screening": 1, + "law enforcement": 1, + "license plate matching": 1, + "license plate recognition": 1, + "marketing material generation": 1, + "optimize movement to win a computer game": 1, + "passport photo quality check": 1, + "performance prediction": 1, + "predict grades on exam": 1, + "predict necessary amount of medical coverage": 1, + "predict recidivism risk": 1, + "recipe generation": 1, + "recruitment screening": 1, + "remote proctoring": 1, + "risk prediction": 1, + "robotics": 1, + "spam filter": 1, + "stoplight recognition": 1, + "vaccine allocation": 1, + "video classification": 1, + "video suggestion": 1, + "voice alteration": 1, + "weapons detection": 1, + "word association": 1 + }, + "CSETv1.Deployed": { + "yes": 1442, + "no": 103, + "maybe": 37 + }, + "CSETv1.Embedded": { + "no": 976, + "yes": 592, + "maybe": 14 + }, + "CSETv1.Injuries": { + "0": 1480, + "1": 66, + "2": 5, + "10": 27, + "40": 1, + "54": 17, + "416": 3, + "1391": 12, + "55000": 3 + }, + "classifications": { + "CSETv1_Annotator-1:User Test in Controlled Conditions:no": 1841, + "CSETv1_Annotator-1:Impact on Critical Services:no": 1799, + "CSETv1_Annotator-1:Multiple AI Interaction:no": 1797, + "CSETv1_Annotator-1:Producer Test in Controlled Conditions:no": 1785, + "CSETv1_Annotator-1:Report, Test, or Study of data:no": 1755, + "CSETv1_Annotator-1:Deployed:yes": 1715, + "CSETv1_Annotator-1:Involving Minor:no": 1701, + "CSETv1_Annotator-1:Rights Violation:no": 1690, + "CSETv1_Annotator-1:Producer Test in Operational Conditions:no": 1683, + "CSETv1_Annotator-1:User Test in Operational Conditions:no": 1567, + "CSETv1:User Test in Controlled Conditions:no": 1555, + "CSETv1_Annotator-1:Intentional Harm:No. Not intentionally designed to perform harm": 1542, + "CSETv1_Annotator-1:Public Sector Deployment:no": 1526, + "CSETv1:Multiple AI Interaction:no": 1525, + "CSETv1:Report, Test, or Study of data:no": 1511, + "CSETv1:Producer Test in Controlled Conditions:no": 1509, + "CSETv1_Annotator-1:Detrimental Content:no": 1499, + "CSETv1_Annotator-1:Entertainment Industry:no": 1498, + "CSETv1_Annotator-1:AI System:yes": 1497, + "CSETv1:Impact on Critical Services:no": 1474, + "CSETv1:Involving Minor:no": 1469, + "CSETv1:Intentional Harm:No. Not intentionally designed to perform harm": 1462, + "CSETv1:Producer Test in Operational Conditions:no": 1453, + "CSETv1:Deployed:yes": 1442, + "CSETv1:Clear link to Technology:yes": 1425, + "CSETv1_Annotator-1:Clear link to Technology:yes": 1422, + "CSETv1:Entertainment Industry:no": 1401, + "CSETv1:User Test in Operational Conditions:no": 1367, + "CSETv1:Rights Violation:no": 1332, + "CSETv1:Public Sector Deployment:no": 1324, + "CSETv1:Detrimental Content:no": 1281, + "CSETv1_Annotator-1:Protected Characteristic:no": 1270, + "CSETv1_Annotator-1:Harm Distribution Basis:none": 1223, + "CSETv1:AI System:yes": 1221, + "CSETv1_Annotator-1:Embedded:no": 1206, + "CSETv1_Annotator-1:Annotator’s AI special interest intangible harm assessment:no": 1192, + "CSETv1_Annotator-1:AI Harm Level:none": 1153, + "CSETv1_Annotator-1:Physical Objects:no": 1150, + "CSETv1_Annotator-1:Special Interest Intangible Harm:no": 1087, + "CSETv1:Protected Characteristic:no": 1070, + "CSETv1:Harm Distribution Basis:none": 1059, + "CSETv1:AI Harm Level:none": 1003, + "CSETv1:Embedded:no": 976, + "CSETv1:Annotator’s AI special interest intangible harm assessment:no": 949, + "CSETv1:Physical Objects:no": 946, + "CSETv1_Annotator-1:Location Country (two letters):US": 946, + "CSETv0:Intent:Accident": 935, + "CSETv1_Annotator-2:User Test in Controlled Conditions:no": 930, + "CSETv0:Relevant AI functions:Cognition": 924, + "CSETv1_Annotator-2:Entertainment Industry:no": 908, + "CSETv1_Annotator-2:Multiple AI Interaction:no": 904, + "CSETv1_Annotator-2:Producer Test in Operational Conditions:no": 904, + "CSETv1_Annotator-2:Impact on Critical Services:no": 901, + "CSETv1_Annotator-1:Location Region:North America": 899, + "CSETv0:Nature of End User:Amateur": 896, + "CSETv0:Relevant AI functions:Perception": 892, + "CSETv1_Annotator-2:Producer Test in Controlled Conditions:no": 884, + "CSETv1_Annotator-2:Report, Test, or Study of data:no": 870, + "CSETv1:Special Interest Intangible Harm:no": 862, + "CSETv1_Annotator-1:Autonomy Level:Autonomy1": 852, + "CSETv1_Annotator-2:Intentional Harm:No. Not intentionally designed to perform harm": 850, + "CSETv1:Autonomy Level:Autonomy1": 847, + "CSETv1_Annotator-2:Involving Minor:no": 843, + "CSETv1_Annotator-2:Deployed:yes": 841, + "CSETv1_Annotator-2:Rights Violation:no": 826, + "CSETv1_Annotator-2:Public Sector Deployment:no": 819, + "CSETv1_Annotator-2:Detrimental Content:no": 813, + "CSETv1_Annotator-1:Tangible Harm:no tangible harm, near-miss, or issue": 794, + "CSETv1:Location Region:North America": 781, + "CSETv1_Annotator-2:User Test in Operational Conditions:no": 773, + "CSETv1:Location Country (two letters):US": 769, + "CSETv0:Relevant AI functions:Action": 768, + "CSETv1_Annotator-1:Special Interest Intangible Harm:yes": 732, + "CSETv1_Annotator-1:Tangible Harm:tangible harm definitively occurred": 719, + "CSETv0:Physical System:Software only": 704, + "CSETv1_Annotator-2:AI Harm Level:none": 704, + "CSETv1:Special Interest Intangible Harm:yes": 703, + "CSETv1_Annotator-2:Protected Characteristic:no": 689, + "CSETv1:Tangible Harm:tangible harm definitively occurred": 678, + "CSETv1_Annotator-2:AI System:yes": 675, + "CSETv1_Annotator-1:Physical Objects:yes": 664, + "CSETv1_Annotator-2:Embedded:no": 649, + "CSETv1_Annotator-1:Annotator’s AI special interest intangible harm assessment:yes": 647, + "CSETv1_Annotator-1:Embedded:yes": 643, + "CSETv1_Annotator-1:Sector of Deployment:information and communication": 639, + "CSETv0:Severity:Negligible": 634, + "CSETv1:Tangible Harm:no tangible harm, near-miss, or issue": 618, + "CSETv1:Physical Objects:yes": 615, + "CSETv1:Annotator’s AI special interest intangible harm assessment:yes": 609, + "CSETv1_Annotator-2:Annotator’s AI special interest intangible harm assessment:no": 607, + "CSETv1_Annotator-2:Harm Distribution Basis:none": 596, + "CSETv1:Embedded:yes": 592, + "CSETv1_Annotator-1:Protected Characteristic:yes": 565, + "CSETv1:Sector of Deployment:information and communication": 562, + "CSETv1_Annotator-2:Special Interest Intangible Harm:no": 551, + "CSETv1_Annotator-2:Physical Objects:no": 550, + "CSETv1_Annotator-1:Autonomy Level:Autonomy3": 546, + "CSETv0:Near Miss:Harm caused": 537, + "CSETv1_Annotator-2:Clear link to Technology:yes": 522, + "CSETv1_Annotator-1:Location Region:Global": 519, + "CSETv0:Problem Nature:Robustness": 507, + "CSETv1:Protected Characteristic:yes": 505, + "CSETv0:Problem Nature:Specification": 473, + "CSETv0:Near Miss:Unclear/unknown": 461, + "CSETv1_Annotator-2:Location Region:North America": 454, + "CSETv0:Level of Autonomy:High": 452, + "CSETv1:Data Inputs:text": 443, + "CSETv1_Annotator-2:Location Country (two letters):US": 441, + "CSETv1_Annotator-3:Producer Test in Controlled Conditions:no": 435, + "CSETv1_Annotator-3:Involving Minor:no": 427, + "CSETv1_Annotator-3:User Test in Controlled Conditions:no": 424, + "CSETv1_Annotator-2:Tangible Harm:no tangible harm, near-miss, or issue": 422, + "CSETv1_Annotator-3:Producer Test in Operational Conditions:no": 420, + "CSETv1_Annotator-3:Multiple AI Interaction:no": 418, + "CSETv1_Annotator-2:Clear link to Technology:no": 414, + "CSETv0:Level of Autonomy:Medium": 413, + "CSETv1_Annotator-3:Report, Test, or Study of data:no": 412, + "CSETv1_Annotator-3:Deployed:yes": 409, + "CSETv1_Annotator-2:Physical Objects:yes": 404, + "CSETv1:Location Region:Global": 402, + "CSETv1_Annotator-3:Detrimental Content:no": 394, + "CSETv1_Annotator-3:Entertainment Industry:no": 391, + "CSETv1_Annotator-1:Date of Incident Year:2017": 389, + "CSETv1_Annotator-2:Special Interest Intangible Harm:yes": 389, + "CSETv1_Annotator-3:Impact on Critical Services:no": 388, + "CSETv1_Annotator-3:Intentional Harm:No. Not intentionally designed to perform harm": 388, + "CSETv1_Annotator-2:Tangible Harm:tangible harm definitively occurred": 383, + "CSETv1:Date of Incident Year:2017": 382, + "CSETv1_Annotator-1:AI Harm Level:AI tangible harm event": 381, + "CSETv1_Annotator-2:Autonomy Level:Autonomy1": 379, + "CSETv1:AI Harm Level:AI tangible harm event": 373, + "CSETv1:Autonomy Level:Autonomy3": 370, + "CSETv1_Annotator-1:Autonomy Level:Autonomy2": 369, + "CSETv1_Annotator-1:Sector of Deployment:transportation and storage": 368, + "CSETv1_Annotator-1:Harm Distribution Basis:race": 364, + "CSETv0:Location:Global": 355, + "CSETv0:Sector of Deployment:Information and communication": 352, + "CSETv1_Annotator-3:AI System:yes": 350, + "CSETv1:Harm Distribution Basis:race": 346, + "CSETv1_Annotator-3:Public Sector Deployment:no": 346, + "CSETv1_Annotator-3:User Test in Operational Conditions:no": 342, + "CSETv1_Annotator-1:Clear link to Technology:no": 339, + "CSETv1_Annotator-2:Annotator’s AI special interest intangible harm assessment:yes": 333, + "CSETv1_Annotator-3:Rights Violation:no": 322, + "CSETv1_Annotator-3:Clear link to Technology:yes": 305, + "CSETv1_Annotator-1:Public Sector Deployment:yes": 304, + "CSETv1_Annotator-2:Embedded:yes": 301, + "CSETv0:Problem Nature:Unknown/unclear": 292, + "CSETv1:Data Inputs:images": 286, + "CSETv1_Annotator-1:AI System:no": 286, + "CSETv1_Annotator-1:Data Inputs:images": 283, + "CSETv1_Annotator-1:User Test in Operational Conditions:yes": 283, + "CSETv0:Harm Type:Harm to physical health/safety": 282, + "CSETv1_Annotator-2:Autonomy Level:Autonomy2": 281, + "CSETv1_Annotator-1:Detrimental Content:yes": 279, + "CSETv1:Sector of Deployment:transportation and storage": 273, + "CSETv1:AI System:no": 269, + "CSETv1_Annotator-2:Sector of Deployment:information and communication": 269, + "CSETv1:Date of Incident Year:2016": 268, + "CSETv1_Annotator-2:Date of Incident Year:2017": 267, + "CSETv0:Severity:Minor": 262, + "CSETv1:Detrimental Content:yes": 262, + "CSETv1:Autonomy Level:Autonomy2": 261, + "CSETv1_Annotator-2:Protected Characteristic:yes": 261, + "CSETv1_Annotator-2:Data Inputs:Text": 260, + "CSETv1_Annotator-3:Protected Characteristic:no": 258, + "CSETv1_Annotator-3:Physical Objects:no": 256, + "CSETv1_Annotator-1:Sector of Deployment:Arts, entertainment and recreation": 252, + "CSETv1_Annotator-3:Harm Distribution Basis:none": 252, + "CSETv1:Public Sector Deployment:yes": 249, + "CSETv1_Annotator-1:Harm Distribution Basis:sex": 243, + "CSETv0:Named Entities:Google": 242, + "CSETv1_Annotator-3:Special Interest Intangible Harm:no": 241, + "CSETv0:Harm Type:Harm to social or political systems": 239, + "CSETv1_Annotator-1:Data Inputs:sensor data": 239, + "CSETv0:Technology Purveyor:Google": 238, + "CSETv0:Harm Distribution Basis:Race": 235, + "CSETv1:AI tools and methods:natural language processing": 235, + "CSETv1:Data Inputs:video": 234, + "CSETv1_Annotator-1:Entertainment Industry:yes": 229, + "CSETv1_Annotator-3:Annotator’s AI special interest intangible harm assessment:no": 226, + "CSETv0:System Developer:Google": 224, + "CSETv1_Annotator-1:Data Inputs:text": 223, + "CSETv1_Annotator-1:Date of Incident Year:2016": 223, + "CSETv1_Annotator-1:Date of Incident Year:2018": 222, + "CSETv0:AI Techniques:Machine learning": 221, + "CSETv1_Annotator-2:Autonomy Level:Autonomy3": 221, + "CSETv0:Harm Type:Financial harm": 220, + "CSETv0:Near Miss:Near miss": 219, + "CSETv1_Annotator-3:Autonomy Level:Autonomy1": 219, + "CSETv1_Annotator-1:Sector of Deployment:law enforcement": 218, + "CSETv1:AI tools and methods:computer vision": 217, + "CSETv1_Annotator-1:Data Inputs:video input": 216, + "CSETv0:Physical System:Vehicle/mobile robot": 215, + "CSETv1_Annotator-2:AI System:no": 215, + "CSETv1_Annotator-3:Embedded:yes": 213, + "CSETv1:User Test in Operational Conditions:yes": 211, + "CSETv1_Annotator-3:Embedded:no": 211, + "CSETv0:Problem Nature:Assurance": 207, + "CSETv1_Annotator-2:Location Region:Global": 205, + "CSETv1_Annotator-3:AI Harm Level:none": 204, + "CSETv0:Harm Type:Psychological harm": 202, + "CSETv1_Annotator-1:Tangible Harm:imminent risk of tangible harm (near miss) did occur": 201, + "CSETv1:Harm Distribution Basis:sex": 198, + "CSETv0:Intent:Unclear": 197, + "CSETv1_Annotator-2:AI tools and methods:Natural Language Processesing": 196, + "CSETv0:Sector of Deployment:Transportation and storage": 191, + "CSETv1:Sector of Deployment:Arts, entertainment and recreation": 190, + "CSETv1_Annotator-3:Location Region:North America": 187, + "CSETv1_Annotator-3:Tangible Harm:tangible harm definitively occurred": 187, + "CSETv0:AI Applications:Facial recognition": 185, + "CSETv1_Annotator-3:Location Country (two letters):US": 185, + "CSETv1_Annotator-1:Location Region:Europe": 183, + "CSETv1_Annotator-2:Date of Incident Year:2016": 183, + "CSETv1_Annotator-3:Annotator’s AI special interest intangible harm assessment:yes": 183, + "CSETv1:Date of Incident Year:2018": 181, + "CSETv1:Tangible Harm:imminent risk of tangible harm (near miss) did occur": 180, + "CSETv1_Annotator-1:Producer Test in Operational Conditions:yes": 180, + "CSETv1_Annotator-2:User Test in Operational Conditions:yes": 180, + "CSETv1_Annotator-3:Physical Objects:yes": 180, + "CSETv1_Annotator-3:Special Interest Intangible Harm:yes": 180, + "CSETv0:Harm Type:Harm to civil liberties": 179, + "CSETv1:Location Region:Asia": 177, + "CSETv0:Nature of End User:Expert": 176, + "CSETv1_Annotator-3:Sector of Deployment:information and communication": 176, + "CSETv1_Annotator-2:Harm Distribution Basis:race": 174, + "CSETv1_Annotator-3:Protected Characteristic:yes": 170, + "CSETv1:Rights Violation:yes": 168, + "CSETv1_Annotator-1:Location Region:Asia": 163, + "CSETv1_Annotator-1:Involving Minor:yes": 157, + "CSETv0:Sector of Deployment:Arts, entertainment and recreation": 156, + "CSETv1_Annotator-3:AI Harm Level:AI tangible harm event": 156, + "CSETv1_Annotator-1:AI Harm Level:AI tangible harm near-miss": 155, + "CSETv1_Annotator-1:Tangible Harm:non-imminent risk of tangible harm (an issue) occurred": 155, + "CSETv0:Sector of Deployment:Public administration and defence": 154, + "CSETv1:Sector of Deployment:wholesale and retail trade": 154, + "CSETv1_Annotator-1:Data Inputs:camera input": 154, + "CSETv1_Annotator-1:Sector of Deployment:wholesale and retail trade": 154, + "CSETv1:Location Region:Europe": 152, + "CSETv1_Annotator-2:Data Inputs:video": 150, + "CSETv1_Annotator-3:Tangible Harm:no tangible harm, near-miss, or issue": 149, + "CSETv0:AI Applications:autonomous driving": 147, + "CSETv1:Data Inputs:sensor data": 147, + "CSETv1_Annotator-2:Tangible Harm:non-imminent risk of tangible harm (an issue) occurred": 147, + "CSETv1_Annotator-1:Date of Incident Year:2023": 145, + "CSETv0:Harm Distribution Basis:Sex": 144, + "CSETv1:Data Inputs:audio": 143, + "CSETv1:Data Inputs:lidar": 143, + "CSETv1_Annotator-1:Entertainment Industry:maybe": 142, + "CSETv0:Public Sector Deployment:true": 141, + "CSETv1_Annotator-3:Date of Incident Year:2017": 141, + "CSETv1:Entertainment Industry:yes": 140, + "CSETv0:AI Techniques:Facial recognition": 139, + "CSETv1:Infrastructure Sectors:transportation": 139, + "CSETv1_Annotator-3:Location Region:Global": 138, + "CSETv0:Named Entities:Amazon": 136, + "CSETv1_Annotator-1:AI tools and methods:natural language processing": 135, + "GMF:Potential AI Technical Failure:Generalization Failure": 135, + "CSETv1_Annotator-1:Data Inputs:facial images": 134, + "CSETv0:AI Techniques:Natural language processing": 133, + "CSETv0:Harm Type:Harm to physical property": 132, + "CSETv1_Annotator-1:AI Harm Level:AI tangible harm issue": 132, + "CSETv1:AI Task:facial recognition": 131, + "CSETv1:Data Inputs:radar": 131, + "CSETv0:Technology Purveyor:Amazon": 129, + "CSETv1:Producer Test in Operational Conditions:yes": 129, + "CSETv1_Annotator-1:Date of Incident Year:2020": 129, + "CSETv1_Annotator-2:AI tools and methods:computer vision": 129, + "CSETv1_Annotator-1:Date of Incident Year:2021": 128, + "CSETv1_Annotator-1:Date of Incident Year:2022": 128, + "CSETv1_Annotator-2:Public Sector Deployment:yes": 128, + "CSETv1_Annotator-2:AI Harm Level:AI tangible harm event": 126, + "CSETv1_Annotator-2:Location Region:Asia": 126, + "CSETv1:Sector of Deployment:administrative and support service activities": 125, + "CSETv1_Annotator-3:Autonomy Level:Autonomy3": 125, + "CSETv1_Annotator-1:Rights Violation:yes": 124, + "CSETv1_Annotator-1:Sector of Deployment:public administration": 123, + "CSETv0:Infrastructure Sectors:Transportation": 122, + "CSETv0:Level of Autonomy:Low": 122, + "CSETv1:Sector of Deployment:public administration": 117, + "CSETv1_Annotator-1:AI Task:facial recognition": 117, + "CSETv1_Annotator-2:Harm Distribution Basis:sex": 116, + "CSETv1_Annotator-1:Harm Distribution Basis:nation of origin, citizenship, immigrant status": 115, + "CSETv1_Annotator-1:Sector of Deployment:professional, scientific and technical activities": 115, + "CSETv0:Severity:Unclear/unknown": 114, + "CSETv1:Sector of Deployment:law enforcement": 114, + "CSETv1_Annotator-1:AI Task:navigation": 114, + "CSETv0:AI Applications:decision support": 113, + "CSETv0:System Developer:Amazon": 112, + "CSETv1:AI Task:navigation": 112, + "CSETv1_Annotator-2:Involving Minor:yes": 111, + "CSETv1_Annotator-2:Detrimental Content:yes": 110, + "CSETv0:Physical System:Consumer device": 108, + "CSETv1_Annotator-1:Clear link to Technology:maybe": 108, + "CSETv1_Annotator-1:Intentional Harm:Yes. Intentionally designed to perform harm and did create intended harm": 107, + "CSETv1_Annotator-2:Sector of Deployment:transportation and storage": 107, + "CSETv1_Annotator-3:AI tools and methods:unclear": 107, + "CSETv0:AI Applications:recommendation engine": 106, + "CSETv1:Impact on Critical Services:yes": 106, + "CSETv1:Involving Minor:yes": 106, + "CSETv1_Annotator-3:Data Inputs:text": 106, + "CSETv0:Severity:Moderate": 105, + "CSETv1:Clear link to Technology:no": 105, + "CSETv1:AI Harm Level:AI tangible harm near-miss": 104, + "CSETv1:Autonomy Level:unclear": 104, + "CSETv1_Annotator-1:AI tools and methods:facial recognition": 104, + "CSETv1_Annotator-2:Data Inputs:images": 104, + "CSETv0:Lives Lost:true": 103, + "CSETv0:Technology Purveyor:Tesla": 103, + "CSETv1:Deployed:no": 103, + "CSETv1_Annotator-1:AI Task:autonomous navigation": 103, + "CSETv0:Severity:Severe": 102, + "CSETv1_Annotator-1:Autonomy Level:unclear": 102, + "CSETv1_Annotator-1:Date of Incident Year:2014": 102, + "CSETv1_Annotator-2:Sector of Deployment:wholesale and retail trade": 102, + "CSETv1_Annotator-2:Location Region:Europe": 101, + "CSETv1:Data Inputs:facial images": 100, + "CSETv1:Harm Distribution Basis:nation of origin, citizenship, immigrant status": 100, + "CSETv1_Annotator-3:Harm Distribution Basis:race": 100, + "CSETv1_Annotator-1:AI Task:text generation": 99, + "CSETv1_Annotator-2:Sector of Deployment:administrative and support service activities": 99, + "CSETv1:Tangible Harm:non-imminent risk of tangible harm (an issue) occurred": 97, + "CSETv1_Annotator-1:AI Task:semi-autonomous navigation": 96, + "CSETv1:Data Inputs:camera input": 95, + "CSETv1:Date of Incident Year:2014": 95, + "CSETv1_Annotator-2:Date of Incident Year:2015": 95, + "CSETv1_Annotator-2:Rights Violation:yes": 94, + "GMF:Potential AI Technical Failure:Context Misidentification": 94, + "CSETv1:AI System:maybe": 92, + "CSETv1:Sector of Deployment:human health and social work activities": 92, + "CSETv1_Annotator-1:Detrimental Content:maybe": 91, + "CSETv1_Annotator-3:User Test in Operational Conditions:yes": 91, + "CSETv0:AI Applications:image classification": 90, + "CSETv0:Sector of Deployment:Administrative and support service activities": 90, + "CSETv1_Annotator-1:Harm Distribution Basis:religion": 90, + "CSETv1_Annotator-1:Location Region:Oceania": 90, + "CSETv1_Annotator-3:Rights Violation:yes": 90, + "CSETv1_Annotator-2:Data Inputs:audio": 89, + "CSETv0:Level of Autonomy:Unclear/unknown": 88, + "CSETv1:Harm Distribution Basis:religion": 88, + "CSETv1_Annotator-1:AI Task:content generation": 88, + "CSETv1_Annotator-1:AI System:maybe": 86, + "CSETv1_Annotator-1:Data Inputs:radar": 86, + "CSETv0:AI Applications:Natural language processing": 85, + "CSETv0:Intent:Deliberate or expected": 85, + "CSETv1_Annotator-1:Harm Distribution Basis:financial means": 84, + "CSETv1:Date of Incident Year:2020": 83, + "CSETv1_Annotator-2:Report, Test, or Study of data:yes": 83, + "CSETv1:Date of Incident Year:2021": 82, + "CSETv1:Rights Violation:maybe": 82, + "CSETv1_Annotator-2:AI Harm Level:AI tangible harm issue": 82, + "CSETv1_Annotator-3:Clear link to Technology:no": 82, + "CSETv1_Annotator-3:Sector of Deployment:law enforcement": 82, + "CSETv1_Annotator-1:Producer Test in Controlled Conditions:yes": 80, + "CSETv0:Named Entities:Tesla": 79, + "CSETv1_Annotator-1:AI Task:chat bot": 79, + "CSETv1_Annotator-1:Data Inputs:spatial data": 79, + "CSETv1_Annotator-1:Deployed:no": 79, + "CSETv0:AI Applications:image recognition": 78, + "CSETv1:Date of Incident Year:2015": 78, + "CSETv1_Annotator-1:Data Inputs:search queries": 78, + "CSETv1_Annotator-3:Public Sector Deployment:yes": 78, + "CSETv1:AI tools and methods:Natural Language Processesing": 77, + "CSETv1:Date of Incident Year:2023": 77, + "CSETv1_Annotator-1:AI tools and methods:machine learning": 77, + "CSETv1_Annotator-1:Harm Distribution Basis:sexual orientation or gender identity": 77, + "CSETv0:AI Techniques:Unclear": 76, + "CSETv1:Lives Lost:1": 76, + "CSETv1_Annotator-2:Deployed:no": 76, + "CSETv0:AI Techniques:Tesla Autopilot": 75, + "CSETv0:System Developer:Tesla": 75, + "CSETv1:Infrastructure Sectors:healthcare and public health": 75, + "CSETv1_Annotator-1:Deployed:maybe": 75, + "CSETv1_Annotator-2:Date of Incident Year:2018": 74, + "CSETv1_Annotator-2:Infrastructure Sectors:transportation": 74, + "CSETv1:Harm Distribution Basis:sexual orientation or gender identity": 73, + "CSETv1_Annotator-1:Data Inputs:radar input": 73, + "CSETv1_Annotator-2:Autonomy Level:unclear": 73, + "CSETv1:Sector of Deployment:professional, scientific and technical activities": 71, + "CSETv1_Annotator-1:Date of Incident Year:2015": 71, + "CSETv1_Annotator-1:Sector of Deployment:financial and insurance activities": 71, + "CSETv1:Intentional Harm:Yes. Intentionally designed to perform harm and did create intended harm": 70, + "CSETv1_Annotator-3:Sector of Deployment:transportation and storage": 70, + "CSETv0:Relevant AI functions:Unclear": 69, + "CSETv1:Producer Test in Controlled Conditions:yes": 69, + "CSETv1:Sector of Deployment:financial and insurance activities": 69, + "GMF:Known AI Technology:Face Detection": 69, + "CSETv1:Data Inputs:search queries": 68, + "CSETv1_Annotator-1:Data Inputs:voice input": 68, + "CSETv1_Annotator-1:Sector of Deployment:human health and social work activities": 68, + "CSETv1_Annotator-2:Infrastructure Sectors:healthcare and public health": 68, + "CSETv1_Annotator-2:Sector of Deployment:human health and social work activities": 68, + "CSETv1:Physical System Type:Manufacturing Robot": 67, + "CSETv1:Sector of Deployment:manufacturing": 67, + "CSETv1_Annotator-2:Sector of Deployment:manufacturing": 67, + "CSETv1:AI Harm Level:AI tangible harm issue": 66, + "CSETv1:Injuries:1": 66, + "CSETv1_Annotator-1:Data Inputs:user queries": 66, + "CSETv1_Annotator-1:Lives Lost:1": 66, + "CSETv1_Annotator-1:Location Country (two letters):AU": 66, + "CSETv1_Annotator-2:Producer Test in Controlled Conditions:yes": 66, + "CSETv1_Annotator-3:Data Inputs:video": 65, + "CSETv0:Named Entities:Microsoft": 64, + "CSETv1:Data Inputs:speech": 64, + "CSETv1:Location Region:Oceania": 64, + "CSETv1_Annotator-1:AI Task:self-driving": 64, + "CSETv1_Annotator-1:Data Inputs:geospatial data": 64, + "CSETv1_Annotator-2:AI System:maybe": 64, + "CSETv1_Annotator-2:Date of Incident Year:2023": 64, + "CSETv1_Annotator-2:Sector of Deployment:financial and insurance activities": 64, + "CSETv1:Operating Conditions:operationally representative": 63, + "CSETv1:Report, Test, or Study of data:yes": 63, + "CSETv0:AI Applications:self-driving vehicle": 62, + "CSETv0:AI Techniques:Deep learning": 62, + "CSETv0:Named Entities:Knightscope": 62, + "CSETv0:Named Entities:Knightscope K5": 62, + "CSETv0:System Developer:Knightscope": 62, + "CSETv0:Technology Purveyor:Knightscope": 62, + "CSETv1:AI Task:patrolling": 62, + "CSETv1_Annotator-1:AI Task:virtual assistant technology": 62, + "CSETv1_Annotator-1:Physical System Type:rocket/egg shaped, 300 pound, 5 ft. tall security robot": 62, + "CSETv1_Annotator-2:Data Inputs:lidar": 62, + "CSETv1_Annotator-2:Location Region:Oceania": 62, + "CSETv1_Annotator-3:Location Region:Asia": 62, + "CSETv1:Data Inputs:video input": 61, + "CSETv1:Data Inputs:voice": 61, + "CSETv1_Annotator-1:Injuries:1": 61, + "CSETv1_Annotator-2:Data Inputs:voice": 61, + "CSETv0:Sector of Deployment:Financial and insurance activities": 60, + "CSETv1_Annotator-1:Data Inputs:lidar": 60, + "CSETv1_Annotator-1:Data Inputs:user prompts": 60, + "CSETv1_Annotator-2:Intentional Harm:Yes. Intentionally designed to perform harm and did create intended harm": 60, + "CSETv1_Annotator-2:Sector of Deployment:law enforcement": 60, + "CSETv0:Harm Distribution Basis:National origin or immigrant status": 59, + "CSETv1:Location Country (two letters):RU": 59, + "CSETv1:Physical System Type:none": 59, + "CSETv1_Annotator-1:AI tools and methods:voice recognition": 59, + "CSETv1_Annotator-1:Data Inputs:speech": 59, + "CSETv1_Annotator-1:Report, Test, or Study of data:maybe": 59, + "CSETv1_Annotator-2:Physical System Type:none": 59, + "GMF:Known AI Technical Failure:Unsafe Exposure or Access": 59, + "CSETv1:AI Task:virtual assistant technology": 58, + "CSETv1:Data Inputs:photos": 58, + "CSETv0:AI Techniques:environmental sensing": 57, + "CSETv0:Named Entities:Stanford Shopping Center": 57, + "CSETv1:AI Task:security": 57, + "CSETv1:Data Inputs:road data": 57, + "CSETv1:Physical System Type:rocket/egg shaped, 300 pound, 5 ft. tall security robot": 57, + "CSETv1_Annotator-1:AI Task:patrolling": 57, + "CSETv1_Annotator-1:AI Task:security": 57, + "CSETv1_Annotator-2:AI tools and methods:Text to Speach": 57, + "CSETv1_Annotator-2:Data Inputs:road data": 57, + "CSETv1_Annotator-2:Operating Conditions:operationally representative": 57, + "CSETv1_Annotator-1:Location Country (two letters):CN": 56, + "CSETv1_Annotator-3:Date of Incident Year:2016": 56, + "GMF:Potential AI Technical Failure:Dataset Imbalance": 56, + "CSETv0:AI Techniques:open-source": 55, + "CSETv0:Technology Purveyor:Microsoft": 55, + "CSETv1:AI Task:object detection": 55, + "CSETv1:AI tools and methods:facial recognition": 55, + "CSETv1:AI tools and methods:voice recognition": 55, + "CSETv1_Annotator-1:Physical Objects:maybe": 55, + "CSETv1_Annotator-1:Report, Test, or Study of data:yes": 55, + "CSETv1_Annotator-1:Rights Violation:maybe": 55, + "CSETv1_Annotator-1:Sector of Deployment:manufacturing": 55, + "CSETv1_Annotator-2:AI tools and methods:Audio Transcription": 55, + "CSETv1_Annotator-2:Physical System Type:Manufacturing Robot": 55, + "GMF:Known AI Technical Failure:Distributional Bias": 55, + "GMF:Known AI Technology:Recurrent Neural Network": 55, + "CSETv1:AI Task:autonomous driving": 54, + "CSETv1:AI tools and methods:neural networks": 54, + "CSETv1_Annotator-1:Data Inputs:audio inputs": 54, + "GMF:Potential AI Technology:Regression": 54, + "CSETv0:Harm Distribution Basis:Financial means": 53, + "CSETv1:AI Task:self-driving": 53, + "CSETv1:Date of Incident Year:2013": 53, + "CSETv1:Location Country (two letters):": 53, + "CSETv1_Annotator-1:AI Task:image identification": 53, + "CSETv1_Annotator-2:Infrastructure Sectors:communications": 53, + "GMF:Known AI Technology:Distributional Learning": 53, + "CSETv0:AI Techniques:image classification": 52, + "CSETv1:Clear link to Technology:maybe": 52, + "CSETv1:Data Inputs:spatial data": 52, + "CSETv1_Annotator-1:Date of Incident Year:2019": 52, + "CSETv1_Annotator-1:Sector of Deployment:Education": 52, + "GMF:Potential AI Technical Failure:Underspecification": 52, + "CSETv0:Location:Palo Alto, CA": 51, + "CSETv0:Named Entities:Apple": 51, + "CSETv0:Named Entities:Joshua Brown": 51, + "CSETv0:Technology Purveyor:Apple": 51, + "CSETv1:AI Task:object recognition": 51, + "CSETv1:AI tools and methods:audio transcription": 51, + "CSETv1:AI tools and methods:machine learning": 51, + "CSETv1:Data Inputs:infrared camera": 51, + "CSETv1:Data Inputs:personal data": 51, + "CSETv1_Annotator-1:Data Inputs:infrared camera": 51, + "CSETv1_Annotator-2:Impact on Critical Services:yes": 51, + "CSETv1_Annotator-2:Physical System Type:Amazon Alexa": 51, + "CSETv1_Annotator-1:Special Interest Intangible Harm:maybe": 50, + "GMF:Known AI Technology:Convolutional Neural Network": 50, + "CSETv0:AI Applications:chatbot": 49, + "CSETv0:Harm Distribution Basis:Religion": 49, + "CSETv1:AI Task:chat bot": 49, + "CSETv1_Annotator-1:AI Task:smart suggestions": 49, + "CSETv1:Multiple AI Interaction:yes": 48, + "CSETv1_Annotator-1:AI Harm Level:unclear": 48, + "CSETv1_Annotator-1:Date of Incident Year:2013": 48, + "CSETv1_Annotator-1:Infrastructure Sectors:transportation": 48, + "CSETv1_Annotator-1:Multiple AI Interaction:yes": 48, + "CSETv1_Annotator-3:AI Harm Level:unclear": 48, + "CSETv1_Annotator-3:Data Inputs:audio": 48, + "CSETv1_Annotator-3:Location Region:Europe": 48, + "CSETv1:Data Inputs:image alt tags": 47, + "CSETv1_Annotator-1:Data Inputs:image alt tags": 47, + "CSETv1_Annotator-2:AI Task:autonomous driving": 47, + "CSETv1_Annotator-3:Impact on Critical Services:yes": 47, + "CSETv0:AI Techniques:autonomous vehicle": 46, + "CSETv1:AI Task:text generation": 46, + "CSETv1_Annotator-1:Impact on Critical Services:yes": 46, + "CSETv1_Annotator-2:Location Country (two letters):": 46, + "CSETv0:System Developer:Apple": 45, + "CSETv1:AI tools and methods:image mapping": 45, + "CSETv1:AI tools and methods:point mapping": 45, + "CSETv1:Data Inputs:dot projector": 45, + "CSETv1:Location Country (two letters):CN": 45, + "CSETv1:Physical System Type:Apple iPhone X": 45, + "CSETv1_Annotator-1:AI Task:image generation": 45, + "CSETv1_Annotator-1:AI tools and methods:facial reconstruction": 45, + "CSETv1_Annotator-1:AI tools and methods:image mapping": 45, + "CSETv1_Annotator-1:AI tools and methods:image reconstruction": 45, + "CSETv1_Annotator-1:AI tools and methods:point mapping": 45, + "CSETv1_Annotator-1:Data Inputs:dot projector": 45, + "CSETv1_Annotator-1:Physical System Type:Apple iPhone X": 45, + "CSETv1_Annotator-2:Date of Incident Year:2020": 45, + "CSETv1_Annotator-2:Producer Test in Operational Conditions:yes": 45, + "CSETv1_Annotator-3:AI System:no": 45, + "CSETv1_Annotator-3:Data Inputs:radar": 45, + "CSETv1_Annotator-3:Harm Distribution Basis:sex": 45, + "CSETv1_Annotator-3:Harm Distribution Basis:sexual orientation or gender identity": 45, + "GMF:Known AI Goal:Autonomous Driving": 45, + "GMF:Potential AI Technical Failure:Unauthorized Data": 45, + "GMF:Potential AI Technology:Image Segmentation": 45, + "CSETv0:AI Applications:voice recognition": 44, + "CSETv0:System Developer:Microsoft": 44, + "CSETv1:AI Task:search optimization": 44, + "CSETv1:Data Inputs:GPS": 44, + "CSETv1:Harm Distribution Basis:financial means": 44, + "CSETv1:Intentional Harm:unclear": 44, + "CSETv1_Annotator-2:Entertainment Industry:yes": 44, + "CSETv1_Annotator-3:Harm Distribution Basis:religion": 44, + "GMF:Potential AI Technology:Classification": 44, + "GMF:Potential AI Technology:Convolutional Neural Network": 44, + "CSETv0:AI Techniques:language recognition": 43, + "CSETv1:AI Task:autonomous navigation": 43, + "CSETv1:Date of Incident Year:2011": 43, + "CSETv1:Date of Incident Year:2019": 43, + "CSETv1_Annotator-1:AI Task:content moderation": 43, + "CSETv1_Annotator-1:AI Task:search optimization": 43, + "CSETv1_Annotator-1:AI Task:search suggestion": 43, + "CSETv1_Annotator-1:Location Country (two letters):DE": 43, + "CSETv1_Annotator-2:AI Task:self driving": 43, + "CSETv1_Annotator-2:Multiple AI Interaction:yes": 43, + "GMF:Known AI Technology:Language Modeling": 43, + "CSETv0:Location:Los Angeles, CA": 42, + "CSETv1:AI tools and methods:unclear": 42, + "CSETv1_Annotator-1:Date of Incident Year:2012": 42, + "CSETv1_Annotator-1:Sector of Deployment:administrative and support service activities": 42, + "CSETv1_Annotator-3:Detrimental Content:yes": 42, + "CSETv0:Named Entities:China": 41, + "CSETv1:AI Task:search suggestion": 41, + "CSETv1:Date of Incident Year:2012": 41, + "CSETv1:Entertainment Industry:maybe": 41, + "CSETv1:Harm Distribution Basis:disability": 41, + "CSETv1:Location Country (two letters):IN": 41, + "CSETv1:Sector of Deployment:Education": 41, + "CSETv1_Annotator-1:Data Inputs:age": 41, + "CSETv1_Annotator-1:Harm Distribution Basis:disability": 41, + "CSETv1_Annotator-1:Lives Lost:2": 41, + "CSETv1_Annotator-2:Date of Incident Year:2014": 41, + "CSETv1_Annotator-2:Location Country (two letters):IN": 41, + "CSETv1_Annotator-3:AI System:maybe": 41, + "CSETv1_Annotator-3:Autonomy Level:Autonomy2": 41, + "GMF:Potential AI Technical Failure:Underfitting": 41, + "CSETv0:AI Applications:environmental sensing": 40, + "CSETv0:AI Techniques:virtual assistant": 40, + "CSETv0:Harm Distribution Basis:Sexual orientation or gender identity": 40, + "CSETv1:AI Task:Image search": 40, + "CSETv1:Location Country (two letters):AU": 40, + "CSETv1_Annotator-1:Harm Distribution Basis:ideology": 40, + "CSETv1_Annotator-2:AI Task:Personal Assistant": 40, + "CSETv1_Annotator-2:Date of Incident Year:2021": 40, + "CSETv1_Annotator-3:Tangible Harm:imminent risk of tangible harm (near miss) did occur": 40, + "CSETv0:AI Techniques:Amazon Alexa": 39, + "CSETv0:Location:Australia": 39, + "CSETv0:Named Entities:Alan Tudge": 39, + "CSETv0:Named Entities:Centrelink": 39, + "CSETv0:Named Entities:Centrelink Master Program": 39, + "CSETv0:Named Entities:Department of Human Services": 39, + "CSETv0:Named Entities:Richard Glenn": 39, + "CSETv0:System Developer:Centrelink Master Program": 39, + "CSETv0:Technology Purveyor:Centrelink Master Program": 39, + "CSETv0:Technology Purveyor:Department of Human Services": 39, + "CSETv1:Data Inputs:income data": 39, + "CSETv1:Data Inputs:tabular": 39, + "CSETv1:Data Inputs:tax reports": 39, + "CSETv1:Detrimental Content:maybe": 39, + "CSETv1_Annotator-1:Data Inputs:income data": 39, + "CSETv1_Annotator-1:Data Inputs:laser input": 39, + "CSETv1_Annotator-1:Data Inputs:tax reports": 39, + "CSETv1_Annotator-1:Data Inputs:words": 39, + "CSETv1_Annotator-1:Infrastructure Sectors:government facilities": 39, + "CSETv1_Annotator-2:Data Inputs:amazon account": 39, + "CSETv1_Annotator-2:Infrastructure Sectors:government facilities": 39, + "CSETv1_Annotator-2:Lives Lost:1": 39, + "CSETv1_Annotator-2:Location Country (two letters):AU": 39, + "CSETv1_Annotator-3:Autonomy Level:unclear": 39, + "CSETv1_Annotator-3:Data Inputs:lidar": 39, + "CSETv1_Annotator-3:Sector of Deployment:wholesale and retail trade": 39, + "GMF:Potential AI Technical Failure:Hardware Failure": 39, + "GMF:Potential AI Technical Failure:Lack of Capability Control": 39, + "GMF:Potential AI Technology:Ensemble Aggregation": 39, + "CSETv0:AI Techniques:natural language processing model": 38, + "CSETv1:AI Task:predict recidivism": 38, + "CSETv1:AI tools and methods:object detection": 38, + "CSETv1_Annotator-1:AI Task:predict recidivism": 38, + "CSETv1_Annotator-1:AI tools and methods:computer vision": 38, + "CSETv1_Annotator-2:AI tools and methods:Machine Learning": 38, + "CSETv1_Annotator-2:Sector of Deployment:other": 38, + "GMF:Potential AI Technical Failure:Data or Labelling Noise": 38, + "CSETv0:AI Techniques:law enforcement algorithm": 37, + "CSETv0:Infrastructure Sectors:Government facilities": 37, + "CSETv1:AI Task:Rank Applicants": 37, + "CSETv1:AI Task:translation": 37, + "CSETv1:Data Inputs:Amazon account information": 37, + "CSETv1:Deployed:maybe": 37, + "CSETv1_Annotator-1:AI Task:search result ranking": 37, + "CSETv1_Annotator-1:Data Inputs:137 questionnaire responses": 37, + "CSETv1_Annotator-1:Data Inputs:Amazon account information": 37, + "CSETv1_Annotator-2:AI Task:Rank Applicants": 37, + "CSETv1_Annotator-3:Date of Incident Year:2020": 37, + "GMF:Known AI Technical Failure:Misuse": 37, + "GMF:Known AI Technology:Automatic Speech Recognition": 37, + "CSETv0:AI Applications:language translation": 36, + "CSETv1:AI Harm Level:unclear": 36, + "CSETv1:Data Inputs:Facebook posts": 36, + "CSETv1:Data Inputs:air quality": 36, + "CSETv1:Date of Incident Year:2022": 36, + "CSETv1:Infrastructure Sectors:financial services": 36, + "CSETv1_Annotator-1:AI Task:transportation": 36, + "CSETv1_Annotator-1:Location Country (two letters):IN": 36, + "CSETv1_Annotator-1:Location Country (two letters):RU": 36, + "CSETv1_Annotator-1:Physical System Type:Tesla Model S": 36, + "CSETv1_Annotator-3:Entertainment Industry:yes": 36, + "GMF:Known AI Goal:Face Recognition": 36, + "CSETv0:AI Applications:Interpreting traffic patterns": 35, + "CSETv0:AI Applications:image processing": 35, + "CSETv0:Harm Distribution Basis:Geography": 35, + "CSETv0:Infrastructure Sectors:Information technology": 35, + "CSETv0:Named Entities:San Diego TV": 35, + "CSETv1:AI Task:prediction": 35, + "CSETv1:AI Task:self driving": 35, + "CSETv1:AI Task:semi-autonomous navigation": 35, + "CSETv1:AI Task:surveillance": 35, + "CSETv1:Data Inputs:Crime Records": 35, + "CSETv1:Harm Distribution Basis:geography": 35, + "CSETv1:Location Country (two letters):IE": 35, + "CSETv1:Physical System Type:Amazon Alexa Echo Dot speaker": 35, + "CSETv1:Physical System Type:Volvo XC90 SUV": 35, + "CSETv1_Annotator-1:AI Task:image modification": 35, + "CSETv1_Annotator-1:AI tools and methods:audio transcription": 35, + "CSETv1_Annotator-1:Location Country (two letters):IE": 35, + "CSETv1_Annotator-1:Physical System Type:Amazon Alexa Echo Dot speaker": 35, + "CSETv1_Annotator-1:Sector of Deployment:defense": 35, + "CSETv1_Annotator-2:AI Harm Level:AI tangible harm near-miss": 35, + "CSETv1_Annotator-2:Deployed:maybe": 35, + "CSETv1_Annotator-3:Data Inputs:face image": 35, + "CSETv1_Annotator-3:Date of Incident Year:2021": 35, + "GMF:Known AI Goal:AI Voice Assistant": 35, + "GMF:Known AI Technology:Acoustic Fingerprint": 35, + "GMF:Known AI Technology:Neural Network": 35, + "GMF:Potential AI Technical Failure:Inadequate Anonymization": 35, + "GMF:Potential AI Technology:Distributional Learning": 35, + "CSETv0:AI Techniques:radar": 34, + "CSETv0:Harm Type:Harm to intangible property": 34, + "CSETv0:Sector of Deployment:Professional, scientific and technical activities": 34, + "CSETv1:AI Task:Driving": 34, + "CSETv1_Annotator-1:Data Inputs:photos": 34, + "CSETv1_Annotator-1:Protected Characteristic:maybe": 34, + "CSETv1_Annotator-1:Public Sector Deployment:maybe": 34, + "CSETv1_Annotator-2:AI Task:recommender": 34, + "CSETv1_Annotator-2:Sector of Deployment:Arts, entertainment and recreation": 34, + "GMF:Potential AI Technical Failure:Overfitting": 34, + "CSETv0:Location:Edinburgh, Scotland": 33, + "CSETv0:Named Entities:Edinburgh": 33, + "CSETv1:AI Task:resume screening": 33, + "CSETv1:Data Inputs:Resume": 33, + "CSETv1:Data Inputs:traffic": 33, + "CSETv1:Harm Distribution Basis:ideology": 33, + "CSETv1_Annotator-1:AI Task:obstacle avoidance": 33, + "CSETv1_Annotator-1:AI Task:recruitment": 33, + "CSETv1_Annotator-1:AI Task:resume screening": 33, + "CSETv1_Annotator-1:Data Inputs:resumes": 33, + "CSETv1_Annotator-2:AI tools and methods:Natural language processing": 33, + "CSETv1_Annotator-2:Data Inputs:Resume": 33, + "CSETv1_Annotator-2:Data Inputs:personal data": 33, + "CSETv1_Annotator-2:Data Inputs:photos": 33, + "CSETv1_Annotator-2:Data Inputs:traffic": 33, + "CSETv1_Annotator-2:Date of Incident Year:2013": 33, + "CSETv1_Annotator-2:Infrastructure Sectors:unclear": 33, + "CSETv1_Annotator-2:Location Country (two letters):IE": 33, + "CSETv1_Annotator-2:Rights Violation:maybe": 33, + "CSETv1_Annotator-3:Tangible Harm:unclear": 33, + "GMF:Known AI Technology:Generative Adversarial Network": 33, + "GMF:Potential AI Technical Failure:Misinformation Generation Hazard": 33, + "CSETv0:AI Applications:content filtering": 32, + "CSETv0:AI Applications:environment sensing": 32, + "CSETv0:AI Applications:self-driving": 32, + "CSETv0:Named Entities:Facebook": 32, + "CSETv0:System Developer:Facebook": 32, + "CSETv0:Technology Purveyor:Facebook": 32, + "CSETv1:AI tools and methods:human language technology": 32, + "CSETv1:Location Country (two letters):DE": 32, + "CSETv1_Annotator-1:AI Task:deepfake video generation": 32, + "CSETv1:AI Task:content generation": 31, + "CSETv1:AI Task:deepfake video generation": 31, + "CSETv1:AI tools and methods:search engine optimization": 31, + "CSETv1:Data Inputs:age": 31, + "CSETv1:Data Inputs:personal information": 31, + "CSETv1:Infrastructure Sectors:defense-industrial base": 31, + "CSETv1:Sector of Deployment:defense": 31, + "CSETv1_Annotator-1:AI Task:image search": 31, + "CSETv1_Annotator-1:AI tools and methods:neural networks": 31, + "CSETv1_Annotator-1:AI tools and methods:search engine optimization": 31, + "CSETv1_Annotator-1:Sector of Deployment:other": 31, + "CSETv1_Annotator-2:Location Country (two letters):DE": 31, + "GMF:Known AI Goal:Autonomous Drones": 31, + "CSETv0:AI Applications:forecasting": 30, + "CSETv0:AI Applications:stock trading": 30, + "CSETv0:AI Techniques:stock market algorithm": 30, + "CSETv0:Financial Cost:Short term: $1 trillion unclear of long-term impact": 30, + "CSETv0:Infrastructure Sectors:Financial services": 30, + "CSETv0:Location:UK/USA": 30, + "CSETv0:Location:Washington, D.C.": 30, + "CSETv0:Named Entities:Chicago Merchant Exchange": 30, + "CSETv0:Named Entities:Dow Jones Industrial Index": 30, + "CSETv0:Named Entities:Electronic Privacy Information Center (EPIC)": 30, + "CSETv0:Named Entities:Mountain View": 30, + "CSETv0:Named Entities:Navinder Singh Saroa": 30, + "CSETv0:Technology Purveyor:Navinder Singh Sarao": 30, + "CSETv1:AI Task:autocomplete": 30, + "CSETv1:AI Task:content moderation": 30, + "CSETv1:AI Task:recommender": 30, + "CSETv1:Data Inputs:radar input": 30, + "CSETv1:Data Inputs:stock data": 30, + "CSETv1:Data Inputs:stock orders": 30, + "CSETv1:Data Inputs:stock prices": 30, + "CSETv1:Data Inputs:thermal imaging": 30, + "CSETv1:Data Inputs:words": 30, + "CSETv1:Date of Incident Year:2010": 30, + "CSETv1:Harm Distribution Basis:age": 30, + "CSETv1_Annotator-1:AI Task:surveillance": 30, + "CSETv1_Annotator-1:Data Inputs:Youtube videos": 30, + "CSETv1_Annotator-1:Data Inputs:air quality data": 30, + "CSETv1_Annotator-1:Data Inputs:personal information": 30, + "CSETv1_Annotator-1:Data Inputs:stock orders": 30, + "CSETv1_Annotator-1:Data Inputs:stock prices": 30, + "CSETv1_Annotator-1:Data Inputs:thermal imaging": 30, + "CSETv1_Annotator-1:Date of Incident Year:2010": 30, + "CSETv1_Annotator-1:Infrastructure Sectors:financial services": 30, + "CSETv1_Annotator-2:Data Inputs:stock data": 30, + "CSETv1_Annotator-2:Date of Incident Year:2010": 30, + "CSETv1_Annotator-3:AI Task:Patrolling": 30, + "CSETv1_Annotator-3:Data Inputs:Facebook posts": 30, + "CSETv1_Annotator-3:Data Inputs:Unclear": 30, + "CSETv1_Annotator-3:Data Inputs:air quality": 30, + "CSETv1_Annotator-3:Data Inputs:thermal imaging": 30, + "CSETv1_Annotator-3:Physical System Type:autonomous security robot": 30, + "GMF:Known AI Goal:Automatic Stock Trading": 30, + "GMF:Potential AI Technical Failure:Gaming Vulnerability": 30, + "CSETv0:AI Applications:Content curation": 29, + "CSETv0:AI Techniques:GANs": 29, + "CSETv0:AI Techniques:RNNs": 29, + "CSETv0:Harm Type:Other:Reputational harm/social harm (libel and defamation)": 29, + "CSETv0:Location:Delhi, India": 29, + "CSETv0:Location:Unknown": 29, + "CSETv0:Location:Williston, FL": 29, + "CSETv0:Named Entities:Barack Obama": 29, + "CSETv0:Named Entities:Delhi Metro Rail Corporation": 29, + "CSETv0:Named Entities:Hyuandai Rotem": 29, + "CSETv0:Named Entities:Jordan Peele": 29, + "CSETv0:Named Entities:Tesla Autopilot": 29, + "CSETv0:Named Entities:Tesla Model S": 29, + "CSETv0:Named Entities:University of Washington": 29, + "CSETv0:System Developer:FakeApp": 29, + "CSETv0:System Developer:University of Washington": 29, + "CSETv0:Technology Purveyor:Delhi Metro Rail Corporation": 29, + "CSETv0:Technology Purveyor:Hyuandai Rotem": 29, + "CSETv0:Technology Purveyor:Jordan Peele": 29, + "CSETv0:Technology Purveyor:University of Washington": 29, + "CSETv1:AI Task:content ranking": 29, + "CSETv1:AI tools and methods:Sensor Data Processing": 29, + "CSETv1:Data Inputs:Train position/status": 29, + "CSETv1:Data Inputs:geospatial data": 29, + "CSETv1:Data Inputs:track sensors": 29, + "CSETv1:Infrastructure Sectors:communications": 29, + "CSETv1:Operating Conditions:Testing": 29, + "CSETv1:Physical System Type:Camera": 29, + "CSETv1:Physical System Type:Metro train": 29, + "CSETv1_Annotator-1:AI Task:autocomplete": 29, + "CSETv1_Annotator-1:AI Task:object detection": 29, + "CSETv1_Annotator-1:Annotator’s AI special interest intangible harm assessment:maybe": 29, + "CSETv1_Annotator-1:Date of Incident Year:2011": 29, + "CSETv1_Annotator-1:Location Region:--": 29, + "CSETv1_Annotator-1:Physical System Type:Automobile": 29, + "CSETv1_Annotator-1:Physical System Type:Metro train": 29, + "CSETv1_Annotator-2:AI Task:Deepfake": 29, + "CSETv1_Annotator-2:Data Inputs:Train position/status": 29, + "CSETv1_Annotator-2:Data Inputs:track sensors": 29, + "CSETv1_Annotator-2:Detrimental Content:maybe": 29, + "CSETv1_Annotator-2:Operating Conditions:Testing": 29, + "CSETv1_Annotator-2:Physical System Type:Metro train": 29, + "CSETv1_Annotator-3:AI Task:autocomplete": 29, + "CSETv1_Annotator-3:AI Task:content ranking": 29, + "CSETv1_Annotator-3:Date of Incident Year:2011": 29, + "GMF:Known AI Goal:Deepfake Video Generation": 29, + "GMF:Known AI Goal:Hate Speech Detection": 29, + "GMF:Known AI Technical Failure:Adversarial Data": 29, + "GMF:Potential AI Technical Failure:Distributional Bias": 29, + "GMF:Potential AI Technical Failure:Misuse": 29, + "GMF:Potential AI Technology:3D reconstruction": 29, + "GMF:Potential AI Technology:Other domain-specific approaches": 29, + "CSETv0:AI Applications:Text generation": 28, + "CSETv0:AI Applications:comprehension": 28, + "CSETv0:AI Applications:computer vision": 28, + "CSETv0:AI Applications:language output": 28, + "CSETv0:AI Techniques:content creation": 28, + "CSETv0:AI Techniques:driving lane detection": 28, + "CSETv0:AI Techniques:image recognition": 28, + "CSETv0:AI Techniques:language recognition natural language processing": 28, + "CSETv0:AI Techniques:object detection": 28, + "CSETv0:AI Techniques:scene segmentation": 28, + "CSETv0:Location:Cambridge, MA": 28, + "CSETv0:Location:Mountain View, CA": 28, + "CSETv0:Location:Storey County, Nevada": 28, + "CSETv0:Named Entities:Lexus": 28, + "CSETv0:Named Entities:MIT Media Lab": 28, + "CSETv0:Named Entities:Massachusetts Intstitute of Technology": 28, + "CSETv0:Named Entities:Norman": 28, + "CSETv0:Named Entities:Panasonic": 28, + "CSETv0:Named Entities:Reddit": 28, + "CSETv0:Named Entities:Santa Clara Valley Transportation Authority": 28, + "CSETv0:Named Entities:Tay": 28, + "CSETv0:Named Entities:Twitter": 28, + "CSETv0:Named Entities:Xiaoice": 28, + "CSETv0:System Developer:MIT Media Lab": 28, + "CSETv0:Technology Purveyor:MIT Media Lab": 28, + "CSETv0:Technology Purveyor:Twitter": 28, + "CSETv1:AI Task:Generate Captions": 28, + "CSETv1:AI Task:assembly": 28, + "CSETv1:AI Task:image identification": 28, + "CSETv1:AI Task:image interpretation": 28, + "CSETv1:AI Task:production": 28, + "CSETv1:AI tools and methods:prediction": 28, + "CSETv1:Data Inputs:Captions": 28, + "CSETv1:Data Inputs:image captions": 28, + "CSETv1:Data Inputs:laser input": 28, + "CSETv1:Data Inputs:video captions": 28, + "CSETv1:Date of Incident Year:2009": 28, + "CSETv1:Operating Conditions:Driverless car": 28, + "CSETv1:Physical System Type:Vehicles (Lexus, Audi, Chrysler Pacifica)": 28, + "CSETv1_Annotator-1:AI Task:assembly": 28, + "CSETv1_Annotator-1:AI Task:image classification": 28, + "CSETv1_Annotator-1:AI Task:image interpretation": 28, + "CSETv1_Annotator-1:AI Task:production": 28, + "CSETv1_Annotator-1:AI tools and methods:human language technology": 28, + "CSETv1_Annotator-1:AI tools and methods:large language models": 28, + "CSETv1_Annotator-1:Data Inputs:image captions": 28, + "CSETv1_Annotator-1:Data Inputs:video captions": 28, + "CSETv1_Annotator-1:Date of Incident Year:2024": 28, + "CSETv1_Annotator-1:Physical System Type:Vehicles (Lexus, Audi, Chrysler Pacifica)": 28, + "CSETv1_Annotator-2:AI Task:Generate Captions": 28, + "CSETv1_Annotator-2:AI Task:Manufacturing Car": 28, + "CSETv1_Annotator-2:Data Inputs:Captions": 28, + "CSETv1_Annotator-2:Harm Distribution Basis:nation of origin, citizenship, immigrant status": 28, + "CSETv1_Annotator-2:Harm Distribution Basis:religion": 28, + "GMF:Known AI Technical Failure:Generalization Failure": 28, + "GMF:Known AI Technical Failure:Limited Dataset": 28, + "CSETv0:AI Applications:Early warning system": 27, + "CSETv0:AI Applications:targeted advertising": 27, + "CSETv0:AI Techniques:Google Adsense": 27, + "CSETv0:AI Techniques:Oko satellites": 27, + "CSETv0:Harm Type:Other:Reputational harm": 27, + "CSETv0:Infrastructure Sectors:Critical manufacturing": 27, + "CSETv0:Infrastructure Sectors:Nuclear": 27, + "CSETv0:Location:Baunatal, Germany": 27, + "CSETv0:Location:Soviet Union": 27, + "CSETv0:Named Entities:Carnegie Mellon University": 27, + "CSETv0:Named Entities:Harvard University": 27, + "CSETv0:Named Entities:Harwin Cheng": 27, + "CSETv0:Named Entities:Oko": 27, + "CSETv0:Named Entities:Soviet Union": 27, + "CSETv0:Named Entities:Tiffany Teng": 27, + "CSETv0:Named Entities:United States": 27, + "CSETv0:Named Entities:Volkswagen": 27, + "CSETv0:Named Entities:William Santana Li": 27, + "CSETv0:Named Entities:www.instantcheckmate.com": 27, + "CSETv0:Physical System:Weapons system": 27, + "CSETv0:System Developer:Soviet Union": 27, + "CSETv0:Technology Purveyor:Instant Checkmate": 27, + "CSETv0:Technology Purveyor:Soviet Union": 27, + "CSETv0:Technology Purveyor:United States": 27, + "CSETv0:Technology Purveyor:Volkswagen": 27, + "CSETv1:AI Task:personalized online advertising": 27, + "CSETv1:Data Inputs:satellite data": 27, + "CSETv1:Data Inputs:sonar": 27, + "CSETv1:Data Inputs:vibration detectors": 27, + "CSETv1:Date of Incident Year:1983": 27, + "CSETv1:Injuries:10": 27, + "CSETv1_Annotator-1:AI Task:personalized online advertising": 27, + "CSETv1_Annotator-1:Data Inputs:satellite data": 27, + "CSETv1_Annotator-1:Date of Incident Year:1983": 27, + "CSETv1_Annotator-1:Injuries:10": 27, + "CSETv1_Annotator-1:Sector of Deployment:accommodation and food service activities": 27, + "CSETv1_Annotator-2:AI Task:facial recognition": 27, + "CSETv1_Annotator-2:AI tools and methods:Sensor Data Processing": 27, + "CSETv1_Annotator-2:Data Inputs:sonar": 27, + "CSETv1_Annotator-2:Data Inputs:vibration detectors": 27, + "CSETv1_Annotator-2:Injuries:1": 27, + "CSETv1_Annotator-2:Location Country (two letters):CN": 27, + "CSETv1_Annotator-2:Physical System Type:5' tall, 300 pound, egg shaped security robot": 27, + "CSETv1_Annotator-3:Annotator’s AI special interest intangible harm assessment:maybe": 27, + "CSETv1_Annotator-3:Clear link to Technology:maybe": 27, + "CSETv1_Annotator-3:Date of Incident Year:1983": 27, + "CSETv1_Annotator-3:Infrastructure Sectors:defense-industrial base": 27, + "CSETv1_Annotator-3:Intentional Harm:Yes. Intentionally designed to perform harm but created an unintended harm (a different harm may have occurred)": 27, + "CSETv1_Annotator-3:Location Country (two letters):RU": 27, + "CSETv1_Annotator-3:Sector of Deployment:defense": 27, + "CSETv1_Annotator-3:Tangible Harm:non-imminent risk of tangible harm (an issue) occurred": 27, + "GMF:Known AI Goal:Threat Detection": 27, + "GMF:Known AI Technical Failure:Black Swan Event": 27, + "GMF:Known AI Technical Failure:Data or Labelling Noise": 27, + "GMF:Known AI Technical Failure:Dataset Imbalance": 27, + "GMF:Potential AI Technology:Face Detection": 27, + "GMF:Potential AI Technology:Satellite Imaging": 27, + "CSETv0:Location:Beitar Illit, Israel": 26, + "CSETv0:Named Entities:Beitar Illit": 26, + "CSETv0:Named Entities:Israel": 26, + "CSETv0:Named Entities:Israeli Police": 26, + "CSETv0:Sector of Deployment:Human health and social work activities": 26, + "CSETv1:Data Inputs:Youtube videos": 26, + "CSETv1:Data Inputs:traffic patterns": 26, + "CSETv1:Location Country (two letters):PS": 26, + "CSETv1:Location Country (two letters):VN": 26, + "CSETv1_Annotator-1:AI Task:route optimization": 26, + "CSETv1_Annotator-1:AI tools and methods:Dijkstra Algorithm": 26, + "CSETv1_Annotator-1:AI tools and methods:shortest-path algorithm": 26, + "CSETv1_Annotator-1:Data Inputs:GPS input": 26, + "CSETv1_Annotator-1:Data Inputs:traffic patterns": 26, + "CSETv1_Annotator-1:Data Inputs:user traffic reports": 26, + "CSETv1_Annotator-1:Location Country (two letters):GB": 26, + "CSETv1_Annotator-1:User Test in Controlled Conditions:yes": 26, + "CSETv1_Annotator-2:AI Task:Translation": 26, + "CSETv1_Annotator-2:Location Country (two letters):PS": 26, + "CSETv1_Annotator-3:AI Task:Translation": 26, + "CSETv1_Annotator-3:Location Country (two letters):PS": 26, + "GMF:Known AI Goal:Translation": 26, + "GMF:Potential AI Technical Failure:Misconfigured Aggregation": 26, + "GMF:Potential AI Technology:Image Classification": 26, + "GMF:Potential AI Technology:Intermediate modeling": 26, + "GMF:Potential AI Technology:Multimodal Learning": 26, + "CSETv0:Harm Type:Other": 25, + "CSETv0:Location:Ningbo, China": 25, + "CSETv0:Location:United States": 25, + "CSETv0:Named Entities:Dong Mingzhu": 25, + "CSETv0:Named Entities:Google Photos": 25, + "CSETv0:Named Entities:Gree Electric Appliances": 25, + "CSETv0:Named Entities:Ningbo": 25, + "CSETv0:Physical System:Other:CCTV cameras, displays": 25, + "CSETv0:System Developer:Ningbo traffic police": 25, + "CSETv0:Technology Purveyor:Ningbo traffic police": 25, + "CSETv1:AI Task:image classification": 25, + "CSETv1:AI Task:jaywalking detection": 25, + "CSETv1:Data Inputs:sex": 25, + "CSETv1:Infrastructure Sectors:information technology": 25, + "CSETv1:Lives Lost:2": 25, + "CSETv1:Operating Conditions:night": 25, + "CSETv1:Physical System Type:Tesla vehicle (Model 3, Model S, Model X)": 25, + "CSETv1:User Test in Controlled Conditions:yes": 25, + "CSETv1_Annotator-1:AI Task:jaywalking detection": 25, + "CSETv1_Annotator-1:Data Inputs:camera footage": 25, + "CSETv1_Annotator-1:Data Inputs:sex": 25, + "CSETv1_Annotator-1:Date of Incident Year:2009": 25, + "CSETv1_Annotator-1:Harm Distribution Basis:age": 25, + "CSETv1_Annotator-1:Physical System Type:Tesla vehicle (Model 3, Model S, Model X)": 25, + "CSETv1_Annotator-1:Physical System Type:autonomous car": 25, + "CSETv1_Annotator-2:AI Task:Detect Jaywalkers": 25, + "CSETv1_Annotator-2:Physical System Type:Camera": 25, + "CSETv1_Annotator-3:AI Harm Level:AI tangible harm issue": 25, + "GMF:Potential AI Technical Failure:Concept Drift": 25 + }, + "CSETv1.AI System": { + "yes": 1221, + "no": 269, + "maybe": 92 + }, + "CSETv1.Lives Lost": { + "0": 1476, + "1": 76, + "2": 25, + "18": 2, + "51": 1, + "130": 3, + "144": 12, + "189": 19 + }, + "GMF.Known AI Goal": { + "Autonomous Driving": 45, + "Face Recognition": 36, + "AI Voice Assistant": 35, + "Autonomous Drones": 31, + "Automatic Stock Trading": 30, + "Deepfake Video Generation": 29, + "Hate Speech Detection": 29, + "Threat Detection": 27, + "Translation": 26, + "Image Tagging": 24, + "Content Recommendation": 14, + "Content Search": 14, + "Market Forecasting": 14, + "NSFW Content Detection": 14, + "Chatbot": 13, + "Audio Localization": 11, + "Gunshot Detection": 11, + "Automatic Skill Assessment": 10, + "Scheduling": 10, + "Robotic Manipulation": 8, + "Visual Art Generation": 7, + "Smart Devices": 6, + "Substance Detection": 6, + "Code Generation": 5, + "Image Cropping": 5, + "Activity Tracking": 3, + "Question Answering": 3, + "Copyrighted Content Detection": 2, + "Data Grouping": 2, + "Social Media Content Generation": 2, + "Automated Content Curation": 1, + "Behavioral Modeling": 1, + "Financial Processing": 1, + "License Plate Recognition": 1, + "Recidivism Prediction": 1 + }, + "CSETv1.Data Inputs": { + "text": 443, + "images": 286, + "video": 234, + "sensor data": 147, + "audio": 143, + "lidar": 143, + "radar": 131, + "facial images": 100, + "camera input": 95, + "search queries": 68, + "speech": 64, + "video input": 61, + "voice": 61, + "photos": 58, + "road data": 57, + "spatial data": 52, + "infrared camera": 51, + "personal data": 51, + "image alt tags": 47, + "dot projector": 45, + "GPS": 44, + "income data": 39, + "tabular": 39, + "tax reports": 39, + "Amazon account information": 37, + "Facebook posts": 36, + "air quality": 36, + "Crime Records": 35, + "Resume": 33, + "traffic": 33, + "age": 31, + "personal information": 31, + "radar input": 30, + "stock data": 30, + "stock orders": 30, + "stock prices": 30, + "thermal imaging": 30, + "words": 30, + "Train position/status": 29, + "geospatial data": 29, + "track sensors": 29, + "Captions": 28, + "image captions": 28, + "laser input": 28, + "video captions": 28, + "satellite data": 27, + "sonar": 27, + "vibration detectors": 27, + "Youtube videos": 26, + "traffic patterns": 26, + "sex": 25, + "DAO token IDs": 24, + "GNSS Antennae input": 24, + "GPS input": 24, + "code": 24, + "inertial measurement unit input": 24, + "odometer input": 24, + "sales data": 24, + "traffic light signals": 24, + "user prompts": 24, + "user traffic reports": 24, + "previous criminal history": 23, + "user queries": 23, + "137 questionnaire responses": 22, + "Police reports": 22, + "camera footage": 22, + "criminal degree": 22, + "route specifications": 22, + "employee profile": 21, + "infrared images": 21, + "employee status": 20, + "sensor": 17, + "messages": 16, + "criminal record": 15, + "questionnaire responses": 15, + "keywords": 14, + "store camera footage": 14, + "surveillance footage": 14, + "census data": 13, + "maps": 13, + "names": 13, + "news articles": 13, + "past location of crime": 13, + "past time of crime": 13, + "past type of crime": 13, + "patrol shifts": 13, + "population density": 13, + "game data": 11, + "microphone inputs": 11, + "patient data": 11, + "schedules": 11, + "security footage": 11, + "selfies": 11, + "store traffic": 10, + "test scores": 10, + "worker profiles": 10, + "audio inputs": 9, + "search terms": 9, + "sensor input": 8, + "treatment cost": 7, + "Facebook accounts": 6, + "Youtube content": 6, + "credit report": 6, + "income": 6, + "motion": 6, + "personal debt": 6, + "personal finances": 6, + "temperature": 6, + "CDPH guidelines": 5, + "Facebook ads": 5, + "Instagram accounts": 5, + "Uber accounts": 5, + "department": 5, + "driver behavior data": 5, + "driver rating data": 5, + "job role": 5, + "medical personnel data": 5, + "148 million tax returns": 4, + "780000 audits": 4, + "Uber driver account information": 4, + "applicant data": 4, + "bluetooth": 4, + "cameras": 4, + "census demographics": 4, + "electronic health records": 4, + "employee activity": 4, + "employee performance data": 4, + "geographical information": 4, + "home valuations": 4, + "homeowner-submitted details": 4, + "house pictures": 4, + "images from social media": 4, + "images from websites": 4, + "property records": 4, + "real estate data": 4, + "scraped images": 4, + "social media accounts": 4, + "student data": 4, + "tax records": 4, + "taxpayer names": 4, + "taxpayer profile": 4, + "user data": 4, + "Employee Info": 3, + "GPA": 3, + "Product Info": 3, + "RFID": 3, + "Time off Task (TOT)": 3, + "Unclear": 3, + "Warehouse data": 3, + "creatinine levels": 3, + "email addresses": 3, + "ethical judgments": 3, + "ethical scenarios": 3, + "medical data": 3, + "race": 3, + "student grades": 3, + "usernames": 3, + "voice input": 3, + "water samples": 3, + "worker performance data": 3, + "Amazon products": 2, + "Drake songs": 2, + "Drake voice": 2, + "The Weeknd songs": 2, + "The Weeknd voice": 2, + "TikTok posts": 2, + "area of research interest": 2, + "biometrics": 2, + "body scans": 2, + "faculty advisor": 2, + "family relationships": 2, + "letter of recommendation": 2, + "letters of recommendation": 2, + "license plate images": 2, + "patient information": 2, + "photos in Google photos": 2, + "purchase history": 2, + "reliability index": 2, + "shift data": 2, + "songs": 2, + "statement of interest": 2, + "university name": 2, + "university previously attended": 2, + "websites": 2, + "286 questions": 1, + "Amazon items": 1, + "Amazon purchases": 1, + "Burmese text": 1, + "Facebook comments": 1, + "Facebook likes": 1, + "Facebook reactions": 1, + "Facebook reshares": 1, + "Instagram posts": 1, + "Internet usage": 1, + "SAT and ACT scores": 1, + "TikTok accounts": 1, + "Twitter posts": 1, + "acceleration": 1, + "advertisements": 1, + "alleged crimes": 1, + "arrest photos": 1, + "article text": 1, + "audio recordings": 1, + "biometric data": 1, + "breaking": 1, + "credits attempted": 1, + "credits completed": 1, + "dates of significance": 1, + "delivery times": 1, + "diagnosis": 1, + "driver performance metrics": 1, + "emails": 1, + "estimated skills": 1, + "health data": 1, + "health measures": 1, + "high school percentile": 1, + "in-van camera input": 1, + "income statements": 1, + "ingredients": 1, + "job application": 1, + "living situation": 1, + "marketing material": 1, + "national IDs": 1, + "phone calls": 1, + "photo IDs": 1, + "physical function": 1, + "predicted final grades": 1, + "property data": 1, + "school attended": 1, + "school principal assessments": 1, + "schools' historical IB results": 1, + "seatbelt usage": 1, + "social media posts": 1, + "speed": 1, + "standardized test results": 1, + "statewide average test scores": 1, + "student evaluations": 1, + "student test results": 1, + "symptoms": 1, + "texting": 1, + "uploaded images": 1, + "user comments": 1, + "user engagement": 1, + "user reports": 1, + "voice recordings": 1, + "webpage content": 1, + "website code": 1, + "welfare recipient data": 1, + "x-ray images": 1, + "youtube history": 1, + "zip code": 1 + }, + "is_incident_report": { + "true": 3871 + }, + "epoch_incident_date": { + "433382400": 27, + "706752000": 2, + "828489600": 1, + "889056000": 4, + "921542400": 3, + "1048291200": 1, + "1049241600": 4, + "1054425600": 3, + "1057017600": 2, + "1140825600": 3, + "1185408000": 7, + "1211500800": 24, + "1214870400": 1, + "1216339200": 4, + "1238371200": 1, + "1251763200": 1, + "1273276800": 30, + "1301961600": 29, + "1303084800": 1, + "1316476800": 2, + "1325376000": 1, + "1325721600": 1, + "1330128000": 7, + "1336089600": 4, + "1343779200": 10, + "1349740800": 11, + "1354320000": 2, + "1355097600": 2, + "1358899200": 27, + "1375142400": 2, + "1377993600": 5, + "1379030400": 3, + "1380585600": 12, + "1385510400": 1, + "1388534400": 1, + "1390262400": 6, + "1393891200": 5, + "1397606400": 2, + "1399507200": 2, + "1405382400": 27, + "1407974400": 10, + "1408060800": 10, + "1409011200": 2, + "1412121600": 2, + "1413590400": 20, + "1418083200": 3, + "1419206400": 1, + "1425168000": 16, + "1427155200": 1, + "1427846400": 3, + "1428105600": 11, + "1430438400": 2, + "1431302400": 11, + "1431993600": 14, + "1433116800": 2, + "1433289600": 24, + "1434240000": 1, + "1435708800": 39, + "1435795200": 12, + "1436745600": 12, + "1436918400": 1, + "1438387200": 1, + "1441065600": 13, + "1443139200": 5, + "1446508800": 22, + "1447372800": 3, + "1447804800": 13, + "1449273600": 35, + "1451606400": 1, + "1453248000": 4, + "1454284800": 1, + "1454457600": 1, + "1455062400": 7, + "1456790400": 8, + "1458777600": 28, + "1459382400": 18, + "1459468800": 4, + "1459814400": 4, + "1460160000": 1, + "1460678400": 1, + "1463961600": 37, + "1464220800": 3, + "1464739200": 10, + "1464825600": 12, + "1465948800": 5, + "1466121600": 24, + "1467244800": 22, + "1467331200": 29, + "1468281600": 27, + "1469059200": 1, + "1470787200": 33, + "1472688000": 5, + "1473033600": 10, + "1473120000": 9, + "1473379200": 13, + "1474848000": 28, + "1475280000": 2, + "1475884800": 29, + "1479254400": 2, + "1481068800": 22, + "1481760000": 1, + "1482364800": 1, + "1483056000": 16, + "1483142400": 4, + "1483228800": 1, + "1484438400": 4, + "1485907200": 1, + "1487894400": 6, + "1488153600": 9, + "1488326400": 2, + "1489363200": 3, + "1489536000": 2, + "1491436800": 5, + "1491523200": 2, + "1491782400": 3, + "1492041600": 10, + "1492214400": 1, + "1493078400": 23, + "1493337600": 1, + "1493596800": 1, + "1494201600": 1, + "1495152000": 3, + "1496448000": 8, + "1497484800": 10, + "1498867200": 35, + "1499040000": 2, + "1499644800": 7, + "1500249600": 30, + "1501545600": 2, + "1501632000": 16, + "1502755200": 2, + "1503705600": 4, + "1504742400": 1, + "1505260800": 45, + "1505433600": 1, + "1505692800": 1, + "1505952000": 2, + "1506470400": 1, + "1507593600": 5, + "1507766400": 5, + "1508198400": 26, + "1508976000": 7, + "1509494400": 1, + "1510099200": 24, + "1510185600": 4, + "1510704000": 8, + "1511222400": 1, + "1512259200": 29, + "1512518400": 22, + "1515715200": 3, + "1515974400": 2, + "1516579200": 5, + "1516838400": 1, + "1517443200": 1, + "1518652800": 2, + "1521331200": 25, + "1521763200": 14, + "1522627200": 28, + "1523404800": 4, + "1523491200": 3, + "1524182400": 2, + "1524441600": 2, + "1524960000": 4, + "1525132800": 6, + "1525910400": 4, + "1525996800": 1, + "1527292800": 4, + "1527552000": 4, + "1527811200": 1, + "1528675200": 3, + "1530489600": 1, + "1530662400": 1, + "1530921600": 4, + "1532044800": 2, + "1532131200": 3, + "1532563200": 1, + "1533081600": 3, + "1534118400": 4, + "1534291200": 5, + "1535760000": 6, + "1538352000": 2, + "1540598400": 19, + "1541462400": 25, + "1541635200": 5, + "1541721600": 2, + "1542931200": 2, + "1543363200": 2, + "1543622400": 25, + "1543795200": 1, + "1543968000": 17, + "1546300800": 1, + "1548806400": 4, + "1548979200": 3, + "1549238400": 3, + "1550102400": 3, + "1551398400": 6, + "1552262400": 3, + "1552608000": 1, + "1554076800": 7, + "1554249600": 3, + "1559260800": 3, + "1559347200": 5, + "1559520000": 2, + "1561161600": 8, + "1561420800": 2, + "1561939200": 3, + "1562112000": 3, + "1562371200": 3, + "1562716800": 1, + "1562803200": 2, + "1564531200": 3, + "1566604800": 1, + "1567728000": 2, + "1569715200": 3, + "1570147200": 5, + "1570492800": 3, + "1570579200": 1, + "1571097600": 6, + "1571875200": 7, + "1572998400": 4, + "1573430400": 6, + "1573516800": 6, + "1573776000": 4, + "1574985600": 4, + "1575676800": 2, + "1577577600": 8, + "1577836800": 6, + "1577923200": 1, + "1579046400": 4, + "1579305600": 1, + "1579564800": 6, + "1580342400": 11, + "1580947200": 1, + "1581033600": 2, + "1582070400": 3, + "1582156800": 3, + "1582502400": 4, + "1583712000": 4, + "1584316800": 4, + "1584403200": 1, + "1584921600": 2, + "1585267200": 4, + "1585699200": 1, + "1586822400": 2, + "1586908800": 1, + "1588032000": 3, + "1588377600": 2, + "1590883200": 9, + "1590969600": 4, + "1591401600": 12, + "1591574400": 2, + "1591747200": 1, + "1591833600": 2, + "1592006400": 2, + "1592179200": 2, + "1592352000": 1, + "1592611200": 8, + "1592956800": 4, + "1593216000": 1, + "1593302400": 6, + "1593561600": 9, + "1593993600": 1, + "1595030400": 1, + "1595894400": 8, + "1596412800": 1, + "1596585600": 2, + "1596672000": 3, + "1596758400": 3, + "1597276800": 8, + "1598918400": 1, + "1600128000": 2, + "1600387200": 5, + "1600560000": 1, + "1601337600": 3, + "1601683200": 3, + "1601942400": 2, + "1602028800": 1, + "1602115200": 2, + "1602201600": 2, + "1602720000": 1, + "1603152000": 5, + "1603238400": 2, + "1603324800": 1, + "1603497600": 2, + "1603929600": 1, + "1604188800": 3, + "1605052800": 4, + "1605398400": 1, + "1606435200": 2, + "1607040000": 2, + "1607212800": 1, + "1607990400": 2, + "1608249600": 5, + "1608681600": 13, + "1608768000": 1, + "1608854400": 2, + "1609027200": 1, + "1609459200": 4, + "1610236800": 4, + "1610323200": 1, + "1610496000": 2, + "1610668800": 1, + "1611187200": 2, + "1612137600": 1, + "1612483200": 3, + "1613001600": 1, + "1613088000": 1, + "1613433600": 1, + "1614124800": 1, + "1614297600": 2, + "1614556800": 1, + "1614643200": 4, + "1615766400": 2, + "1615939200": 7, + "1617235200": 3, + "1618272000": 1, + "1618617600": 9, + "1618963200": 1, + "1619568000": 1, + "1619654400": 2, + "1619827200": 8, + "1620259200": 3, + "1621296000": 7, + "1621728000": 1, + "1622160000": 6, + "1622592000": 7, + "1622678400": 4, + "1623456000": 2, + "1624924800": 5, + "1625097600": 1, + "1625184000": 1, + "1625356800": 4, + "1625875200": 3, + "1625961600": 2, + "1626134400": 2, + "1626393600": 4, + "1626739200": 1, + "1626998400": 3, + "1627603200": 1, + "1627776000": 14, + "1627948800": 4, + "1628985600": 4, + "1630454400": 3, + "1631836800": 3, + "1632096000": 2, + "1633046400": 4, + "1633478400": 1, + "1634256000": 3, + "1634515200": 1, + "1634774400": 1, + "1634860800": 3, + "1635379200": 10, + "1635724800": 8, + "1635811200": 4, + "1635897600": 1, + "1636243200": 1, + "1636502400": 5, + "1637452800": 3, + "1638748800": 18, + "1638921600": 1, + "1639180800": 7, + "1640390400": 2, + "1640476800": 2, + "1640563200": 2, + "1640995200": 1, + "1642118400": 1, + "1642204800": 10, + "1642809600": 3, + "1643155200": 1, + "1643932800": 5, + "1644451200": 3, + "1644537600": 6, + "1644883200": 1, + "1645574400": 1, + "1645747200": 4, + "1646006400": 4, + "1646092800": 7, + "1646179200": 1, + "1646265600": 1, + "1646611200": 2, + "1647302400": 3, + "1647388800": 9, + "1647475200": 2, + "1647907200": 2, + "1648771200": 8, + "1648944000": 1, + "1649116800": 2, + "1649203200": 2, + "1649808000": 1, + "1650326400": 5, + "1650412800": 3, + "1650499200": 8, + "1651968000": 2, + "1652400000": 2, + "1652659200": 17, + "1652832000": 4, + "1653436800": 3, + "1654041600": 1, + "1654214400": 12, + "1654905600": 4, + "1654992000": 2, + "1656374400": 1, + "1658102400": 1, + "1658361600": 5, + "1658448000": 3, + "1658620800": 3, + "1658793600": 2, + "1659830400": 3, + "1660521600": 3, + "1660694400": 1, + "1661040000": 2, + "1661126400": 3, + "1661385600": 2, + "1661731200": 1, + "1662508800": 2, + "1662854400": 2, + "1663027200": 3, + "1663200000": 18, + "1663891200": 1, + "1664755200": 2, + "1664841600": 8, + "1665446400": 1, + "1665878400": 5, + "1667952000": 1, + "1668124800": 14, + "1668470400": 6, + "1668902400": 9, + "1669075200": 6, + "1669248000": 8, + "1669334400": 9, + "1669507200": 1, + "1669766400": 18, + "1669852800": 15, + "1671408000": 23, + "1671580800": 26, + "1672185600": 10, + "1672531200": 5, + "1672704000": 8, + "1672876800": 1, + "1673395200": 9, + "1674000000": 4, + "1674172800": 3, + "1674259200": 2, + "1675036800": 19, + "1675296000": 2, + "1675382400": 6, + "1675641600": 5, + "1675728000": 18, + "1675814400": 4, + "1675987200": 2, + "1676160000": 11, + "1676332800": 12, + "1676419200": 3, + "1676505600": 23, + "1676851200": 4, + "1677024000": 1, + "1677456000": 2, + "1677542400": 22, + "1677801600": 2, + "1677974400": 5, + "1678492800": 2, + "1678838400": 5, + "1679011200": 2, + "1679270400": 2, + "1679356800": 11, + "1679529600": 2, + "1679616000": 5, + "1679875200": 7, + "1680048000": 2, + "1680220800": 5, + "1680307200": 3, + "1680912000": 2, + "1681430400": 7, + "1681689600": 2, + "1682380800": 1, + "1682899200": 1, + "1683072000": 2, + "1683158400": 58, + "1683763200": 22, + "1683849600": 1, + "1684108800": 10, + "1684368000": 1, + "1684627200": 1, + "1684713600": 19, + "1684886400": 2, + "1685232000": 1, + "1685318400": 46, + "1685577600": 3, + "1685923200": 2, + "1686096000": 1, + "1686528000": 1, + "1686614400": 4, + "1687392000": 2, + "1687478400": 2, + "1687564800": 1, + "1687737600": 1, + "1687910400": 1, + "1688083200": 3, + "1688342400": 1, + "1688515200": 1, + "1689033600": 2, + "1689552000": 1, + "1689897600": 1, + "1690156800": 3, + "1691452800": 4, + "1691625600": 1, + "1691712000": 1, + "1691971200": 2, + "1692144000": 2, + "1693094400": 1, + "1693353600": 1, + "1693612800": 1, + "1694390400": 1, + "1694908800": 10, + "1695081600": 1, + "1696204800": 27, + "1696377600": 1, + "1696636800": 14, + "1696723200": 10, + "1697414400": 44, + "1697500800": 1, + "1698105600": 1, + "1698278400": 3, + "1698710400": 17, + "1698883200": 1, + "1699401600": 2, + "1699488000": 3, + "1699574400": 3, + "1700697600": 1, + "1701043200": 43, + "1701388800": 3, + "1701820800": 3, + "1702252800": 8, + "1702339200": 12, + "1702512000": 3, + "1702684800": 1, + "1702857600": 6, + "1703030400": 32, + "1703548800": 30, + "1703635200": 1, + "1703808000": 1, + "1704758400": 8, + "1704931200": 1, + "1705017600": 5, + "1705104000": 1, + "1705276800": 4, + "1705536000": 2, + "1705795200": 14, + "1705968000": 2, + "1706054400": 31, + "1706400000": 17, + "1706572800": 2, + "1706745600": 3, + "1706832000": 21, + "1707091200": 1, + "1707264000": 2, + "1707350400": 1, + "1707609600": 1, + "1707696000": 1, + "1707782400": 6, + "1707868800": 7, + "1708128000": 1, + "1708214400": 6, + "1708300800": 1, + "1708387200": 17, + "1708473600": 35, + "1708560000": 1, + "1709078400": 34, + "1709251200": 6, + "1709510400": 1, + "1709683200": 3, + "1710374400": 7, + "1710979200": 1, + "1711065600": 3, + "1711152000": 3, + "1711411200": 9, + "1711584000": 2, + "1711670400": 2, + "1711929600": 3, + "1712016000": 3, + "1712102400": 9, + "1712188800": 2, + "1712275200": 1, + "1712361600": 3, + "1712448000": 2, + "1712534400": 1, + "1713052800": 1, + "1713139200": 1, + "1713225600": 2, + "1713484800": 10, + "1713830400": 1, + "1713916800": 3, + "1714003200": 2, + "1714089600": 4, + "1714348800": 2, + "1714694400": 3, + "1715558400": 2, + "1715644800": 9, + "1715731200": 2, + "1715817600": 1, + "1716163200": 16, + "1716249600": 1, + "1716422400": 4, + "1716508800": 16, + "1716595200": 2, + "1716681600": 1, + "1716940800": 1, + "1717027200": 2, + "1717113600": 2, + "1717200000": 3, + "1717459200": 1, + "1717632000": 1, + "1717718400": 5, + "1717804800": 2, + "1717891200": 2, + "1718150400": 2, + "1718236800": 1, + "1718668800": 3, + "1718755200": 1, + "1718841600": 1, + "1718928000": 2, + "1719014400": 1, + "1719100800": 5, + "1719273600": 1, + "1719360000": 4, + "1719446400": 2, + "1719619200": 1, + "1719792000": 4, + "1719878400": 2, + "1719964800": 2, + "1720051200": 2, + "1720310400": 2, + "1720569600": 1, + "1720828800": 1, + "1721088000": 1, + "1721260800": 1, + "1721520000": 2, + "1721606400": 1, + "1721692800": 1, + "1721865600": 2, + "1721952000": 3, + "1722211200": 1, + "1722297600": 5, + "1722902400": 1, + "1723075200": 1, + "1723507200": 3, + "1723593600": 4, + "1723766400": 3, + "1723939200": 4, + "1724112000": 1, + "1724198400": 1, + "1724371200": 1, + "1724803200": 7, + "1725235200": 2, + "1725321600": 2, + "1725408000": 3, + "1725753600": 1, + "1725840000": 2, + "1726185600": 2, + "1726617600": 1, + "1726704000": 12, + "1727136000": 8, + "1727222400": 7, + "1727481600": 2, + "1727740800": 3, + "1727827200": 13, + "1728172800": 1, + "1728259200": 20, + "1728345600": 2, + "1728432000": 1, + "1728604800": 1, + "1728777600": 3, + "1728950400": 7, + "1729036800": 11, + "1729209600": 2, + "1729468800": 2, + "1729641600": 1, + "1729728000": 1, + "1729900800": 1, + "1730505600": 1, + "1731456000": 1, + "1732060800": 2 + }, + "CSETv1.AI Harm Level": { + "none": 1003, + "AI tangible harm event": 373, + "AI tangible harm near-miss": 104, + "AI tangible harm issue": 66, + "unclear": 36 + }, + "CSETv1.Tangible Harm": { + "tangible harm definitively occurred": 678, + "no tangible harm, near-miss, or issue": 618, + "imminent risk of tangible harm (near miss) did occur": 180, + "non-imminent risk of tangible harm (an issue) occurred": 97, + "unclear": 9 + }, + "epoch_date_published": { + "1062028800": 1, + "1084579200": 1, + "1085011200": 1, + "1095292800": 1, + "1102723200": 1, + "1170806400": 1, + "1211500800": 1, + "1214870400": 1, + "1219104000": 1, + "1238803200": 1, + "1239494400": 5, + "1239580800": 7, + "1239667200": 5, + "1239840000": 1, + "1240012800": 1, + "1240531200": 1, + "1242259200": 1, + "1273276800": 1, + "1282176000": 1, + "1285804800": 1, + "1301961600": 2, + "1302048000": 1, + "1303430400": 1, + "1303862400": 1, + "1304553600": 1, + "1308009600": 1, + "1316476800": 1, + "1325721600": 2, + "1329350400": 2, + "1330128000": 1, + "1330300800": 1, + "1330387200": 1, + "1330560000": 1, + "1332720000": 1, + "1332892800": 1, + "1347753600": 1, + "1348099200": 1, + "1348704000": 1, + "1351641600": 1, + "1352160000": 1, + "1357948800": 1, + "1359417600": 1, + "1359676800": 1, + "1359936000": 5, + "1360022400": 5, + "1360108800": 4, + "1360195200": 2, + "1360800000": 1, + "1364860800": 1, + "1366070400": 1, + "1366243200": 1, + "1368403200": 1, + "1368489600": 1, + "1369785600": 1, + "1375142400": 1, + "1375747200": 1, + "1376697600": 1, + "1380153600": 1, + "1382140800": 1, + "1382572800": 1, + "1390262400": 1, + "1396569600": 1, + "1396656000": 1, + "1398211200": 1, + "1398643200": 1, + "1399334400": 1, + "1400630400": 1, + "1400803200": 2, + "1405382400": 1, + "1407283200": 2, + "1407369600": 1, + "1407974400": 1, + "1411689600": 1, + "1413590400": 1, + "1415318400": 1, + "1418083200": 1, + "1419379200": 1, + "1419811200": 2, + "1423267200": 1, + "1427932800": 1, + "1428105600": 1, + "1428537600": 2, + "1428624000": 2, + "1428710400": 1, + "1429056000": 1, + "1429142400": 1, + "1429574400": 2, + "1429660800": 7, + "1429747200": 5, + "1430092800": 1, + "1430784000": 1, + "1430870400": 1, + "1431302400": 1, + "1431993600": 1, + "1433289600": 1, + "1433376000": 1, + "1435276800": 3, + "1435363200": 1, + "1435449600": 1, + "1435536000": 1, + "1435622400": 1, + "1435708800": 9, + "1435795200": 28, + "1435881600": 1, + "1435968000": 1, + "1436140800": 2, + "1436313600": 1, + "1436572800": 1, + "1436745600": 2, + "1437350400": 1, + "1437436800": 6, + "1437523200": 2, + "1437868800": 1, + "1438560000": 1, + "1439078400": 1, + "1439424000": 5, + "1439510400": 2, + "1439683200": 1, + "1440633600": 1, + "1441065600": 1, + "1443052800": 2, + "1443571200": 1, + "1446422400": 1, + "1446508800": 6, + "1446595200": 1, + "1446681600": 1, + "1446854400": 1, + "1447286400": 1, + "1447804800": 1, + "1448409600": 1, + "1449273600": 1, + "1451606400": 1, + "1452729600": 1, + "1454112000": 1, + "1454457600": 1, + "1454544000": 1, + "1455062400": 2, + "1455235200": 1, + "1455840000": 1, + "1456444800": 1, + "1456704000": 3, + "1456790400": 9, + "1457049600": 1, + "1457568000": 5, + "1458000000": 1, + "1458691200": 1, + "1458777600": 6, + "1458864000": 7, + "1458950400": 3, + "1459036800": 2, + "1459296000": 1, + "1459382400": 1, + "1459555200": 1, + "1459987200": 2, + "1460073600": 2, + "1460419200": 1, + "1461283200": 1, + "1462060800": 1, + "1463961600": 6, + "1464048000": 1, + "1464220800": 3, + "1464393600": 1, + "1464825600": 2, + "1464912000": 3, + "1464998400": 2, + "1465084800": 1, + "1465171200": 1, + "1465257600": 2, + "1465344000": 2, + "1465430400": 4, + "1465516800": 7, + "1466121600": 1, + "1466208000": 3, + "1466294400": 1, + "1466380800": 1, + "1466553600": 1, + "1466812800": 2, + "1466899200": 1, + "1467244800": 1, + "1467331200": 13, + "1467417600": 7, + "1467504000": 1, + "1467590400": 1, + "1467676800": 1, + "1467849600": 1, + "1467936000": 1, + "1468195200": 1, + "1468281600": 4, + "1468368000": 12, + "1468454400": 6, + "1468540800": 3, + "1468800000": 1, + "1469059200": 1, + "1469232000": 1, + "1469491200": 1, + "1470441600": 2, + "1470700800": 1, + "1470787200": 2, + "1470873600": 1, + "1471392000": 1, + "1471564800": 1, + "1471824000": 2, + "1471996800": 1, + "1472083200": 1, + "1472428800": 1, + "1472601600": 1, + "1473033600": 1, + "1473120000": 2, + "1473206400": 1, + "1473292800": 4, + "1473379200": 5, + "1473465600": 1, + "1473811200": 2, + "1474243200": 1, + "1474329600": 1, + "1474675200": 1, + "1474761600": 2, + "1474848000": 5, + "1475798400": 1, + "1475884800": 1, + "1476057600": 1, + "1476144000": 1, + "1477267200": 1, + "1477872000": 1, + "1479427200": 4, + "1480550400": 1, + "1480636800": 1, + "1480723200": 1, + "1480982400": 1, + "1481068800": 10, + "1481155200": 9, + "1481760000": 1, + "1481846400": 2, + "1482019200": 1, + "1482278400": 1, + "1482364800": 1, + "1482537600": 1, + "1482883200": 3, + "1483056000": 1, + "1483142400": 4, + "1483315200": 1, + "1483401600": 2, + "1483488000": 5, + "1483574400": 3, + "1483660800": 7, + "1483747200": 2, + "1483833600": 2, + "1483920000": 10, + "1484006400": 4, + "1484092800": 3, + "1484524800": 3, + "1484611200": 2, + "1485216000": 1, + "1485561600": 1, + "1486339200": 1, + "1486425600": 2, + "1486857600": 1, + "1487894400": 2, + "1487980800": 2, + "1488067200": 1, + "1488153600": 3, + "1488240000": 1, + "1488326400": 5, + "1488499200": 2, + "1488585600": 1, + "1488931200": 2, + "1489622400": 1, + "1489795200": 1, + "1490227200": 3, + "1490572800": 1, + "1491264000": 1, + "1491782400": 4, + "1491868800": 1, + "1492041600": 1, + "1492128000": 1, + "1492473600": 1, + "1492560000": 1, + "1493078400": 6, + "1493164800": 1, + "1493251200": 1, + "1493424000": 1, + "1493596800": 1, + "1493769600": 1, + "1493856000": 1, + "1494201600": 1, + "1495152000": 2, + "1495238400": 2, + "1496102400": 1, + "1497225600": 1, + "1497312000": 2, + "1497830400": 1, + "1498003200": 1, + "1498089600": 2, + "1498694400": 1, + "1499040000": 1, + "1499299200": 1, + "1499644800": 3, + "1499731200": 5, + "1499817600": 2, + "1499904000": 2, + "1499990400": 3, + "1500249600": 2, + "1500336000": 21, + "1500422400": 10, + "1500595200": 2, + "1500854400": 1, + "1501027200": 1, + "1501200000": 1, + "1501545600": 1, + "1501718400": 7, + "1501804800": 5, + "1501891200": 3, + "1502064000": 1, + "1502150400": 1, + "1502236800": 2, + "1502323200": 11, + "1502496000": 1, + "1502755200": 1, + "1502928000": 1, + "1503014400": 2, + "1503187200": 1, + "1503273600": 2, + "1503360000": 1, + "1503446400": 1, + "1504224000": 1, + "1504742400": 2, + "1504915200": 1, + "1505088000": 1, + "1505260800": 4, + "1505347200": 4, + "1505433600": 1, + "1505692800": 6, + "1505779200": 3, + "1505952000": 2, + "1506038400": 2, + "1506470400": 1, + "1506556800": 1, + "1506729600": 1, + "1507248000": 1, + "1507507200": 1, + "1507680000": 3, + "1507766400": 3, + "1507852800": 2, + "1507939200": 1, + "1508198400": 1, + "1508630400": 2, + "1508716800": 4, + "1508803200": 12, + "1508889600": 4, + "1508976000": 6, + "1509062400": 3, + "1509235200": 2, + "1509321600": 1, + "1509408000": 5, + "1509494400": 3, + "1509667200": 1, + "1509840000": 1, + "1509926400": 2, + "1510012800": 1, + "1510099200": 2, + "1510185600": 14, + "1510272000": 7, + "1510358400": 2, + "1510531200": 8, + "1510617600": 6, + "1510704000": 4, + "1510876800": 1, + "1510963200": 1, + "1511308800": 1, + "1511740800": 1, + "1511827200": 3, + "1512000000": 1, + "1512086400": 1, + "1512259200": 1, + "1512518400": 2, + "1512604800": 4, + "1512691200": 11, + "1512777600": 4, + "1513036800": 2, + "1513123200": 4, + "1513209600": 1, + "1513296000": 2, + "1513382400": 1, + "1513468800": 1, + "1513555200": 1, + "1513641600": 5, + "1513728000": 11, + "1513814400": 7, + "1514764800": 2, + "1515628800": 3, + "1515715200": 2, + "1516060800": 2, + "1516147200": 7, + "1516233600": 6, + "1516579200": 3, + "1516752000": 4, + "1516838400": 2, + "1517529600": 2, + "1517788800": 3, + "1518566400": 3, + "1519776000": 2, + "1521417600": 2, + "1521504000": 7, + "1521763200": 3, + "1522281600": 6, + "1522454400": 6, + "1522627200": 5, + "1522800000": 2, + "1523404800": 5, + "1523491200": 3, + "1523577600": 4, + "1523664000": 3, + "1523836800": 4, + "1523923200": 3, + "1524009600": 5, + "1524096000": 2, + "1524182400": 2, + "1524528000": 2, + "1525132800": 3, + "1525478400": 2, + "1525651200": 4, + "1525737600": 3, + "1526342400": 2, + "1526428800": 2, + "1526515200": 3, + "1527120000": 2, + "1527206400": 2, + "1527465600": 2, + "1527552000": 4, + "1528070400": 3, + "1528156800": 2, + "1528243200": 2, + "1528329600": 11, + "1528416000": 5, + "1528761600": 2, + "1528848000": 5, + "1529020800": 2, + "1529539200": 5, + "1529625600": 8, + "1529712000": 3, + "1529884800": 6, + "1531180800": 2, + "1532390400": 2, + "1532476800": 2, + "1533686400": 2, + "1536192000": 2, + "1536624000": 2, + "1537228800": 2, + "1537401600": 3, + "1537488000": 3, + "1537833600": 2, + "1537920000": 4, + "1538092800": 2, + "1538438400": 2, + "1539129600": 7, + "1539216000": 14, + "1539302400": 4, + "1540857600": 2, + "1541635200": 5, + "1541808000": 2, + "1542326400": 2, + "1542844800": 2, + "1542931200": 8, + "1543017600": 2, + "1543190400": 2, + "1543276800": 6, + "1543363200": 6, + "1543449600": 4, + "1543622400": 3, + "1543708800": 3, + "1543795200": 6, + "1543881600": 3, + "1543968000": 5, + "1544054400": 11, + "1544140800": 6, + "1544486400": 2, + "1547510400": 2, + "1549324800": 2, + "1553126400": 2, + "1553731200": 2, + "1555113600": 4, + "1555200000": 7, + "1555977600": 3, + "1557360000": 2, + "1557964800": 2, + "1561420800": 2, + "1568592000": 2, + "1570147200": 3, + "1570665600": 2, + "1571270400": 3, + "1571875200": 3, + "1572998400": 4, + "1573516800": 4, + "1574121600": 2, + "1579305600": 3, + "1580256000": 2, + "1581292800": 2, + "1582070400": 2, + "1582588800": 2, + "1582675200": 3, + "1582761600": 3, + "1588291200": 2, + "1588896000": 2, + "1590105600": 2, + "1590710400": 3, + "1590796800": 4, + "1590969600": 5, + "1591056000": 2, + "1591660800": 3, + "1591747200": 2, + "1592956800": 3, + "1594339200": 2, + "1595203200": 2, + "1595980800": 2, + "1596067200": 3, + "1596153600": 2, + "1596499200": 5, + "1596585600": 3, + "1597104000": 2, + "1597190400": 2, + "1597622400": 3, + "1597795200": 2, + "1597881600": 2, + "1597968000": 2, + "1598486400": 2, + "1599091200": 2, + "1599609600": 2, + "1599696000": 2, + "1599782400": 2, + "1600646400": 2, + "1601337600": 2, + "1601942400": 3, + "1602028800": 2, + "1602201600": 4, + "1602720000": 2, + "1603152000": 2, + "1603324800": 2, + "1603670400": 3, + "1605744000": 2, + "1605830400": 2, + "1606262400": 2, + "1606521600": 3, + "1606780800": 2, + "1608249600": 4, + "1608422400": 2, + "1608768000": 2, + "1609027200": 3, + "1609286400": 2, + "1609545600": 2, + "1609718400": 2, + "1609804800": 2, + "1610236800": 2, + "1610323200": 2, + "1610409600": 3, + "1610496000": 4, + "1610582400": 3, + "1610928000": 2, + "1611014400": 2, + "1611878400": 4, + "1612137600": 2, + "1612310400": 2, + "1612569600": 2, + "1612828800": 2, + "1613001600": 2, + "1613433600": 2, + "1613692800": 2, + "1614211200": 2, + "1614556800": 2, + "1614643200": 2, + "1615334400": 2, + "1615507200": 3, + "1615939200": 3, + "1616025600": 3, + "1616112000": 2, + "1617321600": 2, + "1617926400": 3, + "1618704000": 3, + "1618790400": 2, + "1618876800": 2, + "1619395200": 2, + "1619568000": 3, + "1621382400": 2, + "1621468800": 2, + "1622592000": 3, + "1622678400": 3, + "1622764800": 3, + "1624838400": 2, + "1624924800": 4, + "1625184000": 2, + "1625443200": 3, + "1626220800": 3, + "1626652800": 4, + "1626825600": 2, + "1627257600": 2, + "1627603200": 2, + "1628208000": 2, + "1628467200": 2, + "1629331200": 2, + "1629849600": 2, + "1632096000": 2, + "1634083200": 4, + "1634601600": 2, + "1634688000": 3, + "1634860800": 3, + "1635811200": 2, + "1635897600": 3, + "1636329600": 4, + "1636416000": 3, + "1636502400": 2, + "1636588800": 3, + "1636761600": 2, + "1636934400": 6, + "1637193600": 2, + "1637452800": 3, + "1638144000": 3, + "1638403200": 2, + "1638662400": 2, + "1639008000": 4, + "1639094400": 2, + "1639353600": 2, + "1639440000": 8, + "1639526400": 4, + "1639612800": 4, + "1640649600": 2, + "1641427200": 2, + "1641772800": 3, + "1642464000": 3, + "1643241600": 3, + "1643760000": 4, + "1643932800": 2, + "1644192000": 2, + "1644278400": 2, + "1644796800": 2, + "1645574400": 2, + "1646006400": 3, + "1646092800": 2, + "1646611200": 3, + "1646697600": 5, + "1646784000": 3, + "1647388800": 5, + "1647475200": 7, + "1647820800": 3, + "1647907200": 3, + "1647993600": 2, + "1648166400": 3, + "1648425600": 2, + "1648512000": 3, + "1648684800": 2, + "1648771200": 2, + "1649030400": 2, + "1649116800": 2, + "1649548800": 4, + "1649635200": 2, + "1650585600": 3, + "1650672000": 3, + "1650844800": 4, + "1650931200": 2, + "1653177600": 2, + "1653609600": 2, + "1653868800": 2, + "1654128000": 2, + "1654214400": 2, + "1654473600": 4, + "1655164800": 2, + "1655251200": 2, + "1657065600": 2, + "1657152000": 3, + "1657238400": 3, + "1657497600": 2, + "1658102400": 2, + "1658361600": 2, + "1658448000": 5, + "1658620800": 4, + "1658707200": 3, + "1658793600": 3, + "1658880000": 2, + "1658966400": 3, + "1659657600": 2, + "1659744000": 3, + "1659916800": 2, + "1660089600": 2, + "1660521600": 2, + "1661126400": 4, + "1661212800": 2, + "1661299200": 2, + "1661385600": 2, + "1661472000": 2, + "1661904000": 2, + "1661990400": 2, + "1662076800": 2, + "1662940800": 2, + "1663027200": 2, + "1663113600": 2, + "1663286400": 4, + "1663632000": 2, + "1664755200": 2, + "1664841600": 3, + "1665014400": 3, + "1665100800": 2, + "1665360000": 2, + "1665619200": 3, + "1666224000": 2, + "1667174400": 2, + "1667260800": 2, + "1668124800": 2, + "1668643200": 2, + "1668729600": 2, + "1669075200": 2, + "1669852800": 6, + "1670025600": 2, + "1670198400": 5, + "1670284800": 4, + "1670371200": 3, + "1670457600": 2, + "1670544000": 3, + "1670803200": 2, + "1670976000": 5, + "1671148800": 3, + "1671408000": 4, + "1671494400": 4, + "1671580800": 9, + "1671667200": 5, + "1672272000": 2, + "1672444800": 2, + "1672617600": 6, + "1672704000": 4, + "1672876800": 3, + "1672963200": 5, + "1673049600": 2, + "1673136000": 2, + "1673222400": 9, + "1673308800": 5, + "1673395200": 3, + "1673481600": 4, + "1673568000": 4, + "1673654400": 2, + "1673827200": 3, + "1673913600": 12, + "1674000000": 5, + "1674086400": 4, + "1674172800": 4, + "1674432000": 2, + "1674518400": 3, + "1674604800": 4, + "1674691200": 3, + "1675036800": 5, + "1675123200": 8, + "1675209600": 7, + "1675382400": 3, + "1675641600": 6, + "1675728000": 6, + "1675814400": 16, + "1675900800": 6, + "1675987200": 4, + "1676246400": 8, + "1676332800": 7, + "1676419200": 9, + "1676505600": 20, + "1676592000": 6, + "1676678400": 5, + "1676764800": 5, + "1676851200": 6, + "1676937600": 14, + "1677024000": 3, + "1677110400": 3, + "1677196800": 3, + "1677456000": 3, + "1677542400": 2, + "1677715200": 2, + "1677974400": 2, + "1678060800": 7, + "1678147200": 4, + "1678233600": 2, + "1678492800": 3, + "1678665600": 8, + "1678838400": 2, + "1678924800": 3, + "1679011200": 3, + "1679356800": 4, + "1679443200": 3, + "1679529600": 4, + "1679616000": 2, + "1679961600": 7, + "1680220800": 5, + "1680566400": 2, + "1680652800": 3, + "1681344000": 3, + "1681862400": 4, + "1682121600": 2, + "1682380800": 2, + "1682467200": 3, + "1682553600": 4, + "1683763200": 5, + "1683849600": 9, + "1684195200": 2, + "1684281600": 4, + "1684368000": 5, + "1684454400": 2, + "1684713600": 10, + "1684800000": 9, + "1684972800": 3, + "1685145600": 10, + "1685232000": 3, + "1685318400": 9, + "1685404800": 33, + "1685491200": 15, + "1685577600": 20, + "1685664000": 10, + "1685923200": 4, + "1686096000": 5, + "1686182400": 6, + "1686268800": 2, + "1686614400": 3, + "1687392000": 2, + "1687478400": 3, + "1687737600": 2, + "1687910400": 4, + "1688083200": 2, + "1688774400": 3, + "1689033600": 3, + "1689120000": 2, + "1690156800": 2, + "1693353600": 3, + "1693612800": 2, + "1694563200": 3, + "1695168000": 2, + "1695600000": 2, + "1695859200": 4, + "1696204800": 9, + "1696291200": 8, + "1696377600": 4, + "1696464000": 2, + "1696809600": 7, + "1697500800": 2, + "1698019200": 2, + "1698105600": 6, + "1698278400": 3, + "1698710400": 8, + "1698796800": 4, + "1698883200": 17, + "1698969600": 13, + "1699056000": 3, + "1699228800": 7, + "1699315200": 3, + "1699401600": 9, + "1699488000": 6, + "1699747200": 2, + "1699920000": 3, + "1700006400": 6, + "1700092800": 9, + "1700179200": 2, + "1700265600": 2, + "1700438400": 2, + "1700697600": 2, + "1701043200": 2, + "1701129600": 11, + "1701820800": 2, + "1701907200": 2, + "1702252800": 4, + "1702339200": 20, + "1702512000": 3, + "1702857600": 3, + "1702944000": 6, + "1703030400": 15, + "1703116800": 5, + "1703548800": 3, + "1703635200": 4, + "1703721600": 2, + "1703808000": 11, + "1704067200": 2, + "1704758400": 4, + "1704844800": 8, + "1704931200": 8, + "1705017600": 6, + "1705104000": 4, + "1705276800": 4, + "1705363200": 7, + "1705622400": 2, + "1705881600": 5, + "1705968000": 2, + "1706054400": 4, + "1706140800": 3, + "1706227200": 6, + "1706486400": 2, + "1706572800": 16, + "1706659200": 14, + "1706832000": 3, + "1707004800": 4, + "1707091200": 14, + "1707177600": 5, + "1707264000": 2, + "1707350400": 4, + "1707696000": 6, + "1707782400": 9, + "1707868800": 12, + "1707955200": 8, + "1708041600": 3, + "1708128000": 2, + "1708214400": 2, + "1708300800": 4, + "1708473600": 14, + "1708560000": 26, + "1708646400": 9, + "1708732800": 3, + "1708905600": 4, + "1708992000": 3, + "1709251200": 2, + "1709856000": 2, + "1710374400": 5, + "1711152000": 3, + "1711497600": 3, + "1711584000": 2, + "1711670400": 2, + "1712016000": 3, + "1712102400": 8, + "1712188800": 3, + "1712275200": 4, + "1712448000": 2, + "1712534400": 3, + "1712707200": 2, + "1713139200": 2, + "1713830400": 2, + "1713916800": 2, + "1714003200": 8, + "1714089600": 7, + "1714176000": 3, + "1714348800": 2, + "1715644800": 3, + "1715731200": 4, + "1715817600": 2, + "1715904000": 4, + "1716163200": 12, + "1716249600": 6, + "1716336000": 4, + "1716422400": 7, + "1716508800": 3, + "1716595200": 2, + "1716681600": 2, + "1716768000": 2, + "1717113600": 2, + "1717200000": 2, + "1717977600": 3, + "1718064000": 3, + "1718150400": 5, + "1718236800": 4, + "1718668800": 2, + "1718841600": 3, + "1718928000": 2, + "1719187200": 4, + "1719273600": 2, + "1719532800": 2, + "1719619200": 3, + "1719792000": 2, + "1719878400": 3, + "1719964800": 2, + "1720569600": 2, + "1721692800": 2, + "1721952000": 2, + "1722124800": 2, + "1722384000": 2, + "1723507200": 2, + "1723593600": 6, + "1724025600": 3, + "1724112000": 2, + "1724198400": 2, + "1724716800": 2, + "1724976000": 2, + "1725235200": 5, + "1725321600": 5, + "1725408000": 4, + "1726012800": 2, + "1726185600": 2, + "1726704000": 2, + "1727136000": 2, + "1727222400": 3, + "1727308800": 10, + "1727740800": 3, + "1727827200": 6, + "1727913600": 2, + "1728000000": 2, + "1728086400": 3, + "1728172800": 2, + "1728259200": 4, + "1728432000": 2, + "1728604800": 10, + "1728691200": 6, + "1728777600": 3, + "1728864000": 18, + "1728950400": 7, + "1729036800": 3, + "1729123200": 5, + "1729209600": 7, + "1729468800": 4, + "1729555200": 8, + "1729641600": 12, + "1729728000": 15, + "1729814400": 5, + "1729900800": 3, + "1732060800": 3 + }, + "CSETv1.Autonomy Level": { + "Autonomy1": 847, + "Autonomy3": 370, + "Autonomy2": 261, + "unclear": 104 + }, + "CSETv1.Involving Minor": { + "no": 1469, + "yes": 106, + "maybe": 7 + }, + "CSETv1.Location Region": { + "North America": 781, + "Global": 402, + "Asia": 177, + "Europe": 152, + "Oceania": 64, + "Africa": 5, + "South America": 1 + }, + "CSETv1.Intentional Harm": { + "No. Not intentionally designed to perform harm": 1462, + "Yes. Intentionally designed to perform harm and did create intended harm": 70, + "unclear": 44, + "Yes. Intentionally designed to perform harm but created an unintended harm (a different harm may have occurred)": 2 + }, + "CSETv1.Physical Objects": { + "no": 946, + "yes": 615, + "maybe": 21 + }, + "CSETv1.Rights Violation": { + "no": 1332, + "yes": 168, + "maybe": 82 + }, + "GMF.Known AI Technology": { + "Face Detection": 69, + "Recurrent Neural Network": 55, + "Distributional Learning": 53, + "Convolutional Neural Network": 50, + "Language Modeling": 43, + "Automatic Speech Recognition": 37, + "Acoustic Fingerprint": 35, + "Neural Network": 35, + "Generative Adversarial Network": 33, + "Keyword Filtering": 24, + "Geolocation Data": 15, + "Collaborative Filtering": 14, + "Content-based Filtering": 14, + "Transformer": 14, + "Autoencoder": 13, + "Acoustic Triangulation": 11, + "Character NGrams": 9, + "Classification": 6, + "Gesture Recognition": 6, + "Diverse Data": 4, + "Multimodal Learning": 4, + "Regression": 4, + "Image Segmentation": 2, + "Optical Character Recognition": 1, + "Visual Object Detection": 1 + }, + "CSETv1.Detrimental Content": { + "no": 1281, + "yes": 262, + "maybe": 39 + }, + "CSETv1.AI tools and methods": { + "natural language processing": 235, + "computer vision": 217, + "Natural Language Processesing": 77, + "facial recognition": 55, + "voice recognition": 55, + "neural networks": 54, + "audio transcription": 51, + "machine learning": 51, + "image mapping": 45, + "point mapping": 45, + "unclear": 42, + "object detection": 38, + "human language technology": 32, + "search engine optimization": 31, + "Sensor Data Processing": 29, + "prediction": 28, + "Dijkstra Algorithm": 24, + "face detection": 24, + "logistic regression": 24, + "shortest-path algorithm": 24, + "non-linear SVM": 22, + "signal detection theory": 22, + "facial reconstruction": 21, + "image reconstruction": 21, + "large language models": 17, + "image match": 16, + "text to speech": 16, + "deep learning": 15, + "mesh autoencoders": 13, + "classification": 12, + "vector embedding": 11, + "machine vision": 10, + "large language model": 8, + "regular expressions": 6, + "robotics": 5, + "speech recognition": 5, + "estimation": 4, + "neural network": 3, + "transformer": 3, + "Text to Speach": 2, + "emotion detection": 2, + "large-language models": 2, + "logistic regression model": 2, + "ranking": 2, + "C4.5 algorithm": 1, + "GPT-3": 1, + "Markov decision process": 1, + "SSD network algorithm": 1, + "VCG network": 1, + "YOLO network algorithm": 1, + "classiification": 1, + "convolutional neural networks": 1, + "decision tree": 1, + "linear algebra": 1, + "natural language": 1, + "natural language response": 1, + "pixel embeddings": 1, + "recommendation": 1, + "reinforcement learning": 1, + "response model": 1, + "text scraping": 1, + "unsupervised learning": 1, + "website code": 1 + }, + "CSETv1.Operating Conditions": { + "operationally representative": 63, + "Testing": 29, + "Driverless car": 28, + "night": 25, + "natural disaster - wildfires": 22, + "Twin faces": 21, + "unclear enunciation": 16, + "inclement weather and snow": 6, + "COVID-19 pandemic": 4, + "inclement weather - snow": 2, + "seasonal road closures": 2, + "Non-operational": 1 + }, + "CSETv1.Physical System Type": { + "Manufacturing Robot": 67, + "none": 59, + "rocket/egg shaped, 300 pound, 5 ft. tall security robot": 57, + "Apple iPhone X": 45, + "Amazon Alexa Echo Dot speaker": 35, + "Volvo XC90 SUV": 35, + "Camera": 29, + "Metro train": 29, + "Vehicles (Lexus, Audi, Chrysler Pacifica)": 28, + "Tesla vehicle (Model 3, Model S, Model X)": 25, + "Tesla Model S": 24, + "oval, eight-seater, autonomous, electric shuttle bus": 24, + "Boeing 737 Max airplane": 19, + "Amazon Echo Dot Smart Speaker": 16, + "medical robot": 12, + "Vehicles (Audi Q5 Crossover, Lexus RX400h Crossover)": 11, + "microphones": 11, + "Electric vehicle": 10, + "Tesla Model 3": 6, + "smoke detector": 6, + "A cone-shaped, 400-pound security robot on wheels": 5, + "Nissan vehicles (Rogue, Rogue Hybrid, Rogue Sport, Sentra)": 5, + "Amazon Echo": 4, + "Tesla sedan": 4, + "Vehicles (Volvo, Lexus, Ford, Google, etc.)": 4, + "drone": 4, + "fulfillment robot": 4, + "2016 Tesla Model S": 3, + "Handheld barcode scanner; flat, round mobile robots;": 3, + "4ft tall vaguely humanoid droid on wheels": 2, + "Amazon Echo Dot": 2, + "Humanoid robot": 2, + "body scanner": 2, + "car": 2, + "store security camera": 2, + "Palm VII": 1, + "Starship delivery robot": 1, + "in-van cameras and sensors": 1, + "robotic dog": 1, + "vaguely humanoid robot": 1 + }, + "CSETv1.Sector of Deployment": { + "information and communication": 562, + "transportation and storage": 273, + "Arts, entertainment and recreation": 190, + "wholesale and retail trade": 154, + "administrative and support service activities": 125, + "public administration": 117, + "law enforcement": 114, + "human health and social work activities": 92, + "professional, scientific and technical activities": 71, + "financial and insurance activities": 69, + "manufacturing": 67, + "Education": 41, + "defense": 31, + "accommodation and food service activities": 18, + "other": 10, + "real estate activities": 5, + "other service activities": 2, + "unclear": 2 + }, + "GMF.Potential AI Technology": { + "Regression": 54, + "Image Segmentation": 45, + "Classification": 44, + "Convolutional Neural Network": 44, + "Ensemble Aggregation": 39, + "Distributional Learning": 35, + "3D reconstruction": 29, + "Other domain-specific approaches": 29, + "Face Detection": 27, + "Satellite Imaging": 27, + "Image Classification": 26, + "Intermediate modeling": 26, + "Multimodal Learning": 26, + "Diverse Data": 13, + "Visual Object Detection": 11, + "Clustering": 6, + "Geolocation Data": 4, + "Optical Character Recognition": 4, + "Transformer": 3, + "Acoustic Fingerprint": 2, + "Spectrogram": 2, + "Siamese Network": 1 + }, + "CSETv1.Date of Incident Year": { + "1979": 4, + "1983": 27, + "2000": 13, + "2001": 6, + "2008": 12, + "2009": 28, + "2010": 30, + "2011": 43, + "2012": 41, + "2013": 53, + "2014": 95, + "2015": 78, + "2016": 268, + "2017": 382, + "2018": 181, + "2019": 43, + "2020": 83, + "2021": 82, + "2022": 36, + "2023": 77 + }, + "CSETv1.Entertainment Industry": { + "no": 1401, + "yes": 140, + "maybe": 41 + }, + "CSETv1.Infrastructure Sectors": { + "transportation": 139, + "healthcare and public health": 75, + "financial services": 36, + "defense-industrial base": 31, + "communications": 29, + "information technology": 25, + "emergency services": 23, + "food and agriculture": 8, + "Other": 3, + "commercial facilities": 2, + "government facilities": 2, + "unclear": 2 + }, + "CSETv1.Harm Distribution Basis": { + "none": 1059, + "race": 346, + "sex": 198, + "nation of origin, citizenship, immigrant status": 100, + "religion": 88, + "sexual orientation or gender identity": 73, + "financial means": 44, + "disability": 41, + "geography": 35, + "ideology": 33, + "age": 30, + "familial status (e.g., having or not having children) or pregnancy": 4, + "unclear": 4, + "other": 3 + }, + "CSETv1.Multiple AI Interaction": { + "no": 1525, + "yes": 48, + "maybe": 9 + }, + "GMF.Known AI Technical Failure": { + "Unsafe Exposure or Access": 59, + "Distributional Bias": 55, + "Misuse": 37, + "Adversarial Data": 29, + "Generalization Failure": 28, + "Limited Dataset": 28, + "Black Swan Event": 27, + "Data or Labelling Noise": 27, + "Dataset Imbalance": 27, + "Underfitting": 24, + "Lack of Adversarial Robustness": 23, + "Tuning Issues": 17, + "Inappropriate Training Content": 16, + "Inadequate Anonymization": 13, + "Unauthorized Data": 13, + "Underspecification": 10, + "Context Misidentification": 9, + "Gaming Vulnerability": 5, + "Lack of Capability Control": 5, + "Concept Drift": 4, + "Misinformation Generation Hazard": 3, + "Incomplete Data Attribute Capture": 2, + "Lack of Explainability": 2, + "Lack of Transparency": 2, + "Misconfigured Threshold": 1 + }, + "CSETv1.Clear link to Technology": { + "yes": 1425, + "no": 105, + "maybe": 52 + }, + "CSETv1.Protected Characteristic": { + "no": 1070, + "yes": 505, + "maybe": 7 + }, + "CSETv1.Public Sector Deployment": { + "no": 1324, + "yes": 249, + "maybe": 9 + }, + "CSETv1.Impact on Critical Services": { + "no": 1474, + "yes": 106, + "maybe": 2 + }, + "GMF.Potential AI Technical Failure": { + "Generalization Failure": 135, + "Context Misidentification": 94, + "Dataset Imbalance": 56, + "Underspecification": 52, + "Unauthorized Data": 45, + "Underfitting": 41, + "Hardware Failure": 39, + "Lack of Capability Control": 39, + "Data or Labelling Noise": 38, + "Inadequate Anonymization": 35, + "Overfitting": 34, + "Misinformation Generation Hazard": 33, + "Gaming Vulnerability": 30, + "Distributional Bias": 29, + "Misuse": 29, + "Misconfigured Aggregation": 26, + "Concept Drift": 25, + "Misaligned Objective": 23, + "Misconfigured Threshold": 18, + "Incomplete Data Attribute Capture": 17, + "Limited Dataset": 17, + "Covariate Shift": 16, + "Inadequate Data Sampling": 13, + "Tuning Issues": 10, + "Limited User Access": 6, + "Data Memorization": 5, + "Lack of Explainability": 5, + "Adversarial Data": 4, + "Backup Failure": 4, + "Lack of Transparency": 4, + "Software Bug": 4, + "Robustness Failure": 3, + "Black Box": 2, + "Lack of Adversarial Robustness": 2, + "Problematic Input": 2 + }, + "CSETv1.Location Country (two letters)": { + "US": 769, + "RU": 59, + "CN": 45, + "IN": 41, + "AU": 40, + "IE": 35, + "DE": 32, + "PS": 26, + "VN": 26, + "NZ": 24, + "ID": 19, + "KR": 17, + "GB": 13, + "NL": 6, + "United States": 6, + "CA": 5, + "FR": 5, + "GR": 4, + "LY": 4, + "SE": 4, + "IT": 2, + "JP": 2, + "AR": 1, + "CH": 1, + "IL": 1, + "RS": 1 + }, + "CSETv1.Report, Test, or Study of data": { + "no": 1511, + "yes": 63, + "maybe": 8 + }, + "CSETv1.Special Interest Intangible Harm": { + "no": 862, + "yes": 703, + "maybe": 17 + }, + "CSETv1.User Test in Controlled Conditions": { + "no": 1555, + "yes": 25, + "maybe": 2 + }, + "CSETv1.User Test in Operational Conditions": { + "no": 1367, + "yes": 211, + "maybe": 4 + }, + "CSETv1.Producer Test in Controlled Conditions": { + "no": 1509, + "yes": 69, + "maybe": 4 + }, + "CSETv1.Producer Test in Operational Conditions": { + "no": 1453, + "yes": 129 + }, + "CSETv1.Annotator’s AI special interest intangible harm assessment": { + "no": 949, + "yes": 609, + "maybe": 24 + } + }, + "facets_stats": { + "incident_id": { + "min": 1, + "max": 850, + "avg": 336, + "sum": 1304307 + }, + "CSETv1.Injuries": { + "min": 0, + "max": 55000, + "avg": 114, + "sum": 184244 + }, + "CSETv1.Lives Lost": { + "min": 0, + "max": 189, + "avg": 3, + "sum": 5922 + }, + "epoch_incident_date": { + "min": 433382400, + "max": 1732060800, + "avg": 1570344663, + "sum": 6078804192000 + }, + "epoch_date_published": { + "min": 828489600, + "max": 1732147200, + "avg": 1606384020, + "sum": 6218312544000 + }, + "CSETv1.Date of Incident Year": { + "min": 1979, + "max": 2023, + "avg": 2015, + "sum": 3189067 + } + }, + "exhaustiveFacetsCount": true, + "exhaustiveFacetValues": false, + "exhaustiveNbHits": true, + "exhaustive": { + "facetsCount": true, + "facetValues": false, + "nbHits": true + }, + "query": "", + "params": "distinct=false&facetFilters=%5B%5B%22is_incident_report%3Atrue%22%5D%5D&facets=%5B%22CSETv1.AI%20Harm%20Level%22%2C%22CSETv1.AI%20System%22%2C%22CSETv1.AI%20System%20Description%22%2C%22CSETv1.AI%20Tangible%20Harm%20Level%20Notes%22%2C%22CSETv1.AI%20Task%22%2C%22CSETv1.AI%20tools%20and%20methods%22%2C%22CSETv1.Annotation%20Status%22%2C%22CSETv1.Annotator%22%2C%22CSETv1.Annotator%E2%80%99s%20AI%20special%20interest%20intangible%20harm%20assessment%22%2C%22CSETv1.Autonomy%20Level%22%2C%22CSETv1.Clear%20link%20to%20Technology%22%2C%22CSETv1.Clear%20link%20to%20technology%22%2C%22CSETv1.Data%20Inputs%22%2C%22CSETv1.Date%20of%20Incident%20Day%22%2C%22CSETv1.Date%20of%20Incident%20Month%22%2C%22CSETv1.Date%20of%20Incident%20Year%22%2C%22CSETv1.Deployed%22%2C%22CSETv1.Detrimental%20Content%22%2C%22CSETv1.Embedded%22%2C%22CSETv1.Entertainment%20Industry%22%2C%22CSETv1.Entities%22%2C%22CSETv1.Estimated%20Date%22%2C%22CSETv1.Estimated%20Harm%20Quantities%22%2C%22CSETv1.Harm%20Distribution%20Basis%22%2C%22CSETv1.Harm%20Domain%22%2C%22CSETv1.Harmed%20Class%20of%20Entities%22%2C%22CSETv1.Impact%20on%20Critical%20Services%22%2C%22CSETv1.Incident%20Number%22%2C%22CSETv1.Infrastructure%20Sectors%22%2C%22CSETv1.Injuries%22%2C%22CSETv1.Intentional%20Harm%22%2C%22CSETv1.Involving%20Minor%22%2C%22CSETv1.Lives%20Lost%22%2C%22CSETv1.Location%20City%22%2C%22CSETv1.Location%20Country%20(two%20letters)%22%2C%22CSETv1.Location%20Region%22%2C%22CSETv1.Location%20State%2FProvince%20(two%20letters)%22%2C%22CSETv1.Multiple%20AI%20Interaction%22%2C%22CSETv1.Notes%20(%20Tangible%20Harm%20Quantities%20Information)%22%2C%22CSETv1.Notes%20(AI%20Functionality%20and%20Techniques)%22%2C%22CSETv1.Notes%20(AI%20special%20interest%20intangible%20harm)%22%2C%22CSETv1.Notes%20(Environmental%20and%20Temporal%20Characteristics)%22%2C%22CSETv1.Notes%20(Information%20about%20AI%20System)%22%2C%22CSETv1.Notes%20(special%20interest%20intangible%20harm)%22%2C%22CSETv1.Operating%20Conditions%22%2C%22CSETv1.Peer%20Reviewer%22%2C%22CSETv1.Physical%20Objects%22%2C%22CSETv1.Physical%20System%20Type%22%2C%22CSETv1.Producer%20Test%20in%20Controlled%20Conditions%22%2C%22CSETv1.Producer%20Test%20in%20Operational%20Conditions%22%2C%22CSETv1.Protected%20Characteristic%22%2C%22CSETv1.Public%20Sector%20Deployment%22%2C%22CSETv1.Quality%20Control%22%2C%22CSETv1.Report%2C%20Test%2C%20or%20Study%20of%20data%22%2C%22CSETv1.Rights%20Violation%22%2C%22CSETv1.Sector%20of%20Deployment%22%2C%22CSETv1.Special%20Interest%20Intangible%20Harm%22%2C%22CSETv1.Tangible%20Harm%22%2C%22CSETv1.There%20is%20a%20potentially%20identifiable%20specific%20entity%20that%20experienced%20the%20harm%22%2C%22CSETv1.User%20Test%20in%20Controlled%20Conditions%22%2C%22CSETv1.User%20Test%20in%20Operational%20Conditions%22%2C%22GMF.Known%20AI%20Goal%22%2C%22GMF.Known%20AI%20Goal%20Classification%20Discussion%22%2C%22GMF.Known%20AI%20Goal%20Snippets%22%2C%22GMF.Known%20AI%20Technical%20Failure%22%2C%22GMF.Known%20AI%20Technical%20Failure%20Classification%20Discussion%22%2C%22GMF.Known%20AI%20Technical%20Failure%20Snippets%22%2C%22GMF.Known%20AI%20Technology%22%2C%22GMF.Known%20AI%20Technology%20Classification%20Discussion%22%2C%22GMF.Known%20AI%20Technology%20Snippets%22%2C%22GMF.Potential%20AI%20Goal%22%2C%22GMF.Potential%20AI%20Goal%20Classification%20Discussion%22%2C%22GMF.Potential%20AI%20Goal%20Snippets%22%2C%22GMF.Potential%20AI%20Technical%20Failure%22%2C%22GMF.Potential%20AI%20Technical%20Failure%20Classification%20Discussion%22%2C%22GMF.Potential%20AI%20Technical%20Failure%20Snippets%22%2C%22GMF.Potential%20AI%20Technology%22%2C%22GMF.Potential%20AI%20Technology%20Classification%20Discussion%22%2C%22GMF.Potential%20AI%20Technology%20Snippets%22%2C%22authors%22%2C%22classifications%22%2C%22epoch_date_published%22%2C%22epoch_incident_date%22%2C%22flag%22%2C%22incident_id%22%2C%22is_incident_report%22%2C%22language%22%2C%22namespaces%22%2C%22source_domain%22%2C%22submitters%22%2C%22tags%22%5D&highlightPostTag=__%2Fais-highlight__&highlightPreTag=__ais-highlight__&hitsPerPage=28&maxValuesPerFacet=999&page=0&query=&tagFilters=", + "index": "instant_search-en-featured", + "renderingContent": {}, + "processingTimeMS": 3, + "processingTimingsMS": { + "_request": { + "roundTrip": 160 + }, + "afterFetch": { + "format": { + "highlighting": 3, + "snippeting": 6, + "total": 12 + }, + "total": 2 + }, + "fetch": { + "total": 1 + }, + "total": 3 + }, + "serverTimeMS": 16, + } + ] +}; \ No newline at end of file diff --git a/site/gatsby-site/playwright/utils.ts b/site/gatsby-site/playwright/utils.ts index c6b09c5b1b..66ac5c8893 100644 --- a/site/gatsby-site/playwright/utils.ts +++ b/site/gatsby-site/playwright/utils.ts @@ -10,6 +10,7 @@ import * as crypto from 'node:crypto'; import { ObjectId } from 'bson'; import users from './seeds/customData/users'; import authUsers from './seeds/auth/users'; +import { algoliaMock } from './fixtures/algoliaMock'; declare module '@playwright/test' { interface Request { @@ -333,3 +334,31 @@ export function getLanguages() { { code: 'ja', hrefLang: 'ja', name: 'Japanese', localName: '日本語', langDir: 'ltr', dateFormat: 'YYYY/MM/DD' }, ]; } + +// TODO: this mock should pull from the database instead of being hardcoded +export async function mockAlgolia(page: Page) { + + await page.route('**/*.algolia.net/1/indexes/*/queries*', async route => { + const response = await route.fetch(); + + await route.fulfill({ + status: 200, + json: algoliaMock, + headers: { + ...response.headers(), + } + }); + }); + + await page.route('**/*.algolianet.com/1/indexes/*/queries*', async route => { + const response = await route.fetch(); + + await route.fulfill({ + status: 200, + json: algoliaMock, + headers: { + ...response.headers(), + } + }); + }); +} \ No newline at end of file From b4b6133d7df7c6e271f96e7077b294126ed589dc Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Thu, 28 Nov 2024 18:34:14 -0300 Subject: [PATCH 58/99] Update tests --- .../gatsby-site/playwright/e2e-full/apps/newsdigest.spec.ts | 2 +- site/gatsby-site/playwright/e2e-full/dynamicCite.spec.ts | 6 +++--- site/gatsby-site/playwright/e2e/entities.spec.ts | 3 +-- 3 files changed, 5 insertions(+), 6 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/apps/newsdigest.spec.ts b/site/gatsby-site/playwright/e2e-full/apps/newsdigest.spec.ts index 8d5f070b85..1247da68a7 100644 --- a/site/gatsby-site/playwright/e2e-full/apps/newsdigest.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/apps/newsdigest.spec.ts @@ -32,7 +32,7 @@ test.describe('News Digest', () => { test('Should dismiss and restore items', async ({ page, login, skipOnEmptyEnvironment }) => { - const userId = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); + const userId = await login(); await init({ customData: { users: [{ userId, first_name: 'Test', last_name: 'User', roles: ['admin'] }] } }, { drop: true }); await page.goto(url); diff --git a/site/gatsby-site/playwright/e2e-full/dynamicCite.spec.ts b/site/gatsby-site/playwright/e2e-full/dynamicCite.spec.ts index 1911d6b8f3..afa42896cc 100644 --- a/site/gatsby-site/playwright/e2e-full/dynamicCite.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/dynamicCite.spec.ts @@ -18,7 +18,7 @@ test.describe('Dynamic Cite pages', () => { test('Should load dynamic Incident data', async ({ page, login, skipOnEmptyEnvironment }) => { - const [userId] = await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); + const [userId] = await login(); await init(); await seedCollection({ name: 'users', docs: [{ userId: userId, roles: ['admin'], first_name: 'John', last_name: 'Doe' }], drop: false }); @@ -44,7 +44,7 @@ test.describe('Dynamic Cite pages', () => { }); test('Should display a new Variant if live mode is turned on', async ({ page, login, skipOnEmptyEnvironment }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); + await login(); await page.goto(url); const new_date_published = '2000-01-01'; @@ -74,7 +74,7 @@ test.describe('Dynamic Cite pages', () => { }); test('There should not be image errors (400)', async ({ page, login, skipOnEmptyEnvironment }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); + await login(); await page.goto(url); await page.addInitScript(() => { window.console.error = (...args) => { diff --git a/site/gatsby-site/playwright/e2e/entities.spec.ts b/site/gatsby-site/playwright/e2e/entities.spec.ts index bd53470e55..7bb41e8cc1 100644 --- a/site/gatsby-site/playwright/e2e/entities.spec.ts +++ b/site/gatsby-site/playwright/e2e/entities.spec.ts @@ -1,5 +1,4 @@ import { expect } from '@playwright/test'; -import config from '../config'; import { test } from '../utils'; test.describe('Entities page', () => { @@ -66,7 +65,7 @@ test.describe('Entities page', () => { await page.goto(url); expect(await page.locator('[data-cy="edit-entity-btn"]').count()).toBe(0); - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); + await login(); await page.goto(url); const editButton = page.locator('[data-cy="edit-entity-btn"]').first(); expect(await editButton.getAttribute('href')).toBe('/entities/edit?entity_id=facebook'); From b4fa1d01d31078bfdc516dbcb922d6324df7fbca Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 29 Nov 2024 17:44:37 -0300 Subject: [PATCH 59/99] update subscriptions tests --- .../playwright/e2e-full/subscription.spec.ts | 117 ++++++------------ 1 file changed, 37 insertions(+), 80 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/subscription.spec.ts b/site/gatsby-site/playwright/e2e-full/subscription.spec.ts index d28fc06443..13a7effdba 100644 --- a/site/gatsby-site/playwright/e2e-full/subscription.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/subscription.spec.ts @@ -13,7 +13,19 @@ test.describe('Subscriptions', () => { await init(); - const [userId] = await login(); + await login(); + + await page.goto(url); + + await expect(page.locator('[data-cy="incident-subscription-item"]')).toHaveCount(2); + await expect(page.locator(`[data-cy="incident-subscription-item"]`).getByText('Updates on incident #1: Incident 1')).toBeVisible(); + await expect(page.locator(`[data-cy="incident-subscription-item"]`).getByText('Updates on incident #2: Incident 2')).toBeVisible(); + }); + + test("Incident Updates: Should display a information message if the user doesn't have subscriptions", async ({ page, login }) => { + await init(); + + await login(); const subscriptions: DBSubscription[] = [ { @@ -21,22 +33,12 @@ test.describe('Subscriptions', () => { entityId: undefined, incident_id: 1, type: SUBSCRIPTION_TYPE.incident, - userId: userId, + userId: 'random-user-id', }, ] - await seedFixture({ customData: { subscriptions } }, false); - - await page.goto(url); - - await expect(page.locator('[data-cy="incident-subscription-item"]')).toHaveCount(1); - await expect(page.locator(`[data-cy="incident-subscription-item"]`).getByText('Updates on incident #1: Incident 1')).toBeVisible(); - }); - - test("Incident Updates: Should display a information message if the user doesn't have subscriptions", async ({ page, login }) => { - await init(); + await seedFixture({ customData: { subscriptions } }); - await login(); await page.goto(url); @@ -49,25 +51,6 @@ test.describe('Subscriptions', () => { const [userId, accessToken] = await login(); - const subscriptions: DBSubscription[] = [ - { - _id: new ObjectId("62f40cd14016f5858d72385d"), - entityId: undefined, - incident_id: 1, - type: SUBSCRIPTION_TYPE.incident, - userId: userId, - }, - { - _id: new ObjectId("62f40cd14016f5858d72385e"), - entityId: undefined, - incident_id: 2, - type: SUBSCRIPTION_TYPE.incident, - userId: userId, - } - ] - - await seedFixture({ customData: { subscriptions } }, false); - await page.goto(url); @@ -91,7 +74,20 @@ test.describe('Subscriptions', () => { { Cookie: `next-auth.session-token=${encodeURIComponent(accessToken)};` } ); - expect(subscriptionsData).toMatchObject([{ _id: "62f40cd14016f5858d72385e" }]); + expect(subscriptionsData).toMatchObject([ + { + _id: "619b47eb5eed5334edfa3bd7", + userId: { + userId: "6737a6e881955aa4905ccb04", + }, + }, + { + _id: "60a7c5b7b4f5b8a6d8f9c7e7", + userId: { + userId: "6737a6e881955aa4905ccb04", + }, + }, + ]); }); test('New Incidents: Should display the switch toggle off if user does not have a subscription', async ({ page, login }) => { @@ -161,51 +157,25 @@ test.describe('Subscriptions', () => { type: SUBSCRIPTION_TYPE.incident, userId: userId, }, - { - _id: new ObjectId("62f40cd14016f5858d72385e"), - entityId: undefined, - incident_id: 2, - type: SUBSCRIPTION_TYPE.incident, - userId: userId, - } ] await seedFixture({ customData: { subscriptions } }, false); await page.goto(url); - await expect(page.locator(`[data-cy="incident-subscription-item"]`)).toHaveCount(1); - await expect(page.locator(`[data-cy="incident-subscription-item"]`).getByText('Updates on incident #2: Incident 2')).toBeVisible(); + await expect(page.locator(`[data-cy="incident-subscription-item"]`)).toHaveCount(2); }); test('Entity: Should display user subscriptions', async ({ page, login }) => { await init(); - const [userId] = await login(); - - const subscriptions: DBSubscription[] = [ - { - _id: new ObjectId("62f40cd14016f5858d72385d"), - entityId: 'entity-1', - type: SUBSCRIPTION_TYPE.entity, - userId: userId, - }, - { - _id: new ObjectId("62f40cd14016f5858d72385e"), - entityId: 'entity-2', - type: SUBSCRIPTION_TYPE.entity, - userId: userId, - } - ] - - await seedFixture({ customData: { subscriptions } }, false); + await login(); await page.goto(url); await expect(page.locator('[data-cy="entity-subscription-item"]').getByText('New Entity 1 Entity incidents')).toBeVisible(); - await expect(page.locator('[data-cy="entity-subscription-item"]').getByText('New Entity 2 Entity incidents')).toBeVisible(); }); test("Entity: Should display a information message if the user doesn't have subscriptions", async ({ page, login }) => { @@ -214,6 +184,10 @@ test.describe('Subscriptions', () => { await login(); + const subscriptions: DBSubscription[] = [] + + await seedFixture({ customData: { subscriptions } }); + await page.goto(url); await expect(page.locator('[data-cy="entity-subscription-item"]')).not.toBeVisible(); @@ -226,23 +200,6 @@ test.describe('Subscriptions', () => { const [userId, accessToken] = await login(); - const subscriptions: DBSubscription[] = [ - { - _id: new ObjectId("62f40cd14016f5858d72385d"), - entityId: 'entity-1', - type: SUBSCRIPTION_TYPE.entity, - userId: userId, - }, - { - _id: new ObjectId("62f40cd14016f5858d72385e"), - entityId: 'entity-2', - type: SUBSCRIPTION_TYPE.entity, - userId: userId, - } - ] - - await seedFixture({ customData: { subscriptions } }, false); - await page.goto(url); @@ -263,10 +220,10 @@ test.describe('Subscriptions', () => { } }`, }, { - Cookie: `next-auth.session-token=${encodeURIComponent(accessToken)};` + Cookie: `next-auth.session-token=${encodeURIComponent(accessToken)};` }); - expect(subscriptionsData).toMatchObject([{ _id: "62f40cd14016f5858d72385e" }]); + expect(subscriptionsData).not.toMatchObject([{ _id: "619b47eb5eed5334edfa3bd7" }]); }); }); \ No newline at end of file From 4c5ce1ee41dfb140adc4c56be9e3abd74a6ffddc Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 29 Nov 2024 17:44:52 -0300 Subject: [PATCH 60/99] Update seeds --- site/gatsby-site/playwright/seeds/customData/subscriptions.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/gatsby-site/playwright/seeds/customData/subscriptions.ts b/site/gatsby-site/playwright/seeds/customData/subscriptions.ts index aa00e42959..989f9c222f 100644 --- a/site/gatsby-site/playwright/seeds/customData/subscriptions.ts +++ b/site/gatsby-site/playwright/seeds/customData/subscriptions.ts @@ -19,7 +19,7 @@ const items: DBSubscription[] = [ { _id: new ObjectId('60a7c5b7b4f5b8a6d8f9c7e7'), entityId: undefined, - incident_id: 1, + incident_id: 2, type: "incident", userId: '6737a6e881955aa4905ccb04', } From 2cefdc3abb68b9e06b2b2351228e77c7905422cd Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 29 Nov 2024 18:57:09 -0300 Subject: [PATCH 61/99] Update tests --- .../playwright/e2e-full/incidents/history.spec.ts | 7 +++++++ site/gatsby-site/playwright/e2e-full/submit.spec.ts | 2 ++ 2 files changed, 9 insertions(+) diff --git a/site/gatsby-site/playwright/e2e-full/incidents/history.spec.ts b/site/gatsby-site/playwright/e2e-full/incidents/history.spec.ts index b163b23719..77a6af4059 100644 --- a/site/gatsby-site/playwright/e2e-full/incidents/history.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/incidents/history.spec.ts @@ -14,6 +14,8 @@ test.describe('Incidents', () => { test('Should display the Version History table data', async ({ page }) => { + await init(); + const { data: { history_incidents } } = await query({ query: gql` query { @@ -29,6 +31,9 @@ test.describe('Incidents', () => { await page.goto(url); await page.locator('h2').getByText('Version History').waitFor(); + + await page.locator('[data-cy="history-table"]').waitFor(); + const rows = await page.locator('[data-cy="history-row"]').elementHandles(); expect(rows.length).toBe(4); @@ -164,6 +169,8 @@ test.describe('Incidents', () => { await page.goto(url); + await page.locator('[data-cy="history-row"]').waitFor(); + const rows = await page.locator('[data-cy="history-row"]'); await rows.nth(0).locator('[data-cy="view-full-version-button"]').click(); diff --git a/site/gatsby-site/playwright/e2e-full/submit.spec.ts b/site/gatsby-site/playwright/e2e-full/submit.spec.ts index 615d0f6b37..538fec1cbc 100644 --- a/site/gatsby-site/playwright/e2e-full/submit.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/submit.spec.ts @@ -156,6 +156,8 @@ test.describe('The Submit form', () => { test('As editor, should submit a new incident report, adding an incident title and editors.', async ({ page, login, skipOnEmptyEnvironment }) => { + test.slow(); + await init(); await login(); From d617048cb194d1bfdc74430c076d37ed869e8633 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 29 Nov 2024 19:12:12 -0300 Subject: [PATCH 62/99] Update tests --- .../e2e-full/incidents/history.spec.ts | 2 +- .../playwright/e2e-full/login.spec.ts | 14 ++- site/gatsby-site/playwright/e2e/login.spec.ts | 107 +----------------- 3 files changed, 10 insertions(+), 113 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/incidents/history.spec.ts b/site/gatsby-site/playwright/e2e-full/incidents/history.spec.ts index 77a6af4059..df711d26d4 100644 --- a/site/gatsby-site/playwright/e2e-full/incidents/history.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/incidents/history.spec.ts @@ -169,7 +169,7 @@ test.describe('Incidents', () => { await page.goto(url); - await page.locator('[data-cy="history-row"]').waitFor(); + await page.locator('[data-cy="history-row"]').first().waitFor(); const rows = await page.locator('[data-cy="history-row"]'); diff --git a/site/gatsby-site/playwright/e2e-full/login.spec.ts b/site/gatsby-site/playwright/e2e-full/login.spec.ts index 65614daf62..38ad3bb049 100644 --- a/site/gatsby-site/playwright/e2e-full/login.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/login.spec.ts @@ -1,5 +1,5 @@ import { expect } from '@playwright/test'; -import { generateMagicLink, test, testUser } from '../utils'; +import { generateMagicLink, test } from '../utils'; import { init } from '../memory-mongo'; test.describe('Login', () => { @@ -29,7 +29,7 @@ test.describe('Login', () => { await page.getByText('Login').click(); - const email = testUser.email; + const email = 'test.user@incidentdatabase.ai'; await page.route('**/api/auth/signin/http-email', async (route) => { @@ -48,9 +48,11 @@ test.describe('Login', () => { }); }); - await expect(page.locator('input[name=email]')).toBeVisible(); - - await page.locator('input[name=email]').fill(email); + await expect(async () => { + await page.locator('input[name=email]').clear(); + await page.locator('input[name=email]').fill(email); + await expect(page.locator('[data-cy="login-btn"]')).toBeEnabled({ timeout: 1000 }); + }).toPass(); const signupResponse = page.waitForResponse('**/api/auth/signin/http-email'); @@ -65,7 +67,7 @@ test.describe('Login', () => { async ({ page, skipOnEmptyEnvironment, login }) => { const redirectTo = '/cite/1/'; - const magicLink = await generateMagicLink(testUser.email, redirectTo); + const magicLink = await generateMagicLink('test.user@incidentdatabase.ai', redirectTo); await page.goto(magicLink); diff --git a/site/gatsby-site/playwright/e2e/login.spec.ts b/site/gatsby-site/playwright/e2e/login.spec.ts index 84288eebaf..18ed57a9d5 100644 --- a/site/gatsby-site/playwright/e2e/login.spec.ts +++ b/site/gatsby-site/playwright/e2e/login.spec.ts @@ -1,6 +1,4 @@ -import { expect } from '@playwright/test'; -import { waitForRequest, conditionalIntercept, test } from '../utils'; -import config from '../config'; +import { test } from '../utils'; test.describe('Login', () => { const url = '/login'; @@ -8,107 +6,4 @@ test.describe('Login', () => { test('Should successfully load login page', async ({ page }) => { await page.goto(url); }); - - test('Should redirect to home page after login by default', - async ({ page, skipOnEmptyEnvironment, login }) => { - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); - - await expect(page).toHaveURL('/'); - } - ); - - test.skip('Should redirect to the account page if the signup storage key is set', - async ({ page, skipOnEmptyEnvironment, login }) => { - - await page.goto('/'); - - await page.evaluate(() => window.localStorage.setItem('signup', '1')); - - await login(config.E2E_ADMIN_USERNAME, config.E2E_ADMIN_PASSWORD); - - await expect(page).toHaveURL('/account/?askToCompleteProfile=1'); - - await expect(page.getByTestId('edit-user-modal')).toBeVisible({ timeout: 30000 }); - - const localStorage = await page.evaluate(() => window.localStorage); - - expect(localStorage.signup).toBeUndefined(); - } - ); - - test('Should redirect to specific page after login if redirectTo is provided', - async ({ page, skipOnEmptyEnvironment, login }) => { - const redirectTo = '/cite/10/'; - - await page.goto(`${url}?redirectTo=${redirectTo}`); - await page.locator('input[name=email]').fill(config.E2E_ADMIN_USERNAME); - await page.locator('input[name=password]').fill(config.E2E_ADMIN_PASSWORD); - await page.locator('[data-cy="login-btn"]').click(); - - await expect(page).toHaveURL(redirectTo, { timeout: 60000 }); - } - ); - - test('Should display error toast if the email address or password is incorrect', async ({ page }) => { - await page.goto(url); - await page.locator('input[name=email]').fill('fakeUser@test.com'); - await page.locator('input[name=password]').fill('fakePassword'); - await page.locator('[data-cy="login-btn"]').click(); - - await expect(page.locator('[data-cy="toast"]')).toBeVisible(); - await expect(page.locator('[data-cy="toast"]')).toContainText('unauthorized'); - }); - - test('Should disable Login button if email address is not valid', async ({ page }) => { - await page.goto(url); - await page.locator('input[name=email]').fill('fakeUser'); - await page.locator('input[name=password]').fill('fakePassword'); - await expect(page.locator('[data-cy="login-btn"]')).toBeDisabled(); - - await page.locator('input[name=email]').fill('fakeUser@test.com'); - await expect(page.locator('[data-cy="login-btn"]')).toBeEnabled(); - }); - - test('Should redirect to forgot password page if the user clicks on "Forgot password?" link', async ({ page }) => { - await page.goto(url); - await page.getByText('Forgot password?').click(); - await expect(page).toHaveURL('/forgotpassword/', { timeout: 30000 }); - }); - - test('Should give the option to resend Email verification if the user is not confirmed', async ({ page, login }) => { - await conditionalIntercept( - page, - '**/login', - (req) => req.method() == "POST" && req.postDataJSON().username == config.E2E_ADMIN_USERNAME, - { - error: 'confirmation required', - error_code: 'AuthError', - link: 'https://services.cloud.mongodb.com/groups/633205e6aecbcc4b2c2067c3/apps/633207f10d438f13ab3ab4d6/logs?co_id=6549772172bdb9e8eadeea95' - }, - 'Login', - 401 - ); - - await conditionalIntercept( - page, - '**/auth/providers/local-userpass/confirm/call', - (req) => req.postDataJSON().email == config.E2E_ADMIN_USERNAME, - {}, - 'Confirmation', - 204 - ); - - await page.goto(url); - - await page.locator('input[name=email]').fill(config.E2E_ADMIN_USERNAME); - await page.locator('input[name=password]').fill(config.E2E_ADMIN_PASSWORD); - await page.locator('[data-cy="login-btn"]').click(); - - await waitForRequest('Login'); - await expect(page.locator('[data-cy="toast"]').getByText('Resend Verification email')).toBeVisible(); - await page.locator('[data-cy="toast"]').getByText('Resend Verification email').click(); - - await waitForRequest('Confirmation'); - await expect(page.locator('[data-cy="toast"]').getByText(`Verification email sent to ${config.E2E_ADMIN_USERNAME}`)).toBeVisible(); - }); }); From 94bb5995383e273cc546a5d5281dd9dcb51d1154 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 29 Nov 2024 19:12:23 -0300 Subject: [PATCH 63/99] set default status invalid --- site/gatsby-site/src/pages/login.js | 1 + 1 file changed, 1 insertion(+) diff --git a/site/gatsby-site/src/pages/login.js b/site/gatsby-site/src/pages/login.js index eaae438bf9..4a6fecbbad 100644 --- a/site/gatsby-site/src/pages/login.js +++ b/site/gatsby-site/src/pages/login.js @@ -70,6 +70,7 @@ const Login = () => { ) : ( <> Date: Mon, 2 Dec 2024 19:25:35 -0300 Subject: [PATCH 64/99] Update test --- .../playwright/e2e-full/rollbar.spec.ts | 39 +++++++++++++++++++ .../e2e/integration/rollbar.spec.ts | 24 ------------ 2 files changed, 39 insertions(+), 24 deletions(-) create mode 100644 site/gatsby-site/playwright/e2e-full/rollbar.spec.ts delete mode 100644 site/gatsby-site/playwright/e2e/integration/rollbar.spec.ts diff --git a/site/gatsby-site/playwright/e2e-full/rollbar.spec.ts b/site/gatsby-site/playwright/e2e-full/rollbar.spec.ts new file mode 100644 index 0000000000..8fb3d5bb6d --- /dev/null +++ b/site/gatsby-site/playwright/e2e-full/rollbar.spec.ts @@ -0,0 +1,39 @@ +import { expect } from "@playwright/test"; +import { test } from "../utils"; + +test.describe('Rollbar', () => { + + test('Should log an error to Rollbar', async ({ page, login }) => { + + const rollbarMock = page.waitForRequest('https://api.rollbar.com/api/1/item/'); + + await page.route('https://api.rollbar.com/api/1/item/', async (route) => { + await route.fulfill({ + status: 200, + body: JSON.stringify({ + err: 0, + result: { id: "12345" } + }) + }); + }); + + await page.route('**/api/auth/signin/http-email', async (route) => { + await route.fulfill({ + status: 200, + body: JSON.stringify({ + url: null, + error: "Email service not available" + }) + }); + }); + + await page.goto('/login'); + + await page.locator('input[name="email"]').fill("test.user@test.com"); + await page.locator('[data-cy="login-btn"]').click(); + + const rollbarRequest = await rollbarMock; + + expect(rollbarRequest.postDataJSON()).toMatchObject({ access_token: expect.any(String) }); + }); +}); \ No newline at end of file diff --git a/site/gatsby-site/playwright/e2e/integration/rollbar.spec.ts b/site/gatsby-site/playwright/e2e/integration/rollbar.spec.ts deleted file mode 100644 index 92c590cc49..0000000000 --- a/site/gatsby-site/playwright/e2e/integration/rollbar.spec.ts +++ /dev/null @@ -1,24 +0,0 @@ -import { expect } from "@playwright/test"; -import { test } from "../../utils"; -import config from "../../config"; - -test('Should log an error to Rollbar', async ({ page, login }) => { - const rollbarAPICall = page.waitForRequest('https://api.rollbar.com/api/1/item/'); - - await page.goto('/login'); - - await page.locator('input[name="email"]').fill(config.E2E_ADMIN_USERNAME); - await page.locator('input[name="password"]').fill('invalidPassword'); - await page.locator('[data-cy="login-btn"]').click(); - - const rollbarRequest = await rollbarAPICall; - const response = await rollbarRequest.response(); - - if (response) { - expect(response.status()).toBe(200); - const responseBody = await response. json(); - expect(responseBody.err).toBe(0); - } else { - throw new Error('No response received for Rollbar API call.'); - } -}); From a535b20d169fcb0feb8012720e44f69b550373a5 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Mon, 2 Dec 2024 19:26:11 -0300 Subject: [PATCH 65/99] Update login --- site/gatsby-site/src/pages/login.js | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/site/gatsby-site/src/pages/login.js b/site/gatsby-site/src/pages/login.js index 4a6fecbbad..2ae747debd 100644 --- a/site/gatsby-site/src/pages/login.js +++ b/site/gatsby-site/src/pages/login.js @@ -32,16 +32,20 @@ const Login = () => { const handleSubmit = useCallback( async ({ email }, { setSubmitting }) => { - const result = await logIn(email, redirectTo); + try { + const result = await logIn(email, redirectTo); - if (!result.error) { - navigate(`/verify-request/?email=${encodeURIComponent(email)}`); - } else { + if (!result.error) { + navigate(`/verify-request/?email=${encodeURIComponent(email)}`); + } else { + throw result?.error; + } + } catch (e) { // TODO: Add more specific error messages addToast({ message: t('An unknown error has occurred'), severity: SEVERITY.danger, - error: result.error, + error: e, }); } From 5d46951abde37e16c2697fa25048db621f9c06d6 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Mon, 2 Dec 2024 19:56:56 -0300 Subject: [PATCH 66/99] Update workflows --- .github/workflows/deploy.yml | 2 ++ .github/workflows/test-api.yml | 2 ++ .github/workflows/test-build.yml | 2 ++ .github/workflows/test-playwright-full.yml | 5 +++++ .github/workflows/test-playwright.yml | 2 ++ 5 files changed, 13 insertions(+) diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml index 92e2f07fbc..445731be32 100644 --- a/.github/workflows/deploy.yml +++ b/.github/workflows/deploy.yml @@ -123,6 +123,8 @@ jobs: ROLLBAR_POST_SERVER_ITEM_ACCESS_TOKEN: ${{ secrets.GATSBY_ROLLBAR_TOKEN }} API_MONGODB_CONNECTION_STRING: ${{ secrets.API_MONGODB_CONNECTION_STRING }} SITE_URL: ${{ inputs.site-url || vars.SITE_URL }} + NEXTAUTH_URL: ${{ inputs.site-url || vars.SITE_URL }} + NEXTAUTH_SECRET: ${{ secrets.NEXTAUTH_SECRET }} - name: Build size run: | diff --git a/.github/workflows/test-api.yml b/.github/workflows/test-api.yml index 25d95b0440..12df1bfa15 100644 --- a/.github/workflows/test-api.yml +++ b/.github/workflows/test-api.yml @@ -70,6 +70,8 @@ jobs: NOTIFICATIONS_SENDER_NAME: Test Preview NOTIFICATIONS_SENDER: test@test.com SITE_URL: http://localhost:8000 + NEXTAUTH_URL: http://localhost:8000 + NEXTAUTH_SECRET: dummy - name: Upload coverage reports to Codecov uses: codecov/codecov-action@v4 diff --git a/.github/workflows/test-build.yml b/.github/workflows/test-build.yml index 143beefab6..71809f1ef0 100644 --- a/.github/workflows/test-build.yml +++ b/.github/workflows/test-build.yml @@ -109,6 +109,8 @@ jobs: REALM_API_PRIVATE_KEY: ${{ secrets.REALM_API_PRIVATE_KEY }} ROLLBAR_POST_SERVER_ITEM_ACCESS_TOKEN: ${{ secrets.GATSBY_ROLLBAR_TOKEN }} API_MONGODB_CONNECTION_STRING: ${{ secrets.API_MONGODB_CONNECTION_STRING }} + NEXTAUTH_URL: http://localhost:8000 + NEXTAUTH_SECRET: 678x1irXYWeiOqTwCv1awvkAUbO9eHa5xzQEYhxhMms - name: Cache build uses: actions/cache/save@v4 diff --git a/.github/workflows/test-playwright-full.yml b/.github/workflows/test-playwright-full.yml index 559ab59ca7..b4850d0266 100644 --- a/.github/workflows/test-playwright-full.yml +++ b/.github/workflows/test-playwright-full.yml @@ -108,6 +108,9 @@ jobs: REALM_API_PRIVATE_KEY: ${{ secrets.REALM_API_PRIVATE_KEY }} ROLLBAR_POST_SERVER_ITEM_ACCESS_TOKEN: ${{ secrets.GATSBY_ROLLBAR_TOKEN }} API_MONGODB_CONNECTION_STRING: mongodb://127.0.0.1:4110/ + SITE_URL: http://localhost:8000 + NEXTAUTH_URL: http://localhost:8000 + NEXTAUTH_SECRET: 678x1irXYWeiOqTwCv1awvkAUbO9eHa5xzQEYhxhMms - name: Install playwright browsers run: npx playwright install --with-deps @@ -137,6 +140,8 @@ jobs: NOTIFICATIONS_SENDER_NAME: Test Preview NOTIFICATIONS_SENDER: test@test.com SITE_URL: http://localhost:8000 + NEXTAUTH_URL: http://localhost:8000 + NEXTAUTH_SECRET: 678x1irXYWeiOqTwCv1awvkAUbO9eHa5xzQEYhxhMms - name: Upload Playwright traces if: failure() diff --git a/.github/workflows/test-playwright.yml b/.github/workflows/test-playwright.yml index 7a6a6536d5..c1c1b4db0c 100644 --- a/.github/workflows/test-playwright.yml +++ b/.github/workflows/test-playwright.yml @@ -92,6 +92,8 @@ jobs: NOTIFICATIONS_SENDER_NAME: Test Preview NOTIFICATIONS_SENDER: test@test.com SITE_URL: http://localhost:8000 + NEXTAUTH_URL: http://localhost:8000 + NEXTAUTH_SECRET: 678x1irXYWeiOqTwCv1awvkAUbO9eHa5xzQEYhxhMms - uses: actions/upload-artifact@v4 if: ${{ !cancelled() }} From 99bf234545e0b3d256fa75dd890b5e8e4711390a Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 3 Dec 2024 14:22:23 -0300 Subject: [PATCH 67/99] Add netlify redirect --- site/gatsby-site/deploy-netlify.toml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/site/gatsby-site/deploy-netlify.toml b/site/gatsby-site/deploy-netlify.toml index 50b010945d..70526ec5d0 100644 --- a/site/gatsby-site/deploy-netlify.toml +++ b/site/gatsby-site/deploy-netlify.toml @@ -29,4 +29,9 @@ [[redirects]] from = "/api/lookupbyurl" to = "/.netlify/functions/lookupbyurl" - status = 200 \ No newline at end of file + status = 200 + +[[redirects]] + from = "/api/auth" + to = "/.netlify/functions/auth" + status = 200 From ca46f0c64bd464321ff0afa66c1c9310fd5726e5 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 3 Dec 2024 19:54:24 -0300 Subject: [PATCH 68/99] Add missing wildcard --- site/gatsby-site/deploy-netlify.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/gatsby-site/deploy-netlify.toml b/site/gatsby-site/deploy-netlify.toml index 70526ec5d0..4cb00e4985 100644 --- a/site/gatsby-site/deploy-netlify.toml +++ b/site/gatsby-site/deploy-netlify.toml @@ -32,6 +32,6 @@ status = 200 [[redirects]] - from = "/api/auth" + from = "/api/auth/*" to = "/.netlify/functions/auth" status = 200 From 25417e985763bd1af76e8403c81e1fde48b38582 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 3 Dec 2024 21:14:37 -0300 Subject: [PATCH 69/99] Remove hardcoded values --- site/gatsby-site/playwright/config.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/site/gatsby-site/playwright/config.ts b/site/gatsby-site/playwright/config.ts index 04cc8cee8a..a5070eec45 100644 --- a/site/gatsby-site/playwright/config.ts +++ b/site/gatsby-site/playwright/config.ts @@ -12,10 +12,8 @@ const config: ConfigType = { E2E_ADMIN_USERNAME: process.env.E2E_ADMIN_USERNAME!, IS_EMPTY_ENVIRONMENT: process.env.IS_EMPTY_ENVIRONMENT ?? '', AVAILABLE_LANGUAGES: process.env.GATSBY_AVAILABLE_LANGUAGES ?? '', - - // TODO: add theses values to the workflow - NEXTAUTH_URL: 'http://localhost:8000', - NEXTAUTH_SECRET: '678x1irXYWeiOqTwCv1awvkAUbO9eHa5xzQEYhxhMms=', + NEXTAUTH_URL: process.env.NEXTAUTH_URL!, + NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET!, } Object.keys(config).forEach((key) => { From 7057e58d3cea6a4de35d39e8aee42a8c2f67614e Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 3 Dec 2024 21:18:51 -0300 Subject: [PATCH 70/99] debug --- site/gatsby-site/server/config.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/site/gatsby-site/server/config.ts b/site/gatsby-site/server/config.ts index b67372a0f6..6f915e89f4 100644 --- a/site/gatsby-site/server/config.ts +++ b/site/gatsby-site/server/config.ts @@ -38,6 +38,8 @@ Object.keys(config).forEach((key) => { if (config[key as keyof Config] === undefined || config[key as keyof Config] === '') { throw new Error(`Config property ${key} is undefined`); } + + console.log(`Config property ${key}, value: ${config[key as keyof Config]}`); }); export default config; \ No newline at end of file From a43a48c8f75565227b811b5445ceab8d25e4c51b Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Wed, 4 Dec 2024 18:36:05 -0300 Subject: [PATCH 71/99] Remove hardcoded url --- site/gatsby-site/server/config.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/site/gatsby-site/server/config.ts b/site/gatsby-site/server/config.ts index 6f915e89f4..11d6d0d905 100644 --- a/site/gatsby-site/server/config.ts +++ b/site/gatsby-site/server/config.ts @@ -28,10 +28,8 @@ const config: Config = { NOTIFICATIONS_SENDER_NAME: process.env.NOTIFICATIONS_SENDER_NAME!, NOTIFICATIONS_SENDER: process.env.NOTIFICATIONS_SENDER!, SITE_URL: process.env.SITE_URL! || process.env.URL!, - - //TODO: add these to workflow - NEXTAUTH_URL: 'http://localhost:8000', - NEXTAUTH_SECRET: '678x1irXYWeiOqTwCv1awvkAUbO9eHa5xzQEYhxhMms=', + NEXTAUTH_URL: process.env.NEXTAUTH_URL!, + NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET!, } Object.keys(config).forEach((key) => { From 1a9618a05bbeb175c456807ecf4400a1eaf9a54e Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Thu, 12 Dec 2024 19:51:50 -0300 Subject: [PATCH 72/99] Do not allow users to login without signing up first --- site/gatsby-site/netlify/functions/auth.ts | 4 +-- site/gatsby-site/nextauth.config.ts | 36 +++++++++++++++++-- site/gatsby-site/src/contexts/UserContext.tsx | 4 +-- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/site/gatsby-site/netlify/functions/auth.ts b/site/gatsby-site/netlify/functions/auth.ts index 6102e75cd6..261ec6b766 100644 --- a/site/gatsby-site/netlify/functions/auth.ts +++ b/site/gatsby-site/netlify/functions/auth.ts @@ -84,9 +84,9 @@ export const handler = async (event, context) => { try { - const authConfig = await getAuthConfig(); + const authConfig = await getAuthConfig(req); - await NextAuth(req, res, authConfig) + await NextAuth(req as any, res as any, authConfig) const response = res.getResponse(); diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts index eac5406837..2ab513b921 100644 --- a/site/gatsby-site/nextauth.config.ts +++ b/site/gatsby-site/nextauth.config.ts @@ -2,6 +2,7 @@ import { MongoClient, ServerApiVersion } from "mongodb" import { NextAuthOptions } from "next-auth" import config from './server/config' import { sendEmail } from "./server/emails" +import { AdapterUser } from "next-auth/adapters" //TODO: add this to the workflow file, this needs to be set via env variable // SEE: https://github.com/nextauthjs/next-auth/discussions/9785 @@ -26,7 +27,7 @@ export const sendVerificationRequest = async ({ identifier: email, url }: { iden }) } -export const getAuthConfig = async (): Promise => { +export const getAuthConfig = async (req: any): Promise => { const { MongoDBAdapter } = await import("@auth/mongodb-adapter"); @@ -52,7 +53,38 @@ export const getAuthConfig = async (): Promise => { updateAge: 24 * 60 * 60 // 24 hours }, callbacks: { - async session({ session, token, user, newSession }) { + /** + * Custom sign-in callback for NextAuth + * + * NextAuth doesn't natively differentiate between signup and signin operations. + * To handle this, we: + * 1. Pass an 'operation' parameter from the client side signin call + * 2. Check if the user's email is verified when they attempt to sign in + * 3. If unverified and operation='signin', return a URL string that stops the signin flow + * but appears identical to a successful flow (for security) + * + * The URL return value is treated by NextAuth as a redirect, which prevents the + * signin flow from completing while maintaining consistent behavior whether the + * email is verified or not. This avoids leaking information about email verification + * status through different error flows. + * + * @param {Object} params - NextAuth signIn callback parameters + * @param {User} params.user - The user attempting to sign in + * @returns {Promise} + * - Returns true to allow sign in + * - Returns URL string to gracefully stop signin while appearing successful + */ + async signIn({ user }) { + + if (!(user as AdapterUser).emailVerified && req?.query?.operation == 'signin') { + + return config.SITE_URL + '/api/auth/verify-request?provider=http-email&type=email' + } + + return true; + }, + + async session({ session, token, user, newSession, }) { const customData = await client.db('customData').collection('users').findOne({ userId: user.id }); diff --git a/site/gatsby-site/src/contexts/UserContext.tsx b/site/gatsby-site/src/contexts/UserContext.tsx index 3d800bc8a3..11cbc453e1 100644 --- a/site/gatsby-site/src/contexts/UserContext.tsx +++ b/site/gatsby-site/src/contexts/UserContext.tsx @@ -104,13 +104,13 @@ export const UserContextProvider: React.FC = ({ childr }, logIn: async (email: string, callbackUrl: string) => { - const result = await signIn('http-email', { email, redirect: false, callbackUrl }); + const result = await signIn('http-email', { email, redirect: false, callbackUrl }, { operation: 'signin' }); return result; }, signUp: async (email: string, callbackUrl: string) => { - const result = await signIn('http-email', { email, redirect: false, callbackUrl }); + const result = await signIn('http-email', { email, redirect: false, callbackUrl }, { operation: 'signup' }); return result; }, From 18b9f24897d43753d0d8edac6bdc7b1f91fefd8b Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Thu, 12 Dec 2024 19:53:01 -0300 Subject: [PATCH 73/99] Use different templates for sign ups and sign ins --- site/gatsby-site/nextauth.config.ts | 24 +- .../server/emails/templates/Login.ts | 32 +++ .../server/emails/templates/MagicLink.ts | 234 ------------------ .../server/emails/templates/Signup.ts | 32 +++ .../server/emails/templates/index.ts | 18 +- 5 files changed, 97 insertions(+), 243 deletions(-) create mode 100644 site/gatsby-site/server/emails/templates/Login.ts delete mode 100644 site/gatsby-site/server/emails/templates/MagicLink.ts create mode 100644 site/gatsby-site/server/emails/templates/Signup.ts diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts index 2ab513b921..fd22343436 100644 --- a/site/gatsby-site/nextauth.config.ts +++ b/site/gatsby-site/nextauth.config.ts @@ -39,7 +39,29 @@ export const getAuthConfig = async (req: any): Promise => { name: "Email", type: "email", maxAge: 60 * 60 * 24, // Email link will expire in 24 hours - sendVerificationRequest, + async sendVerificationRequest({ identifier: email, url }: { identifier: string, url: string }) { + + const user = await client.db('auth').collection('users').findOne({ email }); + + if (user) { + + await sendEmail({ + recipients: [{ email }], + subject: 'Login link', + templateId: 'Login', + dynamicData: { magicLink: url }, + }) + } + else { + + await sendEmail({ + recipients: [{ email }], + subject: 'Signup link', + templateId: 'Signup', + dynamicData: { magicLink: url }, + }) + } + } } ], theme: { diff --git a/site/gatsby-site/server/emails/templates/Login.ts b/site/gatsby-site/server/emails/templates/Login.ts new file mode 100644 index 0000000000..4b7d410e25 --- /dev/null +++ b/site/gatsby-site/server/emails/templates/Login.ts @@ -0,0 +1,32 @@ +import { + insertContent, +} from './shared' + +const getEmailTemplate = () => { + + return insertContent( + ` +
+

+ Click the link below to sign in to the AI Incident Database: +

+ +

+ {{magicLink}} +

+ +

+ This link will expire in 24 hours. If you did not request this email, please ignore it. +

+
+ +

+ Sincerely,
+ Responsible AI Collaborative +

+ `, + { title: 'Signup' } + ); +}; + +export default getEmailTemplate(); diff --git a/site/gatsby-site/server/emails/templates/MagicLink.ts b/site/gatsby-site/server/emails/templates/MagicLink.ts deleted file mode 100644 index 399f97ea6f..0000000000 --- a/site/gatsby-site/server/emails/templates/MagicLink.ts +++ /dev/null @@ -1,234 +0,0 @@ -export default ` - - - - - - - - - - - - - - - - -
-
- - - - -
- - - - -
- - - - -
- - - - - -
- - - - - - -
-
-
AI INCIDENT - DATABASE
-
-
-
- - - - - - -
-
- Click here to log in:
- {{magicLink}} -
-
-
- -
-
-
-
-
- - -`; \ No newline at end of file diff --git a/site/gatsby-site/server/emails/templates/Signup.ts b/site/gatsby-site/server/emails/templates/Signup.ts new file mode 100644 index 0000000000..bd4575f1d3 --- /dev/null +++ b/site/gatsby-site/server/emails/templates/Signup.ts @@ -0,0 +1,32 @@ +import { + insertContent, +} from './shared' + +const getEmailTemplate = () => { + + return insertContent( + ` +
+

+ Click the link below to verify your email address for the AI Incident Database: +

+ +

+ {{magicLink}} +

+ +

+ This link will expire in 24 hours. If you did not request this email, please ignore it. +

+
+ +

+ Sincerely,
+ Responsible AI Collaborative +

+ `, + { title: 'Signup' } + ); +}; + +export default getEmailTemplate(); diff --git a/site/gatsby-site/server/emails/templates/index.ts b/site/gatsby-site/server/emails/templates/index.ts index 9d3e7cf032..b04f7a9c74 100644 --- a/site/gatsby-site/server/emails/templates/index.ts +++ b/site/gatsby-site/server/emails/templates/index.ts @@ -1,19 +1,21 @@ import EntityIncidentUpdated from './EntityIncidentUpdated'; import IncidentUpdate from './IncidentUpdate'; -import MagicLink from './MagicLink'; +import Login from './Login'; +import Signup from './Signup'; import NewEntityIncident from './NewEntityIncident'; import NewIncident from './NewIncident'; import NewReportAddedToAnIncident from './NewReportAddedToAnIncident'; import SubmissionApproved from './SubmissionApproved'; const templates: Record = { - EntityIncidentUpdated: EntityIncidentUpdated, - IncidentUpdate: IncidentUpdate, - NewEntityIncident: NewEntityIncident, - NewIncident: NewIncident, - NewReportAddedToAnIncident: NewReportAddedToAnIncident, - SubmissionApproved: SubmissionApproved, - MagicLink: MagicLink, + EntityIncidentUpdated, + IncidentUpdate, + NewEntityIncident, + NewIncident, + NewReportAddedToAnIncident, + SubmissionApproved, + Login, + Signup, }; export default templates; \ No newline at end of file From 19ef9df067023fa7458aabb4b0a0c2f90d8c254f Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 13 Dec 2024 17:58:10 -0300 Subject: [PATCH 74/99] Use local copy of package to fix issues with Jest not supporting ESM natively --- site/gatsby-site/nextauth.config.ts | 3 +- site/gatsby-site/server/MongoDBAdapter.ts | 251 ++++++++++++++++++++++ 2 files changed, 252 insertions(+), 2 deletions(-) create mode 100644 site/gatsby-site/server/MongoDBAdapter.ts diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts index fd22343436..a23922e166 100644 --- a/site/gatsby-site/nextauth.config.ts +++ b/site/gatsby-site/nextauth.config.ts @@ -3,6 +3,7 @@ import { NextAuthOptions } from "next-auth" import config from './server/config' import { sendEmail } from "./server/emails" import { AdapterUser } from "next-auth/adapters" +import { MongoDBAdapter } from "./server/MongoDBAdapter" //TODO: add this to the workflow file, this needs to be set via env variable // SEE: https://github.com/nextauthjs/next-auth/discussions/9785 @@ -29,8 +30,6 @@ export const sendVerificationRequest = async ({ identifier: email, url }: { iden export const getAuthConfig = async (req: any): Promise => { - const { MongoDBAdapter } = await import("@auth/mongodb-adapter"); - return { providers: [ // @ts-ignore diff --git a/site/gatsby-site/server/MongoDBAdapter.ts b/site/gatsby-site/server/MongoDBAdapter.ts new file mode 100644 index 0000000000..0e7d8286a5 --- /dev/null +++ b/site/gatsby-site/server/MongoDBAdapter.ts @@ -0,0 +1,251 @@ +/** + *
+ *

Official MongoDB adapter for Auth.js / NextAuth.js.

+ * + * + * + *
+ * + * ## Installation + * + * ```bash npm2yarn + * npm install @auth/mongodb-adapter mongodb + * ``` + * + * @module @auth/mongodb-adapter + */ +import { ObjectId } from "mongodb" + +import type { + Adapter, + AdapterUser, + AdapterAccount, + AdapterSession, + VerificationToken, +} from "@auth/core/adapters" +import type { MongoClient } from "mongodb" + +/** + * This adapter uses https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-2.html#using-declarations-and-explicit-resource-management. + * This feature is very new and requires runtime polyfills for `Symbol.asyncDispose` in order to work properly in all environments. + * It is also required to set in the `tsconfig.json` file the compilation target to `es2022` or below and configure the `lib` option to include `esnext` or `esnext.disposable`. + * + * You can find more information about this feature and the polyfills in the link above. + */ +// @ts-expect-error read only property is not assignable +Symbol.asyncDispose ??= Symbol("Symbol.asyncDispose") + +/** This is the interface of the MongoDB adapter options. */ +export interface MongoDBAdapterOptions { + /** + * The name of the {@link https://www.mongodb.com/docs/manual/core/databases-and-collections/#collections MongoDB collections}. + */ + collections?: { + Users?: string + Accounts?: string + Sessions?: string + VerificationTokens?: string + } + /** + * The name you want to give to the MongoDB database + */ + databaseName?: string + /** + * Callback function for managing the closing of the MongoDB client. + * This could be useful when `client` is provided as a function returning MongoClient | Promise. + * It allows for more customized management of database connections, + * addressing persistence, container reuse, and connection closure issues. + */ + onClose?: (client: MongoClient) => Promise +} + +export const defaultCollections: Required< + Required["collections"] +> = { + Users: "users", + Accounts: "accounts", + Sessions: "sessions", + VerificationTokens: "verification_tokens", +} + +export const format = { + /** Takes a MongoDB object and returns a plain old JavaScript object */ + from>(object: Record): T { + const newObject: Record = {} + for (const key in object) { + const value = object[key] + if (key === "_id") { + newObject.id = value.toHexString() + } else if (key === "userId") { + newObject[key] = value.toHexString() + } else { + newObject[key] = value + } + } + return newObject as T + }, + /** Takes a plain old JavaScript object and turns it into a MongoDB object */ + to>(object: Record) { + const newObject: Record = { + _id: _id(object.id), + } + for (const key in object) { + const value = object[key] + if (key === "userId") newObject[key] = _id(value) + else if (key === "id") continue + else newObject[key] = value + } + return newObject as T & { _id: ObjectId } + }, +} + +/** @internal */ +export function _id(hex?: string) { + if (hex?.length !== 24) return new ObjectId() + return new ObjectId(hex) +} + +export function MongoDBAdapter( + /** + * The MongoDB client. + * + * The MongoDB team recommends providing a non-connected `MongoClient` instance to avoid unhandled promise rejections if the client fails to connect. + * + * Alternatively, you can also pass: + * - A promise that resolves to a connected `MongoClient` (not recommended). + * - A function, to handle more complex and custom connection strategies. + * + * Using a function that returns `MongoClient | Promise`, combined with `options.onClose`, can be useful when you want a more advanced and customized connection strategy to address challenges related to persistence, container reuse, and connection closure. + */ + client: + | MongoClient + | Promise + | (() => MongoClient | Promise), + options: MongoDBAdapterOptions = {} +): Adapter { + const { collections } = options + const { from, to } = format + + const getDb = async () => { + const _client: MongoClient = await (typeof client === "function" + ? client() + : client) + const _db = _client.db(options.databaseName) + const c = { ...defaultCollections, ...collections } + return { + U: _db.collection(c.Users), + A: _db.collection(c.Accounts), + S: _db.collection(c.Sessions), + V: _db.collection(c?.VerificationTokens), + [Symbol.asyncDispose]: async () => { + await options.onClose?.(_client) + }, + } + } + + return { + async createUser(data) { + const user = to(data) + await using db = await getDb() + await db.U.insertOne(user) + return from(user) + }, + async getUser(id) { + await using db = await getDb() + const user = await db.U.findOne({ _id: _id(id) }) + if (!user) return null + return from(user) + }, + async getUserByEmail(email) { + await using db = await getDb() + const user = await db.U.findOne({ email }) + if (!user) return null + return from(user) + }, + async getUserByAccount(provider_providerAccountId) { + await using db = await getDb() + const account = await db.A.findOne(provider_providerAccountId) + if (!account) return null + const user = await db.U.findOne({ _id: new ObjectId(account.userId) }) + if (!user) return null + return from(user) + }, + async updateUser(data) { + const { _id, ...user } = to(data) + await using db = await getDb() + const result = await db.U.findOneAndUpdate( + { _id }, + { $set: user }, + { returnDocument: "after" } + ) + + return from(result!) + }, + async deleteUser(id) { + const userId = _id(id) + await using db = await getDb() + await Promise.all([ + db.A.deleteMany({ userId: userId as any }), + db.S.deleteMany({ userId: userId as any }), + db.U.deleteOne({ _id: userId }), + ]) + }, + linkAccount: async (data) => { + const account = to(data) + await using db = await getDb() + await db.A.insertOne(account) + return account + }, + async unlinkAccount(provider_providerAccountId) { + await using db = await getDb() + const account = await db.A.findOneAndDelete(provider_providerAccountId) + return from(account!) + }, + async getSessionAndUser(sessionToken) { + await using db = await getDb() + const session = await db.S.findOne({ sessionToken }) + if (!session) return null + const user = await db.U.findOne({ _id: new ObjectId(session.userId) }) + if (!user) return null + return { + user: from(user), + session: from(session), + } + }, + async createSession(data) { + const session = to(data) + await using db = await getDb() + await db.S.insertOne(session) + return from(session) + }, + async updateSession(data) { + const { _id, ...session } = to(data) + await using db = await getDb() + const updatedSession = await db.S.findOneAndUpdate( + { sessionToken: session.sessionToken }, + { $set: session }, + { returnDocument: "after" } + ) + return from(updatedSession!) + }, + async deleteSession(sessionToken) { + await using db = await getDb() + const session = await db.S.findOneAndDelete({ + sessionToken, + }) + return from(session!) + }, + async createVerificationToken(data) { + await using db = await getDb() + await db.V.insertOne(to(data)) + return data + }, + async useVerificationToken(identifier_token) { + await using db = await getDb() + const verificationToken = await db.V.findOneAndDelete(identifier_token) + if (!verificationToken) return null + const { _id, ...rest } = verificationToken + return rest + }, + } +} From 740aa01f2e5c69babcda9ea04212cc8df862eb35 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 13 Dec 2024 17:59:15 -0300 Subject: [PATCH 75/99] Move functions to utils file --- site/gatsby-site/netlify/functions/auth.ts | 92 ++------------------- site/gatsby-site/src/utils/serverless.ts | 95 +++++++++++++++++++--- 2 files changed, 91 insertions(+), 96 deletions(-) diff --git a/site/gatsby-site/netlify/functions/auth.ts b/site/gatsby-site/netlify/functions/auth.ts index 261ec6b766..5f1be535cd 100644 --- a/site/gatsby-site/netlify/functions/auth.ts +++ b/site/gatsby-site/netlify/functions/auth.ts @@ -1,83 +1,9 @@ import NextAuth from "next-auth"; import { getAuthConfig } from "../../nextauth.config"; -import { createResponse } from '../../src/utils/serverless' +import { createResponse, recreateRequest } from '../../src/utils/serverless' +import { Handler } from '@netlify/functions' -const parseBody = (event) => { - - const contentType = event.headers['content-type'] || event.headers['Content-Type']; - const { body } = event; - - if (!body) return undefined; - - if (contentType?.includes('application/json')) { - try { - return JSON.parse(body); - } catch (e) { - console.error('Failed to parse JSON body:', e); - return undefined; - } - } - - if (contentType?.includes('application/x-www-form-urlencoded')) { - const params = new URLSearchParams(body); - const formData: Record = {}; - params.forEach((value, key) => { - formData[key] = value; - }); - return formData; - } - - return undefined; -}; - -const parseCookies = (cookieHeader: string) => { - const cookies: Record = {}; - if (!cookieHeader) return cookies; - - cookieHeader.split(';').forEach(cookie => { - const parts = cookie.trim().split('='); - if (parts.length >= 2) { - const key = parts[0]; - const value = parts.slice(1).join('='); - cookies[key] = decodeURIComponent(value); - } - }); - - return cookies; -}; - -const parseHeaders = (event: any) => { - - return { - ...event.headers, - host: event.headers.host || new URL(process.env.NEXTAUTH_URL).host, - } -} - -const parseQuery = (event: any) => { - - const path = event.path.replace('/api/auth/', '') - const nextAuthArray = path.split('/').filter(Boolean); - - return { - ...event.queryStringParameters, - nextauth: nextAuthArray, - action: nextAuthArray[nextAuthArray.length - 1], - providerId: nextAuthArray[1] - } -} - -const recreateRequest = (event) => { - return { - method: event.httpMethod, - query: parseQuery(event), - cookies: parseCookies(event.headers.cookie), - headers: parseHeaders(event), - body: parseBody(event), - } -} - -export const handler = async (event, context) => { +export const handler: Handler = async (event) => { const req = recreateRequest(event); const res = createResponse(); @@ -86,7 +12,8 @@ export const handler = async (event, context) => { const authConfig = await getAuthConfig(req); - await NextAuth(req as any, res as any, authConfig) + // @ts-ignore + await NextAuth(req, res, authConfig) const response = res.getResponse(); @@ -94,8 +21,8 @@ export const handler = async (event, context) => { return { ...response, - statusCode: response.statusCode || 200, - body: response.body || '{}', + statusCode: response.statusCode, + body: response.body, headers: { ...responseHeaders, 'Cache-Control': 'no-store, max-age=0' @@ -103,14 +30,11 @@ export const handler = async (event, context) => { } } catch (error) { - console.error('NextAuth Error:', error) - console.error('Request details:', req); - return { statusCode: 500, body: JSON.stringify({ error: 'Internal Server Error', - details: error.message, + details: (error as Error).message, }) } } diff --git a/site/gatsby-site/src/utils/serverless.ts b/site/gatsby-site/src/utils/serverless.ts index 558102f012..bfc62ab1df 100644 --- a/site/gatsby-site/src/utils/serverless.ts +++ b/site/gatsby-site/src/utils/serverless.ts @@ -1,13 +1,16 @@ +import config from "../../server/config" +import { HandlerEvent } from '@netlify/functions' + export const createResponse = () => { let responseHeaders: Record = { 'Content-Type': 'application/json' } let response: Record = {} const res = { - getHeader: (name: any) => { + getHeader: (name: string) => { return responseHeaders[name] }, - setHeader: (name: any, value: any) => { + setHeader: (name: string, value: string) => { if (name.toLowerCase() === 'set-cookie') { response.multiValueHeaders = { ...response.multiValueHeaders, @@ -18,23 +21,16 @@ export const createResponse = () => { response.headers = responseHeaders } }, - status: (statusCode: any) => { + status: (statusCode: number) => { response.statusCode = statusCode return res }, - send: (body: any) => { + send: (body: string | Record) => { response.body = typeof body === 'string' ? body : JSON.stringify(body) }, - json: (json: any) => { + json: (json: Record) => { response.body = JSON.stringify(json) }, - redirect: (statusOrUrl: any, url: any) => { - const statusCode = typeof statusOrUrl === 'number' ? statusOrUrl : 302 - const redirectUrl = typeof statusOrUrl === 'string' ? statusOrUrl : url - response.statusCode = statusCode - responseHeaders.Location = redirectUrl - response.headers = responseHeaders - }, end: () => { }, getResponse: () => response, getResponseHeaders: () => responseHeaders, @@ -42,3 +38,78 @@ export const createResponse = () => { return res } + +const parseBody = (event: HandlerEvent) => { + + const contentType = event.headers['content-type'] || event.headers['Content-Type']; + const { body } = event; + + if (!body) return undefined; + + if (contentType?.includes('application/json')) { + try { + return JSON.parse(body); + } catch (e) { + console.error('Failed to parse JSON body:', e); + return undefined; + } + } + + if (contentType?.includes('application/x-www-form-urlencoded')) { + const params = new URLSearchParams(body); + const formData: Record = {}; + params.forEach((value, key) => { + formData[key] = value; + }); + return formData; + } + + return undefined; +}; + +const parseCookies = (cookieHeader: string) => { + const cookies: Record = {}; + if (!cookieHeader) return cookies; + + cookieHeader.split(';').forEach(cookie => { + const parts = cookie.trim().split('='); + if (parts.length >= 2) { + const key = parts[0]; + const value = parts.slice(1).join('='); + cookies[key] = decodeURIComponent(value); + } + }); + + return cookies; +}; + +const parseHeaders = (event: any) => { + + return { + ...event.headers, + host: event.headers.host || new URL(config.NEXTAUTH_URL).host, + } +} + +const parseQuery = (event: any) => { + + const path = event.path.replace('/api/auth/', '') + const nextAuthArray = path.split('/').filter(Boolean); + + return { + ...event.queryStringParameters, + nextauth: nextAuthArray, + action: nextAuthArray[nextAuthArray.length - 1], + providerId: nextAuthArray[1] + } +} + +export const recreateRequest = (event: HandlerEvent) => { + return { + method: event.httpMethod, + query: parseQuery(event), + cookies: parseCookies(event.headers.cookie ?? ''), + headers: parseHeaders(event), + body: parseBody(event), + } +} \ No newline at end of file From 65dc40838048f13d0f6f89c8ab42eb837566eded Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 13 Dec 2024 18:00:08 -0300 Subject: [PATCH 76/99] Remove unused function --- site/gatsby-site/nextauth.config.ts | 10 ---------- 1 file changed, 10 deletions(-) diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts index a23922e166..1a52e017a3 100644 --- a/site/gatsby-site/nextauth.config.ts +++ b/site/gatsby-site/nextauth.config.ts @@ -18,16 +18,6 @@ const client = new MongoClient(config.API_MONGODB_CONNECTION_STRING!, { }, }) -export const sendVerificationRequest = async ({ identifier: email, url }: { identifier: string, url: string }) => { - - await sendEmail({ - recipients: [{ email }], - subject: 'Login link', - templateId: 'MagicLink', - dynamicData: { magicLink: url }, - }) -} - export const getAuthConfig = async (req: any): Promise => { return { From 2e1bb7b9b6af17e8ee5c27b81f0bc78bd35954ff Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 13 Dec 2024 18:00:40 -0300 Subject: [PATCH 77/99] Add tests to signin auth methods --- .../server/tests/next-auth.spec.ts | 116 ++++++++++++++++++ site/gatsby-site/server/tests/utils.ts | 8 ++ 2 files changed, 124 insertions(+) create mode 100644 site/gatsby-site/server/tests/next-auth.spec.ts diff --git a/site/gatsby-site/server/tests/next-auth.spec.ts b/site/gatsby-site/server/tests/next-auth.spec.ts new file mode 100644 index 0000000000..61daf45f45 --- /dev/null +++ b/site/gatsby-site/server/tests/next-auth.spec.ts @@ -0,0 +1,116 @@ +import { handler } from '../../netlify/functions/auth'; +import { HandlerEvent, HandlerContext } from '@netlify/functions' +import * as emails from '../emails'; +import { getCollection, seedFixture } from './utils'; +import config from '../config'; + +function mockAuthEvent(operation: string, email: string, callbackUrl: string): Partial { + + const encodedEmail = encodeURIComponent(email); + const encodedCallbackUrl = encodeURIComponent(callbackUrl); + + return { + path: "/api/auth/signin/http-email", + httpMethod: "POST", + queryStringParameters: { + operation, + }, + headers: { + cookie: `next-auth.callback-url=${encodedCallbackUrl}; next-auth.csrf-token=3fc5b5ba3bb4457090ea32b335e69294637dca5a9473dcc669a4ed00cdadf199%7C1ecd6e1064b23beb6a1e215165a586be0a08bed2d588cc0a440ab3e43c4d2e87`, + connection: "close", + origin: "http://localhost:8000", + "content-length": "181", + "content-type": "application/x-www-form-urlencoded", + referer: "http://localhost:8000/signup/", + "accept-encoding": "gzip, deflate, br, zstd", + "accept-language": "en-US,en;q=0.5", + accept: "*/*", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:134.0) Gecko/20100101 Firefox/134.0", + host: "localhost:8000", + }, + body: `email=${encodedEmail}&redirect=false&callbackUrl=${callbackUrl}&csrfToken=3fc5b5ba3bb4457090ea32b335e69294637dca5a9473dcc669a4ed00cdadf199&json=true`, + } +} + +describe('Auth', () => { + + test('Should not send an magic link email to unregistered users on sign in ', async () => { + + await seedFixture({ + auth: { + users: [], + }, + }); + + + const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); + + const event = mockAuthEvent('signin', 'test.user@incidentdatabase.ai', '/'); + + const response = await handler(event as HandlerEvent, {} as HandlerContext); + + expect(sendEmailMock).not.toHaveBeenCalled(); + expect(response).toMatchObject({ + statusCode: 200, + headers: { + 'Cache-Control': 'no-store, max-age=0', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ url: config.SITE_URL + '/api/auth/verify-request?provider=http-email&type=email' }), + }); + + const users = await getCollection('auth', 'users').find({}).toArray(); + + expect(users).toHaveLength(0); + }); + + test('Should send an magic link email to registered users on sign in ', async () => { + + const email = "test.user@incidentdatabase.ai"; + + await seedFixture({ + auth: { + users: [ + { email, emailVerified: new Date().toString() } + ], + }, + }); + + const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); + const event = mockAuthEvent('signin', email, '/'); + + const response = await handler(event as HandlerEvent, {} as HandlerContext); + + expect(sendEmailMock).toHaveBeenCalledWith({ + dynamicData: { + magicLink: expect.stringMatching(/^http:\/\/localhost:8000\/api\/auth\/callback\/http-email\?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2F&token=.+&email=test.user%40incidentdatabase.ai$/), + }, + recipients: [ + { + email: "test.user@incidentdatabase.ai", + }, + ], + subject: "Login link", + templateId: "Login", + + }); + + expect(response).toMatchObject({ + statusCode: 200, + headers: { + 'Cache-Control': 'no-store, max-age=0', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ url: config.SITE_URL + '/api/auth/verify-request?provider=http-email&type=email' }), + }); + + const users = await getCollection('auth', 'users').find({}).toArray(); + + expect(users).toMatchObject([ + { + email: "test.user@incidentdatabase.ai", + emailVerified: expect.any(String), + } + ]); + }); +}); diff --git a/site/gatsby-site/server/tests/utils.ts b/site/gatsby-site/server/tests/utils.ts index 8c4b6f0736..f383271565 100644 --- a/site/gatsby-site/server/tests/utils.ts +++ b/site/gatsby-site/server/tests/utils.ts @@ -164,4 +164,12 @@ export const mockSession = (userId: string) => { return user ? { id: user.userId, roles: user.roles } : null; }) +} + +export const getCollection = (databaseName: string, collectionName: string) => { + + const client = new MongoClient(process.env.API_MONGODB_CONNECTION_STRING!); + const collection = client.db(databaseName).collection(collectionName); + + return collection; } \ No newline at end of file From b7bd66d9690b23225e6a99bf6c23a4575b088f3e Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 13 Dec 2024 18:01:09 -0300 Subject: [PATCH 78/99] Fix small issues --- site/gatsby-site/server/config.ts | 2 -- site/gatsby-site/server/context.ts | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/site/gatsby-site/server/config.ts b/site/gatsby-site/server/config.ts index 11d6d0d905..ef6479ad95 100644 --- a/site/gatsby-site/server/config.ts +++ b/site/gatsby-site/server/config.ts @@ -36,8 +36,6 @@ Object.keys(config).forEach((key) => { if (config[key as keyof Config] === undefined || config[key as keyof Config] === '') { throw new Error(`Config property ${key} is undefined`); } - - console.log(`Config property ${key}, value: ${config[key as keyof Config]}`); }); export default config; \ No newline at end of file diff --git a/site/gatsby-site/server/context.ts b/site/gatsby-site/server/context.ts index be85ab4ccf..165a1d1577 100644 --- a/site/gatsby-site/server/context.ts +++ b/site/gatsby-site/server/context.ts @@ -7,7 +7,7 @@ import { createResponse } from '../src/utils/serverless' export const verifyToken = async (req: IncomingMessage) => { - const authConfig = await getAuthConfig(); + const authConfig = await getAuthConfig(req); const res = createResponse(); const session = await getServerSession(req as any, res as any, authConfig as any); From f1da9ce1bb0dbcb83e2800753b81213626935dab Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 13 Dec 2024 18:02:01 -0300 Subject: [PATCH 79/99] Remove unused packages --- site/gatsby-site/package-lock.json | 750 +++++++++++++++++++++-------- site/gatsby-site/package.json | 3 +- 2 files changed, 545 insertions(+), 208 deletions(-) diff --git a/site/gatsby-site/package-lock.json b/site/gatsby-site/package-lock.json index d444a1de50..36f76121a4 100644 --- a/site/gatsby-site/package-lock.json +++ b/site/gatsby-site/package-lock.json @@ -12,7 +12,6 @@ "@apollo/client": "^3.7.8", "@apollo/server": "^4.10.2", "@as-integrations/aws-lambda": "^3.1.0", - "@auth/mongodb-adapter": "^3.7.3", "@aws-sdk/client-s3": "^3.633.0", "@billboard.js/react": "^1.0.1", "@bytemd/react": "^1.15.0", @@ -816,61 +815,6 @@ "optional": true, "peer": true }, - "node_modules/@auth/mongodb-adapter": { - "version": "3.7.4", - "resolved": "https://registry.npmjs.org/@auth/mongodb-adapter/-/mongodb-adapter-3.7.4.tgz", - "integrity": "sha512-EZ7aSNnPWNVEtYRCmUbHtb3UxGNB52EJpVq0NDFdbxHypeqdUaz8ClHoKO/o99lQUT1BWCli819dlldWUKyEgw==", - "dependencies": { - "@auth/core": "0.37.4" - }, - "peerDependencies": { - "mongodb": "^6" - } - }, - "node_modules/@auth/mongodb-adapter/node_modules/@auth/core": { - "version": "0.37.4", - "resolved": "https://registry.npmjs.org/@auth/core/-/core-0.37.4.tgz", - "integrity": "sha512-HOXJwXWXQRhbBDHlMU0K/6FT1v+wjtzdKhsNg0ZN7/gne6XPsIrjZ4daMcFnbq0Z/vsAbYBinQhhua0d77v7qw==", - "dependencies": { - "@panva/hkdf": "^1.2.1", - "jose": "^5.9.6", - "oauth4webapi": "^3.1.1", - "preact": "10.24.3", - "preact-render-to-string": "6.5.11" - }, - "peerDependencies": { - "@simplewebauthn/browser": "^9.0.1", - "@simplewebauthn/server": "^9.0.2", - "nodemailer": "^6.8.0" - }, - "peerDependenciesMeta": { - "@simplewebauthn/browser": { - "optional": true - }, - "@simplewebauthn/server": { - "optional": true - }, - "nodemailer": { - "optional": true - } - } - }, - "node_modules/@auth/mongodb-adapter/node_modules/oauth4webapi": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/oauth4webapi/-/oauth4webapi-3.1.3.tgz", - "integrity": "sha512-dik5wEMdFL5p3JlijYvM7wMNCgaPhblLIDCZtdXcaZp5wgu5Iwmsu7lMzgFhIDTi5d0BJo03LVoOoFQvXMeOeQ==", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/@auth/mongodb-adapter/node_modules/preact-render-to-string": { - "version": "6.5.11", - "resolved": "https://registry.npmjs.org/preact-render-to-string/-/preact-render-to-string-6.5.11.tgz", - "integrity": "sha512-ubnauqoGczeGISiOh6RjX0/cdaF8v/oDXIjO85XALCQjwQP+SB4RDXXtvZ6yTYSjG+PC1QRP2AhPgCEsM2EvUw==", - "peerDependencies": { - "preact": ">=10" - } - }, "node_modules/@aws-crypto/crc32": { "version": "3.0.0", "license": "Apache-2.0", @@ -6042,8 +5986,7 @@ }, "node_modules/@cloudinary/html/node_modules/typescript": { "version": "4.9.5", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", - "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -6067,9 +6010,8 @@ }, "node_modules/@colors/colors": { "version": "1.5.0", - "resolved": "https://registry.npmjs.org/@colors/colors/-/colors-1.5.0.tgz", - "integrity": "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=0.1.90" } @@ -6094,6 +6036,16 @@ "@jridgewell/sourcemap-codec": "^1.4.10" } }, + "node_modules/@emnapi/runtime": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", + "integrity": "sha512-kEBmG8KyqtxJZv+ygbEim+KCGtIq1fC22Ms3S4ziXmYKm8uyoLX0MHONVKwp+9opg390VaKRNt4a7A9NwmpNhw==", + "optional": true, + "peer": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@eslint-community/regexpp": { "version": "4.6.1", "license": "MIT", @@ -8779,6 +8731,28 @@ "@img/sharp-libvips-darwin-arm64": "1.0.4" } }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, "node_modules/@img/sharp-libvips-darwin-arm64": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", @@ -8795,6 +8769,307 @@ "url": "https://opencollective.com/libvips" } }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "optional": true, + "peer": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, "node_modules/@imgix/gatsby": { "version": "2.1.3", "license": "BSD-2-Clause", @@ -10427,15 +10702,15 @@ "license": "MIT" }, "node_modules/@next/env": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/env/-/env-15.0.3.tgz", - "integrity": "sha512-t9Xy32pjNOvVn2AS+Utt6VmyrshbpfUMhIjFO60gI58deSo/KgLOp31XZ4O+kY/Is8WAGYwA5gR7kOb1eORDBA==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.0.tgz", + "integrity": "sha512-UcCO481cROsqJuszPPXJnb7GGuLq617ve4xuAyyNG4VSSocJNtMU5Fsx+Lp6mlN8c7W58aZLc5y6D/2xNmaK+w==", "peer": true }, "node_modules/@next/swc-darwin-arm64": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.0.3.tgz", - "integrity": "sha512-s3Q/NOorCsLYdCKvQlWU+a+GeAd3C8Rb3L1YnetsgwXzhc3UTWrtQpB/3eCjFOdGUj5QmXfRak12uocd1ZiiQw==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-arm64/-/swc-darwin-arm64-15.1.0.tgz", + "integrity": "sha512-ZU8d7xxpX14uIaFC3nsr4L++5ZS/AkWDm1PzPO6gD9xWhFkOj2hzSbSIxoncsnlJXB1CbLOfGVN4Zk9tg83PUw==", "cpu": [ "arm64" ], @@ -10448,6 +10723,118 @@ "node": ">= 10" } }, + "node_modules/@next/swc-darwin-x64": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-darwin-x64/-/swc-darwin-x64-15.1.0.tgz", + "integrity": "sha512-DQ3RiUoW2XC9FcSM4ffpfndq1EsLV0fj0/UY33i7eklW5akPUCo6OX2qkcLXZ3jyPdo4sf2flwAED3AAq3Om2Q==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "darwin" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-gnu": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-gnu/-/swc-linux-arm64-gnu-15.1.0.tgz", + "integrity": "sha512-M+vhTovRS2F//LMx9KtxbkWk627l5Q7AqXWWWrfIzNIaUFiz2/NkOFkxCFyNyGACi5YbA8aekzCLtbDyfF/v5Q==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-arm64-musl": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-arm64-musl/-/swc-linux-arm64-musl-15.1.0.tgz", + "integrity": "sha512-Qn6vOuwaTCx3pNwygpSGtdIu0TfS1KiaYLYXLH5zq1scoTXdwYfdZtwvJTpB1WrLgiQE2Ne2kt8MZok3HlFqmg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-gnu": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-gnu/-/swc-linux-x64-gnu-15.1.0.tgz", + "integrity": "sha512-yeNh9ofMqzOZ5yTOk+2rwncBzucc6a1lyqtg8xZv0rH5znyjxHOWsoUtSq4cUTeeBIiXXX51QOOe+VoCjdXJRw==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-linux-x64-musl": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-linux-x64-musl/-/swc-linux-x64-musl-15.1.0.tgz", + "integrity": "sha512-t9IfNkHQs/uKgPoyEtU912MG6a1j7Had37cSUyLTKx9MnUpjj+ZDKw9OyqTI9OwIIv0wmkr1pkZy+3T5pxhJPg==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "linux" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-arm64-msvc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-arm64-msvc/-/swc-win32-arm64-msvc-15.1.0.tgz", + "integrity": "sha512-WEAoHyG14t5sTavZa1c6BnOIEukll9iqFRTavqRVPfYmfegOAd5MaZfXgOGG6kGo1RduyGdTHD4+YZQSdsNZXg==", + "cpu": [ + "arm64" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, + "node_modules/@next/swc-win32-x64-msvc": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/@next/swc-win32-x64-msvc/-/swc-win32-x64-msvc-15.1.0.tgz", + "integrity": "sha512-J1YdKuJv9xcixzXR24Dv+4SaDKc2jj31IVUEMdO5xJivMTXuE6MAdIi4qPjSymHuFG8O5wbfWKnhJUcHHpj5CA==", + "cpu": [ + "x64" + ], + "optional": true, + "os": [ + "win32" + ], + "peer": true, + "engines": { + "node": ">= 10" + } + }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", "license": "MIT", @@ -11604,9 +11991,8 @@ }, "node_modules/@playwright/test": { "version": "1.47.2", - "resolved": "https://registry.npmjs.org/@playwright/test/-/test-1.47.2.tgz", - "integrity": "sha512-jTXRsoSPONAs8Za9QEQdyjFn+0ZQFjCiIztAIF6bi1HqhBzG9Ma7g1WotyiGqFSBRZjIEqMdT8RUlbk1QVhzCQ==", "devOptional": true, + "license": "Apache-2.0", "dependencies": { "playwright": "1.47.2" }, @@ -14098,9 +14484,8 @@ }, "node_modules/@types/yauzl": { "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", "dev": true, + "license": "MIT", "optional": true, "dependencies": { "@types/node": "*" @@ -14883,9 +15268,8 @@ }, "node_modules/archy": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/archy/-/archy-1.0.0.tgz", - "integrity": "sha512-Xg+9RwCg/0p32teKdGMPTPnVXKD0w3DfHnFTficozsAgsvq2XenPJq/MYpzzQ/v8zrOyJn6Ds39VA4JIDwFfqw==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/arg": { "version": "5.0.2", @@ -16157,8 +16541,7 @@ }, "node_modules/call-bind": { "version": "1.0.7", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", - "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -18452,8 +18835,7 @@ }, "node_modules/define-data-property": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -18508,9 +18890,8 @@ }, "node_modules/delegates": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", - "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", - "dev": true + "dev": true, + "license": "MIT" }, "node_modules/denque": { "version": "2.1.0", @@ -19177,8 +19558,7 @@ }, "node_modules/es-define-property": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", - "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "license": "MIT", "dependencies": { "get-intrinsic": "^1.2.4" }, @@ -19188,8 +19568,7 @@ }, "node_modules/es-errors": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", - "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -20337,9 +20716,8 @@ }, "node_modules/extract-zip": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, + "license": "BSD-2-Clause", "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", @@ -20357,9 +20735,8 @@ }, "node_modules/extract-zip/node_modules/get-stream": { "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, + "license": "MIT", "dependencies": { "pump": "^3.0.0" }, @@ -20523,9 +20900,8 @@ }, "node_modules/fd-slicer": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", "dev": true, + "license": "MIT", "dependencies": { "pend": "~1.2.0" } @@ -23642,8 +24018,7 @@ }, "node_modules/get-intrinsic": { "version": "1.2.4", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", - "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2", @@ -23782,9 +24157,8 @@ }, "node_modules/global-dirs": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.1.tgz", - "integrity": "sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==", "dev": true, + "license": "MIT", "dependencies": { "ini": "2.0.0" }, @@ -24386,8 +24760,7 @@ }, "node_modules/has-property-descriptors": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" }, @@ -24560,8 +24933,7 @@ }, "node_modules/hast-util-heading-rank": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/hast-util-heading-rank/-/hast-util-heading-rank-3.0.0.tgz", - "integrity": "sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, @@ -24572,8 +24944,7 @@ }, "node_modules/hast-util-heading-rank/node_modules/@types/hast": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", "dependencies": { "@types/unist": "*" } @@ -25186,8 +25557,7 @@ }, "node_modules/http-cache-semantics": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz", - "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==" + "license": "BSD-2-Clause" }, "node_modules/http-errors": { "version": "2.0.0", @@ -25537,9 +25907,8 @@ }, "node_modules/ini": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", - "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", "dev": true, + "license": "ISC", "engines": { "node": ">=10" } @@ -26025,9 +26394,8 @@ }, "node_modules/is-installed-globally": { "version": "0.4.0", - "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", - "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", "dev": true, + "license": "MIT", "dependencies": { "global-dirs": "^3.0.0", "is-path-inside": "^3.0.2" @@ -26127,9 +26495,8 @@ }, "node_modules/is-path-inside": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", - "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", "dev": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -26419,8 +26786,7 @@ }, "node_modules/isomorphic-unfetch": { "version": "3.1.0", - "resolved": "https://registry.npmjs.org/isomorphic-unfetch/-/isomorphic-unfetch-3.1.0.tgz", - "integrity": "sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==", + "license": "MIT", "dependencies": { "node-fetch": "^2.6.1", "unfetch": "^4.2.0" @@ -28260,8 +28626,8 @@ }, "node_modules/jose": { "version": "5.9.6", - "resolved": "https://registry.npmjs.org/jose/-/jose-5.9.6.tgz", - "integrity": "sha512-AMlnetc9+CV9asI19zHmrgS/WYsWUwCn2R7RzlbJWD7F9eWYUTGyBmU9o6PxngtLGOiDGPRu+Uc4fhKzbpteZQ==", + "devOptional": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/panva" } @@ -28316,8 +28682,7 @@ }, "node_modules/json-buffer": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" + "license": "MIT" }, "node_modules/json-diff-ts": { "version": "1.2.6", @@ -28500,8 +28865,7 @@ }, "node_modules/keyv": { "version": "4.5.4", - "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", - "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", + "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -28938,8 +29302,7 @@ }, "node_modules/lodash.once": { "version": "4.1.1", - "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", - "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==" + "license": "MIT" }, "node_modules/lodash.pick": { "version": "4.4.0", @@ -29180,8 +29543,7 @@ }, "node_modules/lowercase-keys": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-2.0.0.tgz", - "integrity": "sha512-tqNXrS78oMOE73NMxK4EMLQsQowWf8jKooH9g7xPavRT706R6bkQJ6DY2Te7QukaZsulxa30wQ7bk0pm4XiHmA==", + "license": "MIT", "engines": { "node": ">=8" } @@ -29209,8 +29571,7 @@ }, "node_modules/mailersend": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/mailersend/-/mailersend-2.3.0.tgz", - "integrity": "sha512-pe498Ry7VaAb+oqcYqmPw1V7FlECG/mcqahQ3SiK54en4ZkyRwjyxoQwA9VU4s3npB+I44LlQGUudObZQe4/jA==", + "license": "MIT", "dependencies": { "gaxios": "^5.0.1", "isomorphic-unfetch": "^3.1.0", @@ -29219,8 +29580,7 @@ }, "node_modules/mailersend/node_modules/gaxios": { "version": "5.1.3", - "resolved": "https://registry.npmjs.org/gaxios/-/gaxios-5.1.3.tgz", - "integrity": "sha512-95hVgBRgEIRQQQHIbnxBXeHbW4TqFk4ZDJW7wmVtvYar72FdhRIo1UGOLS2eRAKCPEdPBWu+M7+A33D9CdX9rA==", + "license": "Apache-2.0", "dependencies": { "extend": "^3.0.2", "https-proxy-agent": "^5.0.0", @@ -29233,8 +29593,7 @@ }, "node_modules/mailersend/node_modules/is-stream": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", + "license": "MIT", "engines": { "node": ">=8" }, @@ -30699,8 +31058,7 @@ }, "node_modules/mongodb": { "version": "6.10.0", - "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-6.10.0.tgz", - "integrity": "sha512-gP9vduuYWb9ZkDM546M+MP2qKVk5ZG2wPF63OvSRuUbqCR+11ZCAE1mOfllhlAG0wcoJY5yDL/rV3OmYEwXIzg==", + "license": "Apache-2.0", "dependencies": { "@mongodb-js/saslprep": "^1.1.5", "bson": "^6.7.0", @@ -43945,7 +44303,6 @@ "node_modules/netlify-cli/node_modules/unix-dgram": { "version": "2.0.6", "dev": true, - "hasInstallScript": true, "license": "ISC", "optional": true, "dependencies": { @@ -44796,14 +45153,14 @@ } }, "node_modules/next": { - "version": "15.0.3", - "resolved": "https://registry.npmjs.org/next/-/next-15.0.3.tgz", - "integrity": "sha512-ontCbCRKJUIoivAdGB34yCaOcPgYXr9AAkV/IwqFfWWTXEPUgLYkSkqBhIk9KK7gGmgjc64B+RdoeIDM13Irnw==", + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/next/-/next-15.1.0.tgz", + "integrity": "sha512-QKhzt6Y8rgLNlj30izdMbxAwjHMFANnLwDwZ+WQh5sMhyt4lEBqDK9QpvWHtIM4rINKPoJ8aiRZKg5ULSybVHw==", "peer": true, "dependencies": { - "@next/env": "15.0.3", + "@next/env": "15.1.0", "@swc/counter": "0.1.3", - "@swc/helpers": "0.5.13", + "@swc/helpers": "0.5.15", "busboy": "1.6.0", "caniuse-lite": "^1.0.30001579", "postcss": "8.4.31", @@ -44816,22 +45173,22 @@ "node": "^18.18.0 || ^19.8.0 || >= 20.0.0" }, "optionalDependencies": { - "@next/swc-darwin-arm64": "15.0.3", - "@next/swc-darwin-x64": "15.0.3", - "@next/swc-linux-arm64-gnu": "15.0.3", - "@next/swc-linux-arm64-musl": "15.0.3", - "@next/swc-linux-x64-gnu": "15.0.3", - "@next/swc-linux-x64-musl": "15.0.3", - "@next/swc-win32-arm64-msvc": "15.0.3", - "@next/swc-win32-x64-msvc": "15.0.3", + "@next/swc-darwin-arm64": "15.1.0", + "@next/swc-darwin-x64": "15.1.0", + "@next/swc-linux-arm64-gnu": "15.1.0", + "@next/swc-linux-arm64-musl": "15.1.0", + "@next/swc-linux-x64-gnu": "15.1.0", + "@next/swc-linux-x64-musl": "15.1.0", + "@next/swc-win32-arm64-msvc": "15.1.0", + "@next/swc-win32-x64-msvc": "15.1.0", "sharp": "^0.33.5" }, "peerDependencies": { "@opentelemetry/api": "^1.1.0", "@playwright/test": "^1.41.2", "babel-plugin-react-compiler": "*", - "react": "^18.2.0 || 19.0.0-rc-66855b96-20241106", - "react-dom": "^18.2.0 || 19.0.0-rc-66855b96-20241106", + "react": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", + "react-dom": "^18.2.0 || 19.0.0-rc-de68d2f4-20241204 || ^19.0.0", "sass": "^1.3.0" }, "peerDependenciesMeta": { @@ -44850,9 +45207,9 @@ } }, "node_modules/next-auth": { - "version": "4.24.10", - "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.10.tgz", - "integrity": "sha512-8NGqiRO1GXBcVfV8tbbGcUgQkAGsX4GRzzXXea4lDikAsJtD5KiEY34bfhUOjHLvr6rT6afpcxw2H8EZqOV6aQ==", + "version": "4.24.11", + "resolved": "https://registry.npmjs.org/next-auth/-/next-auth-4.24.11.tgz", + "integrity": "sha512-pCFXzIDQX7xmHFs4KVH4luCjaCbuPRtZ9oBUjUhOk84mZ9WVPf94n87TxYI4rSRf9HmfHEF8Yep3JrYDVOo3Cw==", "dependencies": { "@babel/runtime": "^7.20.13", "@panva/hkdf": "^1.0.2", @@ -44868,8 +45225,8 @@ "@auth/core": "0.34.2", "next": "^12.2.5 || ^13 || ^14 || ^15", "nodemailer": "^6.6.5", - "react": "^17.0.2 || ^18", - "react-dom": "^17.0.2 || ^18" + "react": "^17.0.2 || ^18 || ^19", + "react-dom": "^17.0.2 || ^18 || ^19" }, "peerDependenciesMeta": { "@auth/core": { @@ -44901,12 +45258,12 @@ "license": "ISC" }, "node_modules/next/node_modules/@swc/helpers": { - "version": "0.5.13", - "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.13.tgz", - "integrity": "sha512-UoKGxQ3r5kYI9dALKJapMmuK+1zWM/H17Z1+iwnNmzcJRnfFuevZs375TA5rW31pu4BS4NoSy1fRsexDXfWn5w==", + "version": "0.5.15", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.5.15.tgz", + "integrity": "sha512-JQ5TuMi45Owi4/BIMAJBoSQoOJu12oOk/gADqlcUL9JEdHB8vyjUSsxqeNXnmXHjYKMi2WcYtezGEEhqUI/E2g==", "peer": true, "dependencies": { - "tslib": "^2.4.0" + "tslib": "^2.8.0" } }, "node_modules/next/node_modules/detect-libc": { @@ -44972,6 +45329,12 @@ "@img/sharp-win32-x64": "0.33.5" } }, + "node_modules/next/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "peer": true + }, "node_modules/nice-try": { "version": "1.0.5", "license": "MIT" @@ -45323,8 +45686,7 @@ }, "node_modules/normalize-url": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-6.1.0.tgz", - "integrity": "sha512-DlL+XwOy3NxAQ8xuC0okPgK46iuVNAK01YN7RueYBqqFeGsBjV9XmCAzAdgt+667bCl5kPh9EqKKDwnaPG1I7A==", + "license": "MIT", "engines": { "node": ">=10" }, @@ -45417,8 +45779,7 @@ }, "node_modules/object-inspect": { "version": "1.13.3", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.3.tgz", - "integrity": "sha512-kDCGIbxkDSXE3euJZZXzc6to7fCrKHNI/hSRQnRuQ+BWjFNzZwiFF8fj/6o2t2G9/jTj8PSIYTfCLelLZEeRpA==", + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -45648,9 +46009,9 @@ } }, "node_modules/openid-client": { - "version": "5.7.0", - "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.0.tgz", - "integrity": "sha512-4GCCGZt1i2kTHpwvaC/sCpTpQqDnBzDzuJcJMbH+y1Q5qI8U8RBvoSh28svarXszZHR5BAMXbJPX1PGPRE3VOA==", + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/openid-client/-/openid-client-5.7.1.tgz", + "integrity": "sha512-jDBPgSVfTnkIh71Hg9pRvtJc6wTwqjRkN88+gCFtYWrlP4Yx2Dsrow8uPi3qLr/aeymPF3o2+dS+wOpglK04ew==", "dependencies": { "jose": "^4.15.9", "lru-cache": "^6.0.0", @@ -45833,8 +46194,7 @@ }, "node_modules/p-cancelable": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/p-cancelable/-/p-cancelable-2.1.1.tgz", - "integrity": "sha512-BZOr3nRQHOntUjTrH8+Lh54smKHoHyur8We1V8DSMVrl5A2malOOwuJRnKRDjSnkoeBh4at6BwEnb5I7Jl31wg==", + "license": "MIT", "engines": { "node": ">=8" } @@ -46567,8 +46927,7 @@ }, "node_modules/pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", - "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -46716,9 +47075,8 @@ }, "node_modules/playwright": { "version": "1.47.2", - "resolved": "https://registry.npmjs.org/playwright/-/playwright-1.47.2.tgz", - "integrity": "sha512-nx1cLMmQWqmA3UsnjaaokyoUpdVaaDhJhMoxX2qj3McpjnsqFHs516QAKYhqHAgOP+oCFTEOCOAaD1RgD/RQfA==", "devOptional": true, + "license": "Apache-2.0", "dependencies": { "playwright-core": "1.47.2" }, @@ -46734,9 +47092,8 @@ }, "node_modules/playwright-core": { "version": "1.47.2", - "resolved": "https://registry.npmjs.org/playwright-core/-/playwright-core-1.47.2.tgz", - "integrity": "sha512-3JvMfF+9LJfe16l7AbSmU555PaTl2tPyQsVInqm3id16pdDfvZ8TTZ/pyzmkbDrZTQefyzU7AIHlZqQnxpqHVQ==", "devOptional": true, + "license": "Apache-2.0", "bin": { "playwright-core": "cli.js" }, @@ -47363,8 +47720,7 @@ }, "node_modules/preact": { "version": "10.24.3", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.3.tgz", - "integrity": "sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==", + "license": "MIT", "funding": { "type": "opencollective", "url": "https://opencollective.com/preact" @@ -47545,9 +47901,8 @@ }, "node_modules/process": { "version": "0.11.10", - "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", - "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==", "dev": true, + "license": "MIT", "engines": { "node": ">= 0.6.0" } @@ -47744,8 +48099,7 @@ }, "node_modules/qs": { "version": "6.13.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", - "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.0.6" }, @@ -48929,8 +49283,7 @@ }, "node_modules/rehype-slug": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/rehype-slug/-/rehype-slug-6.0.0.tgz", - "integrity": "sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0", "github-slugger": "^2.0.0", @@ -48945,21 +49298,18 @@ }, "node_modules/rehype-slug/node_modules/@types/hast": { "version": "3.0.4", - "resolved": "https://registry.npmjs.org/@types/hast/-/hast-3.0.4.tgz", - "integrity": "sha512-WPs+bbQw5aCj+x6laNGWLH3wviHtoCv/P3+otBhbOhJgG8qtpdAMlTCxLtsTWA7LH1Oh/bFCHsBn0TPS5m30EQ==", + "license": "MIT", "dependencies": { "@types/unist": "*" } }, "node_modules/rehype-slug/node_modules/@types/unist": { "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@types/unist/-/unist-3.0.3.tgz", - "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==" + "license": "MIT" }, "node_modules/rehype-slug/node_modules/hast-util-to-string": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/hast-util-to-string/-/hast-util-to-string-3.0.1.tgz", - "integrity": "sha512-XelQVTDWvqcl3axRfI0xSeoVKzyIFPwsAGSLIsKdJKQMXDYJS4WYrBNF/8J7RdhIcFI2BOHgAifggsvsxp/3+A==", + "license": "MIT", "dependencies": { "@types/hast": "^3.0.0" }, @@ -48970,8 +49320,7 @@ }, "node_modules/rehype-slug/node_modules/unist-util-is": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/unist-util-is/-/unist-util-is-6.0.0.tgz", - "integrity": "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==", + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0" }, @@ -48982,8 +49331,7 @@ }, "node_modules/rehype-slug/node_modules/unist-util-visit": { "version": "5.0.0", - "resolved": "https://registry.npmjs.org/unist-util-visit/-/unist-util-visit-5.0.0.tgz", - "integrity": "sha512-MR04uvD+07cwl/yhVuVWAtw+3GOR/knlL55Nd/wAdblk27GCVt3lqpTivy/tkJcZoNPzTwS1Y+KMojlLDhoTzg==", + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0", @@ -48996,8 +49344,7 @@ }, "node_modules/rehype-slug/node_modules/unist-util-visit-parents": { "version": "6.0.1", - "resolved": "https://registry.npmjs.org/unist-util-visit-parents/-/unist-util-visit-parents-6.0.1.tgz", - "integrity": "sha512-L/PqWzfTP9lzzEa6CKs0k2nARxTdZduw3zyh8d2NVBnsyvHjSX4TWse388YrrQKbvI8w20fGjGlhgT96WwKykw==", + "license": "MIT", "dependencies": { "@types/unist": "^3.0.0", "unist-util-is": "^6.0.0" @@ -49898,8 +50245,7 @@ }, "node_modules/responselike": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/responselike/-/responselike-2.0.1.tgz", - "integrity": "sha512-4gl03wn3hj1HP3yzgdI7d3lCkF95F21Pz4BPGvKHinyQzALR5CapwC8yIi0Rh58DEMQ/SguC03wFj2k0M/mHhw==", + "license": "MIT", "dependencies": { "lowercase-keys": "^2.0.0" }, @@ -50335,8 +50681,7 @@ }, "node_modules/set-function-length": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -50485,8 +50830,7 @@ }, "node_modules/side-channel": { "version": "1.0.6", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", - "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", + "license": "MIT", "dependencies": { "call-bind": "^1.0.7", "es-errors": "^1.3.0", @@ -50916,8 +51260,7 @@ }, "node_modules/sshpk": { "version": "1.18.0", - "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.18.0.tgz", - "integrity": "sha512-2p2KJZTSqQ/I3+HX42EpYOa2l3f8Erv8MWKsy2I9uf4wA7yFIkXRffYdsx86y6z4vHtV8u7g+pPlr8/4ouAxsQ==", + "license": "MIT", "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", @@ -52334,8 +52677,7 @@ }, "node_modules/tmp": { "version": "0.2.3", - "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.3.tgz", - "integrity": "sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==", + "license": "MIT", "engines": { "node": ">=14.14" } @@ -52738,8 +53080,7 @@ }, "node_modules/typescript": { "version": "5.6.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.6.3.tgz", - "integrity": "sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==", + "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -52892,9 +53233,8 @@ }, "node_modules/unbzip2-stream": { "version": "1.4.3", - "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz", - "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==", "dev": true, + "license": "MIT", "dependencies": { "buffer": "^5.2.1", "through": "^2.3.8" @@ -52948,8 +53288,7 @@ }, "node_modules/unfetch": { "version": "4.2.0", - "resolved": "https://registry.npmjs.org/unfetch/-/unfetch-4.2.0.tgz", - "integrity": "sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==" + "license": "MIT" }, "node_modules/unicode-canonical-property-names-ecmascript": { "version": "2.0.0", @@ -54272,9 +54611,8 @@ }, "node_modules/yauzl": { "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", "dev": true, + "license": "MIT", "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" diff --git a/site/gatsby-site/package.json b/site/gatsby-site/package.json index 88299e128b..bc5b16d2b0 100644 --- a/site/gatsby-site/package.json +++ b/site/gatsby-site/package.json @@ -8,7 +8,6 @@ "@apollo/client": "^3.7.8", "@apollo/server": "^4.10.2", "@as-integrations/aws-lambda": "^3.1.0", - "@auth/mongodb-adapter": "^3.7.3", "@aws-sdk/client-s3": "^3.633.0", "@billboard.js/react": "^1.0.1", "@bytemd/react": "^1.15.0", @@ -213,4 +212,4 @@ "optionalDependencies": { "@parcel/watcher-linux-x64-glibc": "^2.4.0" } -} \ No newline at end of file +} From 476bf88effeed761f3ed012ac64d774a026bfc7f Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Mon, 16 Dec 2024 14:27:28 -0300 Subject: [PATCH 80/99] Add next auth sign up tests --- site/gatsby-site/nextauth.config.ts | 2 +- .../server/tests/next-auth.spec.ts | 266 ++++++++++++++---- site/gatsby-site/src/contexts/UserContext.tsx | 2 +- 3 files changed, 215 insertions(+), 55 deletions(-) diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts index 1a52e017a3..11fc51dbc6 100644 --- a/site/gatsby-site/nextauth.config.ts +++ b/site/gatsby-site/nextauth.config.ts @@ -87,7 +87,7 @@ export const getAuthConfig = async (req: any): Promise => { */ async signIn({ user }) { - if (!(user as AdapterUser).emailVerified && req?.query?.operation == 'signin') { + if (!(user as AdapterUser).emailVerified && req?.query?.operation == 'login') { return config.SITE_URL + '/api/auth/verify-request?provider=http-email&type=email' } diff --git a/site/gatsby-site/server/tests/next-auth.spec.ts b/site/gatsby-site/server/tests/next-auth.spec.ts index 61daf45f45..f81da3d89c 100644 --- a/site/gatsby-site/server/tests/next-auth.spec.ts +++ b/site/gatsby-site/server/tests/next-auth.spec.ts @@ -34,83 +34,243 @@ function mockAuthEvent(operation: string, email: string, callbackUrl: string): P describe('Auth', () => { - test('Should not send an magic link email to unregistered users on sign in ', async () => { + describe('Login', () => { + + test('Should not send an magic link email to unregistered users and to add them to the database', async () => { + + await seedFixture({ + auth: { + users: [], + }, + }); - await seedFixture({ - auth: { - users: [], - }, - }); + const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); - const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); + const event = mockAuthEvent('login', 'test.user@incidentdatabase.ai', '/'); - const event = mockAuthEvent('signin', 'test.user@incidentdatabase.ai', '/'); + const response = await handler(event as HandlerEvent, {} as HandlerContext); - const response = await handler(event as HandlerEvent, {} as HandlerContext); + expect(sendEmailMock).not.toHaveBeenCalled(); + expect(response).toMatchObject({ + statusCode: 200, + headers: { + 'Cache-Control': 'no-store, max-age=0', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ url: config.SITE_URL + '/api/auth/verify-request?provider=http-email&type=email' }), + }); + + const users = await getCollection('auth', 'users').find({}).toArray(); - expect(sendEmailMock).not.toHaveBeenCalled(); - expect(response).toMatchObject({ - statusCode: 200, - headers: { - 'Cache-Control': 'no-store, max-age=0', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ url: config.SITE_URL + '/api/auth/verify-request?provider=http-email&type=email' }), + expect(users).toHaveLength(0); }); - const users = await getCollection('auth', 'users').find({}).toArray(); + test('Should send an magic link email to registered users', async () => { - expect(users).toHaveLength(0); - }); + const email = "test.user@incidentdatabase.ai"; - test('Should send an magic link email to registered users on sign in ', async () => { + await seedFixture({ + auth: { + users: [ + { email, emailVerified: new Date().toString() } + ], + }, + }); - const email = "test.user@incidentdatabase.ai"; + const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); + const event = mockAuthEvent('login', email, '/'); - await seedFixture({ - auth: { - users: [ - { email, emailVerified: new Date().toString() } + const response = await handler(event as HandlerEvent, {} as HandlerContext); + + expect(sendEmailMock).toHaveBeenCalledWith({ + dynamicData: { + magicLink: expect.stringMatching(/^http:\/\/localhost:8000\/api\/auth\/callback\/http-email\?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2F&token=.+&email=test.user%40incidentdatabase.ai$/), + }, + recipients: [ + { + email: "test.user@incidentdatabase.ai", + }, ], - }, - }); + subject: "Login link", + templateId: "Login", - const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); - const event = mockAuthEvent('signin', email, '/'); + }); - const response = await handler(event as HandlerEvent, {} as HandlerContext); + expect(response).toMatchObject({ + statusCode: 200, + headers: { + 'Cache-Control': 'no-store, max-age=0', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ url: config.SITE_URL + '/api/auth/verify-request?provider=http-email&type=email' }), + }); - expect(sendEmailMock).toHaveBeenCalledWith({ - dynamicData: { - magicLink: expect.stringMatching(/^http:\/\/localhost:8000\/api\/auth\/callback\/http-email\?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2F&token=.+&email=test.user%40incidentdatabase.ai$/), - }, - recipients: [ + const users = await getCollection('auth', 'users').find({}).toArray(); + + expect(users).toMatchObject([ { email: "test.user@incidentdatabase.ai", + emailVerified: expect.any(String), + } + ]); + }); + + test('Should forward callbackUrl', async () => { + + const email = "test.user@incidentdatabase.ai"; + const callbackUrl = '/some-path/some-page'; + + await seedFixture({ + auth: { + users: [ + { email, emailVerified: new Date().toString() } + ], + }, + }); + + const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); + const event = mockAuthEvent('login', email, callbackUrl); + + await handler(event as HandlerEvent, {} as HandlerContext); + + expect(sendEmailMock).toHaveBeenCalledWith({ + dynamicData: { + magicLink: expect.stringMatching(/^http:\/\/localhost:8000\/api\/auth\/callback\/http-email\?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2Fsome-path%2Fsome-page&token=.+&email=test.user%40incidentdatabase.ai$/), + }, + recipients: [ + { + email: "test.user@incidentdatabase.ai", + }, + ], + subject: "Login link", + templateId: "Login", + + }); + }); + }); + + describe('Signup', () => { + + test('Should send a Sig nup link email to unregistered users', async () => { + + const email = "test.user@incidentdatabase.ai"; + + await seedFixture({ + auth: { + users: [], + verification_tokens: [], + }, + }); + + const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); + const event = mockAuthEvent('signup', email, '/'); + + const response = await handler(event as HandlerEvent, {} as HandlerContext); + + expect(sendEmailMock).toHaveBeenCalledWith({ + dynamicData: { + magicLink: expect.stringMatching(/^http:\/\/localhost:8000\/api\/auth\/callback\/http-email\?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2F&token=.+&email=test.user%40incidentdatabase.ai$/), + }, + recipients: [ + { + email: "test.user@incidentdatabase.ai", + }, + ], + subject: "Signup link", + templateId: "Signup", + }); + + expect(response).toMatchObject({ + statusCode: 200, + headers: { + 'Cache-Control': 'no-store, max-age=0', + 'Content-Type': 'application/json', }, - ], - subject: "Login link", - templateId: "Login", + body: JSON.stringify({ url: config.SITE_URL + '/api/auth/verify-request?provider=http-email&type=email' }), + }); + const users = await getCollection('auth', 'users').find({}).toArray(); + expect(users).toMatchObject([]); + + const tokens = await getCollection('auth', 'verification_tokens').find({}).toArray(); + expect(tokens).toMatchObject([{ identifier: email, }]); }); - expect(response).toMatchObject({ - statusCode: 200, - headers: { - 'Cache-Control': 'no-store, max-age=0', - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ url: config.SITE_URL + '/api/auth/verify-request?provider=http-email&type=email' }), + test('Should send a Sig in link email to registered users', async () => { + + const email = "test.user@incidentdatabase.ai"; + + await seedFixture({ + auth: { + users: [ + { email, emailVerified: new Date().toString() } + ], + verification_tokens: [], + }, + }); + + const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); + const event = mockAuthEvent('signup', email, '/'); + + const response = await handler(event as HandlerEvent, {} as HandlerContext); + + expect(sendEmailMock).toHaveBeenCalledWith({ + dynamicData: { + magicLink: expect.stringMatching(/^http:\/\/localhost:8000\/api\/auth\/callback\/http-email\?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2F&token=.+&email=test.user%40incidentdatabase.ai$/), + }, + recipients: [ + { + email: "test.user@incidentdatabase.ai", + }, + ], + subject: "Login link", + templateId: "Login", + }); + + expect(response).toMatchObject({ + statusCode: 200, + headers: { + 'Cache-Control': 'no-store, max-age=0', + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ url: config.SITE_URL + '/api/auth/verify-request?provider=http-email&type=email' }), + }); + + const tokens = await getCollection('auth', 'verification_tokens').find({}).toArray(); + expect(tokens).toMatchObject([{ identifier: email, }]); }); - const users = await getCollection('auth', 'users').find({}).toArray(); + test('Should forward callbackUrl', async () => { + + const email = "test.user@incidentdatabase.ai"; + const callbackUrl = '/some-path/some-page'; + + await seedFixture({ + auth: { + users: [ + ], + }, + }); + + const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); + const event = mockAuthEvent('signup', email, callbackUrl); + + await handler(event as HandlerEvent, {} as HandlerContext); + + expect(sendEmailMock).toHaveBeenCalledWith({ + dynamicData: { + magicLink: expect.stringMatching(/^http:\/\/localhost:8000\/api\/auth\/callback\/http-email\?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2Fsome-path%2Fsome-page&token=.+&email=test.user%40incidentdatabase.ai$/), + }, + recipients: [ + { + email: "test.user@incidentdatabase.ai", + }, + ], + subject: "Signup link", + templateId: "Signup", + }); + }); - expect(users).toMatchObject([ - { - email: "test.user@incidentdatabase.ai", - emailVerified: expect.any(String), - } - ]); }); }); diff --git a/site/gatsby-site/src/contexts/UserContext.tsx b/site/gatsby-site/src/contexts/UserContext.tsx index 11cbc453e1..a09622a669 100644 --- a/site/gatsby-site/src/contexts/UserContext.tsx +++ b/site/gatsby-site/src/contexts/UserContext.tsx @@ -104,7 +104,7 @@ export const UserContextProvider: React.FC = ({ childr }, logIn: async (email: string, callbackUrl: string) => { - const result = await signIn('http-email', { email, redirect: false, callbackUrl }, { operation: 'signin' }); + const result = await signIn('http-email', { email, redirect: false, callbackUrl }, { operation: 'login' }); return result; }, From cb909b927f07d4fe16a233d1c57b138baafec214 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Mon, 16 Dec 2024 17:09:25 -0300 Subject: [PATCH 81/99] Use encoded value --- site/gatsby-site/server/tests/next-auth.spec.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/site/gatsby-site/server/tests/next-auth.spec.ts b/site/gatsby-site/server/tests/next-auth.spec.ts index f81da3d89c..aca9ace3ba 100644 --- a/site/gatsby-site/server/tests/next-auth.spec.ts +++ b/site/gatsby-site/server/tests/next-auth.spec.ts @@ -28,7 +28,7 @@ function mockAuthEvent(operation: string, email: string, callbackUrl: string): P "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:134.0) Gecko/20100101 Firefox/134.0", host: "localhost:8000", }, - body: `email=${encodedEmail}&redirect=false&callbackUrl=${callbackUrl}&csrfToken=3fc5b5ba3bb4457090ea32b335e69294637dca5a9473dcc669a4ed00cdadf199&json=true`, + body: `email=${encodedEmail}&redirect=false&callbackUrl=${encodedCallbackUrl}&csrfToken=3fc5b5ba3bb4457090ea32b335e69294637dca5a9473dcc669a4ed00cdadf199&json=true`, } } @@ -152,7 +152,7 @@ describe('Auth', () => { describe('Signup', () => { - test('Should send a Sig nup link email to unregistered users', async () => { + test('Should send a Sign nup link email to unregistered users', async () => { const email = "test.user@incidentdatabase.ai"; @@ -197,7 +197,7 @@ describe('Auth', () => { expect(tokens).toMatchObject([{ identifier: email, }]); }); - test('Should send a Sig in link email to registered users', async () => { + test('Should send a Sign in link email to registered users', async () => { const email = "test.user@incidentdatabase.ai"; From 4b4f5184badfc98fd245a897f30cebbc58f4032a Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Mon, 16 Dec 2024 20:13:35 -0300 Subject: [PATCH 82/99] Add migration to fetch users from atlas and insert them in the new auth collection --- .../2024.12.16T20.19.34.migrate-users-auth.js | 125 ++++++++++++++++++ 1 file changed, 125 insertions(+) create mode 100644 site/gatsby-site/migrations/2024.12.16T20.19.34.migrate-users-auth.js diff --git a/site/gatsby-site/migrations/2024.12.16T20.19.34.migrate-users-auth.js b/site/gatsby-site/migrations/2024.12.16T20.19.34.migrate-users-auth.js new file mode 100644 index 0000000000..61a7b17e9a --- /dev/null +++ b/site/gatsby-site/migrations/2024.12.16T20.19.34.migrate-users-auth.js @@ -0,0 +1,125 @@ +const { ObjectId } = require('bson'); + +const jwt = require('jsonwebtoken'); + +for (const key of [ + 'REALM_API_PUBLIC_KEY', + 'REALM_API_PRIVATE_KEY', + 'REALM_API_GROUP_ID', + 'REALM_API_APP_ID', +]) { + if (!process.env[key]) { + throw new Error(`Environment variable ${key} is required`); + } +} + +let cachedToken = null; + +let tokenExpiration = null; + +/** + * Fetches the access token for the MongoDB Atlas Admin API. + * + * Note: The token is cached to speed up subsequent requests. Tokens expire after 30 minutes. + * + * @returns {Promise} A promise that resolves to the access token. + */ +const getAccessToken = async () => { + const refreshDate = tokenExpiration ? tokenExpiration * 1000 - 5 * 60 * 1000 : null; + + // Refresh the authentication token well before expiration to avoid interruptions. + + const now = Date.now(); + + if (cachedToken && refreshDate && now < refreshDate) { + return cachedToken; + } + + const loginResponse = await fetch( + 'https://services.cloud.mongodb.com/api/admin/v3.0/auth/providers/mongodb-cloud/login', + { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ + username: process.env.REALM_API_PUBLIC_KEY, + apiKey: process.env.REALM_API_PRIVATE_KEY, + }), + } + ); + + const data = await loginResponse.json(); + + if (loginResponse.status != 200) { + throw new Error(`Login failed: ${data.error}`); + } + + const decoded = jwt.decode(data.access_token); + + cachedToken = data.access_token; + tokenExpiration = decoded.exp; + + return cachedToken; +}; + +const apiRequest = async ({ path, params = {}, method = 'GET' }) => { + const accessToken = await getAccessToken(); + + const url = new URL( + `https://services.cloud.mongodb.com/api/admin/v3.0/groups/${process.env.REALM_API_GROUP_ID}/apps/${process.env.REALM_API_APP_ID}${path}` + ); + + if (Object.keys(params).length > 0) { + const searchParams = new URLSearchParams(params); + + url.search = searchParams.toString(); + } + + const headers = { Authorization: `Bearer ${accessToken}` }; + + let response = null; + + if (method == 'GET') { + const result = await fetch(url.toString(), { headers }); + + response = await result.json(); + } else { + throw `Unsupported method ${method}`; + } + + return response; +}; + +/** + * + * @param {{context: {client: import('mongodb').MongoClient}}} context + */ +exports.up = async ({ context: { client } }) => { + await client.connect(); + + const users = await client.db('customData').collection('users').find().toArray(); + + for (const user of users) { + const response = await apiRequest({ path: `/users/${user.userId}` }); + + if (response?.data?.email) { + try { + await client + .db('auth') + .collection('users') + .insertOne({ + _id: ObjectId.createFromHexString(user.userId), + email: response?.data?.email, + emailVerified: new Date(), + }); + + console.log(`Migrated user ${user.userId}`); + } catch (e) { + console.error(`Failed to migrate user ${user.userId}: ${e}`); + } + } else { + console.error(`User ${user.userId} not found`); + } + } +}; From 5e16d9314e978c7d80cc9dac8ad1cdecc83dadcc Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Mon, 16 Dec 2024 21:29:14 -0300 Subject: [PATCH 83/99] Add tests to user creation. --- .../server/tests/next-auth.spec.ts | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) diff --git a/site/gatsby-site/server/tests/next-auth.spec.ts b/site/gatsby-site/server/tests/next-auth.spec.ts index aca9ace3ba..868f577ec1 100644 --- a/site/gatsby-site/server/tests/next-auth.spec.ts +++ b/site/gatsby-site/server/tests/next-auth.spec.ts @@ -32,6 +32,30 @@ function mockAuthEvent(operation: string, email: string, callbackUrl: string): P } } +function mockMagicLinkEvent(email: string, token: string, callbackUrl: string): Partial { + + return { + path: "/api/auth/callback/http-email", + httpMethod: "GET", + queryStringParameters: { + callbackUrl, + token, + email, + }, + headers: { + cookie: "", + connection: "close", + "accept-encoding": "gzip, deflate, br, zstd", + "accept-language": "en-US,en;q=0.5", + accept: "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:134.0) Gecko/20100101 Firefox/134.0", + host: "localhost:8000", + }, + body: undefined, + } +} + + describe('Auth', () => { describe('Login', () => { @@ -273,4 +297,51 @@ describe('Auth', () => { }); }); + + describe('Callback', () => { + + test('Should verify the email and create a user with the appropriate userId', async () => { + + const email = "test.user@incidentdatabase.ai"; + // hashed token gets sent in the magic link email + const hashedToken = '4456ea1edd2d80807c5722de9fa3584ba7b2f9c4b12984ae00f500ddb41d378e'; + // token stored in the database + const token = 'afffee2a9c99aacb855bf822fbddc770dd455448111ca75adc0727f7dd81c0c8'; + + await seedFixture({ + auth: { + users: [], + verification_tokens: [ + { identifier: email, token, expires: new Date(new Date().getTime() + 1000 * 60 * 60).toString() } + ], + }, + customData: { + users: [], + } + }); + + const event = mockMagicLinkEvent(email, hashedToken, '/'); + + const response = await handler(event as HandlerEvent, {} as HandlerContext); + + expect(response).toMatchObject({ + statusCode: 302, + headers: { + Location: '/account?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2F', + }, + }); + + const users = await getCollection('auth', 'users').find({}).toArray(); + + expect(users).toMatchObject([{ email }]); + + + const customDataUsers = await getCollection('customData', 'users').find({}).toArray(); + + expect(customDataUsers).toMatchObject([{ + userId: users[0]._id.toString(), + roles: ['subscriber'], + }]); + }); + }); }); From 3060987264c0d2a074c8f2b1ba934fc2ff4a0af6 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Mon, 16 Dec 2024 22:36:00 -0300 Subject: [PATCH 84/99] Update test to handle new param --- site/gatsby-site/playwright/e2e-full/signup.spec.ts | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/signup.spec.ts b/site/gatsby-site/playwright/e2e-full/signup.spec.ts index a7e180059e..83860dfc03 100644 --- a/site/gatsby-site/playwright/e2e-full/signup.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/signup.spec.ts @@ -45,7 +45,7 @@ test.describe('Signup', () => { test('Should display the error toast message if any other sign up error occurs', async ({ page }) => { await page.goto(url); - await page.route('**/api/auth/signin/http-email', async (route) => { + await page.route('**/api/auth/signin/http-email*', async (route) => { await route.fulfill({ status: 200, @@ -62,11 +62,9 @@ test.describe('Signup', () => { }); }); - await page.locator('[data-cy="signup-btn"]').click(); - await page.locator('input[name=email]').fill('test@test.com'); - const signupResponse = page.waitForResponse('**/api/auth/signin/http-email'); + const signupResponse = page.waitForResponse('**/api/auth/signin/http-email*'); await page.locator('[data-cy="signup-btn"]').click(); From 27f8369f0c96f0afc2249add859a4e38f2119431 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Mon, 16 Dec 2024 23:07:48 -0300 Subject: [PATCH 85/99] Update tests --- site/gatsby-site/playwright/e2e-full/login.spec.ts | 4 ++-- site/gatsby-site/playwright/e2e-full/rollbar.spec.ts | 2 +- site/gatsby-site/playwright/e2e-full/signup.spec.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/site/gatsby-site/playwright/e2e-full/login.spec.ts b/site/gatsby-site/playwright/e2e-full/login.spec.ts index 38ad3bb049..9c93aa5596 100644 --- a/site/gatsby-site/playwright/e2e-full/login.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/login.spec.ts @@ -31,7 +31,7 @@ test.describe('Login', () => { const email = 'test.user@incidentdatabase.ai'; - await page.route('**/api/auth/signin/http-email', async (route) => { + await page.route('**/api/auth/signin/http-email*', async (route) => { const formData = new URLSearchParams(await route.request().postData()); @@ -54,7 +54,7 @@ test.describe('Login', () => { await expect(page.locator('[data-cy="login-btn"]')).toBeEnabled({ timeout: 1000 }); }).toPass(); - const signupResponse = page.waitForResponse('**/api/auth/signin/http-email'); + const signupResponse = page.waitForResponse('**/api/auth/signin/http-email*'); await page.locator('[data-cy="login-btn"]').click(); diff --git a/site/gatsby-site/playwright/e2e-full/rollbar.spec.ts b/site/gatsby-site/playwright/e2e-full/rollbar.spec.ts index 8fb3d5bb6d..0f401f5fba 100644 --- a/site/gatsby-site/playwright/e2e-full/rollbar.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/rollbar.spec.ts @@ -17,7 +17,7 @@ test.describe('Rollbar', () => { }); }); - await page.route('**/api/auth/signin/http-email', async (route) => { + await page.route('**/api/auth/signin/http-email*', async (route) => { await route.fulfill({ status: 200, body: JSON.stringify({ diff --git a/site/gatsby-site/playwright/e2e-full/signup.spec.ts b/site/gatsby-site/playwright/e2e-full/signup.spec.ts index 83860dfc03..759fa75fb4 100644 --- a/site/gatsby-site/playwright/e2e-full/signup.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/signup.spec.ts @@ -13,7 +13,7 @@ test.describe('Signup', () => { const email = 'new.user@test.com'; - await page.route('**/api/auth/signin/http-email', async (route) => { + await page.route('**/api/auth/signin/http-email*', async (route) => { const formData = new URLSearchParams(await route.request().postData() || ''); expect(formData.get('email')).toBe(email); @@ -33,7 +33,7 @@ test.describe('Signup', () => { await page.locator('input[name=email]').fill(email); - const signupResponse = page.waitForResponse('**/api/auth/signin/http-email'); + const signupResponse = page.waitForResponse('**/api/auth/signin/http-email*'); await page.locator('[data-cy="signup-btn"]').click(); From a76fe7fad9a196545f1e200837a0a45cfa2f9576 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Thu, 19 Dec 2024 17:52:49 -0300 Subject: [PATCH 86/99] Undo readme changes --- README.md | 392 +++++++++++++++++++++++++++++++- site/gatsby-site/README.md | 454 ++----------------------------------- 2 files changed, 414 insertions(+), 432 deletions(-) diff --git a/README.md b/README.md index 585b61bdb0..10b06390f9 100644 --- a/README.md +++ b/README.md @@ -108,6 +108,12 @@ The site has three components that are considered "serverless," meaning there is More details are available in the `Production System` information below. We recommend most people forego setting up a development environment with their own Index and Database. You should instead concentrate on setting up a Gatsby development site. +**Style guide:** + +1. `ESLint` and `Prettier` have been configured to help enforcing code styles. Configuration details can be found in `.eslintrc.json` and `.prettierrc`. +2. [Husky](https://github.com/typicode/husky#readme) and [lint-staged](https://github.com/okonet/lint-staged) are installed and `pre-commit` hook added to check lint/prettier issues on staged files and fix them automatically before making commit. +3. `format` and `lint` scripts can be used manually to fix style issues. + ## Production System ### Netlify @@ -128,10 +134,68 @@ See [mongo.md](mongo.md) [Cloudinary](https://www.cloudinary.com) is what we use to host and manage report images. +## Setting up a development environment + ## Important Notice This project is currently undergoing a significant restructuring as we transition away from the recently deprecated Atlas GraphQL endpoint. Please note that some parts of the documentation may be outdated. For the most up-to-date information and guidance, please follow [this link](site/gatsby-site/README.md) to the latest documentation. +Depending on what feature you are working on, there will be different systems you'll need to set up after you've forked and cloned this repository: + +### Basic setup +Get a Gatsby environment working. Most of the time, you'll only need to run: + +``` +npm install --global gatsby-cli +``` +Create a `.env` file under `site/gatsby-site` with the following contents: + +``` +GATSBY_REALM_APP_ID=aiidstitch2-sasvc +MONGODB_CONNECTION_STRING=mongodb+srv://readonly:vNMlVM35rsTlMUTr@aiiddev.seam4.mongodb.net +MONGODB_TRANSLATIONS_CONNECTION_STRING=mongodb+srv://readonly:vNMlVM35rsTlMUTr@aiiddev.seam4.mongodb.net +MONGODB_REPLICA_SET=aiiddev-shard-00-02.seam4.mongodb.net,aiiddev-shard-00-01.seam4.mongodb.net,aiiddev-shard-00-00.seam4.mongodb.net + +GATSBY_ALGOLIA_APP_ID=JD5JCVZEVS +GATSBY_ALGOLIA_SEARCH_KEY=c5e99d93261645721a1765fe4414389c +GATSBY_AVAILABLE_LANGUAGES=en,es,fr +SKIP_PAGE_CREATOR=createTsneVisualizationPage +GATSBY_PRISMIC_REPO_NAME= +PRISMIC_ACCESS_TOKEN= +IS_EMPTY_ENVIRONMENT= +``` + +For `GATSBY_PRISMIC_REPO_NAME` and `PRISMIC_ACCESS_TOKEN` variables, please [follow prismic setup below](https://github.com/responsible-ai-collaborative/aiid#prismic-setup) + +For complete empty environment (no database data, no Algolia index, no Prismic content), set `IS_EMPTY_ENVIRONMENT=true`. This will disable all tests that require data. + +This will give you access to our `staging` environment, so please be sure you are on the `staging` branch. + +In the same folder, install dependencies using `npm` (do not use `yarn`, it will ignore the `package-lock.json` file): + +``` +npm install +``` + +You are ready to start a local copy of the project: + +``` +gatsby develop +``` +You should have a local copy of the project running on https://localhost:8000. + +The values you placed into the env file are all associated with a staging environment that is periodically rebuilt from the production environment. While this helps you get setup more quickly, if you will be making changes to the backend you will need your own development backend that you can control, modify, and potentially break. + +#### Additional Configuration + +When building the site, some steps can take a while to run. This can be inconvenient when you are working on a feature unrelated to the steps taking the most time in the build process. To avoid this problem, you can set the environment variable `SKIP_PAGE_CREATOR` to a comma-separated list of page-creator functions found in [`gatsby-node`](https://github.com/responsible-ai-collaborative/aiid/blob/main/site/gatsby-site/gatsby-node.js) that should be skipped. These include: `createMdxPages`, `createCitationPages`, `createWordCountsPages`, `createBackupsPage`, `createTaxonomyPages`, `createDownloadIndexPage`, `createDuplicatePages`, `createTsneVisualizationPage`, and `createEntitiesPages`. For instance, to run a development build skipping the creation of the TSNE (spatial) visualization and citation pages, you would run: + +```bash +SKIP_PAGE_CREATOR=createTsneVisualizationPage,createCitiationPages gatsby develop +``` + +In general, skipping the TSNE visualization has the most significant reduction in build time. + ### MongoDB setup If the feature you are working on includes structural changes to the MongoDB database or Realm functions, you'll need to create your own project by going to https://cloud.mongodb.com and following these steps: @@ -287,6 +351,64 @@ If the feature you are working on depends on Google's Geocoding API, please add GOOGLE_MAPS_API_KEY=XXXXXXXXXXXX ``` +## Prismic setup +This project uses Prismic to fetch page content. You can still run the project without setting a Prismic account. + +1. Sign up for a new [Prismic](https://prismic.io/) account or log in to your account if you already have one +2. In `Create a new repository` section choose `Something else` +3. Give your repository a name and choose `gatsby` in the technology dropdown +4. Choose your plan (if you only need one user, the free plan is enough) +5. Click `Create repository` +6. Create a new token in Settings > API & Security > Content API tab > Change Repository security to `Private API – Require an access token for any request` > Create new app > Permanent access tokens > Save value for later + +### Adding the Prismic content types + +#### Prismic Custom Types +You can find the list of all custom types in the folder `custom_types` + +#### How to create a new Custom Type +1. From the prismic left menu click `Custom Types` +2. Click `Create new custom type` +3. Give it a name (name of the json in custom_types folder) +4. Click `JSON editor` +5. Paste the JSON content from the predefined custom types inside the json +6. Click `Save` + +#### Adding Prismic documents + +1. On the Prismic dashboard left menu click `Documents` +2. Click `Create new` +3. Fill in all the mandatory fields +4. Click `Save` +5. Keep in mind that the new content won't be available on your page until you Publish it. +6. In order to publish it, click `Publish` + +#### Prismic & Netlify Hook integration + +In order for your recently published Prismic content to be available on your page, a Netlify build needs to be triggered. +In order to do this, you need to create a Netlify Build Hook. + +#### Prismic environment variables + +Add the following environment variable on Netlify: +`GATSBY_PRISMIC_REPO_NAME=[name_of_your_repository]` (step 3 from Prismic Setup section) +`PRISMIC_ACCESS_TOKEN=[you_prismic_access_token]` (step 6 from Prismic Setup section) + +#### Create Prismic/Netlify Hook +1. Login to your Netlify +2. Go to `Deploys` +3. Go to `Deploy settings` +4. Scroll to `Build Hooks` +5. Click `Add build hook` +6. Give it a name and assign a branch +7. Click save +8. Copy the generated URL +9. Go to your Prismic repository +10. Go to `Settings` > `Webhooks` +11. Create a new webhook and paste the URL in the URL field +12. In `Triggers` select `A document is published` and `A document is unpublished` +13. Click `Add this webhook` + ## User Roles All site users have one or more roles assigned to them. The role determines what actions the user can take on the site. @@ -305,6 +427,214 @@ As soon as a user is signed in, the system assigns a `subscriber` role by defaul | `admin` | This role has full access to the site, including the ability to edit users' roles. | +## Front-end development + +### Tailwind CSS & Flowbite + +This project uses [Tailwind CSS](https://tailwindcss.com/) framework with its class syntax. +More specifically, we base our components on [Flowbite React](https://flowbite-react.com/) and [Flowbite](https://flowbite.com/) which is built on top of TailwindCSS. + +### Steps for developing + +In order to keep styling consistency on the site, we follow a set of steps when developing. This is also to make the development process more agile and simple. + +1. Develop your component using [Flowbite React components](https://flowbite-react.com/) +2. If your components is not fully contemplated by Flowbite react, check [Flowbite components](https://flowbite.com/#components) and use the provided HTMLs. +3. If you need to improve styling, use only Tailwind CSS classes. + +**Examples** +If you want to place a new [Flowbite React button](https://flowbite-react.com/buttons): + +```javascript +import { Button } from 'flowbite-react'; + +const YourComponent = () => { + return +} + +``` + +If you want to customize a [Flowbite button](https://flowbite.com/docs/components/buttons/): + +```javascript +const YourComponent = () => { + return +} +``` + +## Deployment Setup + +Deployment of the site consists of two parts: deployment of the backend related features that runs as a GitHub Action and deployment of the frontend related features that runs on Netlify: + +### Netlify +The Netlify build process runs every time a push is made to an open PR or `main` or `develop`. +To correctly set up this process, the following environment variables need to be created using Netlify's build settings UI: + +``` +ALGOLIA_ADMIN_KEY= +AWS_LAMBDA_JS_RUNTIME=nodejs18.x # required to run the Gatsby v5 +GATSBY_ALGOLIA_APP_ID= +GATSBY_ALGOLIA_SEARCH_KEY= +GATSBY_REALM_APP_ID= +MONGODB_CONNECTION_STRING= +MONGODB_REPLICA_SET= +GATSBY_EXCLUDE_DATASTORE_FROM_BUNDLE=1 # specific to Netlify, for large sites +GATSBY_CPU_COUNT=2 # limits the number of Gatsby threads, helping with deployment stability +NODE_VERSION=18 # this is required by Gatsby v5 +NODE_OPTIONS=--max-old-space-size=4096 # increase default heap size to prevent crashes during build +# The following "CLOUDFLARE_R2" variables are required to create the /research/snapshots/ page +CLOUDFLARE_R2_ACCOUNT_ID=[The Cloudflare R2 account ID (e.g.: 8f4144a9d995a9921d0200db59f6a00e)] +CLOUDFLARE_R2_ACCESS_KEY_ID=[The Cloudflare R2 access key ID (e.g.: 7aa73208bc89cee3195879e578b291ee)] +CLOUDFLARE_R2_SECRET_ACCESS_KEY=[The Cloudflare R2 secret access key] +CLOUDFLARE_R2_BUCKET_NAME=[The Cloudflare R2 bucket name (e.g.: 'aiid-public')] +GATSBY_CLOUDFLARE_R2_PUBLIC_BUCKET_URL=[The Cloudflare R2 public bucket URL (e.g.: https://pub-daddb16dc28841779b83690f75eb5c58.r2.dev)] +``` + +### New Netlify Setup + +This guide walks you through the steps to set up a Netlify site for your project by importing an existing project from GitHub. + +### Prerequisites + +- Ensure you have a GitHub account and your project is already pushed to a repository. +- Make sure you have a Netlify account. If not, sign up at [Netlify](https://www.netlify.com/). + +### Steps to Set Up + +#### 1. Add New Site + +- Go to your Netlify dashboard. +- Click on **Add New Site**. + +#### 2. Import Existing Project + +- Choose **Import Existing Project**. + +#### 3. Deploy with GitHub + +- Select **Deploy with GitHub** to connect your GitHub account. + +#### 4. Select Repository + +- Choose the repository where your project is located. + +#### 5. Configure Deployment + +- Under **Branch to Deploy**, select `main`. This setting doesn't matter for now. +- Leave all other settings as default. +- Click on **Deploy Site**. + +#### 6. Site Configuration + +##### Build and Deploy + +- Navigate to **Site Configuration** > **Build & Deploy**. +- Under **Build Settings** > **Build Status**, find **Stopped Builds**. +- Click **Save**. + +##### Site Details + +- Go to **Site Configuration** > **Site Details**. +- Copy the `NETLIFY_SITE_ID`. This will be useful when setting up the GitHub environment. + +### Github Actions +Two workflows take care of deploying the Realm app to both `production` and `staging` environments, defined in `realm-production.yml` and `realm-staging.yml`. Each workflow looks for environment variables defined in a GitHub Environment named `production` and `staging`. + +These environments must contain the following variables: +``` +GATSBY_REALM_APP_ID= +REALM_API_PRIVATE_KEY= +REALM_API_PUBLIC_KEY= +``` +To get your Public and Private API Key, follow these [instructions](https://www.mongodb.com/docs/atlas/configure-api-access/#std-label-create-org-api-key). + +### Deployment Workflows on GitHub Actions + +We have integrated our testing and deployment processes with GitHub Actions. There are three primary workflows for deployment: Deploy Previews, Staging, and Production. The goal of these workflows is to continuously test and integrate changes in pull requests across environments. + +#### 1) Deploy Previews Workflow + +- **File:** [/.github/workflows/preview.yml](/.github/workflows/preview.yml) +- **Trigger:** This workflow is activated for pushes to pull requests that target the `staging` branch. +- **Process:** Executes both the integration tests and deploys the application to Netlify. +- **Post-Deployment:** Upon a successful deployment, the workflow automatically posts a comment on the pull request. This comment includes a link to the Netlify preview of the changes and a link to the Netlify deploy log. +- **Environment:** This workflow uses the `staging` GitHub environment. + +#### 2) Staging Workflow (WIP) + +- **Trigger:** Runs only on pushes to the `staging` branch. +- **Process:** Executes both the integration tests and deploys to Netlify. +- **Deployment Criteria:** If the tests fail, no deployment will be carried out. +- **Environment:** This workflow uses the `staging` GitHub environment. + +#### 3) Production Workflow (WIP) + +- **Trigger:** Runs only on pushes to the `main` branch. +- **Process:** Executes both the integration tests and deploys to Netlify. +- **Deployment Criteria:** If the tests fail, no deployment will be carried out. +- **Environment:** This workflow uses the `production` GitHub environment. + +### GitHub Environment Configuration + +All three workflows share a common set of environment variables, which need to be defined for each environment. (Currently, we have only two environments: `staging` and `production`.) These variables are categorized into secrets and standard variables, and are accessed via GitHub actions as such. + +#### Secrets + +- `ALGOLIA_ADMIN_KEY` +- `CLOUDFLARE_R2_ACCESS_KEY_ID` +- `CLOUDFLARE_R2_ACCOUNT_ID` +- `CLOUDFLARE_R2_BUCKET_NAME` +- `CLOUDFLARE_R2_SECRET_ACCESS_KEY` +- `CYPRESS_RECORD_KEY` +- `E2E_ADMIN_PASSWORD` +- `E2E_ADMIN_USERNAME` +- `GOOGLE_TRANSLATE_API_KEY` +- `MONGODB_CONNECTION_STRING` +- `MONGODB_MIGRATIONS_CONNECTION_STRING` +- `MONGODB_REPLICA_SET` +- `MONGODB_TRANSLATIONS_CONNECTION_STRING` +- `NETLIFY_AUTH_TOKEN` +- `PRISMIC_ACCESS_TOKEN` +- `REALM_API_PRIVATE_KEY` +- `REALM_GRAPHQL_API_KEY` +- `REALM_API_PUBLIC_KEY` +- `GATSBY_ROLLBAR_TOKEN` + +#### Variables + +- `CYPRESS_PROJECT_ID` +- `GATSBY_ALGOLIA_APP_ID` +- `GATSBY_ALGOLIA_SEARCH_KEY` +- `GATSBY_AVAILABLE_LANGUAGES` +- `GATSBY_CLOUDFLARE_R2_PUBLIC_BUCKET_URL` +- `GATSBY_PRISMIC_REPO_NAME` +- `GATSBY_REALM_APP_ID` +- `NETLIFY_SITE_ID` +- `REALM_API_APP_ID` +- `REALM_API_GROUP_ID` + +### Testing + +For integration testing, we use Cypress. You can run the desktop app continuously as part of your development environment or run it on demand in headless mode. + +First, add two new environment variables: + +``` +E2E_ADMIN_USERNAME= +E2E_ADMIN_PASSWORD= +``` +As their names imply, they should be an existing user's credentials with the `admin` role. + +To use the desktop version, run: +``` +npm run test:e2e +``` + +And to run it in continuous integration (headless) mode: +``` +npm run test:e2e:ci +``` + ## Adding new Taxonomies ### To add new taxonomies, follow these steps: @@ -397,10 +727,38 @@ Let's say you want to add the `CTECH` Taxonomy. Restarting Gatsby should make the new taxonomy available on the citation pages, so you can visit /cite/1 to see a form for editing the taxonomy. Please note that you will need to be logged in to a user account on the application to see the form. +## Database Migrations +Migration files are stored in the `/site/gatsby-size/migrations` folder and their executions are logged in the `migrations` collection. + +### Configuration +Please add a connection string with read/write permissions to the mongo database: +``` +MONGODB_MIGRATIONS_CONNECTION_STRING= +``` +### Execution +Migrations run automatically as part of Gatsby's `onPreBootstrap` event, but it is also possible to run them from the command line for debugging or testing purposes: + +To run all pending migrations: +``` +node migrator up +``` +To run a specific migration: +``` +node migrator up --name 2022.02.18T16.29.14.increment-report-number.js +``` +### Adding a new migration +To add a new migration, execute the following command and define the `up` and `down` processes as pertinent. +``` +node migrator create --name increment-report-number.js --folder migrations +``` + +Execution is taken care of by the [umzug](https://github.com/sequelize/umzug) package. Please refer to its documentation for more information. ## Public GraphQL endpoint +The site exposes a read-only GraphQL endpoint at `/api/graphql`, which is a reflection of the Realm's auto-generated endpoint. -You can check the endpoint here: [https://incidentdatabase.ai/api/graphql](https://incidentdatabase.ai/api/graphql). +### Accessing the endpoint +You can check the endpoint [here](https://cloud.hasura.io/public/graphiql?endpoint=https%3A%2F%2Fincidentdatabase.ai%2Fapi%2Fgraphql) ### Sample request @@ -450,6 +808,38 @@ In addition to that, you have to add your Netlify site URL to the allowed origin 5. Click `Save Draft` 6. Deploy draft +## Social Networks login integration + +To enable social network login, you will need to add the following configuration to your Atlas App Service. + +Add this secret value to your Atlas App Service following the instructions in the [Atlas App Services documentation](https://www.mongodb.com/docs/atlas/app-services/values-and-secrets/define-and-manage-secrets/). + +``` +facebookAppSecret = [Facebook App Secret, see comment below for more information] +``` + +- To get the Facebook App Secret you should go to the [Facebook Developer Portal](https://developers.facebook.com/apps/), and click on your app > Settings > Basic. + +On Facebook Authentication settings, set the "Client ID" with the Facebook App Id. To get the Facebook App ID you should go to the [Facebook Developer Portal](https://developers.facebook.com/apps/), and check your app. + +"Redirect URIs" is the URL that the user will be redirected to after successfully authenticating with Facebook or Google. It should point to `/logincallback` page. For Production the URI is `https://incidentdatabase.ai/logincallback`, for Staging the URI is `https://staging-aiid.netlify.app/logincallback` + + +About Facebook Authentication instructions: https://www.mongodb.com/docs/realm/web/authenticate/#facebook-authentication + +### Error logging + +This project uses [Rollbar](https://rollbar.com) for error logging for the whole site, including background processes. + +To log the errors a Realm secret value should be set: +``` +rollbarAccessToken: [The access token value from your Rollbar account > Projects > Your project > Project Access Tokens > post_server_item] +``` +In addition to that, this env variable should be set as well: +``` +GATSBY_ROLLBAR_TOKEN: [The access token value from your Rollbar account > Projects > Your project > Project Access Tokens > post_server_item] +``` + ### Restoring Production database to Staging There is a GitHub Workflow "Restore Prod DB into Staging" that can be triggered manually to dump and restore Production database into Staging database (both `aiidprod` and `translations` databases) diff --git a/site/gatsby-site/README.md b/site/gatsby-site/README.md index 52ed1cb883..8c73bd7cfe 100644 --- a/site/gatsby-site/README.md +++ b/site/gatsby-site/README.md @@ -2,7 +2,7 @@ Once you have cloned the repository, to set up a local development environment for the AIID project, follow these steps: -### 1. **Navigate to the Gatsby Site Directory and Install Dependencies** +1. **Navigate to the Gatsby Site Directory** Open your terminal and navigate to the `site/gatsby-site` directory: @@ -10,15 +10,17 @@ Once you have cloned the repository, to set up a local development environment f cd site/gatsby-site ``` +2. **Install Dependencies** + Run the following command to install all necessary dependencies: ```bash npm install ``` -### 2. **Configure Environment Variables** +3. **Configure Environment Variables** -Create a `.env` file in the root of the `gatsby-site` directory. Add the following environment variables to the file, replacing the placeholders with your actual credentials: + Create a `.env` file in the root of the `gatsby-site` directory. Add the following environment variables to the file, replacing the placeholders with your actual credentials: ```env REALM_API_APP_ID= # Application ID for MongoDB Realm API @@ -51,97 +53,9 @@ Create a `.env` file in the root of the `gatsby-site` directory. Add the followi SITE_URL=http://localhost:8000 ``` -# Atlas App Services (formerly Realm) - -REALM_API_APP_ID= # Application ID for MongoDB Realm API -REALM_API_GROUP_ID= # Group ID for MongoDB Realm API -REALM_API_PRIVATE_KEY= # Private key for accessing the MongoDB Realm API -REALM_API_PUBLIC_KEY= # Public key for accessing the MongoDB Realm API -REALM_GRAPHQL_API_KEY= # API key for accessing the Realm GraphQL API -REALM_APP_ID= # App ID used to access MongoDB Realm services -GATSBY_REALM_APP_ID= # Application ID used in the Gatsby frontend for MongoDB Realm, same as REALM_APP_ID - - -# Mongo database - -API_MONGODB_CONNECTION_STRING=mongodb://127.0.0.1:4110 # MongoDB connection string -MONGODB_CONNECTION_STRING=mongodb://127.0.0.1:4110 # MongoDB connection string -MONGODB_REPLICA_SET=mongodb://127.0.0.1:4110 # Name of the MongoDB replica set for high availability -MONGODB_TRANSLATIONS_CONNECTION_STRING=mongodb://127.0.0.1:4110 # MongoDB connection string for the translations database - - -# Rollbar - -ROLLBAR_POST_SERVER_ITEM_ACCESS_TOKEN=dummy # Token for sending error reports to Rollbar from the server -GATSBY_ROLLBAR_TOKEN=dummy # Token for Rollbar error tracking in the Gatsby frontend - - -# Algolia - -GATSBY_ALGOLIA_APP_ID=JD5JCVZEVS # Application ID for Algolia search integration in the Gatsby app -GATSBY_ALGOLIA_SEARCH_KEY=c5e99d93261645721a1765fe4414389c # Public search key for Algolia, used in the Gatsby frontend -ALGOLIA_ADMIN_KEY= # Admin key for managing the Algolia index - - -# Translations - -GATSBY_AVAILABLE_LANGUAGES=en,es # List of languages available for the Gatsby app (e.g., en, es, fr) -GOOGLE_TRANSLATE_API_KEY= # API key for accessing Google Translate services - - -# Cloudflare R2 storage - -CLOUDFLARE_R2_ACCOUNT_ID= # Account ID for Cloudflare R2 storage service -CLOUDFLARE_R2_ACCESS_KEY_ID= # Access key ID for Cloudflare R2 storage -CLOUDFLARE_R2_SECRET_ACCESS_KEY= # Secret access key for Cloudflare R2 storage -CLOUDFLARE_R2_BUCKET_NAME= # Name of the Cloudflare R2 bucket for storage -GATSBY_CLOUDFLARE_R2_PUBLIC_BUCKET_URL= # Public URL for accessing the Cloudflare R2 bucket from the Gatsby app - - -# Email notifications - -MAILERSEND_API_KEY=dummy # API key for MailerSend email service or dummy value if you don't plan to send emails -NOTIFICATIONS_SENDER_NAME=AIID Notifications # Name of the sender for email notifications -NOTIFICATIONS_SENDER=notifications@incidentdatabase.ai # Email address of the sender for email notifications - - -# Prismic - -PRISMIC_ACCESS_TOKEN= -GATSBY_PRISMIC_REPO_NAME=aiidstaging - + Ensure that each variable is set correctly to match your development environment's requirements. -# Other - -GOOGLE_MAPS_API_KEY= # API key for accessing Google Maps services -SITE_URL=http://localhost:8000 - -``` - - - -#### MailerSend environment variables - -Unless the feature you are working on requires sending email notifications, you can leave these variables with the `dummy` value. Otherwise, follow the instructions here: [MailerSend setup](#mailersend-setup) - - -#### Cloudflare R2 storage environment variables - -Unless the feature you are working on requires Cloudflare R2 storage, you can leave these variables empty. Otherwise, follow the instructions here: [Cloudflare R2 storage](#Cloudflare-R2-storage) - - -#### Additional Configuration - -When building the site, some steps can take a while to run. This can be inconvenient when you are working on a feature unrelated to the steps taking the most time in the build process. To avoid this problem, you can set the environment variable `SKIP_PAGE_CREATOR` to a comma-separated list of page-creator functions found in [`gatsby-node`](https://github.com/responsible-ai-collaborative/aiid/blob/main/site/gatsby-site/gatsby-node.js) that should be skipped. These include: `createMdxPages`, `createCitationPages`, `createWordCountsPages`, `createBackupsPage`, `createTaxonomyPages`, `createDownloadIndexPage`, `createDuplicatePages`, `createTsneVisualizationPage`, and `createEntitiesPages`. For instance, to run a development build skipping the creation of the TSNE (spatial) visualization and citation pages, you would run: - -```bash -SKIP_PAGE_CREATOR=createTsneVisualizationPage,createCitiationPages gatsby develop -``` - -In general, skipping the TSNE visualization has the most significant reduction in build time. - - -### 3. **Start a Memory Mongo Instance** +4. **Start a Memory Mongo Instance** To start a memory MongoDB instance, run the following command: @@ -149,7 +63,7 @@ In general, skipping the TSNE visualization has the most significant reduction i npm run start:memory-mongo ``` -### 4. **Start Gatsby and Netlify Development Server** +5. **Start Gatsby and Netlify Development Server** Finally, start the Gatsby development server along with Netlify dev using: @@ -159,58 +73,15 @@ In general, skipping the TSNE visualization has the most significant reduction i Follow these steps to get your local environment up and running for development with the AIID project. Make sure to replace the placeholder values in the `.env` file with your actual credentials to ensure proper functionality. - -## AIID Frontend - -### Overview - -The AIID frontend is built using Gatsby, a static site generator that allows for fast, optimized websites. The frontend is designed to provide a user-friendly interface for browsing incidents, submitting new incidents, and viewing incident details. - -### Tailwind CSS & Flowbite - -This project uses [Tailwind CSS](https://tailwindcss.com/) framework with its class syntax. -More specifically, we base our components on [Flowbite React](https://flowbite-react.com/) and [Flowbite](https://flowbite.com/) which is built on top of TailwindCSS. - -### Steps for developing - -In order to keep styling consistency on the site, we follow a set of steps when developing. This is also to make the development process more agile and simple. - -1. Develop your component using [Flowbite React components](https://flowbite-react.com/) -2. If your components is not fully contemplated by Flowbite react, check [Flowbite components](https://flowbite.com/#components) and use the provided HTMLs. -3. If you need to improve styling, use only Tailwind CSS classes. - -**Examples** -If you want to place a new [Flowbite React button](https://flowbite-react.com/buttons): - -```javascript -import { Button } from 'flowbite-react'; - -const YourComponent = () => { - return -} - -``` - -If you want to customize a [Flowbite button](https://flowbite.com/docs/components/buttons/): - -```javascript -const YourComponent = () => { - return -} -``` - ## AIID API ### Overview The AIID API is built to facilitate interactions with the AI Incident Database. It is implemented as a collection of serverless functions that are composed ("stitched") into a singular GraphQL endpoint. -The GraphQL API can be accessed and tested in two ways: +1. **Access the Apollo Explorer** -- Production endpoint: `https://incidentdatabase.ai/api/graphql` -- Local development endpoint: `http://localhost:8000/api/graphql` - -Both URLs support interactive exploration through Apollo Explorer, allowing you to visually build and test GraphQL queries and mutations. + Navigate to `http://localhost:8000/graphql` in your web browser. The Apollo Explorer instance should be displayed, allowing you to introspect and run queries against the API. ### Performing Queries @@ -225,6 +96,8 @@ query { } ``` +### Expected Response + The query should return a response similar to this: ```json @@ -255,6 +128,16 @@ The API is contained within the `server` directory. The following folders are pr - **`local.ts`**: Handles the local GraphQL schema, where migrated fields from the remote schema are added. These fields are ignored in `remote.ts`. - **`schema.ts`**: Combines the remote and local schemas into the final schema using **schema stitching** from GraphQL Tools. - **`netlify/functions/graphql.ts`**: Sets up the **GraphQL server** and exposes it as a **Netlify function**, loading the schema from `schema.ts`. +- +### Running Tests + +To run Jest tests locally: + +```sh +npm run test:api +``` + +It is recommended to install the Jest extension for VS Code to enhance the testing experience. ### Running Code Generation @@ -366,295 +249,4 @@ And finally, as part of the site build process, we processed all pending notific "incident_id": 374, "processed": false } - ``` - - -## Testing - -### E2E Testing - -We use Playwright for end-to-end testing. You can either run the tests against the local development environment or against a local build of the site. - -#### Local Development Environment - -First make sure you you've followed the steps to set up the local development environment for the AIID project. - -Make sure you have a local mongo instance running: - -```sh -npm run start:memory-mongo -``` - -Then, start the Gatsby and Netlify development server: - -```sh -npm run start -``` - -Finally, run the Playwright tests: - -```sh -npm run test:e2e -``` - -Running tests this way will allow you to make changes to the site and see the results of the tests in real time. - -#### Local Build - -First, start a memory MongoDB instance: - -```sh -npm run start:memory-mongo -``` - -Then, build the Gatsby site: - -```sh -npm run build -``` - -Then, start the Gatsby and Netlify development server: - -```sh -npm run start -``` - -Finally, run the Playwright tests: - -```sh -npm run test:e2e -``` - -Running tests this way will allow you to test the site as it would be deployed to production. - -#### VS Code Extension - -It is recommended to install the [Playwright extension](https://marketplace.visualstudio.com/items?itemName=ms-playwright.playwright) for VS Code to enhance the testing experience. It allows you to run tests directly from the editor, view test results, and debug tests. - -> [!NOTE] -> Make sure to have `/site/gatsby-site` as the root folder in vscode to run the tests. - - -### API - -We use Jest for API testing. It does not have any dependencies on the local development environment, so you can run the tests at any time: - -```sh -npm run test:api -``` - -#### VS Code Extension - -It is recommended to install the [Jest extension](https://marketplace.visualstudio.com/items?itemName=Orta.vscode-jest) for VS Code to enhance the testing experience. It allows you to run tests directly from the editor, view test results, and debug tests. - -> [!NOTE] -> Make sure to have `/site/gatsby-site` as the root folder in vscode to run the tests. - -## Style guide - -1. `ESLint` and `Prettier` have been configured to help enforcing code styles. Configuration details can be found in `.eslintrc.json` and `.prettierrc`. -2. [Husky](https://github.com/typicode/husky#readme) and [lint-staged](https://github.com/okonet/lint-staged) are installed and `pre-commit` hook added to check lint/prettier issues on staged files and fix them automatically before making commit. -3. `format` and `lint` scripts can be used manually to fix style issues. - - -## Database Migrations -Migration files are stored in the `/site/gatsby-size/migrations` folder and their executions are logged in the `migrations` collection. - -### Configuration -Please add a connection string with read/write permissions to the mongo database: -``` -MONGODB_MIGRATIONS_CONNECTION_STRING= -``` -### Execution -Migrations run automatically as part of Gatsby's `onPreBootstrap` event, but it is also possible to run them from the command line for debugging or testing purposes: - -To run all pending migrations: -``` - -### Netlify - -Netlify is used to host the AIID frontend and API. - -#### Prerequisites - -- Ensure you have a GitHub account and your project is already pushed to a repository. -- Make sure you have a Netlify account. If not, sign up at [Netlify](https://www.netlify.com/). - -#### Steps to Set Up - -##### 1. Add New Site - -- Go to your Netlify dashboard. -- Click on **Add New Site**. - -##### 2. Import Existing Project - -- Choose **Import Existing Project**. - -##### 3. Deploy with GitHub - -- Select **Deploy with GitHub** to connect your GitHub account. - -##### 4. Select Repository - -- Choose the repository where your project is located. - -##### 5. Configure Deployment - -- Under **Branch to Deploy**, select `main`. This setting doesn't matter for now. -- Leave all other settings as default. -- Click on **Deploy Site**. - -##### 6. Site Configuration - -###### Build and Deploy - -- Navigate to **Site Configuration** > **Build & Deploy**. -- Under **Build Settings** > **Build Status**, find **Stopped Builds**. -- Click **Save**. - -###### Site Details - -- Go to **Site Configuration** > **Site Details**. -- Copy the `NETLIFY_SITE_ID`. This will be useful when setting up the GitHub environment. - -#### 7. Personal Access Token - -Go to your Netlify account settings and create a new personal access token. This token will be used to authenticate the GitHub Actions workflows. `NETLIFY_AUTH_TOKEN` should be set as a GitHub secret. -### Prismic setup - -This project uses Prismic to fetch page content. You can still run the project without setting a Prismic account. - -1. Sign up for a new [Prismic](https://prismic.io/) account or log in to your account if you already have one -2. In `Create a new repository` section choose `Something else` -3. Give your repository a name and choose `gatsby` in the technology dropdown -4. Choose your plan (if you only need one user, the free plan is enough) -5. Click `Create repository` -6. Create a new token in Settings > API & Security > Content API tab > Change Repository security to `Private API – Require an access token for any request` > Create new app > Permanent access tokens > Save value for later - -#### Adding the Prismic content types - -##### Prismic Custom Types -You can find the list of all custom types in the folder `custom_types` - -##### How to create a new Custom Type -1. From the prismic left menu click `Custom Types` -2. Click `Create new custom type` -3. Give it a name (name of the json in custom_types folder) -4. Click `JSON editor` -5. Paste the JSON content from the predefined custom types inside the json -6. Click `Save` - -##### Adding Prismic documents - -1. On the Prismic dashboard left menu click `Documents` -2. Click `Create new` -3. Fill in all the mandatory fields -4. Click `Save` -5. Keep in mind that the new content won't be available on your page until you Publish it. -6. In order to publish it, click `Publish` - -#### Prismic & Netlify Hook integration - -In order for your recently published Prismic content to be available on your page, a Netlify build needs to be triggered. -In order to do this, you need to create a Netlify Build Hook. - -#### Prismic environment variables - -Add the following environment variable on Netlify: -`GATSBY_PRISMIC_REPO_NAME=[name_of_your_repository]` (step 3 from Prismic Setup section) -`PRISMIC_ACCESS_TOKEN=[you_prismic_access_token]` (step 6 from Prismic Setup section) - -#### Create Prismic/Netlify Hook -1. Login to your Netlify -2. Go to `Deploys` -3. Go to `Deploy settings` -4. Scroll to `Build Hooks` -5. Click `Add build hook` -6. Give it a name and assign a branch -7. Click save -8. Copy the generated URL -9. Go to your Prismic repository -10. Go to `Settings` > `Webhooks` -11. Create a new webhook and paste the URL in the URL field -12. In `Triggers` select `A document is published` and `A document is unpublished` -13. Click `Add this webhook` - -### MailerSend setup - -Create an account on [MailerSend](https://www.mailersend.com/) and generate and API Key. This key should be added to the `.env` file as `MAILERSEND_API_KEY`. - -### Cloudflare R2 storage - -Create an account on [Cloudflare](https://www.cloudflare.com/) and create a new R2 storage bucket. - -### Deployment Workflows on GitHub Actions - -We have integrated our testing and deployment processes with GitHub Actions. There are three primary workflows for deployment: Deploy Previews, Staging, and Production. The goal of these workflows is to continuously test and integrate changes in pull requests across environments. - -##### 1. Deploy Previews Workflow - -- **File:** [/.github/workflows/preview.yml](/.github/workflows/preview.yml) -- **Trigger:** This workflow is activated for pushes to pull requests that target the `staging` branch. -- **Process:** Executes both the integration tests and deploys the application to Netlify. -- **Post-Deployment:** Upon a successful deployment, the workflow automatically posts a comment on the pull request. This comment includes a link to the Netlify preview of the changes and a link to the Netlify deploy log. -- **Environment:** This workflow uses the `staging` GitHub environment. - -##### 2. Staging Workflow - -- **Trigger:** Runs only on pushes to the `staging` branch. -- **Process:** Executes both the integration tests and deploys to Netlify. -- **Deployment Criteria:** If the tests fail, no deployment will be carried out. -- **Environment:** This workflow uses the `staging` GitHub environment. - -##### 3. Production Workflow - -- **Trigger:** Runs only on pushes to the `main` branch. -- **Process:** Executes both the integration tests and deploys to Netlify. -- **Deployment Criteria:** If the tests fail, no deployment will be carried out. -- **Environment:** This workflow uses the `production` GitHub environment. - -#### GitHub Environment Configuration - -All three workflows share a common set of environment variables, which need to be defined for each environment. (Currently, we have only two environments: `staging` and `production`.) These variables are categorized into secrets and standard variables, and are accessed via GitHub actions as such. - -#### Secrets - -- `ALGOLIA_ADMIN_KEY` -- `CLOUDFLARE_R2_ACCESS_KEY_ID` -- `CLOUDFLARE_R2_ACCOUNT_ID` -- `CLOUDFLARE_R2_BUCKET_NAME` -- `CLOUDFLARE_R2_SECRET_ACCESS_KEY` -- `CYPRESS_RECORD_KEY` -- `E2E_ADMIN_PASSWORD` -- `E2E_ADMIN_USERNAME` -- `GOOGLE_TRANSLATE_API_KEY` -- `MONGODB_CONNECTION_STRING` -- `MONGODB_MIGRATIONS_CONNECTION_STRING` -- `MONGODB_REPLICA_SET` -- `MONGODB_TRANSLATIONS_CONNECTION_STRING` -- `NETLIFY_AUTH_TOKEN` -- `PRISMIC_ACCESS_TOKEN` -- `REALM_API_PRIVATE_KEY` -- `REALM_GRAPHQL_API_KEY` -- `REALM_API_PUBLIC_KEY` -- `GATSBY_ROLLBAR_TOKEN` - -(outdated) - -#### Variables - -- `CYPRESS_PROJECT_ID` -- `GATSBY_ALGOLIA_APP_ID` -- `GATSBY_ALGOLIA_SEARCH_KEY` -- `GATSBY_AVAILABLE_LANGUAGES` -- `GATSBY_CLOUDFLARE_R2_PUBLIC_BUCKET_URL` -- `GATSBY_PRISMIC_REPO_NAME` -- `GATSBY_REALM_APP_ID` -- `NETLIFY_SITE_ID` -- `REALM_API_APP_ID` -- `REALM_API_GROUP_ID` - -(outdated) - - + ``` \ No newline at end of file From 4dde1993546eeba3f0a265d8ebd2f0963856741b Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 24 Dec 2024 15:30:49 -0300 Subject: [PATCH 87/99] Cleanup tsconfigs --- site/gatsby-site/playwright/memory-mongo.ts | 2 +- site/gatsby-site/playwright/utils.ts | 2 + site/gatsby-site/server/tsconfig.json | 15 ---- site/gatsby-site/tsconfig.json | 87 --------------------- 4 files changed, 3 insertions(+), 103 deletions(-) delete mode 100644 site/gatsby-site/server/tsconfig.json diff --git a/site/gatsby-site/playwright/memory-mongo.ts b/site/gatsby-site/playwright/memory-mongo.ts index 10ab056c3b..79b9289a88 100644 --- a/site/gatsby-site/playwright/memory-mongo.ts +++ b/site/gatsby-site/playwright/memory-mongo.ts @@ -111,7 +111,7 @@ export const execute = async (fn: (client: MongoClient) => Promise) => { // command line support -let instance: MongoMemoryServer = null; +let instance: MongoMemoryServer | null = null; async function start() { diff --git a/site/gatsby-site/playwright/utils.ts b/site/gatsby-site/playwright/utils.ts index 0391f610e5..bbdb8d4795 100644 --- a/site/gatsby-site/playwright/utils.ts +++ b/site/gatsby-site/playwright/utils.ts @@ -263,7 +263,9 @@ export const getApolloClient = () => { uri: `http://localhost:8000/api/graphql`, fetch: async (uri, options) => { + // @ts-ignore options.headers.email = config.E2E_ADMIN_USERNAME!; + // @ts-ignore options.headers.password = config.E2E_ADMIN_PASSWORD!; return fetch(uri, options); diff --git a/site/gatsby-site/server/tsconfig.json b/site/gatsby-site/server/tsconfig.json deleted file mode 100644 index 37fa66f4ca..0000000000 --- a/site/gatsby-site/server/tsconfig.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "extends": "../tsconfig.json", - "compilerOptions": { - "noEmit": true, - // be explicit about types included - // to avoid clashing with Jest types - "types": [ - "jest" - ], - }, - "include": [ - "../node_modules/jest", - "./**/*.ts" - ] -} \ No newline at end of file diff --git a/site/gatsby-site/tsconfig.json b/site/gatsby-site/tsconfig.json index 33ac7e7112..9e93d1b224 100644 --- a/site/gatsby-site/tsconfig.json +++ b/site/gatsby-site/tsconfig.json @@ -1,102 +1,15 @@ { "compilerOptions": { - /* Visit https://aka.ms/tsconfig to read more about this file */ - /* Projects */ - // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ - // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ - // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ - // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ - // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ - // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ - /* Language and Environment */ "target": "ES2017", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ - // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ "jsx": "react", /* Specify what JSX code is generated. */ - // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ - // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ - // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ - // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ - // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ - // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ - // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ - // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ - // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ - /* Modules */ "module": "ES2020", /* Specify what module code is generated. */ - // "rootDir": "./", /* Specify the root folder within your source files. */ "moduleResolution": "bundler", /* Specify how TypeScript looks up a file from a given module specifier. */ - // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ "paths": { "contexts/*": ["./src/contexts/*"] }, /* Specify a set of entries that re-map imports to additional lookup locations. */ - // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ - // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ /* Specify type package names to be included without being referenced in a source file. */ - // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ - // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ - // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ - // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ - // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ - // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ - // "resolveJsonModule": true, /* Enable importing .json files. */ - // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ - // "noResolve": true, /* Disallow 'import's, 'require's or ''s from expanding the number of files TypeScript should add to a project. */ - /* JavaScript Support */ - // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ - // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ - // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ - /* Emit */ - // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ - // "declarationMap": true, /* Create sourcemaps for d.ts files. */ - // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ - // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ - // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ - // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ - // "outDir": "./", /* Specify an output folder for all emitted files. */ - // "removeComments": true, /* Disable emitting comments. */ - // "noEmit": true, /* Disable emitting files from a compilation. */ - // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ - // "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */ - // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ - // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ - // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ - // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ - // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ - // "newLine": "crlf", /* Set the newline character for emitting files. */ - // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ - // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ - // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ - // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ - // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ - // "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */ - /* Interop Constraints */ - // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ - // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ - // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ - /* Type Checking */ "strict": true, /* Enable all strict type-checking options. */ - // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ - // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ - // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ - // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ - // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ - // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ - // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ - // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ - // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ - // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ - // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ - // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ - // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ - // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ - // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ - // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ - // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ - // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ - /* Completeness */ - // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ "skipLibCheck": true, /* Skip type checking all .d.ts files. */ "allowJs": true }, From 2908019e6ee60aa0f13e0ab48289fb41246d6da5 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 24 Dec 2024 16:16:52 -0300 Subject: [PATCH 88/99] Add test to session callback --- .../server/tests/next-auth.spec.ts | 103 ++++++++++++++++-- 1 file changed, 95 insertions(+), 8 deletions(-) diff --git a/site/gatsby-site/server/tests/next-auth.spec.ts b/site/gatsby-site/server/tests/next-auth.spec.ts index 868f577ec1..65680521d6 100644 --- a/site/gatsby-site/server/tests/next-auth.spec.ts +++ b/site/gatsby-site/server/tests/next-auth.spec.ts @@ -3,8 +3,9 @@ import { HandlerEvent, HandlerContext } from '@netlify/functions' import * as emails from '../emails'; import { getCollection, seedFixture } from './utils'; import config from '../config'; +import { ObjectId } from 'bson'; -function mockAuthEvent(operation: string, email: string, callbackUrl: string): Partial { +function mockAuthEmailEvent(operation: string, email: string, callbackUrl: string,): Partial { const encodedEmail = encodeURIComponent(email); const encodedCallbackUrl = encodeURIComponent(callbackUrl); @@ -55,6 +56,32 @@ function mockMagicLinkEvent(email: string, token: string, callbackUrl: string): } } +function mockAuthSessionEvent(sessionToken: string): Partial { + + const encodedCallbackUrl = encodeURIComponent('/'); + + return { + path: "/api/auth/session", + httpMethod: "GET", + queryStringParameters: {}, + headers: { + cookie: `next-auth.session-token=${sessionToken}; next-auth.callback-url=${encodedCallbackUrl}; next-auth.csrf-token=3fc5b5ba3bb4457090ea32b335e69294637dca5a9473dcc669a4ed00cdadf199%7C1ecd6e1064b23beb6a1e215165a586be0a08bed2d588cc0a440ab3e43c4d2e87`, + connection: "close", + origin: "http://localhost:8000", + "content-length": "181", + "content-type": "application/x-www-form-urlencoded", + referer: "http://localhost:8000/signup/", + "accept-encoding": "gzip, deflate, br, zstd", + "accept-language": "en-US,en;q=0.5", + accept: "*/*", + "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10.15; rv:134.0) Gecko/20100101 Firefox/134.0", + host: "localhost:8000", + }, + body: ``, + } +} + + describe('Auth', () => { @@ -71,7 +98,7 @@ describe('Auth', () => { const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); - const event = mockAuthEvent('login', 'test.user@incidentdatabase.ai', '/'); + const event = mockAuthEmailEvent('login', 'test.user@incidentdatabase.ai', '/'); const response = await handler(event as HandlerEvent, {} as HandlerContext); @@ -103,7 +130,7 @@ describe('Auth', () => { }); const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); - const event = mockAuthEvent('login', email, '/'); + const event = mockAuthEmailEvent('login', email, '/'); const response = await handler(event as HandlerEvent, {} as HandlerContext); @@ -154,7 +181,7 @@ describe('Auth', () => { }); const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); - const event = mockAuthEvent('login', email, callbackUrl); + const event = mockAuthEmailEvent('login', email, callbackUrl); await handler(event as HandlerEvent, {} as HandlerContext); @@ -188,7 +215,7 @@ describe('Auth', () => { }); const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); - const event = mockAuthEvent('signup', email, '/'); + const event = mockAuthEmailEvent('signup', email, '/'); const response = await handler(event as HandlerEvent, {} as HandlerContext); @@ -235,7 +262,7 @@ describe('Auth', () => { }); const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); - const event = mockAuthEvent('signup', email, '/'); + const event = mockAuthEmailEvent('signup', email, '/'); const response = await handler(event as HandlerEvent, {} as HandlerContext); @@ -278,7 +305,7 @@ describe('Auth', () => { }); const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); - const event = mockAuthEvent('signup', email, callbackUrl); + const event = mockAuthEmailEvent('signup', email, callbackUrl); await handler(event as HandlerEvent, {} as HandlerContext); @@ -298,7 +325,7 @@ describe('Auth', () => { }); - describe('Callback', () => { + describe('Callbacks', () => { test('Should verify the email and create a user with the appropriate userId', async () => { @@ -343,5 +370,65 @@ describe('Auth', () => { roles: ['subscriber'], }]); }); + + test('Should return enriched session data when hitting session endpoint', async () => { + const userId = new ObjectId('5f9f1b3b1c9d440000000000'); + const email = 'test.user@incidentdatabase.ai'; + const sessionToken = 'f59bedfb-f318-4b55-ad29-9887abba8f06' + const expires = new Date(Date.now() + 24 * 60 * 60 * 1000); + + await seedFixture({ + auth: { + users: [{ + _id: userId, + email, + emailVerified: new Date(), + }], + sessions: [{ + userId, + sessionToken, + expires, + }] + }, + customData: { + users: [{ + userId: userId.toHexString(), + roles: ['subscriber', 'editor'], + first_name: 'Test', + last_name: 'User' + }] + } + }); + + const event = mockAuthSessionEvent(sessionToken); + + const response = await handler(event as HandlerEvent, {} as HandlerContext); + + expect(response).toMatchObject({ + body: JSON.stringify({ + user: { + email, + id: userId, + roles: ['subscriber', 'editor'], + first_name: 'Test', + last_name: 'User' + }, + expires: expires.toISOString(), + }), + }); + }); + + test('Should return null session when no valid session exists', async () => { + + const event = mockAuthSessionEvent('fake-session-token'); + + const response = await handler(event as HandlerEvent, {} as HandlerContext); + + expect(response).toMatchObject({ + body: `{}`, + statusCode: 200, + }); + }); }); + }); From a15093ea30d0b04ab6a089fc72250ed1acc2841d Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 24 Dec 2024 18:45:23 -0300 Subject: [PATCH 89/99] Different copy for login and signup --- site/gatsby-site/src/pages/login.js | 2 +- site/gatsby-site/src/pages/signup.js | 2 +- site/gatsby-site/src/pages/verify-request.js | 42 +++++++++++++------- 3 files changed, 30 insertions(+), 16 deletions(-) diff --git a/site/gatsby-site/src/pages/login.js b/site/gatsby-site/src/pages/login.js index 2ae747debd..7621476451 100644 --- a/site/gatsby-site/src/pages/login.js +++ b/site/gatsby-site/src/pages/login.js @@ -36,7 +36,7 @@ const Login = () => { const result = await logIn(email, redirectTo); if (!result.error) { - navigate(`/verify-request/?email=${encodeURIComponent(email)}`); + navigate(`/verify-request`, { state: { email, operation: 'login' } }); } else { throw result?.error; } diff --git a/site/gatsby-site/src/pages/signup.js b/site/gatsby-site/src/pages/signup.js index ed28396bf6..341599f2ec 100644 --- a/site/gatsby-site/src/pages/signup.js +++ b/site/gatsby-site/src/pages/signup.js @@ -42,7 +42,7 @@ const SignUp = () => { const result = await signUp(email, '/account/?askToCompleteProfile=1'); if (!result.error) { - await navigate(`/verify-request/?email=${encodeURIComponent(email)}`); + await navigate(`/verify-request`, { state: { email, operation: 'signup' } }); } else { // TODO: Add more specific error messages addToast({ diff --git a/site/gatsby-site/src/pages/verify-request.js b/site/gatsby-site/src/pages/verify-request.js index 600baf5944..53c7999eff 100644 --- a/site/gatsby-site/src/pages/verify-request.js +++ b/site/gatsby-site/src/pages/verify-request.js @@ -1,15 +1,12 @@ import React from 'react'; import { useUserContext } from 'contexts/UserContext'; import { Trans, useTranslation } from 'react-i18next'; -import { StringParam, useQueryParams } from 'use-query-params'; import HeadContent from 'components/HeadContent'; import { Spinner } from 'flowbite-react'; -const VerifyRequest = () => { +const VerifyRequest = ({ location: { state } }) => { const { user, loading } = useUserContext(); - const [{ email }] = useQueryParams({ email: StringParam }); - if (loading) { return (
@@ -26,16 +23,33 @@ const VerifyRequest = () => { ); } - return ( - <> -

- Check your email -

-

- A sign in link has been sent to {email} -

- - ); + if (state?.operation == 'login') { + return ( + <> +

+ Check your email +

+

+ A sign in link has been sent to {state.email} +

+ + ); + } + + if (state?.operation == 'signup') { + return ( + <> +

+ Check your email +

+

+ A sign up link has been sent to {state.email} +

+ + ); + } + + return null; }; export const Head = (props) => { From d8f5773762f6039f037bda2069019f8190d6ff3d Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 24 Dec 2024 18:56:34 -0300 Subject: [PATCH 90/99] Update test --- site/gatsby-site/playwright/e2e-full/signup.spec.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/site/gatsby-site/playwright/e2e-full/signup.spec.ts b/site/gatsby-site/playwright/e2e-full/signup.spec.ts index 759fa75fb4..37d497cead 100644 --- a/site/gatsby-site/playwright/e2e-full/signup.spec.ts +++ b/site/gatsby-site/playwright/e2e-full/signup.spec.ts @@ -39,7 +39,7 @@ test.describe('Signup', () => { await signupResponse; - await expect(page.getByText('A sign in link has been sent to new.user@test.com')).toBeVisible(); + await expect(page.getByText('A sign up link has been sent to new.user@test.com')).toBeVisible(); }); test('Should display the error toast message if any other sign up error occurs', async ({ page }) => { From e14ec8836416a3bbbb35a71822f1810d8a38b252 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Mon, 27 Jan 2025 18:19:42 -0300 Subject: [PATCH 91/99] Fix tests --- site/gatsby-site/server/tests/entities.spec.ts | 4 ++-- site/gatsby-site/server/tests/notifications.spec.ts | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/site/gatsby-site/server/tests/entities.spec.ts b/site/gatsby-site/server/tests/entities.spec.ts index 201b179dac..bf3e485426 100644 --- a/site/gatsby-site/server/tests/entities.spec.ts +++ b/site/gatsby-site/server/tests/entities.spec.ts @@ -1,6 +1,6 @@ import { expect, jest, it } from '@jest/globals'; import { ApolloServer } from "@apollo/server"; -import { makeRequest, seedFixture, startTestServer } from "./utils"; +import { makeRequest, mockSession, seedFixture, startTestServer } from "./utils"; import * as context from '../context'; describe(`Entities`, () => { @@ -73,7 +73,7 @@ describe(`Entities`, () => { }); - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession("123"); const response = await makeRequest(url, mutationData); diff --git a/site/gatsby-site/server/tests/notifications.spec.ts b/site/gatsby-site/server/tests/notifications.spec.ts index e5da0700c1..ad797eff4c 100644 --- a/site/gatsby-site/server/tests/notifications.spec.ts +++ b/site/gatsby-site/server/tests/notifications.spec.ts @@ -1629,8 +1629,7 @@ describe(`Notifications`, () => { } }); - - jest.spyOn(context, 'verifyToken').mockResolvedValue({ sub: "123" }) + mockSession('123'); // No recipients jest.spyOn(common, 'getUserAdminData').mockResolvedValue(null); From 54788dbb96cfde2bb13322d52c4c3a52e4ee6bbc Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 28 Jan 2025 18:59:32 -0300 Subject: [PATCH 92/99] Use single email send api for auth emails --- site/gatsby-site/nextauth.config.ts | 4 +- site/gatsby-site/server/emails/index.ts | 67 ++++++++++++++++++- .../src/scripts/process-notifications.ts | 21 +++--- 3 files changed, 77 insertions(+), 15 deletions(-) diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts index 11fc51dbc6..8b6efc5983 100644 --- a/site/gatsby-site/nextauth.config.ts +++ b/site/gatsby-site/nextauth.config.ts @@ -35,7 +35,7 @@ export const getAuthConfig = async (req: any): Promise => { if (user) { await sendEmail({ - recipients: [{ email }], + recipient: { email }, subject: 'Login link', templateId: 'Login', dynamicData: { magicLink: url }, @@ -44,7 +44,7 @@ export const getAuthConfig = async (req: any): Promise => { else { await sendEmail({ - recipients: [{ email }], + recipient: { email }, subject: 'Signup link', templateId: 'Signup', dynamicData: { magicLink: url }, diff --git a/site/gatsby-site/server/emails/index.ts b/site/gatsby-site/server/emails/index.ts index 54b4712ed3..2ad28cc5b4 100644 --- a/site/gatsby-site/server/emails/index.ts +++ b/site/gatsby-site/server/emails/index.ts @@ -4,7 +4,7 @@ import templates from "./templates"; import * as reporter from '../reporter'; import assert from "assert"; -interface SendEmailParams { +export interface SendBulkEmailParams { recipients: { email: string; userId?: string; @@ -48,7 +48,7 @@ export const mailersendBulkSend = async (emails: EmailParams[]) => { await mailersend.email.sendBulk(emails); } -export const sendEmail = async ({ recipients, subject, dynamicData, templateId }: SendEmailParams) => { +export const sendBulkEmails = async ({ recipients, subject, dynamicData, templateId }: SendBulkEmailParams) => { const emailTemplateBody = templates[templateId]; @@ -96,3 +96,66 @@ export const sendEmail = async ({ recipients, subject, dynamicData, templateId } throw error; } } + +export const mailersendSingleSend = async (email: EmailParams) => { + + const mailersend = new MailerSend({ + apiKey: config.MAILERSEND_API_KEY, + }); + + assert(email.to.length == 1, 'Email must have exactly one recipient'); + assert(!email.cc, 'Should not use the "cc" field'); + + await mailersend.email.send(email); +} + +export interface SendEmailParams { + recipient: { + email: string; + userId?: string; + }; + subject: string; + dynamicData?: { + magicLink?: string; + }; + templateId: string; +} + +export const sendEmail = async ({ recipient, subject, dynamicData, templateId }: SendEmailParams) => { + + const emailTemplateBody = templates[templateId]; + + if (!emailTemplateBody) { + throw new Error(`Template not found: ${templateId}`); + } + + const personalization = { + email: recipient.email, + data: { + ...dynamicData, + email: recipient.email, + userId: recipient.userId, + siteUrl: config.SITE_URL, + } + } + + const emailParams = new EmailParams() + .setFrom({ email: config.NOTIFICATIONS_SENDER, name: config.NOTIFICATIONS_SENDER_NAME }) + .setTo([new Recipient(recipient.email)]) + .setPersonalization([personalization]) + .setSubject(subject) + .setHtml(emailTemplateBody); + //TODO: add a text version of the email + // .setText("Greetings from the team, you got this message through MailerSend."); + + try { + + await mailersendSingleSend(emailParams); + + } catch (error: any) { + error.message = `[Send Email]: ${error.message}`; + reporter.error(error); + + throw error; + } +} diff --git a/site/gatsby-site/src/scripts/process-notifications.ts b/site/gatsby-site/src/scripts/process-notifications.ts index f707e130c9..530b895d3e 100644 --- a/site/gatsby-site/src/scripts/process-notifications.ts +++ b/site/gatsby-site/src/scripts/process-notifications.ts @@ -3,7 +3,7 @@ import config from "../../server/config"; import { Context, DBEntity, DBIncident, DBNotification, DBReport, DBSubscription } from "../../server/interfaces"; import * as reporter from '../../server/reporter'; import { MongoClient } from "mongodb"; -import { sendEmail } from "../../server/emails"; +import { SendBulkEmailParams, sendBulkEmails } from "../../server/emails"; const usersCache: UserAdminData[] = []; @@ -104,8 +104,8 @@ async function notificationsToNewIncidents(context: Context) { const incident = await incidentsCollection.findOne({ incident_id: pendingNotification.incident_id }); if (incident && recipients.length > 0) { - //Send email notification - const sendEmailParams = { + + const sendEmailParams: SendBulkEmailParams = { recipients, subject: 'New Incident {{incidentId}} was created', dynamicData: { @@ -122,7 +122,7 @@ async function notificationsToNewIncidents(context: Context) { templateId: 'NewIncident' // Template value from function name sufix from "site/realm/functions/config.json" }; - await sendEmail(sendEmailParams); + await sendBulkEmails(sendEmailParams); } @@ -203,7 +203,7 @@ async function notificationsToIncidentUpdates(context: Context) { if (incident && recipients.length > 0) { - const sendEmailParams = { + const sendEmailParams: SendBulkEmailParams = { recipients, subject: 'Incident {{incidentId}} was updated', dynamicData: { @@ -218,7 +218,7 @@ async function notificationsToIncidentUpdates(context: Context) { templateId: newReportNumber ? 'NewReportAddedToAnIncident' : 'IncidentUpdate', }; - await sendEmail(sendEmailParams); + await sendBulkEmails(sendEmailParams); console.log(`Incident ${incident.incident_id} updates: Pending notification was processed.`); } @@ -294,7 +294,7 @@ async function notificationsToNewEntityIncidents(context: Context) { if (incident && entity && recipients.length > 0) { - const sendEmailParams = { + const sendEmailParams: SendBulkEmailParams = { recipients, subject: isIncidentUpdate ? 'Update Incident for {{entityName}}' : 'New Incident for {{entityName}}', dynamicData: { @@ -314,7 +314,7 @@ async function notificationsToNewEntityIncidents(context: Context) { templateId: isIncidentUpdate ? 'EntityIncidentUpdated' : 'NewEntityIncident' }; - await sendEmail(sendEmailParams); + await sendBulkEmails(sendEmailParams); console.log(`New "${entity.name}" Entity Incidents: pending notification was processed.`); } @@ -373,8 +373,7 @@ async function notificationsToNewPromotions(context: Context) { if (incident && recipients.length > 0) { - //Send email notification - const sendEmailParams = { + const sendEmailParams: SendBulkEmailParams = { recipients, subject: 'Your submission has been approved!', dynamicData: { @@ -387,7 +386,7 @@ async function notificationsToNewPromotions(context: Context) { templateId: 'SubmissionApproved' // Template value from function name sufix from "site/realm/functions/config.json" }; - await sendEmail(sendEmailParams); + await sendBulkEmails(sendEmailParams); } } catch (error: any) { From d77be0ab572df0cc171a1f24bc1bb89014ea59a8 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 28 Jan 2025 19:55:45 -0300 Subject: [PATCH 93/99] Update test script --- site/gatsby-site/src/scripts/sendEmailTest.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/gatsby-site/src/scripts/sendEmailTest.js b/site/gatsby-site/src/scripts/sendEmailTest.js index 1f7004287c..be0ab6a2f2 100644 --- a/site/gatsby-site/src/scripts/sendEmailTest.js +++ b/site/gatsby-site/src/scripts/sendEmailTest.js @@ -1,4 +1,4 @@ -import { sendEmail } from '../../server/emails'; +import { sendBulkEmails } from '../../server/emails'; // from site/gatsby-site, run with // TEST_EMAIL_TO_ADDRESS=
dotenv run /npx ts-node src/scripts/sendEmailTest.js @@ -33,5 +33,5 @@ if (!email) { console.log(JSON.stringify(sendEmailArguments, null, 2)); - sendEmail(sendEmailArguments); + sendBulkEmails(sendEmailArguments); } From 85b3ccb91bd6d514a2231edc9c46d9e907ab1637 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 31 Jan 2025 22:18:05 -0300 Subject: [PATCH 94/99] update lockfile --- site/gatsby-site/package-lock.json | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/site/gatsby-site/package-lock.json b/site/gatsby-site/package-lock.json index de822ec7a4..2407d70c9f 100644 --- a/site/gatsby-site/package-lock.json +++ b/site/gatsby-site/package-lock.json @@ -78,6 +78,7 @@ "json-bigint-patch": "^0.0.8", "json-diff-ts": "^1.2.6", "jsonwebtoken": "^9.0.2", + "limiter": "^3.0.0", "mailersend": "^2.3.0", "md5": "^2.3.0", "minimatch": "^9.0.4", @@ -28519,6 +28520,11 @@ "node": ">=10" } }, + "node_modules/limiter": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/limiter/-/limiter-3.0.0.tgz", + "integrity": "sha512-hev7DuXojsTFl2YwyzUJMDnZ/qBDd3yZQLSH3aD4tdL1cqfc3TMnoecEJtWFaQFdErZsKoFMBTxF/FBSkgDbEg==" + }, "node_modules/lines-and-columns": { "version": "1.2.4", "license": "MIT" From d75ec65eda3cafef7947bbdc5332f2a9c66f99ba Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Fri, 31 Jan 2025 22:20:07 -0300 Subject: [PATCH 95/99] Update call --- site/gatsby-site/nextauth.config.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/site/gatsby-site/nextauth.config.ts b/site/gatsby-site/nextauth.config.ts index 1f83bb40f5..1ee516b9bb 100644 --- a/site/gatsby-site/nextauth.config.ts +++ b/site/gatsby-site/nextauth.config.ts @@ -35,7 +35,7 @@ export const getAuthConfig = async (req: any): Promise => { if (user) { await sendEmail({ - recipients: [{ email }], + recipient: { email }, subject: 'Secure link to log in to AIID', templateId: 'Login', dynamicData: { magicLink: url }, @@ -44,7 +44,7 @@ export const getAuthConfig = async (req: any): Promise => { else { await sendEmail({ - recipients: [{ email }], + recipient: { email }, subject: 'Secure link to create your AIID account', templateId: 'Signup', dynamicData: { magicLink: url }, From b9eac1db1b76bd05222aa000e80aeae5ab95d79e Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Mon, 3 Feb 2025 17:28:36 -0300 Subject: [PATCH 96/99] Update notification tests --- .../server/tests/notifications.spec.ts | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/site/gatsby-site/server/tests/notifications.spec.ts b/site/gatsby-site/server/tests/notifications.spec.ts index ad797eff4c..72af49beb4 100644 --- a/site/gatsby-site/server/tests/notifications.spec.ts +++ b/site/gatsby-site/server/tests/notifications.spec.ts @@ -43,7 +43,7 @@ describe(`Notifications`, () => { mockSession('123'); - const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); + const sendEmailMock = jest.spyOn(emails, 'sendBulkEmails').mockResolvedValue(); await processNotifications(); @@ -149,7 +149,7 @@ describe(`Notifications`, () => { mockSession('123'); jest.spyOn(common, 'getUserAdminData').mockResolvedValue({ userId: '123', email: 'test@test.com' }); - const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); + const sendEmailMock = jest.spyOn(emails, 'sendBulkEmails').mockResolvedValue(); const result = await processNotifications(); @@ -277,7 +277,7 @@ describe(`Notifications`, () => { mockSession('123'); jest.spyOn(common, 'getUserAdminData').mockResolvedValue({ userId: '123', email: 'test@test.com' }); - const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); + const sendEmailMock = jest.spyOn(emails, 'sendBulkEmails').mockResolvedValue(); const result = await processNotifications(); @@ -403,7 +403,7 @@ describe(`Notifications`, () => { mockSession('123'); jest.spyOn(common, 'getUserAdminData').mockResolvedValue({ userId: '123', email: 'test@test.com' }); - const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); + const sendEmailMock = jest.spyOn(emails, 'sendBulkEmails').mockResolvedValue(); const result = await processNotifications(); @@ -523,7 +523,7 @@ describe(`Notifications`, () => { mockSession('123'); jest.spyOn(common, 'getUserAdminData').mockResolvedValue({ userId: '123', email: 'test@test.com' }); - const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); + const sendEmailMock = jest.spyOn(emails, 'sendBulkEmails').mockResolvedValue(); const result = await processNotifications(); @@ -644,7 +644,7 @@ describe(`Notifications`, () => { mockSession('123'); jest.spyOn(common, 'getUserAdminData').mockResolvedValue({ userId: '123', email: 'test@test.com' }); - const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockResolvedValue(); + const sendEmailMock = jest.spyOn(emails, 'sendBulkEmails').mockResolvedValue(); const result = await processNotifications(); @@ -1294,7 +1294,7 @@ describe(`Notifications`, () => { } }); - jest.spyOn(emails, 'sendEmail').mockRestore(); + jest.spyOn(emails, 'sendBulkEmails').mockRestore(); jest.spyOn(common, 'getUserAdminData').mockResolvedValueOnce({ userId: 'user1', email: 'test@test.com' }) jest.spyOn(common, 'getUserAdminData').mockResolvedValueOnce({ userId: 'user2', email: 'test2@test.com' }); @@ -1503,7 +1503,7 @@ describe(`Notifications`, () => { mockSession('123'); jest.spyOn(common, 'getUserAdminData').mockResolvedValue({ userId: '123', email: 'test@test.com' }); - const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockImplementation(() => { + const sendEmailMock = jest.spyOn(emails, 'sendBulkEmails').mockImplementation(() => { throw new Error('Failed to send email'); }); @@ -1634,7 +1634,7 @@ describe(`Notifications`, () => { // No recipients jest.spyOn(common, 'getUserAdminData').mockResolvedValue(null); - const sendEmailMock = jest.spyOn(emails, 'sendEmail').mockImplementation(() => { + const sendEmailMock = jest.spyOn(emails, 'sendBulkEmails').mockImplementation(() => { throw new Error('Failed to send email'); }); From f93aec3f80ce6c632c758bb943c28832e42941ca Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Mon, 3 Feb 2025 17:33:15 -0300 Subject: [PATCH 97/99] Update auth tests --- .../server/tests/next-auth.spec.ts | 40 +++++++------------ 1 file changed, 15 insertions(+), 25 deletions(-) diff --git a/site/gatsby-site/server/tests/next-auth.spec.ts b/site/gatsby-site/server/tests/next-auth.spec.ts index 1eb48c4976..e8abce0536 100644 --- a/site/gatsby-site/server/tests/next-auth.spec.ts +++ b/site/gatsby-site/server/tests/next-auth.spec.ts @@ -138,11 +138,9 @@ describe('Auth', () => { dynamicData: { magicLink: expect.stringMatching(/^http:\/\/localhost:8000\/api\/auth\/callback\/http-email\?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2F&token=.+&email=test.user%40incidentdatabase.ai$/), }, - recipients: [ - { - email: "test.user@incidentdatabase.ai", - }, - ], + recipient: { + email: "test.user@incidentdatabase.ai", + }, subject: "Secure link to log in to AIID", templateId: "Login", @@ -189,11 +187,9 @@ describe('Auth', () => { dynamicData: { magicLink: expect.stringMatching(/^http:\/\/localhost:8000\/api\/auth\/callback\/http-email\?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2Fsome-path%2Fsome-page&token=.+&email=test.user%40incidentdatabase.ai$/), }, - recipients: [ - { - email: "test.user@incidentdatabase.ai", - }, - ], + recipient: { + email: "test.user@incidentdatabase.ai", + }, subject: "Secure link to log in to AIID", templateId: "Login", @@ -223,11 +219,9 @@ describe('Auth', () => { dynamicData: { magicLink: expect.stringMatching(/^http:\/\/localhost:8000\/api\/auth\/callback\/http-email\?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2F&token=.+&email=test.user%40incidentdatabase.ai$/), }, - recipients: [ - { - email: "test.user@incidentdatabase.ai", - }, - ], + recipient: { + email: "test.user@incidentdatabase.ai", + }, subject: "Secure link to create your AIID account", templateId: "Signup", }); @@ -270,11 +264,9 @@ describe('Auth', () => { dynamicData: { magicLink: expect.stringMatching(/^http:\/\/localhost:8000\/api\/auth\/callback\/http-email\?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2F&token=.+&email=test.user%40incidentdatabase.ai$/), }, - recipients: [ - { - email: "test.user@incidentdatabase.ai", - }, - ], + recipient: { + email: "test.user@incidentdatabase.ai", + }, subject: "Secure link to log in to AIID", templateId: "Login", }); @@ -313,11 +305,9 @@ describe('Auth', () => { dynamicData: { magicLink: expect.stringMatching(/^http:\/\/localhost:8000\/api\/auth\/callback\/http-email\?callbackUrl=http%3A%2F%2Flocalhost%3A8000%2Fsome-path%2Fsome-page&token=.+&email=test.user%40incidentdatabase.ai$/), }, - recipients: [ - { - email: "test.user@incidentdatabase.ai", - }, - ], + recipient: { + email: "test.user@incidentdatabase.ai", + }, subject: "Secure link to create your AIID account", templateId: "Signup", }); From 7198a07e8c43de3c056192d9a5b56c82eb3b1782 Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 4 Feb 2025 17:30:56 -0300 Subject: [PATCH 98/99] Add tests to single email function --- site/gatsby-site/server/tests/emails.spec.ts | 43 +++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/site/gatsby-site/server/tests/emails.spec.ts b/site/gatsby-site/server/tests/emails.spec.ts index 945682c9d6..2adc89a8b0 100644 --- a/site/gatsby-site/server/tests/emails.spec.ts +++ b/site/gatsby-site/server/tests/emails.spec.ts @@ -1,7 +1,7 @@ import { EmailParams } from 'mailersend'; import * as emails from '../emails'; -describe('Emails', () => { +describe('Emails - bulk', () => { test('Should throw an assertion error if the email "to" field contains multiple recipients', async () => { @@ -43,7 +43,48 @@ describe('Emails', () => { .rejects .toThrow('Should not use the \"cc\" field'); }); +}); +describe('Emails - single', () => { + test('Should throw an assertion error if the email "to" field contains multiple recipients', async () => { + + jest.spyOn(emails, 'mailersendSingleSend'); + + const single = new EmailParams() + .setFrom({ email: 'sender@example.com', name: 'Sender' }) + .setTo([ + { email: 'recipient1@example.com', name: 'Recipient 1' }, + { email: 'recipient2@example.com', name: 'Recipient 2' } + ]) + .setSubject('Test Email') + .setText('Test content') + .setHtml('

Test content

') + .setSendAt(Date.now()) + + await expect(emails.mailersendSingleSend(single)) + .rejects + .toThrow('Email must have exactly one recipient'); + }); + + test('Should throw an assertion error if the email "cc" field is used', async () => { + jest.spyOn(emails, 'mailersendBulkSend'); + + const multiRecipientEmail = new EmailParams() + .setFrom({ email: 'sender@example.com', name: 'Sender' }) + .setTo([{ email: 'recipient1@examplee.com`', name: 'Recipient 1' }]) + .setCc([ + { email: 'recipient1@example.com', name: 'Recipient 1' }, + { email: 'recipient2@example.com', name: 'Recipient 2' } + ]) + .setSubject('Test Email') + .setText('Test content') + .setHtml('

Test content

') + .setSendAt(Date.now()) + + await expect(emails.mailersendSingleSend(multiRecipientEmail)) + .rejects + .toThrow('Should not use the \"cc\" field'); + }); }); \ No newline at end of file From 0fa142d59a7c59933a2c742ce0ee8ce64bcac38d Mon Sep 17 00:00:00 2001 From: Cesar Varela Date: Tue, 4 Feb 2025 18:58:45 -0300 Subject: [PATCH 99/99] Add tests to check for proper send email parameters --- site/gatsby-site/server/tests/emails.spec.ts | 243 ++++++++++++------- 1 file changed, 160 insertions(+), 83 deletions(-) diff --git a/site/gatsby-site/server/tests/emails.spec.ts b/site/gatsby-site/server/tests/emails.spec.ts index 2adc89a8b0..220acd143d 100644 --- a/site/gatsby-site/server/tests/emails.spec.ts +++ b/site/gatsby-site/server/tests/emails.spec.ts @@ -1,90 +1,167 @@ import { EmailParams } from 'mailersend'; import * as emails from '../emails'; -describe('Emails - bulk', () => { - - test('Should throw an assertion error if the email "to" field contains multiple recipients', async () => { - - jest.spyOn(emails, 'mailersendBulkSend'); - - const multiRecipientEmail = [new EmailParams() - .setFrom({ email: 'sender@example.com', name: 'Sender' }) - .setTo([ - { email: 'recipient1@example.com', name: 'Recipient 1' }, - { email: 'recipient2@example.com', name: 'Recipient 2' } - ]) - .setSubject('Test Email') - .setText('Test content') - .setHtml('

Test content

') - .setSendAt(Date.now())] - - await expect(emails.mailersendBulkSend(multiRecipientEmail)) - .rejects - .toThrow('Emails must have exactly one recipient'); +describe('Emails', () => { + + describe('bulk', () => { + + test('Should throw an assertion error if the email "to" field contains multiple recipients', async () => { + + jest.spyOn(emails, 'mailersendBulkSend'); + + const multiRecipientEmail = [new EmailParams() + .setFrom({ email: 'sender@example.com', name: 'Sender' }) + .setTo([ + { email: 'recipient1@example.com', name: 'Recipient 1' }, + { email: 'recipient2@example.com', name: 'Recipient 2' } + ]) + .setSubject('Test Email') + .setText('Test content') + .setHtml('

Test content

') + .setSendAt(Date.now())] + + await expect(emails.mailersendBulkSend(multiRecipientEmail)) + .rejects + .toThrow('Emails must have exactly one recipient'); + }); + + test('Should throw an assertion error if the email "cc" field is used', async () => { + + jest.spyOn(emails, 'mailersendBulkSend'); + + const multiRecipientEmail = [new EmailParams() + .setFrom({ email: 'sender@example.com', name: 'Sender' }) + .setTo([{ email: 'recipient1@examplee.com`', name: 'Recipient 1' }]) + .setCc([ + { email: 'recipient1@example.com', name: 'Recipient 1' }, + { email: 'recipient2@example.com', name: 'Recipient 2' } + ]) + .setSubject('Test Email') + .setText('Test content') + .setHtml('

Test content

') + .setSendAt(Date.now())] + + await expect(emails.mailersendBulkSend(multiRecipientEmail)) + .rejects + .toThrow('Should not use the \"cc\" field'); + }); + + test('Should call sendBulkEmail with appropriate params', async () => { + + jest.spyOn(emails, 'sendBulkEmails'); + jest.spyOn(emails, 'mailersendBulkSend').mockResolvedValue(); + + const params: emails.SendBulkEmailParams = + { + recipients: [{ email: 'sender1@example.com' }, { email: 'sender2@example.com' }], + subject: 'Test Email', + templateId: 'Login', + dynamicData: { magicLink: `example.com` }, + } + + await emails.sendBulkEmails(params); + + expect(emails.mailersendBulkSend).toHaveBeenCalledWith([ + expect.objectContaining({ + bcc: undefined, + cc: undefined, + to: [{ email: "sender1@example.com", },], + personalization: [ + expect.objectContaining({ + data: expect.objectContaining({ + magicLink: 'example.com' + }) + }) + ] + }), + expect.objectContaining({ + bcc: undefined, + cc: undefined, + to: [{ email: "sender2@example.com", },], + personalization: [ + expect.objectContaining({ + data: expect.objectContaining({ + magicLink: 'example.com' + }) + }) + ] + }) + ]); + }) }); - test('Should throw an assertion error if the email "cc" field is used', async () => { - - jest.spyOn(emails, 'mailersendBulkSend'); - - const multiRecipientEmail = [new EmailParams() - .setFrom({ email: 'sender@example.com', name: 'Sender' }) - .setTo([{ email: 'recipient1@examplee.com`', name: 'Recipient 1' }]) - .setCc([ - { email: 'recipient1@example.com', name: 'Recipient 1' }, - { email: 'recipient2@example.com', name: 'Recipient 2' } - ]) - .setSubject('Test Email') - .setText('Test content') - .setHtml('

Test content

') - .setSendAt(Date.now())] - - await expect(emails.mailersendBulkSend(multiRecipientEmail)) - .rejects - .toThrow('Should not use the \"cc\" field'); - }); -}); - -describe('Emails - single', () => { - - test('Should throw an assertion error if the email "to" field contains multiple recipients', async () => { - - jest.spyOn(emails, 'mailersendSingleSend'); - - const single = new EmailParams() - .setFrom({ email: 'sender@example.com', name: 'Sender' }) - .setTo([ - { email: 'recipient1@example.com', name: 'Recipient 1' }, - { email: 'recipient2@example.com', name: 'Recipient 2' } - ]) - .setSubject('Test Email') - .setText('Test content') - .setHtml('

Test content

') - .setSendAt(Date.now()) - - await expect(emails.mailersendSingleSend(single)) - .rejects - .toThrow('Email must have exactly one recipient'); - }); - - test('Should throw an assertion error if the email "cc" field is used', async () => { - - jest.spyOn(emails, 'mailersendBulkSend'); - - const multiRecipientEmail = new EmailParams() - .setFrom({ email: 'sender@example.com', name: 'Sender' }) - .setTo([{ email: 'recipient1@examplee.com`', name: 'Recipient 1' }]) - .setCc([ - { email: 'recipient1@example.com', name: 'Recipient 1' }, - { email: 'recipient2@example.com', name: 'Recipient 2' } - ]) - .setSubject('Test Email') - .setText('Test content') - .setHtml('

Test content

') - .setSendAt(Date.now()) - - await expect(emails.mailersendSingleSend(multiRecipientEmail)) - .rejects - .toThrow('Should not use the \"cc\" field'); + describe('single', () => { + + test('Should throw an assertion error if the email "to" field contains multiple recipients', async () => { + + jest.spyOn(emails, 'mailersendSingleSend'); + + const single = new EmailParams() + .setFrom({ email: 'sender@example.com', name: 'Sender' }) + .setTo([ + { email: 'recipient1@example.com', name: 'Recipient 1' }, + { email: 'recipient2@example.com', name: 'Recipient 2' } + ]) + .setSubject('Test Email') + .setText('Test content') + .setHtml('

Test content

') + .setSendAt(Date.now()) + + await expect(emails.mailersendSingleSend(single)) + .rejects + .toThrow('Email must have exactly one recipient'); + }); + + test('Should throw an assertion error if the email "cc" field is used', async () => { + + jest.spyOn(emails, 'mailersendBulkSend'); + + const multiRecipientEmail = new EmailParams() + .setFrom({ email: 'sender@example.com', name: 'Sender' }) + .setTo([{ email: 'recipient1@examplee.com`', name: 'Recipient 1' }]) + .setCc([ + { email: 'recipient1@example.com', name: 'Recipient 1' }, + { email: 'recipient2@example.com', name: 'Recipient 2' } + ]) + .setSubject('Test Email') + .setText('Test content') + .setHtml('

Test content

') + .setSendAt(Date.now()) + + await expect(emails.mailersendSingleSend(multiRecipientEmail)) + .rejects + .toThrow('Should not use the \"cc\" field'); + }); + + test('Should call sendEmail with appropriate params', async () => { + + jest.spyOn(emails, 'sendEmail'); + jest.spyOn(emails, 'mailersendSingleSend').mockResolvedValue(); + + const params: emails.SendEmailParams = + { + recipient: { email: 'sender1@example.com' }, + subject: 'Test Email', + templateId: 'Login', + dynamicData: { magicLink: `example.com` }, + } + + await emails.sendEmail(params); + + expect(emails.mailersendSingleSend).toHaveBeenCalledWith( + expect.objectContaining({ + bcc: undefined, + cc: undefined, + to: [{ email: "sender1@example.com" },], + personalization: [ + expect.objectContaining({ + data: expect.objectContaining({ + magicLink: 'example.com' + }) + }) + ] + }), + ); + }) }); }); \ No newline at end of file