diff --git a/.env.example b/.env.example index ca4be82d1..59e64cb14 100644 --- a/.env.example +++ b/.env.example @@ -48,6 +48,9 @@ SIGNUP_DISABLED=false SIGNUP_ALLOWED_DOMAINS= SIGNUP_ALLOWED_EMAILS= +# Sign-up Email Confirmation +SIGNUP_EMAIL_CONFIRMATION=false + # API rate limit (points,duration,block duration). API_RATE_LIMIT=120,60,600 diff --git a/.github/workflows/build-deploy-container.yml b/.github/workflows/build-deploy-container.yml index d4c423b4f..8619f0ecf 100644 --- a/.github/workflows/build-deploy-container.yml +++ b/.github/workflows/build-deploy-container.yml @@ -14,10 +14,6 @@ jobs: build-publish-webapp: strategy: fail-fast: false - matrix: - platform: - - linux/amd64 - - linux/arm64 name: Build and deploy webapp container runs-on: ubuntu-latest environment: production @@ -30,9 +26,6 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -40,15 +33,14 @@ jobs: - name: Log in to the Container registry uses: docker/login-action@v3 with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GH_TOKEN }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - name: Extract metadata (tags, labels) for Docker id: meta uses: docker/metadata-action@98669ae865ea3cffbcbaa878cf57c20bbf1c6c38 with: - images: ${{ env.REGISTRY }}/${{ env.WEBAPP_IMAGE_NAME }} + images: ${{ env.WEBAPP_IMAGE_NAME }} # Builds and push the Docker image. - name: Build and push Docker image @@ -57,10 +49,11 @@ jobs: with: context: ./ file: ./packages/webapp/Dockerfile - platforms: ${{ matrix.platform }} + platforms: linux/amd64,linux/arm64 push: true tags: ghcr.io/poudelprakash/webapp:latest, ghcr.io/poudelprakash/webapp:${{github.ref_name}} labels: ${{ steps.meta.outputs.labels }} + tags: bigcapitalhq/webapp:latest, bigcapitalhq/webapp:${{github.ref_name}} - name: Export digest run: | @@ -71,7 +64,7 @@ jobs: - name: Upload digest uses: actions/upload-artifact@v4 with: - name: digests-main-${{ env.PLATFORM_PAIR }} + name: digests-webapp path: /tmp/digests/* if-no-files-found: error retention-days: 1 @@ -93,9 +86,6 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - - name: Set up Docker Buildx uses: docker/setup-buildx-action@v3 @@ -103,9 +93,8 @@ jobs: - name: Log in to the Container registry uses: docker/login-action@v3 with: - registry: ${{ env.REGISTRY }} - username: ${{ github.actor }} - password: ${{ secrets.GH_TOKEN }} + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} # Builds and push the Docker image. - name: Build and push Docker image @@ -114,7 +103,7 @@ jobs: with: context: ./ file: ./packages/server/Dockerfile - platforms: ${{ matrix.platform }} + platforms: linux/amd64,linux/arm64 push: true tags: ghcr.io/poudelprakash/server:latest, ghcr.io/poudelprakash/server:${{github.ref_name}} labels: ${{ steps.meta.outputs.labels }} @@ -128,11 +117,11 @@ jobs: - name: Upload digest uses: actions/upload-artifact@v4 with: - name: digests-main-${{ env.PLATFORM_PAIR }} + name: digests-server path: /tmp/digests/* if-no-files-found: error retention-days: 1 - + # Send notification to Slack channel. # - name: Slack Notification built and published server container successfully. # uses: rtCamp/action-slack-notify@v2 diff --git a/CHANGELOG.md b/CHANGELOG.md index ae75e5f45..eaaa61f99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,76 @@ All notable changes to Bigcapital server-side will be in this file. +## [0.16.11] - 06-05-2024 + +### improvements + +* feat: Export resource data to csv, xlsx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/430 +* feat: User email verification after signing-up. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/426 + +### Fixes +* feat(repo): upgrade to latest lerna v8 and pnpm v9 by @benpsnyder in https://github.com/bigcapitalhq/bigcapital/pull/414 +* feat: Update Docker Build-Push Action and Add ARM64 Support by @cloudsbird in https://github.com/bigcapitalhq/bigcapital/pull/412 +* feat: Pushing docker containers by version tag by @cloudsbird in https://github.com/bigcapitalhq/bigcapital/pull/421 + +## [0.16.10] + +* fix: Running migration Docker container on Windows by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/432 + +## [0.16.9] + +* feat: New Relic for tracking by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/429 + +## [0.16.8] + +* feat: Ability to enable/disable the bank connect feature by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/423 + +## [0.16.6] + +* hotfix: fix the subscription plan when subscribe on cloud by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/422 + +## [0.16.5] + +IMPORTANT: If you upgraded to the v0.16 recently you should upgrade to v0.16.4 as soon as possible, because there're some breaking changes affected the sign-in and some users reported couldn't sign-in. + +* feat: Seed free subscription to tenants that have no subscription. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/410 + +## [0.16.3] + +* feat: Integrate Lemon Squeezy payment by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/402 +* feat: optimize the onboarding subscription experience. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/404 +* feat: subscription page content by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/405 +* feat: auto subscribe to free plan once signup on community version. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/406 +* chore: add default value to env variable by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/407 +* fix: absolute storage imports path. by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/408 + +## [0.16.0] + +* feat: add convert to invoice button on estimate drawer toolbar by @ANasouf in https://github.com/bigcapitalhq/bigcapital/pull/361 +* feat(webapp): add mark as delivered to action bar of invoice details … by @ANasouf in https://github.com/bigcapitalhq/bigcapital/pull/360 +* feat(webapp): Dialog to choose the bank service provider by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/378 +* feat: Categorize the bank synced transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/377 +* feat: uncategorize the cashflow transaction by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/381 +* Import resources from csv/xlsx by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/382 +* feat(webapp): import resource UI by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/386 +* fix: import resources improvements by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/388 +* feat: add sample sheet to accounts and bank transactions by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/389 +* fix: show the unique row value in the import preview by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/392 +* feat: advanced parser for numeric and boolean import values by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/394 +* feat: validate the given imported sheet whether is empty by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/395 +* feat: linking relation with id in importing by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/393 +* feat: Aggregate rows import by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/396 +* feat: clean up the imported temp files by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/400 +* feat: add hints to import fields by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/401 + +## [0.15.0] + +* feat: Printing financial reports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/363 +* feat: Convert invoice status after sending mail notification by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/332 +* feat: Bigcapital <> Plaid Integration by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/346 +* fix: Broken transactions by vendor report by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/369 +* fix: Optimize the print style some financial reports by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/370 + ## [0.14.0] - 30-01-2024 * feat: purchases by items exporting by @abouolia in https://github.com/bigcapitalhq/bigcapital/pull/327 diff --git a/README.md b/README.md index 96ff272c3..a50ecdfe2 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@

- + Bigcapital

@@ -27,7 +27,7 @@

- Bigcapital Cloud + Bigcapital Cloud

@@ -51,7 +51,7 @@ Bigcapital is available open-source under AGPL license. You can host it on your ### Docker -To get started with self-hosted with Docker and Docker Compose, take a look at the [Docker guide](https://docs.bigcapital.ly/deployment/docker). +To get started with self-hosted with Docker and Docker Compose, take a look at the [Docker guide](https://docs.bigcapital.app/deployment/docker). ## Development @@ -74,7 +74,7 @@ You can integrate Bigcapital API with your system to organize your transactions # Resources -- [Documentation](https://docs.bigcapital.ly/) - Learn how to use. +- [Documentation](https://docs.bigcapital.app/) - Learn how to use. - [Contribution](https://github.com/bigcapitalhq/bigcapital/blob/develop/CONTRIBUTING.md) - Welcome to any contributions. - [Discord](https://discord.com/invite/c8nPBJafeb) - Ask for help. - [Bug Tracker](https://github.com/bigcapitalhq/bigcapital/issues) - Notify us new bugs. diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index d842d1d86..bdbc136bf 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -25,12 +25,12 @@ services: webapp: container_name: bigcapital-webapp - image: ghcr.io/bigcapitalhq/webapp:latest + image: bigcapitalhq/webapp:latest restart: on-failure server: container_name: bigcapital-server - image: ghcr.io/bigcapitalhq/server:latest + image: bigcapitalhq/server:latest expose: - '3000' links: @@ -102,6 +102,13 @@ services: - LEMONSQUEEZY_WEBHOOK_SECRET=${LEMONSQUEEZY_WEBHOOK_SECRET} - HOSTED_ON_BIGCAPITAL_CLOUD=${HOSTED_ON_BIGCAPITAL_CLOUD} + # New Relic matrics tracking. + - NEW_RELIC_DISTRIBUTED_TRACING_ENABLED=${NEW_RELIC_DISTRIBUTED_TRACING_ENABLED} + - NEW_RELIC_LOG=${NEW_RELIC_LOG} + - NEW_RELIC_AI_MONITORING_ENABLED=${NEW_RELIC_AI_MONITORING_ENABLED} + - NEW_RELIC_CUSTOM_INSIGHTS_EVENTS_MAX_SAMPLES_STORED=${NEW_RELIC_CUSTOM_INSIGHTS_EVENTS_MAX_SAMPLES_STORED} + - NEW_RELIC_SPAN_EVENTS_MAX_SAMPLES_STORED=${NEW_RELIC_SPAN_EVENTS_MAX_SAMPLES_STORED} + # OIDC - OIDC_LOGIN_DISABLED=${OIDC_LOGIN_DISABLED:-false} - OIDC_ISSUER=${OIDC_ISSUER} diff --git a/docker/migration/Dockerfile b/docker/migration/Dockerfile index 162d5039c..d61ef0679 100644 --- a/docker/migration/Dockerfile +++ b/docker/migration/Dockerfile @@ -1,4 +1,4 @@ -FROM ghcr.io/bigcapitalhq/server:latest as build +FROM bigcapitalhq/server:latest as build ARG DB_HOST= \ DB_USER= \ @@ -34,7 +34,5 @@ WORKDIR /app/packages/server RUN git clone https://github.com/vishnubob/wait-for-it.git -ADD docker/migration/start.sh / -RUN chmod +x /start.sh - -CMD ["/start.sh"] \ No newline at end of file +# Once we listen the mysql port run the migration task. +CMD ./wait-for-it/wait-for-it.sh mysql:3306 -- sh -c "node ./build/commands.js system:migrate:latest && node ./build/commands.js tenants:migrate:latest" diff --git a/docker/migration/start.sh b/docker/migration/start.sh deleted file mode 100644 index 5fa9f0c28..000000000 --- a/docker/migration/start.sh +++ /dev/null @@ -1,5 +0,0 @@ -# Migrate the master system database. -./wait-for-it/wait-for-it.sh mysql:3306 -- node ./build/commands.js system:migrate:latest - -# Migrate all tenants. -./wait-for-it/wait-for-it.sh mysql:3306 -- node ./build/commands.js tenants:migrate:latest \ No newline at end of file diff --git a/packages/server/Dockerfile b/packages/server/Dockerfile index 095508e2d..be4426405 100644 --- a/packages/server/Dockerfile +++ b/packages/server/Dockerfile @@ -78,6 +78,9 @@ ENV MAIL_HOST=$MAIL_HOST \ SIGNUP_ALLOWED_DOMAINS=$SIGNUP_ALLOWED_DOMAINS \ SIGNUP_ALLOWED_EMAILS=$SIGNUP_ALLOWED_EMAILS +# New Relic config file. +ENV NEW_RELIC_NO_CONFIG_FILE=true + # Create app directory. WORKDIR /app @@ -89,8 +92,8 @@ RUN npm install -g pnpm # Copy application dependency manifests to the container image. COPY ./package*.json ./ COPY ./pnpm-lock.yaml ./pnpm-lock.yaml -COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml COPY ./lerna.json ./lerna.json +COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml COPY ./packages/server/package*.json ./packages/server/ # Install application dependencies diff --git a/packages/server/package.json b/packages/server/package.json index 1e3660471..2ba20d9d0 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -83,6 +83,7 @@ "mustache": "^3.0.3", "mysql": "^2.17.1", "mysql2": "^1.6.5", + "newrelic": "^11.15.0", "node-cache": "^4.2.1", "nodemailer": "^6.3.0", "nodemon": "^1.19.1", diff --git a/packages/server/resources/locales/en.json b/packages/server/resources/locales/en.json index 13fc8a174..ba4c158b8 100644 --- a/packages/server/resources/locales/en.json +++ b/packages/server/resources/locales/en.json @@ -244,6 +244,7 @@ "account.field.active": "Active", "account.field.currency": "Currency", "account.field.balance": "Balance", + "account.field.bank_balance": "Bank Balance", "account.field.parent_account": "Parent Account", "account.field.created_at": "Created at", "item.field.type": "Item Type", @@ -331,7 +332,7 @@ "bill_payment.field.reference_no": "Reference No.", "bill_payment.field.description": "Description", "bill_payment.field.exchange_rate": "Exchange Rate", - "bill_payment.field.statement": "Statement", + "bill_payment.field.note": "Note", "bill_payment.field.entries.bill": "Bill No.", "bill_payment.field.entries.payment_amount": "Payment Amount", "bill_payment.field.reference": "Reference No.", @@ -431,6 +432,7 @@ "vendor.field.created_at": "Created at", "vendor.field.balance": "Balance", "vendor.field.status": "Status", + "vendor.field.note": "Note", "vendor.field.currency": "Currency", "vendor.field.status.active": "Active", "vendor.field.status.inactive": "Inactive", diff --git a/packages/server/src/api/controllers/Authentication.ts b/packages/server/src/api/controllers/Authentication.ts index c26da733e..354d37475 100644 --- a/packages/server/src/api/controllers/Authentication.ts +++ b/packages/server/src/api/controllers/Authentication.ts @@ -11,6 +11,8 @@ import AuthenticationApplication from '@/services/Authentication/AuthApplication import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser'; import JWTAuth from '@/api/middleware/jwtAuth'; +import JWTAuth from '@/api/middleware/jwtAuth'; +import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser'; @Service() export default class AuthenticationController extends BaseController { @Inject() @@ -30,6 +32,20 @@ export default class AuthenticationController extends BaseController { asyncMiddleware(this.login.bind(this)), this.handlerErrors ); + router.use('/register/verify/resend', JWTAuth); + router.use('/register/verify/resend', AttachCurrentTenantUser); + router.post( + '/register/verify/resend', + asyncMiddleware(this.registerVerifyResendMail.bind(this)), + this.handlerErrors + ); + router.post( + '/register/verify', + this.signupVerifySchema, + this.validationResult, + asyncMiddleware(this.registerVerify.bind(this)), + this.handlerErrors + ); router.post( '/register', this.registerSchema, @@ -107,6 +123,17 @@ export default class AuthenticationController extends BaseController { ]; } + private get signupVerifySchema(): ValidationChain[] { + return [ + check('email') + .exists() + .isString() + .isEmail() + .isLength({ max: DATATYPES_LENGTH.STRING }), + check('token').exists().isString(), + ]; + } + /** * Reset password schema. * @returns {ValidationChain[]} @@ -174,6 +201,58 @@ export default class AuthenticationController extends BaseController { } } + /** + * Verifies the provider user's email after signin-up. + * @param {Request} req + * @param {Response}| res + * @param {Function} next + * @returns {Response|void} + */ + private async registerVerify(req: Request, res: Response, next: Function) { + const signUpVerifyDTO: { email: string; token: string } = + this.matchedBodyData(req); + + try { + const user = await this.authApplication.signUpConfirm( + signUpVerifyDTO.email, + signUpVerifyDTO.token + ); + return res.status(200).send({ + type: 'success', + message: 'The given user has verified successfully', + user, + }); + } catch (error) { + next(error); + } + } + + /** + * Resends the confirmation email to the user. + * @param {Request} req + * @param {Response}| res + * @param {Function} next + */ + private async registerVerifyResendMail( + req: Request, + res: Response, + next: Function + ) { + const { user } = req; + + try { + const data = await this.authApplication.signUpConfirmResend(user.id); + + return res.status(200).send({ + type: 'success', + message: 'The given user has verified successfully', + data, + }); + } catch (error) { + next(error); + } + } + /** * Send reset password handler * @param {Request} req diff --git a/packages/server/src/api/controllers/Export/ExportController.ts b/packages/server/src/api/controllers/Export/ExportController.ts new file mode 100644 index 000000000..632c84932 --- /dev/null +++ b/packages/server/src/api/controllers/Export/ExportController.ts @@ -0,0 +1,99 @@ +import { Inject, Service } from 'typedi'; +import { Router, Request, Response, NextFunction } from 'express'; +import { query } from 'express-validator'; +import BaseController from '@/api/controllers/BaseController'; +import { ServiceError } from '@/exceptions'; +import { ExportApplication } from '@/services/Export/ExportApplication'; +import { ACCEPT_TYPE } from '@/interfaces/Http'; + +@Service() +export class ExportController extends BaseController { + @Inject() + private exportResourceApp: ExportApplication; + + /** + * Router constructor method. + */ + router() { + const router = Router(); + + router.get( + '/', + [ + query('resource').exists(), + query('format').isIn(['csv', 'xlsx']).optional(), + ], + this.validationResult, + this.export.bind(this), + this.catchServiceErrors + ); + return router; + } + + /** + * Imports xlsx/csv to the given resource type. + * @param {Request} req - + * @param {Response} res - + * @param {NextFunction} next - + */ + private async export(req: Request, res: Response, next: NextFunction) { + const { tenantId } = req; + const query = this.matchedQueryData(req); + + try { + const accept = this.accepts(req); + + const acceptType = accept.types([ + ACCEPT_TYPE.APPLICATION_XLSX, + ACCEPT_TYPE.APPLICATION_CSV, + ACCEPT_TYPE.APPLICATION_PDF, + ]); + const data = await this.exportResourceApp.export( + tenantId, + query.resource, + acceptType === ACCEPT_TYPE.APPLICATION_XLSX ? 'xlsx' : 'csv' + ); + if (ACCEPT_TYPE.APPLICATION_CSV === acceptType) { + res.setHeader('Content-Disposition', 'attachment; filename=output.csv'); + res.setHeader('Content-Type', 'text/csv'); + + return res.send(data); + // Retrieves the xlsx format. + } else if (ACCEPT_TYPE.APPLICATION_XLSX === acceptType) { + res.setHeader( + 'Content-Disposition', + 'attachment; filename=output.xlsx' + ); + res.setHeader( + 'Content-Type', + 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' + ); + return res.send(data); + } + } catch (error) { + next(error); + } + } + + /** + * Transforms service errors to response. + * @param {Error} + * @param {Request} req + * @param {Response} res + * @param {ServiceError} error + */ + private catchServiceErrors( + error, + req: Request, + res: Response, + next: NextFunction + ) { + if (error instanceof ServiceError) { + return res.status(400).send({ + errors: [{ type: error.errorType }], + }); + } + + next(error); + } +} diff --git a/packages/server/src/api/index.ts b/packages/server/src/api/index.ts index e5943475b..a3af24354 100644 --- a/packages/server/src/api/index.ts +++ b/packages/server/src/api/index.ts @@ -2,66 +2,67 @@ import { Router } from 'express'; import { Container } from 'typedi'; // Middlewares -import JWTAuth from '@/api/middleware/jwtAuth'; import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser'; -import SubscriptionMiddleware from '@/api/middleware/SubscriptionMiddleware'; -import TenancyMiddleware from '@/api/middleware/TenancyMiddleware'; import EnsureTenantIsInitialized from '@/api/middleware/EnsureTenantIsInitialized'; -import SettingsMiddleware from '@/api/middleware/SettingsMiddleware'; -import I18nMiddleware from '@/api/middleware/I18nMiddleware'; -import I18nAuthenticatedMiddlware from '@/api/middleware/I18nAuthenticatedMiddlware'; import EnsureTenantIsSeeded from '@/api/middleware/EnsureTenantIsSeeded'; +import I18nAuthenticatedMiddlware from '@/api/middleware/I18nAuthenticatedMiddlware'; +import I18nMiddleware from '@/api/middleware/I18nMiddleware'; +import SettingsMiddleware from '@/api/middleware/SettingsMiddleware'; +import SubscriptionMiddleware from '@/api/middleware/SubscriptionMiddleware'; +import TenancyMiddleware from '@/api/middleware/TenancyMiddleware'; +import JWTAuth from '@/api/middleware/jwtAuth'; // Routes -import Authentication from '@/api/controllers/Authentication'; -import InviteUsers from '@/api/controllers/InviteUsers'; -import Organization from '@/api/controllers/Organization'; import Account from '@/api/controllers/Account'; -import Users from '@/api/controllers/Users'; -import Items from '@/api/controllers/Items'; -import ItemCategories from '@/api/controllers/ItemCategories'; -import Accounts from '@/api/controllers/Accounts'; import AccountTypes from '@/api/controllers/AccountTypes'; -import Views from '@/api/controllers/Views'; -import ManualJournals from '@/api/controllers/ManualJournals'; -import FinancialStatements from '@/api/controllers/FinancialStatements'; -import Expenses from '@/api/controllers/Expenses'; -import Settings from '@/api/controllers/Settings'; -import Currencies from '@/api/controllers/Currencies'; +import Accounts from '@/api/controllers/Accounts'; +import Authentication from '@/api/controllers/Authentication'; import Contacts from '@/api/controllers/Contacts/Contacts'; import Customers from '@/api/controllers/Contacts/Customers'; import Vendors from '@/api/controllers/Contacts/Vendors'; -import Sales from '@/api/controllers/Sales'; -import Purchases from '@/api/controllers/Purchases'; -import Resources from './controllers/Resources'; +import Currencies from '@/api/controllers/Currencies'; import ExchangeRates from '@/api/controllers/ExchangeRates'; -import Media from '@/api/controllers/Media'; -import Ping from '@/api/controllers/Ping'; -import { SubscriptionController } from '@/api/controllers/Subscription'; +import Expenses from '@/api/controllers/Expenses'; +import FinancialStatements from '@/api/controllers/FinancialStatements'; import InventoryAdjustments from '@/api/controllers/Inventory/InventoryAdjustments'; -import asyncRenderMiddleware from './middleware/AsyncRenderMiddleware'; -import Jobs from './controllers/Jobs'; +import InviteUsers from '@/api/controllers/InviteUsers'; +import ItemCategories from '@/api/controllers/ItemCategories'; +import Items from '@/api/controllers/Items'; +import ManualJournals from '@/api/controllers/ManualJournals'; +import Media from '@/api/controllers/Media'; import Miscellaneous from '@/api/controllers/Miscellaneous'; +import OidcController from '@/api/controllers/Oidc'; +import Organization from '@/api/controllers/Organization'; import OrganizationDashboard from '@/api/controllers/OrganizationDashboard'; +import Ping from '@/api/controllers/Ping'; +import Purchases from '@/api/controllers/Purchases'; +import Sales from '@/api/controllers/Sales'; +import Settings from '@/api/controllers/Settings'; +import { SubscriptionController } from '@/api/controllers/Subscription'; +import Users from '@/api/controllers/Users'; +import Views from '@/api/controllers/Views'; +import { BranchIntegrationErrorsMiddleware } from '@/services/Branches/BranchIntegrationErrorsMiddleware'; +import { BankingController } from './controllers/Banking/BankingController'; +import { BranchesController } from './controllers/Branches'; import CashflowController from './controllers/Cashflow/CashflowController'; -import AuthorizationMiddleware from './middleware/AuthorizationMiddleware'; -import RolesController from './controllers/Roles'; -import TransactionsLocking from './controllers/TransactionsLocking'; import DashboardController from './controllers/Dashboard'; -import { BranchesController } from './controllers/Branches'; -import { WarehousesController } from './controllers/Warehouses'; -import { WarehousesTransfers } from './controllers/Warehouses/WarehouseTransfers'; -import { WarehousesItemController } from './controllers/Warehouses/WarehousesItem'; -import { BranchIntegrationErrorsMiddleware } from '@/services/Branches/BranchIntegrationErrorsMiddleware'; +import { ExportController } from './controllers/Export/ExportController'; +import { ImportController } from './controllers/Import/ImportController'; import { InventoryItemsCostController } from './controllers/Inventory/InventortyItemsCosts'; +import Jobs from './controllers/Jobs'; import { ProjectsController } from './controllers/Projects/Projects'; import { ProjectTasksController } from './controllers/Projects/Tasks'; import { ProjectTimesController } from './controllers/Projects/Times'; +import Resources from './controllers/Resources'; +import RolesController from './controllers/Roles'; import { TaxRatesController } from './controllers/TaxRates/TaxRates'; -import { ImportController } from './controllers/Import/ImportController'; -import { BankingController } from './controllers/Banking/BankingController'; +import TransactionsLocking from './controllers/TransactionsLocking'; +import { WarehousesController } from './controllers/Warehouses'; +import { WarehousesTransfers } from './controllers/Warehouses/WarehouseTransfers'; +import { WarehousesItemController } from './controllers/Warehouses/WarehousesItem'; import { Webhooks } from './controllers/Webhooks/Webhooks'; -import OidcController from '@/api/controllers/Oidc' +import asyncRenderMiddleware from './middleware/AsyncRenderMiddleware'; +import AuthorizationMiddleware from './middleware/AuthorizationMiddleware'; export default () => { const app = Router(); @@ -70,7 +71,7 @@ export default () => { // --------------------------- app.use(asyncRenderMiddleware); app.use(I18nMiddleware); - + app.use('/oidc', Container.get(OidcController).router()); app.use('/auth', Container.get(Authentication).router()); app.use('/invite', Container.get(InviteUsers).nonAuthRouter()); @@ -143,6 +144,7 @@ export default () => { dashboard.use('/projects', Container.get(ProjectsController).router()); dashboard.use('/tax-rates', Container.get(TaxRatesController).router()); dashboard.use('/import', Container.get(ImportController).router()); + dashboard.use('/export', Container.get(ExportController).router()); dashboard.use('/', Container.get(ProjectTasksController).router()); dashboard.use('/', Container.get(ProjectTimesController).router()); diff --git a/packages/server/src/config/index.ts b/packages/server/src/config/index.ts index 6a61b70d1..72307d080 100644 --- a/packages/server/src/config/index.ts +++ b/packages/server/src/config/index.ts @@ -153,6 +153,13 @@ module.exports = { ), }, + /** + * Sign-up email confirmation + */ + signupConfirmation: { + enabled: parseBoolean(process.env.SIGNUP_EMAIL_CONFIRMATION, false), + }, + /** * Puppeteer remote browserless connection. */ diff --git a/packages/server/src/interfaces/Authentication.ts b/packages/server/src/interfaces/Authentication.ts index 6a2bf625d..41c7aa577 100644 --- a/packages/server/src/interfaces/Authentication.ts +++ b/packages/server/src/interfaces/Authentication.ts @@ -1,6 +1,6 @@ -import { ISystemUser } from './User'; -import { ITenant } from './Tenancy'; import { SystemUser } from '@/system/models'; +import { ITenant } from './Tenancy'; +import { ISystemUser } from './User'; export interface IRegisterDTO { firstName: string; @@ -66,17 +66,28 @@ export interface IAuthResetedPasswordEventPayload { password: string; } - export interface IAuthSendingResetPassword { - user: ISystemUser, + user: ISystemUser; token: string; } export interface IAuthSendedResetPassword { - user: ISystemUser, + user: ISystemUser; token: string; } export interface IAuthGetMetaPOJO { signupDisabled: boolean; oidcLoginDisabled: boolean; -} \ No newline at end of file +} + +export interface IAuthSignUpVerifingEventPayload { + email: string; + verifyToken: string; + userId: number; +} + +export interface IAuthSignUpVerifiedEventPayload { + email: string; + verifyToken: string; + userId: number; +} diff --git a/packages/server/src/interfaces/Model.ts b/packages/server/src/interfaces/Model.ts index 87912bafe..93bb1f7fc 100644 --- a/packages/server/src/interfaces/Model.ts +++ b/packages/server/src/interfaces/Model.ts @@ -126,13 +126,16 @@ export interface IModelMeta { defaultFilterField: string; defaultSort: IModelMetaDefaultSort; - importable?: boolean; + exportable?: boolean; + exportFlattenOn?: string; + importable?: boolean; importAggregator?: string; importAggregateOn?: string; importAggregateBy?: string; fields: { [key: string]: IModelMetaField }; + columns: { [key: string]: IModelMetaColumn }; } // ---- @@ -161,3 +164,22 @@ export type IModelMetaField2 = IModelMetaFieldCommon2 & | IModelMetaRelationField2 | IModelMetaCollectionField ); + +export interface ImodelMetaColumnMeta { + name: string; + accessor?: string; + exportable?: boolean; +} + +interface IModelMetaColumnText { + type: 'text;'; +} + +interface IModelMetaColumnCollection { + type: 'collection'; + collectionOf: 'object'; + columns: { [key: string]: ImodelMetaColumnMeta & IModelMetaColumnText }; +} + +export type IModelMetaColumn = ImodelMetaColumnMeta & + (IModelMetaColumnText | IModelMetaColumnCollection); diff --git a/packages/server/src/loaders/eventEmitter.ts b/packages/server/src/loaders/eventEmitter.ts index 91d814f8a..9da52fd86 100644 --- a/packages/server/src/loaders/eventEmitter.ts +++ b/packages/server/src/loaders/eventEmitter.ts @@ -91,6 +91,7 @@ import { SaleEstimateMarkApprovedOnMailSent } from '@/services/Sales/Estimates/s import { DeleteCashflowTransactionOnUncategorize } from '@/services/Cashflow/subscribers/DeleteCashflowTransactionOnUncategorize'; import { PreventDeleteTransactionOnDelete } from '@/services/Cashflow/subscribers/PreventDeleteTransactionsOnDelete'; import { SubscribeFreeOnSignupCommunity } from '@/services/Subscription/events/SubscribeFreeOnSignupCommunity'; +import { SendVerfiyMailOnSignUp } from '@/services/Authentication/events/SendVerfiyMailOnSignUp'; export default () => { @@ -222,6 +223,7 @@ export const susbcribers = () => { DeleteCashflowTransactionOnUncategorize, PreventDeleteTransactionOnDelete, - SubscribeFreeOnSignupCommunity + SubscribeFreeOnSignupCommunity, + SendVerfiyMailOnSignUp ]; }; diff --git a/packages/server/src/loaders/jobs.ts b/packages/server/src/loaders/jobs.ts index 58da23291..231149f48 100644 --- a/packages/server/src/loaders/jobs.ts +++ b/packages/server/src/loaders/jobs.ts @@ -12,6 +12,7 @@ import { SaleReceiptMailNotificationJob } from '@/services/Sales/Receipts/SaleRe import { PaymentReceiveMailNotificationJob } from '@/services/Sales/PaymentReceives/PaymentReceiveMailNotificationJob'; import { PlaidFetchTransactionsJob } from '@/services/Banking/Plaid/PlaidFetchTransactionsJob'; import { ImportDeleteExpiredFilesJobs } from '@/services/Import/jobs/ImportDeleteExpiredFilesJob'; +import { SendVerifyMailJob } from '@/services/Authentication/jobs/SendVerifyMailJob'; export default ({ agenda }: { agenda: Agenda }) => { new ResetPasswordMailJob(agenda); @@ -27,6 +28,7 @@ export default ({ agenda }: { agenda: Agenda }) => { new PaymentReceiveMailNotificationJob(agenda); new PlaidFetchTransactionsJob(agenda); new ImportDeleteExpiredFilesJobs(agenda); + new SendVerifyMailJob(agenda); agenda.start().then(() => { agenda.every('1 hours', 'delete-expired-imported-files', {}); diff --git a/packages/server/src/models/Account.Settings.ts b/packages/server/src/models/Account.Settings.ts index 5fb8ea8a7..7be8cf404 100644 --- a/packages/server/src/models/Account.Settings.ts +++ b/packages/server/src/models/Account.Settings.ts @@ -7,6 +7,7 @@ export default { sortField: 'name', }, importable: true, + exportable: true, fields: { name: { name: 'account.field.name', @@ -85,6 +86,55 @@ export default { fieldType: 'date', }, }, + columns: { + name: { + name: 'account.field.name', + type: 'text', + }, + code: { + name: 'account.field.code', + type: 'text', + }, + rootType: { + name: 'account.field.root_type', + type: 'text', + accessor: 'accountRootType', + }, + accountType: { + name: 'account.field.type', + accessor: 'accountTypeLabel', + type: 'text', + }, + accountNormal: { + name: 'account.field.normal', + accessor: 'accountNormalFormatted', + }, + currencyCode: { + name: 'account.field.currency', + type: 'text', + }, + bankBalance: { + name: 'account.field.bank_balance', + accessor: 'bankBalanceFormatted', + type: 'text', + exportable: true, + }, + balance: { + name: 'account.field.balance', + accessor: 'amount', + }, + description: { + name: 'account.field.description', + type: 'text', + }, + active: { + name: 'account.field.active', + type: 'boolean', + }, + createdAt: { + name: 'account.field.created_at', + }, + }, fields2: { name: { name: 'account.field.name', diff --git a/packages/server/src/models/Bill.Settings.ts b/packages/server/src/models/Bill.Settings.ts index 2842ff926..890a9635a 100644 --- a/packages/server/src/models/Bill.Settings.ts +++ b/packages/server/src/models/Bill.Settings.ts @@ -5,6 +5,8 @@ export default { sortField: 'bill_date', }, importable: true, + exportFlattenOn: 'entries', + exportable: true, importAggregator: 'group', importAggregateOn: 'entries', importAggregateBy: 'billNumber', @@ -80,6 +82,84 @@ export default { fieldType: 'date', }, }, + columns: { + billNumber: { + name: 'Bill No.', + type: 'text', + }, + referenceNo: { + name: 'Reference No.', + type: 'text', + }, + billDate: { + name: 'Date', + type: 'date', + }, + dueDate: { + name: 'Due Date', + type: 'date', + }, + vendorId: { + name: 'Vendor', + accessor: 'vendor.displayName', + type: 'text', + }, + amount: { + name: 'Amount', + accessor: 'formattedAmount', + }, + exchangeRate: { + name: 'Exchange Rate', + type: 'number', + }, + currencyCode: { + name: 'Currency Code', + type: 'text', + }, + dueAmount: { + name: 'Due Amount', + accessor: 'formattedDueAmount', + }, + paidAmount: { + name: 'Paid Amount', + accessor: 'formattedPaymentAmount', + }, + note: { + name: 'Note', + type: 'text', + }, + open: { + name: 'Open', + type: 'boolean', + }, + entries: { + name: 'Entries', + accessor: 'entries', + type: 'collection', + collectionOf: 'object', + columns: { + itemName: { + name: 'Item Name', + accessor: 'item.name', + }, + rate: { + name: 'Item Rate', + accessor: 'rateFormatted', + }, + quantity: { + name: 'Item Quantity', + accessor: 'quantityFormatted', + }, + description: { + name: 'Item Description', + }, + amount: { + name: 'Item Amount', + accessor: 'totalFormatted', + }, + }, + }, + }, fields2: { billNumber: { name: 'Bill No.', @@ -132,7 +212,7 @@ export default { relationModel: 'Item', relationImportMatch: ['name', 'code'], required: true, - importHint: "Matches the item name or code." + importHint: 'Matches the item name or code.', }, rate: { name: 'Rate', diff --git a/packages/server/src/models/BillPayment.Settings.ts b/packages/server/src/models/BillPayment.Settings.ts index 1f2369f21..4d7de9239 100644 --- a/packages/server/src/models/BillPayment.Settings.ts +++ b/packages/server/src/models/BillPayment.Settings.ts @@ -4,6 +4,7 @@ export default { sortOrder: 'DESC', sortField: 'bill_date', }, + exportable: true, importable: true, importAggregator: 'group', importAggregateOn: 'entries', @@ -67,6 +68,46 @@ export default { fieldType: 'date', }, }, + columns: { + vendor: { + name: 'bill_payment.field.vendor', + type: 'relation', + accessor: 'vendor.displayName', + }, + paymentDate: { + name: 'bill_payment.field.payment_date', + type: 'date', + }, + paymentNumber: { + name: 'bill_payment.field.payment_number', + type: 'text', + }, + paymentAccount: { + name: 'bill_payment.field.payment_account', + accessor: 'paymentAccount.name', + type: 'text', + }, + amount: { + name: 'Amount', + accessor: 'formattedAmount', + }, + currencyCode: { + name: 'Currency Code', + type: 'text', + }, + exchangeRate: { + name: 'bill_payment.field.exchange_rate', + type: 'number', + }, + statement: { + name: 'bill_payment.field.note', + type: 'text', + }, + reference: { + name: 'bill_payment.field.reference', + type: 'text', + }, + }, fields2: { vendorId: { name: 'bill_payment.field.vendor', @@ -84,7 +125,7 @@ export default { name: 'bill_payment.field.payment_number', fieldType: 'text', unique: true, - importHint: "The payment number should be unique." + importHint: 'The payment number should be unique.', }, paymentAccountId: { name: 'bill_payment.field.payment_account', @@ -92,14 +133,14 @@ export default { relationModel: 'Account', relationImportMatch: ['name', 'code'], required: true, - importHint: "Matches the account name or code." + importHint: 'Matches the account name or code.', }, exchangeRate: { name: 'bill_payment.field.exchange_rate', fieldType: 'number', }, statement: { - name: 'bill_payment.field.statement', + name: 'bill_payment.field.note', fieldType: 'text', }, reference: { @@ -120,7 +161,7 @@ export default { relationModel: 'Bill', relationImportMatch: 'billNumber', required: true, - importHint: "Matches the bill number." + importHint: 'Matches the bill number.', }, paymentAmount: { name: 'bill_payment.field.entries.payment_amount', diff --git a/packages/server/src/models/CreditNote.Meta.ts b/packages/server/src/models/CreditNote.Meta.ts index f6317528b..5da0c1d9e 100644 --- a/packages/server/src/models/CreditNote.Meta.ts +++ b/packages/server/src/models/CreditNote.Meta.ts @@ -12,10 +12,14 @@ export default { sortOrder: 'DESC', sortField: 'name', }, + exportable: true, + exportFlattenOn: 'entries', + importable: true, importAggregator: 'group', importAggregateOn: 'entries', importAggregateBy: 'creditNoteNumber', + fields: { customer: { name: 'credit_note.field.customer', @@ -81,6 +85,67 @@ export default { fieldType: 'date', }, }, + columns: { + customer: { + name: 'Customer', + type: 'relation', + accessor: 'customer.displayName', + }, + exchangeRate: { + name: 'Exchange Rate', + type: 'number', + }, + creditNoteDate: { + name: 'Credit Note Date', + type: 'date', + }, + referenceNo: { + name: 'Reference No.', + type: 'text', + }, + note: { + name: 'Note', + type: 'text', + }, + termsConditions: { + name: 'Terms & Conditions', + type: 'text', + }, + creditNoteNumber: { + name: 'Credit Note Number', + type: 'text', + }, + open: { + name: 'Open', + type: 'boolean', + }, + entries: { + name: 'Entries', + type: 'collection', + collectionOf: 'object', + columns: { + itemName: { + name: 'Item Name', + accessor: 'item.name', + }, + rate: { + name: 'Item Rate', + accessor: 'rateFormatted', + }, + quantity: { + name: 'Item Quantity', + accessor: 'quantityFormatted', + }, + description: { + name: 'Item Description', + }, + amount: { + name: 'Item Amount', + accessor: 'totalFormatted', + }, + }, + }, + }, fields2: { customerId: { name: 'Customer', diff --git a/packages/server/src/models/Customer.Settings.ts b/packages/server/src/models/Customer.Settings.ts index fa7bcc2d2..71f631032 100644 --- a/packages/server/src/models/Customer.Settings.ts +++ b/packages/server/src/models/Customer.Settings.ts @@ -1,5 +1,6 @@ export default { importable: true, + exportable: true, defaultFilterField: 'displayName', defaultSort: { sortOrder: 'DESC', @@ -90,6 +91,138 @@ export default { }, }, }, + columns: { + firstName: { + name: 'vendor.field.first_name', + type: 'text', + }, + lastName: { + name: 'vendor.field.last_name', + type: 'text', + }, + displayName: { + name: 'vendor.field.display_name', + type: 'text', + }, + email: { + name: 'vendor.field.email', + type: 'text', + }, + workPhone: { + name: 'vendor.field.work_phone', + type: 'text', + }, + personalPhone: { + name: 'vendor.field.personal_phone', + type: 'text', + }, + companyName: { + name: 'vendor.field.company_name', + type: 'text', + }, + website: { + name: 'vendor.field.website', + type: 'text', + }, + balance: { + name: 'vendor.field.balance', + type: 'number', + }, + openingBalance: { + name: 'vendor.field.opening_balance', + type: 'number', + }, + openingBalanceAt: { + name: 'vendor.field.opening_balance_at', + type: 'date', + }, + currencyCode: { + name: 'vendor.field.currency', + type: 'text', + }, + status: { + name: 'vendor.field.status', + }, + note: { + name: 'vendor.field.note', + }, + // Billing Address + billingAddress1: { + name: 'Billing Address 1', + column: 'billing_address1', + type: 'text', + }, + billingAddress2: { + name: 'Billing Address 2', + column: 'billing_address2', + type: 'text', + }, + billingAddressCity: { + name: 'Billing Address City', + column: 'billing_address_city', + type: 'text', + }, + billingAddressCountry: { + name: 'Billing Address Country', + column: 'billing_address_country', + type: 'text', + }, + billingAddressPostcode: { + name: 'Billing Address Postcode', + column: 'billing_address_postcode', + type: 'text', + }, + billingAddressState: { + name: 'Billing Address State', + column: 'billing_address_state', + type: 'text', + }, + billingAddressPhone: { + name: 'Billing Address Phone', + column: 'billing_address_phone', + type: 'text', + }, + // Shipping Address + shippingAddress1: { + name: 'Shipping Address 1', + column: 'shipping_address1', + type: 'text', + }, + shippingAddress2: { + name: 'Shipping Address 2', + column: 'shipping_address2', + type: 'text', + }, + shippingAddressCity: { + name: 'Shipping Address City', + column: 'shipping_address_city', + type: 'text', + }, + shippingAddressCountry: { + name: 'Shipping Address Country', + column: 'shipping_address_country', + type: 'text', + }, + shippingAddressPostcode: { + name: 'Shipping Address Postcode', + column: 'shipping_address_postcode', + type: 'text', + }, + shippingAddressPhone: { + name: 'Shipping Address Phone', + column: 'shipping_address_phone', + type: 'text', + }, + shippingAddressState: { + name: 'Shipping Address State', + column: 'shipping_address_state', + type: 'text', + }, + createdAt: { + name: 'vendor.field.created_at', + type: 'date', + }, + }, fields2: { customerType: { name: 'Customer Type', diff --git a/packages/server/src/models/Expense.Settings.ts b/packages/server/src/models/Expense.Settings.ts index c1d670526..12c539782 100644 --- a/packages/server/src/models/Expense.Settings.ts +++ b/packages/server/src/models/Expense.Settings.ts @@ -8,6 +8,8 @@ export default { sortField: 'name', }, importable: true, + exportFlattenOn: 'categories', + exportable: true, fields: { payment_date: { name: 'expense.field.payment_date', @@ -61,6 +63,56 @@ export default { fieldType: 'date', }, }, + columns: { + paymentReceive: { + name: 'expense.field.payment_account', + type: 'text', + accessor: 'paymentAccount.name' + }, + referenceNo: { + name: 'expense.field.reference_no', + type: 'text', + }, + paymentDate: { + name: 'expense.field.payment_date', + type: 'date', + }, + currencyCode: { + name: 'expense.field.currency_code', + type: 'text', + }, + exchangeRate: { + name: 'expense.field.exchange_rate', + type: 'number', + }, + description: { + name: 'expense.field.description', + type: 'text', + }, + categories: { + name: 'expense.field.categories', + type: 'collection', + collectionOf: 'object', + columns: { + expenseAccount: { + name: 'expense.field.expense_account', + accessor: 'expenseAccount.name', + }, + amount: { + name: 'expense.field.amount', + accessor: 'amountFormatted', + }, + description: { + name: 'expense.field.line_description', + type: 'text', + }, + }, + }, + publish: { + name: 'expense.field.publish', + type: 'boolean', + }, + }, fields2: { paymentAccountId: { name: 'expense.field.payment_account', @@ -68,7 +120,7 @@ export default { relationModel: 'Account', relationImportMatch: ['name', 'code'], required: true, - importHint: "Matches the account name or code." + importHint: 'Matches the account name or code.', }, referenceNo: { name: 'expense.field.reference_no', @@ -102,7 +154,7 @@ export default { relationModel: 'Account', relationImportMatch: ['name', 'code'], required: true, - importHint: "Matches the account name or code." + importHint: 'Matches the account name or code.', }, amount: { name: 'expense.field.amount', diff --git a/packages/server/src/models/InventoryAdjustment.Settings.ts b/packages/server/src/models/InventoryAdjustment.Settings.ts index 9ef90cbc5..9d7f65237 100644 --- a/packages/server/src/models/InventoryAdjustment.Settings.ts +++ b/packages/server/src/models/InventoryAdjustment.Settings.ts @@ -4,6 +4,54 @@ export default { sortOrder: 'DESC', sortField: 'date', }, + columns: { + date: { + name: 'inventory_adjustment.field.date', + column: 'date', + fieldType: 'date', + exportable: true, + }, + type: { + name: 'inventory_adjustment.field.type', + column: 'type', + fieldType: 'enumeration', + options: [ + { key: 'increment', name: 'inventory_adjustment.field.type.increment' }, + { key: 'decrement', name: 'inventory_adjustment.field.type.decrement' }, + ], + exportable: true, + }, + adjustmentAccount: { + name: 'inventory_adjustment.field.adjustment_account', + type: 'adjustment_account_id', + exportable: true, + }, + reason: { + name: 'inventory_adjustment.field.reason', + type: 'text', + exportable: true, + }, + referenceNo: { + name: 'inventory_adjustment.field.reference_no', + type: 'text', + exportable: true, + }, + description: { + name: 'inventory_adjustment.field.description', + type: 'text', + exportable: true, + }, + publishedAt: { + name: 'inventory_adjustment.field.published_at', + type: 'date', + exportable: true, + }, + createdAt: { + name: 'inventory_adjustment.field.created_at', + type: 'date', + exportable: true, + }, + }, fields: { date: { name: 'inventory_adjustment.field.date', diff --git a/packages/server/src/models/Item.Settings.ts b/packages/server/src/models/Item.Settings.ts index 640c271ea..9c8a50ce8 100644 --- a/packages/server/src/models/Item.Settings.ts +++ b/packages/server/src/models/Item.Settings.ts @@ -1,5 +1,6 @@ export default { importable: true, + exportable: true, defaultFilterField: 'name', defaultSort: { sortField: 'name', @@ -121,6 +122,97 @@ export default { fieldType: 'date', }, }, + columns: { + type: { + name: 'item.field.type', + type: 'text', + exportable: true, + }, + name: { + name: 'item.field.name', + type: 'text', + exportable: true, + }, + code: { + name: 'item.field.code', + type: 'text', + exportable: true, + }, + sellable: { + name: 'item.field.sellable', + type: 'boolean', + exportable: true, + }, + purchasable: { + name: 'item.field.purchasable', + type: 'boolean', + exportable: true, + }, + sellPrice: { + name: 'item.field.cost_price', + type: 'number', + exportable: true, + }, + costPrice: { + name: 'item.field.cost_account', + type: 'number', + exportable: true, + }, + costAccount: { + name: 'item.field.sell_account', + type: 'text', + accessor: 'costAccount.name', + exportable: true, + }, + sellAccount: { + name: 'item.field.sell_description', + type: 'text', + accessor: 'sellAccount.name', + exportable: true, + }, + inventoryAccount: { + name: 'item.field.inventory_account', + type: 'text', + accessor: 'inventoryAccount.name', + exportable: true, + }, + sellDescription: { + name: 'Sell description', + type: 'text', + exportable: true, + }, + purchaseDescription: { + name: 'Purchase description', + type: 'text', + exportable: true, + }, + quantityOnHand: { + name: 'item.field.quantity_on_hand', + type: 'number', + exportable: true, + }, + note: { + name: 'item.field.note', + type: 'text', + exportable: true, + }, + category: { + name: 'item.field.category', + type: 'text', + accessor: 'category.name', + exportable: true, + }, + active: { + name: 'item.field.active', + fieldType: 'boolean', + exportable: true, + }, + createdAt: { + name: 'item.field.created_at', + type: 'date', + exportable: true, + }, + }, fields2: { type: { name: 'item.field.type', @@ -195,7 +287,7 @@ export default { fieldType: 'relation', relationModel: 'ItemCategory', relationImportMatch: ['name'], - importHint: "Matches the category name." + importHint: 'Matches the category name.', }, active: { name: 'item.field.active', diff --git a/packages/server/src/models/ItemCategory.Settings.ts b/packages/server/src/models/ItemCategory.Settings.ts index 32a29cd10..66b7af28f 100644 --- a/packages/server/src/models/ItemCategory.Settings.ts +++ b/packages/server/src/models/ItemCategory.Settings.ts @@ -5,6 +5,7 @@ export default { sortOrder: 'DESC', }, importable: true, + exportable: true, fields: { name: { name: 'item_category.field.name', @@ -28,6 +29,24 @@ export default { columnType: 'date', }, }, + columns: { + name: { + name: 'item_category.field.name', + type: 'text', + }, + description: { + name: 'item_category.field.description', + type: 'text', + }, + count: { + name: 'item_category.field.count', + type: 'text', + }, + createdAt: { + name: 'item_category.field.created_at', + type: 'text', + }, + }, fields2: { name: { name: 'item_category.field.name', diff --git a/packages/server/src/models/ManualJournal.Settings.ts b/packages/server/src/models/ManualJournal.Settings.ts index 11409e1aa..db2712220 100644 --- a/packages/server/src/models/ManualJournal.Settings.ts +++ b/packages/server/src/models/ManualJournal.Settings.ts @@ -5,6 +5,9 @@ export default { sortField: 'name', }, importable: true, + exportFlattenOn: 'entries', + + exportable: true, importAggregator: 'group', importAggregateOn: 'entries', importAggregateBy: 'journalNumber', @@ -56,6 +59,76 @@ export default { fieldType: 'date', }, }, + columns: { + date: { + name: 'manual_journal.field.date', + type: 'date', + }, + journalNumber: { + name: 'manual_journal.field.journal_number', + type: 'text', + }, + reference: { + name: 'manual_journal.field.reference', + type: 'text', + }, + journalType: { + name: 'manual_journal.field.journal_type', + type: 'text', + }, + amount: { + name: 'Amount', + accessor: 'formattedAmount', + }, + currencyCode: { + name: 'manual_journal.field.currency', + type: 'text', + }, + exchangeRate: { + name: 'manual_journal.field.exchange_rate', + type: 'number', + }, + description: { + name: 'manual_journal.field.description', + type: 'text', + }, + entries: { + name: 'Entries', + type: 'collection', + collectionOf: 'object', + columns: { + credit: { + name: 'Credit', + type: 'text', + }, + debit: { + name: 'Debit', + type: 'text', + }, + account: { + name: 'Account', + accessor: 'account.name', + }, + contact: { + name: 'Contact', + accessor: 'contact.displayName', + }, + note: { + name: 'Note', + }, + }, + publish: { + name: 'Publish', + type: 'boolean', + }, + publishedAt: { + name: 'Published At', + }, + }, + createdAt: { + name: 'Created At', + }, + }, fields2: { date: { name: 'manual_journal.field.date', diff --git a/packages/server/src/models/PaymentReceive.Settings.ts b/packages/server/src/models/PaymentReceive.Settings.ts index e96e1c7b2..663b5884d 100644 --- a/packages/server/src/models/PaymentReceive.Settings.ts +++ b/packages/server/src/models/PaymentReceive.Settings.ts @@ -1,5 +1,6 @@ export default { importable: true, + exportable: true, importAggregator: 'group', importAggregateOn: 'entries', importAggregateBy: 'paymentReceiveNo', @@ -57,6 +58,42 @@ export default { fieldDate: 'date', }, }, + columns: { + customer: { + name: 'payment_receive.field.customer', + accessor: 'customer.displayName', + type: 'text', + }, + paymentDate: { + name: 'payment_receive.field.payment_date', + type: 'date', + }, + amount: { + name: 'payment_receive.field.amount', + type: 'number', + }, + referenceNo: { + name: 'payment_receive.field.reference_no', + type: 'text', + }, + depositAccount: { + name: 'payment_receive.field.deposit_account', + accessor: 'depositAccount.name', + type: 'text', + }, + paymentReceiveNo: { + name: 'payment_receive.field.payment_receive_no', + type: 'text', + }, + statement: { + name: 'payment_receive.field.statement', + type: 'text', + }, + created_at: { + name: 'payment_receive.field.created_at', + type: 'date', + }, + }, fields2: { customerId: { name: 'payment_receive.field.customer', @@ -84,12 +121,12 @@ export default { relationModel: 'Account', relationImportMatch: ['name', 'code'], required: true, - importHint: "Matches the account name or code." + importHint: 'Matches the account name or code.', }, paymentReceiveNo: { name: 'payment_receive.field.payment_receive_no', fieldType: 'text', - importHint: "The payment number should be unique." + importHint: 'The payment number should be unique.', }, statement: { name: 'payment_receive.field.statement', @@ -108,7 +145,7 @@ export default { relationModel: 'SaleInvoice', relationImportMatch: 'invoiceNo', required: true, - importHint: "Matches the invoice number." + importHint: 'Matches the invoice number.', }, paymentAmount: { name: 'payment_receive.field.entries.payment_amount', diff --git a/packages/server/src/models/SaleEstimate.Settings.ts b/packages/server/src/models/SaleEstimate.Settings.ts index f92735f34..a9577b4f4 100644 --- a/packages/server/src/models/SaleEstimate.Settings.ts +++ b/packages/server/src/models/SaleEstimate.Settings.ts @@ -4,6 +4,9 @@ export default { sortOrder: 'DESC', sortField: 'estimate_date', }, + exportable: true, + exportFlattenOn: 'entries', + importable: true, importAggregator: 'group', importAggregateOn: 'entries', @@ -73,6 +76,91 @@ export default { columnType: 'date', }, }, + columns: { + customer: { + name: 'Customer', + type: 'text', + accessor: 'customer.displayName', + exportable: true, + }, + estimateDate: { + name: 'Estimate Date', + type: 'date', + exportable: true, + }, + expirationDate: { + name: 'Expiration Date', + type: 'date', + exportable: true, + }, + estimateNumber: { + name: 'Estimate No.', + type: 'text', + exportable: true, + }, + reference: { + name: 'Reference No.', + type: 'text', + exportable: true, + }, + amount: { + name: 'Amount', + accessor: 'formattedAmount', + type: 'text', + }, + exchangeRate: { + name: 'Exchange Rate', + type: 'number', + exportable: true, + }, + currencyCode: { + name: 'Currency', + type: 'text', + exportable: true, + }, + note: { + name: 'Note', + type: 'text', + exportable: true, + }, + termsConditions: { + name: 'Terms & Conditions', + type: 'text', + exportable: true, + }, + delivered: { + name: 'Delivered', + type: 'boolean', + exportable: true, + }, + entries: { + name: 'Entries', + accessor: 'entries', + type: 'collection', + collectionOf: 'object', + columns: { + itemName: { + name: 'Item Name', + accessor: 'item.name', + }, + rate: { + name: 'Item Rate', + accessor: 'rateFormatted', + }, + quantity: { + name: 'Item Quantity', + accessor: 'quantityFormatted', + }, + description: { + name: 'Item Description', + }, + amount: { + name: 'Item Amount', + accessor: 'totalFormatted', + }, + }, + }, + }, fields2: { customerId: { name: 'Customer', @@ -132,7 +220,7 @@ export default { relationModel: 'Item', relationImportMatch: ['name', 'code'], required: true, - importHint: "Matches the item name or code." + importHint: 'Matches the item name or code.', }, rate: { name: 'invoice.field.rate', diff --git a/packages/server/src/models/SaleInvoice.Settings.ts b/packages/server/src/models/SaleInvoice.Settings.ts index c48befbcb..24728522e 100644 --- a/packages/server/src/models/SaleInvoice.Settings.ts +++ b/packages/server/src/models/SaleInvoice.Settings.ts @@ -4,6 +4,9 @@ export default { sortOrder: 'DESC', sortField: 'created_at', }, + exportable: true, + exportFlattenOn: 'entries', + importable: true, importAggregator: 'group', importAggregateOn: 'entries', @@ -87,6 +90,89 @@ export default { fieldType: 'date', }, }, + columns: { + invoiceDate: { + name: 'invoice.field.invoice_date', + type: 'date', + }, + dueDate: { + name: 'invoice.field.due_date', + type: 'date', + }, + referenceNo: { + name: 'invoice.field.reference_no', + type: 'text', + }, + invoiceNo: { + name: 'invoice.field.invoice_no', + type: 'text', + }, + customer: { + name: 'invoice.field.customer', + type: 'text', + accessor: 'customer.displayName', + }, + amount: { + name: 'invoice.field.amount', + type: 'text', + accessor: 'balanceAmountFormatted', + }, + exchangeRate: { + name: 'invoice.field.exchange_rate', + type: 'number', + }, + currencyCode: { + name: 'invoice.field.currency', + type: 'text', + }, + paidAmount: { + name: 'Paid Amount', + accessor: 'paymentAmountFormatted', + }, + dueAmount: { + name: 'Due Amount', + accessor: 'dueAmountFormatted', + }, + invoiceMessage: { + name: 'invoice.field.invoice_message', + type: 'text', + }, + termsConditions: { + name: 'invoice.field.terms_conditions', + type: 'text', + }, + delivered: { + name: 'invoice.field.delivered', + type: 'boolean', + }, + entries: { + name: 'Entries', + accessor: 'entries', + type: 'collection', + collectionOf: 'object', + columns: { + itemName: { + name: 'Item Name', + accessor: 'item.name', + }, + rate: { + name: 'Item Rate', + accessor: 'rateFormatted', + }, + quantity: { + name: 'Item Quantity', + accessor: 'quantityFormatted', + }, + description: { + name: 'Item Description', + }, + amount: { + name: 'Item Amount', + accessor: 'totalFormatted', + }, + }, + }, + }, fields2: { invoiceDate: { name: 'invoice.field.invoice_date', @@ -142,7 +228,7 @@ export default { relationModel: 'Item', relationImportMatch: ['name', 'code'], required: true, - importHint: "Matches the item name or code." + importHint: 'Matches the item name or code.', }, rate: { name: 'invoice.field.rate', diff --git a/packages/server/src/models/SaleReceipt.Settings.ts b/packages/server/src/models/SaleReceipt.Settings.ts index 2b2fe6fa2..3fecd0480 100644 --- a/packages/server/src/models/SaleReceipt.Settings.ts +++ b/packages/server/src/models/SaleReceipt.Settings.ts @@ -4,6 +4,9 @@ export default { sortOrder: 'DESC', sortField: 'created_at', }, + exportable: true, + exportFlattenOn: 'entries', + importable: true, importAggregator: 'group', importAggregateOn: 'entries', @@ -77,6 +80,86 @@ export default { sortCustomQuery: StatusFieldSortQuery, }, }, + columns: { + amount: { + name: 'receipt.field.amount', + column: 'amount', + type: 'number', + }, + depositAccount: { + name: 'receipt.field.deposit_account', + type: 'text', + accessor: 'depositAccount.name', + }, + customer: { + name: 'receipt.field.customer', + type: 'text', + accessor: 'customer.displayName', + }, + receiptDate: { + name: 'receipt.field.receipt_date', + type: 'date', + }, + receiptNumber: { + name: 'receipt.field.receipt_number', + type: 'text', + }, + referenceNo: { + name: 'receipt.field.reference_no', + column: 'reference_no', + type: 'text', + exportable: true, + }, + receiptMessage: { + name: 'receipt.field.receipt_message', + column: 'receipt_message', + type: 'text', + }, + statement: { + name: 'receipt.field.statement', + type: 'text', + }, + status: { + name: 'receipt.field.status', + type: 'enumeration', + options: [ + { key: 'draft', label: 'receipt.field.status.draft' }, + { key: 'closed', label: 'receipt.field.status.closed' }, + ], + exportable: true, + }, + entries: { + name: 'Entries', + accessor: 'entries', + type: 'collection', + collectionOf: 'object', + columns: { + itemName: { + name: 'Item Name', + accessor: 'item.name', + }, + rate: { + name: 'Item Rate', + accessor: 'rateFormatted', + }, + quantity: { + name: 'Item Quantity', + accessor: 'quantityFormatted', + }, + description: { + name: 'Item Description', + }, + amount: { + name: 'Item Amount', + accessor: 'totalFormatted', + }, + }, + }, + createdAt: { + name: 'receipt.field.created_at', + type: 'date', + }, + }, fields2: { receiptDate: { name: 'Receipt Date', @@ -126,7 +209,7 @@ export default { relationModel: 'Item', relationImportMatch: ['name', 'code'], required: true, - importHint: "Matches the item name or code." + importHint: 'Matches the item name or code.', }, rate: { name: 'invoice.field.rate', diff --git a/packages/server/src/models/Vendor.Settings.ts b/packages/server/src/models/Vendor.Settings.ts index 7f22d5ba4..7681dfa10 100644 --- a/packages/server/src/models/Vendor.Settings.ts +++ b/packages/server/src/models/Vendor.Settings.ts @@ -5,6 +5,7 @@ export default { sortField: 'created_at', }, importable: true, + exportable: true, fields: { first_name: { name: 'vendor.field.first_name', @@ -32,7 +33,7 @@ export default { fieldType: 'text', }, personal_phone: { - name: 'vendor.field.personal_pone', + name: 'vendor.field.personal_phone', column: 'personal_phone', fieldType: 'text', }, @@ -90,6 +91,154 @@ export default { }, }, }, + columns: { + firstName: { + name: 'vendor.field.first_name', + type: 'text', + }, + lastName: { + name: 'vendor.field.last_name', + type: 'text', + }, + displayName: { + name: 'vendor.field.display_name', + type: 'text', + }, + email: { + name: 'vendor.field.email', + type: 'text', + }, + workPhone: { + name: 'vendor.field.work_phone', + type: 'text', + }, + personalPhone: { + name: 'vendor.field.personal_phone', + type: 'text', + }, + companyName: { + name: 'vendor.field.company_name', + type: 'text', + }, + website: { + name: 'vendor.field.website', + type: 'text', + }, + balance: { + name: 'vendor.field.balance', + type: 'number', + }, + openingBalance: { + name: 'vendor.field.opening_balance', + type: 'number', + }, + openingBalanceAt: { + name: 'vendor.field.opening_balance_at', + type: 'date', + }, + currencyCode: { + name: 'vendor.field.currency', + type: 'text', + }, + status: { + name: 'vendor.field.status', + }, + note: { + name: 'vendor.field.note', + type: 'text', + }, + // Billing Address + billingAddress1: { + name: 'Billing Address 1', + column: 'billing_address1', + type: 'text', + exportable: true, + }, + billingAddress2: { + name: 'Billing Address 2', + column: 'billing_address2', + type: 'text', + exportable: true, + }, + billingAddressCity: { + name: 'Billing Address City', + column: 'billing_address_city', + type: 'text', + exportable: true, + }, + billingAddressCountry: { + name: 'Billing Address Country', + column: 'billing_address_country', + type: 'text', + exportable: true, + }, + billingAddressPostcode: { + name: 'Billing Address Postcode', + column: 'billing_address_postcode', + type: 'text', + exportable: true, + }, + billingAddressState: { + name: 'Billing Address State', + column: 'billing_address_state', + type: 'text', + exportable: true, + }, + billingAddressPhone: { + name: 'Billing Address Phone', + column: 'billing_address_phone', + type: 'text', + exportable: true, + }, + // Shipping Address + shippingAddress1: { + name: 'Shipping Address 1', + column: 'shipping_address1', + type: 'text', + exportable: true, + }, + shippingAddress2: { + name: 'Shipping Address 2', + column: 'shipping_address2', + type: 'text', + exportable: true, + }, + shippingAddressCity: { + name: 'Shipping Address City', + column: 'shipping_address_city', + type: 'text', + exportable: true, + }, + shippingAddressCountry: { + name: 'Shipping Address Country', + column: 'shipping_address_country', + type: 'text', + exportable: true, + }, + shippingAddressPostcode: { + name: 'Shipping Address Postcode', + column: 'shipping_address_postcode', + type: 'text', + exportable: true, + }, + shippingAddressState: { + name: 'Shipping Address State', + column: 'shipping_address_state', + type: 'text', + exportable: true, + }, + shippingAddressPhone: { + name: 'Shipping Address Phone', + column: 'shipping_address_phone', + type: 'text', + exportable: true, + }, + createdAt: { + name: 'vendor.field.created_at', + type: 'date', + exportable: true, + }, + }, fields2: { firstName: { name: 'vendor.field.first_name', diff --git a/packages/server/src/models/VendorCredit.Meta.ts b/packages/server/src/models/VendorCredit.Meta.ts index 86a3aa103..b57cc275c 100644 --- a/packages/server/src/models/VendorCredit.Meta.ts +++ b/packages/server/src/models/VendorCredit.Meta.ts @@ -12,10 +12,14 @@ export default { sortOrder: 'DESC', sortField: 'name', }, + exportable: true, + exportFlattenOn: 'entries', + importable: true, importAggregator: 'group', importAggregateOn: 'entries', importAggregateBy: 'vendorCreditNumber', + fields: { vendor: { name: 'vendor_credit.field.vendor', @@ -76,6 +80,79 @@ export default { fieldType: 'date', }, }, + columns: { + vendorId: { + name: 'Vendor', + type: 'relation', + accessor: 'vendor.displayName', + }, + exchangeRate: { + name: 'Echange Rate', + type: 'text', + }, + vendorCreditNumber: { + name: 'Vendor Credit No.', + type: 'text', + }, + referenceNo: { + name: 'Refernece No.', + type: 'text', + }, + vendorCreditDate: { + name: 'Vendor Credit Date', + type: 'date', + }, + amount: { + name: 'Amount', + accessor: 'formattedAmount', + }, + creditRemaining: { + name: 'Credits Remaining', + accessor: 'formattedCreditsRemaining', + }, + refundedAmount: { + name: 'Refunded Amount', + accessor: 'refundedAmount', + }, + invoicedAmount: { + name: 'Invoiced Amount', + accessor: 'formattedInvoicedAmount', + }, + note: { + name: 'Note', + type: 'text', + }, + open: { + name: 'Open', + type: 'boolean', + }, + entries: { + name: 'Entries', + type: 'collection', + collectionOf: 'object', + columns: { + itemName: { + name: 'Item Name', + accessor: 'item.name', + }, + rate: { + name: 'Item Rate', + accessor: 'rateFormatted', + }, + quantity: { + name: 'Item Quantity', + accessor: 'quantityFormatted', + }, + description: { + name: 'Item Description', + }, + amount: { + name: 'Item Amount', + accessor: 'totalFormatted', + }, + }, + }, + }, fields2: { vendorId: { name: 'Vendor', @@ -122,7 +199,7 @@ export default { relationModel: 'Item', relationImportMatch: ['name', 'code'], required: true, - importHint: "Matches the item name or code." + importHint: 'Matches the item name or code.', }, rate: { name: 'Rate', diff --git a/packages/server/src/models/WarehouseTransfer.Settings.ts b/packages/server/src/models/WarehouseTransfer.Settings.ts index 8d0bc635d..d20064187 100644 --- a/packages/server/src/models/WarehouseTransfer.Settings.ts +++ b/packages/server/src/models/WarehouseTransfer.Settings.ts @@ -8,6 +8,34 @@ export default { sortField: 'name', sortOrder: 'DESC', }, + columns: { + date: { + name: 'warehouse_transfer.field.date', + type: 'date', + exportable: true, + }, + transaction_number: { + name: 'warehouse_transfer.field.transaction_number', + type: 'text', + exportable: true, + }, + status: { + name: 'warehouse_transfer.field.status', + fieldType: 'enumeration', + options: [ + { key: 'draft', label: 'Draft' }, + { key: 'in-transit', label: 'In Transit' }, + { key: 'transferred', label: 'Transferred' }, + ], + sortable: false, + }, + created_at: { + name: 'warehouse_transfer.field.created_at', + column: 'created_at', + columnType: 'date', + fieldType: 'date', + }, + }, fields: { date: { name: 'warehouse_transfer.field.date', diff --git a/packages/server/src/server.ts b/packages/server/src/server.ts index 9c08e6533..9b05fe2c0 100644 --- a/packages/server/src/server.ts +++ b/packages/server/src/server.ts @@ -1,4 +1,5 @@ import 'reflect-metadata'; // We need this in order to use @Decorators +import 'newrelic'; import './before'; import '@/config'; diff --git a/packages/server/src/services/Accounts/AccountsExportable.ts b/packages/server/src/services/Accounts/AccountsExportable.ts new file mode 100644 index 000000000..86a6293d8 --- /dev/null +++ b/packages/server/src/services/Accounts/AccountsExportable.ts @@ -0,0 +1,31 @@ +import { Inject, Service } from 'typedi'; +import { AccountsApplication } from './AccountsApplication'; +import { Exportable } from '../Export/Exportable'; +import { IAccountsFilter, IAccountsStructureType } from '@/interfaces'; + +@Service() +export class AccountsExportable extends Exportable { + @Inject() + private accountsApplication: AccountsApplication; + + /** + * Retrieves the accounts data to exportable sheet. + * @param {number} tenantId + * @returns + */ + public exportable(tenantId: number, query: IAccountsFilter) { + const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', + inactiveMode: false, + ...query, + structure: IAccountsStructureType.Flat, + pageSize: 12000, + page: 1, + } as IAccountsFilter; + + return this.accountsApplication + .getAccounts(tenantId, parsedQuery) + .then((output) => output.accounts); + } +} diff --git a/packages/server/src/services/Authentication/AuthApplication.ts b/packages/server/src/services/Authentication/AuthApplication.ts index a3cf0fc31..f900e63ac 100644 --- a/packages/server/src/services/Authentication/AuthApplication.ts +++ b/packages/server/src/services/Authentication/AuthApplication.ts @@ -1,16 +1,19 @@ -import { Service, Inject, Container } from 'typedi'; import { + IAuthGetMetaPOJO, + IPasswordReset, IRegisterDTO, ISystemUser, - IPasswordReset, - IAuthGetMetaPOJO, ITenant, } from '@/interfaces'; +import { SystemUser } from '@/system/models'; +import { Inject, Service } from 'typedi'; +import { AuthSendResetPassword } from './AuthSendResetPassword'; import { AuthSigninService } from './AuthSignin'; import { AuthSignupService } from './AuthSignup'; -import { AuthSendResetPassword } from './AuthSendResetPassword'; -import { GetAuthMeta } from './GetAuthMeta'; +import { AuthSignupConfirmService } from './AuthSignupConfirm'; +import { AuthSignupConfirmResend } from './AuthSignupResend'; import { GetAuthMe } from './GetAuthMe'; +import { GetAuthMeta } from './GetAuthMeta'; @Service() export default class AuthenticationApplication { @@ -20,6 +23,12 @@ export default class AuthenticationApplication { @Inject() private authSignupService: AuthSignupService; + @Inject() + private authSignupConfirmService: AuthSignupConfirmService; + + @Inject() + private authSignUpConfirmResendService: AuthSignupConfirmResend; + @Inject() private authResetPasswordService: AuthSendResetPassword; @@ -49,6 +58,28 @@ export default class AuthenticationApplication { return this.authSignupService.signUp(signupDTO); } + /** + * Verfying the provided user's email after signin-up. + * @param {string} email + * @param {string} token + * @returns {Promise} + */ + public async signUpConfirm( + email: string, + token: string + ): Promise { + return this.authSignupConfirmService.signUpConfirm(email, token); + } + + /** + * Resends the confirmation email of the given system user. + * @param {number} userId - System user id. + * @returns {Promise} + */ + public async signUpConfirmResend(userId: number) { + return this.authSignUpConfirmResendService.signUpConfirmResend(userId); + } + /** * Generates and retrieve password reset token for the given user email. * @param {string} email diff --git a/packages/server/src/services/Authentication/AuthSignup.ts b/packages/server/src/services/Authentication/AuthSignup.ts index b064a3d91..17c7f574c 100644 --- a/packages/server/src/services/Authentication/AuthSignup.ts +++ b/packages/server/src/services/Authentication/AuthSignup.ts @@ -1,5 +1,6 @@ -import { isEmpty, omit } from 'lodash'; +import { defaultTo, isEmpty, omit } from 'lodash'; import moment from 'moment'; +import crypto from 'crypto'; import { ServiceError } from '@/exceptions'; import { IAuthSignedUpEventPayload, @@ -42,6 +43,13 @@ export class AuthSignupService { const hashedPassword = await hashPassword(signupDTO.password); + const verifyTokenCrypto = crypto.randomBytes(64).toString('hex'); + const verifiedEnabed = defaultTo(config.signupConfirmation.enabled, false); + const verifyToken = verifiedEnabed ? verifyTokenCrypto : ''; + const verified = !verifiedEnabed; + + const inviteAcceptedAt = moment().format('YYYY-MM-DD'); + // Triggers signin up event. await this.eventPublisher.emitAsync(events.auth.signingUp, { signupDTO, @@ -50,10 +58,12 @@ export class AuthSignupService { const tenant = await this.tenantsManager.createTenant(); const registeredUser = await systemUserRepository.create({ ...omit(signupDTO, 'country'), + verifyToken, + verified, active: true, password: hashedPassword, tenantId: tenant.id, - inviteAcceptedAt: moment().format('YYYY-MM-DD'), + inviteAcceptedAt, }); // Triggers signed up event. await this.eventPublisher.emitAsync(events.auth.signUp, { diff --git a/packages/server/src/services/Authentication/AuthSignupConfirm.ts b/packages/server/src/services/Authentication/AuthSignupConfirm.ts new file mode 100644 index 000000000..b08a4bd2e --- /dev/null +++ b/packages/server/src/services/Authentication/AuthSignupConfirm.ts @@ -0,0 +1,57 @@ +import { Inject, Service } from 'typedi'; +import { ServiceError } from '@/exceptions'; +import { SystemUser } from '@/system/models'; +import { ERRORS } from './_constants'; +import { EventPublisher } from '@/lib/EventPublisher/EventPublisher'; +import events from '@/subscribers/events'; +import { + IAuthSignUpVerifiedEventPayload, + IAuthSignUpVerifingEventPayload, +} from '@/interfaces'; + +@Service() +export class AuthSignupConfirmService { + @Inject() + private eventPublisher: EventPublisher; + + /** + * Verifies the provided user's email after signing-up. + * @throws {ServiceErrors} + * @param {IRegisterDTO} signupDTO + * @returns {Promise} + */ + public async signUpConfirm( + email: string, + verifyToken: string + ): Promise { + const foundUser = await SystemUser.query().findOne({ email, verifyToken }); + + if (!foundUser) { + throw new ServiceError(ERRORS.SIGNUP_CONFIRM_TOKEN_INVALID); + } + const userId = foundUser.id; + + // Triggers `signUpConfirming` event. + await this.eventPublisher.emitAsync(events.auth.signUpConfirming, { + email, + verifyToken, + userId, + } as IAuthSignUpVerifingEventPayload); + + const updatedUser = await SystemUser.query().patchAndFetchById( + foundUser.id, + { + verified: true, + verifyToken: '', + } + ); + // Triggers `signUpConfirmed` event. + await this.eventPublisher.emitAsync(events.auth.signUpConfirmed, { + email, + verifyToken, + userId, + } as IAuthSignUpVerifiedEventPayload); + + return updatedUser as SystemUser; + } +} diff --git a/packages/server/src/services/Authentication/AuthSignupResend.ts b/packages/server/src/services/Authentication/AuthSignupResend.ts new file mode 100644 index 000000000..8edb08f35 --- /dev/null +++ b/packages/server/src/services/Authentication/AuthSignupResend.ts @@ -0,0 +1,34 @@ +import { Inject, Service } from 'typedi'; +import { ServiceError } from '@/exceptions'; +import { SystemUser } from '@/system/models'; +import { ERRORS } from './_constants'; + +@Service() +export class AuthSignupConfirmResend { + @Inject('agenda') + private agenda: any; + + /** + * Resends the email confirmation of the given user. + * @param {number} userId - User ID. + * @returns {Promise} + */ + public async signUpConfirmResend(userId: number) { + const user = await SystemUser.query().findById(userId).throwIfNotFound(); + + // Throw error if the user is already verified. + if (user.verified) { + throw new ServiceError(ERRORS.USER_ALREADY_VERIFIED); + } + // Throw error if the verification token is not exist. + if (!user.verifyToken) { + throw new ServiceError(ERRORS.USER_ALREADY_VERIFIED); + } + const payload = { + email: user.email, + token: user.verifyToken, + fullName: user.firstName, + }; + await this.agenda.now('send-signup-verify-mail', payload); + } +} diff --git a/packages/server/src/services/Authentication/AuthenticationMailMessages.ts b/packages/server/src/services/Authentication/AuthenticationMailMessages.ts index c8c26ea0d..533315e72 100644 --- a/packages/server/src/services/Authentication/AuthenticationMailMessages.ts +++ b/packages/server/src/services/Authentication/AuthenticationMailMessages.ts @@ -33,4 +33,33 @@ export default class AuthenticationMailMesssages { }) .send(); } + + /** + * Sends signup verification mail. + * @param {string} email - Email address + * @param {string} fullName - User name. + * @param {string} token - Verification token. + * @returns {Promise} + */ + public async sendSignupVerificationMail( + email: string, + fullName: string, + token: string + ) { + const verifyUrl = `${config.baseURL}/auth/email_confirmation?token=${token}&email=${email}`; + + await new Mail() + .setSubject('Bigcapital - Verify your email') + .setView('mail/SignupVerifyEmail.html') + .setTo(email) + .setAttachments([ + { + filename: 'bigcapital.png', + path: `${global.__views_dir}/images/bigcapital.png`, + cid: 'bigcapital_logo', + }, + ]) + .setData({ verifyUrl, fullName }) + .send(); + } } diff --git a/packages/server/src/services/Authentication/_constants.ts b/packages/server/src/services/Authentication/_constants.ts index 8c62dbe6c..506525779 100644 --- a/packages/server/src/services/Authentication/_constants.ts +++ b/packages/server/src/services/Authentication/_constants.ts @@ -9,4 +9,6 @@ export const ERRORS = { EMAIL_EXISTS: 'EMAIL_EXISTS', SIGNUP_RESTRICTED_NOT_ALLOWED: 'SIGNUP_RESTRICTED_NOT_ALLOWED', SIGNUP_RESTRICTED: 'SIGNUP_RESTRICTED', + SIGNUP_CONFIRM_TOKEN_INVALID: 'SIGNUP_CONFIRM_TOKEN_INVALID', + USER_ALREADY_VERIFIED: 'USER_ALREADY_VERIFIED', }; diff --git a/packages/server/src/services/Authentication/events/SendVerfiyMailOnSignUp.ts b/packages/server/src/services/Authentication/events/SendVerfiyMailOnSignUp.ts new file mode 100644 index 000000000..14f9aaa07 --- /dev/null +++ b/packages/server/src/services/Authentication/events/SendVerfiyMailOnSignUp.ts @@ -0,0 +1,30 @@ +import { IAuthSignedUpEventPayload } from '@/interfaces'; +import events from '@/subscribers/events'; +import { Inject } from 'typedi'; + +export class SendVerfiyMailOnSignUp { + @Inject('agenda') + private agenda: any; + + /** + * Attaches events with handles. + */ + public attach(bus) { + bus.subscribe(events.auth.signUp, this.handleSendVerifyMailOnSignup); + } + + /** + * + * @param {ITaxRateEditedPayload} payload - + */ + private handleSendVerifyMailOnSignup = async ({ + user, + }: IAuthSignedUpEventPayload) => { + const payload = { + email: user.email, + token: user.verifyToken, + fullName: user.firstName, + }; + await this.agenda.now('send-signup-verify-mail', payload); + }; +} diff --git a/packages/server/src/services/Authentication/jobs/SendVerifyMailJob.ts b/packages/server/src/services/Authentication/jobs/SendVerifyMailJob.ts new file mode 100644 index 000000000..8e09053da --- /dev/null +++ b/packages/server/src/services/Authentication/jobs/SendVerifyMailJob.ts @@ -0,0 +1,35 @@ +import { Container } from 'typedi'; +import AuthenticationMailMesssages from '@/services/Authentication/AuthenticationMailMessages'; + +export class SendVerifyMailJob { + /** + * Constructor method. + * @param {Agenda} agenda + */ + constructor(agenda) { + agenda.define( + 'send-signup-verify-mail', + { priority: 'high' }, + this.handler.bind(this) + ); + } + + /** + * Handle send welcome mail job. + * @param {Job} job + * @param {Function} done + */ + public async handler(job, done: Function): Promise { + const { data } = job.attrs; + const { email, fullName, token } = data; + const authService = Container.get(AuthenticationMailMesssages); + + try { + await authService.sendSignupVerificationMail(email, fullName, token); + done(); + } catch (error) { + console.log(error); + done(error); + } + } +} diff --git a/packages/server/src/services/Contacts/Customers/CustomersExportable.ts b/packages/server/src/services/Contacts/Customers/CustomersExportable.ts new file mode 100644 index 000000000..8654fa31f --- /dev/null +++ b/packages/server/src/services/Contacts/Customers/CustomersExportable.ts @@ -0,0 +1,29 @@ +import { Inject, Service } from 'typedi'; +import { IItemsFilter } from '@/interfaces'; +import { CustomersApplication } from './CustomersApplication'; +import { Exportable } from '@/services/Export/Exportable'; + +@Service() +export class CustomersExportable extends Exportable { + @Inject() + private customersApplication: CustomersApplication; + + /** + * Retrieves the accounts data to exportable sheet. + * @param {number} tenantId + * @returns + */ + public exportable(tenantId: number, query: IItemsFilter) { + const parsedQuery = { + sortOrder: 'DESC', + columnSortBy: 'created_at', + page: 1, + ...query, + pageSize: 12, + } as IItemsFilter; + + return this.customersApplication + .getCustomers(tenantId, parsedQuery) + .then((output) => output.customers); + } +} diff --git a/packages/server/src/services/Contacts/Vendors/VendorsExportable.ts b/packages/server/src/services/Contacts/Vendors/VendorsExportable.ts new file mode 100644 index 000000000..c11f50050 --- /dev/null +++ b/packages/server/src/services/Contacts/Vendors/VendorsExportable.ts @@ -0,0 +1,29 @@ +import { Inject, Service } from 'typedi'; +import { IItemsFilter } from '@/interfaces'; +import { Exportable } from '@/services/Export/Exportable'; +import { VendorsApplication } from './VendorsApplication'; + +@Service() +export class VendorsExportable extends Exportable { + @Inject() + private vendorsApplication: VendorsApplication; + + /** + * Retrieves the accounts data to exportable sheet. + * @param {number} tenantId + * @returns + */ + public exportable(tenantId: number, query: IItemsFilter) { + const parsedQuery = { + sortOrder: 'DESC', + columnSortBy: 'created_at', + page: 1, + ...query, + pageSize: 12, + } as IItemsFilter; + + return this.vendorsApplication + .getVendors(tenantId, parsedQuery) + .then((output) => output.vendors); + } +} diff --git a/packages/server/src/services/CreditNotes/CreditNotesExportable.ts b/packages/server/src/services/CreditNotes/CreditNotesExportable.ts new file mode 100644 index 000000000..09dae2a74 --- /dev/null +++ b/packages/server/src/services/CreditNotes/CreditNotesExportable.ts @@ -0,0 +1,30 @@ +import { Inject, Service } from 'typedi'; +import { ICreditNotesQueryDTO } from '@/interfaces'; +import { Exportable } from '@/services/Export/Exportable'; +import ListCreditNotes from './ListCreditNotes'; + +@Service() +export class CreditNotesExportable extends Exportable { + @Inject() + private getCreditNotes: ListCreditNotes; + + /** + * Retrieves the accounts data to exportable sheet. + * @param {number} tenantId - + * @param {IVendorCreditsQueryDTO} query - + * @returns {} + */ + public exportable(tenantId: number, query: ICreditNotesQueryDTO) { + const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', + ...query, + page: 1, + pageSize: 12000, + } as ICreditNotesQueryDTO; + + return this.getCreditNotes + .getCreditNotesList(tenantId, parsedQuery) + .then((output) => output.creditNotes); + } +} diff --git a/packages/server/src/services/CreditNotes/ListCreditNotes.ts b/packages/server/src/services/CreditNotes/ListCreditNotes.ts index 498d3d74d..11ec2f7fa 100644 --- a/packages/server/src/services/CreditNotes/ListCreditNotes.ts +++ b/packages/server/src/services/CreditNotes/ListCreditNotes.ts @@ -45,7 +45,7 @@ export default class ListCreditNotes extends BaseCreditNotes { ); const { results, pagination } = await CreditNote.query() .onBuild((builder) => { - builder.withGraphFetched('entries'); + builder.withGraphFetched('entries.item'); builder.withGraphFetched('customer'); dynamicFilter.buildQuery()(builder); }) diff --git a/packages/server/src/services/Expenses/ExpensesExportable.ts b/packages/server/src/services/Expenses/ExpensesExportable.ts new file mode 100644 index 000000000..51362912f --- /dev/null +++ b/packages/server/src/services/Expenses/ExpensesExportable.ts @@ -0,0 +1,29 @@ +import { Inject, Service } from 'typedi'; +import { Exportable } from '../Export/Exportable'; +import { IExpensesFilter } from '@/interfaces'; +import { ExpensesApplication } from './ExpensesApplication'; + +@Service() +export class ExpensesExportable extends Exportable { + @Inject() + private expensesApplication: ExpensesApplication; + + /** + * Retrieves the accounts data to exportable sheet. + * @param {number} tenantId + * @returns + */ + public exportable(tenantId: number, query: IExpensesFilter) { + const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', + ...query, + page: 1, + pageSize: 12000, + } as IExpensesFilter; + + return this.expensesApplication + .getExpenses(tenantId, parsedQuery) + .then((output) => output.expenses); + } +} diff --git a/packages/server/src/services/Export/ExportApplication.ts b/packages/server/src/services/Export/ExportApplication.ts new file mode 100644 index 000000000..44a7dc73f --- /dev/null +++ b/packages/server/src/services/Export/ExportApplication.ts @@ -0,0 +1,17 @@ +import { Inject, Service } from 'typedi'; +import { ExportResourceService } from './ExportService'; + +@Service() +export class ExportApplication { + @Inject() + private exportResource: ExportResourceService; + + /** + * Exports the given resource to csv, xlsx or pdf format. + * @param {string} reosurce + * @param {string} format + */ + public export(tenantId: number, resource: string, format: string) { + return this.exportResource.export(tenantId, resource, format); + } +} diff --git a/packages/server/src/services/Export/ExportRegistery.ts b/packages/server/src/services/Export/ExportRegistery.ts new file mode 100644 index 000000000..33271f4ec --- /dev/null +++ b/packages/server/src/services/Export/ExportRegistery.ts @@ -0,0 +1,49 @@ +import { camelCase, upperFirst } from 'lodash'; +import { Exportable } from './Exportable'; + +export class ExportableRegistry { + private static instance: ExportableRegistry; + private exportables: Record; + + /** + * Constructor method. + */ + constructor() { + this.exportables = {}; + } + + /** + * Gets singleton instance of registry. + * @returns {ExportableRegistry} + */ + public static getInstance(): ExportableRegistry { + if (!ExportableRegistry.instance) { + ExportableRegistry.instance = new ExportableRegistry(); + } + return ExportableRegistry.instance; + } + + /** + * Registers the given importable service. + * @param {string} resource + * @param {Exportable} importable + */ + public registerExportable(resource: string, importable: Exportable): void { + const _resource = this.sanitizeResourceName(resource); + this.exportables[_resource] = importable; + } + + /** + * Retrieves the importable service instance of the given resource name. + * @param {string} name + * @returns {Exportable} + */ + public getExportable(name: string): Exportable { + const _name = this.sanitizeResourceName(name); + return this.exportables[_name]; + } + + private sanitizeResourceName(resource: string) { + return upperFirst(camelCase(resource)); + } +} diff --git a/packages/server/src/services/Export/ExportResources.ts b/packages/server/src/services/Export/ExportResources.ts new file mode 100644 index 000000000..b252bfab1 --- /dev/null +++ b/packages/server/src/services/Export/ExportResources.ts @@ -0,0 +1,72 @@ +import Container, { Service } from 'typedi'; +import { AccountsExportable } from '../Accounts/AccountsExportable'; +import { ExportableRegistry } from './ExportRegistery'; +import { ItemsExportable } from '../Items/ItemsExportable'; +import { CustomersExportable } from '../Contacts/Customers/CustomersExportable'; +import { VendorsExportable } from '../Contacts/Vendors/VendorsExportable'; +import { ExpensesExportable } from '../Expenses/ExpensesExportable'; +import { SaleInvoicesExportable } from '../Sales/Invoices/SaleInvoicesExportable'; +import { SaleEstimatesExportable } from '../Sales/Estimates/SaleEstimatesExportable'; +import { SaleReceiptsExportable } from '../Sales/Receipts/SaleReceiptsExportable'; +import { BillsExportable } from '../Purchases/Bills/BillsExportable'; +import { PaymentsReceivedExportable } from '../Sales/PaymentReceives/PaymentsReceivedExportable'; +import { BillPaymentExportable } from '../Purchases/BillPayments/BillPaymentExportable'; +import { ManualJournalsExportable } from '../ManualJournals/ManualJournalExportable'; +import { CreditNotesExportable } from '../CreditNotes/CreditNotesExportable'; +import { VendorCreditsExportable } from '../Purchases/VendorCredits/VendorCreditsExportable'; +import { ItemCategoriesExportable } from '../ItemCategories/ItemCategoriesExportable'; + +@Service() +export class ExportableResources { + private static registry: ExportableRegistry; + + /** + * Consttuctor method. + */ + constructor() { + this.boot(); + } + + /** + * Importable instances. + */ + private importables = [ + { resource: 'Account', exportable: AccountsExportable }, + { resource: 'Item', exportable: ItemsExportable }, + { resource: 'ItemCategory', exportable: ItemCategoriesExportable }, + { resource: 'Customer', exportable: CustomersExportable }, + { resource: 'Vendor', exportable: VendorsExportable }, + { resource: 'Expense', exportable: ExpensesExportable }, + { resource: 'SaleInvoice', exportable: SaleInvoicesExportable }, + { resource: 'SaleEstimate', exportable: SaleEstimatesExportable }, + { resource: 'SaleReceipt', exportable: SaleReceiptsExportable }, + { resource: 'Bill', exportable: BillsExportable }, + { resource: 'PaymentReceive', exportable: PaymentsReceivedExportable }, + { resource: 'BillPayment', exportable: BillPaymentExportable }, + { resource: 'ManualJournal', exportable: ManualJournalsExportable }, + { resource: 'CreditNote', exportable: CreditNotesExportable }, + { resource: 'VendorCredit', exportable: VendorCreditsExportable }, + ]; + + /** + * + */ + public get registry() { + return ExportableResources.registry; + } + + /** + * Boots all the registered importables. + */ + public boot() { + if (!ExportableResources.registry) { + const instance = ExportableRegistry.getInstance(); + + this.importables.forEach((importable) => { + const importableInstance = Container.get(importable.exportable); + instance.registerExportable(importable.resource, importableInstance); + }); + ExportableResources.registry = instance; + } + } +} diff --git a/packages/server/src/services/Export/ExportService.ts b/packages/server/src/services/Export/ExportService.ts new file mode 100644 index 000000000..c9c3dc432 --- /dev/null +++ b/packages/server/src/services/Export/ExportService.ts @@ -0,0 +1,161 @@ +import { Inject, Service } from 'typedi'; +import xlsx from 'xlsx'; +import * as R from 'ramda'; +import { get } from 'lodash'; +import { sanitizeResourceName } from '../Import/_utils'; +import ResourceService from '../Resource/ResourceService'; +import { ExportableResources } from './ExportResources'; +import { ServiceError } from '@/exceptions'; +import { Errors } from './common'; +import { IModelMeta, IModelMetaColumn } from '@/interfaces'; +import { flatDataCollections, getDataAccessor } from './utils'; + +@Service() +export class ExportResourceService { + @Inject() + private resourceService: ResourceService; + + @Inject() + private exportableResources: ExportableResources; + + /** + * Exports the given resource data through csv, xlsx or pdf. + * @param {number} tenantId - Tenant id. + * @param {string} resourceName - Resource name. + * @param {string} format - File format. + */ + public async export(tenantId: number, resourceName: string, format: string = 'csv') { + const resource = sanitizeResourceName(resourceName); + const resourceMeta = this.getResourceMeta(tenantId, resource); + + this.validateResourceMeta(resourceMeta); + + const data = await this.getExportableData(tenantId, resource); + const transformed = this.transformExportedData(tenantId, resource, data); + const exportableColumns = this.getExportableColumns(resourceMeta); + const workbook = this.createWorkbook(transformed, exportableColumns); + + return this.exportWorkbook(workbook, format); + } + + /** + * Retrieves metadata for a specific resource. + * @param {number} tenantId - The tenant identifier. + * @param {string} resource - The name of the resource. + * @returns The metadata of the resource. + */ + private getResourceMeta(tenantId: number, resource: string) { + return this.resourceService.getResourceMeta(tenantId, resource); + } + + /** + * Validates if the resource metadata is exportable. + * @param {any} resourceMeta - The metadata of the resource. + * @throws {ServiceError} If the resource is not exportable or lacks columns. + */ + private validateResourceMeta(resourceMeta: any) { + if (!resourceMeta.exportable || !resourceMeta.columns) { + throw new ServiceError(Errors.RESOURCE_NOT_EXPORTABLE); + } + } + + /** + * Transforms the exported data based on the resource metadata. + * If the resource metadata specifies a flattening attribute (`exportFlattenOn`), + * the data will be flattened based on this attribute using the `flatDataCollections` utility function. + * + * @param {number} tenantId - The tenant identifier. + * @param {string} resource - The name of the resource. + * @param {Array>} data - The original data to be transformed. + * @returns {Array>} - The transformed data. + */ + private transformExportedData( + tenantId: number, + resource: string, + data: Array> + ): Array> { + const resourceMeta = this.getResourceMeta(tenantId, resource); + + return R.when>, Array>>( + R.always(Boolean(resourceMeta.exportFlattenOn)), + (data) => flatDataCollections(data, resourceMeta.exportFlattenOn), + data + ); + } + /** + * Fetches exportable data for a given resource. + * @param {number} tenantId - The tenant identifier. + * @param {string} resource - The name of the resource. + * @returns A promise that resolves to the exportable data. + */ + private async getExportableData(tenantId: number, resource: string) { + const exportable = + this.exportableResources.registry.getExportable(resource); + return exportable.exportable(tenantId, {}); + } + + /** + * Extracts columns that are marked as exportable from the resource metadata. + * @param {IModelMeta} resourceMeta - The metadata of the resource. + * @returns An array of exportable columns. + */ + private getExportableColumns(resourceMeta: IModelMeta) { + const processColumns = ( + columns: { [key: string]: IModelMetaColumn }, + parent = '' + ) => { + return Object.entries(columns) + .filter(([_, value]) => value.exportable !== false) + .flatMap(([key, value]) => { + if (value.type === 'collection' && value.collectionOf === 'object') { + return processColumns(value.columns, key); + } else { + const group = parent; + return [ + { + name: value.name, + type: value.type || 'text', + accessor: value.accessor || key, + group, + }, + ]; + } + }); + }; + return processColumns(resourceMeta.columns); + } + + /** + * Creates a workbook from the provided data and columns. + * @param {any[]} data - The data to be included in the workbook. + * @param {any[]} exportableColumns - The columns to be included in the workbook. + * @returns The created workbook. + */ + private createWorkbook(data: any[], exportableColumns: any[]) { + const workbook = xlsx.utils.book_new(); + const worksheetData = data.map((item) => + exportableColumns.map((col) => get(item, getDataAccessor(col))) + ); + + worksheetData.unshift(exportableColumns.map((col) => col.name)); + + const worksheet = xlsx.utils.aoa_to_sheet(worksheetData); + xlsx.utils.book_append_sheet(workbook, worksheet, 'Exported Data'); + + return workbook; + } + + /** + * Exports the workbook in the specified format. + * @param {any} workbook - The workbook to be exported. + * @param {string} format - The format to export the workbook in. + * @returns The exported workbook data. + */ + private exportWorkbook(workbook: any, format: string) { + if (format.toLowerCase() === 'csv') { + return xlsx.write(workbook, { type: 'buffer', bookType: 'csv' }); + } else if (format.toLowerCase() === 'xlsx') { + return xlsx.write(workbook, { type: 'buffer', bookType: 'xlsx' }); + } + } +} diff --git a/packages/server/src/services/Export/Exportable.ts b/packages/server/src/services/Export/Exportable.ts new file mode 100644 index 000000000..0e8801678 --- /dev/null +++ b/packages/server/src/services/Export/Exportable.ts @@ -0,0 +1,22 @@ +export class Exportable { + /** + * + * @param tenantId + * @returns + */ + public async exportable( + tenantId: number, + query: Record + ): Promise>> { + return []; + } + + /** + * + * @param data + * @returns + */ + public transform(data: Record) { + return data; + } +} diff --git a/packages/server/src/services/Export/common.ts b/packages/server/src/services/Export/common.ts new file mode 100644 index 000000000..5895e3367 --- /dev/null +++ b/packages/server/src/services/Export/common.ts @@ -0,0 +1,3 @@ +export enum Errors { + RESOURCE_NOT_EXPORTABLE = 'RESOURCE_NOT_EXPORTABLE', +} diff --git a/packages/server/src/services/Export/utils.ts b/packages/server/src/services/Export/utils.ts new file mode 100644 index 000000000..e1436d8ab --- /dev/null +++ b/packages/server/src/services/Export/utils.ts @@ -0,0 +1,27 @@ +import { flatMap } from 'lodash'; +/** + * Flattens the data based on a specified attribute. + * @param data - The data to be flattened. + * @param flattenAttr - The attribute to be flattened. + * @returns - The flattened data. + */ +export const flatDataCollections = ( + data: Record, + flattenAttr: string +): Record[] => { + return flatMap(data, (item) => + item[flattenAttr].map((entry) => ({ + ...item, + [flattenAttr]: entry, + })) + ); +}; + +/** + * Gets the data accessor for a given column. + * @param col - The column to get the data accessor for. + * @returns - The data accessor. + */ +export const getDataAccessor = (col: any) => { + return col.group ? `${col.group}.${col.accessor}` : col.accessor; +}; diff --git a/packages/server/src/services/ItemCategories/ItemCategoriesExportable.ts b/packages/server/src/services/ItemCategories/ItemCategoriesExportable.ts new file mode 100644 index 000000000..0cc8142ce --- /dev/null +++ b/packages/server/src/services/ItemCategories/ItemCategoriesExportable.ts @@ -0,0 +1,29 @@ +import { Inject, Service } from 'typedi'; +import { Exportable } from '../Export/Exportable'; +import { IAccountsFilter, IAccountsStructureType } from '@/interfaces'; +import ItemCategoriesService from './ItemCategoriesService'; + +@Service() +export class ItemCategoriesExportable extends Exportable { + @Inject() + private itemCategoriesApplication: ItemCategoriesService; + + /** + * Retrieves the accounts data to exportable sheet. + * @param {number} tenantId + * @returns + */ + public exportable(tenantId: number, query: IAccountsFilter) { + const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', + inactiveMode: false, + ...query, + structure: IAccountsStructureType.Flat, + } as IAccountsFilter; + + return this.itemCategoriesApplication + .getItemCategoriesList(tenantId, parsedQuery, {}) + .then((output) => output.itemCategories); + } +} diff --git a/packages/server/src/services/Items/ItemsExportable.ts b/packages/server/src/services/Items/ItemsExportable.ts new file mode 100644 index 000000000..223b403d9 --- /dev/null +++ b/packages/server/src/services/Items/ItemsExportable.ts @@ -0,0 +1,29 @@ +import { Inject, Service } from 'typedi'; +import { Exportable } from '../Export/Exportable'; +import { IItemsFilter } from '@/interfaces'; +import { ItemsApplication } from './ItemsApplication'; + +@Service() +export class ItemsExportable extends Exportable { + @Inject() + private itemsApplication: ItemsApplication; + + /** + * Retrieves the accounts data to exportable sheet. + * @param {number} tenantId + * @returns + */ + public exportable(tenantId: number, query: IItemsFilter) { + const parsedQuery = { + sortOrder: 'DESC', + columnSortBy: 'created_at', + page: 1, + ...query, + pageSize: 12, + } as IItemsFilter; + + return this.itemsApplication + .getItems(tenantId, parsedQuery) + .then((output) => output.items); + } +} diff --git a/packages/server/src/services/ManualJournals/GetManualJournals.ts b/packages/server/src/services/ManualJournals/GetManualJournals.ts index b6bca8848..d4dd35f4d 100644 --- a/packages/server/src/services/ManualJournals/GetManualJournals.ts +++ b/packages/server/src/services/ManualJournals/GetManualJournals.ts @@ -39,7 +39,7 @@ export class GetManualJournals { tenantId: number, filterDTO: IManualJournalsFilter ): Promise<{ - manualJournals: IManualJournal; + manualJournals: IManualJournal[]; pagination: IPaginationMeta; filterMeta: IFilterMeta; }> => { diff --git a/packages/server/src/services/ManualJournals/ManualJournalExportable.ts b/packages/server/src/services/ManualJournals/ManualJournalExportable.ts new file mode 100644 index 000000000..10d16e992 --- /dev/null +++ b/packages/server/src/services/ManualJournals/ManualJournalExportable.ts @@ -0,0 +1,29 @@ +import { Inject, Service } from 'typedi'; +import { IManualJournalsFilter } from '@/interfaces'; +import { Exportable } from '../Export/Exportable'; +import { ManualJournalsApplication } from './ManualJournalsApplication'; + +@Service() +export class ManualJournalsExportable extends Exportable { + @Inject() + private manualJournalsApplication: ManualJournalsApplication; + + /** + * Retrieves the manual journals data to exportable sheet. + * @param {number} tenantId + * @returns + */ + public exportable(tenantId: number, query: IManualJournalsFilter) { + const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', + ...query, + page: 1, + pageSize: 12000, + } as IManualJournalsFilter; + + return this.manualJournalsApplication + .getManualJournals(tenantId, parsedQuery) + .then((output) => output.manualJournals); + } +} diff --git a/packages/server/src/services/Purchases/BillPayments/BillPaymentExportable.ts b/packages/server/src/services/Purchases/BillPayments/BillPaymentExportable.ts new file mode 100644 index 000000000..8c2a89b48 --- /dev/null +++ b/packages/server/src/services/Purchases/BillPayments/BillPaymentExportable.ts @@ -0,0 +1,28 @@ +import { Inject, Service } from 'typedi'; +import { Exportable } from '@/services/Export/Exportable'; +import { BillPaymentsApplication } from './BillPaymentsApplication'; + +@Service() +export class BillPaymentExportable extends Exportable { + @Inject() + private billPaymentsApplication: BillPaymentsApplication; + + /** + * Retrieves the accounts data to exportable sheet. + * @param {number} tenantId + * @returns + */ + public exportable(tenantId: number, query: any) { + const parsedQuery = { + page: 1, + pageSize: 12, + ...query, + sortOrder: 'desc', + columnSortBy: 'created_at', + } as any; + + return this.billPaymentsApplication + .getBillPayments(tenantId, parsedQuery) + .then((output) => output.billPayments); + } +} diff --git a/packages/server/src/services/Purchases/BillPayments/GetBillPayments.ts b/packages/server/src/services/Purchases/BillPayments/GetBillPayments.ts index 2c9fda01f..9c4464403 100644 --- a/packages/server/src/services/Purchases/BillPayments/GetBillPayments.ts +++ b/packages/server/src/services/Purchases/BillPayments/GetBillPayments.ts @@ -31,7 +31,7 @@ export class GetBillPayments { tenantId: number, filterDTO: IBillPaymentsFilter ): Promise<{ - billPayments: IBillPayment; + billPayments: IBillPayment[]; pagination: IPaginationMeta; filterMeta: IFilterMeta; }> { diff --git a/packages/server/src/services/Purchases/Bills/BillsApplication.ts b/packages/server/src/services/Purchases/Bills/BillsApplication.ts index 1ffdd5aa4..2593b03b2 100644 --- a/packages/server/src/services/Purchases/Bills/BillsApplication.ts +++ b/packages/server/src/services/Purchases/Bills/BillsApplication.ts @@ -99,7 +99,7 @@ export class BillsApplication { tenantId: number, filterDTO: IBillsFilter ): Promise<{ - bills: IBill; + bills: IBill[]; pagination: IPaginationMeta; filterMeta: IFilterMeta; }> { diff --git a/packages/server/src/services/Purchases/Bills/BillsExportable.ts b/packages/server/src/services/Purchases/Bills/BillsExportable.ts new file mode 100644 index 000000000..a45783643 --- /dev/null +++ b/packages/server/src/services/Purchases/Bills/BillsExportable.ts @@ -0,0 +1,29 @@ +import { Inject, Service } from 'typedi'; +import { IBillsFilter } from '@/interfaces'; +import { Exportable } from '@/services/Export/Exportable'; +import { BillsApplication } from './BillsApplication'; + +@Service() +export class BillsExportable extends Exportable { + @Inject() + private billsApplication: BillsApplication; + + /** + * Retrieves the accounts data to exportable sheet. + * @param {number} tenantId + * @returns + */ + public exportable(tenantId: number, query: IBillsFilter) { + const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', + ...query, + page: 1, + pageSize: 12000, + } as IBillsFilter; + + return this.billsApplication + .getBills(tenantId, parsedQuery) + .then((output) => output.bills); + } +} diff --git a/packages/server/src/services/Purchases/Bills/GetBills.ts b/packages/server/src/services/Purchases/Bills/GetBills.ts index 1ea19797d..73abc55da 100644 --- a/packages/server/src/services/Purchases/Bills/GetBills.ts +++ b/packages/server/src/services/Purchases/Bills/GetBills.ts @@ -49,6 +49,7 @@ export class GetBills { const { results, pagination } = await Bill.query() .onBuild((builder) => { builder.withGraphFetched('vendor'); + builder.withGraphFetched('entries.item'); dynamicFilter.buildQuery()(builder); }) .pagination(filter.page - 1, filter.pageSize); diff --git a/packages/server/src/services/Purchases/VendorCredits/VendorCreditTransformer.ts b/packages/server/src/services/Purchases/VendorCredits/VendorCreditTransformer.ts index be1431ac2..282b6f08e 100644 --- a/packages/server/src/services/Purchases/VendorCredits/VendorCreditTransformer.ts +++ b/packages/server/src/services/Purchases/VendorCredits/VendorCreditTransformer.ts @@ -14,6 +14,7 @@ export class VendorCreditTransformer extends Transformer { 'formattedSubtotal', 'formattedVendorCreditDate', 'formattedCreditsRemaining', + 'formattedInvoicedAmount', 'entries', ]; }; @@ -58,6 +59,17 @@ export class VendorCreditTransformer extends Transformer { }); }; + /** + * Retrieves the formatted invoiced amount. + * @param credit + * @returns {string} + */ + protected formattedInvoicedAmount = (credit) => { + return formatNumber(credit.invoicedAmount, { + currencyCode: credit.currencyCode, + }); + }; + /** * Retrieves the entries of the bill. * @param {IVendorCredit} vendorCredit diff --git a/packages/server/src/services/Purchases/VendorCredits/VendorCreditsExportable.ts b/packages/server/src/services/Purchases/VendorCredits/VendorCreditsExportable.ts new file mode 100644 index 000000000..4e0963165 --- /dev/null +++ b/packages/server/src/services/Purchases/VendorCredits/VendorCreditsExportable.ts @@ -0,0 +1,30 @@ +import { Inject, Service } from 'typedi'; +import { IVendorCreditsQueryDTO } from '@/interfaces'; +import ListVendorCredits from './ListVendorCredits'; +import { Exportable } from '@/services/Export/Exportable'; + +@Service() +export class VendorCreditsExportable extends Exportable { + @Inject() + private getVendorCredits: ListVendorCredits; + + /** + * Retrieves the vendor credits data to exportable sheet. + * @param {number} tenantId - + * @param {IVendorCreditsQueryDTO} query - + * @returns {} + */ + public exportable(tenantId: number, query: IVendorCreditsQueryDTO) { + const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', + ...query, + page: 1, + pageSize: 12000, + } as IVendorCreditsQueryDTO; + + return this.getVendorCredits + .getVendorCredits(tenantId, parsedQuery) + .then((output) => output.vendorCredits); + } +} diff --git a/packages/server/src/services/Resource/ResourceService.ts b/packages/server/src/services/Resource/ResourceService.ts index 653599d05..3e0ffbafe 100644 --- a/packages/server/src/services/Resource/ResourceService.ts +++ b/packages/server/src/services/Resource/ResourceService.ts @@ -105,7 +105,11 @@ export default class ResourceService { const $enumerationType = (field) => field.fieldType === 'enumeration' ? field : undefined; - const $hasFields = (field) => 'undefined' !== typeof field.fields ? field : undefined; + const $hasFields = (field) => + 'undefined' !== typeof field.fields ? field : undefined; + + const $hasColumns = (column) => + 'undefined' !== typeof column.columns ? column : undefined; const naviagations = [ ['fields', qim.$each, 'name'], @@ -113,6 +117,8 @@ export default class ResourceService { ['fields2', qim.$each, 'name'], ['fields2', qim.$each, $enumerationType, 'options', qim.$each, 'label'], ['fields2', qim.$each, $hasFields, 'fields', qim.$each, 'name'], + ['columns', qim.$each, 'name'], + ['columns', qim.$each, $hasColumns, 'columns', qim.$each, 'name'], ]; return this.i18nService.i18nApply(naviagations, meta, tenantId); } diff --git a/packages/server/src/services/Sales/Estimates/GetSaleEstimates.ts b/packages/server/src/services/Sales/Estimates/GetSaleEstimates.ts index e0b53adb3..bba1db943 100644 --- a/packages/server/src/services/Sales/Estimates/GetSaleEstimates.ts +++ b/packages/server/src/services/Sales/Estimates/GetSaleEstimates.ts @@ -51,6 +51,7 @@ export class GetSaleEstimates { .onBuild((builder) => { builder.withGraphFetched('customer'); builder.withGraphFetched('entries'); + builder.withGraphFetched('entries.item'); dynamicFilter.buildQuery()(builder); }) .pagination(filter.page - 1, filter.pageSize); diff --git a/packages/server/src/services/Sales/Estimates/SaleEstimatesExportable.ts b/packages/server/src/services/Sales/Estimates/SaleEstimatesExportable.ts new file mode 100644 index 000000000..455805df9 --- /dev/null +++ b/packages/server/src/services/Sales/Estimates/SaleEstimatesExportable.ts @@ -0,0 +1,29 @@ +import { Inject, Service } from 'typedi'; +import { ISalesInvoicesFilter } from '@/interfaces'; +import { Exportable } from '@/services/Export/Exportable'; +import { SaleEstimatesApplication } from './SaleEstimatesApplication'; + +@Service() +export class SaleEstimatesExportable extends Exportable { + @Inject() + private saleEstimatesApplication: SaleEstimatesApplication; + + /** + * Retrieves the accounts data to exportable sheet. + * @param {number} tenantId + * @returns + */ + public exportable(tenantId: number, query: ISalesInvoicesFilter) { + const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', + ...query, + page: 1, + pageSize: 12000, + } as ISalesInvoicesFilter; + + return this.saleEstimatesApplication + .getSaleEstimates(tenantId, parsedQuery) + .then((output) => output.salesEstimates); + } +} diff --git a/packages/server/src/services/Sales/Invoices/GetSaleInvoices.ts b/packages/server/src/services/Sales/Invoices/GetSaleInvoices.ts index b1d9b93db..569aceccb 100644 --- a/packages/server/src/services/Sales/Invoices/GetSaleInvoices.ts +++ b/packages/server/src/services/Sales/Invoices/GetSaleInvoices.ts @@ -49,7 +49,7 @@ export class GetSaleInvoices { ); const { results, pagination } = await SaleInvoice.query() .onBuild((builder) => { - builder.withGraphFetched('entries'); + builder.withGraphFetched('entries.item'); builder.withGraphFetched('customer'); dynamicFilter.buildQuery()(builder); }) diff --git a/packages/server/src/services/Sales/Invoices/SaleInvoicesExportable.ts b/packages/server/src/services/Sales/Invoices/SaleInvoicesExportable.ts new file mode 100644 index 000000000..e806b0939 --- /dev/null +++ b/packages/server/src/services/Sales/Invoices/SaleInvoicesExportable.ts @@ -0,0 +1,29 @@ +import { Inject, Service } from 'typedi'; +import { ISalesInvoicesFilter } from '@/interfaces'; +import { SaleInvoiceApplication } from './SaleInvoicesApplication'; +import { Exportable } from '@/services/Export/Exportable'; + +@Service() +export class SaleInvoicesExportable extends Exportable { + @Inject() + private saleInvoicesApplication: SaleInvoiceApplication; + + /** + * Retrieves the accounts data to exportable sheet. + * @param {number} tenantId + * @returns + */ + public exportable(tenantId: number, query: ISalesInvoicesFilter) { + const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', + ...query, + page: 1, + pageSize: 120000, + } as ISalesInvoicesFilter; + + return this.saleInvoicesApplication + .getSaleInvoices(tenantId, parsedQuery) + .then((output) => output.salesInvoices); + } +} diff --git a/packages/server/src/services/Sales/PaymentReceives/PaymentsReceivedExportable.ts b/packages/server/src/services/Sales/PaymentReceives/PaymentsReceivedExportable.ts new file mode 100644 index 000000000..932c0f5f3 --- /dev/null +++ b/packages/server/src/services/Sales/PaymentReceives/PaymentsReceivedExportable.ts @@ -0,0 +1,30 @@ +import { Inject, Service } from 'typedi'; +import { IAccountsStructureType, IPaymentReceivesFilter } from '@/interfaces'; +import { Exportable } from '@/services/Export/Exportable'; +import { PaymentReceivesApplication } from './PaymentReceivesApplication'; + +@Service() +export class PaymentsReceivedExportable extends Exportable { + @Inject() + private paymentReceivedApp: PaymentReceivesApplication; + + /** + * Retrieves the accounts data to exportable sheet. + * @param {number} tenantId + * @param {IPaymentReceivesFilter} query - + * @returns + */ + public exportable(tenantId: number, query: IPaymentReceivesFilter) { + const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', + inactiveMode: false, + ...query, + structure: IAccountsStructureType.Flat, + } as IPaymentReceivesFilter; + + return this.paymentReceivedApp + .getPaymentReceives(tenantId, parsedQuery) + .then((output) => output.paymentReceives); + } +} diff --git a/packages/server/src/services/Sales/Receipts/GetSaleReceipts.ts b/packages/server/src/services/Sales/Receipts/GetSaleReceipts.ts index 24150e349..1916c5e75 100644 --- a/packages/server/src/services/Sales/Receipts/GetSaleReceipts.ts +++ b/packages/server/src/services/Sales/Receipts/GetSaleReceipts.ts @@ -11,6 +11,9 @@ import { SaleReceiptTransformer } from './SaleReceiptTransformer'; import { TransformerInjectable } from '@/lib/Transformer/TransformerInjectable'; import DynamicListingService from '@/services/DynamicListing/DynamicListService'; +interface GetSaleReceiptsSettings { + fetchEntriesGraph?: boolean; +} @Service() export class GetSaleReceipts { @Inject() @@ -50,7 +53,7 @@ export class GetSaleReceipts { .onBuild((builder) => { builder.withGraphFetched('depositAccount'); builder.withGraphFetched('customer'); - builder.withGraphFetched('entries'); + builder.withGraphFetched('entries.item'); dynamicFilter.buildQuery()(builder); }) diff --git a/packages/server/src/services/Sales/Receipts/SaleReceiptsExportable.ts b/packages/server/src/services/Sales/Receipts/SaleReceiptsExportable.ts new file mode 100644 index 000000000..199c2e5a8 --- /dev/null +++ b/packages/server/src/services/Sales/Receipts/SaleReceiptsExportable.ts @@ -0,0 +1,29 @@ +import { Inject, Service } from 'typedi'; +import { ISalesReceiptsFilter } from '@/interfaces'; +import { Exportable } from '@/services/Export/Exportable'; +import { SaleReceiptApplication } from './SaleReceiptApplication'; + +@Service() +export class SaleReceiptsExportable extends Exportable { + @Inject() + private saleReceiptsApp: SaleReceiptApplication; + + /** + * Retrieves the accounts data to exportable sheet. + * @param {number} tenantId + * @returns + */ + public exportable(tenantId: number, query: ISalesReceiptsFilter) { + const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', + ...query, + page: 1, + pageSize: 12, + } as ISalesReceiptsFilter; + + return this.saleReceiptsApp + .getSaleReceipts(tenantId, parsedQuery) + .then((output) => output.data); + } +} diff --git a/packages/server/src/subscribers/events.ts b/packages/server/src/subscribers/events.ts index 0243cd172..b96cadf95 100644 --- a/packages/server/src/subscribers/events.ts +++ b/packages/server/src/subscribers/events.ts @@ -9,6 +9,9 @@ export default { signUp: 'onSignUp', signingUp: 'onSigningUp', + signUpConfirming: 'signUpConfirming', + signUpConfirmed: 'signUpConfirmed', + sendingResetPassword: 'onSendingResetPassword', sendResetPassword: 'onSendResetPassword', diff --git a/packages/server/src/system/migrations/20240425100821_add_confirmation_columns_to_users.js b/packages/server/src/system/migrations/20240425100821_add_confirmation_columns_to_users.js new file mode 100644 index 000000000..fada1380f --- /dev/null +++ b/packages/server/src/system/migrations/20240425100821_add_confirmation_columns_to_users.js @@ -0,0 +1,12 @@ +exports.up = function (knex) { + return knex.schema + .table('users', (table) => { + table.string('verify_token'); + table.boolean('verified').defaultTo(false); + }) + .then(() => { + return knex('USERS').update({ verified: true }); + }); +}; + +exports.down = (knex) => {}; diff --git a/packages/server/src/system/models/SystemUser.ts b/packages/server/src/system/models/SystemUser.ts index a341ccedf..ce17186df 100644 --- a/packages/server/src/system/models/SystemUser.ts +++ b/packages/server/src/system/models/SystemUser.ts @@ -4,6 +4,12 @@ import SystemModel from '@/system/models/SystemModel'; import SoftDeleteQueryBuilder from '@/collection/SoftDeleteQueryBuilder'; export default class SystemUser extends SystemModel { + firstName!: string; + lastName!: string; + verified!: boolean; + inviteAcceptedAt!: Date | null; + deletedAt!: Date | null; + /** * Table name. */ @@ -29,23 +35,33 @@ export default class SystemUser extends SystemModel { * Virtual attributes. */ static get virtualAttributes() { - return ['fullName', 'isDeleted', 'isInviteAccepted']; + return ['fullName', 'isDeleted', 'isInviteAccepted', 'isVerified']; } /** - * + * Detarmines whether the user is deleted. + * @returns {boolean} */ get isDeleted() { return !!this.deletedAt; } /** - * + * Detarmines whether the sent invite is accepted. + * @returns {boolean} */ get isInviteAccepted() { return !!this.inviteAcceptedAt; } + /** + * Detarmines whether the user's email is verified. + * @returns {boolean} + */ + get isVerified() { + return !!this.verified; + } + /** * Full name attribute. */ diff --git a/packages/server/src/utils/index.ts b/packages/server/src/utils/index.ts index 919cd7af7..ceef5f572 100644 --- a/packages/server/src/utils/index.ts +++ b/packages/server/src/utils/index.ts @@ -1,6 +1,6 @@ import bcrypt from 'bcryptjs'; import moment from 'moment'; -import _ from 'lodash'; +import _, { isEmpty } from 'lodash'; import path from 'path'; import * as R from 'ramda'; @@ -329,7 +329,7 @@ const booleanValuesRepresentingTrue: string[] = ['true', '1']; const booleanValuesRepresentingFalse: string[] = ['false', '0']; const normalizeValue = (value: any): string => - value.toString().trim().toLowerCase(); + value?.toString().trim().toLowerCase(); const booleanValues: string[] = [ ...booleanValuesRepresentingTrue, @@ -338,7 +338,7 @@ const booleanValues: string[] = [ export const parseBoolean = (value: any, defaultValue: T): T | boolean => { const normalizedValue = normalizeValue(value); - if (booleanValues.indexOf(normalizedValue) === -1) { + if (isEmpty(value) || booleanValues.indexOf(normalizedValue) === -1) { return defaultValue; } return booleanValuesRepresentingTrue.indexOf(normalizedValue) !== -1; diff --git a/packages/server/views/mail/SignupVerifyEmail.html b/packages/server/views/mail/SignupVerifyEmail.html new file mode 100644 index 000000000..637e78c12 --- /dev/null +++ b/packages/server/views/mail/SignupVerifyEmail.html @@ -0,0 +1,424 @@ + + + + + + Bigcapital | Reset your password + + + + Verify your email. + + + + + + + + + diff --git a/packages/webapp/Dockerfile b/packages/webapp/Dockerfile index 41054a525..72db05769 100644 --- a/packages/webapp/Dockerfile +++ b/packages/webapp/Dockerfile @@ -7,8 +7,8 @@ WORKDIR /app # Copy application dependency manifests to the container image. COPY ./package*.json ./ COPY ./pnpm-lock.yaml ./pnpm-lock.yaml -COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml COPY ./lerna.json ./lerna.json +COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml COPY ./packages/webapp/package*.json ./packages/webapp/ # Install application dependencies @@ -24,7 +24,7 @@ RUN pnpm install # Build webapp package COPY ./packages/webapp /app/packages/webapp -RUN npm run build:webapp +RUN pnpm run build:webapp FROM nginx diff --git a/packages/webapp/src/components/App.tsx b/packages/webapp/src/components/App.tsx index 949a1861f..4f43a8613 100644 --- a/packages/webapp/src/components/App.tsx +++ b/packages/webapp/src/components/App.tsx @@ -9,13 +9,24 @@ import 'moment/locale/ar-ly'; import 'moment/locale/es-us'; import AppIntlLoader from './AppIntlLoader'; -import PrivateRoute from '@/components/Guards/PrivateRoute'; +import { EnsureAuthenticated } from '@/components/Guards/EnsureAuthenticated'; import GlobalErrors from '@/containers/GlobalErrors/GlobalErrors'; import DashboardPrivatePages from '@/components/Dashboard/PrivatePages'; import { Authentication } from '@/containers/Authentication/Authentication'; +import LazyLoader from '@/components/LazyLoader'; import { SplashScreen, DashboardThemeProvider } from '../components'; import { queryConfig } from '../hooks/query/base'; +import { EnsureUserEmailVerified } from './Guards/EnsureUserEmailVerified'; +import { EnsureAuthNotAuthenticated } from './Guards/EnsureAuthNotAuthenticated'; +import { EnsureUserEmailNotVerified } from './Guards/EnsureUserEmailNotVerified'; + +const EmailConfirmation = LazyLoader({ + loader: () => import('@/containers/Authentication/EmailConfirmation'), +}); +const RegisterVerify = LazyLoader({ + loader: () => import('@/containers/Authentication/RegisterVerify'), +}); /** * App inner. @@ -26,9 +37,30 @@ function AppInsider({ history }) { - + + + + + + + + + + + + + + + + + + - + + + + + diff --git a/packages/webapp/src/components/Dashboard/DashboardBoot.tsx b/packages/webapp/src/components/Dashboard/DashboardBoot.tsx index bae18273e..0d105c112 100644 --- a/packages/webapp/src/components/Dashboard/DashboardBoot.tsx +++ b/packages/webapp/src/components/Dashboard/DashboardBoot.tsx @@ -1,5 +1,5 @@ // @ts-nocheck -import React from 'react'; +import React, { useEffect } from 'react'; import { useAuthenticatedAccount, useCurrentOrganization, @@ -116,6 +116,14 @@ export function useApplicationBoot() { isBooted.current = true; }, ); + // Reset the loading states once the hook unmount. + useEffect( + () => () => { + isAuthUserLoading && !isBooted.current && stopLoading(); + isOrgLoading && !isBooted.current && stopLoading(); + }, + [isAuthUserLoading, isOrgLoading, stopLoading], + ); return { isLoading: isOrgLoading || isAuthUserLoading, diff --git a/packages/webapp/src/components/DialogsContainer.tsx b/packages/webapp/src/components/DialogsContainer.tsx index fc1195545..02f18d071 100644 --- a/packages/webapp/src/components/DialogsContainer.tsx +++ b/packages/webapp/src/components/DialogsContainer.tsx @@ -51,6 +51,7 @@ import EstimateMailDialog from '@/containers/Sales/Estimates/EstimateMailDialog/ import ReceiptMailDialog from '@/containers/Sales/Receipts/ReceiptMailDialog/ReceiptMailDialog'; import PaymentMailDialog from '@/containers/Sales/PaymentReceives/PaymentMailDialog/PaymentMailDialog'; import { ConnectBankDialog } from '@/containers/CashFlow/ConnectBankDialog'; +import { ExportDialog } from '@/containers/Dialogs/ExportDialog'; /** * Dialogs container. @@ -148,6 +149,8 @@ export default function DialogsContainer() { + + ); } diff --git a/packages/webapp/src/components/Guards/EnsureAuthNotAuthenticated.tsx b/packages/webapp/src/components/Guards/EnsureAuthNotAuthenticated.tsx new file mode 100644 index 000000000..97539a32d --- /dev/null +++ b/packages/webapp/src/components/Guards/EnsureAuthNotAuthenticated.tsx @@ -0,0 +1,22 @@ +// @ts-nocheck +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import { useIsAuthenticated } from '@/hooks/state'; + +interface EnsureAuthNotAuthenticatedProps { + children: React.ReactNode; + redirectTo?: string; +} + +export function EnsureAuthNotAuthenticated({ + children, + redirectTo = '/', +}: EnsureAuthNotAuthenticatedProps) { + const isAuthenticated = useIsAuthenticated(); + + return !isAuthenticated ? ( + <>{children} + ) : ( + + ); +} diff --git a/packages/webapp/src/components/Guards/EnsureAuthenticated.tsx b/packages/webapp/src/components/Guards/EnsureAuthenticated.tsx new file mode 100644 index 000000000..0a223a9ae --- /dev/null +++ b/packages/webapp/src/components/Guards/EnsureAuthenticated.tsx @@ -0,0 +1,22 @@ +// @ts-nocheck +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import { useIsAuthenticated } from '@/hooks/state'; + +interface EnsureAuthenticatedProps { + children: React.ReactNode; + redirectTo?: string; +} + +export function EnsureAuthenticated({ + children, + redirectTo = '/auth/login', +}: EnsureAuthenticatedProps) { + const isAuthenticated = useIsAuthenticated(); + + return isAuthenticated ? ( + <>{children} + ) : ( + + ); +} diff --git a/packages/webapp/src/components/Guards/EnsureUserEmailNotVerified.tsx b/packages/webapp/src/components/Guards/EnsureUserEmailNotVerified.tsx new file mode 100644 index 000000000..ac35b649f --- /dev/null +++ b/packages/webapp/src/components/Guards/EnsureUserEmailNotVerified.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import { useAuthUserVerified } from '@/hooks/state'; + +interface EnsureUserEmailNotVerifiedProps { + children: React.ReactNode; + redirectTo?: string; +} + +/** + * Higher Order Component to ensure that the user's email is not verified. + * If is verified, redirects to the inner setup page. + */ +export function EnsureUserEmailNotVerified({ + children, + redirectTo = '/', +}: EnsureUserEmailNotVerifiedProps) { + const isAuthVerified = useAuthUserVerified(); + + if (isAuthVerified) { + return ; + } + return <>{children}; +} diff --git a/packages/webapp/src/components/Guards/EnsureUserEmailVerified.tsx b/packages/webapp/src/components/Guards/EnsureUserEmailVerified.tsx new file mode 100644 index 000000000..f24d93533 --- /dev/null +++ b/packages/webapp/src/components/Guards/EnsureUserEmailVerified.tsx @@ -0,0 +1,24 @@ +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import { useAuthUserVerified } from '@/hooks/state'; + +interface EnsureUserEmailVerifiedProps { + children: React.ReactNode; + redirectTo?: string; +} + +/** + * Higher Order Component to ensure that the user's email is verified. + * If not verified, redirects to the email verification page. + */ +export function EnsureUserEmailVerified({ + children, + redirectTo = '/auth/register/verify', +}: EnsureUserEmailVerifiedProps) { + const isAuthVerified = useAuthUserVerified(); + + if (!isAuthVerified) { + return ; + } + return <>{children}; +} diff --git a/packages/webapp/src/components/Guards/PrivateRoute.tsx b/packages/webapp/src/components/Guards/PrivateRoute.tsx deleted file mode 100644 index 8e8e167b9..000000000 --- a/packages/webapp/src/components/Guards/PrivateRoute.tsx +++ /dev/null @@ -1,19 +0,0 @@ -// @ts-nocheck -import React from 'react'; -import BodyClassName from 'react-body-classname'; -import { Redirect } from 'react-router-dom'; -import { useIsAuthenticated } from '@/hooks/state'; - -export default function PrivateRoute({ component: Component, ...rest }) { - const isAuthenticated = useIsAuthenticated(); - - return ( - - {isAuthenticated ? ( - - ) : ( - - )} - - ); -} diff --git a/packages/webapp/src/constants/dialogs.ts b/packages/webapp/src/constants/dialogs.ts index 34d30bd46..cd425ce58 100644 --- a/packages/webapp/src/constants/dialogs.ts +++ b/packages/webapp/src/constants/dialogs.ts @@ -73,5 +73,6 @@ export enum DialogsName { CustomerTransactionsPdfPreview = 'CustomerTransactionsPdfPreview', VendorTransactionsPdfPreview = 'VendorTransactionsPdfPreview', GeneralLedgerPdfPreview = 'GeneralLedgerPdfPreview', - SalesTaxLiabilitySummaryPdfPreview = 'SalesTaxLiabilitySummaryPdfPreview' + SalesTaxLiabilitySummaryPdfPreview = 'SalesTaxLiabilitySummaryPdfPreview', + Export = 'Export', } diff --git a/packages/webapp/src/containers/Accounting/JournalsLanding/ManualJournalActionsBar.tsx b/packages/webapp/src/containers/Accounting/JournalsLanding/ManualJournalActionsBar.tsx index 676322da1..c6a8c447a 100644 --- a/packages/webapp/src/containers/Accounting/JournalsLanding/ManualJournalActionsBar.tsx +++ b/packages/webapp/src/containers/Accounting/JournalsLanding/ManualJournalActionsBar.tsx @@ -18,7 +18,7 @@ import { Can, If, DashboardActionViewsList, - DashboardActionsBar + DashboardActionsBar, } from '@/components'; import { useRefreshJournals } from '@/hooks/query/manualJournals'; import { useManualJournalsContext } from './ManualJournalsListProvider'; @@ -31,6 +31,7 @@ import withSettingsActions from '@/containers/Settings/withSettingsActions'; import withDialogActions from '@/containers/Dialog/withDialogActions'; import { compose } from '@/utils'; +import { DialogsName } from '@/constants/dialogs'; /** * Manual journal actions bar. @@ -47,6 +48,9 @@ function ManualJournalActionsBar({ // #withSettingsActions addSetting, + + // #withDialogActions + openDialog }) { // History context. const history = useHistory(); @@ -75,13 +79,18 @@ function ManualJournalActionsBar({ // Handle import button click. const handleImportBtnClick = () => { history.push('/manual-journals/import'); - } + }; // Handle table row size change. const handleTableRowSizeChange = (size) => { addSetting('manualJournals', 'tableSize', size); }; + // Handle the export button click. + const handleExportBtnClick = () => { + openDialog(DialogsName.Export, { resource: 'manual_journal' }); + }; + return ( @@ -140,6 +149,7 @@ function ManualJournalActionsBar({ className={Classes.MINIMAL} icon={} text={} + onClick={handleExportBtnClick} /> { history.push('/accounts/import'); }; + // Handle the export button click. + const handleExportBtnClick = () => { + openDialog(DialogsName.Export, { resource: 'account' }); + }; return ( @@ -182,17 +186,18 @@ function AccountsActionsBar({ icon={} text={} /> - + + + + + + + ); +} diff --git a/packages/webapp/src/containers/CashFlow/CashFlowAccounts/CashFlowAccountsActionsBar.tsx b/packages/webapp/src/containers/CashFlow/CashFlowAccounts/CashFlowAccountsActionsBar.tsx index 6d6c4bbbe..0515fdf8d 100644 --- a/packages/webapp/src/containers/CashFlow/CashFlowAccounts/CashFlowAccountsActionsBar.tsx +++ b/packages/webapp/src/containers/CashFlow/CashFlowAccounts/CashFlowAccountsActionsBar.tsx @@ -92,13 +92,13 @@ function CashFlowAccountsActionsBar({ /> + + + + + ); +} + +export const ExportDialogFormContent = compose(withDialogActions)( + ExportDialogFormContentRoot, +); diff --git a/packages/webapp/src/containers/Dialogs/ExportDialog/constants.ts b/packages/webapp/src/containers/Dialogs/ExportDialog/constants.ts new file mode 100644 index 000000000..b971a8433 --- /dev/null +++ b/packages/webapp/src/containers/Dialogs/ExportDialog/constants.ts @@ -0,0 +1,17 @@ +export const ExportResources = [ + { value: 'account', text: 'Accounts' }, + { value: 'item', text: 'Items' }, + { value: 'item_category', text: 'Item Categories' }, + { value: 'customer', text: 'Customers' }, + { value: 'vendor', text: 'Vendors' }, + { value: 'manual_journal', text: 'Manual Journal' }, + { value: 'expense', text: 'Expenses' }, + { value: 'sale_invoice', text: 'Invoices' }, + { value: 'sale_estimate', text: ' Estimates' }, + { value: 'sale_receipt', text: 'Receipts' }, + { value: 'payment_receive', text: 'Payments Received' }, + { value: 'credit_note', text: 'Credit Notes' }, + { value: 'bill', text: 'Bills' }, + { value: 'bill_payment', text: 'Bill Payments' }, + { value: 'vendor_credit', text: 'Vendor Credits' }, +]; diff --git a/packages/webapp/src/containers/Dialogs/ExportDialog/index.tsx b/packages/webapp/src/containers/Dialogs/ExportDialog/index.tsx new file mode 100644 index 000000000..2ca7e562a --- /dev/null +++ b/packages/webapp/src/containers/Dialogs/ExportDialog/index.tsx @@ -0,0 +1,31 @@ +// @ts-nocheck +import React, { lazy } from 'react'; +import { Dialog, DialogSuspense, FormattedMessage as T } from '@/components'; +import withDialogRedux from '@/components/DialogReduxConnect'; +import { compose } from '@/utils'; + +const ExportDialogContent = lazy(() => import('./ExportDialogContent')); + +// User form dialog. +function ExportDialogRoot({ dialogName, payload, isOpen }) { + const { resource = null, format = null } = payload; + + return ( + + + + + + ); +} + +export const ExportDialog = compose(withDialogRedux())(ExportDialogRoot); diff --git a/packages/webapp/src/containers/Dialogs/ExportDialog/type.ts b/packages/webapp/src/containers/Dialogs/ExportDialog/type.ts new file mode 100644 index 000000000..448c30cbc --- /dev/null +++ b/packages/webapp/src/containers/Dialogs/ExportDialog/type.ts @@ -0,0 +1,6 @@ + + +export interface ExportFormInitialValues { + resource?: string; + format?: string; +} \ No newline at end of file diff --git a/packages/webapp/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.tsx b/packages/webapp/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.tsx index e5c7368e4..1a1fb0209 100644 --- a/packages/webapp/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.tsx +++ b/packages/webapp/src/containers/Expenses/ExpensesLanding/ExpenseActionsBar.tsx @@ -33,6 +33,7 @@ import withDialogActions from '@/containers/Dialog/withDialogActions'; import withSettings from '@/containers/Settings/withSettings'; import { compose } from '@/utils'; +import { DialogsName } from '@/constants/dialogs'; /** * Expenses actions bar. @@ -49,6 +50,9 @@ function ExpensesActionsBar({ // #withSettingsActions addSetting, + + // #withDialogActions + openDialog, }) { // History context. const history = useHistory(); @@ -63,7 +67,6 @@ function ExpensesActionsBar({ const onClickNewExpense = () => { history.push('/expenses/new'); }; - // Handle delete button click. const handleBulkDelete = () => {}; @@ -73,21 +76,23 @@ function ExpensesActionsBar({ viewSlug: view ? view.slug : null, }); }; - // Handle click a refresh const handleRefreshBtnClick = () => { refresh(); }; - // Handle the import button click. const handleImportBtnClick = () => { history.push('/expenses/import'); - } - + }; // Handle table row size change. const handleTableRowSizeChange = (size) => { addSetting('expenses', 'tableSize', size); }; + // Handle the export button click. + const handleExportBtnClick = () => { + openDialog(DialogsName.Export, { resource: 'expense' }); + }; + return ( @@ -146,6 +151,7 @@ function ExpensesActionsBar({ className={Classes.MINIMAL} icon={} text={} + onClick={handleExportBtnClick} /> { + openDialog(DialogsName.Export, { resource: 'item' }); + } + return ( @@ -154,6 +164,7 @@ function ItemsActionsBar({ className={Classes.MINIMAL} icon={} text={} + onClick={handleExportBtnClick} /> { + openDialog(DialogsName.Export, { resource: 'item_category' }); + }; return ( @@ -105,6 +110,7 @@ function ItemsCategoryActionsBar({ className={Classes.MINIMAL} icon={} text={} + onClick={handleExportBtnClick} /> diff --git a/packages/webapp/src/containers/Purchases/Bills/BillsLanding/BillsActionsBar.tsx b/packages/webapp/src/containers/Purchases/Bills/BillsLanding/BillsActionsBar.tsx index da5415e45..3d0fbb2ff 100644 --- a/packages/webapp/src/containers/Purchases/Bills/BillsLanding/BillsActionsBar.tsx +++ b/packages/webapp/src/containers/Purchases/Bills/BillsLanding/BillsActionsBar.tsx @@ -32,6 +32,8 @@ import withSettingsActions from '@/containers/Settings/withSettingsActions'; import { useBillsListContext } from './BillsListProvider'; import { useRefreshBills } from '@/hooks/query/bills'; import { compose } from '@/utils'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { DialogsName } from '@/constants/dialogs'; /** * Bills actions bar. @@ -48,6 +50,9 @@ function BillActionsBar({ // #withSettingsActions addSetting, + + // #withDialogActions + openDialog, }) { const history = useHistory(); @@ -81,7 +86,12 @@ function BillActionsBar({ // Handle the import button click. const handleImportBtnClick = () => { history.push('/bills/import'); - } + }; + + // Handle the export button click. + const handleExportBtnClick = () => { + openDialog(DialogsName.Export, { resource: 'bill' }); + }; return ( @@ -141,6 +151,7 @@ function BillActionsBar({ className={Classes.MINIMAL} icon={} text={} + onClick={handleExportBtnClick} /> @@ -170,4 +181,5 @@ export default compose( withSettings(({ billsettings }) => ({ billsTableSize: billsettings?.tableSize, })), + withDialogActions, )(BillActionsBar); diff --git a/packages/webapp/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteActionsBar.tsx b/packages/webapp/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteActionsBar.tsx index e4a7b1ff9..7ba8e7531 100644 --- a/packages/webapp/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteActionsBar.tsx +++ b/packages/webapp/src/containers/Purchases/CreditNotes/CreditNotesLanding/VendorsCreditNoteActionsBar.tsx @@ -22,14 +22,16 @@ import { import { useVendorsCreditNoteListContext } from './VendorsCreditNoteListProvider'; import { VendorCreditAction, AbilitySubject } from '@/constants/abilityOption'; + +import withVendorsCreditNotesActions from './withVendorsCreditNotesActions'; import withSettings from '@/containers/Settings/withSettings'; import withSettingsActions from '@/containers/Settings/withSettingsActions'; import withVendorsCreditNotes from './withVendorsCreditNotes'; -import withVendorsCreditNotesActions from './withVendorsCreditNotesActions'; - +import withDialogActions from '@/containers/Dialog/withDialogActions'; import withVendorActions from './withVendorActions'; import { compose } from '@/utils'; +import { DialogsName } from '@/constants/dialogs'; /** * Vendors Credit note table actions bar. @@ -48,6 +50,9 @@ function VendorsCreditNoteActionsBar({ // #withSettingsActions addSetting, + + // #withDialogActions + openDialog, }) { const history = useHistory(); @@ -77,8 +82,13 @@ function VendorsCreditNoteActionsBar({ // Handle import button click. const handleImportBtnClick = () => { - history.push('/vendor-credits/import') - } + history.push('/vendor-credits/import'); + }; + + // Handle the export button click. + const handleExportBtnClick = () => { + openDialog(DialogsName.Export, { resource: 'vendor_credit' }); + }; return ( @@ -128,6 +138,7 @@ function VendorsCreditNoteActionsBar({ className={Classes.MINIMAL} icon={} text={} + onClick={handleExportBtnClick} /> ({ creditNoteTableSize: vendorsCreditNoteSetting?.tableSize, })), + withDialogActions, )(VendorsCreditNoteActionsBar); diff --git a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeActionsBar.tsx b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeActionsBar.tsx index 1d7e3d67d..f269f63ed 100644 --- a/packages/webapp/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeActionsBar.tsx +++ b/packages/webapp/src/containers/Purchases/PaymentMades/PaymentsLanding/PaymentMadeActionsBar.tsx @@ -33,6 +33,8 @@ import { useRefreshPaymentMades } from '@/hooks/query/paymentMades'; import { PaymentMadeAction, AbilitySubject } from '@/constants/abilityOption'; import { compose } from '@/utils'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { DialogsName } from '@/constants/dialogs'; /** * Payment made actions bar. @@ -47,6 +49,9 @@ function PaymentMadeActionsBar({ // #withSettings paymentMadesTableSize, + // #withDialogActions + openDialog, + // #withSettingsActions addSetting, }) { @@ -81,7 +86,12 @@ function PaymentMadeActionsBar({ // Handle the import button click. const handleImportBtnClick = () => { history.push('/payment-mades/import'); - } + }; + + // Handle the export button click. + const handleExportBtnClick = () => { + openDialog(DialogsName.Export, { resource: 'bill_payment' }); + }; return ( @@ -139,6 +149,7 @@ function PaymentMadeActionsBar({ className={Classes.MINIMAL} icon={} text={} + onClick={handleExportBtnClick} /> @@ -168,4 +179,5 @@ export default compose( withSettings(({ billPaymentSettings }) => ({ paymentMadesTableSize: billPaymentSettings?.tableSize, })), + withDialogActions, )(PaymentMadeActionsBar); diff --git a/packages/webapp/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.tsx b/packages/webapp/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.tsx index 7c8294858..7e55e6a13 100644 --- a/packages/webapp/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.tsx +++ b/packages/webapp/src/containers/Sales/CreditNotes/CreditNotesLanding/CreditNotesActionsBar.tsx @@ -25,8 +25,10 @@ import withCreditNotes from './withCreditNotes'; import withCreditNotesActions from './withCreditNotesActions'; import withSettings from '@/containers/Settings/withSettings'; import withSettingsActions from '@/containers/Settings/withSettingsActions'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; import { compose } from '@/utils'; +import { DialogsName } from '@/constants/dialogs'; /** * Credit note table actions bar. @@ -43,6 +45,9 @@ function CreditNotesActionsBar({ // #withSettingsActions addSetting, + + // #withDialogActions + openDialog, }) { const history = useHistory(); @@ -74,6 +79,11 @@ function CreditNotesActionsBar({ history.push('/credit-notes/import'); }; + // Handle the export button click. + const handleExportBtnClick = () => { + openDialog(DialogsName.Export, { resource: 'credit_note' }); + }; + return ( @@ -122,6 +132,7 @@ function CreditNotesActionsBar({ className={Classes.MINIMAL} icon={} text={} + onClick={handleExportBtnClick} /> ({ creditNoteTableSize: creditNoteSettings?.tableSize, })), + withDialogActions, )(CreditNotesActionsBar); diff --git a/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.tsx b/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.tsx index ff4f5d6bd..2b374d2db 100644 --- a/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.tsx +++ b/packages/webapp/src/containers/Sales/Estimates/EstimatesLanding/EstimatesActionsBar.tsx @@ -31,6 +31,8 @@ import { useEstimatesListContext } from './EstimatesListProvider'; import { useRefreshEstimates } from '@/hooks/query/estimates'; import { SaleEstimateAction, AbilitySubject } from '@/constants/abilityOption'; import { compose } from '@/utils'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { DialogsName } from '@/constants/dialogs'; /** * Estimates list actions bar. @@ -45,6 +47,9 @@ function EstimateActionsBar({ // #withSettings estimatesTableSize, + // #withDialogActions + openDialog, + // #withSettingsActions addSetting, }) { @@ -80,7 +85,11 @@ function EstimateActionsBar({ // Handle the import button click. const handleImportBtnClick = () => { history.push('/estimates/import'); - } + }; + // Handle the export button click. + const handleExportBtnClick = () => { + openDialog(DialogsName.Export, { resource: 'sale_estimate' }); + }; return ( @@ -141,6 +150,7 @@ function EstimateActionsBar({ className={Classes.MINIMAL} icon={} text={} + onClick={handleExportBtnClick} /> ({ estimatesTableSize: estimatesSettings?.tableSize, })), + withDialogActions )(EstimateActionsBar); diff --git a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx index cdfa295a3..8f4a4d686 100644 --- a/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx +++ b/packages/webapp/src/containers/Sales/Invoices/InvoicesLanding/InvoicesActionsBar.tsx @@ -29,6 +29,8 @@ import withInvoiceActions from './withInvoiceActions'; import withSettings from '@/containers/Settings/withSettings'; import withSettingsActions from '@/containers/Settings/withSettingsActions'; import { compose } from '@/utils'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { DialogsName } from '@/constants/dialogs'; /** * Invoices table actions bar. @@ -45,6 +47,9 @@ function InvoiceActionsBar({ // #withSettingsActions addSetting, + + // #withDialogsActions + openDialog }) { const history = useHistory(); @@ -79,6 +84,11 @@ function InvoiceActionsBar({ history.push('/invoices/import'); }; + // Handle the export button click. + const handleExportBtnClick = () => { + openDialog(DialogsName.Export, { resource: 'sale_invoice' }); + }; + return ( @@ -135,6 +145,7 @@ function InvoiceActionsBar({ className={Classes.MINIMAL} icon={} text={} + onClick={handleExportBtnClick} /> ({ invoicesTableSize: invoiceSettings?.tableSize, })), + withDialogActions, )(InvoiceActionsBar); diff --git a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveActionsBar.tsx b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveActionsBar.tsx index c1d510c1d..42bf63666 100644 --- a/packages/webapp/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveActionsBar.tsx +++ b/packages/webapp/src/containers/Sales/PaymentReceives/PaymentsLanding/PaymentReceiveActionsBar.tsx @@ -26,6 +26,7 @@ import withPaymentReceives from './withPaymentReceives'; import withPaymentReceivesActions from './withPaymentReceivesActions'; import withSettings from '@/containers/Settings/withSettings'; import withSettingsActions from '@/containers/Settings/withSettingsActions'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; import { PaymentReceiveAction, AbilitySubject, @@ -33,6 +34,7 @@ import { import { usePaymentReceivesListContext } from './PaymentReceiptsListProvider'; import { useRefreshPaymentReceive } from '@/hooks/query/paymentReceives'; import { compose } from '@/utils'; +import { DialogsName } from '@/constants/dialogs'; /** * Payment receives actions bar. @@ -49,6 +51,9 @@ function PaymentReceiveActionsBar({ // #withSettingsActions addSetting, + + // #withDialogActions + openDialog, }) { // History context. const history = useHistory(); @@ -82,6 +87,10 @@ function PaymentReceiveActionsBar({ const handleImportBtnClick = () => { history.push('/payment-receives/import'); }; + // Handle the export button click. + const handleExportBtnClick = () => { + openDialog(DialogsName.Export, { resource: 'payment_receive' }); + }; return ( @@ -139,6 +148,7 @@ function PaymentReceiveActionsBar({ className={Classes.MINIMAL} icon={} text={} + onClick={handleExportBtnClick} /> @@ -169,4 +179,5 @@ export default compose( withSettings(({ paymentReceiveSettings }) => ({ paymentReceivesTableSize: paymentReceiveSettings?.tableSize, })), + withDialogActions, )(PaymentReceiveActionsBar); diff --git a/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx b/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx index f27a505c9..f4ecfe39f 100644 --- a/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx +++ b/packages/webapp/src/containers/Sales/Receipts/ReceiptsLanding/ReceiptActionsBar.tsx @@ -35,6 +35,8 @@ import { useRefreshReceipts } from '@/hooks/query/receipts'; import { SaleReceiptAction, AbilitySubject } from '@/constants/abilityOption'; import { compose } from '@/utils'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; +import { DialogsName } from '@/constants/dialogs'; /** * Receipts actions bar. @@ -49,6 +51,9 @@ function ReceiptActionsBar({ // #withSettings receiptsTableSize, + // #withDialogActions + openDialog, + // #withSettingsActions addSetting, }) { @@ -86,6 +91,11 @@ function ReceiptActionsBar({ history.push('/receipts/import'); }; + // Handle the export button click. + const handleExportBtnClick = () => { + openDialog(DialogsName.Export, { resource: 'sale_receipt' }); + }; + return ( @@ -145,6 +155,7 @@ function ReceiptActionsBar({ className={Classes.MINIMAL} icon={} text={} + onClick={handleExportBtnClick} /> ({ receiptsTableSize: receiptSettings?.tableSize, })), + withDialogActions, )(ReceiptActionsBar); diff --git a/packages/webapp/src/containers/Vendors/VendorsLanding/VendorActionsBar.tsx b/packages/webapp/src/containers/Vendors/VendorsLanding/VendorActionsBar.tsx index bd54a6b16..86c11ea8a 100644 --- a/packages/webapp/src/containers/Vendors/VendorsLanding/VendorActionsBar.tsx +++ b/packages/webapp/src/containers/Vendors/VendorsLanding/VendorActionsBar.tsx @@ -31,8 +31,10 @@ import withVendors from './withVendors'; import withVendorsActions from './withVendorsActions'; import withSettings from '@/containers/Settings/withSettings'; import withSettingsActions from '@/containers/Settings/withSettingsActions'; +import withDialogActions from '@/containers/Dialog/withDialogActions'; import { compose } from '@/utils'; +import { DialogsName } from '@/constants/dialogs'; /** * Vendors actions bar. @@ -50,6 +52,9 @@ function VendorActionsBar({ // #withSettingsActions addSetting, + + // #withDialogActions + openDialog, }) { const history = useHistory(); @@ -83,10 +88,17 @@ function VendorActionsBar({ const handleTableRowSizeChange = (size) => { addSetting('vendors', 'tableSize', size); }; + // Handle import button success. const handleImportBtnSuccess = () => { history.push('/vendors/import'); }; + + // Handle the export button click. + const handleExportBtnClick = () => { + openDialog(DialogsName.Export, { resource: 'vendor' }); + }; + return ( @@ -138,6 +150,7 @@ function VendorActionsBar({ className={Classes.MINIMAL} icon={} text={} + onClick={handleExportBtnClick} /> ({ vendorsTableSize: vendorsSettings?.tableSize, })), + withDialogActions, )(VendorActionsBar); diff --git a/packages/webapp/src/hooks/query/FinancialReports/use-export.ts b/packages/webapp/src/hooks/query/FinancialReports/use-export.ts new file mode 100644 index 000000000..63e1e5d74 --- /dev/null +++ b/packages/webapp/src/hooks/query/FinancialReports/use-export.ts @@ -0,0 +1,38 @@ +// @ts-nocheck +import { downloadFile } from '@/hooks/useDownloadFile'; +import useApiRequest from '@/hooks/useRequest'; +import { AxiosError } from 'axios'; +import { useMutation } from 'react-query'; + +interface ResourceExportValues { + resource: string; + format: string; +} +/** + * Initiates a download of the balance sheet in XLSX format. + * @param {Object} query - The query parameters for the request. + * @param {Object} args - Additional configurations for the download. + * @returns {Function} A function to trigger the file download. + */ +export const useResourceExport = () => { + const apiRequest = useApiRequest(); + + return useMutation((data: ResourceExportValues) => { + return apiRequest + .get('/export', { + responseType: 'blob', + headers: { + accept: + data.format === 'xlsx' ? 'application/xlsx' : 'application/csv', + }, + params: { + resource: data.resource, + format: data.format, + }, + }) + .then((res) => { + downloadFile(res.data, `${data.resource}.${data.format}`); + return res; + }); + }); +}; diff --git a/packages/webapp/src/hooks/query/authentication.tsx b/packages/webapp/src/hooks/query/authentication.tsx index b2877ec8e..16ee67d7f 100644 --- a/packages/webapp/src/hooks/query/authentication.tsx +++ b/packages/webapp/src/hooks/query/authentication.tsx @@ -137,7 +137,7 @@ export const useAuthResetPassword = (props) => { */ export const useAuthMetadata = (props) => { return useRequestQuery( - [t.AUTH_METADATA_PAGE,], + [t.AUTH_METADATA_PAGE], { method: 'get', url: `auth/meta`, @@ -147,5 +147,35 @@ export const useAuthMetadata = (props) => { defaultData: {}, ...props, }, - ); + ); +}; + +/** + * + */ +export const useAuthSignUpVerifyResendMail = (props) => { + const apiRequest = useApiRequest(); + + return useMutation( + () => apiRequest.post('auth/register/verify/resend'), + props, + ); +}; + +interface AuthSignUpVerifyValues { + token: string; + email: string; } + +/** + * + */ +export const useAuthSignUpVerify = (props) => { + const apiRequest = useApiRequest(); + + return useMutation( + (values: AuthSignUpVerifyValues) => + apiRequest.post('auth/register/verify', values), + props, + ); +}; diff --git a/packages/webapp/src/hooks/query/users.tsx b/packages/webapp/src/hooks/query/users.tsx index 73bcf0691..646308353 100644 --- a/packages/webapp/src/hooks/query/users.tsx +++ b/packages/webapp/src/hooks/query/users.tsx @@ -5,6 +5,7 @@ import { useQueryTenant, useRequestQuery } from '../useQueryRequest'; import useApiRequest from '../useRequest'; import { useSetFeatureDashboardMeta } from '../state/feature'; import t from './types'; +import { useSetAuthEmailConfirmed } from '../state'; // Common invalidate queries. const commonInvalidateQueries = (queryClient) => { @@ -130,6 +131,8 @@ export function useUser(id, props) { } export function useAuthenticatedAccount(props) { + const setEmailConfirmed = useSetAuthEmailConfirmed(); + return useRequestQuery( ['AuthenticatedAccount'], { @@ -139,6 +142,9 @@ export function useAuthenticatedAccount(props) { { select: (response) => response.data.data, defaultData: {}, + onSuccess: (data) => { + setEmailConfirmed(data.is_verified); + }, ...props, }, ); @@ -166,4 +172,3 @@ export const useDashboardMeta = (props) => { }, [state.isSuccess, state.data, setFeatureDashboardMeta]); return state; }; - diff --git a/packages/webapp/src/hooks/state/authentication.tsx b/packages/webapp/src/hooks/state/authentication.tsx index 77351693f..7e57e5508 100644 --- a/packages/webapp/src/hooks/state/authentication.tsx +++ b/packages/webapp/src/hooks/state/authentication.tsx @@ -2,7 +2,10 @@ import { useDispatch, useSelector } from 'react-redux'; import { useCallback } from 'react'; import { isAuthenticated } from '@/store/authentication/authentication.reducer'; -import { setLogin } from '@/store/authentication/authentication.actions'; +import { + setEmailConfirmed, + setLogin, +} from '@/store/authentication/authentication.actions'; import { useQueryClient } from 'react-query'; import { removeCookie } from '@/utils'; @@ -68,3 +71,22 @@ export const useAuthUser = () => { export const useAuthOrganizationId = () => { return useSelector((state) => state.authentication.organizationId); }; + +/** + * Retrieves the user's email verification status. + */ +export const useAuthUserVerified = () => { + return useSelector((state) => state.authentication.verified); +}; + +/** + * Sets the user's email verification status. + */ +export const useSetAuthEmailConfirmed = () => { + const dispatch = useDispatch(); + + return useCallback( + (verified?: boolean = true) => dispatch(setEmailConfirmed(verified)), + [dispatch], + ); +}; diff --git a/packages/webapp/src/routes/authentication.tsx b/packages/webapp/src/routes/authentication.tsx index 77b1fce6f..7ce79dbbc 100644 --- a/packages/webapp/src/routes/authentication.tsx +++ b/packages/webapp/src/routes/authentication.tsx @@ -28,10 +28,16 @@ export default [ loader: () => import('@/containers/Authentication/InviteAccept'), }), }, + { + path: `${BASE_URL}/register/email_confirmation`, + component: LazyLoader({ + loader: () => import('@/containers/Authentication/EmailConfirmation'), + }), + }, { path: `${BASE_URL}/register`, component: LazyLoader({ loader: () => import('@/containers/Authentication/Register'), }), - } + }, ]; diff --git a/packages/webapp/src/store/authentication/authentication.actions.tsx b/packages/webapp/src/store/authentication/authentication.actions.tsx index 4d2d5f048..daefd8f48 100644 --- a/packages/webapp/src/store/authentication/authentication.actions.tsx +++ b/packages/webapp/src/store/authentication/authentication.actions.tsx @@ -3,4 +3,8 @@ import t from '@/store/types'; export const setLogin = () => ({ type: t.LOGIN_SUCCESS }); export const setLogout = () => ({ type: t.LOGOUT }); -export const setStoreReset = () => ({ type: t.RESET }); \ No newline at end of file +export const setStoreReset = () => ({ type: t.RESET }); +export const setEmailConfirmed = (verified?: boolean) => ({ + type: t.SET_EMAIL_VERIFIED, + action: { verified }, +}); diff --git a/packages/webapp/src/store/authentication/authentication.reducer.tsx b/packages/webapp/src/store/authentication/authentication.reducer.tsx index 9972bf2a8..ce56b81ec 100644 --- a/packages/webapp/src/store/authentication/authentication.reducer.tsx +++ b/packages/webapp/src/store/authentication/authentication.reducer.tsx @@ -1,8 +1,9 @@ // @ts-nocheck -import { createReducer } from '@reduxjs/toolkit'; +import { PayloadAction, createReducer } from '@reduxjs/toolkit'; import { persistReducer } from 'redux-persist'; import purgeStoredState from 'redux-persist/es/purgeStoredState'; import storage from 'redux-persist/lib/storage'; +import { isUndefined } from 'lodash'; import { getCookie } from '@/utils'; import t from '@/store/types'; @@ -13,6 +14,7 @@ const initialState = { tenantId: getCookie('tenant_id'), userId: getCookie('authenticated_user_id'), locale: getCookie('locale'), + verified: true, // Let's be optimistic and assume the user's email is confirmed. errors: [], }; @@ -32,6 +34,15 @@ const reducerInstance = createReducer(initialState, { state.errors = []; }, + [t.SET_EMAIL_VERIFIED]: ( + state, + payload: PayloadAction<{ verified?: boolean }>, + ) => { + state.verified = !isUndefined(payload.action.verified) + ? payload.action.verified + : true; + }, + [t.RESET]: (state) => { purgeStoredState(CONFIG); }, diff --git a/packages/webapp/src/store/authentication/authentication.types.tsx b/packages/webapp/src/store/authentication/authentication.types.tsx index c5a5b3c3f..f64af4652 100644 --- a/packages/webapp/src/store/authentication/authentication.types.tsx +++ b/packages/webapp/src/store/authentication/authentication.types.tsx @@ -7,4 +7,5 @@ export default { LOGOUT: 'LOGOUT', LOGIN_CLEAR_ERRORS: 'LOGIN_CLEAR_ERRORS', RESET: 'RESET', + SET_EMAIL_VERIFIED: 'SET_EMAIL_VERIFIED' }; \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index e00f9c85f..0c1e9e860 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -224,6 +224,9 @@ importers: mysql2: specifier: ^1.6.5 version: 1.7.0 + newrelic: + specifier: ^11.15.0 + version: 11.16.0 node-cache: specifier: ^4.2.1 version: 4.2.1 @@ -1879,7 +1882,7 @@ packages: '@babel/helper-plugin-utils': 7.20.2 debug: 4.3.4(supports-color@5.5.0) lodash.debounce: 4.0.8 - resolve: 1.22.1 + resolve: 1.22.6 semver: 6.3.0 transitivePeerDependencies: - supports-color @@ -1895,7 +1898,7 @@ packages: '@babel/helper-plugin-utils': 7.22.5 debug: 4.3.4(supports-color@5.5.0) lodash.debounce: 4.0.8 - resolve: 1.22.1 + resolve: 1.22.6 transitivePeerDependencies: - supports-color dev: false @@ -3783,6 +3786,16 @@ packages: chalk: 4.1.2 dev: true + /@contrast/fn-inspect@3.4.0: + resolution: {integrity: sha512-Jw6dMFEIt/FXF1ihJri2GFNayeEKQ6r+WRjjWl7MdgMup2D4vCPu99ZV8eHSMqNNkj3BEzUNC91ZaJVB1XJmfg==} + engines: {node: '>=12.13.0'} + requiresBuild: true + dependencies: + nan: 2.17.0 + node-gyp-build: 4.8.1 + dev: false + optional: true + /@craco/craco@5.9.0(react-scripts@5.0.1): resolution: {integrity: sha512-2Q8gIB4W0/nPiUxr9iAKUhGsFlXYN0/wngUdK1VWtfV2NtBv+yllNn2AjieaLbttgpQinuOYmDU65vocC0NMDg==} engines: {node: '>=6'} @@ -4093,6 +4106,25 @@ packages: deprecated: the package is rather renamed to @formatjs/ecma-abstract with some changes in functionality (primarily selectUnit is removed and we don't plan to make any further changes to this package dev: false + /@grpc/grpc-js@1.10.7: + resolution: {integrity: sha512-ZMBVjSeDAz3tFSehyO6Pd08xZT1HfIwq3opbeM4cDlBh52gmwp0wVIPcQur53NN0ac68HMZ/7SF2rGRD5KmVmg==} + engines: {node: '>=12.10.0'} + dependencies: + '@grpc/proto-loader': 0.7.13 + '@js-sdsl/ordered-map': 4.4.2 + dev: false + + /@grpc/proto-loader@0.7.13: + resolution: {integrity: sha512-AiXO/bfe9bmxBjxxtYxFAXGZvMaN5s8kO+jBHAJCON8rJoB5YS/D6X7ZNc6XQkuHNmyl4CYaMI1fJ/Gn27RGGw==} + engines: {node: '>=6'} + hasBin: true + dependencies: + lodash.camelcase: 4.3.0 + long: 5.2.3 + protobufjs: 7.2.6 + yargs: 17.7.2 + dev: false + /@hapi/boom@7.4.11: resolution: {integrity: sha512-VSU/Cnj1DXouukYxxkes4nNJonCnlogHvIff1v1RVoN4xzkKhMXX+GRmb3NyH1iar10I9WFPDv2JPwfH3GaV0A==} deprecated: This version has been deprecated and is no longer supported or maintained @@ -4142,6 +4174,7 @@ packages: /@isaacs/cliui@8.0.2: resolution: {integrity: sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA==} engines: {node: '>=12'} + requiresBuild: true dependencies: string-width: 5.1.2 string-width-cjs: /string-width@4.2.3 @@ -4149,7 +4182,6 @@ packages: strip-ansi-cjs: /strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: /wrap-ansi@7.0.0 - dev: true /@istanbuljs/load-nyc-config@1.1.0: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} @@ -4638,6 +4670,10 @@ packages: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.14 + /@js-sdsl/ordered-map@4.4.2: + resolution: {integrity: sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw==} + dev: false + /@juggle/resize-observer@3.4.0: resolution: {integrity: sha512-dfLbk+PwWvFzSxwk3n5ySL0hfBog779o8h68wK/7/APo/7cgyWp5jcXockbxdk5kFRkbeXWm4Fbi9FrdN381sA==} dev: false @@ -4743,6 +4779,63 @@ packages: glob-to-regexp: 0.3.0 dev: false + /@newrelic/native-metrics@10.1.1: + resolution: {integrity: sha512-BvdTMAqS3d94ZwJ6u70dWqZVkX8ev3dybkxRInHMbKV2DE1koQR3nzH2ut3hf1MaRQh4SF6SpUNTUznzCZZtjw==} + engines: {node: '>=16', npm: '>=6'} + requiresBuild: true + dependencies: + nan: 2.19.0 + node-gyp: 10.1.0 + node-gyp-build: 4.8.1 + prebuildify: 6.0.1 + semver: 7.5.4 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + + /@newrelic/ritm@7.2.0: + resolution: {integrity: sha512-I4iVhm+wlTEDJXQT8EydF/U5vlR9bBHrtBGyvd/D9WCucoMtrPrCNyILQh9bZ+46E8QRE7zh6QEGyQcnc3qNMg==} + engines: {node: '>=8.6.0'} + dependencies: + debug: 4.3.4(supports-color@5.5.0) + module-details-from-path: 1.0.3 + resolve: 1.22.6 + transitivePeerDependencies: + - supports-color + dev: false + + /@newrelic/security-agent@1.2.0: + resolution: {integrity: sha512-Snk++TQmqHKuxPYOH5bEU4GCr5xKYurUZWx3oiuoQUV73pw61qeEMrb/8iuGgAghwpCEC/8n+308efqCIZkiiQ==} + dependencies: + axios: 1.6.8 + check-disk-space: 3.4.0 + content-type: 1.0.5 + fast-safe-stringify: 2.1.1 + find-package-json: 1.2.0 + hash.js: 1.1.7 + html-entities: 2.4.0 + is-invalid-path: 1.0.2 + js-yaml: 4.1.0 + jsonschema: 1.4.1 + lodash: 4.17.21 + log4js: 6.9.1 + pretty-bytes: 5.6.0 + request-ip: 3.3.0 + ringbufferjs: 2.0.0 + semver: 7.5.4 + sync-request: 6.1.0 + unescape: 1.0.1 + unescape-js: 1.1.4 + uuid: 9.0.1 + ws: 8.14.2 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + dev: false + /@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1: resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} dependencies: @@ -4775,22 +4868,22 @@ packages: /@npmcli/agent@2.2.2: resolution: {integrity: sha512-OrcNPXdpSl9UX7qPVRWbmWMCSXrcDa2M9DvrbOTj7ao1S4PlqVFYv9/yLKMkrJKZ/V5A/kDBC690or307i26Og==} engines: {node: ^16.14.0 || >=18.0.0} + requiresBuild: true dependencies: agent-base: 7.1.1 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.4 - lru-cache: 10.2.0 + lru-cache: 10.2.1 socks-proxy-agent: 8.0.3 transitivePeerDependencies: - supports-color - dev: true /@npmcli/fs@3.1.0: resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + requiresBuild: true dependencies: semver: 7.5.4 - dev: true /@npmcli/git@5.0.6: resolution: {integrity: sha512-4x/182sKXmQkf0EtXxT26GEsaOATpD7WVtza5hrYivWZeo6QefC6xq9KAXrnjtFKBZ4rZwR7aX/zClYYXgtwLw==} @@ -5117,7 +5210,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} requiresBuild: true - dev: true optional: true /@pkgr/utils@2.3.1: @@ -5191,6 +5283,55 @@ packages: resolution: {integrity: sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==} dev: false + /@prisma/prisma-fmt-wasm@4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085: + resolution: {integrity: sha512-zYz3rFwPB82mVlHGknAPdnSY/a308dhPOblxQLcZgZTDRtDXOE1MgxoRAys+jekwR4/bm3+rZDPs1xsFMsPZig==} + requiresBuild: true + dev: false + optional: true + + /@protobufjs/aspromise@1.1.2: + resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} + dev: false + + /@protobufjs/base64@1.1.2: + resolution: {integrity: sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==} + dev: false + + /@protobufjs/codegen@2.0.4: + resolution: {integrity: sha512-YyFaikqM5sH0ziFZCN3xDC7zeGaB/d0IUb9CATugHWbd1FRFwWwt4ld4OYMPWu5a3Xe01mGAULCdqhMlPl29Jg==} + dev: false + + /@protobufjs/eventemitter@1.1.0: + resolution: {integrity: sha512-j9ednRT81vYJ9OfVuXG6ERSTdEL1xVsNgqpkxMsbIabzSo3goCjDIveeGv5d03om39ML71RdmrGNjG5SReBP/Q==} + dev: false + + /@protobufjs/fetch@1.1.0: + resolution: {integrity: sha512-lljVXpqXebpsijW71PZaCYeIcE5on1w5DlQy5WH6GLbFryLUrBD4932W/E2BSpfRJWseIL4v/KPgBFxDOIdKpQ==} + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/inquire': 1.1.0 + dev: false + + /@protobufjs/float@1.0.2: + resolution: {integrity: sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==} + dev: false + + /@protobufjs/inquire@1.1.0: + resolution: {integrity: sha512-kdSefcPdruJiFMVSbn801t4vFK7KB/5gd2fYvrxhuJYg8ILrmn9SKSX2tZdV6V+ksulWqS7aXjBcRXl3wHoD9Q==} + dev: false + + /@protobufjs/path@1.1.2: + resolution: {integrity: sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==} + dev: false + + /@protobufjs/pool@1.1.0: + resolution: {integrity: sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==} + dev: false + + /@protobufjs/utf8@1.1.0: + resolution: {integrity: sha512-Vvn3zZrhQZkkBE8LSuW3em98c0FwgO4nxzv6OdSxPKJIEKY2bGbHn+mhGIPerzI4twdxaP8/0+06HBpwf345Lw==} + dev: false + /@reduxjs/toolkit@1.9.2(react-redux@7.2.9)(react@18.2.0): resolution: {integrity: sha512-5ZAZ7hwAKWSii5T6NTPmgIBUqyVdlDs+6JjThz6J6dmHLDm6zCzv2OjHIFAi3Vvs1qjmXU0bm6eBojukYXjVMQ==} peerDependencies: @@ -5266,7 +5407,7 @@ packages: builtin-modules: 3.3.0 deepmerge: 4.3.1 is-module: 1.0.0 - resolve: 1.22.1 + resolve: 1.22.6 rollup: 2.79.1 dev: false @@ -5988,6 +6129,12 @@ packages: resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==} dev: true + /@types/concat-stream@1.6.1: + resolution: {integrity: sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==} + dependencies: + '@types/node': 14.18.36 + dev: false + /@types/connect-history-api-fallback@1.5.1: resolution: {integrity: sha512-iaQslNbARe8fctL5Lk+DsmgWOM83lM+7FzP0eQUJs1jd3kBE8NWqBTIT2S8SqQOJjxvt2eyIjpOuYeRXq2AdMw==} dependencies: @@ -6063,6 +6210,12 @@ packages: '@types/serve-static': 1.15.3 dev: false + /@types/form-data@0.0.33: + resolution: {integrity: sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==} + dependencies: + '@types/node': 14.18.36 + dev: false + /@types/glob@7.2.0: resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} dependencies: @@ -6206,6 +6359,10 @@ packages: '@types/node': 18.13.0 dev: false + /@types/node@10.17.60: + resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==} + dev: false + /@types/node@14.18.36: resolution: {integrity: sha512-FXKWbsJ6a1hIrRxv+FoukuHnGTgEzKYGi7kilfMae96AL9UNkPFNWJEEYWzdRI9ooIkbr4AKldyuSTLql06vLQ==} dev: false @@ -6213,6 +6370,10 @@ packages: /@types/node@18.13.0: resolution: {integrity: sha512-gC3TazRzGoOnoKAhUx+Q0t8S9Tzs74z7m0ipwGpSqQrleP14hKxP4/JUeEQcD3W1/aIpnWl8pHowI7WokuZpXg==} + /@types/node@8.10.66: + resolution: {integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==} + dev: false + /@types/normalize-package-data@2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} @@ -6810,7 +6971,7 @@ packages: debug: 4.3.4(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 - semver: 7.3.8 + semver: 7.5.4 tsutils: 3.21.0(typescript@4.9.5) typescript: 4.9.5 transitivePeerDependencies: @@ -6871,7 +7032,7 @@ packages: '@typescript-eslint/typescript-estree': 5.62.0(typescript@4.9.5) eslint: 8.33.0 eslint-scope: 5.1.1 - semver: 7.3.8 + semver: 7.5.4 transitivePeerDependencies: - supports-color - typescript @@ -6892,6 +7053,10 @@ packages: eslint-visitor-keys: 3.3.0 dev: false + /@tyriar/fibonacci-heap@2.0.9: + resolution: {integrity: sha512-bYuSNomfn4hu2tPiDN+JZtnzCpSpbJ/PNeulmocDy3xN2X5OkJL65zo6rPZp65cPPhLF9vfT/dgE+RtFRCSxOA==} + dev: false + /@ucast/core@1.10.1: resolution: {integrity: sha512-sXKbvQiagjFh2JCpaHUa64P4UdJbOxYeC5xiZFn8y6iYdb0WkismduE+RmiJrIjw/eLDYmIEXiQeIYYowmkcAw==} dev: false @@ -7092,7 +7257,7 @@ packages: /abbrev@2.0.0: resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - dev: true + requiresBuild: true /accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} @@ -7127,6 +7292,14 @@ packages: dependencies: acorn: 8.8.2 + /acorn-import-attributes@1.9.5(acorn@8.8.2): + resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} + peerDependencies: + acorn: ^8 + dependencies: + acorn: 8.8.2 + dev: false + /acorn-jsx@5.3.2(acorn@8.8.2): resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} peerDependencies: @@ -7246,7 +7419,7 @@ packages: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -7254,16 +7427,15 @@ packages: resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} engines: {node: '>= 14'} dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color - dev: true /agentkeepalive@4.2.1: resolution: {integrity: sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==} engines: {node: '>= 8.0.0'} dependencies: - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) depd: 1.1.2 humanize-ms: 1.2.1 transitivePeerDependencies: @@ -7408,7 +7580,7 @@ packages: /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} - dev: true + requiresBuild: true /ansi-wrap@0.1.0: resolution: {integrity: sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==} @@ -7844,6 +8016,16 @@ packages: transitivePeerDependencies: - debug + /axios@1.6.8: + resolution: {integrity: sha512-v/ZHtJDU39mDpyBoFVkETcd/uNdxrWRrg3bKpOKzXFA6Bvqopts6ALSMU3y6ijYxbw2B+wPrIv46egTzJXCLGQ==} + dependencies: + follow-redirects: 1.15.6 + form-data: 4.0.0 + proxy-from-env: 1.1.0 + transitivePeerDependencies: + - debug + dev: false + /axobject-query@3.2.1: resolution: {integrity: sha512-jsyHu61e6N4Vbz/v18DHwWYKK0bSWLqn47eeDSKPB7m8tqMHF9YJ+mhIk2lVteyZrY8tnSj/jHOv4YiTCuCJgg==} dependencies: @@ -8737,11 +8919,12 @@ packages: /cacache@18.0.2: resolution: {integrity: sha512-r3NU8h/P+4lVUHfeRw1dtgQYar3DZMm4/cm2bZgOvrFC/su7budSOeqh52VJIC4U4iG1WWwV6vRW0znqBvxNuw==} engines: {node: ^16.14.0 || >=18.0.0} + requiresBuild: true dependencies: '@npmcli/fs': 3.1.0 fs-minipass: 3.0.3 glob: 10.3.12 - lru-cache: 10.2.0 + lru-cache: 10.2.1 minipass: 7.0.4 minipass-collect: 2.0.1 minipass-flush: 1.0.5 @@ -8750,7 +8933,6 @@ packages: ssri: 10.0.5 tar: 6.1.13 unique-filename: 3.0.0 - dev: true /cache-base@1.0.1: resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} @@ -8995,6 +9177,11 @@ packages: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true + /check-disk-space@3.4.0: + resolution: {integrity: sha512-drVkSqfwA+TvuEhFipiR1OC9boEGZL5RrWvVsOthdcvQNXyCCuKkEiTOTXZ7qxSf/GLwq4GvzfrQD/Wz325hgw==} + engines: {node: '>=16'} + dev: false + /check-error@1.0.2: resolution: {integrity: sha512-BrgHpW9NURQgzoNyjfq0Wu6VFO6D7IZEmJNdtgNqpzGG8RuNFHt2jQxWlAs4HMe119chBnv+34syEZtc6IhLtA==} dev: true @@ -9045,7 +9232,6 @@ packages: /chownr@2.0.0: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} - dev: true /chrome-trace-event@1.0.3: resolution: {integrity: sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==} @@ -9155,7 +9341,6 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 wrap-ansi: 7.0.0 - dev: true /clone-buffer@1.0.0: resolution: {integrity: sha512-KLLTJWrvwIP+OPfMn0x2PheDEP20RPUcGXj/ERegTgdmPEZylALQldygiqrPPu8P45uNuPs7ckmReLY6v/iA5g==} @@ -9447,9 +9632,8 @@ packages: dependencies: buffer-from: 1.1.2 inherits: 2.0.4 - readable-stream: 3.6.0 + readable-stream: 3.6.2 typedarray: 0.0.6 - dev: true /configstore@3.1.5: resolution: {integrity: sha512-nlOhI4+fdzoK5xmJ+NY+1gZK56bwEaWZr8fYuXohZ9Vkc1o3a4T/R3M+yE/w7x/ZVJ1zF8c+oaOvF0dztdUgmA==} @@ -10314,6 +10498,11 @@ packages: whatwg-url: 8.7.0 dev: false + /date-format@4.0.14: + resolution: {integrity: sha512-39BOQLs9ZjKh0/patS9nrT8wc3ioX3/eA/zgbKNopnF2wCqJEoxywwwElATYvRsXdnOxA/OQeQoFZ3rFjVajhg==} + engines: {node: '>=4.0'} + dev: false + /date.js@0.3.3: resolution: {integrity: sha512-HgigOS3h3k6HnW011nAb43c5xx5rBXk8P2v/WIT9Zv4koIaVXiH2BURguI78VVp+5Qc076T7OR378JViCnZtBw==} dependencies: @@ -10387,17 +10576,6 @@ packages: ms: 2.1.2 dev: false - /debug@4.3.4: - resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} - engines: {node: '>=6.0'} - peerDependencies: - supports-color: '*' - peerDependenciesMeta: - supports-color: - optional: true - dependencies: - ms: 2.1.2 - /debug@4.3.4(supports-color@5.5.0): resolution: {integrity: sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==} engines: {node: '>=6.0'} @@ -10950,7 +11128,7 @@ packages: /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - dev: true + requiresBuild: true /ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} @@ -11032,7 +11210,6 @@ packages: requiresBuild: true dependencies: iconv-lite: 0.6.3 - dev: true optional: true /end-of-stream@1.4.4: @@ -11114,7 +11291,6 @@ packages: /env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} - dev: true /envinfo@7.8.1: resolution: {integrity: sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==} @@ -11123,7 +11299,6 @@ packages: /err-code@2.0.3: resolution: {integrity: sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==} - dev: true /error-ex@1.3.2: resolution: {integrity: sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==} @@ -12059,7 +12234,7 @@ packages: /exponential-backoff@3.1.1: resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} - dev: true + requiresBuild: true /express-basic-auth@1.2.1: resolution: {integrity: sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA==} @@ -12257,6 +12432,10 @@ packages: resolution: {integrity: sha512-8EZzEP0eKkEEVX+drtd9mtuQ+/QrlfW/5MlwcwK5Nds6EkZ/tRzEexkzUY2mIssnAyVLT+TKHuRXmFNNXYUd6g==} dev: false + /fast-safe-stringify@2.1.1: + resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} + dev: false + /fast-shallow-equal@1.0.0: resolution: {integrity: sha512-HPtaa38cPgWvaCFmRNhlc6NG7pv6NUHqjPgVAkWGoB9mQMwYB27/K0CvOM5Czy+qpT3e8XJ6Q4aPAnzpNpzNaw==} dev: false @@ -12426,6 +12605,10 @@ packages: pkg-dir: 4.2.0 dev: false + /find-package-json@1.2.0: + resolution: {integrity: sha512-+SOGcLGYDJHtyqHd87ysBhmaeQ95oWspDKnMXBrnQ9Eq4OkLNqejgoaD8xVWu6GPa0B6roa6KinCMEMcVeqONw==} + dev: false + /find-root@1.1.0: resolution: {integrity: sha512-NKfW6bec6GfKc0SGx1e07QZY9PE99u0Bft/0rzSD5k3sO/vwkVUpDUKVm5Gpp5Ue3YfShPFTX2070tDs5kB9Ng==} @@ -12542,6 +12725,16 @@ packages: debug: optional: true + /follow-redirects@1.15.6: + resolution: {integrity: sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dev: false + /for-each@0.3.3: resolution: {integrity: sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==} dependencies: @@ -12569,10 +12762,10 @@ packages: /foreground-child@3.1.1: resolution: {integrity: sha512-TMKDUnIte6bfb5nWv7V/caI169OHgvwjb7V4WkeUvbQQdjr5rWKqHFiKWb/fcOwB+CzBT+qbWjvj+DVwRskpIg==} engines: {node: '>=14'} + requiresBuild: true dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - dev: true /forever-agent@0.6.1: resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} @@ -12632,7 +12825,7 @@ packages: memfs: 3.5.3 minimatch: 3.1.2 schema-utils: 2.7.0 - semver: 7.3.8 + semver: 7.5.4 tapable: 1.1.3 typescript: 4.9.5 webpack: 5.76.0(webpack-cli@4.10.0) @@ -12654,7 +12847,6 @@ packages: asynckit: 0.4.0 combined-stream: 1.0.8 mime-types: 2.1.35 - dev: true /form-data@3.0.1: resolution: {integrity: sha512-RHkBKtLWUVwd7SqRIvCZMEvAMoGUp0XU+seQiZejj0COz3RI3hWP4sCv3gZWWLjJTd7rGwcsF5eKZGii0r/hbg==} @@ -12753,6 +12945,15 @@ packages: universalify: 2.0.0 dev: true + /fs-extra@8.1.0: + resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==} + engines: {node: '>=6 <7 || >=8'} + dependencies: + graceful-fs: 4.2.11 + jsonfile: 4.0.0 + universalify: 0.1.2 + dev: false + /fs-extra@9.1.0: resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==} engines: {node: '>=10'} @@ -12768,14 +12969,13 @@ packages: engines: {node: '>= 8'} dependencies: minipass: 3.3.6 - dev: true /fs-minipass@3.0.3: resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + requiresBuild: true dependencies: minipass: 7.0.4 - dev: true /fs-mkdirp-stream@1.0.0: resolution: {integrity: sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==} @@ -12923,6 +13123,11 @@ packages: yargs: 16.2.0 dev: true + /get-port@3.2.0: + resolution: {integrity: sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==} + engines: {node: '>=4'} + dev: false + /get-port@5.1.1: resolution: {integrity: sha512-g/Q1aTSDOxFpchXC4i8ZWvxA1lnPqx/JHqcpIw0/LX9T8x/GBbi6YnlN5nhaKIFkT8oFsscUKgDJYxfwfS6QsQ==} engines: {node: '>=8'} @@ -13104,13 +13309,13 @@ packages: resolution: {integrity: sha512-TCNv8vJ+xz4QiqTpfOJA7HvYv+tNIRHKfUWw/q+v2jdgN4ebz+KY9tGx5J4rHP0o84mNP+ApH66HRX8us3Khqg==} engines: {node: '>=16 || 14 >=14.17'} hasBin: true + requiresBuild: true dependencies: foreground-child: 3.1.1 jackspeak: 2.3.6 minimatch: 9.0.4 minipass: 7.0.4 path-scurry: 1.10.2 - dev: true /glob@7.1.2: resolution: {integrity: sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==} @@ -13559,7 +13764,6 @@ packages: dependencies: inherits: 2.0.4 minimalistic-assert: 1.0.1 - dev: true /hasha@3.0.0: resolution: {integrity: sha512-w0Kz8lJFBoyaurBiNrIvxPqr/gJ6fOfSkpAPOepN3oECqGJag37xPbOv57izi/KP8auHgNYxn5fXtAb+1LsJ6w==} @@ -13776,9 +13980,18 @@ packages: entities: 2.2.0 dev: false + /http-basic@8.1.3: + resolution: {integrity: sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==} + engines: {node: '>=6.0.0'} + dependencies: + caseless: 0.12.0 + concat-stream: 1.6.2 + http-response-object: 3.0.2 + parse-cache-control: 1.0.1 + dev: false + /http-cache-semantics@4.1.1: resolution: {integrity: sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==} - dev: true /http-deceiver@1.2.7: resolution: {integrity: sha512-LmpOGxTfbpgtGVxJrj5k7asXHCgNZp5nLfp+hWc8QQRqtb7fUy6kRY3BO1h9ddF6yIPYUARgxGOwB42DnxIaNw==} @@ -13837,7 +14050,7 @@ packages: dependencies: '@tootallnate/once': 2.0.0 agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color dev: true @@ -13845,12 +14058,12 @@ packages: /http-proxy-agent@7.0.2: resolution: {integrity: sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==} engines: {node: '>= 14'} + requiresBuild: true dependencies: agent-base: 7.1.1 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color - dev: true /http-proxy-middleware@1.3.1: resolution: {integrity: sha512-13eVVDYS4z79w7f1+NPllJtOQFx/FdUW4btIvVRMaRlUY9VGstAbo5MOhLEuUgZFRHn3x50ufn25zkj/boZnEg==} @@ -13895,6 +14108,12 @@ packages: - debug dev: false + /http-response-object@3.0.2: + resolution: {integrity: sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==} + dependencies: + '@types/node': 10.17.60 + dev: false + /http-signature@1.2.0: resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} engines: {node: '>=0.8', npm: '>=1.3.7'} @@ -13923,7 +14142,7 @@ packages: engines: {node: '>= 6'} dependencies: agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -13932,10 +14151,9 @@ packages: engines: {node: '>= 14'} dependencies: agent-base: 7.1.1 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) transitivePeerDependencies: - supports-color - dev: true /human-interval@2.0.1: resolution: {integrity: sha512-r4Aotzf+OtKIGQCB3odUowy4GfUDTy3aTWTfLd7ZF2gBCy3XW3v/dJLRefZnOFFnjqs5B1TypvS8WarpBkYUNQ==} @@ -14070,6 +14288,15 @@ packages: engines: {node: '>=12.2'} dev: true + /import-in-the-middle@1.7.4: + resolution: {integrity: sha512-Lk+qzWmiQuRPPulGQeK5qq0v32k2bHnWrRPFgqyvhw7Kkov5L6MOLOIU3pcWeujc9W4q54Cp3Q2WV16eQkc7Bg==} + dependencies: + acorn: 8.8.2 + acorn-import-attributes: 1.9.5(acorn@8.8.2) + cjs-module-lexer: 1.2.3 + module-details-from-path: 1.0.3 + dev: false + /import-lazy@2.1.0: resolution: {integrity: sha512-m7ZEHgtw69qOGw+jwxXkHlrlIPdTGkyh66zXZ1ajZbxkDBNjSY/LGbmjc7h0s2ELsUDTAhFr55TrPSSqJGPG0A==} engines: {node: '>=4'} @@ -14487,6 +14714,11 @@ packages: engines: {node: '>=8'} dev: true + /is-invalid-path@1.0.2: + resolution: {integrity: sha512-6KLcFrPCEP3AFXMfnWrIFkZpYNBVzZAoBJJDEZKtI3LXkaDjM3uFMJQjxiizUuZTZ9Oh9FNv/soXbx5TcpaDmA==} + engines: {node: '>=6.0'} + dev: false + /is-ip@2.0.0: resolution: {integrity: sha512-9MTn0dteHETtyUx8pxqMwg5hMBi3pvlyglJ+b79KOCca0po23337LbVV2Hl4xmMvfw++ljnO0/+5G6G+0Szh6g==} engines: {node: '>=4'} @@ -14496,7 +14728,6 @@ packages: /is-lambda@1.0.1: resolution: {integrity: sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==} - dev: true /is-map@2.0.2: resolution: {integrity: sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==} @@ -14787,7 +15018,7 @@ packages: /isexe@3.1.1: resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} engines: {node: '>=16'} - dev: true + requiresBuild: true /isobject@2.1.0: resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} @@ -14914,11 +15145,11 @@ packages: /jackspeak@2.3.6: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} engines: {node: '>=14'} + requiresBuild: true dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: true /jake@10.8.5: resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} @@ -15732,7 +15963,7 @@ packages: jest-util: 27.5.1 natural-compare: 1.4.0 pretty-format: 27.5.1 - semver: 7.3.8 + semver: 7.5.4 transitivePeerDependencies: - supports-color dev: false @@ -16116,6 +16347,12 @@ packages: engines: {node: '>=4'} hasBin: true + /json-bigint@1.0.0: + resolution: {integrity: sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ==} + dependencies: + bignumber.js: 9.0.0 + dev: false + /json-parse-better-errors@1.0.2: resolution: {integrity: sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==} @@ -16158,12 +16395,18 @@ packages: resolution: {integrity: sha512-gfFQZrcTc8CnKXp6Y4/CBT3fTc0OVuDofpre4aEeEpSBPV5X5v4+Vmx+8snU7RLPrNHPKSgLxGo9YuQzz20o+w==} dev: true + /jsonfile@4.0.0: + resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==} + optionalDependencies: + graceful-fs: 4.2.11 + dev: false + /jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} dependencies: universalify: 2.0.0 optionalDependencies: - graceful-fs: 4.2.10 + graceful-fs: 4.2.11 /jsonparse@1.3.1: resolution: {integrity: sha512-POQXvpdL69+CluYsillJ7SUhKvytYjW9vG/GKpnf+xP8UWgYEM/RaMzHHofbALDiKbbP1W8UEYmgGl39WkPZsg==} @@ -16183,6 +16426,10 @@ packages: engines: {node: '>=0.10.0'} dev: false + /jsonschema@1.4.1: + resolution: {integrity: sha512-S6cATIPVv1z0IlxdN+zUk5EPjkGCdnhN4wVSBlvoUO1tOLJootbo9CquNJmbIh4yikWHiUedhRYrNPn1arpEmQ==} + dev: false + /jsonwebtoken@8.5.1: resolution: {integrity: sha512-XjwVfRS6jTMsqYs0EsuJ4LGxXV14zQybNd4L2r0UvbVnSF9Af8x7p5MzbJ90Ioz/9TI41/hTCvznF/loiSzn8w==} engines: {node: '>=4', npm: '>=1.4.28'} @@ -16671,7 +16918,7 @@ packages: resolution: {integrity: sha512-Kx8hMakjX03tiGTLAIdJ+lL0htKnXjEZN6hk/tozf/WOuYGdZBJrZ+rCJRbVCugsjB3jMLn9746NsQIf5VjBMw==} engines: {node: '>=4'} dependencies: - graceful-fs: 4.2.11 + graceful-fs: 4.2.10 parse-json: 4.0.0 pify: 3.0.0 strip-bom: 3.0.0 @@ -16680,7 +16927,7 @@ packages: resolution: {integrity: sha512-gUD/epcRms75Cw8RT1pUdHugZYM5ce64ucs2GEISABwkRsOQr0q2wm/MV2TKThycIe5e0ytRweW2RZxclogCdQ==} engines: {node: '>=8'} dependencies: - graceful-fs: 4.2.11 + graceful-fs: 4.2.10 parse-json: 5.2.0 strip-bom: 4.0.0 type-fest: 0.6.0 @@ -16761,7 +17008,6 @@ packages: /lodash.camelcase@4.3.0: resolution: {integrity: sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==} - dev: true /lodash.castarray@4.4.0: resolution: {integrity: sha512-aVx8ztPv7/2ULbArGJ2Y42bG1mEQ5mGjpdvrbJcJFU3TbYybe+QlLS4pst9zV52ymy2in1KpFPiZnAOATxD4+Q==} @@ -16879,6 +17125,19 @@ packages: is-unicode-supported: 0.1.0 dev: true + /log4js@6.9.1: + resolution: {integrity: sha512-1somDdy9sChrr9/f4UlzhdaGfDR2c/SaD2a4T7qEkG4jTS57/B3qmnjLYePwQ8cqWnUHZI0iAKxMBpCZICiZ2g==} + engines: {node: '>=8.0'} + dependencies: + date-format: 4.0.14 + debug: 4.3.4(supports-color@5.5.0) + flatted: 3.2.7 + rfdc: 1.3.1 + streamroller: 3.1.5 + transitivePeerDependencies: + - supports-color + dev: false + /logform@2.5.1: resolution: {integrity: sha512-9FyqAm9o9NKKfiAKfZoYo9bGXXuwMkxQiQttkT4YjjVtQVIQtK6LmVtlxmCaFswo6N4AfEkHqZTV0taDtPotNg==} dependencies: @@ -16904,6 +17163,10 @@ packages: resolution: {integrity: sha512-XsP+KhQif4bjX1kbuSiySJFNAehNxgLb6hPRGJ9QsUr8ajHkuXGdrHmFUTUUXhDwVX2R5bY4JNZEwbUiMhV+MA==} dev: false + /long@5.2.3: + resolution: {integrity: sha512-lcHwpNoggQTObv5apGNCTdJrO69eHOZMi4BNC+rTLER8iHAqGrUVeLh/irVIM7zTw2bOXA8T6uNPeujwOLg/2Q==} + dev: false + /loose-envify@1.4.0: resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} hasBin: true @@ -16933,6 +17196,11 @@ packages: engines: {node: 14 || >=16.14} dev: true + /lru-cache@10.2.1: + resolution: {integrity: sha512-tS24spDe/zXhWbNPErCHs/AGOzbKGHT+ybSBqmdLm8WZ1xXLWvH8Qn71QPAlqVhd0qUTWjy+Kl9JmISgDdEjsA==} + engines: {node: 14 || >=16.14} + requiresBuild: true + /lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} dependencies: @@ -17026,6 +17294,7 @@ packages: /make-fetch-happen@13.0.0: resolution: {integrity: sha512-7ThobcL8brtGo9CavByQrQi+23aIfgYU++wg4B87AIS8Rb2ZBt/MEaDqzA00Xwv/jUjAjYkLHjVolYuTLKda2A==} engines: {node: ^16.14.0 || >=18.0.0} + requiresBuild: true dependencies: '@npmcli/agent': 2.2.2 cacache: 18.0.2 @@ -17040,7 +17309,6 @@ packages: ssri: 10.0.5 transitivePeerDependencies: - supports-color - dev: true /make-iterator@1.0.1: resolution: {integrity: sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==} @@ -17393,9 +17661,9 @@ packages: /minimatch@9.0.4: resolution: {integrity: sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==} engines: {node: '>=16 || 14 >=14.17'} + requiresBuild: true dependencies: brace-expansion: 2.0.1 - dev: true /minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} @@ -17422,27 +17690,26 @@ packages: /minipass-collect@2.0.1: resolution: {integrity: sha512-D7V8PO9oaz7PWGLbCACuI1qEOsq7UKfLotx/C0Aet43fCUB/wfQ7DYeq2oR/svFJGYDHPr38SHATeaj/ZoKHKw==} engines: {node: '>=16 || 14 >=14.17'} + requiresBuild: true dependencies: minipass: 7.0.4 - dev: true /minipass-fetch@3.0.4: resolution: {integrity: sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + requiresBuild: true dependencies: minipass: 7.0.4 minipass-sized: 1.0.3 minizlib: 2.1.2 optionalDependencies: encoding: 0.1.13 - dev: true /minipass-flush@1.0.5: resolution: {integrity: sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==} engines: {node: '>= 8'} dependencies: minipass: 3.3.6 - dev: true /minipass-json-stream@1.0.1: resolution: {integrity: sha512-ODqY18UZt/I8k+b7rl2AENgbWE8IDYam+undIJONvigAz8KR5GWblsFTEfQs0WODsjbSXWlm+JHEv8Gr6Tfdbg==} @@ -17456,26 +17723,22 @@ packages: engines: {node: '>=8'} dependencies: minipass: 3.3.6 - dev: true /minipass-sized@1.0.3: resolution: {integrity: sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==} engines: {node: '>=8'} dependencies: minipass: 3.3.6 - dev: true /minipass@3.3.6: resolution: {integrity: sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==} engines: {node: '>=8'} dependencies: yallist: 4.0.0 - dev: true /minipass@4.0.2: resolution: {integrity: sha512-4Hbzei7ZyBp+1aw0874YWpKOubZd/jc53/XU+gkYry1QV+VvrbO8icLM5CUtm4F0hyXn85DXYKEMIS26gitD3A==} engines: {node: '>=8'} - dev: true /minipass@4.2.8: resolution: {integrity: sha512-fNzuVyifolSLFL4NzpF+wEF4qrgqaaKX0haXPQEdQ7NKAN+WecoKMHV09YcuL/DHxrUsYQOK3MiuDf7Ip2OXfQ==} @@ -17490,7 +17753,7 @@ packages: /minipass@7.0.4: resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} engines: {node: '>=16 || 14 >=14.17'} - dev: true + requiresBuild: true /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} @@ -17498,7 +17761,6 @@ packages: dependencies: minipass: 3.3.6 yallist: 4.0.0 - dev: true /mixin-deep@1.3.2: resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} @@ -17508,6 +17770,12 @@ packages: is-extendable: 1.0.1 dev: false + /mkdirp-classic@0.5.3: + resolution: {integrity: sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==} + requiresBuild: true + dev: false + optional: true + /mkdirp@0.5.1: resolution: {integrity: sha512-SknJC52obPfGQPnjIkXbmA6+5H15E+fR+E4iR2oQ3zzCLbd7/ONua69R/Gw7AgkTLsRG+r5fzksYwWe1AgTyWA==} deprecated: Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.) @@ -17526,7 +17794,6 @@ packages: resolution: {integrity: sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==} engines: {node: '>=10'} hasBin: true - dev: true /mocha@5.2.0: resolution: {integrity: sha512-2IUgKDhc3J7Uug+FxMXuqIyYzH7gJjXECKe/w43IGgQHTSj3InJi+yAA7T24L9bQMRKiUEHxEX37G5JpVUGLcQ==} @@ -17551,6 +17818,10 @@ packages: engines: {node: '>=0.10.0'} dev: true + /module-details-from-path@1.0.3: + resolution: {integrity: sha512-ySViT69/76t8VhE1xXHK6Ch4NcDd26gx0MzKXLO+F7NOtnqH68d9zF94nT8ZWSxXh8ELOERsnJO/sWt1xZYw5A==} + dev: false + /moment-range@4.0.2(moment@2.29.4): resolution: {integrity: sha512-n8sceWwSTjmz++nFHzeNEUsYtDqjgXgcOBzsHi+BoXQU2FW+eU92LUaK8gqOiSu5PG57Q9sYj1Fz4LRDj4FtKA==} peerDependencies: @@ -17827,6 +18098,12 @@ packages: dev: false optional: true + /nan@2.19.0: + resolution: {integrity: sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==} + requiresBuild: true + dev: false + optional: true + /nano-css@5.3.5(react-dom@18.2.0)(react@18.2.0): resolution: {integrity: sha512-vSB9X12bbNu4ALBu7nigJgRViZ6ja3OU7CeuiV1zMIbXOdmkLahgtPmh3GBOlDxbKY0CitqlPdOReGlBLSp+yg==} peerDependencies: @@ -17896,6 +18173,36 @@ packages: /nested-error-stacks@2.1.1: resolution: {integrity: sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==} + /newrelic@11.16.0: + resolution: {integrity: sha512-oEgsJxK9IgP11DS/rZ78S6lUUGNLwGY91tGCIzGkEkhjFpi0nzWp2LaIRDv6hiNX5lQTZb2RWufRFN42f3XqTw==} + engines: {node: '>=16', npm: '>=6.0.0'} + hasBin: true + dependencies: + '@grpc/grpc-js': 1.10.7 + '@grpc/proto-loader': 0.7.13 + '@newrelic/ritm': 7.2.0 + '@newrelic/security-agent': 1.2.0 + '@tyriar/fibonacci-heap': 2.0.9 + concat-stream: 2.0.0 + https-proxy-agent: 7.0.4 + import-in-the-middle: 1.7.4 + json-bigint: 1.0.0 + json-stringify-safe: 5.0.1 + module-details-from-path: 1.0.3 + readable-stream: 3.6.2 + semver: 7.5.4 + winston-transport: 4.5.0 + optionalDependencies: + '@contrast/fn-inspect': 3.4.0 + '@newrelic/native-metrics': 10.1.1 + '@prisma/prisma-fmt-wasm': 4.17.0-16.27eb2449f178cd9fe1a4b892d732cc4795f75085 + transitivePeerDependencies: + - bufferutil + - debug + - supports-color + - utf-8-validate + dev: false + /next-tick@1.1.0: resolution: {integrity: sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==} dev: false @@ -17925,6 +18232,15 @@ packages: engines: {node: '>=4.0.0'} dev: false + /node-abi@3.62.0: + resolution: {integrity: sha512-CPMcGa+y33xuL1E0TcNIu4YyaZCxnnvkVaEXrsosR3FxN+fV8xvb7Mzpb7IgKler10qeMkE6+Dp8qJhpzdq35g==} + engines: {node: '>=10'} + requiresBuild: true + dependencies: + semver: 7.5.4 + dev: false + optional: true + /node-cache@4.2.1: resolution: {integrity: sha512-BOb67bWg2dTyax5kdef5WfU3X8xu4wPg+zHzkvls0Q/QpYycIFRLEEIdAx9Wma43DxG6Qzn4illdZoYseKWa4A==} engines: {node: '>= 0.4.6'} @@ -17967,15 +18283,23 @@ packages: engines: {node: '>= 6.13.0'} dev: false + /node-gyp-build@4.8.1: + resolution: {integrity: sha512-OSs33Z9yWr148JZcbZd5WiAXhh/n9z8TxQcdMhIOlpN9AhWpLfvVFO73+m77bBABQMaY9XSvIa+qk0jlI7Gcaw==} + hasBin: true + requiresBuild: true + dev: false + optional: true + /node-gyp@10.1.0: resolution: {integrity: sha512-B4J5M1cABxPc5PwfjhbV5hoy2DP9p8lFXASnEN6hugXOa61416tnTZ29x9sSwAd0o99XNIcpvDDy1swAExsVKA==} engines: {node: ^16.14.0 || >=18.0.0} hasBin: true + requiresBuild: true dependencies: env-paths: 2.2.1 exponential-backoff: 3.1.1 glob: 10.3.12 - graceful-fs: 4.2.11 + graceful-fs: 4.2.10 make-fetch-happen: 13.0.0 nopt: 7.2.0 proc-log: 3.0.0 @@ -17984,7 +18308,6 @@ packages: which: 4.0.0 transitivePeerDependencies: - supports-color - dev: true /node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -18077,9 +18400,9 @@ packages: resolution: {integrity: sha512-CVDtwCdhYIvnAzFoJ6NJ6dX3oga9/HyciQDnG1vQDjSLMeKLJ4A93ZqYKDrgYSr1FBY5/hMYC+2VCi24pgpkGA==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} hasBin: true + requiresBuild: true dependencies: abbrev: 2.0.0 - dev: true /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -18095,7 +18418,7 @@ packages: dependencies: hosted-git-info: 4.1.0 is-core-module: 2.11.0 - semver: 7.3.8 + semver: 7.5.4 validate-npm-package-license: 3.0.4 dev: true @@ -18205,7 +18528,7 @@ packages: engines: {node: '>=10'} dependencies: hosted-git-info: 3.0.8 - semver: 7.5.4 + semver: 7.3.8 validate-npm-package-name: 3.0.0 dev: true @@ -18291,6 +18614,15 @@ packages: path-key: 2.0.1 dev: false + /npm-run-path@3.1.0: + resolution: {integrity: sha512-Dbl4A/VfiVGLgQv29URL9xshU8XDY1GeLy+fsaZ1AA8JDSfjvr5P5+pzRbWqRSBxk6/DW7MIh8lTM/PaGnP2kg==} + engines: {node: '>=8'} + requiresBuild: true + dependencies: + path-key: 3.1.1 + dev: false + optional: true + /npm-run-path@4.0.1: resolution: {integrity: sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==} engines: {node: '>=8'} @@ -18889,7 +19221,6 @@ packages: engines: {node: '>=10'} dependencies: aggregate-error: 3.1.0 - dev: true /p-pipe@3.1.0: resolution: {integrity: sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw==} @@ -19019,6 +19350,10 @@ packages: safe-buffer: 5.2.1 dev: true + /parse-cache-control@1.0.1: + resolution: {integrity: sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==} + dev: false + /parse-filepath@1.0.2: resolution: {integrity: sha512-FwdRXKCohSVeXqwtYonZTXtbGJKrn+HNyWDYVcp5yuJlesTwNH4rsmRZ+GrKAPJ5bLpRxESMeS+Rl0VCHRvB2Q==} engines: {node: '>=0.8'} @@ -19168,10 +19503,10 @@ packages: /path-scurry@1.10.2: resolution: {integrity: sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==} engines: {node: '>=16 || 14 >=14.17'} + requiresBuild: true dependencies: - lru-cache: 10.2.0 + lru-cache: 10.2.1 minipass: 7.0.4 - dev: true /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -20195,6 +20530,20 @@ packages: picocolors: 1.0.0 source-map-js: 1.0.2 + /prebuildify@6.0.1: + resolution: {integrity: sha512-8Y2oOOateom/s8dNBsGIcnm6AxPmLH4/nanQzL5lQMU+sC0CMhzARZHizwr36pUPLdvBnOkCNQzxg4djuFSgIw==} + hasBin: true + requiresBuild: true + dependencies: + minimist: 1.2.7 + mkdirp-classic: 0.5.3 + node-abi: 3.62.0 + npm-run-path: 3.1.0 + pump: 3.0.0 + tar-fs: 2.1.1 + dev: false + optional: true + /prelude-ls@1.1.2: resolution: {integrity: sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==} engines: {node: '>= 0.8.0'} @@ -20295,7 +20644,7 @@ packages: /proc-log@3.0.0: resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} - dev: true + requiresBuild: true /proc-log@4.2.0: resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} @@ -20345,7 +20694,6 @@ packages: dependencies: err-code: 2.0.3 retry: 0.12.0 - dev: true /promise@7.3.1: resolution: {integrity: sha512-nolQXZ/4L+bP/UGlkfaIujX9BKxGwmQ9OT4mOt5yvy8iK1h3wqTEJCijzGANTCCl9nWjY41juyAn2K3Q1hLLTg==} @@ -20546,6 +20894,25 @@ packages: prosemirror-transform: 1.8.0 dev: false + /protobufjs@7.2.6: + resolution: {integrity: sha512-dgJaEDDL6x8ASUZ1YqWciTRrdOuYNzoOf27oHNfdyvKqHr5i0FV7FSLU+aIeFjyFgVxrpTOtQUi0BLLBymZaBw==} + engines: {node: '>=12.0.0'} + requiresBuild: true + dependencies: + '@protobufjs/aspromise': 1.1.2 + '@protobufjs/base64': 1.1.2 + '@protobufjs/codegen': 2.0.4 + '@protobufjs/eventemitter': 1.1.0 + '@protobufjs/fetch': 1.1.0 + '@protobufjs/float': 1.0.2 + '@protobufjs/inquire': 1.1.0 + '@protobufjs/path': 1.1.2 + '@protobufjs/pool': 1.1.0 + '@protobufjs/utf8': 1.1.0 + '@types/node': 14.18.36 + long: 5.2.3 + dev: false + /protocols@2.0.1: resolution: {integrity: sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==} dev: true @@ -21796,6 +22163,14 @@ packages: string_decoder: 1.3.0 util-deprecate: 1.0.2 + /readable-stream@3.6.2: + resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} + engines: {node: '>= 6'} + dependencies: + inherits: 2.0.4 + string_decoder: 1.3.0 + util-deprecate: 1.0.2 + /readdirp@2.2.1(supports-color@5.5.0): resolution: {integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==} engines: {node: '>=0.10'} @@ -21824,7 +22199,7 @@ packages: resolution: {integrity: sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==} engines: {node: '>= 0.10'} dependencies: - resolve: 1.22.1 + resolve: 1.22.6 dev: false /rechoir@0.7.0: @@ -22111,6 +22486,10 @@ packages: remove-trailing-separator: 1.1.0 dev: false + /request-ip@3.3.0: + resolution: {integrity: sha512-cA6Xh6e0fDBBBwH77SLJaJPBmD3nWVAcF9/XAcsrIHdjhFzFiB5aNQFytdjCGPezU3ROwrR11IddKAM08vohxA==} + dev: false + /request-promise-core@1.1.4(request@2.88.2): resolution: {integrity: sha512-TTbAfBBRdWD7aNNOoVOBH4pN/KigV6LyapYNNlAPA8JwbovRti1E88m3sYAwsLi5ryhPKsE9APwnjFTgdUjTpw==} engines: {node: '>=0.10.0'} @@ -22327,7 +22706,6 @@ packages: /retry@0.12.0: resolution: {integrity: sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==} engines: {node: '>= 4'} - dev: true /retry@0.13.1: resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} @@ -22338,6 +22716,10 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + /rfdc@1.3.1: + resolution: {integrity: sha512-r5a3l5HzYlIC68TpmYKlxWjmOP6wiPJ1vWv2HeLhNsRZMrCkxeqxiHlQ21oXmQ4F3SiryXBHhAD7JZqvOJjFmg==} + dev: false + /rimraf@2.7.1: resolution: {integrity: sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==} hasBin: true @@ -22358,6 +22740,10 @@ packages: glob: 9.3.5 dev: true + /ringbufferjs@2.0.0: + resolution: {integrity: sha512-GCOqTzUsTHF7nrqcgtNGAFotXztLgiePpIDpyWZ7R5I02tmfJWV+/yuJc//Hlsd8G+WzI1t/dc2y/w2imDZdog==} + dev: false + /ripemd160@2.0.2: resolution: {integrity: sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==} dependencies: @@ -22875,7 +23261,7 @@ packages: /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} - dev: true + requiresBuild: true /sigstore@1.9.0: resolution: {integrity: sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==} @@ -23052,7 +23438,7 @@ packages: engines: {node: '>= 10'} dependencies: agent-base: 6.0.2 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) socks: 2.7.1 transitivePeerDependencies: - supports-color @@ -23061,13 +23447,13 @@ packages: /socks-proxy-agent@8.0.3: resolution: {integrity: sha512-VNegTZKhuGq5vSD6XNKlbqWhyt/40CgoEw8XxD6dhnm8Jq9IEa3nIa4HwnM8XOqU0CdB0BwWVXusqiFXfHB3+A==} engines: {node: '>= 14'} + requiresBuild: true dependencies: agent-base: 7.1.1 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) socks: 2.7.1 transitivePeerDependencies: - supports-color - dev: true /socks@2.7.1: resolution: {integrity: sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==} @@ -23304,9 +23690,9 @@ packages: /ssri@10.0.5: resolution: {integrity: sha512-bSf16tAFkGeRlUNDjXu8FzaMQt6g2HZJrun7mtMbIPOddxt3GLMSz5VWUWcqTJUPfLEaDIepGxv+bYQW49596A==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + requiresBuild: true dependencies: minipass: 7.0.4 - dev: true /ssri@9.0.1: resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==} @@ -23433,6 +23819,17 @@ packages: resolution: {integrity: sha512-AiisoFqQ0vbGcZgQPY1cdP2I76glaVA/RauYR4G4thNFgkTqr90yXTo4LYX60Jl+sIlPNHHdGSwo01AvbKUSVQ==} dev: false + /streamroller@3.1.5: + resolution: {integrity: sha512-KFxaM7XT+irxvdqSP1LGLgNWbYN7ay5owZ3r/8t77p+EtSUAfUgtl7be3xtqtOmGUl9K9YPO2ca8133RlTjvKw==} + engines: {node: '>=8.0'} + dependencies: + date-format: 4.0.14 + debug: 4.3.4(supports-color@5.5.0) + fs-extra: 8.1.0 + transitivePeerDependencies: + - supports-color + dev: false + /streamsearch@1.1.0: resolution: {integrity: sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==} engines: {node: '>=10.0.0'} @@ -23515,11 +23912,15 @@ packages: /string-width@5.1.2: resolution: {integrity: sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA==} engines: {node: '>=12'} + requiresBuild: true dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 strip-ansi: 7.1.0 - dev: true + + /string.fromcodepoint@0.2.1: + resolution: {integrity: sha512-n69H31OnxSGSZyZbgBlvYIXlrMhJQ0dQAX1js1QDhpaUH6zmU3QYlj07bCwCNlPOu3oRXIubGPl2gDGnHsiCqg==} + dev: false /string.prototype.matchall@4.0.8: resolution: {integrity: sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==} @@ -23891,6 +24292,21 @@ packages: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} dev: false + /sync-request@6.1.0: + resolution: {integrity: sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==} + engines: {node: '>=8.0.0'} + dependencies: + http-response-object: 3.0.2 + sync-rpc: 1.3.6 + then-request: 6.0.2 + dev: false + + /sync-rpc@1.3.6: + resolution: {integrity: sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==} + dependencies: + get-port: 3.2.0 + dev: false + /synchronous-promise@2.0.17: resolution: {integrity: sha512-AsS729u2RHUfEra9xJrE39peJcc2stq2+poBXX8bcM08Y6g9j/i/PUzwNQqkaJde7Ntg1TO7bSREbR5sdosQ+g==} dev: false @@ -23957,6 +24373,17 @@ packages: tar-stream: 2.2.0 dev: false + /tar-fs@2.1.1: + resolution: {integrity: sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==} + requiresBuild: true + dependencies: + chownr: 1.1.4 + mkdirp-classic: 0.5.3 + pump: 3.0.0 + tar-stream: 2.2.0 + dev: false + optional: true + /tar-stream@2.2.0: resolution: {integrity: sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==} engines: {node: '>=6'} @@ -23989,7 +24416,6 @@ packages: minizlib: 2.1.2 mkdirp: 1.0.4 yallist: 4.0.0 - dev: true /tarn@3.0.2: resolution: {integrity: sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==} @@ -24094,6 +24520,23 @@ packages: /text-table@0.2.0: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} + /then-request@6.0.2: + resolution: {integrity: sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==} + engines: {node: '>=6.0.0'} + dependencies: + '@types/concat-stream': 1.6.1 + '@types/form-data': 0.0.33 + '@types/node': 8.10.66 + '@types/qs': 6.9.8 + caseless: 0.12.0 + concat-stream: 1.6.2 + form-data: 2.5.1 + http-basic: 8.1.3 + http-response-object: 3.0.2 + promise: 8.3.0 + qs: 6.11.0 + dev: false + /thenify-all@1.6.0: resolution: {integrity: sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==} engines: {node: '>=0.8'} @@ -24518,7 +24961,7 @@ packages: engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} dependencies: '@tufjs/models': 1.0.4 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) make-fetch-happen: 11.1.1 transitivePeerDependencies: - supports-color @@ -24529,7 +24972,7 @@ packages: engines: {node: ^16.14.0 || >=18.0.0} dependencies: '@tufjs/models': 2.0.0 - debug: 4.3.4 + debug: 4.3.4(supports-color@5.5.0) make-fetch-happen: 13.0.0 transitivePeerDependencies: - supports-color @@ -24764,6 +25207,19 @@ packages: undertaker-registry: 1.0.1 dev: false + /unescape-js@1.1.4: + resolution: {integrity: sha512-42SD8NOQEhdYntEiUQdYq/1V/YHwr1HLwlHuTJB5InVVdOSbgI6xu8jK5q65yIzuFCfczzyDF/7hbGzVbyCw0g==} + dependencies: + string.fromcodepoint: 0.2.1 + dev: false + + /unescape@1.0.1: + resolution: {integrity: sha512-O0+af1Gs50lyH1nUu3ZyYS1cRh01Q/kUKatTOkSs7jukXE6/NebucDVxyiDsA9AQ4JC1V1jUH9EO8JX2nMDgGQ==} + engines: {node: '>=0.10.0'} + dependencies: + extend-shallow: 2.0.1 + dev: false + /unicode-canonical-property-names-ecmascript@2.0.0: resolution: {integrity: sha512-yY5PpDlfVIU5+y/BSCxAJRBIS1Zc2dDG3Ujq+sR0U+JjUevW2JhocOF+soROYDSaAezOzOKuyyixhD6mBknSmQ==} engines: {node: '>=4'} @@ -24804,16 +25260,16 @@ packages: /unique-filename@3.0.0: resolution: {integrity: sha512-afXhuC55wkAmZ0P18QsVE6kp8JaxrEokN2HGIoIVv2ijHQd419H0+6EigAFcIzXeMIkcIkNBpB3L/DXB3cTS/g==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + requiresBuild: true dependencies: unique-slug: 4.0.0 - dev: true /unique-slug@4.0.0: resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + requiresBuild: true dependencies: imurmurhash: 0.1.4 - dev: true /unique-stream@2.3.1: resolution: {integrity: sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==} @@ -24840,6 +25296,11 @@ packages: resolution: {integrity: sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==} dev: true + /universalify@0.1.2: + resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==} + engines: {node: '>= 4.0.0'} + dev: false + /universalify@0.2.0: resolution: {integrity: sha512-CJ1QgKmNg3CwvAv/kOFmtnEN05f0D/cn9QntgNOQlQF9dgvVTHj3t+8JPdjqawCHk7V/KA+fbUqzZ9XWhcqPUg==} engines: {node: '>= 4.0.0'} @@ -25029,7 +25490,6 @@ packages: /uuid@9.0.1: resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} hasBin: true - dev: true /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -25590,9 +26050,9 @@ packages: resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} engines: {node: ^16.13.0 || >=18.0.0} hasBin: true + requiresBuild: true dependencies: isexe: 3.1.1 - dev: true /wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} @@ -25861,11 +26321,11 @@ packages: /wrap-ansi@8.1.0: resolution: {integrity: sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ==} engines: {node: '>=12'} + requiresBuild: true dependencies: ansi-styles: 6.2.1 string-width: 5.1.2 strip-ansi: 7.1.0 - dev: true /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -25881,7 +26341,7 @@ packages: /write-file-atomic@2.4.3: resolution: {integrity: sha512-GaETH5wwsX+GcnzhPgKcKjJ6M2Cq3/iZp1WyY/X1CSqrW+jVNM9Y7D8EC2sM4ZG/V8wZlSniJnCKWPmBYAucRQ==} dependencies: - graceful-fs: 4.2.11 + graceful-fs: 4.2.10 imurmurhash: 0.1.4 signal-exit: 3.0.7 @@ -25907,7 +26367,7 @@ packages: engines: {node: '>=6'} dependencies: detect-indent: 5.0.0 - graceful-fs: 4.2.11 + graceful-fs: 4.2.10 make-dir: 2.1.0 pify: 4.0.1 sort-keys: 2.0.0 @@ -26082,7 +26542,6 @@ packages: /yargs-parser@21.1.1: resolution: {integrity: sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==} engines: {node: '>=12'} - dev: true /yargs-parser@5.0.1: resolution: {integrity: sha512-wpav5XYiddjXxirPoCTUPbqM0PXvJ9hiBMvuJgInvo4/lAOTZzUprArw17q2O1P2+GHhbBr18/iQwjL5Z9BqfA==} @@ -26141,7 +26600,6 @@ packages: string-width: 4.2.3 y18n: 5.0.8 yargs-parser: 21.1.1 - dev: true /yargs@7.1.2: resolution: {integrity: sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index ccdc80cde..067a01bf0 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -1,2 +1,3 @@ packages: - - "packages/*" \ No newline at end of file + # all packages in direct subdirs of packages/ + - 'packages/*'