diff --git a/.github/workflows/test-unit-snapshot.yml b/.github/workflows/test-unit-snapshot.yml index 7f561c3b8..460389c23 100644 --- a/.github/workflows/test-unit-snapshot.yml +++ b/.github/workflows/test-unit-snapshot.yml @@ -1,4 +1,4 @@ -name: Test - Unit and Snapshot +name: Test - Unit, Integration, Snapshot on: push: @@ -21,3 +21,11 @@ jobs: yarn test env: CI: true + # Dummy environment variables for testing the access-bridge service + APP_SITE_ID: test1234 + APP_API_SECRET: dummy_secret + APP_STRIPE_SECRET: dummy_stripe_secret + APP_BIND_ADDR: localhost + APP_BIND_PORT: 3001 + APP_ACCESS_CONTROL_API_HOST: https://test-cdn.jwplayer.com + APP_SIMS_API_HOST: https://test-sims.jwplayer.com diff --git a/knip.config.ts b/knip.config.ts index 1d22ab1ee..e32756a97 100644 --- a/knip.config.ts +++ b/knip.config.ts @@ -38,7 +38,6 @@ const config: KnipConfig = { 'i18next-parser', 'luxon', // Used in tests 'playwright', // Used in test configs - 'sharp', // Requirement for @vite-pwa/assets-generator 'tsconfig-paths', // Used for e2e test setup 'virtual:pwa-register', // Service Worker code is injected at build time 'virtual:polyfills', // Polyfills are conditionally injected diff --git a/package.json b/package.json index c92bd4ca6..85821ed50 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "prepare": "husky install", "test": "TZ=UTC LC_ALL=en_US.UTF-8 vitest run", "test-watch": "TZ=UTC LC_ALL=en_US.UTF-8 vitest", - "web": "yarn --cwd platforms/web" + "web": "yarn --cwd platforms/web", + "access-bridge": "yarn --cwd platforms/access-bridge" }, "devDependencies": { "@commitlint/cli": "^17.8.1", diff --git a/packages/common/types/errors.ts b/packages/common/types/errors.ts new file mode 100644 index 000000000..c8d76f25a --- /dev/null +++ b/packages/common/types/errors.ts @@ -0,0 +1,8 @@ +export type JWError = { + code: string; + description: string; +}; + +export type JWErrorResponse = { + errors: JWError[]; +}; diff --git a/packages/common/types/passport.ts b/packages/common/types/passport.ts new file mode 100644 index 000000000..959197854 --- /dev/null +++ b/packages/common/types/passport.ts @@ -0,0 +1,4 @@ +export type PassportResponse = { + passport: string; + refresh_token: string; +}; diff --git a/packages/common/types/payment.ts b/packages/common/types/payment.ts new file mode 100644 index 000000000..dde4917aa --- /dev/null +++ b/packages/common/types/payment.ts @@ -0,0 +1,49 @@ +type Recurrence = { + // The frequency at which a subscription is billed. One of `day`, `week`, `month`, or `year`. + interval: 'day' | 'week' | 'month' | 'year'; + // Recurrence duration, for example, `interval=month` and `duration=3` bills every 3 months. + duration: number; + // Trial period interval. For example, a month-long trial is different from a 30-day trial. + trial_period_interval: 'day' | 'week' | 'month' | 'year'; + // Duration of the trial period (in the unit defined by trial_period_interval). + trial_period_duration: number | null; +}; + +export type Price = { + // Unique identifier for the object. + store_price_id: string; + // Dictionary of currencies, where the key is the currency code and the value is an object with an amount property. + // Three-letter [ISO currency code](https://www.iso.org/iso-4217-currency-codes.html), in lowercase. + currencies: { + [currency_code: string]: { + // The unit amount in cents (or local equivalent) to be charged, represented as a whole integer. + amount: number | null; + }; + }; + // Default currency code for this price. + default_currency: string; + // Recurrence details. Can be a Recurrence object or 'one_time'. + recurrence: Recurrence | 'one_time'; + // Billing scheme. For now, we only support `per_unit`. + billing_scheme: 'per_unit'; +}; + +export type Product = { + // Unique identifier for the object. + store_product_id: string; + // The product's name, meant to be displayable to the customer. + name: string; + // The product's description, meant to be displayable to the customer. + description: string; + // The ID of the default price this product is associated with. + default_store_price_id: string; + // Array of price objects. + prices: Price[]; +}; + +// General checkout parameters type. Can be extended by specific payment providers, e.g. Stripe +export type CheckoutParams = { + price_id: string; + success_url: string; + cancel_url: string; +}; diff --git a/packages/common/types/plans.ts b/packages/common/types/plans.ts new file mode 100644 index 000000000..400703850 --- /dev/null +++ b/packages/common/types/plans.ts @@ -0,0 +1,37 @@ +type AccessOptions = { + drm_policy_id: string; + include_tags: string[] | null; + exclude_tags: string[] | null; + include_custom_params: string[] | null; + exclude_custom_params: string[] | null; +}; + +type PlanExternalProviders = { + stripe?: string; + apple?: string; + google?: string; +}; + +export type AccessControlPlan = { + id: string; + exp: number; +}; + +export type Plan = { + id: string; + original_id: number; + exp: number; + metadata: { + name: string; + access: AccessOptions; + access_model: 'free' | 'freeauth' | 'svod'; + external_providers: PlanExternalProviders; + }; +}; + +export type PlansResponse = { + total: number; + page: number; + page_length: number; + plans: Plan[]; +}; diff --git a/platforms/access-bridge/.env.example b/platforms/access-bridge/.env.example new file mode 100644 index 000000000..82c77f2fc --- /dev/null +++ b/platforms/access-bridge/.env.example @@ -0,0 +1,37 @@ +# Application version - Used for tracking and identifying specific releases. +# Automatically set to the version specified in package.json. +# Helps in associating logs, errors, and other metrics with particular versions of the application. +APP_VERSION=$npm_package_version + +# Secrets responsible for signing a request to authenticate with the JW Delivery Gateway. +# In order to use the AC System this authentication is crucial. +# The secrets can be found in the JW Dashboard, under the API Credentials in the top right settings icon. +# Make sure the secrets are V1 and that they refer to the desired property. +# For production env, use this reference on how to store them: https://cloud.google.com/run/docs/configuring/services/secrets +APP_API_SECRET=customer_v1_secret +# site_id or property_id represents the key that corresponds to the APP_API_SECRET defined earlier. +APP_SITE_ID=customer_site_id +# Stripe secret responsible for authenticating Stripe API calls +APP_STRIPE_SECRET=stripe_secret + +# Non-secret variables +# Specifies the network address or IP address on which the server listens for incoming connections. +APP_BIND_ADDR=localhost +# Specifies the port number on which the server listens for incoming connections. +APP_BIND_PORT=8080 +# Specifies the client URL responsible for access related stuff +APP_ACCESS_CONTROL_API_HOST=https://cdn-dev.jwplayer.com +# Specifies the client URL responsible for plans related stuff +APP_SIMS_API_HOST=https://daily-sims.jwplayer.com + +# These are optional and should be added only if tracing with Sentry is needed +# Set the APP_SENTRY_DSN variable to enable Sentry error tracking and performance monitoring. +# Set APP_SENTRY_AUTH_TOKEN to allow Sentry to provide readable stack traces (source maps). +# If this variable is not set, Sentry will not be initialized. +# For production environments, ensure you configure the `APP_SENTRY_TRACE_RATE` +# according to your monitoring needs to balance performance and resource usage. +APP_SENTRY_DSN= +APP_SENTRY_AUTH_TOKEN= +APP_SENTRY_TRACE_RATE= +APP_SENTRY_ORG_NAME= +APP_SENTRY_PROJ_NAME= diff --git a/platforms/access-bridge/.eslintrc.cjs b/platforms/access-bridge/.eslintrc.cjs new file mode 100644 index 000000000..0eb7718e4 --- /dev/null +++ b/platforms/access-bridge/.eslintrc.cjs @@ -0,0 +1,12 @@ +module.exports = { + extends: ['jwp/typescript'], + rules: { + "max-len": ["error", { "code": 120 }], + "import/no-unused-modules": ["error"] + }, + env: { + node: true, // Enables recognition of Node.js global variables and scoping rules + }, + ignorePatterns: ['build'], +}; + diff --git a/platforms/access-bridge/.gitignore b/platforms/access-bridge/.gitignore new file mode 100644 index 000000000..bcea2a995 --- /dev/null +++ b/platforms/access-bridge/.gitignore @@ -0,0 +1,6 @@ +node_modules +build +*.local +.env +# Sentry Config File +.env.sentry-build-plugin diff --git a/platforms/access-bridge/.prettierignore b/platforms/access-bridge/.prettierignore new file mode 100644 index 000000000..378eac25d --- /dev/null +++ b/platforms/access-bridge/.prettierignore @@ -0,0 +1 @@ +build diff --git a/platforms/access-bridge/Makefile b/platforms/access-bridge/Makefile new file mode 100644 index 000000000..c22bdf66c --- /dev/null +++ b/platforms/access-bridge/Makefile @@ -0,0 +1,70 @@ +PACKAGE_JSON := ./package.json +BACKUP_PACKAGE_JSON := ./package-backup.json +ROOT_YARN_LOCK := ../../yarn.lock +PROJECT_YARN_LOCK := ./yarn.lock + +# Default target: deploy and ensure cleanup runs regardless +.PHONY: deploy +deploy: check-setup backup-package modify-package copy-yarn-lock try-deploy post-deploy + +# Step 0: Check setup +.PHONY: check-setup +check-setup: + @echo "Checking setup..." + @command -v gcloud >/dev/null 2>&1 || { echo "Error: gcloud is not installed. Please install Google Cloud SDK."; exit 1; } + @command -v yarn >/dev/null 2>&1 || { echo "Error: yarn is not installed. Please install Yarn."; exit 1; } + @echo "All required tools are installed." + @# Check if Google Cloud is configured + @gcloud config get-value project >/dev/null 2>&1 || { echo "Error: Google Cloud project is not configured. Run 'gcloud init'."; exit 1; } + @echo "Google Cloud is configured." + +# Step 1: Backup the original package.json +.PHONY: backup-package +backup-package: + @echo "Deployment started..." + @cp $(PACKAGE_JSON) $(BACKUP_PACKAGE_JSON) + +# Step 2: Modify `package.json` by removing internal dependencies +# In a monorepo setup, internal packages are not deployed. This step ensures that only the relevant, +# external dependencies are included in the `package.json` for deployment. +.PHONY: modify-package +modify-package: + @# Use `sed` to remove specific internal dependencies by editing package.json in place + @sed -i.bak '/"@jwp\/ott-common"/d' $(PACKAGE_JSON) + @sed -i.bak '/"eslint-config-jwp"/d' $(PACKAGE_JSON) + @rm -f $(PACKAGE_JSON).bak + +# Step 3: Copy the root `yarn.lock` to the project directory +# To maintain dependency consistency in a monorepo, copy the root `yarn.lock` file to the project +# directory so Google Cloud Run can correctly compare and resolve dependencies. +.PHONY: copy-yarn-lock +copy-yarn-lock: + @cp $(ROOT_YARN_LOCK) $(PROJECT_YARN_LOCK) + +# Step 4: Deploy to Google Cloud Run +.PHONY: try-deploy +try-deploy: + @{ \ + set -e; \ + trap 'make post-deploy; exit 1;' INT TERM HUP; \ + gcloud run deploy access-bridge \ + --source=. \ + --platform=managed \ + --region= \ + --allow-unauthenticated; \ + make post-deploy; \ + } + +# Step 5: Cleanup (run whether deploy succeeds or fails) +.PHONY: post-deploy +post-deploy: restore-package clean + +# Step 6: Restore the original package.json +.PHONY: restore-package +restore-package: + @mv $(BACKUP_PACKAGE_JSON) $(PACKAGE_JSON) + +# Step 7: Clean up copied yarn.lock +.PHONY: clean +clean: + @rm -f $(PROJECT_YARN_LOCK) diff --git a/platforms/access-bridge/README.md b/platforms/access-bridge/README.md new file mode 100644 index 000000000..946ba54f2 --- /dev/null +++ b/platforms/access-bridge/README.md @@ -0,0 +1,134 @@ +# Access Bridge + +A service that facilitates seamless communication between the Subscriber Identity Management System (SIMS) and Access Control services. It provides endpoints to generate access passports for authenticated viewers, ensuring secure and efficient access management. + +## Local Setup for Environment Variables + +To set up the project locally, you need to configure environment variables that are crucial for authenticating with the JW Delivery Gateway and for specifying server configurations. + +Here’s how you can set them up: + +Create a `.env.local` file in the root of this project and add the following variables: + +- APP_API_SITE_ID=customer_site_id +- APP_API_SECRET=customer_v1_secret +- APP_BIND_ADDR=localhost +- APP_BIND_PORT=8080 +- APP_ACCESS_CONTROL_API_HOST=https://cdn-dev.jwplayer.com + (Use https://cdn.jwplayer.com for production) +- APP_SIMS_API_HOST=https://daily-sims.jwplayer.com + (Use https://sims.jwplayer.com for production) + +Make sure to replace the placeholder values (e.g., customer_v1_secret) with the actual values from your JW Dashboard. +You can also copy and paste the contents of `.env.example` into `.env.local` and just adjust the APP_API_SECRET. + +## Getting started + +- Run `yarn` to install dependencies +- Navigate to the platform directory `cd platforms/access-bridge` +- Run tests through `yarn test` +- Run `yarn start` to start the server + +## Exposed endpoints + +#### URL: `/v2/sites/{site_id}/access/generate` + +- **Method:** PUT +- **Authorization:** Valid SIMS token +- **Summary:** Generates a new passport for an authenticated viewer based on the information inside the SIMS token. +- **Response:** + ```json + { + "passport": "encrypted_passport", + "refresh_token": "random_string" + } + ``` + +#### URL: `/v2/sites/{site_id}/access/refresh` + +- **Method:** PUT +- **Authorization:** Valid SIMS token +- **Summary:** Regenerates an existing passport with a new expiry and a new refresh token. +- **Request:** + ```json + { + "refresh_token": "string" + } + ``` +- **Response:** + ```json + { + "passport": "encrypted_passport", + "refresh_token": "random_string" + } + ``` + +#### URL: `/v2/sites/{site_id}/products` + +- **Method:** GET +- **Authorization:** None +- **Summary:** Lists all the corresponding stripe products with prices that are connected to the SIMS plans. +- **Response:** [Product payment type](../../../ott-web-app/packages/common/types/payment.ts) + ```json + [ + { + // ... + "id": "prod_QRUHbH7wK5HHPr", + "default_price": "price_1PabInA9TD3ZjIM6EEnKSR7U", + // ... + "prices": [ + { + // ... + "id": "price_1PabInA9TD3ZjIM6EEnKSR7U", + "currency": "usd", + "unit_amount": 15000 + // ... + } + ] + } + // ... + ] + ``` + +#### URL: `/v2/sites/{site_id}/checkout` + +- **Method:** POST +- **Authorization:** Valid SIMS token +- **Summary:** Creates Payment Checkout Session URL where the viewer will be redirected to complete the payment. +- **Request:** + ```json + { + "price_id": "string", // id of the price that is about to be paid + "mode": "string", // subscription (recurring) | payment (one time purchases) + "success_url": "string", // redirect after successful payment + "cancel_url": "string" // redirect after cancel / invalid payment + } + ``` +- **Response:** + ```json + { + "url": "string" // url where the viewer will be redirected to complete the payment. + } + ``` + +#### URL: `/v2/sites/{site_id}/billing-portal` + +- **Method:** POST +- **Authorization:** Valid SIMS token +- **Summary:** Generates Billing Portal Url, where the viewer can view / update their purchase info. +- **Response:** + ```json + { + "url": "billing-portal-url" + } + ``` + +## Developer guidelines + +- Read the workspace guidelines here [developer-guidelines.md](./docs/developer-guidelines.md). +- Read the deployment guidelines here [deployment.md](./docs/deployment.md). +- Read the web platform guidelines here [developer-guidelines.md](../../docs/developer-guidelines.md). + +``` + +``` diff --git a/platforms/access-bridge/docs/deployment.md b/platforms/access-bridge/docs/deployment.md new file mode 100644 index 000000000..cc0fab351 --- /dev/null +++ b/platforms/access-bridge/docs/deployment.md @@ -0,0 +1,50 @@ +# Deployment Instructions for `access-bridge` App + +This document outlines the steps required to deploy the `access-bridge` application to Google Cloud Run. The deployment process involves checking the setup, modifying `package.json`, copying the `yarn.lock` file, deploying the application, and performing cleanup tasks. + +## Prerequisites + +Ensure the following before deployment: + +- **Google Cloud SDK**: Install from [Google Cloud SDK Documentation](https://cloud.google.com/sdk/docs/install). +- Make sure to configure the Google Cloud project using `gcloud init`. + +## Environment Configuration + +Before deploying, ensure the environment variables and secrets required by the application are properly set up: + +1. **Environment Variables and Secrets**: + + - Review the `.env.example` file for a list of required environment variables. + - Set up these variables in Google Cloud Run. Secrets should be managed using [Google Cloud Secret Manager](https://cloud.google.com/security/products/secret-manager), while non-sensitive variables can be set as environment variables in the Google Cloud Run settings. + + Ensure that all sensitive information is securely managed with Secret Manager to avoid exposure of secrets in your deployment. + +## Deployment Overview + +The `make deploy` command automates the deployment process. It performs the following tasks: + +1. **Check Setup**: Verifies that all necessary tools and configurations are in place. +2. **Backup `package.json`**: Creates a backup of the current `package.json` to restore later. +3. **Modify `package.json`**: Removes internal dependencies that may cause conflicts in Google Cloud Run. +4. **Copy `yarn.lock`**: Copies the root `yarn.lock` file to the project directory to ensure dependency consistency. +5. **Deploy**: Deploys the application to Google Cloud Run. +6. **Post-Deployment Cleanup**: Restores the original `package.json` and removes the copied `yarn.lock` file. + +## Deployment Steps + +Before running the deployment command, ensure the following: + +1. **Clean Working Directory**: Ensure there are no uncommitted changes in your working directory. The deployment process assumes a clean state. + +2. **Install Dependencies**: Run `yarn install` to ensure all dependencies are installed and up-to-date. + +3. **Navigate to Project Directory**: Make sure you are in the `access-bridge` directory before running the deployment command. + +4. **Configure Deployment Region**: When running the deployment command, you will be prompted to enter the region where you want to deploy the application. If you prefer not to be prompted each time, you can modify the `Makefile` to include the `--region` flag directly with your preferred region. + +To deploy the `access-bridge` application, run the following command: + +```sh +make deploy +``` diff --git a/platforms/access-bridge/docs/developer-guidelines.md b/platforms/access-bridge/docs/developer-guidelines.md new file mode 100644 index 000000000..cc6239581 --- /dev/null +++ b/platforms/access-bridge/docs/developer-guidelines.md @@ -0,0 +1,81 @@ +# Developer guidelines + +## When working on this project, keep these in mind: + +- Use `yarn` to install dependencies +- Run tests through `yarn test` +- Start the project using `yarn start` + +## Project Structure + +``` +/build* - Directory where the code is compiled by `yarn build` +/docs - Documentation related to the project +/node_modules* - Yarn generated dependencies +/src - Source code for the application + /controllers - Controller modules containing the core logic for handling requests and responses + /services - Services which connect external data sources to the application + /pipeline - Middleware and routing logic + /middleware.ts - Middleware functions and error handling + /routes.ts - Route definitions and route registration + /logger.ts - Logger class that integrates with Sentry if defined with fallback as a development logger + /errors.ts - Custom error classes and error handling logic + /http.ts - HTTP utility functions and setup + /app-config.ts - Configuration settings for the application + /main.ts - Main entry point of the application + /server.ts - Server initialization and configuration +/test - Data and scripts for testing +/.env<.mode> - Environment variables for different modes (e.g., development, production) +/package.json - Yarn file for dependencies and scripts + + + + +* = Generated directories, not in source control + +Note: Some system and util files are not shown above for brevity. +You probably won't need to mess with anything not shown here. +``` + +## Sentry Setup and Configuration + +### Integrating Sentry + +Sentry is integrated into this project to track and monitor errors, as well as performance issues. To ensure that Sentry is properly configured and working as expected, follow the steps below: + +- Environment Variables: + Ensure that your .env. files include the following Sentry-related environment variables: + APP_SENTRY_DSN=Your_Sentry_DSN + APP_SENTRY_AUTH_TOKEN=Your_Sentry_Auth_Token + APP_SENTRY_TRACE_RATE=Trace rate for performance monitoring + APP_SENTRY_ORG_NAME=Your_Sentry_ORG_Name + APP_SENTRY_PROJ_NAME=Your_Sentry_Project_Name + These variables are essential for Sentry to function correctly and should be kept secure. + +- Running the Project: + Use the following commands to install dependencies, run tests, and start the project, with Sentry automatically configured: + Install dependencies: yarn + Run tests: yarn test + Start the project: yarn start + +- Source Maps: + Source maps are configured to be generated during the build process to allow Sentry to provide readable stack traces. + When building the project, the source maps will be automatically uploaded to Sentry if the correct environment variables are set. + +- Error Monitoring: + Sentry captures unhandled errors and sends detailed reports, including stack traces, to help in debugging. + Review the Sentry dashboard regularly to monitor the health of the application. + +- Performance Monitoring: + Sentry is also configured for performance monitoring. Ensure that the trace rate is appropriately set in the environment variables. + +## Adding Sentry to New Features + +When adding new features or making significant changes to the code: + +- Error Handling: + Ensure that errors are properly captured and logged using Sentry. +- Custom Error Classes: + If you define custom error classes, integrate them with Sentry for better tracking. +- Performance Considerations: + For critical paths, consider adding custom performance monitoring using Sentry’s APIs. diff --git a/platforms/access-bridge/docs/frameworks.md b/platforms/access-bridge/docs/frameworks.md new file mode 100644 index 000000000..219ae0da6 --- /dev/null +++ b/platforms/access-bridge/docs/frameworks.md @@ -0,0 +1,76 @@ +# Language, Frameworks, SDK's and Libraries + +## Typescript + +Typescript is a superset of Javascript, TypeScript adds optional types to JavaScript that support tools for large-scale JavaScript applications for any browser, for any host, on any OS. TypeScript compiles to readable, standards-based JavaScript. + +- Optional static typing +- Spot bugs at compile time +- Predictability +- Readability +- Fast refactoring +- Power of OOP + +[Read more.](https://www.typescriptlang.org/) + +## Express + +Express is a minimal and flexible Node.js web application framework that provides a robust set of features for web and mobile applications. It's designed to build web applications and APIs, offering a thin layer of fundamental web application features without obscuring Node.js's built-in features. + +- Minimal and flexible +- Powerful routing and middleware support +- Full HTTP utility methods and middleware +- Fast and unopinionated +- Ideal for building APIs and single-page applications + +[Read more.](https://expressjs.com/) + +## Vitest + +Vitest is a blazing-fast unit testing framework built for modern JavaScript and TypeScript projects. It is designed to work seamlessly with Vite, leveraging its speed and simplicity, but can be used in any project. Vitest provides an intuitive and powerful API that makes writing tests easy and efficient. + +- Fast test execution with minimal configuration +- Built-in TypeScript support +- Instant feedback with hot reloading +- Integrated with Vite but framework-agnostic +- Snapshot testing, mocking, and more + +[Read more.](https://vitest.dev/) + +## JSON Web Token (jsonwebtoken) + +The jsonwebtoken library is used for generating and verifying JSON Web Tokens (JWTs). JWTs are a compact and secure way to transmit information between parties as a JSON object. This library is particularly useful for authentication and authorization purposes. + +- Generate and verify JWTs +- Compact and URL-safe tokens +- Supports various signing algorithms +- Easy integration with existing systems +- Used for secure API calls through the Delivery Gateway + +[Read more.](https://github.com/auth0/node-jsonwebtoken/) + +## Stripe + +The Stripe SDK provides a comprehensive set of APIs and tools for integrating payment processing into web and mobile applications. With the Stripe SDK, developers can easily manage transactions, subscriptions, and financial reports, while ensuring secure and compliant payment handling. + +- Seamless payment integration +- Support for multiple payment methods +- Advanced fraud prevention +- Subscription and billing management +- Detailed financial reporting +- PCI compliance and secure transactions + +[Read more.](https://docs.stripe.com/) + +## Sentry + +Sentry is an open-source error tracking and performance monitoring tool that helps developers monitor and fix crashes in real-time. With Sentry, you can identify and address performance bottlenecks, errors, and exceptions in your applications. Sentry provides detailed stack traces, allowing developers to see exactly where an issue occurred, and integrates seamlessly with many popular languages and frameworks. + +- Real-time error tracking and alerting +- Performance monitoring and tracing +- Detailed stack traces for easy debugging +- Seamless integration with various languages and frameworks +- Support for source maps to map minified code to original source code +- Customizable notifications and workflow integrations + +[Read more.](https://docs.sentry.io/) diff --git a/platforms/access-bridge/lint-staged.config.cjs b/platforms/access-bridge/lint-staged.config.cjs new file mode 100644 index 000000000..998018c6a --- /dev/null +++ b/platforms/access-bridge/lint-staged.config.cjs @@ -0,0 +1,7 @@ +module.exports = { + // Lint and format JavaScript and TypeScript files + '*.{js,ts}': ['eslint --fix', 'prettier --write'], + + // Type check TypeScript files + '*.ts': [() => 'tsc --pretty --noEmit'], +}; diff --git a/platforms/access-bridge/package.json b/platforms/access-bridge/package.json new file mode 100644 index 000000000..64b19c00e --- /dev/null +++ b/platforms/access-bridge/package.json @@ -0,0 +1,42 @@ +{ + "name": "@jwp/access-bridge", + "version": "1.0.0", + "main": "build/main.js", + "engines": { + "node": ">=18.13.0" + }, + "private": true, + "author": "JW Player", + "type": "module", + "scripts": { + "build": "vite build --mode ${MODE:=prod}", + "build-test": "vite build --mode test", + "start": "node build/main.js", + "prestart": "yarn build", + "pretest": "yarn build-test", + "test": "TZ=UTC LC_ALL=en_US.UTF-8 vitest run", + "lint:ts": "tsc --pretty --noEmit -p ./" + }, + "devDependencies": { + "@types/cors": "^2.8.17", + "@types/express": "^4.17.21", + "@types/jsonwebtoken": "^9.0.6", + "@types/node": "^20.14.10", + "eslint-config-jwp": "*", + "typescript": "^5.5.3", + "vite": "^5.3.1", + "vite-plugin-node": "^3.1.0", + "vitest": "^1.6.0" + }, + "dependencies": { + "@jwp/ott-common": "*", + "@sentry/node": "^8.26.0", + "@sentry/profiling-node": "^8.26.0", + "@sentry/vite-plugin": "^2.22.2", + "cors": "^2.8.5", + "dotenv": "^16.4.5", + "express": "^4.19.2", + "jsonwebtoken": "^9.0.2", + "stripe": "^16.8.0" + } +} diff --git a/platforms/access-bridge/prettier.config.cjs b/platforms/access-bridge/prettier.config.cjs new file mode 100644 index 000000000..5db66ae34 --- /dev/null +++ b/platforms/access-bridge/prettier.config.cjs @@ -0,0 +1,11 @@ +module.exports = { + printWidth: 120, // Set the maximum line length + tabWidth: 2, // Number of spaces per indentation level + useTabs: false, // Use spaces for indentation instead of tabs + singleQuote: true, // Use single quotes instead of double quotes + trailingComma: 'es5', // Add trailing commas where valid in ES5 (objects, arrays) + bracketSpacing: true, // Print spaces between brackets in object literals + semi: true, // Print semicolons at the ends of statements + arrowParens: 'always', // Include parentheses around a sole arrow function parameter + endOfLine: 'lf', // Enforce Unix line endings +}; diff --git a/platforms/access-bridge/src/app-config.ts b/platforms/access-bridge/src/app-config.ts new file mode 100644 index 000000000..e24a92ba4 --- /dev/null +++ b/platforms/access-bridge/src/app-config.ts @@ -0,0 +1,39 @@ +import dotenv from 'dotenv'; +// Inject environment variables at runtime +dotenv.config(); + +// Function to assert an environment variable is defined +function requireEnvVar(env: string | undefined, name: string): string { + if (!env) { + throw new Error(`Environment variable "${name}" is not defined.`); + } + return env; +} + +// Secrets resonsible for authenticating requests +export const API_SECRET = requireEnvVar(process.env.APP_API_SECRET, 'APP_API_SECRET'); +export const STRIPE_SECRET = requireEnvVar(process.env.APP_STRIPE_SECRET, 'APP_STRIPE_SECRET'); + +// SITE_ID specifies the property that the customer is using +export const SITE_ID = requireEnvVar(process.env.APP_SITE_ID, 'APP_SITE_ID'); + +// BIND_ADDR specifies the network address or IP address on which the server listens for incoming connections. +// This could be an IP address (e.g., '127.0.0.1' for localhost) or a hostname. +export const BIND_ADDR = requireEnvVar(process.env.APP_BIND_ADDR, 'APP_BIND_ADDR'); + +// BIND_PORT specifies the port number on which the server listens for incoming connections. +// Ensure this port is available and not in use by another application. +export const BIND_PORT = (() => { + const port = parseInt(requireEnvVar(process.env.APP_BIND_PORT, 'APP_BIND_PORT'), 10); + if (isNaN(port)) { + throw new Error(`Environment variable "APP_BIND_PORT" must be a valid number.`); + } + return port; +})(); + +// Client host URLs +export const ACCESS_CONTROL_API_HOST = requireEnvVar( + process.env.APP_ACCESS_CONTROL_API_HOST, + 'APP_ACCESS_CONTROL_API_HOST' +); +export const SIMS_API_HOST = requireEnvVar(process.env.APP_SIMS_API_HOST, 'APP_SIMS_API_HOST'); diff --git a/platforms/access-bridge/src/controllers/access-controller.ts b/platforms/access-bridge/src/controllers/access-controller.ts new file mode 100644 index 000000000..e6f6208b4 --- /dev/null +++ b/platforms/access-bridge/src/controllers/access-controller.ts @@ -0,0 +1,63 @@ +import { Request, Response, NextFunction } from 'express'; + +import { ErrorDefinitions, sendErrors } from '../errors.js'; +import { PassportService } from '../services/passport-service.js'; +import { PlansService } from '../services/plans-service.js'; +import { IdentityService, Viewer } from '../services/identity-service.js'; + +/** + * Controller class responsible for handling access-related services. + * The controller interacts with services for identity management, plans management, and passport generation. + */ +export class AccessController { + private readonly identityService: IdentityService; + private readonly passportService: PassportService; + private readonly plansService: PlansService; + + constructor() { + this.identityService = new IdentityService(); + this.passportService = new PassportService(); + this.plansService = new PlansService(); + } + + /** + * Service handler for generating passport access tokens based on the provided authorization token. + * Retrieves access control plans and generates access tokens. + */ + async generatePassport(req: Request, res: Response, next: NextFunction): Promise { + // Anonymous is default for not authenticated viewers. + // These viewers only have access to free plans. + let viewer: Viewer = { id: 'anonymous', email: '' }; + + const authorization = req.headers['authorization']; + if (authorization) { + viewer = await this.identityService.getAccount({ authorization }); + if (!viewer.id || !viewer.email) { + sendErrors(res, ErrorDefinitions.UnauthorizedError.create()); + return; + } + } + + const viewerEntitledPlans = await this.plansService.getEntitledPlans({ authorization }); + const plans = viewerEntitledPlans + .map((plan) => ({ + id: plan.id, + exp: plan?.exp, + })) + .filter((plan) => plan.id !== undefined && plan.exp !== undefined); + + const passport = await this.passportService.generatePassport({ viewerId: viewer.id, plans }); + + res.json(passport); + } + + /** + * Service handler for refreshing access tokens based on the provided refresh token. + */ + async refreshPassport(req: Request, res: Response, next: NextFunction): Promise { + const { refresh_token: refreshToken } = req.body; + const passport = await this.passportService.refreshPassport({ refreshToken }); + + res.json(passport); + } +} diff --git a/platforms/access-bridge/src/controllers/checkout-controller.ts b/platforms/access-bridge/src/controllers/checkout-controller.ts new file mode 100644 index 000000000..5610000ba --- /dev/null +++ b/platforms/access-bridge/src/controllers/checkout-controller.ts @@ -0,0 +1,71 @@ +import { Request, Response, NextFunction } from 'express'; + +import { ErrorDefinitions, sendErrors } from '../errors.js'; +import { IdentityService } from '../services/identity-service.js'; +import { PaymentService } from '../services/payment-service.js'; +import { StripePaymentService } from '../services/stripe-payment-service.js'; + +/** + * Controller class responsible for handling payment checkout session URLs, where the viewers can complete the payment. + */ +export class CheckoutController { + private readonly identityService: IdentityService; + private readonly paymentService: PaymentService; + + constructor() { + this.identityService = new IdentityService(); + this.paymentService = new StripePaymentService(); + } + + /** + * Service handler for initiating a Payment Checkout session based on the provided checkout params. + * @returns A Promise that resolves with a response containing the URL for the Payment Provider Checkout session. + */ + async initiateCheckout(req: Request, res: Response, next: NextFunction): Promise { + const authorization = req.headers['authorization']; + if (!authorization) { + sendErrors(res, ErrorDefinitions.UnauthorizedError.create()); + return; + } + + const checkoutParams = req.body; + const validationError = this.paymentService.validateCheckoutParams(checkoutParams); + if (validationError) { + sendErrors(res, ErrorDefinitions.ParameterMissingError.create({ parameterName: validationError })); + return; + } + + const viewer = await this.identityService.getAccount({ authorization }); + const checkoutSessionUrl = await this.paymentService.createCheckoutSessionUrl(viewer, checkoutParams); + + res.json({ url: checkoutSessionUrl }); + } + + /** + * Service handler for generating a Billing portal session URL based on the provided viewer. + * Viewers are redirected to this URL where they can manage their purchase info. + * @returns A Promise that resolves with a response containing the URL for the Billing Portal session. + */ + async generateBillingPortalURL(req: Request, res: Response, next: NextFunction): Promise { + const authorization = req.headers['authorization']; + if (!authorization) { + sendErrors(res, ErrorDefinitions.UnauthorizedError.create()); + return; + } + + const viewer = await this.identityService.getAccount({ authorization }); + if (!viewer.id || !viewer.email) { + sendErrors(res, ErrorDefinitions.UnauthorizedError.create()); + return; + } + + const { return_url } = req.body; + if (!return_url) { + sendErrors(res, ErrorDefinitions.ParameterMissingError.create({ parameterName: 'return_url' })); + return; + } + + const billingPortalSessionUrl = await this.paymentService.createBillingPortalSessionUrl(viewer, return_url); + res.json({ url: billingPortalSessionUrl }); + } +} diff --git a/platforms/access-bridge/src/controllers/products-controller.ts b/platforms/access-bridge/src/controllers/products-controller.ts new file mode 100644 index 000000000..dc48d6c3e --- /dev/null +++ b/platforms/access-bridge/src/controllers/products-controller.ts @@ -0,0 +1,32 @@ +import { Request, Response, NextFunction } from 'express'; + +import { PlansService } from '../services/plans-service.js'; +import { StripePaymentService } from '../services/stripe-payment-service.js'; +import { PaymentService } from '../services/payment-service.js'; +/** + * Controller class responsible for handling AC plans and Stripe products. + */ +export class ProductsController { + private readonly plansService: PlansService; + private readonly paymentService: PaymentService; + + constructor() { + this.plansService = new PlansService(); + this.paymentService = new StripePaymentService(); + } + + /** + * Service handler for fetching and returning products from the used Provider with prices based on available plans. + * Retrieves and filters SIMS plans, then matches them with the Payment products based on the external provider IDs. + */ + async getProducts(req: Request, res: Response, next: NextFunction): Promise { + const availablePlans = await this.plansService.getAvailablePlans(); + const stripeProductIds: string[] = availablePlans + // Currently, we only support Stripe as a payment provider, so we filter and use only Stripe product IDs. + .map((plan) => plan.metadata.external_providers?.stripe ?? []) + .flat(); + + const products = await this.paymentService.getProductsWithPrices(stripeProductIds); + res.json(products); + } +} diff --git a/platforms/access-bridge/src/errors.ts b/platforms/access-bridge/src/errors.ts new file mode 100644 index 000000000..0de8c886f --- /dev/null +++ b/platforms/access-bridge/src/errors.ts @@ -0,0 +1,176 @@ +import Stripe from 'stripe'; +import { Response } from 'express'; +import { JWError, JWErrorResponse } from '@jwp/ott-common/types/errors.js'; + +// Define types for error codes and status codes +export type ErrorCode = keyof typeof ErrorDefinitions; +export type ErrorStatusCode = (typeof ErrorDefinitions)[ErrorCode]['statusCode']; + +// Define context types for each error +interface BaseContext { + description?: string; +} + +interface ParameterMissingContext extends BaseContext { + parameterName: string; +} + +interface ParameterInvalidContext extends BaseContext { + parameterName: string; + reason?: string; +} + +// Unified Error Definitions with details and creation functions +export const ErrorDefinitions = { + BadRequestError: { + code: 'bad_request', + statusCode: 400, + description: 'The request was not constructed correctly.', + create: (context?: BaseContext) => new AccessBridgeError('BadRequestError', context), + }, + ParameterMissingError: { + code: 'parameter_missing', + statusCode: 400, + description: 'Required parameter is missing.', + create: (context: Partial) => { + const description = context.description + ? context.description + : context.parameterName + ? `Required parameter ${context.parameterName} is missing.` + : 'Required parameter is missing.'; + + return new AccessBridgeError('ParameterMissingError', { + ...context, + description, + }); + }, + }, + ParameterInvalidError: { + code: 'parameter_invalid', + statusCode: 400, + description: 'Parameter is invalid.', + create: (context: Partial) => { + const parameterName = context.parameterName ? `Parameter ${context.parameterName}` : 'Parameter'; + const reason = context.reason ? ` ${context.reason}` : ''; + const description = `${parameterName} is invalid.${reason}`; + + return new AccessBridgeError('ParameterInvalidError', { + ...context, + description, + }); + }, + }, + UnauthorizedError: { + code: 'unauthorized', + statusCode: 401, + description: 'Missing or invalid auth credentials.', + create: (context?: BaseContext) => new AccessBridgeError('UnauthorizedError', context), + }, + ForbiddenError: { + code: 'forbidden', + statusCode: 403, + description: 'Access to the requested resource is not allowed.', + create: (context?: BaseContext) => new AccessBridgeError('ForbiddenError', context), + }, + NotFoundError: { + code: 'not_found', + statusCode: 404, + description: 'The requested resource could not be found.', + create: (context?: BaseContext) => new AccessBridgeError('NotFoundError', context), + }, + MethodNotAllowedError: { + code: 'method_not_allowed', + statusCode: 405, + description: 'The used HTTP method is not supported on the given resource.', + create: (context?: BaseContext) => new AccessBridgeError('MethodNotAllowedError', context), + }, + InternalError: { + code: 'internal_error', + statusCode: 500, + description: 'An error was encountered while processing the request. Please try again.', + create: (context?: BaseContext) => new AccessBridgeError('InternalError', context), + }, +} as const; + +// Create the base class for errors +export class AccessBridgeError extends Error { + private readonly errorKey: keyof typeof ErrorDefinitions; + private readonly context?: BaseContext; + + constructor(errorKey: keyof typeof ErrorDefinitions, context?: BaseContext) { + super(); + this.errorKey = errorKey; + this.context = context; + } + + get code(): string { + return ErrorDefinitions[this.errorKey].code; + } + + get statusCode(): ErrorStatusCode { + return ErrorDefinitions[this.errorKey].statusCode; + } + + protected get defaultDescription(): string { + return ErrorDefinitions[this.errorKey].description; + } + + get description(): string { + return this.context?.description || this.defaultDescription; + } +} + +// Send errors +export function sendErrors(res: Response, error: AccessBridgeError): void { + const statusCode = error.statusCode; + res.status(statusCode).json({ + errors: [ + { + code: error.code, + description: error.description, + }, + ], + }); +} + +// Type guard to check if the error is a JWErrorResponse +export function isJWError(error: unknown): error is JWErrorResponse { + return ( + typeof error === 'object' && + error !== null && + 'errors' in error && + Array.isArray((error as { errors: unknown }).errors) && + (error as { errors: unknown[] }).errors.every( + (e) => typeof (e as JWError).code === 'string' && typeof (e as JWError).description === 'string' + ) + ); +} + +// Utility function to handle JW errors +export function handleJWError(error: JWErrorResponse): AccessBridgeError { + const jwError = error.errors[0]; + const { code, description } = jwError; + const errorDefinition = Object.keys(ErrorDefinitions).find( + (key) => ErrorDefinitions[key as keyof typeof ErrorDefinitions].code === code + ); + if (errorDefinition) { + return ErrorDefinitions[errorDefinition as keyof typeof ErrorDefinitions].create({ description }); + } + + // Fallback to a generic BadRequestError if no specific match is found + return ErrorDefinitions.BadRequestError.create({ description }); +} + +// Utility function to handle Stripe errors +export function handleStripeError(error: Stripe.errors.StripeError): AccessBridgeError { + if (error.type === 'StripeInvalidRequestError') { + throw ErrorDefinitions.BadRequestError.create({ description: error.message }); + } else if (error.type === 'StripeAuthenticationError') { + throw ErrorDefinitions.UnauthorizedError.create({ description: error.message }); + } else if (error.type === 'StripePermissionError') { + throw ErrorDefinitions.ForbiddenError.create({ description: error.message }); + } + + // Fallback to a generic BadRequestError for unexpected Stripe errors + throw ErrorDefinitions.BadRequestError.create({ description: error.message }); +} diff --git a/platforms/access-bridge/src/http.ts b/platforms/access-bridge/src/http.ts new file mode 100644 index 000000000..a1496e84f --- /dev/null +++ b/platforms/access-bridge/src/http.ts @@ -0,0 +1,91 @@ +/** + * Performs a GET request using fetch with optional authentication token. + * @param url The URL to fetch. + * @param token Optional authentication token for authorization header. + * @returns A promise that resolves to the parsed JSON response. + */ +export async function get(url: string, token?: string): Promise { + const headers: HeadersInit = { + 'Content-Type': 'application/json', + }; + + if (token) { + headers['Authorization'] = token; + } + + const response = await fetch(url, { + method: 'GET', + headers, + }); + + return await handleResponse(response); +} + +/** + * Performs a POST request using fetch with JSON body and optional authentication token. + * @param url The URL to fetch. + * @param body The JSON body to send in the POST request. + * @param token Optional authentication token for authorization header. + * @returns A promise that resolves to the parsed JSON response. + */ +export async function post(url: string, body: U, token?: string): Promise { + const headers: HeadersInit = { + 'Content-Type': 'application/json', + }; + + if (token) { + headers['Authorization'] = token; + } + + const response = await fetch(url, { + method: 'POST', + headers, + body: JSON.stringify(body), + }); + + return await handleResponse(response); +} + +/** + * Performs a PUT request using fetch with JSON body and optional authentication token. + * @param url The URL to fetch. + * @param body The JSON body to send in the PUT request. + * @param token Optional authentication token for authorization header. + * @returns A promise that resolves to the parsed JSON response. + */ +export async function put(url: string, body: U, token?: string): Promise { + const headers: HeadersInit = { + 'Content-Type': 'application/json', + }; + + if (token) { + headers['Authorization'] = token; + } + + const response = await fetch(url, { + method: 'PUT', + headers, + body: JSON.stringify(body), + }); + + return await handleResponse(response); +} + +// Handles the response from a fetch request, throwing an error if the response is not ok. +async function handleResponse(response: Response): Promise { + if (!response.ok) { + let errorData; + try { + errorData = await response.json(); + } catch (error) { + // If parsing JSON fails, throw with status text + throw new Error(`${response.status}, ${response.statusText}`); + } + + // Throw the original error data as-is + throw errorData; + } + + // If response is ok, return the parsed JSON content + return response.json(); +} diff --git a/platforms/access-bridge/src/main.ts b/platforms/access-bridge/src/main.ts new file mode 100644 index 000000000..36a037760 --- /dev/null +++ b/platforms/access-bridge/src/main.ts @@ -0,0 +1,47 @@ +import { BIND_ADDR, BIND_PORT } from './app-config.js'; +import { initializeRoutes } from './pipeline/routes.js'; +import { Server } from './server.js'; +import logger from './pipeline/logger.js'; + +if (BIND_PORT <= 0 || BIND_PORT > 65535) { + logger.error('Error: BIND_PORT must be a valid port number between 1 and 65535.'); + process.exit(1); +} + +const server = new Server(BIND_ADDR, BIND_PORT, initializeRoutes); + +async function startServer() { + try { + await server.listen(); + } catch (err) { + logger.error('Failed to start server:', err); + process.exit(1); + } +} + +startServer(); + +// Gracefully handle termination signals (e.g., Ctrl+C) +process.on('SIGINT', async () => { + logger.info('Shutting down server gracefully..'); + try { + await server.close(); + logger.info('Server closed.'); + process.exit(0); + } catch (err) { + logger.error('Error closing server:', err); + process.exit(1); + } +}); + +// Handle uncaught exceptions +process.on('uncaughtException', (err) => { + logger.error('Uncaught exception occurred:', err); + process.exit(1); +}); + +// Handle unhandled promise rejections +process.on('unhandledRejection', (reason, promise) => { + logger.error(`Unhandled Rejection at: ${promise}:`, reason); + process.exit(1); +}); diff --git a/platforms/access-bridge/src/pipeline/logger.ts b/platforms/access-bridge/src/pipeline/logger.ts new file mode 100644 index 000000000..4f6dabae9 --- /dev/null +++ b/platforms/access-bridge/src/pipeline/logger.ts @@ -0,0 +1,79 @@ +import * as Sentry from '@sentry/node'; +import { nodeProfilingIntegration } from '@sentry/profiling-node'; + +/** + * Logger class for handling various log levels and sending error reports to Sentry if integrated. + */ +class Logger { + constructor() { + this.initializeSentry(); + } + + /** + * Configures the Sentry client for error and performance monitoring. + * Reads Sentry configuration from environment variables and initializes Sentry if a DSN is provided. + */ + private initializeSentry() { + if (process.env.NODE_ENV !== 'production' && process.env.NODE_ENV !== 'test') { + return; // Skip Sentry initialization for non-production/test environments + } + + const SENTRY_DSN = process.env.APP_SENTRY_DSN || ''; + const SENTRY_TRACE_RATE = parseFloat(process.env.APP_SENTRY_TRACE_RATE || '1.0'); + + if (SENTRY_DSN) { + Sentry.init({ + dsn: SENTRY_DSN, + integrations: [nodeProfilingIntegration()], + environment: process.env.MODE || 'development', + release: process.env.APP_VERSION, + tracesSampleRate: SENTRY_TRACE_RATE, + profilesSampleRate: SENTRY_TRACE_RATE, + }); + } + } + + /** + * Logs an error message and sends it to Sentry if integrated. + * @param message - The error message to log. + * @param error - An optional error object to capture. + */ + error(message: string, error?: unknown) { + console.error(message, error ?? ''); + + if (Sentry.getClient()) { + if (error) { + Sentry.captureException(error); + } else { + Sentry.captureMessage(message, 'error'); + } + } + } + + /** + * Logs an informational message and sends it to Sentry if integrated. + * @param message - The informational message to log. + */ + info(message: string) { + console.info(message); + + if (Sentry.getClient()) { + Sentry.captureMessage(message, 'info'); + } + } + + /** + * Logs a warning message and sends it to Sentry if integrated. + * @param message - The warning message to log. + */ + warn(message: string) { + console.warn(message); + + if (Sentry.getClient()) { + Sentry.captureMessage(message, 'warning'); + } + } +} + +const logger = new Logger(); +export default logger; diff --git a/platforms/access-bridge/src/pipeline/middleware.ts b/platforms/access-bridge/src/pipeline/middleware.ts new file mode 100644 index 000000000..74615547d --- /dev/null +++ b/platforms/access-bridge/src/pipeline/middleware.ts @@ -0,0 +1,113 @@ +import * as Sentry from '@sentry/node'; +import Stripe from 'stripe'; +import express, { Request, Response, NextFunction, Express } from 'express'; +import cors from 'cors'; + +import { + AccessBridgeError, + ErrorDefinitions, + handleJWError, + handleStripeError, + isJWError, + sendErrors, +} from '../errors.js'; +import { SITE_ID } from '../app-config.js'; + +import logger from './logger.js'; + +/** + * Middleware class encapsulates global and route-specific middleware. + */ +export class Middleware { + /** + * This ensures that any errors in async operations are caught and passed to the global error handler + */ + asyncWrapper(fn: (req: Request, res: Response, next: NextFunction) => Promise) { + return (req: Request, res: Response, next: NextFunction) => { + Promise.resolve(fn(req, res, next)).catch(next); + }; + } + + /** + * Middleware to validate 'site_id' parameter. + * This should be applied to routes that require site_id validation. + */ + validateSiteId = (req: Request, res: Response, next: NextFunction) => { + if (req.params.site_id !== SITE_ID) { + sendErrors(res, ErrorDefinitions.ParameterInvalidError.create({ parameterName: 'site_id' })); + return; + } + next(); + }; + + /** + * Global error handler middleware for the server. + * This handles AccessBridge-specific errors and other unexpected errors. + */ + globalErrorHandler = (err: unknown, req: Request, res: Response, next: NextFunction) => { + // Handles SyntaxError in request body by responding with a ParameterInvalidError. + if (err instanceof SyntaxError && 'body' in err) { + sendErrors(res, ErrorDefinitions.ParameterInvalidError.create({ parameterName: 'body' })); + return; + } + + if (err instanceof AccessBridgeError) { + sendErrors(res, err); + return; + } + + if (err instanceof Stripe.errors.StripeError) { + const accessBridgeError = handleStripeError(err); + sendErrors(res, accessBridgeError); + } + + if (isJWError(err)) { + const accessBridgeError = handleJWError(err); + sendErrors(res, accessBridgeError); + return; + } + + logger.error('Unexpected error:', err); + sendErrors(res, ErrorDefinitions.InternalError.create()); + }; + + /** + * Middleware to handle 404 Not Found errors. + * This should be registered after all routes to handle undefined endpoints. + */ + notFoundErrorHandler = (req: Request, res: Response, next: NextFunction) => { + sendErrors(res, ErrorDefinitions.NotFoundError.create()); + }; + + /** + * Registers global middlewares. + * @param app The Express application. + */ + initializeCoreMiddleware(app: Express) { + // Middleware to enable Cross-Origin Resource Sharing (CORS) + app.use(cors()); + // Middleware for parsing JSON request bodies + app.use(express.json()); + } + + /** + * Registers global error handling middleware. + * This should be called after all routes are defined to catch errors and handle 404 responses. + * @param app The Express application. + */ + initializeErrorMiddleware(app: Express) { + app.use(this.notFoundErrorHandler); // Handle 404 errors + app.use(this.globalErrorHandler); // Handle all other errors + } + + /** + * Registers Sentry error handler for Expresss. + * The error handler must be registered before any other error middleware and after all controllers + * @param app The Express application. + */ + initializeSentryMiddleware(app: Express) { + if (Sentry.getClient()) { + Sentry.setupExpressErrorHandler(app); + } + } +} diff --git a/platforms/access-bridge/src/pipeline/routes.ts b/platforms/access-bridge/src/pipeline/routes.ts new file mode 100644 index 000000000..db4c2abbe --- /dev/null +++ b/platforms/access-bridge/src/pipeline/routes.ts @@ -0,0 +1,37 @@ +import { Express, Request, Response, NextFunction } from 'express'; + +import { AccessController } from '../controllers/access-controller.js'; +import { ProductsController } from '../controllers/products-controller.js'; +import { CheckoutController } from '../controllers/checkout-controller.js'; + +import { Middleware } from './middleware.js'; + +const middleware = new Middleware(); +const accessController = new AccessController(); +const productsController = new ProductsController(); +const checkoutController = new CheckoutController(); + +/* Disable ESLint max-len and Prettier formatting to keep routes on one line for better readability. */ +/* eslint-disable max-len */ +// prettier-ignore +export function initializeRoutes(app: Express) { + // Register routes with their respective controller methods + addRoute(app, 'put', '/v2/sites/:site_id/access/generate', accessController.generatePassport.bind(accessController)); + addRoute(app, 'put', '/v2/sites/:site_id/access/refresh', accessController.refreshPassport.bind(accessController)); + addRoute(app, 'get', '/v2/sites/:site_id/products', productsController.getProducts.bind(productsController)); + addRoute(app, 'post', '/v2/sites/:site_id/checkout', checkoutController.initiateCheckout.bind(checkoutController)); + addRoute(app, 'post', '/v2/sites/:site_id/billing-portal', checkoutController.generateBillingPortalURL.bind(checkoutController)); +} + +// Adds a route to the Express application with the specified HTTP method, path, and handler. +export function addRoute( + app: Express, + method: 'get' | 'post' | 'put', + path: string, + handler: (req: Request, res: Response, next: NextFunction) => Promise, + customMiddleware: Array<(req: Request, res: Response, next: NextFunction) => void> = [middleware.validateSiteId] +) { + // By default, validateSiteId middleware is added on each registered route with possibility to override. + // asyncWrapper ensures that any errors in async operations are caught and passed to the global error handler. + app[method](path, [...customMiddleware, middleware.asyncWrapper(handler)]); +} diff --git a/platforms/access-bridge/src/server.ts b/platforms/access-bridge/src/server.ts new file mode 100644 index 000000000..9bc13080a --- /dev/null +++ b/platforms/access-bridge/src/server.ts @@ -0,0 +1,87 @@ +import { Server as HTTPServer } from 'http'; + +import express, { Express } from 'express'; + +import { Middleware } from './pipeline/middleware.js'; +import logger from './pipeline/logger.js'; + +/** + * Server class that initializes and manages an Express application with error handling. + */ +export class Server { + private app: Express; + private middleware: Middleware; + private httpServer: HTTPServer | null; + private address: string; + private port: number; + + /** + * Creates an instance of the Server class. + * @param address - Address to bind the server to + * @param port - Port to bind the server to + * @param initializeRoutes - Function to register routes + */ + constructor(address: string, port: number, initializeRoutes: (app: Express) => void) { + this.app = express(); + this.middleware = new Middleware(); + this.httpServer = null; + this.address = address; + this.port = port; + this.initialize(initializeRoutes); + } + + /** + * Initializes the server with middlewares and routes. + * @param initializeRoutes - Function to initialize the defined routes + */ + private initialize(initializeRoutes: (app: Express) => void) { + // Initialize core middleware, e.g., CORS, JSON parsing. + this.middleware.initializeCoreMiddleware(this.app); + + // Initialize the defined routes + initializeRoutes(this.app); + + // Initialize Sentry error handler for Expresss + this.middleware.initializeSentryMiddleware(this.app); + + // Initialize error middleware after all routes are registered + this.middleware.initializeErrorMiddleware(this.app); + } + + /** + * Starts the server and listens on the specified port. + * @returns A promise that resolves to the port the server is listening on + */ + public async listen(): Promise { + return new Promise((resolve, reject) => { + this.httpServer = this.app.listen(this.port, this.address, () => { + this.port = (this.httpServer?.address() as { port: number })?.port || this.port; + logger.info(`Server listening at http://${this.address}:${this.port}`); + resolve(this.port); + }); + + this.httpServer.on('error', (err: Error) => { + reject(err); + }); + }); + } + + /** + * Closes the server connection. + * @returns A promise that resolves when the server is closed + */ + public async close(): Promise { + if (!this.httpServer) { + return Promise.resolve(); + } + return new Promise((resolve, reject) => { + this.httpServer?.close((err?: Error) => { + if (err) { + reject(err); + } else { + resolve(); + } + }); + }); + } +} diff --git a/platforms/access-bridge/src/services/identity-service.ts b/platforms/access-bridge/src/services/identity-service.ts new file mode 100644 index 000000000..24a3da98f --- /dev/null +++ b/platforms/access-bridge/src/services/identity-service.ts @@ -0,0 +1,31 @@ +import { SIMS_API_HOST } from '../app-config.js'; +import { ErrorDefinitions } from '../errors.js'; +import { get } from '../http.js'; + +export type Viewer = { + id: string; + email: string; +}; + +/** + * Service class responsible for interacting with the Account API that handles identity information. + */ +export class IdentityService { + /** + * Retrieves viewer information based on the provided Authorization token. + * + * @param authorization The Bearer token used to authenticate the request. + * @returns A Promise that resolves to an Account object. + */ + async getAccount({ authorization }: { authorization: string }): Promise { + const account = await get(`${SIMS_API_HOST}/v3/accounts`, authorization); + if (!account) { + throw ErrorDefinitions.NotFoundError.create({ description: 'Account not found.' }); + } + + return { + id: account.id.toString(), + email: account.email, + }; + } +} diff --git a/platforms/access-bridge/src/services/passport-service.ts b/platforms/access-bridge/src/services/passport-service.ts new file mode 100644 index 000000000..8e1cdc20a --- /dev/null +++ b/platforms/access-bridge/src/services/passport-service.ts @@ -0,0 +1,68 @@ +import jwt from 'jsonwebtoken'; +import { PassportResponse } from '@jwp/ott-common/types/passport.js'; +import { AccessControlPlan } from '@jwp/ott-common/types/plans.js'; + +import { ACCESS_CONTROL_API_HOST, API_SECRET, SITE_ID } from '../app-config.js'; +import { put } from '../http.js'; + +type GeneratePassportParams = { + viewerId: string; + plans: AccessControlPlan[]; +}; + +/** + * PassportService handles interactions with the passport APIs. + * It provides methods to generating access tokens (passport and refresh token). + */ +export class PassportService { + /** + * Generate access tokens for a specific site and subscriber. + * @param email The subscriber's email address. + * @param plans Array of access plans for the subscriber. + * @returns A Promise resolving to an AccessTokensResponse. + */ + async generatePassport({ viewerId, plans }: GeneratePassportParams): Promise { + const url = await this.generateSignedUrl(`/v2/sites/${SITE_ID}/access/generate`); + const payload = { + subscriber_info: { + email: viewerId, + plans, + }, + }; + + return await put(url, payload); + } + + /** + * Refresh access tokens for a specific site using a refresh token. + * @param refreshToken The refresh token to use for token refresh. + * @returns A Promise resolving to an AccessTokensResponse. + */ + async refreshPassport({ refreshToken }: { refreshToken: string }): Promise { + const url = await this.generateSignedUrl(`/v2/sites/${SITE_ID}/access/refresh`); + const payload = { + refresh_token: refreshToken, + }; + + return await put(url, payload); + } + + // URL signer - needed for validating requests on Delivery Gateway + // More about this: https://docs.jwplayer.com/platform/reference/protect-your-content-with-signed-urls + async generateSignedUrl(path: string, host: string = ACCESS_CONTROL_API_HOST): Promise { + const now = new Date(); + const token = jwt.sign( + { + // Sets expiration 3.6 seconds from now, rounded up to align with the next 300 ms interval for consistency. + exp: Math.ceil((now.getTime() + 3600) / 300) * 300, + resource: path, + }, + API_SECRET, + { + noTimestamp: true, + } + ); + + return `${host}${path}?token=${token}`; + } +} diff --git a/platforms/access-bridge/src/services/payment-service.ts b/platforms/access-bridge/src/services/payment-service.ts new file mode 100644 index 000000000..3980cc62e --- /dev/null +++ b/platforms/access-bridge/src/services/payment-service.ts @@ -0,0 +1,44 @@ +import type { CheckoutParams, Product } from '@jwp/ott-common/types/payment.js'; + +import { Viewer } from './identity-service'; +/** + * PaymentService interface defines the contract for payment service implementations. + * Any class implementing this should handle products, prices, checkout and payment, + * from a specific payment provider (e.g., Stripe, Google, Apple). + */ +export interface PaymentService { + /** + * Retrieves products with prices based on the provided product IDs. + * The implementation should interact with the payment provider's API to fetch products and prices details. + * + * @param productIds - An array of product IDs to match and filter the provider's store products. + * @returns A Promise that resolves to an array of products, each containing associated price details. + */ + getProductsWithPrices(productIds: string[]): Promise; + + /** + * Creates a checkout session based on the provided viewer and checkout parameters. + * This method should be implemented by each provider's payment service. + * + * @param viewer The viewer making the payment (e.g., their email and ID). + * @param params The generic checkout parameters that will be customized for each payment provider. + * @returns A Promise resolving to a checkout session URL depending on the provider. + */ + createCheckoutSessionUrl(viewer: Viewer, params: CheckoutParams): Promise; + + /** + * Validates the provided checkout parameters based on the specific provider's requirements. + * @param params - The checkout parameters to validate. + * @returns An error string if validation fails, or null if validation succeeds. + */ + validateCheckoutParams(params: CheckoutParams): string | null; + + /** + * Creates a billing portal session URL for the provided viewer and redirectUrl. + * The generated session URL redirects the viewer where they can update their purchase info. + * @param viewer The viewer making the payment (e.g., their email and ID). + * @param returnUrl The url where to redirect them after completion of the updates. + * @returns A Promise resolving to the URL of the billing portal session. + */ + createBillingPortalSessionUrl(viewer: Viewer, returnUrl: string): Promise; +} diff --git a/platforms/access-bridge/src/services/plans-service.ts b/platforms/access-bridge/src/services/plans-service.ts new file mode 100644 index 000000000..effc5da67 --- /dev/null +++ b/platforms/access-bridge/src/services/plans-service.ts @@ -0,0 +1,42 @@ +import { Plan, PlansResponse } from '@jwp/ott-common/types/plans.js'; + +import { SIMS_API_HOST, SITE_ID } from '../app-config.js'; +import { get } from '../http.js'; + +/** + * Service class responsible for interacting with the Plans API. + */ +export class PlansService { + /** + * Fetches a list of plans available for a specific site. + * These are the plans defined by the customer that are available for purchase by viewers. + * + * @returns A Promise that resolves to an array of `Plan` objects. + * If no plans are available, an empty array is returned. + */ + async getAvailablePlans(): Promise { + const response = await get(`${SIMS_API_HOST}/v3/sites/${SITE_ID}/plans`); + if (!response?.plans) { + return []; + } + + return response.plans; + } + + /** + * Fetches a list of plans available / purchased for a specific site by the viewer. + * These plans are stored in the user's passport, enabling access to specific content. + * @param authorization The Bearer token used to authenticate the viewer and their entitlements. + * This parameter can be undefined if only free plans are to be fetched. + * @returns A Promise that resolves to an array of `Plan` objects. + * If no plans are available, an empty array is returned. + */ + async getEntitledPlans({ authorization }: { authorization?: string }): Promise { + const response = await get(`${SIMS_API_HOST}/v3/sites/${SITE_ID}/entitlements`, authorization); + if (!response?.plans) { + return []; + } + + return response.plans; + } +} diff --git a/platforms/access-bridge/src/services/stripe-payment-service.ts b/platforms/access-bridge/src/services/stripe-payment-service.ts new file mode 100644 index 000000000..1dcbb775d --- /dev/null +++ b/platforms/access-bridge/src/services/stripe-payment-service.ts @@ -0,0 +1,184 @@ +import Stripe from 'stripe'; +import { Product, Price, CheckoutParams } from '@jwp/ott-common/types/payment.js'; + +import { STRIPE_SECRET } from '../app-config.js'; +import { AccessBridgeError } from '../errors.js'; +import logger from '../pipeline/logger.js'; + +import { PaymentService } from './payment-service.js'; +import { Viewer } from './identity-service.js'; + +/** + * Service class responsible for interacting with the Stripe API to fetch products. + */ +export class StripePaymentService implements PaymentService { + private stripe: Stripe; + + constructor() { + this.stripe = new Stripe(STRIPE_SECRET, { + // By specifying an API version, we ensure that our integration continues to work + // as expected, even if new versions of the Stripe API are released. + // If no version is specified, the Stripe client will default to the account's current API version, + // which may lead to unexpected behavior if the account is upgraded to a newer API. + apiVersion: '2024-06-20', + }); + } + + /** + * Retrieves Stripe products with prices based on the provided productIds. + * Only products with valid prices are returned. + * @param productIds The array of product IDs to fetch. + * @returns A Promise resolving to an array of filtered Product objects. + */ + async getProductsWithPrices(productIds: string[]): Promise { + if (!productIds.length) { + return []; + } + + const productsWithPrices = await Promise.all( + productIds.map(async (productId) => { + try { + const product = await this.stripe.products.retrieve(productId); + if (!product.active) { + // Only include active products + return null; + } + + const prices = await this.stripe.prices.list({ product: product.id }); + if (!prices.data.length) { + // Only include products with prices + return null; + } + + const mappedPrices = this.mapPrices(prices.data); + + return this.mapProduct(product, mappedPrices); + } catch (error) { + console.error(`Failed to fetch product or prices for product ${productId}:`, error); + return null; // Skip products that fail to fetch prices + } + }) + ); + + // Filter out null products (those that failed to retrieve or have no prices) + return productsWithPrices.filter((product) => product !== null) as Product[]; + } + + /** + * Creates a Stripe Checkout session URL, where the viewer will be redirected to complete the payment. + * @param viewer Email address and viewer id from the auth token used for creating the checkout session. + * @param params Stripe checkout params to use for creating the checkout session. + * @returns A Promise resolving to a Stripe Checkout Session URL for the checkout page. + */ + async createCheckoutSessionUrl(viewer: Viewer, params: CheckoutParams): Promise { + const sessionParams: Stripe.Checkout.SessionCreateParams = { + payment_method_types: ['card'], + line_items: [ + { + price: params.price_id, + quantity: 1, + }, + ], + metadata: { + // This is very important as it's our only way of connecting the payment back to the viewer + viewer_id: viewer.id, + }, + customer_email: viewer.email, + mode: 'subscription', // at this moment we only support subscription mode + success_url: params.success_url, + cancel_url: params.cancel_url, + subscription_data: { + metadata: { + // This is very important as it's our only way of connecting the payment back to the viewer + viewer_id: viewer.id, + }, + }, + }; + + const checkoutSession = await this.stripe.checkout.sessions.create(sessionParams); + return checkoutSession.url; + } + + /** + * Creates a Stripe billing portal session for a given customer ID. + * @param customerId The ID of the customer for whom the session is created. + * @returns A Promise resolving to the URL of the billing portal session. + */ + async createBillingPortalSessionUrl(viewer: Viewer, returnUrl: string): Promise { + const customers = await this.stripe.customers.search({ + query: `email:"${viewer.email}"`, + }); + + if (customers.data.length === 0) { + logger.info(`Viewer 'id: ${viewer.id}, email: ${viewer.email}' does not exist in Stripe.`); + throw new AccessBridgeError('NotFoundError', { + description: `Viewer does not exist in Stripe.`, + }); + } + + // Create the billing portal session using the retrieved customer ID + const customerId = customers.data[0].id; + const session = await this.stripe.billingPortal.sessions.create({ + customer: customerId, + return_url: returnUrl, + }); + + return session.url; + } + + /** + * Validates the provided checkout parameters. + * Checks for the presence of required fields: 'price_id', 'success_url', and 'cancel_url'. + * If any required parameter is missing, returns an error message; otherwise, returns null. + * @param params - The checkout parameters to validate. + * @returns A string containing the name of the missing parameter if validation fails, + * or null if all required parameters are present. + */ + validateCheckoutParams(params: CheckoutParams): string | null { + const requiredParams: (keyof CheckoutParams)[] = ['price_id', 'success_url', 'cancel_url']; + const missingParam = requiredParams.find((param) => !params[param]); + return missingParam ? `Missing required parameter: ${missingParam}` : null; + } + + /** + * Maps the Stripe product to our custom Product type. + * @param product The Stripe product object. + * @param prices The list of custom Price objects mapped from Stripe prices. + * @returns A Product object with the required fields. + */ + private mapProduct(product: Stripe.Product, prices: Price[]): Product { + return { + store_product_id: product.id, + name: product.name, + description: product.description ?? '', + default_store_price_id: product.default_price as string, + prices: prices, + }; + } + + /** + * Maps Stripe prices to our custom Price type. + * @param stripePrices The list of Stripe price objects. + * @returns A list of custom Price objects. + */ + private mapPrices(stripePrices: Stripe.Price[]): Price[] { + return stripePrices.map((price) => ({ + store_price_id: price.id, + currencies: { + [price.currency]: { + amount: price.unit_amount, + }, + }, + default_currency: price.currency, + recurrence: price.recurring + ? { + interval: price.recurring.interval, + duration: price.recurring.interval_count ?? 1, + trial_period_interval: 'day', // Stripe only supports day for trial period. + trial_period_duration: price.recurring.trial_period_days ?? null, + } + : 'one_time', // Set 'one_time' if there's no recurrence. + billing_scheme: 'per_unit', // We only support `per_unit` scheme. + })); + } +} diff --git a/platforms/access-bridge/stylelint.config.cjs b/platforms/access-bridge/stylelint.config.cjs new file mode 100644 index 000000000..67e3aa538 --- /dev/null +++ b/platforms/access-bridge/stylelint.config.cjs @@ -0,0 +1,3 @@ +module.exports = { + extends: ['stylelint-config-jwp'], +}; diff --git a/platforms/access-bridge/test/fixtures.ts b/platforms/access-bridge/test/fixtures.ts new file mode 100644 index 000000000..38a72750e --- /dev/null +++ b/platforms/access-bridge/test/fixtures.ts @@ -0,0 +1,234 @@ +import Stripe from 'stripe'; +import { Plan } from '@jwp/ott-common/types/plans.js'; +import { Price, Product } from '@jwp/ott-common/types/payment.js'; + +import { Viewer } from '../src/services/identity-service'; +import { ErrorDefinitions } from '../src/errors.js'; + +// Utility function to get Unix timestamp +export const getTimestamp = (daysOffset: number): number => { + const now = new Date(); + now.setDate(now.getDate() + daysOffset); + return Math.floor(now.getTime() / 1000); +}; + +// Precompute timestamps +const FUTURE_EXPIRY = getTimestamp(30); // 30 days from now +const PAST_EXPIRY = getTimestamp(-30); // 30 days ago + +// API endpoints constant +export const ENDPOINTS = { + GENERATE_PASSPORT: '/v2/sites/:site_id/access/generate', + REFRESH_PASSPORT: '/v2/sites/:site_id/access/refresh', + PRODUCTS: '/v2/sites/:site_id/products', + CHECKOUT: '/v2/sites/:site_id/checkout', + BILLING_PORTAL: '/v2/sites/:site_id/billing-portal', +}; + +// mock data for access tokens +export const ACCESS_TOKENS = { + PASSPORT: { + VALID: 'valid-passport', + INVALID: 'invalid-passport', + }, + REFRESH_TOKEN: { + VALID: 'valid-refresh-token', + INVALID: 'invalid-refresh-token', + }, +}; + +export const VALID_PLAN_ID = 'plan1234'; +export const VIEWER: Viewer = { + id: '123456', + email: 'dummy@test.com', +}; + +// Plan mock creation function +const createMockPlan = ({ id, original_id, exp, metadata }: Plan): Plan => ({ + id, + original_id, + exp, + metadata: { + name: metadata.name || '', + access: { + drm_policy_id: metadata.access.drm_policy_id, + include_tags: metadata.access.include_tags || [], + exclude_tags: metadata.access.exclude_tags || [], + include_custom_params: metadata.access.include_custom_params || [], + exclude_custom_params: metadata.access.exclude_custom_params || [], + }, + access_model: metadata.access_model, + external_providers: metadata.external_providers || {}, + }, +}); + +export const PLANS = { + VALID: [ + createMockPlan({ + id: 'plan1234', + original_id: 123456, + exp: FUTURE_EXPIRY, + metadata: { + name: 'Test plan', + access: { + drm_policy_id: 'drm_policy_123', + include_tags: [], + exclude_tags: [], + include_custom_params: [], + exclude_custom_params: [], + }, + access_model: 'svod', + external_providers: { + stripe: 'stripe_id', + }, + }, + }), + ], + FREE: [ + createMockPlan({ + id: 'free1234', + original_id: 123457, + exp: FUTURE_EXPIRY, + metadata: { + name: 'Free plan', + access: { + drm_policy_id: 'drm_policy_456', + include_tags: [], + exclude_tags: [], + include_custom_params: [], + exclude_custom_params: [], + }, + access_model: 'free', + external_providers: {}, + }, + }), + ], + INVALID: [ + createMockPlan({ + id: 'plan123456', + original_id: 123458, + exp: FUTURE_EXPIRY, + metadata: { + name: 'Invalid plan', + access: { + drm_policy_id: 'drm_policy_789', + include_tags: [], + exclude_tags: [], + include_custom_params: [], + exclude_custom_params: [], + }, + access_model: 'svod', + external_providers: {}, + }, + }), + ], + EXPIRED: [ + createMockPlan({ + id: 'plan1234', + original_id: 123459, + exp: PAST_EXPIRY, + metadata: { + name: 'Expired plan', + access: { + drm_policy_id: 'drm_policy_101', + include_tags: [], + exclude_tags: [], + include_custom_params: [], + exclude_custom_params: [], + }, + access_model: 'svod', + external_providers: {}, + }, + }), + ], +}; + +// Valid and invalid site id mock +export const SITE_ID = { + VALID: 'test1234', + VALID_UPPER: 'A1B2C3D4', + INVALID: 'invalid1234', + SHORT: 'abc123', + LONG: 'abcd12345', + SPECIAL: 'abcd123!', + EMPTY: '', +}; + +// Authorization mock - valid and invalid token +export const AUTHORIZATION = { + VALID: 'Bearer valid-authorization', + INVALID: 'Bearer invalid-authorization', + MISSING: '', +}; + +// Store price mock +export const STORE_PRICE: Price = { + store_price_id: 'price_123456789', + currencies: { + usd: { + amount: 1000, // Amount in cents for USD + }, + }, + default_currency: 'usd', + recurrence: { + interval: 'month', + duration: 1, // Occurs every 1 month + trial_period_interval: 'month', // Free trial is based on months + trial_period_duration: 1, // 1 month trial + }, + billing_scheme: 'per_unit', // Only per_unit supported for now +}; + +// Store product mock +export const STORE_PRODUCT: Product = { + store_product_id: 'prod_123456789', + name: 'Sample Product', + description: 'A high-quality product description', + default_store_price_id: 'price_123456789', + prices: [STORE_PRICE], +}; + +// Dummy stripe customer id +export const STRIPE_CUSTOMER_ID = 'cus_Qi45IcSi81LstA'; + +// mock of the handled error cases for Stripe +export const STRIPE_ERRORS = [ + { + error: new Stripe.errors.StripeInvalidRequestError({ + type: 'invalid_request_error', + message: 'Invalid request', + }), + expectedCode: ErrorDefinitions.BadRequestError.code, + statusCode: 400, + }, + + { + error: new Stripe.errors.StripeAuthenticationError({ + type: 'authentication_error', + message: 'Not authenticated.', + }), + expectedCode: ErrorDefinitions.UnauthorizedError.code, + statusCode: 401, + }, + + { + error: new Stripe.errors.StripePermissionError({ + type: 'invalid_grant', + message: 'Permission error request.', + }), + expectedCode: ErrorDefinitions.ForbiddenError.code, + statusCode: 403, + }, + + { + error: new Stripe.errors.StripeAPIError({ + type: 'api_error', + message: 'Invalid request', + }), + expectedCode: ErrorDefinitions.BadRequestError.code, + statusCode: 400, + }, +]; + +// mock of stripe session url +export const STRIPE_SESSION_URL = 'https://example.com'; diff --git a/platforms/access-bridge/test/integration/access.test.ts b/platforms/access-bridge/test/integration/access.test.ts new file mode 100644 index 000000000..7e5de10a6 --- /dev/null +++ b/platforms/access-bridge/test/integration/access.test.ts @@ -0,0 +1,180 @@ +import http from 'http'; + +import { Express } from 'express'; +import { describe, it, beforeAll, afterAll, expect } from 'vitest'; + +import { AccessController } from '../../src/controllers/access-controller.js'; +import { MockServer } from '../mock-server.js'; +import { ACCESS_TOKENS, AUTHORIZATION, ENDPOINTS, SITE_ID } from '../fixtures.js'; +import { MockAccessController } from '../mocks/access.js'; +import { ErrorDefinitions } from '../../src/errors.js'; +import { addRoute } from '../../src/pipeline/routes.js'; +import { validateSiteId } from '../mocks/middleware.js'; + +describe('AccessController tests', () => { + let mockServer: MockServer; + let accessController: AccessController; + + beforeAll(async () => { + accessController = new MockAccessController(); + + const initializeRoutes = (app: Express) => { + addRoute(app, 'put', ENDPOINTS.GENERATE_PASSPORT, accessController.generatePassport.bind(accessController), [ + validateSiteId, + ]); + addRoute(app, 'put', ENDPOINTS.REFRESH_PASSPORT, accessController.refreshPassport.bind(accessController), [ + validateSiteId, + ]); + }; + + mockServer = await MockServer.create(initializeRoutes); + }); + + const generatePassportTestCases = [ + { + description: 'should generate passport access tokens without authorization', + requestOptions: { + method: 'PUT', + path: ENDPOINTS.GENERATE_PASSPORT.replace(':site_id', SITE_ID.VALID), + }, + expectedStatusCode: 200, + expectedResponse: { + passport: ACCESS_TOKENS.PASSPORT.VALID, + refresh_token: ACCESS_TOKENS.REFRESH_TOKEN.VALID, + }, + }, + { + description: 'should generate passport access tokens with valid authorization', + requestOptions: { + headers: { Authorization: AUTHORIZATION.VALID }, + method: 'PUT', + path: ENDPOINTS.GENERATE_PASSPORT.replace(':site_id', SITE_ID.VALID), + }, + expectedStatusCode: 200, + expectedResponse: { + passport: ACCESS_TOKENS.PASSPORT.VALID, + refresh_token: ACCESS_TOKENS.REFRESH_TOKEN.VALID, + }, + }, + { + description: 'should return UnauthorizedError for invalid authorization', + requestOptions: { + headers: { Authorization: AUTHORIZATION.INVALID }, + method: 'PUT', + path: ENDPOINTS.GENERATE_PASSPORT.replace(':site_id', SITE_ID.VALID), + }, + expectedStatusCode: 401, + expectedError: ErrorDefinitions.UnauthorizedError.code, + }, + { + description: 'should return ParameterInvalidError for invalid site_id', + requestOptions: { + headers: { Authorization: AUTHORIZATION.VALID }, + method: 'PUT', + path: ENDPOINTS.GENERATE_PASSPORT.replace(':site_id', SITE_ID.INVALID), + }, + expectedStatusCode: 400, + expectedError: ErrorDefinitions.ParameterInvalidError.code, + }, + ]; + + const refreshPassportTestCases = [ + { + description: 'should refresh passport access tokens with valid refresh_token', + requestOptions: { + headers: { + 'Content-Type': 'application/json', + }, + method: 'PUT', + path: ENDPOINTS.REFRESH_PASSPORT.replace(':site_id', SITE_ID.VALID), + body: JSON.stringify({ + refresh_token: ACCESS_TOKENS.REFRESH_TOKEN.VALID, + }), + }, + expectedStatusCode: 200, + expectedResponse: { + passport: ACCESS_TOKENS.PASSPORT.VALID, + refresh_token: ACCESS_TOKENS.REFRESH_TOKEN.VALID, + }, + }, + { + description: 'should not generate access tokens with invalid site_id', + requestOptions: { + headers: { + 'Content-Type': 'application/json', + }, + method: 'PUT', + path: ENDPOINTS.REFRESH_PASSPORT.replace(':site_id', SITE_ID.INVALID), + body: JSON.stringify({ + refresh_token: ACCESS_TOKENS.REFRESH_TOKEN.VALID, + }), + }, + expectedStatusCode: 400, + expectedError: ErrorDefinitions.ParameterInvalidError.code, + }, + { + description: 'should fail with forbidden for invalid refresh_token provided', + requestOptions: { + headers: { + 'Content-Type': 'application/json', + }, + method: 'PUT', + path: ENDPOINTS.REFRESH_PASSPORT.replace(':site_id', SITE_ID.VALID), + body: JSON.stringify({ + refresh_token: ACCESS_TOKENS.REFRESH_TOKEN.INVALID, + }), + }, + expectedStatusCode: 403, + expectedError: ErrorDefinitions.ForbiddenError.code, + }, + { + description: 'should return ParameterMissingError for missing refresh_token', + requestOptions: { + headers: { + 'Content-Type': 'application/json', + }, + method: 'PUT', + path: ENDPOINTS.REFRESH_PASSPORT.replace(':site_id', SITE_ID.VALID), + body: JSON.stringify({ + // missing refresh_token + }), + }, + expectedStatusCode: 400, + expectedError: ErrorDefinitions.ParameterMissingError.code, + }, + ]; + + const allTestCases = [...generatePassportTestCases, ...refreshPassportTestCases]; + + it.each(allTestCases)( + '$description', + async ({ requestOptions, expectedStatusCode, expectedResponse, expectedError }) => { + const response = await new Promise((resolve) => { + mockServer.request(requestOptions, resolve).end(); + }); + expect(response.statusCode).toBe(expectedStatusCode); + + const body = await new Promise((resolve) => { + let data = ''; + response.on('data', (chunk) => { + data += chunk; + }); + response.on('end', () => { + resolve(data); + }); + }); + + const responseBody = JSON.parse(body); + + if (expectedResponse) { + expect(responseBody).toMatchObject(expectedResponse); + } else if (expectedError) { + expect(responseBody.errors[0].code).toBe(expectedError); + } + } + ); + + afterAll(async () => { + await mockServer.close(); + }); +}); diff --git a/platforms/access-bridge/test/integration/checkout.test.ts b/platforms/access-bridge/test/integration/checkout.test.ts new file mode 100644 index 000000000..72612fd88 --- /dev/null +++ b/platforms/access-bridge/test/integration/checkout.test.ts @@ -0,0 +1,259 @@ +import http from 'http'; + +import { Express } from 'express'; +import { describe, it, beforeAll, afterAll, expect } from 'vitest'; + +import { MockServer } from '../mock-server.js'; +import { ErrorDefinitions } from '../../src/errors.js'; +import { + STRIPE_SESSION_URL, + VALID_PLAN_ID, + ENDPOINTS, + STORE_PRODUCT, + STRIPE_ERRORS, + AUTHORIZATION, + SITE_ID, + STORE_PRICE, +} from '../fixtures.js'; +import { MockCheckoutController } from '../mocks/checkout.js'; +import { addRoute } from '../../src/pipeline/routes.js'; +import { validateSiteId } from '../mocks/middleware.js'; + +describe('CheckoutController tests', () => { + let mockServer: MockServer; + let checkoutController: MockCheckoutController; + + beforeAll(async () => { + checkoutController = new MockCheckoutController(); + + const initializeRoutes = (app: Express) => { + addRoute(app, 'post', ENDPOINTS.CHECKOUT, checkoutController.initiateCheckout.bind(checkoutController), [ + validateSiteId, + ]); + addRoute( + app, + 'post', + ENDPOINTS.BILLING_PORTAL, + checkoutController.generateBillingPortalURL.bind(checkoutController), + [validateSiteId] + ); + }; + + mockServer = await MockServer.create(initializeRoutes); + }); + + const checkoutTestCases = [ + { + description: 'should initiate checkout session successfully', + requestOptions: { + headers: { + Authorization: AUTHORIZATION.VALID, + 'Content-Type': 'application/json', + }, + method: 'POST', + path: ENDPOINTS.CHECKOUT.replace(':site_id', SITE_ID.VALID), + body: JSON.stringify({ + price_id: STORE_PRODUCT.store_product_id, + success_url: 'http://example.com', + cancel_url: 'http://example.com', + }), + }, + expectedStatusCode: 200, + expectedResponse: { + url: STRIPE_SESSION_URL, + }, + }, + { + description: 'should return ParameterInvalidError for invalid site_id', + requestOptions: { + headers: { + Authorization: AUTHORIZATION.VALID, + 'Content-Type': 'application/json', + }, + method: 'POST', + path: ENDPOINTS.CHECKOUT.replace(':site_id', SITE_ID.INVALID), + body: JSON.stringify({ + price_id: STORE_PRICE.store_price_id, + success_url: 'http://example.com', + cancel_url: 'http://example.com', + }), + }, + expectedStatusCode: 400, + expectedError: ErrorDefinitions.ParameterInvalidError.code, + }, + { + description: 'should return UnauthorizedError for missing authorization token', + requestOptions: { + headers: { + Authorization: AUTHORIZATION.MISSING, + 'Content-Type': 'application/json', + }, + method: 'POST', + path: ENDPOINTS.CHECKOUT.replace(':site_id', SITE_ID.VALID), + }, + expectedStatusCode: 401, + expectedError: ErrorDefinitions.UnauthorizedError.code, + }, + { + description: 'should handle missing required parameters', + requestOptions: { + headers: { + Authorization: AUTHORIZATION.VALID, + 'Content-Type': 'application/json', + }, + method: 'POST', + path: ENDPOINTS.CHECKOUT.replace(':site_id', SITE_ID.VALID), + body: JSON.stringify({ + price_id: STORE_PRICE.store_price_id, + // missing success_url + // missing cancel_url + }), + }, + expectedStatusCode: 400, + expectedError: ErrorDefinitions.ParameterMissingError.code, + }, + ]; + + const billingPortalTestCases = [ + { + description: 'should create billing portal URL successfully', + requestOptions: { + headers: { + Authorization: AUTHORIZATION.VALID, + 'Content-Type': 'application/json', + }, + method: 'POST', + path: ENDPOINTS.BILLING_PORTAL.replace(':site_id', SITE_ID.VALID), + body: JSON.stringify({ + return_url: 'http://example.com', + }), + }, + expectedStatusCode: 200, + expectedResponse: { + url: STRIPE_SESSION_URL, + }, + }, + { + description: 'should return ParameterInvalidError for invalid site_id', + requestOptions: { + headers: { + Authorization: AUTHORIZATION.VALID, + 'Content-Type': 'application/json', + }, + method: 'POST', + path: ENDPOINTS.CHECKOUT.replace(':site_id', SITE_ID.INVALID), + body: JSON.stringify({ + return_url: 'http://example.com', + }), + }, + expectedStatusCode: 400, + expectedError: ErrorDefinitions.ParameterInvalidError.code, + }, + { + description: 'should return UnauthorizedError for missing authorization token in billing portal', + requestOptions: { + headers: { + Authorization: AUTHORIZATION.MISSING, + 'Content-Type': 'application/json', + }, + method: 'POST', + path: ENDPOINTS.BILLING_PORTAL.replace(':site_id', SITE_ID.VALID), + }, + expectedStatusCode: 401, + expectedError: ErrorDefinitions.UnauthorizedError.code, + }, + { + description: 'should handle missing required parameters in billing portal', + requestOptions: { + headers: { + Authorization: AUTHORIZATION.VALID, + 'Content-Type': 'application/json', + }, + method: 'POST', + path: ENDPOINTS.BILLING_PORTAL.replace(':site_id', SITE_ID.VALID), + body: JSON.stringify({ + // missing return_url + }), + }, + expectedStatusCode: 400, + expectedError: ErrorDefinitions.ParameterMissingError.code, + }, + ]; + + const allTestCases = [...checkoutTestCases, ...billingPortalTestCases]; + + it.each(allTestCases)( + '$description', + async ({ requestOptions, expectedStatusCode, expectedResponse, expectedError }) => { + const response = await new Promise((resolve) => { + mockServer.request(requestOptions, resolve).end(); + }); + expect(response.statusCode).toBe(expectedStatusCode); + + const body = await new Promise((resolve) => { + let data = ''; + response.on('data', (chunk) => { + data += chunk; + }); + response.on('end', () => { + resolve(data); + }); + }); + + const responseBody = JSON.parse(body); + + if (expectedResponse) { + expect(responseBody).toMatchObject(expectedResponse); + } else if (expectedError) { + expect(responseBody.errors[0].code).toBe(expectedError); + } + } + ); + + STRIPE_ERRORS.forEach(({ error, expectedCode, statusCode }) => { + it(`should handle ${error.type} correctly`, async () => { + checkoutController['paymentService'].setMockBehavior('error', error); + + const requestBody = JSON.stringify({ + access_plan_id: VALID_PLAN_ID, + price_id: STORE_PRICE.store_price_id, + mode: 'subscription', + success_url: 'http://example.com', + cancel_url: 'http://example.com', + }); + + const requestOptions = { + headers: { + Authorization: AUTHORIZATION.VALID, + 'Content-Type': 'application/json', + }, + method: 'POST', + path: ENDPOINTS.CHECKOUT.replace(':site_id', SITE_ID.VALID), + body: requestBody, + }; + + const response = await new Promise((resolve) => { + mockServer.request(requestOptions, resolve).end(); + }); + + expect(response.statusCode).toBe(statusCode); + + const body = await new Promise((resolve) => { + let data = ''; + response.on('data', (chunk) => { + data += chunk; + }); + response.on('end', () => { + resolve(data); + }); + }); + + const responseBody = JSON.parse(body); + expect(responseBody.errors[0].code).toBe(expectedCode); + }); + }); + + afterAll(async () => { + await mockServer.close(); + }); +}); diff --git a/platforms/access-bridge/test/integration/products.test.ts b/platforms/access-bridge/test/integration/products.test.ts new file mode 100644 index 000000000..332c1c15a --- /dev/null +++ b/platforms/access-bridge/test/integration/products.test.ts @@ -0,0 +1,136 @@ +import http from 'http'; + +import { Express } from 'express'; +import { describe, it, beforeAll, afterAll, expect } from 'vitest'; + +import { MockServer } from '../mock-server.js'; +import { ENDPOINTS, SITE_ID, STRIPE_ERRORS, STORE_PRODUCT } from '../fixtures.js'; +import { ErrorDefinitions } from '../../src/errors.js'; +import { MockBehavior, MockPaymentService, MockProductsController } from '../mocks/products.js'; +import { addRoute } from '../../src/pipeline/routes.js'; +import { validateSiteId } from '../mocks/middleware.js'; + +describe('ProductsController tests', () => { + let mockServer: MockServer; + let productsController: MockProductsController; + + beforeAll(async () => { + productsController = new MockProductsController(); + + const initializeRoutes = (app: Express) => { + addRoute(app, 'get', ENDPOINTS.PRODUCTS, productsController.getProducts.bind(productsController), [ + validateSiteId, + ]); + }; + + mockServer = await MockServer.create(initializeRoutes); + }); + + const testCases = [ + { + description: 'should list stripe products', + requestOptions: { + method: 'GET', + path: ENDPOINTS.PRODUCTS.replace(':site_id', SITE_ID.VALID), + }, + mockBehavior: 'default' as MockBehavior, + expectedStatusCode: 200, + expectedResponse: [STORE_PRODUCT], + }, + { + description: 'should handle empty products', + requestOptions: { + method: 'GET', + path: ENDPOINTS.PRODUCTS.replace(':site_id', SITE_ID.VALID), + }, + mockBehavior: 'empty' as MockBehavior, + expectedStatusCode: 200, + expectedResponse: [], + }, + { + description: 'should return ParameterInvalidError for invalid site_id', + requestOptions: { + method: 'GET', + path: ENDPOINTS.PRODUCTS.replace(':site_id', SITE_ID.INVALID), + }, + expectedStatusCode: 400, + expectedError: ErrorDefinitions.ParameterInvalidError.code, + }, + { + description: 'should return NotFoundError for invalid route', + requestOptions: { + method: 'GET', + path: `${ENDPOINTS.PRODUCTS.replace(':site_id', SITE_ID.INVALID)}/invalid`, + }, + expectedStatusCode: 404, + expectedError: ErrorDefinitions.NotFoundError.code, + }, + ]; + + it.each(testCases)( + '$description', + async ({ requestOptions, mockBehavior, expectedStatusCode, expectedResponse, expectedError }) => { + if (mockBehavior) { + (productsController['paymentService'] as MockPaymentService).setMockBehavior(mockBehavior); + } + + const response = await new Promise((resolve) => { + mockServer.request(requestOptions, resolve).end(); + }); + + expect(response.statusCode).toBe(expectedStatusCode); + + const body = await new Promise((resolve) => { + let data = ''; + response.on('data', (chunk) => { + data += chunk; + }); + response.on('end', () => { + resolve(data); + }); + }); + + const responseBody = JSON.parse(body); + + if (expectedResponse) { + expect(responseBody).toMatchObject(expectedResponse); + } else if (expectedError) { + expect(responseBody.errors[0].code).toBe(expectedError); + } + } + ); + + STRIPE_ERRORS.forEach(({ error, expectedCode, statusCode }) => { + it(`should handle ${error.type} correctly`, async () => { + (productsController['paymentService'] as MockPaymentService).setMockBehavior('error', error); + + const requestOptions = { + method: 'GET', + path: ENDPOINTS.PRODUCTS.replace(':site_id', SITE_ID.VALID), + }; + + const response = await new Promise((resolve) => { + mockServer.request(requestOptions, resolve).end(); + }); + + expect(response.statusCode).toBe(statusCode); + + const body = await new Promise((resolve) => { + let data = ''; + response.on('data', (chunk) => { + data += chunk; + }); + response.on('end', () => { + resolve(data); + }); + }); + + const responseBody = JSON.parse(body); + expect(responseBody.errors[0].code).toBe(expectedCode); + }); + }); + + afterAll(async () => { + await mockServer.close(); + }); +}); diff --git a/platforms/access-bridge/test/mock-server.ts b/platforms/access-bridge/test/mock-server.ts new file mode 100644 index 000000000..60f5c7f35 --- /dev/null +++ b/platforms/access-bridge/test/mock-server.ts @@ -0,0 +1,48 @@ +import http from 'http'; +import { RequestOptions } from 'https'; + +import { Express } from 'express'; + +import { Server } from '../src/server.js'; + +interface ExtendedRequestOptions extends RequestOptions { + body?: string; +} + +export class MockServer { + private server: Server; + readonly port: number; + + constructor(server: Server, port: number) { + this.server = server; + this.port = port; + } + + static async create(initializeRoutes: (app: Express) => void): Promise { + // Use port 0 to let the OS select an available port for testing + const server = new Server('localhost', 0, initializeRoutes); + const port = await server.listen(); + return new this(server, port); + } + + addRequestOptions(options: http.RequestOptions): http.RequestOptions { + options.host = 'localhost'; + options.port = this.port; + return options; + } + + request(options: ExtendedRequestOptions, callback?: (res: http.IncomingMessage) => void): http.ClientRequest { + const req = http.request(this.addRequestOptions(options), callback); + + if (options.body) { + req.write(options.body); + } + + req.end(); + return req; + } + + async close(): Promise { + await this.server.close(); + } +} diff --git a/platforms/access-bridge/test/mocks/access.ts b/platforms/access-bridge/test/mocks/access.ts new file mode 100644 index 000000000..e2de4ea91 --- /dev/null +++ b/platforms/access-bridge/test/mocks/access.ts @@ -0,0 +1,61 @@ +import { Plan } from '@jwp/ott-common/types/plans.js'; + +import { PassportService } from '../../src/services/passport-service.js'; +import { PlansService } from '../../src/services/plans-service.js'; +import { ACCESS_TOKENS, PLANS, AUTHORIZATION, VIEWER } from '../fixtures.js'; +import { IdentityService } from '../../src/services/identity-service.js'; +import { AccessController } from '../../src/controllers/access-controller.js'; +import { ErrorDefinitions } from '../../src/errors.js'; + +// Mock IdentityService +class MockIdentityService extends IdentityService { + async getAccount({ authorization }: { authorization: string }) { + if (authorization === AUTHORIZATION.INVALID) { + throw ErrorDefinitions.UnauthorizedError.create(); + } + + return VIEWER; + } +} + +// Mock PassportService +class MockPassportService extends PassportService { + async generatePassport() { + return { passport: ACCESS_TOKENS.PASSPORT.VALID, refresh_token: ACCESS_TOKENS.REFRESH_TOKEN.VALID }; + } + + async refreshPassport({ refreshToken }: { refreshToken: string }) { + if (!refreshToken) { + throw ErrorDefinitions.ParameterMissingError.create({}); + } + if (refreshToken === ACCESS_TOKENS.REFRESH_TOKEN.INVALID) { + throw ErrorDefinitions.ForbiddenError.create({ description: 'Invalid refresh_token provided' }); + } + return { passport: ACCESS_TOKENS.PASSPORT.VALID, refresh_token: ACCESS_TOKENS.REFRESH_TOKEN.VALID }; + } +} + +// Mock PlansService +class MockPlansService extends PlansService { + async getAvailablePlans(): Promise { + return PLANS.VALID; + } + + async getEntitledPlans({ authorization }: { authorization: string }): Promise { + if (!authorization) { + // if no auth, only free plans available + return PLANS.FREE; + } + + return PLANS.VALID; + } +} + +export class MockAccessController extends AccessController { + constructor() { + super(); + Reflect.set(this, 'identityService', new MockIdentityService()); + Reflect.set(this, 'passportService', new MockPassportService()); + Reflect.set(this, 'plansService', new MockPlansService()); + } +} diff --git a/platforms/access-bridge/test/mocks/checkout.ts b/platforms/access-bridge/test/mocks/checkout.ts new file mode 100644 index 000000000..3eef709cb --- /dev/null +++ b/platforms/access-bridge/test/mocks/checkout.ts @@ -0,0 +1,73 @@ +import { NextFunction, Request, Response } from 'express'; + +import { ErrorDefinitions, sendErrors } from '../../src/errors.js'; +import { AUTHORIZATION, VIEWER } from '../fixtures.js'; +import { IdentityService } from '../../src/services/identity-service.js'; + +import { MockStripePaymentService } from './payment.js'; + +// Mock IdentityService +class MockIdentityService extends IdentityService { + async getAccount({ authorization }: { authorization: string }) { + if (authorization === AUTHORIZATION.INVALID) { + throw ErrorDefinitions.UnauthorizedError.create(); + } + + return VIEWER; + } +} + +// Mock Controller +export class MockCheckoutController { + private identityService: MockIdentityService; + private paymentService: MockStripePaymentService; + + constructor() { + this.identityService = new MockIdentityService(); + this.paymentService = new MockStripePaymentService(); + } + + async initiateCheckout(req: Request, res: Response, next: NextFunction) { + const authorization = req.headers['authorization']; + if (!authorization) { + sendErrors(res, ErrorDefinitions.UnauthorizedError.create()); + return; + } + + const checkoutParams = req.body; + const validationError = this.paymentService.validateCheckoutParams(checkoutParams); + if (validationError) { + sendErrors(res, ErrorDefinitions.ParameterMissingError.create({ parameterName: validationError })); + return; + } + + const viewer = await this.identityService.getAccount({ authorization }); + const checkoutSessionUrl = await this.paymentService.createCheckoutSessionUrl(viewer, checkoutParams); + + res.end(JSON.stringify({ url: checkoutSessionUrl })); + } + + async generateBillingPortalURL(req: Request, res: Response, next: NextFunction) { + const authorization = req.headers['authorization']; + if (!authorization) { + sendErrors(res, ErrorDefinitions.UnauthorizedError.create()); + return; + } + + // Get the email address from the Authorization token + const viewer = await this.identityService.getAccount({ authorization }); + if (!viewer.id || !viewer.email) { + sendErrors(res, ErrorDefinitions.UnauthorizedError.create()); + return; + } + + const { return_url } = req.body; + if (!return_url) { + sendErrors(res, ErrorDefinitions.ParameterMissingError.create({ parameterName: 'return_url' })); + return; + } + + const billingPortalSessionUrl = await this.paymentService.createBillingPortalSessionUrl(viewer, return_url); + res.json({ url: billingPortalSessionUrl }); + } +} diff --git a/platforms/access-bridge/test/mocks/middleware.ts b/platforms/access-bridge/test/mocks/middleware.ts new file mode 100644 index 000000000..470f8b356 --- /dev/null +++ b/platforms/access-bridge/test/mocks/middleware.ts @@ -0,0 +1,17 @@ +import { NextFunction, Request, Response } from 'express'; + +import { ErrorDefinitions, sendErrors } from '../../src/errors'; +import { SITE_ID } from '../fixtures'; + +/** + * Mock Middleware to validate 'site_id' parameter + */ +export const validateSiteId = (req: Request, res: Response, next: NextFunction) => { + if (req.params.site_id !== SITE_ID.VALID) { + sendErrors(res, ErrorDefinitions.ParameterInvalidError.create({ parameterName: 'site_id' })); + return; + } + + // If valid, move to the next middleware or controller + next(); +}; diff --git a/platforms/access-bridge/test/mocks/payment.ts b/platforms/access-bridge/test/mocks/payment.ts new file mode 100644 index 000000000..4eb2fef26 --- /dev/null +++ b/platforms/access-bridge/test/mocks/payment.ts @@ -0,0 +1,74 @@ +import Stripe from 'stripe'; +import { CheckoutParams } from '@jwp/ott-common/types/payment'; + +import { PaymentService } from '../../src/services/payment-service'; +import { AccessBridgeError, ErrorDefinitions } from '../../src/errors'; +import { STORE_PRODUCT, STRIPE_SESSION_URL } from '../fixtures'; +import { Viewer } from '../../src/services/identity-service'; + +import { MockBehavior } from './products'; + +export interface MockPaymentService extends PaymentService { + setMockBehavior(behavior: 'default' | 'empty' | 'error', error?: Stripe.errors.StripeError): unknown; +} + +// Mock StripePaymentService +export class MockStripePaymentService implements MockPaymentService { + private mockBehavior: MockBehavior = 'default'; + private mockError: AccessBridgeError | null = null; + + async getProductsWithPrices(productIds: string[]) { + if (this.mockBehavior === 'error' && this.mockError) { + throw this.mockError; + } + + if (this.mockBehavior === 'empty') { + return []; + } + + return [STORE_PRODUCT]; + } + + async createCheckoutSessionUrl(viewer: Viewer, params: CheckoutParams): Promise { + if (this.mockBehavior === 'error' && this.mockError) { + throw this.mockError; + } + + return STRIPE_SESSION_URL; + } + + async createBillingPortalSessionUrl(viewer: Viewer, returnUrl: string): Promise { + if (this.mockBehavior === 'error' && this.mockError) { + throw this.mockError; + } + + return STRIPE_SESSION_URL; + } + + validateCheckoutParams(params: CheckoutParams): string | null { + const requiredParams: (keyof CheckoutParams)[] = ['price_id', 'success_url', 'cancel_url']; + const missingParam = requiredParams.find((param) => !params[param]); + return missingParam ? `Missing required parameter: ${missingParam}` : null; + } + + // Method to set the mock behavior + setMockBehavior(behavior: 'default' | 'empty' | 'error', error?: Stripe.errors.StripeError) { + this.mockBehavior = behavior; + + if (behavior === 'error' && error instanceof Stripe.errors.StripeError) { + switch (error.type) { + case 'StripeInvalidRequestError': + this.mockError = ErrorDefinitions.BadRequestError.create(); + break; + case 'StripeAuthenticationError': + this.mockError = ErrorDefinitions.UnauthorizedError.create(); + break; + case 'StripePermissionError': + this.mockError = ErrorDefinitions.ForbiddenError.create(); + break; + default: + this.mockError = ErrorDefinitions.BadRequestError.create(); + } + } + } +} diff --git a/platforms/access-bridge/test/mocks/products.ts b/platforms/access-bridge/test/mocks/products.ts new file mode 100644 index 000000000..95b305095 --- /dev/null +++ b/platforms/access-bridge/test/mocks/products.ts @@ -0,0 +1,30 @@ +import Stripe from 'stripe'; +import { Plan } from '@jwp/ott-common/types/plans.js'; + +import { PlansService } from '../../src/services/plans-service.js'; +import { PLANS } from '../fixtures.js'; +import { ProductsController } from '../../src/controllers/products-controller.js'; +import { PaymentService } from '../../src/services/payment-service.js'; + +import { MockStripePaymentService } from './payment.js'; + +export type MockBehavior = 'default' | 'empty' | 'error'; + +export interface MockPaymentService extends PaymentService { + setMockBehavior(behavior: 'default' | 'empty' | 'error', error?: Stripe.errors.StripeError): unknown; +} + +// Mock PlansService +export class MockPlansService extends PlansService { + async getAvailablePlans(): Promise { + return PLANS.VALID; + } +} + +export class MockProductsController extends ProductsController { + constructor() { + super(); + Reflect.set(this, 'paymentService', new MockStripePaymentService()); + Reflect.set(this, 'plansService', new MockPlansService()); + } +} diff --git a/platforms/access-bridge/test/unit/errors.test.ts b/platforms/access-bridge/test/unit/errors.test.ts new file mode 100644 index 000000000..401598d28 --- /dev/null +++ b/platforms/access-bridge/test/unit/errors.test.ts @@ -0,0 +1,56 @@ +import { describe, it, expect } from 'vitest'; + +import { ErrorDefinitions } from '../../src/errors.js'; + +describe('AccessBridgeError', () => { + // Test for each error definition + for (const [key, definition] of Object.entries(ErrorDefinitions)) { + const errorKey = key as keyof typeof ErrorDefinitions; + const { code, statusCode, description } = definition; + + it(`should create ${errorKey} with the correct code and status code`, () => { + const error = definition.create({}); + expect(error.code).toBe(code); + expect(error.statusCode).toBe(statusCode); + + // Test default description + const expectedDescription = description.replace('{parameterName}', '').replace('{reason}', ''); + expect(error.description).toBe(expectedDescription); + }); + + it(`should create ${errorKey} with a custom description`, () => { + // Define context based on error type + const context = (() => { + switch (errorKey) { + case 'ParameterMissingError': + return { parameterName: 'testParam' }; + case 'ParameterInvalidError': + return { parameterName: 'testParam', reason: 'Invalid reason' }; + default: + return {}; + } + })(); + + // Define the custom description + const customDescription = (() => { + switch (errorKey) { + case 'ParameterMissingError': + return `Required parameter ${context.parameterName} is missing.`; + case 'ParameterInvalidError': + return `Parameter ${context.parameterName} is invalid. ${context.reason || ''}`; + default: + return description; + } + })(); + + // Create the error with context and custom description + const error = definition.create({ + ...context, + description: customDescription, + }); + + // Check that the description matches + expect(error.description).toBe(customDescription); + }); + } +}); diff --git a/platforms/access-bridge/test/unit/logger.test.ts b/platforms/access-bridge/test/unit/logger.test.ts new file mode 100644 index 000000000..a12c386a9 --- /dev/null +++ b/platforms/access-bridge/test/unit/logger.test.ts @@ -0,0 +1,98 @@ +import { describe, it, expect, vi, beforeEach } from 'vitest'; +import * as Sentry from '@sentry/node'; + +import logger from '../../src/pipeline/logger.js'; + +describe('Logger Tests', () => { + beforeEach(() => { + // Reset all mocks to ensure a clean slate for each test + vi.resetAllMocks(); + }); + + describe('when Sentry is configured', () => { + const mockCaptureException = vi.fn(); + const mockCaptureMessage = vi.fn(); + + beforeEach(() => { + // Mock Sentry's getClient to return an object with captureException and captureMessage + vi.mocked(Sentry.getClient).mockReturnValue({ + captureException: mockCaptureException, + captureMessage: mockCaptureMessage, + } as unknown as Sentry.NodeClient); + + // Ensure Sentry's captureException and captureMessage methods are also mocked + vi.mocked(Sentry.captureException).mockImplementation(mockCaptureException); + vi.mocked(Sentry.captureMessage).mockImplementation(mockCaptureMessage); + }); + + it('should call Sentry.captureException on error', () => { + const error = new Error('Test error'); + const message = 'Test error message'; + + // Call logger.error which should trigger Sentry.captureException + logger.error(message, error); + + // Verify that captureException was called with the correct arguments + expect(mockCaptureException).toHaveBeenCalledWith(error); + }); + + it('should call Sentry.captureMessage on info', () => { + const message = 'Test info message'; + + // Call logger.info which should trigger Sentry.captureMessage with 'info' + logger.info(message); + + // Verify that captureMessage was called with the correct arguments + expect(mockCaptureMessage).toHaveBeenCalledWith(message, 'info'); + }); + + it('should call Sentry.captureMessage on warn', () => { + const message = 'Test warn message'; + + // Call logger.warn which should trigger Sentry.captureMessage with 'warning' + logger.warn(message); + + // Verify that captureMessage was called with the correct arguments + expect(mockCaptureMessage).toHaveBeenCalledWith(message, 'warning'); + }); + }); + + describe('when Sentry is not configured', () => { + beforeEach(() => { + // Mock Sentry's getClient to return undefined + vi.mocked(Sentry.getClient).mockReturnValue(undefined); + + // Ensure Sentry's captureException and captureMessage methods are mocked + vi.mocked(Sentry.captureException).mockImplementation(vi.fn()); + vi.mocked(Sentry.captureMessage).mockImplementation(vi.fn()); + }); + + it('should not call Sentry.captureException', () => { + const error = new Error('Test error'); + const message = 'Test error message'; + + logger.error(message, error); + + // Verify that captureException was not called + expect(Sentry.captureException).not.toHaveBeenCalled(); + }); + + it('should not call Sentry.captureMessage for info', () => { + const message = 'Test info message'; + + logger.info(message); + + // Verify that captureMessage was not called + expect(Sentry.captureMessage).not.toHaveBeenCalled(); + }); + + it('should not call Sentry.captureMessage for warn', () => { + const message = 'Test warn message'; + + logger.warn(message); + + // Verify that captureMessage was not called + expect(Sentry.captureMessage).not.toHaveBeenCalled(); + }); + }); +}); diff --git a/platforms/access-bridge/test/unit/signing.test.ts b/platforms/access-bridge/test/unit/signing.test.ts new file mode 100644 index 000000000..e8b76584c --- /dev/null +++ b/platforms/access-bridge/test/unit/signing.test.ts @@ -0,0 +1,20 @@ +import { describe, it, expect } from 'vitest'; + +import { PassportService } from '../../src/services/passport-service.js'; + +describe('PassportService generateSignedUrl test', () => { + const service = new PassportService(); + + it('should generate a signed URL with the correct token', async () => { + const path = '/path/to/resource'; + const clientHost = 'https://example.com'; + + const result = await service.generateSignedUrl(path, clientHost); + + // Parse the result URL to extract the token + const url = new URL(result); + const token = url.searchParams.get('token'); + + expect(result).toBe(`${clientHost}${path}?token=${token}`); + }); +}); diff --git a/platforms/access-bridge/test/vitest.setup.ts b/platforms/access-bridge/test/vitest.setup.ts new file mode 100644 index 000000000..58e772880 --- /dev/null +++ b/platforms/access-bridge/test/vitest.setup.ts @@ -0,0 +1,20 @@ +import { vi } from 'vitest'; +import * as Sentry from '@sentry/node'; + +// Mock sentry lib +vi.mock('@sentry/node', async (importOriginal) => { + const actual = (await importOriginal()) as typeof Sentry; + + return { + ...actual, + init: vi.fn(), + captureException: vi.fn(), + captureMessage: vi.fn(), + getClient: vi.fn(), + }; +}); + +beforeEach(() => { + // Clear all mocks + vi.clearAllMocks(); +}); diff --git a/platforms/access-bridge/tsconfig.json b/platforms/access-bridge/tsconfig.json new file mode 100644 index 000000000..9f3252104 --- /dev/null +++ b/platforms/access-bridge/tsconfig.json @@ -0,0 +1,18 @@ +{ + "compilerOptions": { + "outDir": "build", + "incremental": true, + "module": "ESNext", + "target": "ESNext", + "moduleResolution": "node", + "strict": true, + "forceConsistentCasingInFileNames": true, + "skipLibCheck": true, + "esModuleInterop": true, + "sourceMap": true, + "noUnusedLocals": true, + "types": ["vitest/globals"], + }, + "include": ["test/**/*", "src/**/*", "instrument.mjs"], + "exclude": ["node_modules", "build"], +} diff --git a/platforms/access-bridge/vite.config.ts b/platforms/access-bridge/vite.config.ts new file mode 100644 index 000000000..c671b75ca --- /dev/null +++ b/platforms/access-bridge/vite.config.ts @@ -0,0 +1,58 @@ +import { defineConfig, loadEnv, PluginOption } from 'vite'; +import { VitePluginNode } from 'vite-plugin-node'; +import type { ConfigEnv, UserConfigExport } from 'vitest/config'; +import { sentryVitePlugin } from '@sentry/vite-plugin'; + +export default ({ mode, command }: ConfigEnv): UserConfigExport => { + const envPrefix = 'APP_'; + const env = loadEnv(mode, process.cwd(), envPrefix); + + // Shorten default mode names to dev / prod + mode = mode === 'development' ? 'dev' : mode; + mode = mode === 'production' ? 'prod' : mode; + + // Ensure builds are always production type + if (command === 'build') { + process.env.NODE_ENV = 'production'; + } + + // Define the initial plugins array with the Vite Node plugin for Node.js support + const plugins: PluginOption[] = [ + VitePluginNode({ + adapter: 'express', + appPath: 'src/main.ts', + tsCompiler: 'esbuild', + }), + ]; + + // Conditionally add the Sentry plugin based on the mode and presence of Sentry-related env variables + if (mode === 'prod' && env.APP_SENTRY_DSN && env.APP_SENTRY_AUTH_TOKEN) { + plugins.push( + sentryVitePlugin({ + authToken: env.APP_SENTRY_AUTH_TOKEN, + org: env.APP_SENTRY_ORG_NAME, + project: env.APP_SENTRY_PROJ_NAME, + }) + ); + } + + return defineConfig({ + server: { + port: parseInt(env.APP_BIND_PORT), + }, + envPrefix, + plugins, + build: { + outDir: 'build', + sourcemap: true, + }, + test: { + globals: true, + include: ['**/*.test.ts'], + setupFiles: 'test/vitest.setup.ts', + chaiConfig: { + truncateThreshold: 1000, + }, + }, + }); +}; diff --git a/yarn.lock b/yarn.lock index 2671a7a1d..295f2bb20 100644 --- a/yarn.lock +++ b/yarn.lock @@ -65,6 +65,11 @@ resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.24.7.tgz#d23bbea508c3883ba8251fb4164982c36ea577ed" integrity sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw== +"@babel/compat-data@^7.25.2": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.25.4.tgz#7d2a80ce229890edcf4cc259d4d696cb4dae2fcb" + integrity sha512-+LGRog6RAsCJrrrg/IO6LGmpphNe5DiK30dGjCoxxeGv49B10/3XYGxPsAwrDlMFcFEvdAUavDT8r9k/hSyQqQ== + "@babel/core@^7.11.1", "@babel/core@^7.13.16", "@babel/core@^7.20.0", "@babel/core@^7.21.3", "@babel/core@^7.24.4", "@babel/core@^7.24.5", "@babel/core@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.24.7.tgz#b676450141e0b52a3d43bc91da86aa608f950ac4" @@ -86,6 +91,27 @@ json5 "^2.2.3" semver "^6.3.1" +"@babel/core@^7.18.5": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.25.2.tgz#ed8eec275118d7613e77a352894cd12ded8eba77" + integrity sha512-BBt3opiCOxUr9euZ5/ro/Xv8/V7yJ5bjYMqG/C1YAo8MIKAnumZalCN+msbci3Pigy4lIQfPUpfMM27HMGaYEA== + dependencies: + "@ampproject/remapping" "^2.2.0" + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.0" + "@babel/helper-compilation-targets" "^7.25.2" + "@babel/helper-module-transforms" "^7.25.2" + "@babel/helpers" "^7.25.0" + "@babel/parser" "^7.25.0" + "@babel/template" "^7.25.0" + "@babel/traverse" "^7.25.2" + "@babel/types" "^7.25.2" + convert-source-map "^2.0.0" + debug "^4.1.0" + gensync "^1.0.0-beta.2" + json5 "^2.2.3" + semver "^6.3.1" + "@babel/generator@^7.20.0", "@babel/generator@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.24.7.tgz#1654d01de20ad66b4b4d99c135471bc654c55e6d" @@ -96,6 +122,16 @@ "@jridgewell/trace-mapping" "^0.3.25" jsesc "^2.5.1" +"@babel/generator@^7.25.0", "@babel/generator@^7.25.4": + version "7.25.5" + resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.25.5.tgz#b31cf05b3fe8c32d206b6dad03bb0aacbde73450" + integrity sha512-abd43wyLfbWoxC6ahM8xTkqLpGB2iWBVyuKC9/srhFunCd1SDNrV1s72bBpK4hLj8KLzHBBcOblvLQZBNw9r3w== + dependencies: + "@babel/types" "^7.25.4" + "@jridgewell/gen-mapping" "^0.3.5" + "@jridgewell/trace-mapping" "^0.3.25" + jsesc "^2.5.1" + "@babel/helper-annotate-as-pure@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.24.7.tgz#5373c7bc8366b12a033b4be1ac13a206c6656aab" @@ -122,6 +158,17 @@ lru-cache "^5.1.1" semver "^6.3.1" +"@babel/helper-compilation-targets@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.25.2.tgz#e1d9410a90974a3a5a66e84ff55ef62e3c02d06c" + integrity sha512-U2U5LsSaZ7TAt3cfaymQ8WHh0pxvdHoEk6HVpaexxixjyEquMh0L0YNJNM6CTGKMXV1iksi0iZkGw4AcFkPaaw== + dependencies: + "@babel/compat-data" "^7.25.2" + "@babel/helper-validator-option" "^7.24.8" + browserslist "^4.23.1" + lru-cache "^5.1.1" + semver "^6.3.1" + "@babel/helper-create-class-features-plugin@^7.18.6", "@babel/helper-create-class-features-plugin@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.24.7.tgz#2eaed36b3a1c11c53bdf80d53838b293c52f5b3b" @@ -206,6 +253,16 @@ "@babel/helper-split-export-declaration" "^7.24.7" "@babel/helper-validator-identifier" "^7.24.7" +"@babel/helper-module-transforms@^7.25.2": + version "7.25.2" + resolved "https://registry.yarnpkg.com/@babel/helper-module-transforms/-/helper-module-transforms-7.25.2.tgz#ee713c29768100f2776edf04d4eb23b8d27a66e6" + integrity sha512-BjyRAbix6j/wv83ftcVJmBt72QtHI56C7JXZoG2xATiLpmoC7dpd8WnkikExHDVPpi/3qCmO6WY1EaXOluiecQ== + dependencies: + "@babel/helper-module-imports" "^7.24.7" + "@babel/helper-simple-access" "^7.24.7" + "@babel/helper-validator-identifier" "^7.24.7" + "@babel/traverse" "^7.25.2" + "@babel/helper-optimise-call-expression@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.24.7.tgz#8b0a0456c92f6b323d27cfd00d1d664e76692a0f" @@ -264,6 +321,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.7.tgz#4d2d0f14820ede3b9807ea5fc36dfc8cd7da07f2" integrity sha512-7MbVt6xrwFQbunH2DNQsAP5sTGxfqQtErvBIvIMi6EQnbgUOuVYanvREcmFrOPhoXBrTtjhhP+lW+o5UfK+tDg== +"@babel/helper-string-parser@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-string-parser/-/helper-string-parser-7.24.8.tgz#5b3329c9a58803d5df425e5785865881a81ca48d" + integrity sha512-pO9KhhRcuUyGnJWwyEgnRJTSIZHiT+vMD0kPeD+so0l7mxkMT19g3pjY9GTnHySck/hDzq+dtW/4VgnMkippsQ== + "@babel/helper-validator-identifier@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.24.7.tgz#75b889cfaf9e35c2aaf42cf0d72c8e91719251db" @@ -274,6 +336,11 @@ resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.7.tgz#24c3bb77c7a425d1742eec8fb433b5a1b38e62f6" integrity sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw== +"@babel/helper-validator-option@^7.24.8": + version "7.24.8" + resolved "https://registry.yarnpkg.com/@babel/helper-validator-option/-/helper-validator-option-7.24.8.tgz#3725cdeea8b480e86d34df15304806a06975e33d" + integrity sha512-xb8t9tD1MHLungh/AIoWYN+gVHaB9kwlu8gffXGSt3FFEIT7RjS+xWbc2vUD1UTZdIpKj/ab3rdqJ7ufngyi2Q== + "@babel/helper-wrap-function@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/helper-wrap-function/-/helper-wrap-function-7.24.7.tgz#52d893af7e42edca7c6d2c6764549826336aae1f" @@ -292,6 +359,14 @@ "@babel/template" "^7.24.7" "@babel/types" "^7.24.7" +"@babel/helpers@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.25.0.tgz#e69beb7841cb93a6505531ede34f34e6a073650a" + integrity sha512-MjgLZ42aCm0oGjJj8CtSM3DB8NOOf8h2l7DCTePJs29u+v7yO/RBX9nShlKMgFnRks/Q4tBAe7Hxnov9VkGwLw== + dependencies: + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.0" + "@babel/highlight@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.24.7.tgz#a05ab1df134b286558aae0ed41e6c5f731bf409d" @@ -307,6 +382,13 @@ resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.24.7.tgz#9a5226f92f0c5c8ead550b750f5608e766c8ce85" integrity sha512-9uUYRm6OqQrCqQdG1iCBwBPZgN8ciDBro2nIOFaiRz1/BCxaI7CNvQbDHvsArAC7Tw9Hda/B3U+6ui9u4HWXPw== +"@babel/parser@^7.25.0", "@babel/parser@^7.25.4": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.25.4.tgz#af4f2df7d02440286b7de57b1c21acfb2a6f257a" + integrity sha512-nq+eWrOgdtu3jG5Os4TQP3x3cLA8hR8TvJNjD8vnPa20WGycimcparWnLK4jJhElTK6SDyuJo1weMKO/5LpmLA== + dependencies: + "@babel/types" "^7.25.4" + "@babel/plugin-bugfix-firefox-class-in-computed-class-key@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/plugin-bugfix-firefox-class-in-computed-class-key/-/plugin-bugfix-firefox-class-in-computed-class-key-7.24.7.tgz#fd059fd27b184ea2b4c7e646868a9a381bbc3055" @@ -1204,6 +1286,15 @@ "@babel/parser" "^7.24.7" "@babel/types" "^7.24.7" +"@babel/template@^7.25.0": + version "7.25.0" + resolved "https://registry.yarnpkg.com/@babel/template/-/template-7.25.0.tgz#e733dc3134b4fede528c15bc95e89cb98c52592a" + integrity sha512-aOOgh1/5XzKvg1jvVz7AVrx2piJ2XBi227DHmbY6y+bM9H2FlN+IfecYu4Xl0cNiiVejlsCri89LUsbj8vJD9Q== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/parser" "^7.25.0" + "@babel/types" "^7.25.0" + "@babel/traverse@^7.20.0", "@babel/traverse@^7.24.7": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.24.7.tgz#de2b900163fa741721ba382163fe46a936c40cf5" @@ -1220,6 +1311,19 @@ debug "^4.3.1" globals "^11.1.0" +"@babel/traverse@^7.25.2": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/traverse/-/traverse-7.25.4.tgz#648678046990f2957407e3086e97044f13c3e18e" + integrity sha512-VJ4XsrD+nOvlXyLzmLzUs/0qjFS4sK30te5yEFlvbbUNEgKaVb2BHZUpAL+ttLPQAHNrsI3zZisbfha5Cvr8vg== + dependencies: + "@babel/code-frame" "^7.24.7" + "@babel/generator" "^7.25.4" + "@babel/parser" "^7.25.4" + "@babel/template" "^7.25.0" + "@babel/types" "^7.25.4" + debug "^4.3.1" + globals "^11.1.0" + "@babel/types@^7.0.0", "@babel/types@^7.20.0", "@babel/types@^7.20.7", "@babel/types@^7.21.3", "@babel/types@^7.24.7", "@babel/types@^7.4.4": version "7.24.7" resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.24.7.tgz#6027fe12bc1aa724cd32ab113fb7f1988f1f66f2" @@ -1229,6 +1333,15 @@ "@babel/helper-validator-identifier" "^7.24.7" to-fast-properties "^2.0.0" +"@babel/types@^7.25.0", "@babel/types@^7.25.2", "@babel/types@^7.25.4": + version "7.25.4" + resolved "https://registry.yarnpkg.com/@babel/types/-/types-7.25.4.tgz#6bcb46c72fdf1012a209d016c07f769e10adcb5f" + integrity sha512-zQ1ijeeCXVEh+aNL0RlmkPkG8HUiDcU2pzQQFjtbntgAczRASFzj4H+6+bV+dy1ntKR14I/DypeuRG1uma98iQ== + dependencies: + "@babel/helper-string-parser" "^7.24.8" + "@babel/helper-validator-identifier" "^7.24.7" + to-fast-properties "^2.0.0" + "@bcoe/v8-coverage@^0.2.3": version "0.2.3" resolved "https://registry.yarnpkg.com/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz#75a2e8b51cb758a7553d6804a5932d7aace75c39" @@ -2242,6 +2355,270 @@ resolved "https://registry.yarnpkg.com/@one-ini/wasm/-/wasm-0.1.1.tgz#6013659736c9dbfccc96e8a9c2b3de317df39323" integrity sha512-XuySG1E38YScSJoMlqovLru4KTUNSjgVTIjyh7qMX6aNN5HY5Ct5LhRJdxO79JtTzKfzV/bnWpz+zquYrISsvw== +"@opentelemetry/api-logs@0.52.1": + version "0.52.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.52.1.tgz#52906375da4d64c206b0c4cb8ffa209214654ecc" + integrity sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A== + dependencies: + "@opentelemetry/api" "^1.0.0" + +"@opentelemetry/api-logs@0.53.0": + version "0.53.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api-logs/-/api-logs-0.53.0.tgz#c478cbd8120ec2547b64edfa03a552cfe42170be" + integrity sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw== + dependencies: + "@opentelemetry/api" "^1.0.0" + +"@opentelemetry/api@^1.0.0", "@opentelemetry/api@^1.8", "@opentelemetry/api@^1.9.0": + version "1.9.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/api/-/api-1.9.0.tgz#d03eba68273dc0f7509e2a3d5cba21eae10379fe" + integrity sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg== + +"@opentelemetry/context-async-hooks@^1.25.1": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/context-async-hooks/-/context-async-hooks-1.26.0.tgz#fa92f722cf685685334bba95f258d3ef9fce60f6" + integrity sha512-HedpXXYzzbaoutw6DFLWLDket2FwLkLpil4hGCZ1xYEIMTcivdfwEOISgdbLEWyG3HW52gTq2V9mOVJrONgiwg== + +"@opentelemetry/core@1.26.0", "@opentelemetry/core@^1.1.0", "@opentelemetry/core@^1.25.1", "@opentelemetry/core@^1.8.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/core/-/core-1.26.0.tgz#7d84265aaa850ed0ca5813f97d831155be42b328" + integrity sha512-1iKxXXE8415Cdv0yjG3G6hQnB5eVEsJce3QaawX8SjDn0mAS0ZM8fAbZZJD4ajvhC15cePvosSCut404KrIIvQ== + dependencies: + "@opentelemetry/semantic-conventions" "1.27.0" + +"@opentelemetry/instrumentation-connect@0.39.0": + version "0.39.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-connect/-/instrumentation-connect-0.39.0.tgz#32bdbaac464cba061c95df6c850ee81efdd86f8b" + integrity sha512-pGBiKevLq7NNglMgqzmeKczF4XQMTOUOTkK8afRHMZMnrK3fcETyTH7lVaSozwiOM3Ws+SuEmXZT7DYrrhxGlg== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@types/connect" "3.4.36" + +"@opentelemetry/instrumentation-express@0.42.0": + version "0.42.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-express/-/instrumentation-express-0.42.0.tgz#279f195aa66baee2b98623a16666c6229c8e7564" + integrity sha512-YNcy7ZfGnLsVEqGXQPT+S0G1AE46N21ORY7i7yUQyfhGAL4RBjnZUqefMI0NwqIl6nGbr1IpF0rZGoN8Q7x12Q== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-fastify@0.39.0": + version "0.39.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fastify/-/instrumentation-fastify-0.39.0.tgz#96a040e4944daf77c53a8fe5a128bc3b2568e4aa" + integrity sha512-SS9uSlKcsWZabhBp2szErkeuuBDgxOUlllwkS92dVaWRnMmwysPhcEgHKB8rUe3BHg/GnZC1eo1hbTZv4YhfoA== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-fs@0.15.0": + version "0.15.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-fs/-/instrumentation-fs-0.15.0.tgz#41658507860f39fee5209bca961cea8d24ca2a83" + integrity sha512-JWVKdNLpu1skqZQA//jKOcKdJC66TWKqa2FUFq70rKohvaSq47pmXlnabNO+B/BvLfmidfiaN35XakT5RyMl2Q== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + +"@opentelemetry/instrumentation-generic-pool@0.39.0": + version "0.39.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-generic-pool/-/instrumentation-generic-pool-0.39.0.tgz#2b9af16ad82d5cbe67125c0125753cecd162a728" + integrity sha512-y4v8Y+tSfRB3NNBvHjbjrn7rX/7sdARG7FuK6zR8PGb28CTa0kHpEGCJqvL9L8xkTNvTXo+lM36ajFGUaK1aNw== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + +"@opentelemetry/instrumentation-graphql@0.43.0": + version "0.43.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-graphql/-/instrumentation-graphql-0.43.0.tgz#71bb94ea775c70dbd388c739b397ec1418f3f170" + integrity sha512-aI3YMmC2McGd8KW5du1a2gBA0iOMOGLqg4s9YjzwbjFwjlmMNFSK1P3AIg374GWg823RPUGfVTIgZ/juk9CVOA== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + +"@opentelemetry/instrumentation-hapi@0.41.0": + version "0.41.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-hapi/-/instrumentation-hapi-0.41.0.tgz#de8711907256d8fae1b5faf71fc825cef4a7ddbb" + integrity sha512-jKDrxPNXDByPlYcMdZjNPYCvw0SQJjN+B1A+QH+sx+sAHsKSAf9hwFiJSrI6C4XdOls43V/f/fkp9ITkHhKFbQ== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-http@0.53.0": + version "0.53.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-http/-/instrumentation-http-0.53.0.tgz#0d806adf1b3aba036bc46e16162e3c0dbb8a6b60" + integrity sha512-H74ErMeDuZfj7KgYCTOFGWF5W9AfaPnqLQQxeFq85+D29wwV2yqHbz2IKLYpkOh7EI6QwDEl7rZCIxjJLyc/CQ== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/instrumentation" "0.53.0" + "@opentelemetry/semantic-conventions" "1.27.0" + semver "^7.5.2" + +"@opentelemetry/instrumentation-ioredis@0.43.0": + version "0.43.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-ioredis/-/instrumentation-ioredis-0.43.0.tgz#dbadabaeefc4cb47c406f878444f1bcac774fa89" + integrity sha512-i3Dke/LdhZbiUAEImmRG3i7Dimm/BD7t8pDDzwepSvIQ6s2X6FPia7561gw+64w+nx0+G9X14D7rEfaMEmmjig== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-kafkajs@0.3.0": + version "0.3.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-kafkajs/-/instrumentation-kafkajs-0.3.0.tgz#6687bce4dac8b90ef8ccbf1b662d5d1e95a34414" + integrity sha512-UnkZueYK1ise8FXQeKlpBd7YYUtC7mM8J0wzUSccEfc/G8UqHQqAzIyYCUOUPUKp8GsjLnWOOK/3hJc4owb7Jg== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-koa@0.43.0": + version "0.43.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-koa/-/instrumentation-koa-0.43.0.tgz#963fd192a1b5f6cbae5dabf4ec82e3105cbb23b1" + integrity sha512-lDAhSnmoTIN6ELKmLJBplXzT/Jqs5jGZehuG22EdSMaTwgjMpxMDI1YtlKEhiWPWkrz5LUsd0aOO0ZRc9vn3AQ== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-mongodb@0.47.0": + version "0.47.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongodb/-/instrumentation-mongodb-0.47.0.tgz#f8107d878281433905e717f223fb4c0f10356a7b" + integrity sha512-yqyXRx2SulEURjgOQyJzhCECSh5i1uM49NUaq9TqLd6fA7g26OahyJfsr9NE38HFqGRHpi4loyrnfYGdrsoVjQ== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/sdk-metrics" "^1.9.1" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-mongoose@0.42.0": + version "0.42.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mongoose/-/instrumentation-mongoose-0.42.0.tgz#375afd21adfcd897a8f521c1ffd2d91e6a428705" + integrity sha512-AnWv+RaR86uG3qNEMwt3plKX1ueRM7AspfszJYVkvkehiicC3bHQA6vWdb6Zvy5HAE14RyFbu9+2hUUjR2NSyg== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-mysql2@0.41.0": + version "0.41.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql2/-/instrumentation-mysql2-0.41.0.tgz#6377b6e2d2487fd88e1d79aa03658db6c8d51651" + integrity sha512-REQB0x+IzVTpoNgVmy5b+UnH1/mDByrneimP6sbDHkp1j8QOl1HyWOrBH/6YWR0nrbU3l825Em5PlybjT3232g== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@opentelemetry/sql-common" "^0.40.1" + +"@opentelemetry/instrumentation-mysql@0.41.0": + version "0.41.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-mysql/-/instrumentation-mysql-0.41.0.tgz#2d50691ead5219774bd36d66c35d5b4681485dd7" + integrity sha512-jnvrV6BsQWyHS2qb2fkfbfSb1R/lmYwqEZITwufuRl37apTopswu9izc0b1CYRp/34tUG/4k/V39PND6eyiNvw== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@types/mysql" "2.15.26" + +"@opentelemetry/instrumentation-nestjs-core@0.40.0": + version "0.40.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-nestjs-core/-/instrumentation-nestjs-core-0.40.0.tgz#2c0e6405b56caaec32747d55c57ff9a034668ea8" + integrity sha512-WF1hCUed07vKmf5BzEkL0wSPinqJgH7kGzOjjMAiTGacofNXjb/y4KQ8loj2sNsh5C/NN7s1zxQuCgbWbVTGKg== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-pg@0.44.0": + version "0.44.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-pg/-/instrumentation-pg-0.44.0.tgz#1e97a0aeb2dca068ee23ce75884a0a0063a7ce3f" + integrity sha512-oTWVyzKqXud1BYEGX1loo2o4k4vaU1elr3vPO8NZolrBtFvQ34nx4HgUaexUDuEog00qQt+MLR5gws/p+JXMLQ== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@opentelemetry/sql-common" "^0.40.1" + "@types/pg" "8.6.1" + "@types/pg-pool" "2.0.6" + +"@opentelemetry/instrumentation-redis-4@0.42.0": + version "0.42.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-redis-4/-/instrumentation-redis-4-0.42.0.tgz#fc01104cfe884c7546385eaae03c57a47edd19d1" + integrity sha512-NaD+t2JNcOzX/Qa7kMy68JbmoVIV37fT/fJYzLKu2Wwd+0NCxt+K2OOsOakA8GVg8lSpFdbx4V/suzZZ2Pvdjg== + dependencies: + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/redis-common" "^0.36.2" + "@opentelemetry/semantic-conventions" "^1.27.0" + +"@opentelemetry/instrumentation-undici@0.6.0": + version "0.6.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation-undici/-/instrumentation-undici-0.6.0.tgz#9436ee155c8dcb0b760b66947c0e0f347688a5ef" + integrity sha512-ABJBhm5OdhGmbh0S/fOTE4N69IZ00CsHC5ijMYfzbw3E5NwLgpQk5xsljaECrJ8wz1SfXbO03FiSuu5AyRAkvQ== + dependencies: + "@opentelemetry/core" "^1.8.0" + "@opentelemetry/instrumentation" "^0.53.0" + +"@opentelemetry/instrumentation@0.53.0", "@opentelemetry/instrumentation@^0.53.0": + version "0.53.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.53.0.tgz#e6369e4015eb5112468a4d45d38dcada7dad892d" + integrity sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A== + dependencies: + "@opentelemetry/api-logs" "0.53.0" + "@types/shimmer" "^1.2.0" + import-in-the-middle "^1.8.1" + require-in-the-middle "^7.1.1" + semver "^7.5.2" + shimmer "^1.2.1" + +"@opentelemetry/instrumentation@^0.49 || ^0.50 || ^0.51 || ^0.52.0": + version "0.52.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/instrumentation/-/instrumentation-0.52.1.tgz#2e7e46a38bd7afbf03cf688c862b0b43418b7f48" + integrity sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw== + dependencies: + "@opentelemetry/api-logs" "0.52.1" + "@types/shimmer" "^1.0.2" + import-in-the-middle "^1.8.1" + require-in-the-middle "^7.1.1" + semver "^7.5.2" + shimmer "^1.2.1" + +"@opentelemetry/redis-common@^0.36.2": + version "0.36.2" + resolved "https://registry.yarnpkg.com/@opentelemetry/redis-common/-/redis-common-0.36.2.tgz#906ac8e4d804d4109f3ebd5c224ac988276fdc47" + integrity sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g== + +"@opentelemetry/resources@1.26.0", "@opentelemetry/resources@^1.26.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/resources/-/resources-1.26.0.tgz#da4c7366018bd8add1f3aa9c91c6ac59fd503cef" + integrity sha512-CPNYchBE7MBecCSVy0HKpUISEeJOniWqcHaAHpmasZ3j9o6V3AyBzhRc90jdmemq0HOxDr6ylhUbDhBqqPpeNw== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/semantic-conventions" "1.27.0" + +"@opentelemetry/sdk-metrics@^1.9.1": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-metrics/-/sdk-metrics-1.26.0.tgz#37bb0afb1d4447f50aab9cdd05db6f2d8b86103e" + integrity sha512-0SvDXmou/JjzSDOjUmetAAvcKQW6ZrvosU0rkbDGpXvvZN+pQF6JbK/Kd4hNdK4q/22yeruqvukXEJyySTzyTQ== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/resources" "1.26.0" + +"@opentelemetry/sdk-trace-base@^1.22", "@opentelemetry/sdk-trace-base@^1.26.0": + version "1.26.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/sdk-trace-base/-/sdk-trace-base-1.26.0.tgz#0c913bc6d2cfafd901de330e4540952269ae579c" + integrity sha512-olWQldtvbK4v22ymrKLbIcBi9L2SpMO84sCPY54IVsJhP9fRsxJT194C/AVaAuJzLE30EdhhM1VmvVYR7az+cw== + dependencies: + "@opentelemetry/core" "1.26.0" + "@opentelemetry/resources" "1.26.0" + "@opentelemetry/semantic-conventions" "1.27.0" + +"@opentelemetry/semantic-conventions@1.27.0", "@opentelemetry/semantic-conventions@^1.27.0": + version "1.27.0" + resolved "https://registry.yarnpkg.com/@opentelemetry/semantic-conventions/-/semantic-conventions-1.27.0.tgz#1a857dcc95a5ab30122e04417148211e6f945e6c" + integrity sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg== + +"@opentelemetry/sql-common@^0.40.1": + version "0.40.1" + resolved "https://registry.yarnpkg.com/@opentelemetry/sql-common/-/sql-common-0.40.1.tgz#93fbc48d8017449f5b3c3274f2268a08af2b83b6" + integrity sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg== + dependencies: + "@opentelemetry/core" "^1.1.0" + "@paulirish/trace_engine@^0.0.23": version "0.0.23" resolved "https://registry.yarnpkg.com/@paulirish/trace_engine/-/trace_engine-0.0.23.tgz#b3eec22421ee562837b371ddcd4659483837ec92" @@ -2257,6 +2634,15 @@ resolved "https://registry.yarnpkg.com/@polka/url/-/url-0.5.0.tgz#b21510597fd601e5d7c95008b76bf0d254ebfd31" integrity sha512-oZLYFEAzUKyi3SKnXvj32ZCEGH6RDnao7COuCVhDydMS9NrCSVXhM79VaKyP5+Zc33m0QXEd2DN3UkU7OsHcfw== +"@prisma/instrumentation@5.19.1": + version "5.19.1" + resolved "https://registry.yarnpkg.com/@prisma/instrumentation/-/instrumentation-5.19.1.tgz#146319cf85f22b7a43296f0f40cfeac55516e66e" + integrity sha512-VLnzMQq7CWroL5AeaW0Py2huiNKeoMfCH3SUxstdzPrlWQi6UQ9UrfcbUkNHlVFqOMacqy8X/8YtE0kuKDpD9w== + dependencies: + "@opentelemetry/api" "^1.8" + "@opentelemetry/instrumentation" "^0.49 || ^0.50 || ^0.51 || ^0.52.0" + "@opentelemetry/sdk-trace-base" "^1.22" + "@promptbook/utils@0.58.0": version "0.58.0" resolved "https://registry.yarnpkg.com/@promptbook/utils/-/utils-0.58.0.tgz#f85439d56e5c0593b81a1003c9dd0769f89ba76f" @@ -2665,7 +3051,7 @@ estree-walker "^1.0.1" picomatch "^2.2.2" -"@rollup/pluginutils@^4.2.0", "@rollup/pluginutils@^4.2.1": +"@rollup/pluginutils@^4.1.1", "@rollup/pluginutils@^4.2.0", "@rollup/pluginutils@^4.2.1": version "4.2.1" resolved "https://registry.yarnpkg.com/@rollup/pluginutils/-/pluginutils-4.2.1.tgz#e6c6c3aba0744edce3fb2074922d3776c0af2a6d" integrity sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ== @@ -2762,6 +3148,79 @@ resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.18.0.tgz#5d694d345ce36b6ecf657349e03eb87297e68da4" integrity sha512-UOo5FdvOL0+eIVTgS4tIdbW+TtnBLWg1YBCcU2KWM7nuNwRz9bksDX1bekJJCpu25N1DVWaCwnT39dVQxzqS8g== +"@sentry/babel-plugin-component-annotate@2.22.2": + version "2.22.2" + resolved "https://registry.yarnpkg.com/@sentry/babel-plugin-component-annotate/-/babel-plugin-component-annotate-2.22.2.tgz#f4a1ddea4bcac06584a6cec9a43ec088cbb6caaf" + integrity sha512-6kFAHGcs0npIC4HTt4ULs8uOfEucvMI7VW4hoyk17jhRaW8CbxzxfWCfIeRbDkE8pYwnARaq83tu025Hrk2zgA== + +"@sentry/bundler-plugin-core@2.22.2": + version "2.22.2" + resolved "https://registry.yarnpkg.com/@sentry/bundler-plugin-core/-/bundler-plugin-core-2.22.2.tgz#bd418541245c5167a439d4e28a84096deb20c512" + integrity sha512-TwEEW4FeEJ5Mamp4fGnktfVjzN77KAW0xFQsEPuxZtOAPG17zX/PGvdyRX/TE1jkZWhTzqUDIdgzqlNLjyEnUw== + dependencies: + "@babel/core" "^7.18.5" + "@sentry/babel-plugin-component-annotate" "2.22.2" + "@sentry/cli" "^2.33.1" + dotenv "^16.3.1" + find-up "^5.0.0" + glob "^9.3.2" + magic-string "0.30.8" + unplugin "1.0.1" + +"@sentry/cli-darwin@2.33.1": + version "2.33.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-darwin/-/cli-darwin-2.33.1.tgz#e4eb1dd01ee3ce2788025426b860ccc63759589c" + integrity sha512-+4/VIx/E1L2hChj5nGf5MHyEPHUNHJ/HoG5RY+B+vyEutGily1c1+DM2bum7RbD0xs6wKLIyup5F02guzSzG8A== + +"@sentry/cli-linux-arm64@2.33.1": + version "2.33.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm64/-/cli-linux-arm64-2.33.1.tgz#9ea1718c21ef32ca83b0852ca29fb461fd26d25a" + integrity sha512-DbGV56PRKOLsAZJX27Jt2uZ11QfQEMmWB4cIvxkKcFVE+LJP4MVA+MGGRUL6p+Bs1R9ZUuGbpKGtj0JiG6CoXw== + +"@sentry/cli-linux-arm@2.33.1": + version "2.33.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-arm/-/cli-linux-arm-2.33.1.tgz#e8a1dca4d008dd6a72ab5935304c104e98e2901c" + integrity sha512-zbxEvQju+tgNvzTOt635le4kS/Fbm2XC2RtYbCTs034Vb8xjrAxLnK0z1bQnStUV8BkeBHtsNVrG+NSQDym2wg== + +"@sentry/cli-linux-i686@2.33.1": + version "2.33.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-i686/-/cli-linux-i686-2.33.1.tgz#f1fe8dd4d6dde0812a94fba31de8054ddfb7284a" + integrity sha512-g2LS4oPXkPWOfKWukKzYp4FnXVRRSwBxhuQ9eSw2peeb58ZIObr4YKGOA/8HJRGkooBJIKGaAR2mH2Pk1TKaiA== + +"@sentry/cli-linux-x64@2.33.1": + version "2.33.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-linux-x64/-/cli-linux-x64-2.33.1.tgz#6e086675356a9eb79731bf9e447d078bae1b5adf" + integrity sha512-IV3dcYV/ZcvO+VGu9U6kuxSdbsV2kzxaBwWUQxtzxJ+cOa7J8Hn1t0koKGtU53JVZNBa06qJWIcqgl4/pCuKIg== + +"@sentry/cli-win32-i686@2.33.1": + version "2.33.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-i686/-/cli-win32-i686-2.33.1.tgz#0e6b36c4a2f5f6e85a59247a123d276b3ef10f1a" + integrity sha512-F7cJySvkpzIu7fnLKNHYwBzZYYwlhoDbAUnaFX0UZCN+5DNp/5LwTp37a5TWOsmCaHMZT4i9IO4SIsnNw16/zQ== + +"@sentry/cli-win32-x64@2.33.1": + version "2.33.1" + resolved "https://registry.yarnpkg.com/@sentry/cli-win32-x64/-/cli-win32-x64-2.33.1.tgz#2d00b38a2dd9f3355df91825582ada3ea0034e86" + integrity sha512-8VyRoJqtb2uQ8/bFRKNuACYZt7r+Xx0k2wXRGTyH05lCjAiVIXn7DiS2BxHFty7M1QEWUCMNsb/UC/x/Cu2wuA== + +"@sentry/cli@^2.33.1": + version "2.33.1" + resolved "https://registry.yarnpkg.com/@sentry/cli/-/cli-2.33.1.tgz#cfbdffdd896b05b92a659baf435b5607037af928" + integrity sha512-dUlZ4EFh98VFRPJ+f6OW3JEYQ7VvqGNMa0AMcmvk07ePNeK/GicAWmSQE4ZfJTTl80ul6HZw1kY01fGQOQlVRA== + dependencies: + https-proxy-agent "^5.0.0" + node-fetch "^2.6.7" + progress "^2.0.3" + proxy-from-env "^1.1.0" + which "^2.0.2" + optionalDependencies: + "@sentry/cli-darwin" "2.33.1" + "@sentry/cli-linux-arm" "2.33.1" + "@sentry/cli-linux-arm64" "2.33.1" + "@sentry/cli-linux-i686" "2.33.1" + "@sentry/cli-linux-x64" "2.33.1" + "@sentry/cli-win32-i686" "2.33.1" + "@sentry/cli-win32-x64" "2.33.1" + "@sentry/core@6.19.7": version "6.19.7" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-6.19.7.tgz#156aaa56dd7fad8c89c145be6ad7a4f7209f9785" @@ -2773,6 +3232,14 @@ "@sentry/utils" "6.19.7" tslib "^1.9.3" +"@sentry/core@8.30.0": + version "8.30.0" + resolved "https://registry.yarnpkg.com/@sentry/core/-/core-8.30.0.tgz#f929e42e9a537bfa3eb6024082714e9ab98d822b" + integrity sha512-CJ/FuWLw0QEKGKXGL/nm9eaOdajEcmPekLuHAuOCxID7N07R9l9laz3vFbAkUZ97GGDv3sYrJZgywfY3Moropg== + dependencies: + "@sentry/types" "8.30.0" + "@sentry/utils" "8.30.0" + "@sentry/hub@6.19.7": version "6.19.7" resolved "https://registry.yarnpkg.com/@sentry/hub/-/hub-6.19.7.tgz#58ad7776bbd31e9596a8ec46365b45cd8b9cfd11" @@ -2791,6 +3258,44 @@ "@sentry/types" "6.19.7" tslib "^1.9.3" +"@sentry/node@8.30.0", "@sentry/node@^8.26.0": + version "8.30.0" + resolved "https://registry.yarnpkg.com/@sentry/node/-/node-8.30.0.tgz#730461be3a3382ab17e2c3f95c08e4e85a207429" + integrity sha512-Tog0Ag7sU3lNj4cPUZy1KRJXyYXZlWiwlk34KYNNxAk0vDiK6W0bF8mvS+aaUukgb7FO5A0eu9l+VApdBJOr3Q== + dependencies: + "@opentelemetry/api" "^1.9.0" + "@opentelemetry/context-async-hooks" "^1.25.1" + "@opentelemetry/core" "^1.25.1" + "@opentelemetry/instrumentation" "^0.53.0" + "@opentelemetry/instrumentation-connect" "0.39.0" + "@opentelemetry/instrumentation-express" "0.42.0" + "@opentelemetry/instrumentation-fastify" "0.39.0" + "@opentelemetry/instrumentation-fs" "0.15.0" + "@opentelemetry/instrumentation-generic-pool" "0.39.0" + "@opentelemetry/instrumentation-graphql" "0.43.0" + "@opentelemetry/instrumentation-hapi" "0.41.0" + "@opentelemetry/instrumentation-http" "0.53.0" + "@opentelemetry/instrumentation-ioredis" "0.43.0" + "@opentelemetry/instrumentation-kafkajs" "0.3.0" + "@opentelemetry/instrumentation-koa" "0.43.0" + "@opentelemetry/instrumentation-mongodb" "0.47.0" + "@opentelemetry/instrumentation-mongoose" "0.42.0" + "@opentelemetry/instrumentation-mysql" "0.41.0" + "@opentelemetry/instrumentation-mysql2" "0.41.0" + "@opentelemetry/instrumentation-nestjs-core" "0.40.0" + "@opentelemetry/instrumentation-pg" "0.44.0" + "@opentelemetry/instrumentation-redis-4" "0.42.0" + "@opentelemetry/instrumentation-undici" "0.6.0" + "@opentelemetry/resources" "^1.26.0" + "@opentelemetry/sdk-trace-base" "^1.26.0" + "@opentelemetry/semantic-conventions" "^1.27.0" + "@prisma/instrumentation" "5.19.1" + "@sentry/core" "8.30.0" + "@sentry/opentelemetry" "8.30.0" + "@sentry/types" "8.30.0" + "@sentry/utils" "8.30.0" + import-in-the-middle "^1.11.0" + "@sentry/node@^6.17.4": version "6.19.7" resolved "https://registry.yarnpkg.com/@sentry/node/-/node-6.19.7.tgz#32963b36b48daebbd559e6f13b1deb2415448592" @@ -2805,11 +3310,37 @@ lru_map "^0.3.3" tslib "^1.9.3" +"@sentry/opentelemetry@8.30.0": + version "8.30.0" + resolved "https://registry.yarnpkg.com/@sentry/opentelemetry/-/opentelemetry-8.30.0.tgz#b80417d44e4d20f03a7ecf5173a8a4ed4f317a57" + integrity sha512-6mCIP2zvxAiEsNEoF8kv+UUD4XGWSKJU6RY5BF1U26HLitXv1fNPtzaTR96Ehv9h0zktjLfqfpVUZ7DGkdBvLA== + dependencies: + "@sentry/core" "8.30.0" + "@sentry/types" "8.30.0" + "@sentry/utils" "8.30.0" + +"@sentry/profiling-node@^8.26.0": + version "8.30.0" + resolved "https://registry.yarnpkg.com/@sentry/profiling-node/-/profiling-node-8.30.0.tgz#f30a7d0b2e3ffe7dc65993b33574414b6b590111" + integrity sha512-HKjIjHRtgEpUjO8LXadWEA/G8fwZ4Ej/KVfUK3tdZYimY6ISwzLQj/PFIoROOdqqWEw5z1mcF89AgIhGoKaH+Q== + dependencies: + "@sentry/core" "8.30.0" + "@sentry/node" "8.30.0" + "@sentry/types" "8.30.0" + "@sentry/utils" "8.30.0" + detect-libc "^2.0.2" + node-abi "^3.61.0" + "@sentry/types@6.19.7": version "6.19.7" resolved "https://registry.yarnpkg.com/@sentry/types/-/types-6.19.7.tgz#c6b337912e588083fc2896eb012526cf7cfec7c7" integrity sha512-jH84pDYE+hHIbVnab3Hr+ZXr1v8QABfhx39KknxqKWr2l0oEItzepV0URvbEhB446lk/S/59230dlUUIBGsXbg== +"@sentry/types@8.30.0": + version "8.30.0" + resolved "https://registry.yarnpkg.com/@sentry/types/-/types-8.30.0.tgz#5f5011f5b16bafd30a039ca5e8c337e948c703fb" + integrity sha512-kgWW2BCjBmVlSQRG32GonHEVyeDbys74xf9mLPvynwHTgw3+NUlNAlEdu05xnb2ow4bCTHfbkS5G1zRgyv5k4Q== + "@sentry/utils@6.19.7": version "6.19.7" resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-6.19.7.tgz#6edd739f8185fd71afe49cbe351c1bbf5e7b7c79" @@ -2818,6 +3349,21 @@ "@sentry/types" "6.19.7" tslib "^1.9.3" +"@sentry/utils@8.30.0": + version "8.30.0" + resolved "https://registry.yarnpkg.com/@sentry/utils/-/utils-8.30.0.tgz#2343dd8593ea83890b3e0d792ed3fa257955a26b" + integrity sha512-wZxU2HWlzsnu8214Xy7S7cRIuD6h8Z5DnnkojJfX0i0NLooepZQk2824el1Q13AakLb7/S8CHSHXOMnCtoSduw== + dependencies: + "@sentry/types" "8.30.0" + +"@sentry/vite-plugin@^2.22.2": + version "2.22.2" + resolved "https://registry.yarnpkg.com/@sentry/vite-plugin/-/vite-plugin-2.22.2.tgz#9d0ca7b3dc29616457c3d0a22e4885506e32184d" + integrity sha512-LJSNTw75UJq77v2jCan8cRh0w1u6W30jxQsbqF7YyyhhfjPTyFUXYday9RDDe84qDEnspbZmgeTSWTvaTGsBRg== + dependencies: + "@sentry/bundler-plugin-core" "2.22.2" + unplugin "1.0.1" + "@sideway/address@^4.1.5": version "4.1.5" resolved "https://registry.yarnpkg.com/@sideway/address/-/address-4.1.5.tgz#4bc149a0076623ced99ca8208ba780d65a99b9d5" @@ -3066,6 +3612,35 @@ dependencies: "@babel/types" "^7.20.7" +"@types/body-parser@*": + version "1.19.5" + resolved "https://registry.yarnpkg.com/@types/body-parser/-/body-parser-1.19.5.tgz#04ce9a3b677dc8bd681a17da1ab9835dc9d3ede4" + integrity sha512-fB3Zu92ucau0iQ0JMCFQE7b/dv8Ot07NI3KaZIkIUNXq82k4eBAqUaneXfleGY9JWskeS9y+u0nXMyspcuQrCg== + dependencies: + "@types/connect" "*" + "@types/node" "*" + +"@types/connect@*": + version "3.4.38" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.38.tgz#5ba7f3bc4fbbdeaff8dded952e5ff2cc53f8d858" + integrity sha512-K6uROf1LD88uDQqJCktA4yzL1YYAK6NgfsI0v/mTgyPKWsX1CnJ0XPSDhViejru1GcRkLWb8RlzFYJRqGUbaug== + dependencies: + "@types/node" "*" + +"@types/connect@3.4.36": + version "3.4.36" + resolved "https://registry.yarnpkg.com/@types/connect/-/connect-3.4.36.tgz#e511558c15a39cb29bd5357eebb57bd1459cd1ab" + integrity sha512-P63Zd/JUGq+PdrM1lv0Wv5SBYeA2+CORvbrXbngriYY0jzLUWfQMQQxOhjONEz/wlHOAxOdY7CY65rgQdTjq2w== + dependencies: + "@types/node" "*" + +"@types/cors@^2.8.17": + version "2.8.17" + resolved "https://registry.yarnpkg.com/@types/cors/-/cors-2.8.17.tgz#5d718a5e494a8166f569d986794e49c48b216b2b" + integrity sha512-8CGDvrBj1zgo2qE+oS3pOCyYNqCPryMWY2bGfwA0dcfopWGgxs+78df0Rs3rc9THP4JkOhLsAa+15VdpAqkcUA== + dependencies: + "@types/node" "*" + "@types/dompurify@^3.0.5": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-3.0.5.tgz#02069a2fcb89a163bacf1a788f73cb415dd75cb7" @@ -3091,11 +3666,36 @@ resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== +"@types/express-serve-static-core@^4.17.33": + version "4.19.5" + resolved "https://registry.yarnpkg.com/@types/express-serve-static-core/-/express-serve-static-core-4.19.5.tgz#218064e321126fcf9048d1ca25dd2465da55d9c6" + integrity sha512-y6W03tvrACO72aijJ5uF02FRq5cgDR9lUxddQ8vyF+GvmjJQqbzDcJngEjURc+ZsG31VI3hODNZJ2URj86pzmg== + dependencies: + "@types/node" "*" + "@types/qs" "*" + "@types/range-parser" "*" + "@types/send" "*" + +"@types/express@^4.17.21": + version "4.17.21" + resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" + integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== + dependencies: + "@types/body-parser" "*" + "@types/express-serve-static-core" "^4.17.33" + "@types/qs" "*" + "@types/serve-static" "*" + "@types/googlepay@0.7.6": version "0.7.6" resolved "https://registry.yarnpkg.com/@types/googlepay/-/googlepay-0.7.6.tgz#ba444ad8b2945e70f873673b8f5371745b8cfe37" integrity sha512-5003wG+qvf4Ktf1hC9IJuRakNzQov00+Xf09pAWGJLpdOjUrq0SSLCpXX7pwSeTG9r5hrdzq1iFyZcW7WVyr4g== +"@types/http-errors@*": + version "2.0.4" + resolved "https://registry.yarnpkg.com/@types/http-errors/-/http-errors-2.0.4.tgz#7eb47726c391b7345a6ec35ad7f4de469cf5ba4f" + integrity sha512-D0CFMMtydbJAegzOyHjtiKPLlvnm3iTZyZRSZoLq2mRhDdmLfIWOCYPfQJ4cu2erKghU++QvjcUjp/5h7hESpA== + "@types/ini@^1.3.34": version "1.3.34" resolved "https://registry.yarnpkg.com/@types/ini/-/ini-1.3.34.tgz#99a69ecfccdfc3f6e91b411d4208aaa3c4cc9685" @@ -3130,6 +3730,13 @@ resolved "https://registry.yarnpkg.com/@types/json5/-/json5-0.0.29.tgz#ee28707ae94e11d2b827bcbe5270bcea7f3e71ee" integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ== +"@types/jsonwebtoken@^9.0.6": + version "9.0.6" + resolved "https://registry.yarnpkg.com/@types/jsonwebtoken/-/jsonwebtoken-9.0.6.tgz#d1af3544d99ad992fb6681bbe60676e06b032bd3" + integrity sha512-/5hndP5dCjloafCXns6SZyESp3Ldq7YjH3zwzwczYnjxIT0Fqzk5ROSYVGfFyczIue7IUEj8hkvLbPoLQ18vQw== + dependencies: + "@types/node" "*" + "@types/jwplayer@^8.31.1": version "8.31.1" resolved "https://registry.yarnpkg.com/@types/jwplayer/-/jwplayer-8.31.1.tgz#c70c6e69981880a635ff13362a0ec490134a3242" @@ -3157,6 +3764,11 @@ resolved "https://registry.yarnpkg.com/@types/marked/-/marked-4.3.2.tgz#e2e0ad02ebf5626bd215c5bae2aff6aff0ce9eac" integrity sha512-a79Yc3TOk6dGdituy8hmTTJXjOkZ7zsFYV10L337ttq/rec8lRMDBpV7fL3uLx6TgbFCa5DU/h8FmIBQPSbU0w== +"@types/mime@^1": + version "1.3.5" + resolved "https://registry.yarnpkg.com/@types/mime/-/mime-1.3.5.tgz#1ef302e01cf7d2b5a0fa526790c9123bf1d06690" + integrity sha512-/pyBZWSLD2n0dcHE3hq8s8ZvcETHtEuF+3E7XVt0Ig2nvsVQXdghHVcEkIWjy9A0wKfTn97a/PSDYohKIlnP/w== + "@types/minimatch@^3.0.3": version "3.0.5" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.5.tgz#1001cc5e6a3704b83c236027e77f2f58ea010f40" @@ -3167,6 +3779,13 @@ resolved "https://registry.yarnpkg.com/@types/minimist/-/minimist-1.2.5.tgz#ec10755e871497bcd83efe927e43ec46e8c0747e" integrity sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag== +"@types/mysql@2.15.26": + version "2.15.26" + resolved "https://registry.yarnpkg.com/@types/mysql/-/mysql-2.15.26.tgz#f0de1484b9e2354d587e7d2bd17a873cc8300836" + integrity sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ== + dependencies: + "@types/node" "*" + "@types/node@*": version "20.14.5" resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.5.tgz#fe35e3022ebe58b8f201580eb24e1fcfc0f2487d" @@ -3179,6 +3798,13 @@ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.5.1.tgz#178d58ee7e4834152b0e8b4d30cbfab578b9bb30" integrity sha512-4tT2UrL5LBqDwoed9wZ6N3umC4Yhz3W3FloMmiiG4JwmUJWpie0c7lcnUNd4gtMKuDEO4wRVS8B6Xa0uMRsMKg== +"@types/node@>=8.1.0": + version "22.5.5" + resolved "https://registry.yarnpkg.com/@types/node/-/node-22.5.5.tgz#52f939dd0f65fc552a4ad0b392f3c466cc5d7a44" + integrity sha512-Xjs4y5UPO/CLdzpgR6GirZJx36yScjh73+2NlLlkFRSoQN8B0DpfXPdZGnvVmLRLOsqDpOfTNv7D9trgGhmOIA== + dependencies: + undici-types "~6.19.2" + "@types/node@^18.19.37": version "18.19.50" resolved "https://registry.yarnpkg.com/@types/node/-/node-18.19.50.tgz#8652b34ee7c0e7e2004b3f08192281808d41bf5a" @@ -3193,6 +3819,13 @@ dependencies: undici-types "~5.26.4" +"@types/node@^20.14.10": + version "20.14.10" + resolved "https://registry.yarnpkg.com/@types/node/-/node-20.14.10.tgz#a1a218290f1b6428682e3af044785e5874db469a" + integrity sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ== + dependencies: + undici-types "~5.26.4" + "@types/normalize-package-data@^2.4.0": version "2.4.4" resolved "https://registry.yarnpkg.com/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz#56e2cc26c397c038fab0e3a917a12d5c5909e901" @@ -3208,6 +3841,31 @@ resolved "https://registry.yarnpkg.com/@types/payment/-/payment-2.1.7.tgz#02e6cb0cf8b576e72a35477111c1d754d3ac3d1f" integrity sha512-J9BCWMJ+S2PBCSVTvdE+/ziiV73Cu3Wu6WQCC6D+1AO8vphGaQUM6hOy+UaKo84md5mrMcn7P9G/TmD9Y2922A== +"@types/pg-pool@2.0.6": + version "2.0.6" + resolved "https://registry.yarnpkg.com/@types/pg-pool/-/pg-pool-2.0.6.tgz#1376d9dc5aec4bb2ec67ce28d7e9858227403c77" + integrity sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ== + dependencies: + "@types/pg" "*" + +"@types/pg@*": + version "8.11.9" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.11.9.tgz#3e92f7edbe4df9de9397f5308d7fe80c31faefe8" + integrity sha512-M4mYeJZRBD9lCBCGa72F44uKSV9eJrAFfjlPJagdA6pgIr2OPJULFB7nqnZzOdqXG0qzHlgtZKzTdIgbmHitSg== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^4.0.1" + +"@types/pg@8.6.1": + version "8.6.1" + resolved "https://registry.yarnpkg.com/@types/pg/-/pg-8.6.1.tgz#099450b8dc977e8197a44f5229cedef95c8747f9" + integrity sha512-1Kc4oAGzAl7uqUStZCDvaLFqZrW9qWSjXOmBfdgyBP5La7Us6Mg4GBvRlSoaZMhQF/zSj1C8CtKMBkoiT8eL8w== + dependencies: + "@types/node" "*" + pg-protocol "*" + pg-types "^2.2.0" + "@types/postcss-modules-local-by-default@^4.0.2": version "4.0.2" resolved "https://registry.yarnpkg.com/@types/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.2.tgz#8fee7513dd1558d74713d817c183a33a6dc583f9" @@ -3227,6 +3885,16 @@ resolved "https://registry.yarnpkg.com/@types/prop-types/-/prop-types-15.7.12.tgz#12bb1e2be27293c1406acb6af1c3f3a1481d98c6" integrity sha512-5zvhXYtRNRluoE/jAp4GVsSduVUzNWKkOZrCDBWYtE7biZywwdC2AcEzg+cSMLFRfVgeAFqpfNabiPjxFddV1Q== +"@types/qs@*": + version "6.9.15" + resolved "https://registry.yarnpkg.com/@types/qs/-/qs-6.9.15.tgz#adde8a060ec9c305a82de1babc1056e73bd64dce" + integrity sha512-uXHQKES6DQKKCLh441Xv/dwxOq1TVS3JPUMlEqoEglvlhR6Mxnlew/Xq/LRVHpLyk7iK3zODe1qYHIMltO7XGg== + +"@types/range-parser@*": + version "1.2.7" + resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" + integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== + "@types/react-dom@^18.0.0", "@types/react-dom@^18.3.0": version "18.3.0" resolved "https://registry.yarnpkg.com/@types/react-dom/-/react-dom-18.3.0.tgz#0cbc818755d87066ab6ca74fbedb2547d74a82b0" @@ -3268,6 +3936,28 @@ resolved "https://registry.yarnpkg.com/@types/resolve/-/resolve-1.20.2.tgz#97d26e00cd4a0423b4af620abecf3e6f442b7975" integrity sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q== +"@types/send@*": + version "0.17.4" + resolved "https://registry.yarnpkg.com/@types/send/-/send-0.17.4.tgz#6619cd24e7270793702e4e6a4b958a9010cfc57a" + integrity sha512-x2EM6TJOybec7c52BX0ZspPodMsQUd5L6PRwOunVyVUhXiBSKf3AezDL8Dgvgt5o0UfKNfuA0eMLr2wLT4AiBA== + dependencies: + "@types/mime" "^1" + "@types/node" "*" + +"@types/serve-static@*": + version "1.15.7" + resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" + integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== + dependencies: + "@types/http-errors" "*" + "@types/node" "*" + "@types/send" "*" + +"@types/shimmer@^1.0.2", "@types/shimmer@^1.2.0": + version "1.2.0" + resolved "https://registry.yarnpkg.com/@types/shimmer/-/shimmer-1.2.0.tgz#9b706af96fa06416828842397a70dfbbf1c14ded" + integrity sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg== + "@types/stack-utils@^2.0.0": version "2.0.3" resolved "https://registry.yarnpkg.com/@types/stack-utils/-/stack-utils-2.0.3.tgz#6209321eb2c1712a7e7466422b8cb1fc0d9dd5d8" @@ -3588,7 +4278,7 @@ abort-controller@^3.0.0: dependencies: event-target-shim "^5.0.0" -accepts@^1.3.7, accepts@~1.3.5, accepts@~1.3.7: +accepts@^1.3.7, accepts@~1.3.5, accepts@~1.3.7, accepts@~1.3.8: version "1.3.8" resolved "https://registry.yarnpkg.com/accepts/-/accepts-1.3.8.tgz#0bf0be125b67014adcb0b0921e62db7bffe16b2e" integrity sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw== @@ -3596,6 +4286,11 @@ accepts@^1.3.7, accepts@~1.3.5, accepts@~1.3.7: mime-types "~2.1.34" negotiator "0.6.3" +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== + acorn-jsx@^5.3.2: version "5.3.2" resolved "https://registry.yarnpkg.com/acorn-jsx/-/acorn-jsx-5.3.2.tgz#7ed5bb55908b3b2f1bc55c6af1653bada7f07937" @@ -3618,6 +4313,11 @@ acorn@^8.11.0, acorn@^8.11.3, acorn@^8.4.1, acorn@^8.8.2, acorn@^8.9.0: resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.0.tgz#1627bfa2e058148036133b8d9b51a700663c294c" integrity sha512-RTvkC4w+KNXrM39/lWCUaG0IbRkWdCv7W/IOW9oU6SawyxulvkQy5HQPVTKxEjczcUvapcrw3cFx/60VN/NRNw== +acorn@^8.8.1: + version "8.12.1" + resolved "https://registry.yarnpkg.com/acorn/-/acorn-8.12.1.tgz#71616bdccbe25e27a54439e0046e89ca76df2248" + integrity sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg== + agent-base@6: version "6.0.2" resolved "https://registry.yarnpkg.com/agent-base/-/agent-base-6.0.2.tgz#49fff58577cfee3f37176feab4c22e00f86d7f77" @@ -3809,6 +4509,11 @@ array-buffer-byte-length@^1.0.0, array-buffer-byte-length@^1.0.1: call-bind "^1.0.5" is-array-buffer "^3.0.4" +array-flatten@1.1.1: + version "1.1.1" + resolved "https://registry.yarnpkg.com/array-flatten/-/array-flatten-1.1.1.tgz#9a5f699051b1e7073328f2a008968b64ea2955d2" + integrity sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg== + array-ify@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" @@ -4162,6 +4867,24 @@ bo-selector@0.0.10: resolved "https://registry.yarnpkg.com/bo-selector/-/bo-selector-0.0.10.tgz#9816dcb00adf374ea87941a863b2acfc026afa3e" integrity sha512-Drm8W3MFLNhzHTXG93g8ll7wBlmiRr5C9W8R0sbsNQp/8h1IoPnzDH4dEQuJx8VaNq02io2ZfFnzKC1s64xRJg== +body-parser@1.20.2: + version "1.20.2" + resolved "https://registry.yarnpkg.com/body-parser/-/body-parser-1.20.2.tgz#6feb0e21c4724d06de7ff38da36dad4f57a747fd" + integrity sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA== + dependencies: + bytes "3.1.2" + content-type "~1.0.5" + debug "2.6.9" + depd "2.0.0" + destroy "1.2.0" + http-errors "2.0.0" + iconv-lite "0.4.24" + on-finished "2.4.1" + qs "6.11.0" + raw-body "2.5.2" + type-is "~1.6.18" + unpipe "1.0.0" + boolbase@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e" @@ -4265,6 +4988,16 @@ browserslist@^4.22.2, browserslist@^4.23.0: node-releases "^2.0.14" update-browserslist-db "^1.0.16" +browserslist@^4.23.1: + version "4.23.3" + resolved "https://registry.yarnpkg.com/browserslist/-/browserslist-4.23.3.tgz#debb029d3c93ebc97ffbc8d9cbb03403e227c800" + integrity sha512-btwCFJVjI4YWDNfau8RhZ+B1Q/VLoUITrm3RlP6y1tYGWIOa+InuYiRGXUBXo8nA1qKmHMyLB/iVQg5TT4eFoA== + dependencies: + caniuse-lite "^1.0.30001646" + electron-to-chromium "^1.5.4" + node-releases "^2.0.18" + update-browserslist-db "^1.1.0" + bser@2.1.1: version "2.1.1" resolved "https://registry.yarnpkg.com/bser/-/bser-2.1.1.tgz#e6787da20ece9d07998533cfd9de6f5c38f4bc05" @@ -4277,6 +5010,11 @@ buffer-crc32@~0.2.3: resolved "https://registry.yarnpkg.com/buffer-crc32/-/buffer-crc32-0.2.13.tgz#0d333e3f00eac50aa1454abd30ef8c2a5d9a7242" integrity sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ== +buffer-equal-constant-time@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz#f8e71132f7ffe6e01a5c9697a4c6f3e48d5cc819" + integrity sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA== + buffer-from@^1.0.0: version "1.1.2" resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" @@ -4347,6 +5085,11 @@ bytes@3.0.0: resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" integrity sha512-pMhOfFDPiv9t5jjIXkHosWmkSyQbvsgEVNkz0ERHbuLh2T/7j4Mqqpz523Fe8MVY89KC6Sh/QfS2sM+SjgFDcw== +bytes@3.1.2: + version "3.1.2" + resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.1.2.tgz#8b0beeb98605adf1b128fa4386403c009e0221a5" + integrity sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg== + cac@^6.7.14: version "6.7.14" resolved "https://registry.yarnpkg.com/cac/-/cac-6.7.14.tgz#804e1e6f506ee363cb0e3ccbb09cad5dd9870959" @@ -4434,6 +5177,11 @@ caniuse-lite@^1.0.30001629: resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001636.tgz#b15f52d2bdb95fad32c2f53c0b68032b85188a78" integrity sha512-bMg2vmr8XBsbL6Lr0UHXy/21m84FTxDLWn2FSqMd5PrlbMxwJlQnC2YWYxVgp66PZE+BBNF2jYQUBKCo1FDeZg== +caniuse-lite@^1.0.30001646: + version "1.0.30001653" + resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001653.tgz#b8af452f8f33b1c77f122780a4aecebea0caca56" + integrity sha512-XGWQVB8wFQ2+9NZwZ10GxTYC5hk0Fa+q8cSkr0tgvMhYhMHP/QC+WTgrePMDBWiWc/pV+1ik82Al20XOK25Gcw== + centra@^2.7.0: version "2.7.0" resolved "https://registry.yarnpkg.com/centra/-/centra-2.7.0.tgz#4c8312a58436e8a718302011561db7e6a2b0ec18" @@ -4688,6 +5436,11 @@ ci-info@^3.2.0: resolved "https://registry.yarnpkg.com/ci-info/-/ci-info-3.9.0.tgz#4279a62028a7b1f262f3473fc9605f5e218c59b4" integrity sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ== +cjs-module-lexer@^1.2.2: + version "1.4.1" + resolved "https://registry.yarnpkg.com/cjs-module-lexer/-/cjs-module-lexer-1.4.1.tgz#707413784dbb3a72aa11c2f2b042a0bef4004170" + integrity sha512-cuSVIHi9/9E/+821Qjdvngor+xpnlwnuwIyZOaLmHBVdXL+gP+I6QQB9VkO7RI77YIcTV+S1W9AreJ5eN63JBA== + class-transformer@0.5.1: version "0.5.1" resolved "https://registry.yarnpkg.com/class-transformer/-/class-transformer-0.5.1.tgz#24147d5dffd2a6cea930a3250a677addf96ab336" @@ -5082,6 +5835,18 @@ console-grid@^2.2.2: resolved "https://registry.yarnpkg.com/console-grid/-/console-grid-2.2.2.tgz#7b786f6c977b1ae5dcaab21c167f97329ca3fad4" integrity sha512-ohlgXexdDTKLNsZz7DSJuCAwmRc8omSS61txOk39W3NOthgKGr1a1jJpZ5BCQe4PlrwMw01OvPQ1Bl3G7Y/uFg== +content-disposition@0.5.4: + version "0.5.4" + resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.4.tgz#8b82b4efac82512a02bb0b1dcec9d2c5e8eb5bfe" + integrity sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ== + dependencies: + safe-buffer "5.2.1" + +content-type@~1.0.4, content-type@~1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/content-type/-/content-type-1.0.5.tgz#8b773162656d1d1086784c8f23a54ce6d73d7918" + integrity sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA== + conventional-changelog-angular@^6.0.0: version "6.0.0" resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-6.0.0.tgz#a9a9494c28b7165889144fd5b91573c4aa9ca541" @@ -5116,6 +5881,16 @@ convert-source-map@^2.0.0: resolved "https://registry.yarnpkg.com/convert-source-map/-/convert-source-map-2.0.0.tgz#4b560f649fc4e918dd0ab75cf4961e8bc882d82a" integrity sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg== +cookie-signature@1.0.6: + version "1.0.6" + resolved "https://registry.yarnpkg.com/cookie-signature/-/cookie-signature-1.0.6.tgz#e303a882b342cc3ee8ca513a79999734dab3ae2c" + integrity sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ== + +cookie@0.6.0: + version "0.6.0" + resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.6.0.tgz#2798b04b071b0ecbff0dbb62a505a8efa4e19051" + integrity sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw== + cookie@^0.4.1: version "0.4.2" resolved "https://registry.yarnpkg.com/cookie/-/cookie-0.4.2.tgz#0e41f24de5ecf317947c82fc789e06a884824432" @@ -5150,6 +5925,14 @@ core-util-is@~1.0.0: resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.3.tgz#a6042d3634c2b27e9328f837b965fac83808db85" integrity sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ== +cors@^2.8.5: + version "2.8.5" + resolved "https://registry.yarnpkg.com/cors/-/cors-2.8.5.tgz#eac11da51592dd86b9f06f6e7ac293b3df875d29" + integrity sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g== + dependencies: + object-assign "^4" + vary "^1" + cosmiconfig-typescript-loader@^4.0.0: version "4.4.0" resolved "https://registry.yarnpkg.com/cosmiconfig-typescript-loader/-/cosmiconfig-typescript-loader-4.4.0.tgz#f3feae459ea090f131df5474ce4b1222912319f9" @@ -5436,7 +6219,7 @@ debug@^3.2.7: dependencies: ms "^2.1.1" -debug@~4.3.6: +debug@^4.3.5, debug@~4.3.6: version "4.3.7" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.7.tgz#87945b4151a011d76d95a198d7111c865c360a52" integrity sha512-Er2nc/H7RrMXZBFCEim6TCmMk02Z8vLC2Rbi1KEBggpo0fS6l0S1nnapwmIi3yW/+GOJap1Krg4w0Hg80oCqgQ== @@ -5868,7 +6651,7 @@ dotenv-expand@^8.0.2: resolved "https://registry.yarnpkg.com/dotenv-expand/-/dotenv-expand-8.0.3.tgz#29016757455bcc748469c83a19b36aaf2b83dd6e" integrity sha512-SErOMvge0ZUyWd5B0NXMQlDkN+8r+HhVUsxgOO7IoPDOdDRD2JjExpN6y3KnFR66jsJMwSn1pqIivhU5rcJiNg== -dotenv@^16.0.0, dotenv@^16.4.2: +dotenv@^16.0.0, dotenv@^16.3.1, dotenv@^16.4.2, dotenv@^16.4.5: version "16.4.5" resolved "https://registry.yarnpkg.com/dotenv/-/dotenv-16.4.5.tgz#cdd3b3b604cb327e286b4762e13502f717cb099f" integrity sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg== @@ -5926,6 +6709,13 @@ easy-table@1.2.0: optionalDependencies: wcwidth "^1.0.1" +ecdsa-sig-formatter@1.0.11: + version "1.0.11" + resolved "https://registry.yarnpkg.com/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz#ae0f0fa2d85045ef14a817daa3ce9acd0489e5bf" + integrity sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ== + dependencies: + safe-buffer "^5.0.1" + edge-paths@^3.0.5: version "3.0.5" resolved "https://registry.yarnpkg.com/edge-paths/-/edge-paths-3.0.5.tgz#9a35361d701d9b5dc07f641cebe8da01ede80937" @@ -5978,6 +6768,11 @@ electron-to-chromium@^1.4.796: resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.4.805.tgz#1d526e384c20944a3c68f618f9774edc384c4733" integrity sha512-8W4UJwX/w9T0QSzINJckTKG6CYpAUTqsaWcWIsdud3I1FYJcMgW9QqT1/4CBff/pP/TihWh13OmiyY8neto6vw== +electron-to-chromium@^1.5.4: + version "1.5.13" + resolved "https://registry.yarnpkg.com/electron-to-chromium/-/electron-to-chromium-1.5.13.tgz#1abf0410c5344b2b829b7247e031f02810d442e6" + integrity sha512-lbBcvtIJ4J6sS4tb5TLp1b4LyfCdMkwStzXPyAgVgTRAsep4bvrAGaBOP7ZJtQMNJpSQ9SqG4brWOroNaQtm7Q== + email-addresses@^5.0.0: version "5.0.0" resolved "https://registry.yarnpkg.com/email-addresses/-/email-addresses-5.0.0.tgz#7ae9e7f58eef7d5e3e2c2c2d3ea49b78dc854fa6" @@ -6572,6 +7367,43 @@ expand-template@^2.0.3: resolved "https://registry.yarnpkg.com/expand-template/-/expand-template-2.0.3.tgz#6e14b3fcee0f3a6340ecb57d2e8918692052a47c" integrity sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg== +express@^4.19.2: + version "4.19.2" + resolved "https://registry.yarnpkg.com/express/-/express-4.19.2.tgz#e25437827a3aa7f2a827bc8171bbbb664a356465" + integrity sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q== + dependencies: + accepts "~1.3.8" + array-flatten "1.1.1" + body-parser "1.20.2" + content-disposition "0.5.4" + content-type "~1.0.4" + cookie "0.6.0" + cookie-signature "1.0.6" + debug "2.6.9" + depd "2.0.0" + encodeurl "~1.0.2" + escape-html "~1.0.3" + etag "~1.8.1" + finalhandler "1.2.0" + fresh "0.5.2" + http-errors "2.0.0" + merge-descriptors "1.0.1" + methods "~1.1.2" + on-finished "2.4.1" + parseurl "~1.3.3" + path-to-regexp "0.1.7" + proxy-addr "~2.0.7" + qs "6.11.0" + range-parser "~1.2.1" + safe-buffer "5.2.1" + send "0.18.0" + serve-static "1.15.0" + setprototypeof "1.2.0" + statuses "2.0.1" + type-is "~1.6.18" + utils-merge "1.0.1" + vary "~1.1.2" + external-editor@^3.0.3: version "3.1.0" resolved "https://registry.yarnpkg.com/external-editor/-/external-editor-3.1.0.tgz#cb03f740befae03ea4d283caed2741a83f335495" @@ -6753,6 +7585,19 @@ finalhandler@1.1.2: statuses "~1.5.0" unpipe "~1.0.0" +finalhandler@1.2.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/finalhandler/-/finalhandler-1.2.0.tgz#7d23fe5731b207b4640e4fcd00aec1f9207a7b32" + integrity sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg== + dependencies: + debug "2.6.9" + encodeurl "~1.0.2" + escape-html "~1.0.3" + on-finished "2.4.1" + parseurl "~1.3.3" + statuses "2.0.1" + unpipe "~1.0.0" + find-cache-dir@^2.0.0: version "2.1.0" resolved "https://registry.yarnpkg.com/find-cache-dir/-/find-cache-dir-2.1.0.tgz#8d0f94cd13fe43c6c7c261a0d86115ca918c05f7" @@ -6888,6 +7733,11 @@ formdata-polyfill@^4.0.10: dependencies: fetch-blob "^3.1.2" +forwarded@0.2.0: + version "0.2.0" + resolved "https://registry.yarnpkg.com/forwarded/-/forwarded-0.2.0.tgz#2269936428aad4c15c7ebe9779a84bf0b2a81811" + integrity sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow== + fresh@0.5.2: version "0.5.2" resolved "https://registry.yarnpkg.com/fresh/-/fresh-0.5.2.tgz#3d8cadd90d976569fa835ab1f8e4b23a105605a7" @@ -7219,6 +8069,16 @@ glob@^7.0.3, glob@^7.1.1, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^9.3.2: + version "9.3.5" + resolved "https://registry.yarnpkg.com/glob/-/glob-9.3.5.tgz#ca2ed8ca452781a3009685607fdf025a899dfe21" + integrity sha512-e1LleDykUz2Iu+MTYdkSsuWX8lvAjAcs0Xef0lNIu0S2wOAzuTxCJtcd9S3cijlwYF18EsU3rzb8jPVobxDh9Q== + dependencies: + fs.realpath "^1.0.0" + minimatch "^8.0.2" + minipass "^4.2.4" + path-scurry "^1.6.1" + global-dirs@^0.1.1: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" @@ -7643,6 +8503,13 @@ ico-endec@*: resolved "https://registry.yarnpkg.com/ico-endec/-/ico-endec-0.1.6.tgz#9b320cc3ed0a0c779f54e998a8db49002abd7c6e" integrity sha512-ZdLU38ZoED3g1j3iEyzcQj+wAkY2xfWNkymszfJPoxucIUhK7NayQ+/C4Kv0nDFMIsbtbEHldv3V8PU494/ueQ== +iconv-lite@0.4.24, iconv-lite@^0.4.24: + version "0.4.24" + resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" + integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== + dependencies: + safer-buffer ">= 2.1.2 < 3" + iconv-lite@0.6.3, iconv-lite@^0.6.3: version "0.6.3" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.6.3.tgz#a52f80bf38da1952eb5c681790719871a1a72501" @@ -7650,13 +8517,6 @@ iconv-lite@0.6.3, iconv-lite@^0.6.3: dependencies: safer-buffer ">= 2.1.2 < 3.0.0" -iconv-lite@^0.4.24: - version "0.4.24" - resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" - integrity sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA== - dependencies: - safer-buffer ">= 2.1.2 < 3" - icss-utils@^5.0.0, icss-utils@^5.1.0: version "5.1.0" resolved "https://registry.yarnpkg.com/icss-utils/-/icss-utils-5.1.0.tgz#c6be6858abd013d768e98366ae47e25d5887b1ae" @@ -7720,6 +8580,16 @@ import-fresh@^3.0.0, import-fresh@^3.2.1, import-fresh@^3.3.0: parent-module "^1.0.0" resolve-from "^4.0.0" +import-in-the-middle@^1.11.0, import-in-the-middle@^1.8.1: + version "1.11.0" + resolved "https://registry.yarnpkg.com/import-in-the-middle/-/import-in-the-middle-1.11.0.tgz#a94c4925b8da18256cde3b3b7b38253e6ca5e708" + integrity sha512-5DimNQGoe0pLUHbR9qK84iWaWjjbsxiqXnw6Qz64+azRgleqv9k2kTt5fw7QsOpmaGYtuxxursnPPsnTKEx10Q== + dependencies: + acorn "^8.8.2" + acorn-import-attributes "^1.9.5" + cjs-module-lexer "^1.2.2" + module-details-from-path "^1.0.3" + import-lazy@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/import-lazy/-/import-lazy-4.0.0.tgz#e8eb627483a0a43da3c03f3e35548be5cb0cc153" @@ -7826,6 +8696,11 @@ ip-address@^9.0.5: jsbn "1.1.0" sprintf-js "^1.1.3" +ipaddr.js@1.9.1: + version "1.9.1" + resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" + integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== + is-arguments@^1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/is-arguments/-/is-arguments-1.1.1.tgz#15b3f88fda01f2a97fec84ca761a560f123efa9b" @@ -8631,6 +9506,22 @@ jsonpointer@^5.0.0: resolved "https://registry.yarnpkg.com/jsonpointer/-/jsonpointer-5.0.1.tgz#2110e0af0900fd37467b5907ecd13a7884a1b559" integrity sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ== +jsonwebtoken@^9.0.2: + version "9.0.2" + resolved "https://registry.yarnpkg.com/jsonwebtoken/-/jsonwebtoken-9.0.2.tgz#65ff91f4abef1784697d40952bb1998c504caaf3" + integrity sha512-PRp66vJ865SSqOlgqS8hujT5U4AOgMfhrwYIuIhfKaoSCZcirrmASQr8CX7cUg+RMih+hgznrjp99o+W4pJLHQ== + dependencies: + jws "^3.2.2" + lodash.includes "^4.3.0" + lodash.isboolean "^3.0.3" + lodash.isinteger "^4.0.4" + lodash.isnumber "^3.0.3" + lodash.isplainobject "^4.0.6" + lodash.isstring "^4.0.1" + lodash.once "^4.0.0" + ms "^2.1.1" + semver "^7.5.4" + "jsx-ast-utils@^2.4.1 || ^3.0.0": version "3.3.5" resolved "https://registry.yarnpkg.com/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz#4766bd05a8e2a11af222becd19e15575e52a853a" @@ -8641,6 +9532,23 @@ jsonpointer@^5.0.0: object.assign "^4.1.4" object.values "^1.1.6" +jwa@^1.4.1: + version "1.4.1" + resolved "https://registry.yarnpkg.com/jwa/-/jwa-1.4.1.tgz#743c32985cb9e98655530d53641b66c8645b039a" + integrity sha512-qiLX/xhEEFKUAJ6FiBMbes3w9ATzyk5W7Hvzpa/SLYdxNtng+gcurvrI7TbACjIXlsJyr05/S1oUhZrc63evQA== + dependencies: + buffer-equal-constant-time "1.0.1" + ecdsa-sig-formatter "1.0.11" + safe-buffer "^5.0.1" + +jws@^3.2.2: + version "3.2.2" + resolved "https://registry.yarnpkg.com/jws/-/jws-3.2.2.tgz#001099f3639468c9414000e99995fa52fb478304" + integrity sha512-YHlZCB6lMTllWDtSPHz/ZXTsi8S00usEV6v1tjq8tOUZzw7DpSDWVXjXDre6ed1w/pd495ODpHZYSdkRTsa0HA== + dependencies: + jwa "^1.4.1" + safe-buffer "^5.0.1" + jwt-decode@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/jwt-decode/-/jwt-decode-4.0.0.tgz#2270352425fd413785b2faf11f6e755c5151bd4b" @@ -8931,16 +9839,41 @@ lodash.debounce@^4.0.8: resolved "https://registry.yarnpkg.com/lodash.debounce/-/lodash.debounce-4.0.8.tgz#82d79bff30a67c4005ffd5e2515300ad9ca4d7af" integrity sha512-FT1yDzDYEoYWhnSGnpE/4Kj1fLZkDFyqRb7fNt6FdYOSxlUWAtp42Eh6Wb0rGIv/m9Bgo7x4GhQbm5Ys4SG5ow== +lodash.includes@^4.3.0: + version "4.3.0" + resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f" + integrity sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w== + +lodash.isboolean@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6" + integrity sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg== + lodash.isfunction@^3.0.9: version "3.0.9" resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051" integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw== +lodash.isinteger@^4.0.4: + version "4.0.4" + resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343" + integrity sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA== + +lodash.isnumber@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz#3ce76810c5928d03352301ac287317f11c0b1ffc" + integrity sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw== + lodash.isplainobject@^4.0.6: version "4.0.6" resolved "https://registry.yarnpkg.com/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz#7c526a52d89b45c45cc690b88163be0497f550cb" integrity sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA== +lodash.isstring@^4.0.1: + version "4.0.1" + resolved "https://registry.yarnpkg.com/lodash.isstring/-/lodash.isstring-4.0.1.tgz#d527dfb5456eca7cc9bb95d5daeaf88ba54a5451" + integrity sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw== + lodash.kebabcase@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.kebabcase/-/lodash.kebabcase-4.1.1.tgz#8489b1cb0d29ff88195cceca448ff6d6cc295c36" @@ -8956,6 +9889,11 @@ lodash.mergewith@^4.6.2: resolved "https://registry.yarnpkg.com/lodash.mergewith/-/lodash.mergewith-4.6.2.tgz#617121f89ac55f59047c7aec1ccd6654c6590f55" integrity sha512-GK3g5RPZWTRSeLSpgP8Xhra+pnjBC56q9FZYe1d5RN3TJ35dbkGy3YqBSMbyCrlbi+CM9Z3Jk5yTL7RCsqboyQ== +lodash.once@^4.0.0: + version "4.1.1" + resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac" + integrity sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg== + lodash.snakecase@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/lodash.snakecase/-/lodash.snakecase-4.1.1.tgz#39d714a35357147837aefd64b5dcbb16becd8f8d" @@ -9119,6 +10057,13 @@ lz-utils@^2.0.2: resolved "https://registry.yarnpkg.com/lz-utils/-/lz-utils-2.0.2.tgz#9ccf1f76400617da5b3f5a05192f5227cffd6881" integrity sha512-i1PJN4hNEevkrvLMqNWCCac1BcB5SRaghywG7HVzWOyVkFOasLCG19ND1sY1F/ZEsM6SnGtoXyBWnmfqOM5r6g== +magic-string@0.30.8: + version "0.30.8" + resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.8.tgz#14e8624246d2bedba70d5462aa99ac9681844613" + integrity sha512-ISQTe55T2ao7XtlAStud6qwYPZjE4GK1S/BeVPus4jrq6JuOnQ00YKQC581RWhR122W7msZV263KzVeLoqidyQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.4.15" + magic-string@^0.25.0, magic-string@^0.25.7: version "0.25.9" resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.25.9.tgz#de7f9faf91ef8a1c91d02c2e5314c8277dbcdd1c" @@ -9220,6 +10165,11 @@ mdn-data@2.0.30: resolved "https://registry.yarnpkg.com/mdn-data/-/mdn-data-2.0.30.tgz#ce4df6f80af6cfbe218ecd5c552ba13c4dfa08cc" integrity sha512-GaqWWShW4kv/G9IEucWScBx9G1/vsFZZJUO+tD26M8J8z3Kw5RDQjaoZe03YAClgeS/SWPOcb4nkFBTEi5DUEA== +media-typer@0.3.0: + version "0.3.0" + resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" + integrity sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ== + memoize-one@^5.0.0: version "5.2.1" resolved "https://registry.yarnpkg.com/memoize-one/-/memoize-one-5.2.1.tgz#8337aa3c4335581839ec01c3d594090cebe8f00e" @@ -9265,6 +10215,11 @@ meow@^8.0.0, meow@^8.1.2: type-fest "^0.18.0" yargs-parser "^20.2.3" +merge-descriptors@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/merge-descriptors/-/merge-descriptors-1.0.1.tgz#b00aaa556dd8b44568150ec9d1b953f3f90cbb61" + integrity sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w== + merge-stream@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/merge-stream/-/merge-stream-2.0.0.tgz#52823629a14dd00c9770fb6ad47dc6310f2c1f60" @@ -9280,6 +10235,11 @@ metaviewport-parser@0.3.0: resolved "https://registry.yarnpkg.com/metaviewport-parser/-/metaviewport-parser-0.3.0.tgz#6af1e99b5eaf250c049e0af1e84143a39750dea6" integrity sha512-EoYJ8xfjQ6kpe9VbVHvZTZHiOl4HL1Z18CrZ+qahvLXT7ZO4YTC2JMyt5FaUp9JJp6J4Ybb/z7IsCXZt86/QkQ== +methods@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/methods/-/methods-1.1.2.tgz#5529a4d67654134edcc5266656835b0f851afcee" + integrity sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w== + metro-babel-transformer@0.80.9: version "0.80.9" resolved "https://registry.yarnpkg.com/metro-babel-transformer/-/metro-babel-transformer-0.80.9.tgz#7051ba377b7d2140abd23f4846bbbb1e81fea99b" @@ -9487,7 +10447,7 @@ mime-lite@^1.0.3: resolved "https://registry.yarnpkg.com/mime-lite/-/mime-lite-1.0.3.tgz#778e1880842545f71b223898cdff1c9ddbc995fe" integrity sha512-V85l97zJSTG8FEvmdTlmNYb0UMrVBwvRjw7bWTf/aT6KjFwtz3iTz8D2tuFIp7lwiaO2C5ecnrEmSkkMRCrqVw== -mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.34: +mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.24, mime-types@~2.1.34: version "2.1.35" resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.35.tgz#381a871b62a734450660ae3deee44813f70d959a" integrity sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw== @@ -9562,6 +10522,13 @@ minimatch@^5.0.1: dependencies: brace-expansion "^2.0.1" +minimatch@^8.0.2: + version "8.0.4" + resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-8.0.4.tgz#847c1b25c014d4e9a7f68aaf63dedd668a626229" + integrity sha512-W0Wvr9HyFXZRGIDgCicunpQ299OKXs9RgZfaukz4qAW/pJhcpUfupc9c+OObPOFueNy8VSrZgEmDtk6Kh4WzDA== + dependencies: + brace-expansion "^2.0.1" + minimatch@^9.0.4: version "9.0.4" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.4.tgz#8e49c731d1749cbec05050ee5145147b32496a51" @@ -9588,6 +10555,11 @@ minimist@^1.1.0, minimist@^1.2.0, minimist@^1.2.3, minimist@^1.2.5, minimist@^1. resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== +minipass@^4.2.4: + version "4.2.8" + resolved "https://registry.yarnpkg.com/minipass/-/minipass-4.2.8.tgz#f0010f64393ecfc1d1ccb5f582bcaf45f48e1a3a" + integrity sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ== + "minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: version "7.1.2" resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" @@ -9661,6 +10633,11 @@ mocha@10.4.0: yargs-parser "20.2.4" yargs-unparser "2.0.0" +module-details-from-path@^1.0.3: + version "1.0.3" + resolved "https://registry.yarnpkg.com/module-details-from-path/-/module-details-from-path-1.0.3.tgz#114c949673e2a8a35e9d35788527aa37b679da2b" + integrity sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A== + moment@^2.19.3: version "2.30.1" resolved "https://registry.yarnpkg.com/moment/-/moment-2.30.1.tgz#f8c91c07b7a786e30c59926df530b4eac96974ae" @@ -9858,6 +10835,13 @@ node-abi@^3.3.0: dependencies: semver "^7.3.5" +node-abi@^3.61.0: + version "3.67.0" + resolved "https://registry.yarnpkg.com/node-abi/-/node-abi-3.67.0.tgz#1d159907f18d18e18809dbbb5df47ed2426a08df" + integrity sha512-bLn/fU/ALVBE9wj+p4Y21ZJWYFjUXLXPi/IewyLZkx3ApxKDNBWCKdReeKOtD8dWpOdDCeMyLh6ZewzcLsG2Nw== + dependencies: + semver "^7.3.5" + node-abort-controller@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/node-abort-controller/-/node-abort-controller-3.1.1.tgz#a94377e964a9a37ac3976d848cb5c765833b8548" @@ -9880,7 +10864,7 @@ node-domexception@^1.0.0: resolved "https://registry.yarnpkg.com/node-domexception/-/node-domexception-1.0.0.tgz#6888db46a1f71c0b76b3f7555016b63fe64766e5" integrity sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ== -node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.11, node-fetch@^2.6.12: +node-fetch@^2.2.0, node-fetch@^2.6.0, node-fetch@^2.6.11, node-fetch@^2.6.12, node-fetch@^2.6.7: version "2.7.0" resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.7.0.tgz#d0f0fa6e3e2dc1d27efcd8ad99d550bda94d187d" integrity sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A== @@ -9923,6 +10907,11 @@ node-releases@^2.0.14: resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.14.tgz#2ffb053bceb8b2be8495ece1ab6ce600c4461b0b" integrity sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw== +node-releases@^2.0.18: + version "2.0.18" + resolved "https://registry.yarnpkg.com/node-releases/-/node-releases-2.0.18.tgz#f010e8d35e2fe8d6b2944f03f70213ecedc4ca3f" + integrity sha512-d9VeXT4SJ7ZeOqGX6R5EM022wpL+eWPooLI+5UpWn2jCT1aosUQEhQP214x33Wkwx3JQMvIm+tIoVOdodFS40g== + node-stream-zip@^1.9.1: version "1.15.0" resolved "https://registry.yarnpkg.com/node-stream-zip/-/node-stream-zip-1.15.0.tgz#158adb88ed8004c6c49a396b50a6a5de3bca33ea" @@ -10023,7 +11012,7 @@ ob1@0.80.9: resolved "https://registry.yarnpkg.com/ob1/-/ob1-0.80.9.tgz#4ae3edd807536097674ff943509089f5d4e0649f" integrity sha512-v9yOxowkZbxWhKOaaTyLjIm1aLy4ebMNcSn4NYJKOAI/Qv+SkfEfszpLr2GIxsccmb2Y2HA9qtsqiIJ80ucpVA== -object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: +object-assign@^4, object-assign@^4.0.1, object-assign@^4.1.0, object-assign@^4.1.1: version "4.1.1" resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863" integrity sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg== @@ -10103,6 +11092,11 @@ oblivious-set@1.4.0: resolved "https://registry.yarnpkg.com/oblivious-set/-/oblivious-set-1.4.0.tgz#1ee7c90f0605bb2a182fbcc8fffbe324d9994b43" integrity sha512-szyd0ou0T8nsAqHtprRcP3WidfsN1TnAR5yWXf2mFCEr5ek3LEOkT6EZ/92Xfs74HIdyhG5WkGxIssMU0jBaeg== +obuf@~1.1.2: + version "1.1.2" + resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" + integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== + on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" @@ -10487,7 +11481,7 @@ path-posix@^1.0.0: resolved "https://registry.yarnpkg.com/path-posix/-/path-posix-1.0.0.tgz#06b26113f56beab042545a23bfa88003ccac260f" integrity sha512-1gJ0WpNIiYcQydgg3Ed8KzvIqTsDpNwq+cjBCssvBtuTWjEqY1AW+i+OepiEMqDCzyro9B2sLAe4RBPajMYFiA== -path-scurry@^1.11.1: +path-scurry@^1.11.1, path-scurry@^1.6.1: version "1.11.1" resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== @@ -10495,6 +11489,11 @@ path-scurry@^1.11.1: lru-cache "^10.2.0" minipass "^5.0.0 || ^6.0.2 || ^7.0.0" +path-to-regexp@0.1.7: + version "0.1.7" + resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" + integrity sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ== + path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -10545,6 +11544,45 @@ performance-now@^2.1.0: resolved "https://registry.yarnpkg.com/performance-now/-/performance-now-2.1.0.tgz#6309f4e0e5fa913ec1c69307ae364b4b377c9e7b" integrity sha512-7EAHlyLHI56VEIdK57uwHdHKIaAGbnXPiw0yWbarQZOKaKpvUIgW0jWRVLiatnM+XXlSwsanIBH/hzGMJulMow== +pg-int8@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" + integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== + +pg-numeric@1.0.2: + version "1.0.2" + resolved "https://registry.yarnpkg.com/pg-numeric/-/pg-numeric-1.0.2.tgz#816d9a44026086ae8ae74839acd6a09b0636aa3a" + integrity sha512-BM/Thnrw5jm2kKLE5uJkXqqExRUY/toLHda65XgFTBTFYZyopbKjBe29Ii3RbkvlsMoFwD+tHeGaCjjv0gHlyw== + +pg-protocol@*: + version "1.6.1" + resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.6.1.tgz#21333e6d83b01faaebfe7a33a7ad6bfd9ed38cb3" + integrity sha512-jPIlvgoD63hrEuihvIg+tJhoGjUsLPn6poJY9N5CnlPd91c2T18T/9zBtLxZSb1EhYxBRoZJtzScCaWlYLtktg== + +pg-types@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-2.2.0.tgz#2d0250d636454f7cfa3b6ae0382fdfa8063254a3" + integrity sha512-qTAAlrEsl8s4OiEQY69wDvcMIdQN6wdz5ojQiOy6YRMuynxenON0O5oCpJI6lshc6scgAY8qvJ2On/p+CXY0GA== + dependencies: + pg-int8 "1.0.1" + postgres-array "~2.0.0" + postgres-bytea "~1.0.0" + postgres-date "~1.0.4" + postgres-interval "^1.1.0" + +pg-types@^4.0.1: + version "4.0.2" + resolved "https://registry.yarnpkg.com/pg-types/-/pg-types-4.0.2.tgz#399209a57c326f162461faa870145bb0f918b76d" + integrity sha512-cRL3JpS3lKMGsKaWndugWQoLOCoP+Cic8oseVcbr0qhPzYD5DWXK+RZ9LY9wxRf7RQia4SCwQlXk0q6FCPrVng== + dependencies: + pg-int8 "1.0.1" + pg-numeric "1.0.2" + postgres-array "~3.0.1" + postgres-bytea "~3.0.0" + postgres-date "~2.1.0" + postgres-interval "^3.0.0" + postgres-range "^1.1.1" + phin@^3.7.0: version "3.7.1" resolved "https://registry.yarnpkg.com/phin/-/phin-3.7.1.tgz#bf841da75ee91286691b10e41522a662aa628fd6" @@ -10759,6 +11797,55 @@ postcss@^8.0.0, postcss@^8.4.28, postcss@^8.4.32, postcss@^8.4.35, postcss@^8.4. picocolors "^1.0.0" source-map-js "^1.2.0" +postgres-array@~2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-2.0.0.tgz#48f8fce054fbc69671999329b8834b772652d82e" + integrity sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA== + +postgres-array@~3.0.1: + version "3.0.2" + resolved "https://registry.yarnpkg.com/postgres-array/-/postgres-array-3.0.2.tgz#68d6182cb0f7f152a7e60dc6a6889ed74b0a5f98" + integrity sha512-6faShkdFugNQCLwucjPcY5ARoW1SlbnrZjmGl0IrrqewpvxvhSLHimCVzqeuULCbG0fQv7Dtk1yDbG3xv7Veog== + +postgres-bytea@~1.0.0: + version "1.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-1.0.0.tgz#027b533c0aa890e26d172d47cf9ccecc521acd35" + integrity sha512-xy3pmLuQqRBZBXDULy7KbaitYqLcmxigw14Q5sj8QBVLqEwXfeybIKVWiqAXTlcvdvb0+xkOtDbfQMOf4lST1w== + +postgres-bytea@~3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-bytea/-/postgres-bytea-3.0.0.tgz#9048dc461ac7ba70a6a42d109221619ecd1cb089" + integrity sha512-CNd4jim9RFPkObHSjVHlVrxoVQXz7quwNFpz7RY1okNNme49+sVyiTvTRobiLV548Hx/hb1BG+iE7h9493WzFw== + dependencies: + obuf "~1.1.2" + +postgres-date@~1.0.4: + version "1.0.7" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-1.0.7.tgz#51bc086006005e5061c591cee727f2531bf641a8" + integrity sha512-suDmjLVQg78nMK2UZ454hAG+OAW+HQPZ6n++TNDUX+L0+uUlLywnoxJKDou51Zm+zTCjrCl0Nq6J9C5hP9vK/Q== + +postgres-date@~2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/postgres-date/-/postgres-date-2.1.0.tgz#b85d3c1fb6fb3c6c8db1e9942a13a3bf625189d0" + integrity sha512-K7Juri8gtgXVcDfZttFKVmhglp7epKb1K4pgrkLxehjqkrgPhfG6OO8LHLkfaqkbpjNRnra018XwAr1yQFWGcA== + +postgres-interval@^1.1.0: + version "1.2.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-1.2.0.tgz#b460c82cb1587507788819a06aa0fffdb3544695" + integrity sha512-9ZhXKM/rw350N1ovuWHbGxnGh/SNJ4cnxHiM0rxE4VN41wsg8P8zWn9hv/buK00RP4WvlOyr/RBDiptyxVbkZQ== + dependencies: + xtend "^4.0.0" + +postgres-interval@^3.0.0: + version "3.0.0" + resolved "https://registry.yarnpkg.com/postgres-interval/-/postgres-interval-3.0.0.tgz#baf7a8b3ebab19b7f38f07566c7aab0962f0c86a" + integrity sha512-BSNDnbyZCXSxgA+1f5UU2GmwhoI0aU5yMxRGO8CdFEcY2BQF9xm/7MqKnYoM1nJDk8nONNWDk9WeSmePFhQdlw== + +postgres-range@^1.1.1: + version "1.1.4" + resolved "https://registry.yarnpkg.com/postgres-range/-/postgres-range-1.1.4.tgz#a59c5f9520909bcec5e63e8cf913a92e4c952863" + integrity sha512-i/hbxIE9803Alj/6ytL7UHQxRvZkI9O4Sy+J3HGc4F4oo/2eQAjTSNJ0bfxyse3bH0nuVesCk+3IRLaMtG3H6w== + preact@10.13.2: version "10.13.2" resolved "https://registry.yarnpkg.com/preact/-/preact-10.13.2.tgz#2c40c73d57248b57234c4ae6cd9ab9d8186ebc0a" @@ -10847,7 +11934,7 @@ process-nextick-args@^2.0.1, process-nextick-args@~2.0.0: resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2" integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag== -progress@2.0.3: +progress@2.0.3, progress@^2.0.3: version "2.0.3" resolved "https://registry.yarnpkg.com/progress/-/progress-2.0.3.tgz#7e8cf8d8f5b8f239c1bc68beb4eb78567d572ef8" integrity sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA== @@ -10913,6 +12000,14 @@ proto-list@~1.2.1: resolved "https://registry.yarnpkg.com/proto-list/-/proto-list-1.2.4.tgz#212d5bfe1318306a420f6402b8e26ff39647a849" integrity sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA== +proxy-addr@~2.0.7: + version "2.0.7" + resolved "https://registry.yarnpkg.com/proxy-addr/-/proxy-addr-2.0.7.tgz#f19fe69ceab311eeb94b42e70e8c2070f9ba1025" + integrity sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg== + dependencies: + forwarded "0.2.0" + ipaddr.js "1.9.1" + proxy-agent@6.3.1: version "6.3.1" resolved "https://registry.yarnpkg.com/proxy-agent/-/proxy-agent-6.3.1.tgz#40e7b230552cf44fd23ffaf7c59024b692612687" @@ -11002,6 +12097,20 @@ qj@~2.0.0: resolved "https://registry.yarnpkg.com/qj/-/qj-2.0.0.tgz#054dd3b75ce018728d23a0605f030dfda0bd158a" integrity sha512-8466vlnAF/piI42tzMBUfhaAWn2yBNPOLSSbA2YBlEh+S8CxBXbAO1AwuDReGKYX6LlsK19wBL9cpXZGlgsXxA== +qs@6.11.0: + version "6.11.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.11.0.tgz#fd0d963446f7a65e1367e01abd85429453f0c37a" + integrity sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q== + dependencies: + side-channel "^1.0.4" + +qs@^6.11.0: + version "6.13.0" + resolved "https://registry.yarnpkg.com/qs/-/qs-6.13.0.tgz#6ca3bd58439f7e245655798997787b0d88a51906" + integrity sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg== + dependencies: + side-channel "^1.0.6" + qs@^6.9.4: version "6.12.1" resolved "https://registry.yarnpkg.com/qs/-/qs-6.12.1.tgz#39422111ca7cbdb70425541cba20c7d7b216599a" @@ -11084,6 +12193,16 @@ range-parser@~1.2.1: resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031" integrity sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg== +raw-body@2.5.2: + version "2.5.2" + resolved "https://registry.yarnpkg.com/raw-body/-/raw-body-2.5.2.tgz#99febd83b90e08975087e8f1f9419a149366b68a" + integrity sha512-8zGqypfENjCIqGhgXToC8aB2r7YrBX+AQAfIPs/Mlk+BtPTztOvTS01NRW/3Eh60J+a48lt8qsCzirQ6loCVfA== + dependencies: + bytes "3.1.2" + http-errors "2.0.0" + iconv-lite "0.4.24" + unpipe "1.0.0" + rc@^1.2.7: version "1.2.8" resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed" @@ -11509,6 +12628,15 @@ require-from-string@^2.0.2: resolved "https://registry.yarnpkg.com/require-from-string/-/require-from-string-2.0.2.tgz#89a7fdd938261267318eafe14f9c32e598c36909" integrity sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw== +require-in-the-middle@^7.1.1: + version "7.4.0" + resolved "https://registry.yarnpkg.com/require-in-the-middle/-/require-in-the-middle-7.4.0.tgz#606977820d4b5f9be75e5a108ce34cfed25b3bb4" + integrity sha512-X34iHADNbNDfr6OTStIAHWSAvvKQRYgLO6duASaVf7J2VA3lvmNYboAHOuLC2huav1IwgZJtyEcJCKVzFxOSMQ== + dependencies: + debug "^4.3.5" + module-details-from-path "^1.0.3" + resolve "^1.22.8" + require-main-filename@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" @@ -11558,7 +12686,7 @@ resolve-options@^2.0.0: dependencies: value-or-function "^4.0.0" -resolve@^1.1.7, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.22.1, resolve@^1.22.4: +resolve@^1.1.7, resolve@^1.10.0, resolve@^1.14.2, resolve@^1.19.0, resolve@^1.22.1, resolve@^1.22.4, resolve@^1.22.8: version "1.22.8" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.22.8.tgz#b6c87a9f2aa06dfab52e3d70ac8cde321fa5a48d" integrity sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw== @@ -11756,7 +12884,7 @@ safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g== -safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: +safe-buffer@5.2.1, safe-buffer@^5.0.1, safe-buffer@^5.1.0, safe-buffer@^5.1.2, safe-buffer@~5.2.0: version "5.2.1" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.2.1.tgz#1eaf9fa9bdb1fdd4ec75f58f9cdb4e6b7827eec6" integrity sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ== @@ -11908,7 +13036,7 @@ serialize-javascript@^6.0.1: dependencies: randombytes "^2.1.0" -serve-static@^1.13.1: +serve-static@1.15.0, serve-static@^1.13.1: version "1.15.0" resolved "https://registry.yarnpkg.com/serve-static/-/serve-static-1.15.0.tgz#faaef08cffe0a1a62f60cad0c4e513cff0ac9540" integrity sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g== @@ -12038,6 +13166,11 @@ shell-quote@^1.6.1, shell-quote@^1.7.2, shell-quote@^1.7.3: resolved "https://registry.yarnpkg.com/shell-quote/-/shell-quote-1.8.1.tgz#6dbf4db75515ad5bac63b4f1894c3a154c766680" integrity sha512-6j1W9l1iAs/4xYBI1SYOVZyFcCis9b4KCLQ8fgAGG07QvzaRLVVRQvAy85yNmmZSjYjg4MWh4gNvlPujU/5LpA== +shimmer@^1.2.1: + version "1.2.1" + resolved "https://registry.yarnpkg.com/shimmer/-/shimmer-1.2.1.tgz#610859f7de327b587efebf501fb43117f9aff337" + integrity sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw== + side-channel@^1.0.4, side-channel@^1.0.6: version "1.0.6" resolved "https://registry.yarnpkg.com/side-channel/-/side-channel-1.0.6.tgz#abd25fb7cd24baf45466406b1096b7831c9215f2" @@ -12591,6 +13724,14 @@ strip-outer@^1.0.1: dependencies: escape-string-regexp "^1.0.2" +stripe@^16.8.0: + version "16.8.0" + resolved "https://registry.yarnpkg.com/stripe/-/stripe-16.8.0.tgz#1433e21c5f7505d270f5bc5228d08a6b63a43546" + integrity sha512-6rOIcGOkxcc29jvhEyOYmpPFilekOBV+7vpemAoIAfbtCRW1yxzdDGM0/0vyekHglLL+wqGpP5ldrhO3dJ2JEQ== + dependencies: + "@types/node" ">=8.1.0" + qs "^6.11.0" + strnum@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/strnum/-/strnum-1.0.5.tgz#5c4e829fe15ad4ff0d20c3db5ac97b73c9b072db" @@ -13229,6 +14370,14 @@ type-fest@^1.0.1, type-fest@^1.2.1, type-fest@^1.2.2: resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-1.4.0.tgz#e9fb813fe3bf1744ec359d55d1affefa76f14be1" integrity sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA== +type-is@~1.6.18: + version "1.6.18" + resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" + integrity sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g== + dependencies: + media-typer "0.3.0" + mime-types "~2.1.24" + typed-array-buffer@^1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/typed-array-buffer/-/typed-array-buffer-1.0.2.tgz#1867c5d83b20fcb5ccf32649e5e2fc7424474ff3" @@ -13322,6 +14471,11 @@ typescript@^5.1.3: resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.2.tgz#c26f023cb0054e657ce04f72583ea2d85f8d0507" integrity sha512-NcRtPEOsPFFWjobJEtfihkLCZCXZt/os3zf8nTxjVH3RvTSxjrCamJpbExGvYOF+tFHc3pA65qpdwPbzjohhew== +typescript@^5.5.3: + version "5.5.3" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.3.tgz#e1b0a3c394190838a0b168e771b0ad56a0af0faa" + integrity sha512-/hreyEujaB0w76zKo6717l3L0o/qEUtRgdvUBvlkhoWeOVMjMuHNHk0BRBzikzuGDqNmPQbg5ifMEqsHLiIUcQ== + ua-parser-js@^1.0.37: version "1.0.38" resolved "https://registry.yarnpkg.com/ua-parser-js/-/ua-parser-js-1.0.38.tgz#66bb0c4c0e322fe48edfe6d446df6042e62f25e2" @@ -13372,6 +14526,11 @@ undici-types@~5.26.4: resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-5.26.5.tgz#bcd539893d00b56e964fd2657a4866b221a65617" integrity sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA== +undici-types@~6.19.2: + version "6.19.8" + resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02" + integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw== + unicode-canonical-property-names-ecmascript@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-2.0.0.tgz#301acdc525631670d39f6146e0e77ff6bbdebddc" @@ -13430,11 +14589,21 @@ unload@2.4.1: resolved "https://registry.yarnpkg.com/unload/-/unload-2.4.1.tgz#b0c5b7fb44e17fcbf50dcb8fb53929c59dd226a5" integrity sha512-IViSAm8Z3sRBYA+9wc0fLQmU9Nrxb16rcDmIiR6Y9LJSZzI7QY5QsDhqPpKOjAn0O9/kfK1TfNEMMAGPTIraPw== -unpipe@~1.0.0: +unpipe@1.0.0, unpipe@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/unpipe/-/unpipe-1.0.0.tgz#b2bf4ee8514aae6165b4817829d21b2ef49904ec" integrity sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ== +unplugin@1.0.1: + version "1.0.1" + resolved "https://registry.yarnpkg.com/unplugin/-/unplugin-1.0.1.tgz#83b528b981cdcea1cad422a12cd02e695195ef3f" + integrity sha512-aqrHaVBWW1JVKBHmGo33T5TxeL0qWzfvjWokObHA9bYmN7eNDkwOxmLjhioHl9878qDFMAaT51XNroRyuz7WxA== + dependencies: + acorn "^8.8.1" + chokidar "^3.5.3" + webpack-sources "^3.2.3" + webpack-virtual-modules "^0.5.0" + upath@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894" @@ -13448,6 +14617,14 @@ update-browserslist-db@^1.0.16: escalade "^3.1.2" picocolors "^1.0.1" +update-browserslist-db@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/update-browserslist-db/-/update-browserslist-db-1.1.0.tgz#7ca61c0d8650766090728046e416a8cde682859e" + integrity sha512-EdRAaAyk2cUE1wOf2DkEhzxqOQvFOoRJFNS6NeyJ01Gp2beMRpBAINjM2iDXE3KCuKhwnvHIQCJm6ThL2Z+HzQ== + dependencies: + escalade "^3.1.2" + picocolors "^1.0.1" + uri-js@^4.2.2, uri-js@^4.4.1: version "4.4.1" resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e" @@ -13540,7 +14717,7 @@ value-or-function@^4.0.0: resolved "https://registry.yarnpkg.com/value-or-function/-/value-or-function-4.0.0.tgz#70836b6a876a010dc3a2b884e7902e9db064378d" integrity sha512-aeVK81SIuT6aMJfNo9Vte8Dw0/FZINGBV8BfCraGtqVxIeLAEhJyoWs8SmvRVmXfGss2PmmOwZCuBPbZR+IYWg== -vary@~1.1.2: +vary@^1, vary@~1.1.2: version "1.1.2" resolved "https://registry.yarnpkg.com/vary/-/vary-1.1.2.tgz#2299f02c6ded30d4a5961b0b9f74524a18f634fc" integrity sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg== @@ -13642,6 +14819,15 @@ vite-plugin-html@^3.2.2: node-html-parser "^5.3.3" pathe "^0.2.0" +vite-plugin-node@^3.1.0: + version "3.1.0" + resolved "https://registry.yarnpkg.com/vite-plugin-node/-/vite-plugin-node-3.1.0.tgz#e8f695f1634475f2e816ff8a734e6f844e6fd250" + integrity sha512-LN9byKedZaokEWcH+DTdeV7YLN34dWH7Em2ldHZ77oVGvC/uxHnK43AGPrV9+9CoOh1x9X3Yc55vW/OQYssFDQ== + dependencies: + "@rollup/pluginutils" "^4.1.1" + chalk "^4.1.2" + debug "^4.3.2" + vite-plugin-pwa@^0.19.8: version "0.19.8" resolved "https://registry.yarnpkg.com/vite-plugin-pwa/-/vite-plugin-pwa-0.19.8.tgz#f7be200a4426207358aef807b4a6e1ecbc14d345" @@ -13805,6 +14991,16 @@ webidl-conversions@^7.0.0: resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-7.0.0.tgz#256b4e1882be7debbf01d05f0aa2039778ea080a" integrity sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g== +webpack-sources@^3.2.3: + version "3.2.3" + resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" + integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== + +webpack-virtual-modules@^0.5.0: + version "0.5.0" + resolved "https://registry.yarnpkg.com/webpack-virtual-modules/-/webpack-virtual-modules-0.5.0.tgz#362f14738a56dae107937ab98ea7062e8bdd3b6c" + integrity sha512-kyDivFZ7ZM0BVOUteVbDFhlRt7Ah/CSPwJdi8hBpkK7QLumUqdLtVfm/PX/hkcnrvr0i77fO5+TjZ94Pe+C9iw== + websocket-stream@^5.5.2: version "5.5.2" resolved "https://registry.yarnpkg.com/websocket-stream/-/websocket-stream-5.5.2.tgz#49d87083d96839f0648f5513bbddd581f496b8a2"