From 4368c1847970defe663242b3df6970644a9ed662 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Fri, 26 Apr 2024 12:21:40 +0200 Subject: [PATCH 01/27] feat: User email verification after signing-up. --- .../src/api/controllers/Authentication.ts | 79 ++++ .../server/src/interfaces/Authentication.ts | 21 +- packages/server/src/loaders/eventEmitter.ts | 4 +- packages/server/src/loaders/jobs.ts | 2 + .../Authentication/AuthApplication.ts | 33 ++ .../src/services/Authentication/AuthSignup.ts | 3 + .../Authentication/AuthSignupConfirm.ts | 57 +++ .../Authentication/AuthSignupResend.ts | 35 ++ .../AuthenticationMailMessages.ts | 30 ++ .../src/services/Authentication/_constants.ts | 2 + .../events/SendVerfiyMailOnSignUp.ts | 30 ++ .../Authentication/jobs/SendVerifyMailJob.ts | 35 ++ packages/server/src/subscribers/events.ts | 3 + ...00821_add_confirmation_columns_to_users.js | 8 + .../server/src/system/models/SystemUser.ts | 20 +- .../server/views/mail/SignupVerifyEmail.html | 424 ++++++++++++++++++ 16 files changed, 778 insertions(+), 8 deletions(-) create mode 100644 packages/server/src/services/Authentication/AuthSignupConfirm.ts create mode 100644 packages/server/src/services/Authentication/AuthSignupResend.ts create mode 100644 packages/server/src/services/Authentication/events/SendVerfiyMailOnSignUp.ts create mode 100644 packages/server/src/services/Authentication/jobs/SendVerifyMailJob.ts create mode 100644 packages/server/src/system/migrations/20240425100821_add_confirmation_columns_to_users.js create mode 100644 packages/server/views/mail/SignupVerifyEmail.html diff --git a/packages/server/src/api/controllers/Authentication.ts b/packages/server/src/api/controllers/Authentication.ts index aae43f4b4..c162508ba 100644 --- a/packages/server/src/api/controllers/Authentication.ts +++ b/packages/server/src/api/controllers/Authentication.ts @@ -28,6 +28,20 @@ export default class AuthenticationController extends BaseController { asyncMiddleware(this.login.bind(this)), this.handlerErrors ); + router.post( + 'register/verify/resend', + [check('email').exists().isEmail()], + this.validationResult, + 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, @@ -99,6 +113,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[]} @@ -166,6 +191,60 @@ 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 = 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); + } + } + + /** + * + * @param {Request} req + * @param {Response}| res + * @param {Function} next + * @returns + */ + private async registerVerifyResendMail( + req: Request, + res: Response, + next: Function + ) { + const signUpVerifyDTO = 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); + } + } + /** * Send reset password handler * @param {Request} req diff --git a/packages/server/src/interfaces/Authentication.ts b/packages/server/src/interfaces/Authentication.ts index 253c178f9..af29d0b5f 100644 --- a/packages/server/src/interfaces/Authentication.ts +++ b/packages/server/src/interfaces/Authentication.ts @@ -66,16 +66,27 @@ 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 { +export interface IAuthGetMetaPOJO { signupDisabled: 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/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/services/Authentication/AuthApplication.ts b/packages/server/src/services/Authentication/AuthApplication.ts index 9fa74c973..bbc547f1f 100644 --- a/packages/server/src/services/Authentication/AuthApplication.ts +++ b/packages/server/src/services/Authentication/AuthApplication.ts @@ -9,6 +9,13 @@ import { AuthSigninService } from './AuthSignin'; import { AuthSignupService } from './AuthSignup'; import { AuthSendResetPassword } from './AuthSendResetPassword'; import { GetAuthMeta } from './GetAuthMeta'; +import { AuthSignupConfirmService } from './AuthSignupConfirm'; +import { SystemUser } from '@/system/models'; + +interface ISignupConfirmDTO { + token: string; + email: string; +} @Service() export default class AuthenticationApplication { @@ -18,6 +25,9 @@ export default class AuthenticationApplication { @Inject() private authSignupService: AuthSignupService; + @Inject() + private authSignupConfirmService: AuthSignupConfirmService; + @Inject() private authResetPasswordService: AuthSendResetPassword; @@ -44,6 +54,29 @@ 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); + } + + /** + * + * @param {string} email + * @param {string} token + * @returns + */ + public async signUpConfirmSend(email: string, token: string) { + return this.authSignupConfirmService.signUpConfirm(email, token); + } + /** * 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..2be8c63a2 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 moment from 'moment'; +import crypto from 'crypto'; import { ServiceError } from '@/exceptions'; import { IAuthSignedUpEventPayload, @@ -41,6 +42,7 @@ export class AuthSignupService { await this.validateEmailUniqiness(signupDTO.email); const hashedPassword = await hashPassword(signupDTO.password); + const verifyToken = crypto.randomBytes(64).toString('hex'); // Triggers signin up event. await this.eventPublisher.emitAsync(events.auth.signingUp, { @@ -50,6 +52,7 @@ export class AuthSignupService { const tenant = await this.tenantsManager.createTenant(); const registeredUser = await systemUserRepository.create({ ...omit(signupDTO, 'country'), + verifyToken, active: true, password: hashedPassword, tenantId: tenant.id, diff --git a/packages/server/src/services/Authentication/AuthSignupConfirm.ts b/packages/server/src/services/Authentication/AuthSignupConfirm.ts new file mode 100644 index 000000000..940b6ae7f --- /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; + } +} diff --git a/packages/server/src/services/Authentication/AuthSignupResend.ts b/packages/server/src/services/Authentication/AuthSignupResend.ts new file mode 100644 index 000000000..581a20236 --- /dev/null +++ b/packages/server/src/services/Authentication/AuthSignupResend.ts @@ -0,0 +1,35 @@ +import { ServiceError } from '@/exceptions'; +import { SystemUser } from '@/system/models'; +import { Inject, Service } from 'typedi'; +import { ERRORS } from './_constants'; + +@Service() +export class AuthSignupConfirmResend { + @Inject('agenda') + private agenda: any; + + /** + * + * @param {number} tenantId + * @param {string} email + */ + public async signUpConfirmResend(email: string) { + const user = await SystemUser.query() + .findOne({ email }) + .throwIfNotFound(); + + // + if (user.verified) { + throw new ServiceError(ERRORS.USER_ALREADY_VERIFIED) + } + 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..e974d22b9 100644 --- a/packages/server/src/services/Authentication/AuthenticationMailMessages.ts +++ b/packages/server/src/services/Authentication/AuthenticationMailMessages.ts @@ -33,4 +33,34 @@ 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, + ) { + 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: `${config.baseURL}/auth/reset_password/${token}`, + 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/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..125f508f5 --- /dev/null +++ b/packages/server/src/system/migrations/20240425100821_add_confirmation_columns_to_users.js @@ -0,0 +1,8 @@ +exports.up = function (knex) { + return knex.schema.table('users', (table) => { + table.string('verify_token'); + table.boolean('verified').defaultTo(false); + }); +}; + +exports.down = (knex) => {}; diff --git a/packages/server/src/system/models/SystemUser.ts b/packages/server/src/system/models/SystemUser.ts index a341ccedf..627caaeb6 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. */ @@ -33,19 +39,29 @@ export default class SystemUser extends SystemModel { } /** - * + * 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/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. + + + + + + + + + From b9fc0cdd9e3b52dbc541827dfc3b7bb0b25f6722 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 28 Apr 2024 17:51:11 +0200 Subject: [PATCH 02/27] feat: wip email confirmation --- .../src/api/controllers/Authentication.ts | 21 +++--- .../Authentication/AuthApplication.ts | 4 + .../Authentication/AuthSignupResend.ts | 10 +-- packages/webapp/src/components/App.tsx | 26 ++++++- .../Guards/EnsureAuthNotAuthenticated.tsx | 14 ++++ .../Guards/EnsureUserEmailVerified.tsx | 22 ++++++ .../src/components/Guards/PrivateRoute.tsx | 18 ++--- .../Authentication/AuthContainer.tsx | 34 +++++++++ .../Authentication/Authentication.tsx | 9 +-- .../Authentication/EmailConfirmation.tsx | 27 +++++++ .../Authentication/RegisterVerify.module.scss | 18 +++++ .../Authentication/RegisterVerify.tsx | 74 +++++++++++++++++++ .../webapp/src/hooks/query/authentication.tsx | 27 +++++++ .../webapp/src/hooks/state/authentication.tsx | 7 ++ packages/webapp/src/routes/authentication.tsx | 8 +- 15 files changed, 283 insertions(+), 36 deletions(-) create mode 100644 packages/webapp/src/components/Guards/EnsureAuthNotAuthenticated.tsx create mode 100644 packages/webapp/src/components/Guards/EnsureUserEmailVerified.tsx create mode 100644 packages/webapp/src/containers/Authentication/AuthContainer.tsx create mode 100644 packages/webapp/src/containers/Authentication/EmailConfirmation.tsx create mode 100644 packages/webapp/src/containers/Authentication/RegisterVerify.module.scss create mode 100644 packages/webapp/src/containers/Authentication/RegisterVerify.tsx diff --git a/packages/server/src/api/controllers/Authentication.ts b/packages/server/src/api/controllers/Authentication.ts index c162508ba..87ac2166c 100644 --- a/packages/server/src/api/controllers/Authentication.ts +++ b/packages/server/src/api/controllers/Authentication.ts @@ -9,6 +9,8 @@ import { DATATYPES_LENGTH } from '@/data/DataTypes'; import LoginThrottlerMiddleware from '@/api/middleware/LoginThrottlerMiddleware'; import AuthenticationApplication from '@/services/Authentication/AuthApplication'; +import JWTAuth from '@/api/middleware/jwtAuth'; +import AttachCurrentTenantUser from '@/api/middleware/AttachCurrentTenantUser'; @Service() export default class AuthenticationController extends BaseController { @Inject() @@ -28,10 +30,10 @@ 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', - [check('email').exists().isEmail()], - this.validationResult, + '/register/verify/resend', asyncMiddleware(this.registerVerifyResendMail.bind(this)), this.handlerErrors ); @@ -199,7 +201,8 @@ export default class AuthenticationController extends BaseController { * @returns {Response|void} */ private async registerVerify(req: Request, res: Response, next: Function) { - const signUpVerifyDTO = this.matchedBodyData(req); + const signUpVerifyDTO: { email: string; token: string } = + this.matchedBodyData(req); try { const user = await this.authApplication.signUpConfirm( @@ -228,17 +231,15 @@ export default class AuthenticationController extends BaseController { res: Response, next: Function ) { - const signUpVerifyDTO = this.matchedBodyData(req); + const { user } = req; try { - const user = await this.authApplication.signUpConfirm( - signUpVerifyDTO.email, - signUpVerifyDTO.token - ); + const data = await this.authApplication.signUpConfirm(user.id); + return res.status(200).send({ type: 'success', message: 'The given user has verified successfully', - user, + data, }); } catch (error) { next(error); diff --git a/packages/server/src/services/Authentication/AuthApplication.ts b/packages/server/src/services/Authentication/AuthApplication.ts index bbc547f1f..47f2168a3 100644 --- a/packages/server/src/services/Authentication/AuthApplication.ts +++ b/packages/server/src/services/Authentication/AuthApplication.ts @@ -11,6 +11,7 @@ import { AuthSendResetPassword } from './AuthSendResetPassword'; import { GetAuthMeta } from './GetAuthMeta'; import { AuthSignupConfirmService } from './AuthSignupConfirm'; import { SystemUser } from '@/system/models'; +import { AuthSignupConfirmResend } from './AuthSignupResend'; interface ISignupConfirmDTO { token: string; @@ -28,6 +29,9 @@ export default class AuthenticationApplication { @Inject() private authSignupConfirmService: AuthSignupConfirmService; + @Inject() + private authSignUpConfirmResendService: AuthSignupConfirmResend; + @Inject() private authResetPasswordService: AuthSendResetPassword; diff --git a/packages/server/src/services/Authentication/AuthSignupResend.ts b/packages/server/src/services/Authentication/AuthSignupResend.ts index 581a20236..5c764e80c 100644 --- a/packages/server/src/services/Authentication/AuthSignupResend.ts +++ b/packages/server/src/services/Authentication/AuthSignupResend.ts @@ -13,14 +13,12 @@ export class AuthSignupConfirmResend { * @param {number} tenantId * @param {string} email */ - public async signUpConfirmResend(email: string) { - const user = await SystemUser.query() - .findOne({ email }) - .throwIfNotFound(); + public async signUpConfirmResend(userId: number) { + const user = await SystemUser.query().findById(userId).throwIfNotFound(); - // + // if (user.verified) { - throw new ServiceError(ERRORS.USER_ALREADY_VERIFIED) + throw new ServiceError(ERRORS.USER_ALREADY_VERIFIED); } if (user.verifyToken) { throw new ServiceError(ERRORS.USER_ALREADY_VERIFIED); diff --git a/packages/webapp/src/components/App.tsx b/packages/webapp/src/components/App.tsx index 949a1861f..57effb72a 100644 --- a/packages/webapp/src/components/App.tsx +++ b/packages/webapp/src/components/App.tsx @@ -14,8 +14,15 @@ 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'; + +const RegisterVerify = LazyLoader({ + loader: () => import('@/containers/Authentication/RegisterVerify'), +}); /** * App inner. @@ -26,9 +33,24 @@ function AppInsider({ history }) { - + + + + + + + + + + + + - + + + + + diff --git a/packages/webapp/src/components/Guards/EnsureAuthNotAuthenticated.tsx b/packages/webapp/src/components/Guards/EnsureAuthNotAuthenticated.tsx new file mode 100644 index 000000000..e849a572c --- /dev/null +++ b/packages/webapp/src/components/Guards/EnsureAuthNotAuthenticated.tsx @@ -0,0 +1,14 @@ +// @ts-nocheck +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import { useIsAuthenticated } from '@/hooks/state'; + +interface PrivateRouteProps { + children: React.ReactNode; +} + +export function EnsureAuthNotAuthenticated({ children }: PrivateRouteProps) { + const isAuthenticated = useIsAuthenticated(); + + return !isAuthenticated ? 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..6fb57c4a5 --- /dev/null +++ b/packages/webapp/src/components/Guards/EnsureUserEmailVerified.tsx @@ -0,0 +1,22 @@ +import React from 'react'; +import { Redirect } from 'react-router-dom'; +import { useAuthUserVerified } from '@/hooks/state'; + +interface EnsureUserEmailVerifiedProps { + children: React.ReactNode; +} + +/** + * 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, +}: 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 index 8e8e167b9..a4ef8d3b7 100644 --- a/packages/webapp/src/components/Guards/PrivateRoute.tsx +++ b/packages/webapp/src/components/Guards/PrivateRoute.tsx @@ -4,16 +4,16 @@ import BodyClassName from 'react-body-classname'; import { Redirect } from 'react-router-dom'; import { useIsAuthenticated } from '@/hooks/state'; -export default function PrivateRoute({ component: Component, ...rest }) { +interface PrivateRouteProps { + children: React.ReactNode; +} + +export default function PrivateRoute({ children }: PrivateRouteProps) { const isAuthenticated = useIsAuthenticated(); - return ( - - {isAuthenticated ? ( - - ) : ( - - )} - + return isAuthenticated ? ( + children + ) : ( + ); } diff --git a/packages/webapp/src/containers/Authentication/AuthContainer.tsx b/packages/webapp/src/containers/Authentication/AuthContainer.tsx new file mode 100644 index 000000000..6ba68c196 --- /dev/null +++ b/packages/webapp/src/containers/Authentication/AuthContainer.tsx @@ -0,0 +1,34 @@ +// @ts-nocheck +import styled from 'styled-components'; +import { Icon, FormattedMessage as T } from '@/components'; + +interface AuthContainerProps { + children: React.ReactNode; +} + +export function AuthContainer({ children }: AuthContainerProps) { + return ( + + + + + + + {children} + + + ); +} + +const AuthPage = styled.div``; +const AuthInsider = styled.div` + width: 384px; + margin: 0 auto; + margin-bottom: 40px; + padding-top: 80px; +`; + +const AuthLogo = styled.div` + text-align: center; + margin-bottom: 40px; +`; diff --git a/packages/webapp/src/containers/Authentication/Authentication.tsx b/packages/webapp/src/containers/Authentication/Authentication.tsx index 8b05570c4..34a99f104 100644 --- a/packages/webapp/src/containers/Authentication/Authentication.tsx +++ b/packages/webapp/src/containers/Authentication/Authentication.tsx @@ -1,24 +1,17 @@ // @ts-nocheck import React from 'react'; -import { Redirect, Route, Switch, useLocation } from 'react-router-dom'; +import { Route, Switch, useLocation } from 'react-router-dom'; import BodyClassName from 'react-body-classname'; import styled from 'styled-components'; import { TransitionGroup, CSSTransition } from 'react-transition-group'; import authenticationRoutes from '@/routes/authentication'; import { Icon, FormattedMessage as T } from '@/components'; -import { useIsAuthenticated } from '@/hooks/state'; import { AuthMetaBootProvider } from './AuthMetaBoot'; import '@/style/pages/Authentication/Auth.scss'; export function Authentication() { - const to = { pathname: '/' }; - const isAuthenticated = useIsAuthenticated(); - - if (isAuthenticated) { - return ; - } return ( diff --git a/packages/webapp/src/containers/Authentication/EmailConfirmation.tsx b/packages/webapp/src/containers/Authentication/EmailConfirmation.tsx new file mode 100644 index 000000000..c9aeb985e --- /dev/null +++ b/packages/webapp/src/containers/Authentication/EmailConfirmation.tsx @@ -0,0 +1,27 @@ +// @ts-nocheck +import { useEffect } from 'react'; +import { useHistory, useParams } from 'react-router-dom'; +import { useAuthSignUpVerify } from '@/hooks/query'; + +export default function EmailConfirmation() { + const { mutateAsync: authSignupVerify } = useAuthSignUpVerify(); + const params = useParams(); + const history = useHistory(); + + const token = params.token; + const email = params.email; + + useEffect(() => { + if (!token || !email) { + history.push('register/email_confirmation'); + } + }, [history, token, email]); + + useEffect(() => { + authSignupVerify(token, email) + .then(() => {}) + .catch((error) => {}); + }, [token, email, authSignupVerify]); + + return null; +} diff --git a/packages/webapp/src/containers/Authentication/RegisterVerify.module.scss b/packages/webapp/src/containers/Authentication/RegisterVerify.module.scss new file mode 100644 index 000000000..f0754f470 --- /dev/null +++ b/packages/webapp/src/containers/Authentication/RegisterVerify.module.scss @@ -0,0 +1,18 @@ + +.root { + text-align: center; +} + +.title{ + font-size: 18px; + font-weight: 600; + margin-bottom: 0.5rem; + color: #252A31; +} + +.description{ + margin-bottom: 1rem; + font-size: 15px; + line-height: 1.45; + color: #404854; +} \ No newline at end of file diff --git a/packages/webapp/src/containers/Authentication/RegisterVerify.tsx b/packages/webapp/src/containers/Authentication/RegisterVerify.tsx new file mode 100644 index 000000000..2147a8f2e --- /dev/null +++ b/packages/webapp/src/containers/Authentication/RegisterVerify.tsx @@ -0,0 +1,74 @@ +// @ts-nocheck +import { Button, Intent } from '@blueprintjs/core'; +import AuthInsider from './AuthInsider'; +import { AuthInsiderCard } from './_components'; +import styles from './RegisterVerify.module.scss'; +import { AppToaster, Stack } from '@/components'; +import { useAuthActions } from '@/hooks/state'; +import { useAuthSignUpVerifyResendMail } from '@/hooks/query'; +import { AuthContainer } from './AuthContainer'; +import { useHistory } from 'react-router-dom'; + +export default function RegisterVerify() { + const history = useHistory(); + const { setLogout } = useAuthActions(); + const { mutateAsync: resendSignUpVerifyMail, isLoading } = + useAuthSignUpVerifyResendMail(); + + const handleResendMailBtnClick = () => { + resendSignUpVerifyMail() + .then(() => { + AppToaster.show({ + intent: Intent.SUCCESS, + message: 'The verification mail has sent successfully.', + }); + }) + .catch(() => { + AppToaster.show({ + intent: Intent.DANGER, + message: 'Something went wrong.', + }); + }); + }; + + // Handle logout link click. + const handleSignOutBtnClick = () => { + setLogout(); + }; + + return ( + + + +

Please verify your email

+

+ We sent an email to asdahmed@gmail.com Click the + link inside to get started. +

+ + + + + + +
+
+
+ ); +} diff --git a/packages/webapp/src/hooks/query/authentication.tsx b/packages/webapp/src/hooks/query/authentication.tsx index a604ec322..d43d244c1 100644 --- a/packages/webapp/src/hooks/query/authentication.tsx +++ b/packages/webapp/src/hooks/query/authentication.tsx @@ -90,3 +90,30 @@ export const useAuthMetadata = (props) => { }, ); } + + +/** + * + */ +export const useAuthSignUpVerifyResendMail = (props) => { + const apiRequest = useApiRequest(); + + return useMutation( + () => apiRequest.post('auth/register/verify/resend'), + props, + ); +}; + + + +/** + * + */ +export const useAuthSignUpVerify = (props) => { + const apiRequest = useApiRequest(); + + return useMutation( + (token: string, email: string) => apiRequest.post('auth/register/verify'), + props, + ); +}; \ No newline at end of file diff --git a/packages/webapp/src/hooks/state/authentication.tsx b/packages/webapp/src/hooks/state/authentication.tsx index 4fb20bf6c..b520e1ac1 100644 --- a/packages/webapp/src/hooks/state/authentication.tsx +++ b/packages/webapp/src/hooks/state/authentication.tsx @@ -64,3 +64,10 @@ export const useAuthUser = () => { export const useAuthOrganizationId = () => { return useSelector((state) => state.authentication.organizationId); }; + +/** + * + */ +export const useAuthUserVerified = () => { + return useSelector(() => false); +}; 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'), }), - } + }, ]; From 9103b60653afbb362a405d320c3738f53946b541 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Sun, 28 Apr 2024 18:12:59 +0200 Subject: [PATCH 03/27] feat: New Relic tracking (#429) --- docker-compose.prod.yml | 8 + packages/server/Dockerfile | 3 + packages/server/package.json | 1 + packages/server/src/server.ts | 1 + pnpm-lock.yaml | 963 ++++++++++++++++++++++++++++++++-- 5 files changed, 929 insertions(+), 47 deletions(-) diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index fa873e7de..9620598e4 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -102,6 +102,14 @@ 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} + + database_migration: container_name: bigcapital-database-migration build: diff --git a/packages/server/Dockerfile b/packages/server/Dockerfile index 095508e2d..16415b9ae 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 diff --git a/packages/server/package.json b/packages/server/package.json index 515c609b4..45fea4b88 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -82,6 +82,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/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/pnpm-lock.yaml b/pnpm-lock.yaml index c38501297..a148077f6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -218,6 +218,9 @@ importers: mysql2: specifier: ^1.6.5 version: 1.7.0 + newrelic: + specifier: ^11.15.0 + version: 11.15.0 node-cache: specifier: ^4.2.1 version: 4.2.1 @@ -1870,7 +1873,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 @@ -1886,7 +1889,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 @@ -3774,6 +3777,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.6.0 + dev: false + optional: true + /@craco/craco@5.9.0(react-scripts@5.0.1): resolution: {integrity: sha512-2Q8gIB4W0/nPiUxr9iAKUhGsFlXYN0/wngUdK1VWtfV2NtBv+yllNn2AjieaLbttgpQinuOYmDU65vocC0NMDg==} engines: {node: '>=6'} @@ -4088,6 +4101,25 @@ packages: resolution: {integrity: sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==} dev: true + /@grpc/grpc-js@1.10.6: + resolution: {integrity: sha512-xP58G7wDQ4TCmN/cMUHh00DS7SRDv/+lC+xFLrTkMIN8h55X5NhZMLYbvy7dSELP15qlI6hPhNCRWVMtZMwqLA==} + engines: {node: '>=12.10.0'} + dependencies: + '@grpc/proto-loader': 0.7.12 + '@js-sdsl/ordered-map': 4.4.2 + dev: false + + /@grpc/proto-loader@0.7.12: + resolution: {integrity: sha512-DCVwMxqYzpUCiDMl7hQ384FqP4T3DbNpXU8pt681l3UWCip1WUiD5JrkImUwCB9a7f2cq4CUTmi5r/xIMRPY1Q==} + 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 @@ -4134,6 +4166,20 @@ packages: warning: 4.0.3 dev: false + /@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 + strip-ansi: 7.1.0 + strip-ansi-cjs: /strip-ansi@6.0.1 + wrap-ansi: 8.1.0 + wrap-ansi-cjs: /wrap-ansi@7.0.0 + dev: false + optional: true + /@isaacs/string-locale-compare@1.1.0: resolution: {integrity: sha512-SQ7Kzhh9+D+ZW9MA0zkYv3VXhIDNx+LzM6EJ+/65I3QY+enU6Itte7E5XX7EWrqLW2FN4n06GWzBnPoC3th2aQ==} dev: true @@ -4618,6 +4664,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 @@ -5370,6 +5420,78 @@ packages: glob-to-regexp: 0.3.0 dev: false + /@newrelic/aws-sdk@7.4.1: + resolution: {integrity: sha512-WiNkAtjptvK9E+9ZNRoYLtsEf6bvpK9XuN0LeaBfm38/G93ubcGIyzFV+NiRCVCtCePYna4jXs2/tGSq6Y+NdQ==} + engines: {node: '>=16.0.0'} + dev: false + + /@newrelic/koa@9.1.0: + resolution: {integrity: sha512-huLV/11IZ1CByVlNzU79bUV1p/SHpglFNPT1DJV5NfcfW+czZ0VIWH9gJd8PK1azaZ1Gy2+HV+nZ1mFuoIANnA==} + engines: {node: '>=16.0.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.0 + 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 + + /@newrelic/superagent@7.0.1: + resolution: {integrity: sha512-QZlW0VxHSVOXcMAtlkg+Mth0Nz3vFku8rfzTEmoI/pXcckHXGEYuiVUhhboCTD3xTKVgnZRUp9BWF6SOggGUSw==} + engines: {node: '>=16.0'} + dev: false + /@nicolo-ribaudo/eslint-scope-5-internals@5.1.1-v1: resolution: {integrity: sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==} dependencies: @@ -5399,6 +5521,21 @@ packages: '@nodelib/fs.scandir': 2.1.5 fastq: 1.15.0 + /@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.1 + socks-proxy-agent: 8.0.3 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + /@npmcli/arborist@5.3.0: resolution: {integrity: sha512-+rZ9zgL1lnbl8Xbb1NQdMjveOMwj4lIYfcDtyJHHi5x4X8jtR6m8SXooJMZy5vmFVZ8w7A2Bnd/oX9eTuU8w5A==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -5448,9 +5585,18 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} dependencies: '@gar/promisify': 1.1.3 - semver: 7.3.8 + semver: 7.5.4 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: false + optional: true + /@npmcli/git@3.0.2: resolution: {integrity: sha512-CAcd08y3DWBJqJDpfuVL0uijlq5oaXaOJEKHKc4wqrjd00gkvTZB+nFuLn+doOOKddaQS9JfqtNoFCO2LCvA3w==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -5462,7 +5608,7 @@ packages: proc-log: 2.0.1 promise-inflight: 1.0.1 promise-retry: 2.0.1 - semver: 7.3.8 + semver: 7.5.4 which: 2.0.2 transitivePeerDependencies: - bluebird @@ -5494,7 +5640,7 @@ packages: cacache: 16.1.3 json-parse-even-better-errors: 2.3.1 pacote: 13.6.2 - semver: 7.3.8 + semver: 7.5.4 transitivePeerDependencies: - bluebird - supports-color @@ -5720,6 +5866,13 @@ packages: typescript: 4.9.5 dev: true + /@pkgjs/parseargs@0.11.0: + resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} + engines: {node: '>=14'} + requiresBuild: true + dev: false + optional: true + /@pkgr/utils@2.3.1: resolution: {integrity: sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} @@ -5791,6 +5944,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: @@ -5866,7 +6068,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 @@ -6477,6 +6679,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: @@ -6552,6 +6760,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: @@ -6695,6 +6909,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 @@ -6702,6 +6920,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==} @@ -7298,7 +7520,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: @@ -7359,7 +7581,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 @@ -7380,6 +7602,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 @@ -7576,6 +7802,13 @@ packages: /abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} + /abbrev@2.0.0: + resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + requiresBuild: true + dev: false + optional: true + /accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} @@ -7609,6 +7842,14 @@ packages: dependencies: acorn: 8.8.2 + /acorn-import-assertions@1.9.0(acorn@8.8.2): + resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} + 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: @@ -7732,6 +7973,15 @@ packages: transitivePeerDependencies: - supports-color + /agent-base@7.1.1: + resolution: {integrity: sha512-H0TSyFNDMomMNJQBn8wFV5YC/2eJ+VXECwOadZJT554xP6cODZHPX3H9QMQECxvrgiSOP1pHjy1sMWQVYJOUOA==} + engines: {node: '>= 14'} + dependencies: + debug: 4.3.4(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: false + /agentkeepalive@4.2.1: resolution: {integrity: sha512-Zn4cw2NEqd+9fiSVWMscnjyQ1a8Yfoc5oBajLeo5w+YBHgDUcEBY2hS4YpTz6iN5f/2zQiktcuM6tS8x1p9dpA==} engines: {node: '>= 8.0.0'} @@ -7880,6 +8130,13 @@ packages: engines: {node: '>=10'} dev: false + /ansi-styles@6.2.1: + resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} + engines: {node: '>=12'} + requiresBuild: true + dev: false + optional: true + /ansi-wrap@0.1.0: resolution: {integrity: sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==} engines: {node: '>=0.10.0'} @@ -8312,6 +8569,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: @@ -9171,7 +9438,7 @@ packages: /builtins@5.0.1: resolution: {integrity: sha512-qwVpFEHNfhYJIzNRBvd2C1kyo6jz3ZSMPyyuR47OPdiKWlbYnZNyDWuyR175qDnAJLiCo5fBBqPb3RiXgWlkOQ==} dependencies: - semver: 7.3.8 + semver: 7.5.4 dev: true /busboy@1.6.0: @@ -9222,6 +9489,26 @@ packages: - bluebird dev: true + /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.1 + minipass: 7.0.4 + minipass-collect: 2.0.1 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + p-map: 4.0.0 + ssri: 10.0.5 + tar: 6.1.13 + unique-filename: 3.0.0 + dev: false + optional: true + /cache-base@1.0.1: resolution: {integrity: sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==} engines: {node: '>=0.10.0'} @@ -9457,6 +9744,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 @@ -9507,7 +9799,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==} @@ -9617,7 +9908,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==} @@ -9915,9 +10205,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 /config-chain@1.1.13: resolution: {integrity: sha512-qj+f8APARXHrM0hraqXYb2/bOVSV4PvJQlNZ/DVj0QrmNM2q2euizkeuVckQ57J+W0mRH6Hvi+k50M4Jul2VRQ==} @@ -10760,6 +11049,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: @@ -11391,6 +11685,12 @@ packages: object.defaults: 1.1.0 dev: false + /eastasianwidth@0.2.0: + resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + requiresBuild: true + dev: false + optional: true + /ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} dependencies: @@ -11472,7 +11772,6 @@ packages: requiresBuild: true dependencies: iconv-lite: 0.6.3 - dev: true optional: true /end-of-stream@1.4.4: @@ -11554,7 +11853,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==} @@ -11563,7 +11861,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==} @@ -12482,6 +12779,12 @@ packages: jest-message-util: 27.5.1 dev: false + /exponential-backoff@3.1.1: + resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} + requiresBuild: true + dev: false + optional: true + /express-basic-auth@1.2.1: resolution: {integrity: sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA==} dependencies: @@ -12689,6 +12992,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 @@ -12858,6 +13165,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==} @@ -12974,6 +13285,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: @@ -12998,6 +13319,16 @@ packages: signal-exit: 3.0.7 dev: true + /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: false + optional: true + /forever-agent@0.6.1: resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} dev: false @@ -13056,7 +13387,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) @@ -13078,7 +13409,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==} @@ -13168,6 +13498,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.10 + 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'} @@ -13182,7 +13521,15 @@ 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: false + optional: true /fs-mkdirp-stream@1.0.0: resolution: {integrity: sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==} @@ -13330,6 +13677,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'} @@ -13492,6 +13844,20 @@ packages: - supports-color dev: false + /glob@10.3.12: + 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: false + optional: true + /glob@7.1.2: resolution: {integrity: sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==} dependencies: @@ -13937,7 +14303,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==} @@ -14147,9 +14512,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==} @@ -14213,6 +14587,18 @@ packages: - supports-color dev: true + /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(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: false + optional: true + /http-proxy-middleware@1.3.1: resolution: {integrity: sha512-13eVVDYS4z79w7f1+NPllJtOQFx/FdUW4btIvVRMaRlUY9VGstAbo5MOhLEuUgZFRHn3x50ufn25zkj/boZnEg==} engines: {node: '>=8.0.0'} @@ -14256,7 +14642,13 @@ packages: - debug dev: false - /http-signature@1.2.0: + /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'} dependencies: @@ -14288,6 +14680,16 @@ packages: transitivePeerDependencies: - supports-color + /https-proxy-agent@7.0.4: + resolution: {integrity: sha512-wlwpilI7YdjSkWaQ/7omYBMTliDcmCN8OLihO6I9B86g06lMyAoqgoDpV0XqoaPOKj+0DIdAvnsWfyAAhmimcg==} + engines: {node: '>= 14'} + dependencies: + agent-base: 7.1.1 + debug: 4.3.4(supports-color@5.5.0) + transitivePeerDependencies: + - supports-color + dev: false + /human-interval@2.0.1: resolution: {integrity: sha512-r4Aotzf+OtKIGQCB3odUowy4GfUDTy3aTWTfLd7ZF2gBCy3XW3v/dJLRefZnOFFnjqs5B1TypvS8WarpBkYUNQ==} dependencies: @@ -14414,6 +14816,15 @@ packages: engines: {node: '>=12.2'} dev: true + /import-in-the-middle@1.7.3: + resolution: {integrity: sha512-R2I11NRi0lI3jD2+qjqyVlVEahsejw7LDnYEbGb47QEFjczE3bZYsmWheCTQA+LFs2DzOQxR7Pms7naHW1V4bQ==} + dependencies: + acorn: 8.8.2 + acorn-import-assertions: 1.9.0(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'} @@ -14828,6 +15239,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'} @@ -14837,7 +15253,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==} @@ -15124,6 +15539,13 @@ packages: /isexe@2.0.0: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + /isexe@3.1.1: + resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} + engines: {node: '>=16'} + requiresBuild: true + dev: false + optional: true + /isobject@2.1.0: resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} engines: {node: '>=0.10.0'} @@ -15246,6 +15668,17 @@ packages: set-function-name: 2.0.1 dev: false + /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: false + optional: true + /jake@10.8.5: resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} engines: {node: '>=10'} @@ -16043,7 +16476,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 @@ -16423,6 +16856,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==} @@ -16464,6 +16903,12 @@ 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.10 + dev: false + /jsonfile@6.1.0: resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==} dependencies: @@ -16489,6 +16934,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'} @@ -16872,7 +17321,7 @@ packages: normalize-package-data: 4.0.1 npm-package-arg: 9.1.2 npm-registry-fetch: 13.3.1 - semver: 7.3.8 + semver: 7.5.4 ssri: 9.0.1 transitivePeerDependencies: - bluebird @@ -17027,7 +17476,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==} @@ -17145,6 +17593,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: @@ -17170,6 +17631,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 @@ -17194,6 +17659,13 @@ packages: engines: {node: '>=0.10.0'} dev: false + /lru-cache@10.2.1: + resolution: {integrity: sha512-tS24spDe/zXhWbNPErCHs/AGOzbKGHT+ybSBqmdLm8WZ1xXLWvH8Qn71QPAlqVhd0qUTWjy+Kl9JmISgDdEjsA==} + engines: {node: 14 || >=16.14} + requiresBuild: true + dev: false + optional: true + /lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} dependencies: @@ -17286,6 +17758,27 @@ packages: - supports-color dev: true + /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 + http-cache-semantics: 4.1.1 + is-lambda: 1.0.1 + minipass: 7.0.4 + minipass-fetch: 3.0.4 + minipass-flush: 1.0.5 + minipass-pipeline: 1.2.4 + negotiator: 0.6.3 + promise-retry: 2.0.1 + ssri: 10.0.5 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + /make-iterator@1.0.1: resolution: {integrity: sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==} engines: {node: '>=0.10.0'} @@ -17620,6 +18113,15 @@ packages: dependencies: brace-expansion: 2.0.1 + /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: false + optional: true + /minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} engines: {node: '>= 6'} @@ -17642,6 +18144,15 @@ packages: minipass: 3.3.6 dev: true + /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: false + optional: true + /minipass-fetch@2.1.2: resolution: {integrity: sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -17653,12 +18164,24 @@ packages: encoding: 0.1.13 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: false + optional: 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==} @@ -17672,26 +18195,29 @@ 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@7.0.4: + resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} + engines: {node: '>=16 || 14 >=14.17'} + requiresBuild: true + dev: false + optional: true /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} @@ -17699,7 +18225,6 @@ packages: dependencies: minipass: 3.3.6 yallist: 4.0.0 - dev: true /mixin-deep@1.3.2: resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} @@ -17709,6 +18234,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-infer-owner@2.0.0: resolution: {integrity: sha512-sdqtiFt3lkOaYvTXSRIUjkIdPTcxgv5+fgqYE/5qgwdw12cOrAuzzgzvVExIkH/ul1oeHN3bCLOWSG3XOqbKKw==} engines: {node: '>=10'} @@ -17736,7 +18267,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==} @@ -17761,6 +18291,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: @@ -18032,6 +18566,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: @@ -18101,6 +18641,39 @@ packages: /nested-error-stacks@2.1.1: resolution: {integrity: sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==} + /newrelic@11.15.0: + resolution: {integrity: sha512-sP5zLP/JgaTivDiLfS/kcYpQK7lZQTk6Qqw/9hdAOgtzx/uZdKzxLmNzXk3uS0eVnwQVgKvVr3pGILJUGhGtPA==} + engines: {node: '>=16', npm: '>=6.0.0'} + hasBin: true + dependencies: + '@grpc/grpc-js': 1.10.6 + '@grpc/proto-loader': 0.7.12 + '@newrelic/aws-sdk': 7.4.1 + '@newrelic/koa': 9.1.0 + '@newrelic/ritm': 7.2.0 + '@newrelic/security-agent': 1.2.0 + '@newrelic/superagent': 7.0.1 + '@tyriar/fibonacci-heap': 2.0.9 + concat-stream: 2.0.0 + https-proxy-agent: 7.0.4 + import-in-the-middle: 1.7.3 + 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 @@ -18130,6 +18703,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-addon-api@3.2.1: resolution: {integrity: sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==} dev: true @@ -18167,7 +18749,34 @@ packages: /node-gyp-build@4.6.0: resolution: {integrity: sha512-NTZVKn9IylLwUzaKjkas1e4u2DLNcV4rdYagA4PWdPwW87Bi7z+BznyKSRwS/761tV/lzCGXplWsiaMjLqP2zQ==} hasBin: true - dev: true + + /node-gyp-build@4.8.0: + resolution: {integrity: sha512-u6fs2AEUljNho3EYTJNBfImO5QTo/J/1Etd+NVdCj7qWKUSN/bSLkZwhDv7I+w/MSC6qJ4cknepkAYykDdK8og==} + 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.10 + make-fetch-happen: 13.0.0 + nopt: 7.2.0 + proc-log: 3.0.0 + semver: 7.5.4 + tar: 6.1.13 + which: 4.0.0 + transitivePeerDependencies: + - supports-color + dev: false + optional: true /node-gyp@9.3.1: resolution: {integrity: sha512-4Q16ZCqq3g8awk6UplT7AuxQ35XN4R/yf/+wSAwcBUAjg7l58RTactWaP8fIDTi0FzI7YcVLujwExakZlfWkXg==} @@ -18181,7 +18790,7 @@ packages: nopt: 6.0.0 npmlog: 6.0.2 rimraf: 3.0.2 - semver: 7.3.8 + semver: 7.5.4 tar: 6.1.13 which: 2.0.2 transitivePeerDependencies: @@ -18288,6 +18897,16 @@ packages: abbrev: 1.1.1 dev: true + /nopt@7.2.0: + 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: false + optional: true + /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} dependencies: @@ -18302,7 +18921,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 @@ -18312,7 +18931,7 @@ packages: dependencies: hosted-git-info: 5.2.1 is-core-module: 2.11.0 - semver: 7.3.8 + semver: 7.5.4 validate-npm-package-license: 3.0.4 dev: true @@ -18365,7 +18984,7 @@ packages: resolution: {integrity: sha512-65lUsMI8ztHCxFz5ckCEC44DRvEGdZX5usQFriauxHEwt7upv1FKaQEmAtU0YnOAdwuNWCmk64xYiQABNrEyLA==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} dependencies: - semver: 7.3.8 + semver: 7.5.4 dev: true /npm-normalize-package-bin@1.0.1: @@ -18392,7 +19011,7 @@ packages: dependencies: hosted-git-info: 5.2.1 proc-log: 2.0.1 - semver: 7.3.8 + semver: 7.5.4 validate-npm-package-name: 4.0.0 dev: true @@ -18414,7 +19033,7 @@ packages: npm-install-checks: 5.0.0 npm-normalize-package-bin: 2.0.0 npm-package-arg: 9.1.2 - semver: 7.3.8 + semver: 7.5.4 dev: true /npm-registry-fetch@13.3.1: @@ -18456,6 +19075,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'} @@ -19016,7 +19644,6 @@ packages: engines: {node: '>=10'} dependencies: aggregate-error: 3.1.0 - dev: true /p-pipe@3.1.0: resolution: {integrity: sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw==} @@ -19149,6 +19776,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-conflict-json@2.0.2: resolution: {integrity: sha512-jDbRGb00TAPFsKWCpZZOT93SxVP9nONOSgES3AevqRq/CHvavEBvKAjxX9p5Y5F0RZLxH9Ufd9+RwtCsa+lFDA==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -19304,6 +19935,16 @@ packages: path-root-regex: 0.1.2 dev: false + /path-scurry@1.10.2: + resolution: {integrity: sha512-7xTavNy5RQXnsjANvVvMkEjvloOinkAjv/Z6Ildz9v2RinZ4SBKTWFOVRbaF8p0vpHnyjV/UwNDdKuUv6M5qcA==} + engines: {node: '>=16 || 14 >=14.17'} + requiresBuild: true + dependencies: + lru-cache: 10.2.1 + minipass: 7.0.4 + dev: false + optional: true + /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} dev: false @@ -20320,6 +20961,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'} @@ -20413,6 +21068,13 @@ packages: engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} dev: true + /proc-log@3.0.0: + resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} + engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + requiresBuild: true + dev: false + optional: true + /process-nextick-args@2.0.1: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} @@ -20464,7 +21126,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==} @@ -20668,6 +21329,25 @@ packages: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} dev: true + /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 @@ -21902,6 +22582,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 + /readdir-scoped-modules@1.1.0: resolution: {integrity: sha512-asaikDeqAQg7JifRsZn1NJZXo9E+VwlyCfbkZhwyISinqk5zNS6266HS5kah6P0SaQKGF6SkNnZVHUzHFYxYDw==} deprecated: This functionality has been moved to @npmcli/fs @@ -21940,7 +22628,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: @@ -22227,6 +22915,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'} @@ -22443,7 +23135,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==} @@ -22454,6 +23145,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 @@ -22466,6 +23161,10 @@ packages: dependencies: glob: 7.2.3 + /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: @@ -22810,7 +23509,6 @@ packages: hasBin: true dependencies: lru-cache: 6.0.0 - dev: false /send@0.18.0: resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} @@ -22989,6 +23687,13 @@ packages: /signal-exit@3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} + /signal-exit@4.1.0: + resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} + engines: {node: '>=14'} + requiresBuild: true + dev: false + optional: true + /simple-swizzle@0.2.2: resolution: {integrity: sha512-JA//kQgZtbuY83m+xT+tXJkmJncGMTFT+C+g2h2R9uxkYIrE2yy9sgmcLhCnw57/WSD+Eh3J97FPEDFnbXnDUg==} dependencies: @@ -23142,6 +23847,19 @@ packages: - supports-color dev: true + /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(supports-color@5.5.0) + socks: 2.7.1 + transitivePeerDependencies: + - supports-color + dev: false + optional: true + /socks@2.7.1: resolution: {integrity: sha512-7maUZy1N7uo6+WVEX6psASxtNlKaNVMlGQKkG/63nEDdLOWNbiUMoLK7X4uYoLhQstau72mLgfEWcXcwsaHbYQ==} engines: {node: '>= 10.13.0', npm: '>= 3.0.0'} @@ -23381,6 +24099,15 @@ packages: tweetnacl: 0.14.5 dev: false + /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: false + optional: true + /ssri@9.0.1: resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -23506,6 +24233,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'} @@ -23585,6 +24323,21 @@ packages: is-fullwidth-code-point: 3.0.0 strip-ansi: 6.0.1 + /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: false + optional: 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==} dependencies: @@ -23956,6 +24709,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 @@ -24022,6 +24790,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'} @@ -24042,7 +24821,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==} @@ -24147,6 +24925,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'} @@ -24799,6 +25594,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'} @@ -24843,6 +25651,15 @@ packages: unique-slug: 3.0.0 dev: true + /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: false + optional: true + /unique-slug@3.0.0: resolution: {integrity: sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==} engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0} @@ -24850,6 +25667,15 @@ packages: imurmurhash: 0.1.4 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: false + optional: true + /unique-stream@2.3.1: resolution: {integrity: sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==} dependencies: @@ -24875,6 +25701,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'} @@ -25060,6 +25891,11 @@ packages: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} hasBin: true + /uuid@9.0.1: + resolution: {integrity: sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==} + hasBin: true + dev: false + /v8-compile-cache-lib@3.0.1: resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==} @@ -25623,6 +26459,16 @@ packages: dependencies: isexe: 2.0.0 + /which@4.0.0: + resolution: {integrity: sha512-GlaYyEb07DPxYCKhKzplCWBJtvxZcZMrL+4UkrTSJHHPyZU4mYYTv3qaOe77H7EODLSSopAUFAc6W8U4yqvscg==} + engines: {node: ^16.13.0 || >=18.0.0} + hasBin: true + requiresBuild: true + dependencies: + isexe: 3.1.1 + dev: false + optional: true + /wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} dependencies: @@ -25887,6 +26733,17 @@ packages: string-width: 4.2.3 strip-ansi: 6.0.1 + /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: false + optional: true + /wrappy@1.0.2: resolution: {integrity: sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==} @@ -26118,7 +26975,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==} @@ -26166,6 +27022,19 @@ packages: yargs-parser: 21.1.1 dev: true + /yargs@17.7.2: + resolution: {integrity: sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==} + engines: {node: '>=12'} + dependencies: + cliui: 8.0.1 + escalade: 3.1.1 + get-caller-file: 2.0.5 + require-directory: 2.1.1 + string-width: 4.2.3 + y18n: 5.0.8 + yargs-parser: 21.1.1 + dev: false + /yargs@7.1.2: resolution: {integrity: sha512-ZEjj/dQYQy0Zx0lgLMLR8QuaqTihnxirir7EwUHp1Axq4e3+k8jXU5K0VLbNvedv1f4EWtBonDIZm0NUr+jCcA==} dependencies: From 8a96c41258144d03af182565b43055327d9382d9 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 29 Apr 2024 23:45:11 +0200 Subject: [PATCH 04/27] feat: export resource data to csv, xlsx --- .../controllers/Export/ExportController.ts | 100 ++++++++++++++++++ packages/server/src/api/index.ts | 2 + .../services/Accounts/AccountsExportable.ts | 29 +++++ .../src/services/Export/ExportApplication.ts | 17 +++ .../src/services/Export/ExportRegistery.ts | 49 +++++++++ .../src/services/Export/ExportResources.ts | 44 ++++++++ .../src/services/Export/ExportService.ts | 59 +++++++++++ .../server/src/services/Export/Exportable.ts | 22 ++++ .../src/services/Items/ItemsExportable.ts | 23 ++++ 9 files changed, 345 insertions(+) create mode 100644 packages/server/src/api/controllers/Export/ExportController.ts create mode 100644 packages/server/src/services/Accounts/AccountsExportable.ts create mode 100644 packages/server/src/services/Export/ExportApplication.ts create mode 100644 packages/server/src/services/Export/ExportRegistery.ts create mode 100644 packages/server/src/services/Export/ExportResources.ts create mode 100644 packages/server/src/services/Export/ExportService.ts create mode 100644 packages/server/src/services/Export/Exportable.ts create mode 100644 packages/server/src/services/Items/ItemsExportable.ts 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..12b5f938c --- /dev/null +++ b/packages/server/src/api/controllers/Export/ExportController.ts @@ -0,0 +1,100 @@ +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 623e5a0f7..33b47e616 100644 --- a/packages/server/src/api/index.ts +++ b/packages/server/src/api/index.ts @@ -61,6 +61,7 @@ import { TaxRatesController } from './controllers/TaxRates/TaxRates'; import { ImportController } from './controllers/Import/ImportController'; import { BankingController } from './controllers/Banking/BankingController'; import { Webhooks } from './controllers/Webhooks/Webhooks'; +import { ExportController } from './controllers/Export/ExportController'; export default () => { const app = Router(); @@ -141,6 +142,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/services/Accounts/AccountsExportable.ts b/packages/server/src/services/Accounts/AccountsExportable.ts new file mode 100644 index 000000000..c3c24e352 --- /dev/null +++ b/packages/server/src/services/Accounts/AccountsExportable.ts @@ -0,0 +1,29 @@ +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, + } as IAccountsFilter; + + return this.accountsApplication + .getAccounts(tenantId, parsedQuery) + .then((output) => output.accounts); + } +} 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..f86f22589 --- /dev/null +++ b/packages/server/src/services/Export/ExportResources.ts @@ -0,0 +1,44 @@ +import Container, { Service } from 'typedi'; +import { AccountsExportable } from '../Accounts/AccountsExportable'; +import { ExportableRegistry } from './ExportRegistery'; +import { ItemsImportable } from '../Items/ItemsImportable'; +import { ItemsExportable } from '../Items/ItemsExportable'; + +@Service() +export class ExportableResources { + private static registry: ExportableRegistry; + + constructor() { + this.boot(); + } + + /** + * Importable instances. + */ + private importables = [ + { resource: 'Account', exportable: AccountsExportable }, + { resource: 'Item', exportable: ItemsExportable }, + ]; + + /** + * + */ + 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..0bd5c14df --- /dev/null +++ b/packages/server/src/services/Export/ExportService.ts @@ -0,0 +1,59 @@ +import { Inject, Service } from 'typedi'; +import xlsx from 'xlsx'; +import { sanitizeResourceName } from '../Import/_utils'; +import ResourceService from '../Resource/ResourceService'; +import { ExportableResources } from './ExportResources'; + +@Service() +export class ExportResourceService { + @Inject() + private resourceService: ResourceService; + + @Inject() + private exportableResources: ExportableResources; + + /** + * + * @param {number} tenantId + * @param {string} resourceName + * @param {string} format + */ + async export(tenantId: number, resourceName: string, format: string = 'csv') { + const resource = sanitizeResourceName(resourceName); + const resourceMeta = this.resourceService.getResourceMeta( + tenantId, + resource + ); + const exportable = + this.exportableResources.registry.getExportable(resource); + + const data = await exportable.exportable(tenantId, {}); + + const exportableColumns = [ + { + label: 'Account Normal', + accessor: 'accountNormalFormatted', + }, + { + label: 'Account Type', + accessor: 'accountTypeFormatted', + }, + ]; + + const workbook = xlsx.utils.book_new(); + const worksheetData = data.map((item) => + exportableColumns.map((col) => item[col.accessor]) + ); + worksheetData.unshift(exportableColumns.map((col) => col.label)); // Add header row + const worksheet = xlsx.utils.aoa_to_sheet(worksheetData); + xlsx.utils.book_append_sheet(workbook, worksheet, 'Exported Data'); + + if (format.toLowerCase() === 'csv') { + // Convert to CSV using the xlsx package + return xlsx.write(workbook, { type: 'buffer', bookType: 'csv' }); + } else if (format.toLowerCase() === 'xlsx') { + // Write to XLSX format + 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/Items/ItemsExportable.ts b/packages/server/src/services/Items/ItemsExportable.ts new file mode 100644 index 000000000..e25f37161 --- /dev/null +++ b/packages/server/src/services/Items/ItemsExportable.ts @@ -0,0 +1,23 @@ +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 = {} as IItemsFilter; + + return this.itemsApplication + .getItems(tenantId, parsedQuery) + .then((output) => output.items); + } +} From 7e89966f205ef0e1efd1c078346bc0490dc33f8d Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Wed, 1 May 2024 00:20:13 +0200 Subject: [PATCH 05/27] feat: wip export resource data --- .../controllers/Export/ExportController.ts | 1 - packages/server/src/interfaces/Model.ts | 14 ++ .../server/src/models/Account.Settings.ts | 57 ++++++++ packages/server/src/models/Bill.Settings.ts | 45 +++++- .../server/src/models/BillPayment.Settings.ts | 45 +++++- .../server/src/models/Customer.Settings.ts | 130 ++++++++++++++++++ .../server/src/models/Expense.Settings.ts | 46 ++++++- .../models/InventoryAdjustment.Settings.ts | 48 +++++++ packages/server/src/models/Item.Settings.ts | 90 +++++++++++- .../src/models/ItemCategory.Settings.ts | 22 +++ .../src/models/ManualJournal.Settings.ts | 43 ++++++ .../src/models/PaymentReceive.Settings.ts | 49 ++++++- .../server/src/models/SaleInvoice.Settings.ts | 54 +++++++- .../server/src/models/SaleReceipt.Settings.ts | 67 ++++++++- packages/server/src/models/Vendor.Settings.ts | 130 ++++++++++++++++++ .../src/models/WarehouseTransfer.Settings.ts | 28 ++++ .../Contacts/Customers/CustomersExportable.ts | 29 ++++ .../Contacts/Vendors/VendorsExportable.ts | 29 ++++ .../CreditNotes/CreditNotesExportable.ts | 26 ++++ .../services/Expenses/ExpensesExportable.ts | 25 ++++ .../src/services/Export/ExportResources.ts | 28 +++- .../src/services/Export/ExportService.ts | 33 +++-- packages/server/src/services/Export/common.ts | 3 + .../ItemCategoriesExportable.ts | 29 ++++ .../src/services/Items/ItemsExportable.ts | 8 +- .../ManualJournals/ManualJournalExportable.ts | 29 ++++ .../BillPayments/BillPaymentExportable.ts | 24 ++++ .../Purchases/Bills/BillsExportable.ts | 25 ++++ .../VendorCredits/VendorCreditsExportable.ts | 26 ++++ .../src/services/Resource/ResourceService.ts | 1 + .../Estimates/SaleEstimatesExportable.ts | 25 ++++ .../Sales/Invoices/SaleInvoicesExportable.ts | 25 ++++ .../PaymentsReceivedExportable.ts | 30 ++++ .../Sales/Receipts/SaleReceiptsExportable.ts | 25 ++++ 34 files changed, 1259 insertions(+), 30 deletions(-) create mode 100644 packages/server/src/services/Contacts/Customers/CustomersExportable.ts create mode 100644 packages/server/src/services/Contacts/Vendors/VendorsExportable.ts create mode 100644 packages/server/src/services/CreditNotes/CreditNotesExportable.ts create mode 100644 packages/server/src/services/Expenses/ExpensesExportable.ts create mode 100644 packages/server/src/services/Export/common.ts create mode 100644 packages/server/src/services/ItemCategories/ItemCategoriesExportable.ts create mode 100644 packages/server/src/services/ManualJournals/ManualJournalExportable.ts create mode 100644 packages/server/src/services/Purchases/BillPayments/BillPaymentExportable.ts create mode 100644 packages/server/src/services/Purchases/Bills/BillsExportable.ts create mode 100644 packages/server/src/services/Purchases/VendorCredits/VendorCreditsExportable.ts create mode 100644 packages/server/src/services/Sales/Estimates/SaleEstimatesExportable.ts create mode 100644 packages/server/src/services/Sales/Invoices/SaleInvoicesExportable.ts create mode 100644 packages/server/src/services/Sales/PaymentReceives/PaymentsReceivedExportable.ts create mode 100644 packages/server/src/services/Sales/Receipts/SaleReceiptsExportable.ts diff --git a/packages/server/src/api/controllers/Export/ExportController.ts b/packages/server/src/api/controllers/Export/ExportController.ts index 12b5f938c..632c84932 100644 --- a/packages/server/src/api/controllers/Export/ExportController.ts +++ b/packages/server/src/api/controllers/Export/ExportController.ts @@ -48,7 +48,6 @@ export class ExportController extends BaseController { ACCEPT_TYPE.APPLICATION_CSV, ACCEPT_TYPE.APPLICATION_PDF, ]); - const data = await this.exportResourceApp.export( tenantId, query.resource, diff --git a/packages/server/src/interfaces/Model.ts b/packages/server/src/interfaces/Model.ts index 87912bafe..3fc4e2e6c 100644 --- a/packages/server/src/interfaces/Model.ts +++ b/packages/server/src/interfaces/Model.ts @@ -127,12 +127,14 @@ export interface IModelMeta { defaultSort: IModelMetaDefaultSort; importable?: boolean; + exportable?: boolean; importAggregator?: string; importAggregateOn?: string; importAggregateBy?: string; fields: { [key: string]: IModelMetaField }; + columns: { [key: string]: IModelMetaColumn }; } // ---- @@ -161,3 +163,15 @@ export type IModelMetaField2 = IModelMetaFieldCommon2 & | IModelMetaRelationField2 | IModelMetaCollectionField ); + +export interface ImodelMetaColumnMeta { + name: string; + accessor?: string; + exportable?: boolean; +} + +interface IModelMetaColumnText { + type: 'text;'; +} + +export type IModelMetaColumn = ImodelMetaColumnMeta & IModelMetaColumnText; diff --git a/packages/server/src/models/Account.Settings.ts b/packages/server/src/models/Account.Settings.ts index 5fb8ea8a7..2f1ca8639 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,62 @@ export default { fieldType: 'date', }, }, + columns: { + name: { + name: 'account.field.name', + type: 'text', + exportable: true, + }, + code: { + name: 'account.field.code', + type: 'text', + exportable: true, + }, + description: { + name: 'account.field.description', + type: 'text', + exportable: true, + }, + rootType: { + name: 'account.field.root_type', + type: 'text', + exportable: true, + }, + accountType: { + name: 'account.field.type', + accessor: 'accountTypeLabel', + type: 'enumeration', + exportable: true, + }, + accountNormal: { + name: 'account.field.normal', + accessor: 'accountNormalFormatted', + exportable: true, + }, + currencyCode: { + name: 'account.field.currency', + exportable: true, + }, + bankBalance: { + name: 'account.field.bank_balance', + accessor: 'bankBalanceFormatted', + exportable: true, + }, + balance: { + name: 'account.field.bank_balance', + accessor: 'amount', + exportable: true, + }, + active: { + name: 'account.field.active', + type: 'boolean', + exportable: true, + }, + createdAt: { + name: 'account.field.created_at', + exportable: true, + }, + }, 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..8330ecf41 100644 --- a/packages/server/src/models/Bill.Settings.ts +++ b/packages/server/src/models/Bill.Settings.ts @@ -5,6 +5,7 @@ export default { sortField: 'bill_date', }, importable: true, + exportable: true, importAggregator: 'group', importAggregateOn: 'entries', importAggregateBy: 'billNumber', @@ -80,6 +81,48 @@ export default { fieldType: 'date', }, }, + columns: { + billNumber: { + name: 'Bill No.', + type: 'text', + exportable: true, + }, + referenceNo: { + name: 'Reference No.', + type: 'text', + exportable: true, + }, + billDate: { + name: 'Date', + type: 'date', + exportable: true, + }, + dueDate: { + name: 'Due Date', + type: 'date', + exportable: true, + }, + vendorId: { + name: 'Vendor', + type: 'text', + exportable: true, + }, + exchangeRate: { + name: 'Exchange Rate', + type: 'number', + exportable: true, + }, + note: { + name: 'Note', + type: 'text', + exportable: true, + }, + open: { + name: 'Open', + type: 'boolean', + exportable: true, + }, + }, fields2: { billNumber: { name: 'Bill No.', @@ -132,7 +175,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..b4d79114b 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,44 @@ export default { fieldType: 'date', }, }, + columns: { + vendor: { + name: 'bill_payment.field.vendor', + type: 'relation', + exportable: true, + }, + paymentDate: { + name: 'bill_payment.field.payment_date', + type: 'date', + required: true, + exportable: true, + }, + paymentNumber: { + name: 'bill_payment.field.payment_number', + type: 'text', + exportable: true, + }, + paymentAccountId: { + name: 'bill_payment.field.payment_account', + type: 'relation', + exportable: true, + }, + exchangeRate: { + name: 'bill_payment.field.exchange_rate', + type: 'number', + exportable: true, + }, + statement: { + name: 'bill_payment.field.statement', + type: 'text', + exportable: true, + }, + reference: { + name: 'bill_payment.field.reference', + type: 'text', + exportable: true, + }, + }, fields2: { vendorId: { name: 'bill_payment.field.vendor', @@ -84,7 +123,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,7 +131,7 @@ 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', @@ -120,7 +159,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/Customer.Settings.ts b/packages/server/src/models/Customer.Settings.ts index fa7bcc2d2..705fe5fbe 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,135 @@ 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_pone', + type: 'text', + }, + companyName: { + name: 'vendor.field.company_name', + type: 'text', + }, + website: { + name: 'vendor.field.website', + type: 'text', + }, + createdAt: { + name: 'vendor.field.created_at', + type: 'date', + }, + 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', + }, + // 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', + }, + }, 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..a721e325c 100644 --- a/packages/server/src/models/Expense.Settings.ts +++ b/packages/server/src/models/Expense.Settings.ts @@ -8,6 +8,7 @@ export default { sortField: 'name', }, importable: true, + exportabe: true, fields: { payment_date: { name: 'expense.field.payment_date', @@ -61,6 +62,47 @@ export default { fieldType: 'date', }, }, + columns: { + paymentReceive: { + name: 'expense.field.payment_account', + exportable: true, + }, + referenceNo: { + name: 'expense.field.reference_no', + type: 'text', + exportable: true, + }, + paymentDate: { + name: 'expense.field.payment_date', + type: 'date', + exportable: true, + }, + currencyCode: { + name: 'expense.field.currency_code', + type: 'text', + exportable: true, + }, + exchangeRate: { + name: 'expense.field.exchange_rate', + type: 'number', + exportable: true, + }, + description: { + name: 'expense.field.description', + type: 'text', + exportable: true, + }, + categories: { + name: 'expense.field.categories', + type: 'collection', + exportable: true, + }, + publish: { + name: 'expense.field.publish', + type: 'boolean', + exportable: true, + }, + }, fields2: { paymentAccountId: { name: 'expense.field.payment_account', @@ -68,7 +110,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 +144,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..c471fbd3c 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,93 @@ 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', + exportable: true, + }, + sellAccount: { + name: 'item.field.sell_description', + type: 'text', + exportable: true, + }, + inventoryAccount: { + name: 'item.field.inventory_account', + type: 'text', + 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', + 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 +283,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..039b03773 100644 --- a/packages/server/src/models/ItemCategory.Settings.ts +++ b/packages/server/src/models/ItemCategory.Settings.ts @@ -28,6 +28,28 @@ export default { columnType: 'date', }, }, + columns: { + name: { + name: 'item_category.field.name', + type: 'text', + exportable: true, + }, + description: { + name: 'item_category.field.description', + type: 'text', + exportable: true, + }, + count: { + name: 'item_category.field.count', + type: 'text', + exportable: true, + }, + createdAt: { + name: 'item_category.field.created_at', + type: 'text', + exportable: true, + }, + }, 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..9349744ee 100644 --- a/packages/server/src/models/ManualJournal.Settings.ts +++ b/packages/server/src/models/ManualJournal.Settings.ts @@ -5,6 +5,7 @@ export default { sortField: 'name', }, importable: true, + exportable: true, importAggregator: 'group', importAggregateOn: 'entries', importAggregateBy: 'journalNumber', @@ -56,6 +57,48 @@ export default { fieldType: 'date', }, }, + columns: { + date: { + name: 'manual_journal.field.date', + type: 'date', + exportable: true, + }, + journalNumber: { + name: 'manual_journal.field.journal_number', + type: 'text', + exportable: true, + }, + reference: { + name: 'manual_journal.field.reference', + type: 'text', + exportable: true, + }, + journalType: { + name: 'manual_journal.field.journal_type', + type: 'text', + exportable: true, + }, + currencyCode: { + name: 'manual_journal.field.currency', + type: 'text', + exportable: true, + }, + exchange_rate: { + name: 'manual_journal.field.exchange_rate', + type: 'number', + exportable: true, + }, + description: { + name: 'manual_journal.field.description', + type: 'text', + exportable: true, + }, + publish: { + name: 'Publish', + type: 'boolean', + exportable: true, + }, + }, 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..6e559e7e3 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,48 @@ export default { fieldDate: 'date', }, }, + columns: { + customer: { + name: 'payment_receive.field.customer', + type: 'text', + importable: true, + }, + paymentDate: { + name: 'payment_receive.field.payment_date', + type: 'date', + importable: true, + }, + amount: { + name: 'payment_receive.field.amount', + type: 'number', + importable: true, + }, + referenceNo: { + name: 'payment_receive.field.reference_no', + type: 'text', + importable: true, + }, + depositAccount: { + name: 'payment_receive.field.deposit_account', + type: 'text', + importable: true, + }, + paymentReceiveNo: { + name: 'payment_receive.field.payment_receive_no', + type: 'text', + importable: true, + }, + statement: { + name: 'payment_receive.field.statement', + type: 'text', + importable: true, + }, + created_at: { + name: 'payment_receive.field.created_at', + type: 'date', + importable: true, + }, + }, fields2: { customerId: { name: 'payment_receive.field.customer', @@ -84,12 +127,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 +151,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/SaleInvoice.Settings.ts b/packages/server/src/models/SaleInvoice.Settings.ts index c48befbcb..055d534b4 100644 --- a/packages/server/src/models/SaleInvoice.Settings.ts +++ b/packages/server/src/models/SaleInvoice.Settings.ts @@ -87,6 +87,58 @@ export default { fieldType: 'date', }, }, + columns: { + invoiceDate: { + name: 'invoice.field.invoice_date', + type: 'date', + exportable: true, + }, + dueDate: { + name: 'invoice.field.due_date', + type: 'date', + exportable: true, + }, + referenceNo: { + name: 'invoice.field.reference_no', + type: 'text', + exportable: true, + }, + invoiceNo: { + name: 'invoice.field.invoice_no', + type: 'text', + exportable: true, + }, + customer: { + name: 'invoice.field.customer', + type: 'text', + exportable: true, + }, + exchangeRate: { + name: 'invoice.field.exchange_rate', + type: 'number', + exportable: true, + }, + currencyCode: { + name: 'invoice.field.currency', + type: 'text', + exportable: true, + }, + invoiceMessage: { + name: 'invoice.field.invoice_message', + type: 'text', + exportable: true, + }, + termsConditions: { + name: 'invoice.field.terms_conditions', + type: 'text', + exportable: true, + }, + delivered: { + name: 'invoice.field.delivered', + type: 'boolean', + exportable: true, + }, + }, fields2: { invoiceDate: { name: 'invoice.field.invoice_date', @@ -142,7 +194,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..fd9cad6a5 100644 --- a/packages/server/src/models/SaleReceipt.Settings.ts +++ b/packages/server/src/models/SaleReceipt.Settings.ts @@ -77,6 +77,71 @@ export default { sortCustomQuery: StatusFieldSortQuery, }, }, + columns: { + amount: { + name: 'receipt.field.amount', + column: 'amount', + type: 'number', + exportable: true, + }, + depositAccount: { + column: 'deposit_account_id', + name: 'receipt.field.deposit_account', + type: 'relation', + exportable: true, + }, + customer: { + name: 'receipt.field.customer', + column: 'customer_id', + type: 'relation', + exportable: true, + }, + receiptDate: { + name: 'receipt.field.receipt_date', + column: 'receipt_date', + type: 'date', + exportable: true, + }, + receiptNumber: { + name: 'receipt.field.receipt_number', + column: 'receipt_number', + type: 'text', + exportable: true, + }, + referenceNo: { + name: 'receipt.field.reference_no', + column: 'reference_no', + type: 'text', + exportable: true, + }, + receiptMessage: { + name: 'receipt.field.receipt_message', + column: 'receipt_message', + type: 'text', + exportable: true, + }, + statement: { + name: 'receipt.field.statement', + column: 'statement', + type: 'text', + exportable: true, + }, + createdAt: { + name: 'receipt.field.created_at', + column: 'created_at', + type: 'date', + exportable: true, + }, + status: { + name: 'receipt.field.status', + type: 'enumeration', + options: [ + { key: 'draft', label: 'receipt.field.status.draft' }, + { key: 'closed', label: 'receipt.field.status.closed' }, + ], + exportable: true, + }, + }, fields2: { receiptDate: { name: 'Receipt Date', @@ -126,7 +191,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..3d15f4e0a 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', @@ -90,6 +91,135 @@ 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_pone', + type: 'text', + }, + companyName: { + name: 'vendor.field.company_name', + type: 'text', + }, + website: { + name: 'vendor.field.website', + type: 'text', + }, + createdAt: { + name: 'vendor.field.created_at', + type: 'date', + }, + 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', + }, + // 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', + }, + shippingAddressState: { + name: 'Shipping Address State', + column: 'shipping_address_state', + type: 'text', + }, + shippingAddressPhone: { + name: 'Shipping Address Phone', + column: 'shipping_address_phone', + type: 'text', + } + }, fields2: { firstName: { name: 'vendor.field.first_name', 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/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..292a0b8b8 --- /dev/null +++ b/packages/server/src/services/CreditNotes/CreditNotesExportable.ts @@ -0,0 +1,26 @@ +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 = { + ...query, + } as ICreditNotesQueryDTO; + + return this.getCreditNotes + .getCreditNotesList(tenantId, parsedQuery) + .then((output) => output.creditNotes); + } +} diff --git a/packages/server/src/services/Expenses/ExpensesExportable.ts b/packages/server/src/services/Expenses/ExpensesExportable.ts new file mode 100644 index 000000000..fcd27c1e6 --- /dev/null +++ b/packages/server/src/services/Expenses/ExpensesExportable.ts @@ -0,0 +1,25 @@ +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 = { + ...query, + } as IExpensesFilter; + + return this.expensesApplication + .getExpenses(tenantId, parsedQuery) + .then((output) => output.expenses); + } +} diff --git a/packages/server/src/services/Export/ExportResources.ts b/packages/server/src/services/Export/ExportResources.ts index f86f22589..699e19bdf 100644 --- a/packages/server/src/services/Export/ExportResources.ts +++ b/packages/server/src/services/Export/ExportResources.ts @@ -1,13 +1,27 @@ import Container, { Service } from 'typedi'; import { AccountsExportable } from '../Accounts/AccountsExportable'; import { ExportableRegistry } from './ExportRegistery'; -import { ItemsImportable } from '../Items/ItemsImportable'; 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'; @Service() export class ExportableResources { private static registry: ExportableRegistry; + /** + * Consttuctor method. + */ constructor() { this.boot(); } @@ -18,6 +32,18 @@ export class ExportableResources { private importables = [ { resource: 'Account', exportable: AccountsExportable }, { resource: 'Item', exportable: ItemsExportable }, + { 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 } ]; /** diff --git a/packages/server/src/services/Export/ExportService.ts b/packages/server/src/services/Export/ExportService.ts index 0bd5c14df..2449a0325 100644 --- a/packages/server/src/services/Export/ExportService.ts +++ b/packages/server/src/services/Export/ExportService.ts @@ -3,6 +3,8 @@ import xlsx from 'xlsx'; import { sanitizeResourceName } from '../Import/_utils'; import ResourceService from '../Resource/ResourceService'; import { ExportableResources } from './ExportResources'; +import { ServiceError } from '@/exceptions'; +import { Errors } from './common'; @Service() export class ExportResourceService { @@ -13,10 +15,10 @@ export class ExportResourceService { private exportableResources: ExportableResources; /** - * - * @param {number} tenantId - * @param {string} resourceName - * @param {string} format + * 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. */ async export(tenantId: number, resourceName: string, format: string = 'csv') { const resource = sanitizeResourceName(resourceName); @@ -27,24 +29,25 @@ export class ExportResourceService { const exportable = this.exportableResources.registry.getExportable(resource); + if (!resourceMeta.exportable) { + throw new ServiceError(Errors.RESOURCE_NOT_EXPORTABLE); + } const data = await exportable.exportable(tenantId, {}); - const exportableColumns = [ - { - label: 'Account Normal', - accessor: 'accountNormalFormatted', - }, - { - label: 'Account Type', - accessor: 'accountTypeFormatted', - }, - ]; + const exportableColumns = Object.entries(resourceMeta.columns) + .filter(([_, value]) => value.exportable) + .map(([key, value]) => ({ + name: value.name, + type: value.type, + accessor: value.accessor || key, + })); const workbook = xlsx.utils.book_new(); const worksheetData = data.map((item) => exportableColumns.map((col) => item[col.accessor]) ); - worksheetData.unshift(exportableColumns.map((col) => col.label)); // Add header row + worksheetData.unshift(exportableColumns.map((col) => col.name)); // Add header row + const worksheet = xlsx.utils.aoa_to_sheet(worksheetData); xlsx.utils.book_append_sheet(workbook, worksheet, 'Exported 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/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 index e25f37161..223b403d9 100644 --- a/packages/server/src/services/Items/ItemsExportable.ts +++ b/packages/server/src/services/Items/ItemsExportable.ts @@ -14,7 +14,13 @@ export class ItemsExportable extends Exportable { * @returns */ public exportable(tenantId: number, query: IItemsFilter) { - const parsedQuery = {} as IItemsFilter; + const parsedQuery = { + sortOrder: 'DESC', + columnSortBy: 'created_at', + page: 1, + ...query, + pageSize: 12, + } as IItemsFilter; return this.itemsApplication .getItems(tenantId, parsedQuery) diff --git a/packages/server/src/services/ManualJournals/ManualJournalExportable.ts b/packages/server/src/services/ManualJournals/ManualJournalExportable.ts new file mode 100644 index 000000000..81acf1703 --- /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 accounts 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..b451adafb --- /dev/null +++ b/packages/server/src/services/Purchases/BillPayments/BillPaymentExportable.ts @@ -0,0 +1,24 @@ +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 = { + ...query, + } as any; + + return this.billPaymentsApplication + .getBillPayments(tenantId, parsedQuery) + .then((output) => output.billPayments); + } +} 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..d6820cead --- /dev/null +++ b/packages/server/src/services/Purchases/Bills/BillsExportable.ts @@ -0,0 +1,25 @@ +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 = { + ...query, + } as IBillsFilter; + + return this.billsApplication + .getBills(tenantId, parsedQuery) + .then((output) => output.bills); + } +} 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..5280d01c3 --- /dev/null +++ b/packages/server/src/services/Purchases/VendorCredits/VendorCreditsExportable.ts @@ -0,0 +1,26 @@ +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 accounts data to exportable sheet. + * @param {number} tenantId - + * @param {IVendorCreditsQueryDTO} query - + * @returns {} + */ + public exportable(tenantId: number, query: IVendorCreditsQueryDTO) { + const parsedQuery = { + ...query, + } 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..61914157a 100644 --- a/packages/server/src/services/Resource/ResourceService.ts +++ b/packages/server/src/services/Resource/ResourceService.ts @@ -113,6 +113,7 @@ 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'], ]; return this.i18nService.i18nApply(naviagations, meta, tenantId); } 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..2c9769930 --- /dev/null +++ b/packages/server/src/services/Sales/Estimates/SaleEstimatesExportable.ts @@ -0,0 +1,25 @@ +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 = { + ...query, + } as ISalesInvoicesFilter; + + return this.saleEstimatesApplication + .getSaleEstimates(tenantId, parsedQuery) + .then((output) => output.salesEstimates); + } +} 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..ceb2377d0 --- /dev/null +++ b/packages/server/src/services/Sales/Invoices/SaleInvoicesExportable.ts @@ -0,0 +1,25 @@ +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 = { + ...query, + } 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/SaleReceiptsExportable.ts b/packages/server/src/services/Sales/Receipts/SaleReceiptsExportable.ts new file mode 100644 index 000000000..c1db39900 --- /dev/null +++ b/packages/server/src/services/Sales/Receipts/SaleReceiptsExportable.ts @@ -0,0 +1,25 @@ +import { Inject, Service } from 'typedi'; +import { ISalesInvoicesFilter, 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 = { + ...query, + } as ISalesInvoicesFilter; + + return this.saleReceiptsApp + .getSaleReceipts(tenantId, parsedQuery) + .then((output) => output.data); + } +} From 9504bb5ccdb548d35ec83ecb49609894fd0eed16 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Wed, 1 May 2024 00:52:23 +0200 Subject: [PATCH 06/27] fix: Running migration Docker container on Windows (#432) --- docker/migration/Dockerfile | 3 ++- docker/migration/start.sh | 5 ----- 2 files changed, 2 insertions(+), 6 deletions(-) delete mode 100644 docker/migration/start.sh diff --git a/docker/migration/Dockerfile b/docker/migration/Dockerfile index 162d5039c..190c1b389 100644 --- a/docker/migration/Dockerfile +++ b/docker/migration/Dockerfile @@ -37,4 +37,5 @@ 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 From fab71d2b6547a0fe1970d88cdcb8fcf27febe653 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Wed, 1 May 2024 12:45:24 +0200 Subject: [PATCH 07/27] feat: wip configure resources to be exportable --- packages/server/src/models/Bill.Settings.ts | 8 -- .../server/src/models/BillPayment.Settings.ts | 9 +-- packages/server/src/models/CreditNote.Meta.ts | 36 +++++++++ .../server/src/models/Customer.Settings.ts | 28 +++++++ .../server/src/models/Expense.Settings.ts | 3 +- .../src/models/ItemCategory.Settings.ts | 1 + .../src/models/SaleEstimate.Settings.ts | 54 +++++++++++++ .../server/src/models/SaleInvoice.Settings.ts | 1 + .../server/src/models/SaleReceipt.Settings.ts | 1 + packages/server/src/models/Vendor.Settings.ts | 28 +++++++ .../server/src/models/VendorCredit.Meta.ts | 34 +++++++- .../CreditNotes/CreditNotesExportable.ts | 4 + .../services/Expenses/ExpensesExportable.ts | 4 + .../src/services/Export/ExportResources.ts | 4 +- .../src/services/Export/ExportService.ts | 81 ++++++++++++++++--- .../ManualJournals/GetManualJournals.ts | 2 +- .../ManualJournals/ManualJournalExportable.ts | 2 +- .../BillPayments/BillPaymentExportable.ts | 4 + .../Purchases/BillPayments/GetBillPayments.ts | 2 +- .../Purchases/Bills/BillsApplication.ts | 2 +- .../Purchases/Bills/BillsExportable.ts | 4 + .../VendorCredits/VendorCreditsExportable.ts | 6 +- .../Estimates/SaleEstimatesExportable.ts | 4 + .../Sales/Invoices/SaleInvoicesExportable.ts | 4 + .../Sales/Receipts/SaleReceiptsExportable.ts | 8 +- 25 files changed, 295 insertions(+), 39 deletions(-) diff --git a/packages/server/src/models/Bill.Settings.ts b/packages/server/src/models/Bill.Settings.ts index 8330ecf41..1c99721fd 100644 --- a/packages/server/src/models/Bill.Settings.ts +++ b/packages/server/src/models/Bill.Settings.ts @@ -85,42 +85,34 @@ export default { billNumber: { name: 'Bill No.', type: 'text', - exportable: true, }, referenceNo: { name: 'Reference No.', type: 'text', - exportable: true, }, billDate: { name: 'Date', type: 'date', - exportable: true, }, dueDate: { name: 'Due Date', type: 'date', - exportable: true, }, vendorId: { name: 'Vendor', type: 'text', - exportable: true, }, exchangeRate: { name: 'Exchange Rate', type: 'number', - exportable: true, }, note: { name: 'Note', type: 'text', - exportable: true, }, open: { name: 'Open', type: 'boolean', - exportable: true, }, }, fields2: { diff --git a/packages/server/src/models/BillPayment.Settings.ts b/packages/server/src/models/BillPayment.Settings.ts index b4d79114b..c6e56cdc9 100644 --- a/packages/server/src/models/BillPayment.Settings.ts +++ b/packages/server/src/models/BillPayment.Settings.ts @@ -72,38 +72,31 @@ export default { vendor: { name: 'bill_payment.field.vendor', type: 'relation', - exportable: true, + accessor: 'vendor.displayName', }, paymentDate: { name: 'bill_payment.field.payment_date', type: 'date', - required: true, - exportable: true, }, paymentNumber: { name: 'bill_payment.field.payment_number', type: 'text', - exportable: true, }, paymentAccountId: { name: 'bill_payment.field.payment_account', type: 'relation', - exportable: true, }, exchangeRate: { name: 'bill_payment.field.exchange_rate', type: 'number', - exportable: true, }, statement: { name: 'bill_payment.field.statement', type: 'text', - exportable: true, }, reference: { name: 'bill_payment.field.reference', type: 'text', - exportable: true, }, }, fields2: { diff --git a/packages/server/src/models/CreditNote.Meta.ts b/packages/server/src/models/CreditNote.Meta.ts index f6317528b..3a2633359 100644 --- a/packages/server/src/models/CreditNote.Meta.ts +++ b/packages/server/src/models/CreditNote.Meta.ts @@ -12,6 +12,7 @@ export default { sortOrder: 'DESC', sortField: 'name', }, + exportable: true, importable: true, importAggregator: 'group', importAggregateOn: 'entries', @@ -81,6 +82,41 @@ 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', + }, + }, fields2: { customerId: { name: 'Customer', diff --git a/packages/server/src/models/Customer.Settings.ts b/packages/server/src/models/Customer.Settings.ts index 705fe5fbe..08605c891 100644 --- a/packages/server/src/models/Customer.Settings.ts +++ b/packages/server/src/models/Customer.Settings.ts @@ -95,129 +95,157 @@ export default { firstName: { name: 'vendor.field.first_name', type: 'text', + exportable: true, }, lastName: { name: 'vendor.field.last_name', type: 'text', + exportable: true, }, displayName: { name: 'vendor.field.display_name', type: 'text', + exportable: true, }, email: { name: 'vendor.field.email', type: 'text', + exportable: true, }, workPhone: { name: 'vendor.field.work_phone', type: 'text', + exportable: true, }, personalPhone: { name: 'vendor.field.personal_pone', type: 'text', + exportable: true, }, companyName: { name: 'vendor.field.company_name', type: 'text', + exportable: true, }, website: { name: 'vendor.field.website', type: 'text', + exportable: true, }, createdAt: { name: 'vendor.field.created_at', type: 'date', + exportable: true, }, balance: { name: 'vendor.field.balance', type: 'number', + exportable: true, }, openingBalance: { name: 'vendor.field.opening_balance', type: 'number', + exportable: true, }, openingBalanceAt: { name: 'vendor.field.opening_balance_at', type: 'date', + exportable: true, }, currencyCode: { name: 'vendor.field.currency', type: 'text', + exportable: true, }, status: { name: 'vendor.field.status', + exportable: true, }, // 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, }, shippingAddressPhone: { name: 'Shipping Address Phone', column: 'shipping_address_phone', type: 'text', + exportable: true, }, shippingAddressState: { name: 'Shipping Address State', column: 'shipping_address_state', type: 'text', + exportable: true, }, }, fields2: { diff --git a/packages/server/src/models/Expense.Settings.ts b/packages/server/src/models/Expense.Settings.ts index a721e325c..a071b49f3 100644 --- a/packages/server/src/models/Expense.Settings.ts +++ b/packages/server/src/models/Expense.Settings.ts @@ -8,7 +8,7 @@ export default { sortField: 'name', }, importable: true, - exportabe: true, + exportable: true, fields: { payment_date: { name: 'expense.field.payment_date', @@ -65,6 +65,7 @@ export default { columns: { paymentReceive: { name: 'expense.field.payment_account', + type: 'text', exportable: true, }, referenceNo: { diff --git a/packages/server/src/models/ItemCategory.Settings.ts b/packages/server/src/models/ItemCategory.Settings.ts index 039b03773..e2d056cc2 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', diff --git a/packages/server/src/models/SaleEstimate.Settings.ts b/packages/server/src/models/SaleEstimate.Settings.ts index f92735f34..46e0bfa9a 100644 --- a/packages/server/src/models/SaleEstimate.Settings.ts +++ b/packages/server/src/models/SaleEstimate.Settings.ts @@ -4,6 +4,7 @@ export default { sortOrder: 'DESC', sortField: 'estimate_date', }, + exportable: true, importable: true, importAggregator: 'group', importAggregateOn: 'entries', @@ -73,6 +74,59 @@ 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, + }, + 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, + }, + }, fields2: { customerId: { name: 'Customer', diff --git a/packages/server/src/models/SaleInvoice.Settings.ts b/packages/server/src/models/SaleInvoice.Settings.ts index 055d534b4..d8479a004 100644 --- a/packages/server/src/models/SaleInvoice.Settings.ts +++ b/packages/server/src/models/SaleInvoice.Settings.ts @@ -4,6 +4,7 @@ export default { sortOrder: 'DESC', sortField: 'created_at', }, + exportable: true, importable: true, importAggregator: 'group', importAggregateOn: 'entries', diff --git a/packages/server/src/models/SaleReceipt.Settings.ts b/packages/server/src/models/SaleReceipt.Settings.ts index fd9cad6a5..90c6bd5f7 100644 --- a/packages/server/src/models/SaleReceipt.Settings.ts +++ b/packages/server/src/models/SaleReceipt.Settings.ts @@ -4,6 +4,7 @@ export default { sortOrder: 'DESC', sortField: 'created_at', }, + exportable: true, importable: true, importAggregator: 'group', importAggregateOn: 'entries', diff --git a/packages/server/src/models/Vendor.Settings.ts b/packages/server/src/models/Vendor.Settings.ts index 3d15f4e0a..553a1b625 100644 --- a/packages/server/src/models/Vendor.Settings.ts +++ b/packages/server/src/models/Vendor.Settings.ts @@ -95,129 +95,157 @@ export default { firstName: { name: 'vendor.field.first_name', type: 'text', + exportable: true, }, lastName: { name: 'vendor.field.last_name', type: 'text', + exportable: true, }, displayName: { name: 'vendor.field.display_name', type: 'text', + exportable: true, }, email: { name: 'vendor.field.email', type: 'text', + exportable: true, }, workPhone: { name: 'vendor.field.work_phone', type: 'text', + exportable: true, }, personalPhone: { name: 'vendor.field.personal_pone', type: 'text', + exportable: true, }, companyName: { name: 'vendor.field.company_name', type: 'text', + exportable: true, }, website: { name: 'vendor.field.website', type: 'text', + exportable: true, }, createdAt: { name: 'vendor.field.created_at', type: 'date', + exportable: true, }, balance: { name: 'vendor.field.balance', type: 'number', + exportable: true, }, openingBalance: { name: 'vendor.field.opening_balance', type: 'number', + exportable: true, }, openingBalanceAt: { name: 'vendor.field.opening_balance_at', type: 'date', + exportable: true, }, currencyCode: { name: 'vendor.field.currency', type: 'text', + exportable: true, }, status: { name: 'vendor.field.status', + exportable: true, }, // 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, } }, fields2: { diff --git a/packages/server/src/models/VendorCredit.Meta.ts b/packages/server/src/models/VendorCredit.Meta.ts index 86a3aa103..d1ce500b7 100644 --- a/packages/server/src/models/VendorCredit.Meta.ts +++ b/packages/server/src/models/VendorCredit.Meta.ts @@ -12,6 +12,7 @@ export default { sortOrder: 'DESC', sortField: 'name', }, + exportable: true, importable: true, importAggregator: 'group', importAggregateOn: 'entries', @@ -76,6 +77,37 @@ 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', + }, + note: { + name: 'Note', + type: 'text', + }, + open: { + name: 'Open', + type: 'boolean', + }, + }, fields2: { vendorId: { name: 'Vendor', @@ -122,7 +154,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/services/CreditNotes/CreditNotesExportable.ts b/packages/server/src/services/CreditNotes/CreditNotesExportable.ts index 292a0b8b8..09dae2a74 100644 --- a/packages/server/src/services/CreditNotes/CreditNotesExportable.ts +++ b/packages/server/src/services/CreditNotes/CreditNotesExportable.ts @@ -16,7 +16,11 @@ export class CreditNotesExportable extends Exportable { */ public exportable(tenantId: number, query: ICreditNotesQueryDTO) { const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', ...query, + page: 1, + pageSize: 12000, } as ICreditNotesQueryDTO; return this.getCreditNotes diff --git a/packages/server/src/services/Expenses/ExpensesExportable.ts b/packages/server/src/services/Expenses/ExpensesExportable.ts index fcd27c1e6..51362912f 100644 --- a/packages/server/src/services/Expenses/ExpensesExportable.ts +++ b/packages/server/src/services/Expenses/ExpensesExportable.ts @@ -15,7 +15,11 @@ export class ExpensesExportable extends Exportable { */ public exportable(tenantId: number, query: IExpensesFilter) { const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', ...query, + page: 1, + pageSize: 12000, } as IExpensesFilter; return this.expensesApplication diff --git a/packages/server/src/services/Export/ExportResources.ts b/packages/server/src/services/Export/ExportResources.ts index 699e19bdf..b252bfab1 100644 --- a/packages/server/src/services/Export/ExportResources.ts +++ b/packages/server/src/services/Export/ExportResources.ts @@ -14,6 +14,7 @@ import { BillPaymentExportable } from '../Purchases/BillPayments/BillPaymentExpo 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 { @@ -32,6 +33,7 @@ export class ExportableResources { 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 }, @@ -43,7 +45,7 @@ export class ExportableResources { { resource: 'BillPayment', exportable: BillPaymentExportable }, { resource: 'ManualJournal', exportable: ManualJournalsExportable }, { resource: 'CreditNote', exportable: CreditNotesExportable }, - { resource: 'VendorCredit', exportable: VendorCreditsExportable } + { resource: 'VendorCredit', exportable: VendorCreditsExportable }, ]; /** diff --git a/packages/server/src/services/Export/ExportService.ts b/packages/server/src/services/Export/ExportService.ts index 2449a0325..fe69eb234 100644 --- a/packages/server/src/services/Export/ExportService.ts +++ b/packages/server/src/services/Export/ExportService.ts @@ -5,6 +5,7 @@ import ResourceService from '../Resource/ResourceService'; import { ExportableResources } from './ExportResources'; import { ServiceError } from '@/exceptions'; import { Errors } from './common'; +import { IModelMeta } from '@/interfaces'; @Service() export class ExportResourceService { @@ -22,40 +23,94 @@ export class ExportResourceService { */ async export(tenantId: number, resourceName: string, format: string = 'csv') { const resource = sanitizeResourceName(resourceName); - const resourceMeta = this.resourceService.getResourceMeta( - tenantId, - resource - ); - const exportable = - this.exportableResources.registry.getExportable(resource); + const resourceMeta = this.getResourceMeta(tenantId, resource); + + this.validateResourceMeta(resourceMeta); + + const data = await this.getExportableData(tenantId, resource); + const exportableColumns = this.getExportableColumns(resourceMeta); + + const workbook = this.createWorkbook(data, exportableColumns); + + return this.exportWorkbook(workbook, format); + } - if (!resourceMeta.exportable) { + /** + * 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); } - const data = await exportable.exportable(tenantId, {}); + } - const exportableColumns = Object.entries(resourceMeta.columns) - .filter(([_, value]) => value.exportable) + /** + * 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) { + return Object.entries(resourceMeta.columns) + .filter(([_, value]) => value.exportable !== false) .map(([key, value]) => ({ name: value.name, type: value.type, accessor: value.accessor || key, })); + } + /** + * 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) => item[col.accessor]) ); - worksheetData.unshift(exportableColumns.map((col) => col.name)); // Add header row + 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') { - // Convert to CSV using the xlsx package return xlsx.write(workbook, { type: 'buffer', bookType: 'csv' }); } else if (format.toLowerCase() === 'xlsx') { - // Write to XLSX format return xlsx.write(workbook, { type: 'buffer', bookType: 'xlsx' }); } } 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 index 81acf1703..10d16e992 100644 --- a/packages/server/src/services/ManualJournals/ManualJournalExportable.ts +++ b/packages/server/src/services/ManualJournals/ManualJournalExportable.ts @@ -9,7 +9,7 @@ export class ManualJournalsExportable extends Exportable { private manualJournalsApplication: ManualJournalsApplication; /** - * Retrieves the accounts data to exportable sheet. + * Retrieves the manual journals data to exportable sheet. * @param {number} tenantId * @returns */ diff --git a/packages/server/src/services/Purchases/BillPayments/BillPaymentExportable.ts b/packages/server/src/services/Purchases/BillPayments/BillPaymentExportable.ts index b451adafb..8c2a89b48 100644 --- a/packages/server/src/services/Purchases/BillPayments/BillPaymentExportable.ts +++ b/packages/server/src/services/Purchases/BillPayments/BillPaymentExportable.ts @@ -14,7 +14,11 @@ export class BillPaymentExportable extends Exportable { */ public exportable(tenantId: number, query: any) { const parsedQuery = { + page: 1, + pageSize: 12, ...query, + sortOrder: 'desc', + columnSortBy: 'created_at', } as any; return this.billPaymentsApplication 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 index d6820cead..a45783643 100644 --- a/packages/server/src/services/Purchases/Bills/BillsExportable.ts +++ b/packages/server/src/services/Purchases/Bills/BillsExportable.ts @@ -15,7 +15,11 @@ export class BillsExportable extends Exportable { */ public exportable(tenantId: number, query: IBillsFilter) { const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', ...query, + page: 1, + pageSize: 12000, } as IBillsFilter; return this.billsApplication diff --git a/packages/server/src/services/Purchases/VendorCredits/VendorCreditsExportable.ts b/packages/server/src/services/Purchases/VendorCredits/VendorCreditsExportable.ts index 5280d01c3..4e0963165 100644 --- a/packages/server/src/services/Purchases/VendorCredits/VendorCreditsExportable.ts +++ b/packages/server/src/services/Purchases/VendorCredits/VendorCreditsExportable.ts @@ -9,14 +9,18 @@ export class VendorCreditsExportable extends Exportable { private getVendorCredits: ListVendorCredits; /** - * Retrieves the accounts data to exportable sheet. + * 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 diff --git a/packages/server/src/services/Sales/Estimates/SaleEstimatesExportable.ts b/packages/server/src/services/Sales/Estimates/SaleEstimatesExportable.ts index 2c9769930..455805df9 100644 --- a/packages/server/src/services/Sales/Estimates/SaleEstimatesExportable.ts +++ b/packages/server/src/services/Sales/Estimates/SaleEstimatesExportable.ts @@ -15,7 +15,11 @@ export class SaleEstimatesExportable extends Exportable { */ public exportable(tenantId: number, query: ISalesInvoicesFilter) { const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', ...query, + page: 1, + pageSize: 12000, } as ISalesInvoicesFilter; return this.saleEstimatesApplication diff --git a/packages/server/src/services/Sales/Invoices/SaleInvoicesExportable.ts b/packages/server/src/services/Sales/Invoices/SaleInvoicesExportable.ts index ceb2377d0..e806b0939 100644 --- a/packages/server/src/services/Sales/Invoices/SaleInvoicesExportable.ts +++ b/packages/server/src/services/Sales/Invoices/SaleInvoicesExportable.ts @@ -15,7 +15,11 @@ export class SaleInvoicesExportable extends Exportable { */ public exportable(tenantId: number, query: ISalesInvoicesFilter) { const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', ...query, + page: 1, + pageSize: 120000, } as ISalesInvoicesFilter; return this.saleInvoicesApplication diff --git a/packages/server/src/services/Sales/Receipts/SaleReceiptsExportable.ts b/packages/server/src/services/Sales/Receipts/SaleReceiptsExportable.ts index c1db39900..199c2e5a8 100644 --- a/packages/server/src/services/Sales/Receipts/SaleReceiptsExportable.ts +++ b/packages/server/src/services/Sales/Receipts/SaleReceiptsExportable.ts @@ -1,5 +1,5 @@ import { Inject, Service } from 'typedi'; -import { ISalesInvoicesFilter, ISalesReceiptsFilter } from '@/interfaces'; +import { ISalesReceiptsFilter } from '@/interfaces'; import { Exportable } from '@/services/Export/Exportable'; import { SaleReceiptApplication } from './SaleReceiptApplication'; @@ -15,8 +15,12 @@ export class SaleReceiptsExportable extends Exportable { */ public exportable(tenantId: number, query: ISalesReceiptsFilter) { const parsedQuery = { + sortOrder: 'desc', + columnSortBy: 'created_at', ...query, - } as ISalesInvoicesFilter; + page: 1, + pageSize: 12, + } as ISalesReceiptsFilter; return this.saleReceiptsApp .getSaleReceipts(tenantId, parsedQuery) From 00a1e070c6ac5bcccc300cb9fa21b4f8f5e0cd12 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Wed, 1 May 2024 16:26:10 +0200 Subject: [PATCH 08/27] feat: export dialog on resource table --- .../services/Accounts/AccountsExportable.ts | 2 + .../src/components/DialogsContainer.tsx | 3 + packages/webapp/src/constants/dialogs.ts | 3 +- .../ManualJournalActionsBar.tsx | 14 +++- .../Accounts/AccountsActionsBar.tsx | 5 ++ .../CustomersLanding/CustomersActionsBar.tsx | 14 +++- .../ExportDialog/ExportDialogContent.tsx | 17 +++++ .../ExportDialog/ExportDialogForm.schema.ts | 9 +++ .../Dialogs/ExportDialog/ExportDialogForm.tsx | 70 +++++++++++++++++++ .../ExportDialog/ExportDialogFormContent.tsx | 29 ++++++++ .../Dialogs/ExportDialog/constants.ts | 17 +++++ .../containers/Dialogs/ExportDialog/index.tsx | 31 ++++++++ .../containers/Dialogs/ExportDialog/type.ts | 6 ++ .../ExpensesLanding/ExpenseActionsBar.tsx | 16 +++-- .../src/containers/Items/ItemsActionsBar.tsx | 12 ++++ .../ItemsCategoryActionsBar.tsx | 6 ++ .../Bills/BillsLanding/BillsActionsBar.tsx | 14 +++- .../VendorsCreditNoteActionsBar.tsx | 20 ++++-- .../PaymentsLanding/PaymentMadeActionsBar.tsx | 14 +++- .../CreditNotesActionsBar.tsx | 12 ++++ .../EstimatesLanding/EstimatesActionsBar.tsx | 13 +++- .../InvoicesLanding/InvoicesActionsBar.tsx | 12 ++++ .../PaymentReceiveActionsBar.tsx | 11 +++ .../ReceiptsLanding/ReceiptActionsBar.tsx | 12 ++++ .../VendorsLanding/VendorActionsBar.tsx | 14 ++++ .../query/FinancialReports/use-export.ts | 38 ++++++++++ 26 files changed, 398 insertions(+), 16 deletions(-) create mode 100644 packages/webapp/src/containers/Dialogs/ExportDialog/ExportDialogContent.tsx create mode 100644 packages/webapp/src/containers/Dialogs/ExportDialog/ExportDialogForm.schema.ts create mode 100644 packages/webapp/src/containers/Dialogs/ExportDialog/ExportDialogForm.tsx create mode 100644 packages/webapp/src/containers/Dialogs/ExportDialog/ExportDialogFormContent.tsx create mode 100644 packages/webapp/src/containers/Dialogs/ExportDialog/constants.ts create mode 100644 packages/webapp/src/containers/Dialogs/ExportDialog/index.tsx create mode 100644 packages/webapp/src/containers/Dialogs/ExportDialog/type.ts create mode 100644 packages/webapp/src/hooks/query/FinancialReports/use-export.ts diff --git a/packages/server/src/services/Accounts/AccountsExportable.ts b/packages/server/src/services/Accounts/AccountsExportable.ts index c3c24e352..86a6293d8 100644 --- a/packages/server/src/services/Accounts/AccountsExportable.ts +++ b/packages/server/src/services/Accounts/AccountsExportable.ts @@ -20,6 +20,8 @@ export class AccountsExportable extends Exportable { inactiveMode: false, ...query, structure: IAccountsStructureType.Flat, + pageSize: 12000, + page: 1, } as IAccountsFilter; return this.accountsApplication 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/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 ( @@ -186,6 +190,7 @@ function AccountsActionsBar({ className={Classes.MINIMAL} icon={} text={} + onClick={handleExportBtnClick} /> + + + ); +} 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; + }); + }); +}; From 495941f43a3f72abbbdc354f411b75dc68ca40b6 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Wed, 1 May 2024 19:09:53 +0200 Subject: [PATCH 09/27] feat: style the export dialog form --- .../Accounts/AccountsActionsBar.tsx | 14 ++-- .../CashFlowAccountsActionsBar.tsx | 10 +-- .../ExportDialogContent.module.scss | 18 +++++ .../Dialogs/ExportDialog/ExportDialogForm.tsx | 10 ++- .../ExportDialog/ExportDialogFormContent.tsx | 70 ++++++++++++++----- 5 files changed, 91 insertions(+), 31 deletions(-) create mode 100644 packages/webapp/src/containers/Dialogs/ExportDialog/ExportDialogContent.module.scss diff --git a/packages/webapp/src/containers/Accounts/AccountsActionsBar.tsx b/packages/webapp/src/containers/Accounts/AccountsActionsBar.tsx index 0af8e675a..eacaf4a17 100644 --- a/packages/webapp/src/containers/Accounts/AccountsActionsBar.tsx +++ b/packages/webapp/src/containers/Accounts/AccountsActionsBar.tsx @@ -121,7 +121,7 @@ function AccountsActionsBar({ // Handle the export button click. const handleExportBtnClick = () => { openDialog(DialogsName.Export, { resource: 'account' }); - } + }; return ( @@ -186,18 +186,18 @@ function AccountsActionsBar({ icon={} text={} /> - + + + + ); } + +export const ExportDialogFormContent = compose(withDialogActions)( + ExportDialogFormContentRoot, +); From 55aab76c9b9e5054de49ccb78710af9bda7e06e7 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Wed, 1 May 2024 20:15:35 +0200 Subject: [PATCH 10/27] feat: configure columns of resources export --- packages/server/resources/locales/en.json | 3 ++- .../server/src/models/Account.Settings.ts | 25 +++++++------------ packages/server/src/models/Bill.Settings.ts | 17 +++++++++++++ .../server/src/models/BillPayment.Settings.ts | 17 ++++++++++--- packages/server/src/models/Item.Settings.ts | 4 +++ .../src/models/ItemCategory.Settings.ts | 4 --- .../src/models/PaymentReceive.Settings.ts | 10 ++------ .../src/models/SaleEstimate.Settings.ts | 5 ++++ .../server/src/models/SaleInvoice.Settings.ts | 24 ++++++++++-------- .../server/src/models/SaleReceipt.Settings.ts | 20 +++------------ packages/server/src/models/Vendor.Settings.ts | 4 +-- .../src/services/Export/ExportService.ts | 3 ++- 12 files changed, 74 insertions(+), 62 deletions(-) diff --git a/packages/server/resources/locales/en.json b/packages/server/resources/locales/en.json index 13fc8a174..7ba5673b9 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.", diff --git a/packages/server/src/models/Account.Settings.ts b/packages/server/src/models/Account.Settings.ts index 2f1ca8639..7be8cf404 100644 --- a/packages/server/src/models/Account.Settings.ts +++ b/packages/server/src/models/Account.Settings.ts @@ -90,56 +90,49 @@ export default { name: { name: 'account.field.name', type: 'text', - exportable: true, }, code: { name: 'account.field.code', type: 'text', - exportable: true, - }, - description: { - name: 'account.field.description', - type: 'text', - exportable: true, }, rootType: { name: 'account.field.root_type', type: 'text', - exportable: true, + accessor: 'accountRootType', }, accountType: { name: 'account.field.type', accessor: 'accountTypeLabel', - type: 'enumeration', - exportable: true, + type: 'text', }, accountNormal: { name: 'account.field.normal', accessor: 'accountNormalFormatted', - exportable: true, }, currencyCode: { name: 'account.field.currency', - exportable: true, + type: 'text', }, bankBalance: { name: 'account.field.bank_balance', accessor: 'bankBalanceFormatted', + type: 'text', exportable: true, }, balance: { - name: 'account.field.bank_balance', + name: 'account.field.balance', accessor: 'amount', - exportable: true, + }, + description: { + name: 'account.field.description', + type: 'text', }, active: { name: 'account.field.active', type: 'boolean', - exportable: true, }, createdAt: { name: 'account.field.created_at', - exportable: true, }, }, fields2: { diff --git a/packages/server/src/models/Bill.Settings.ts b/packages/server/src/models/Bill.Settings.ts index 1c99721fd..1c2f681ed 100644 --- a/packages/server/src/models/Bill.Settings.ts +++ b/packages/server/src/models/Bill.Settings.ts @@ -100,12 +100,29 @@ export default { }, 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', diff --git a/packages/server/src/models/BillPayment.Settings.ts b/packages/server/src/models/BillPayment.Settings.ts index c6e56cdc9..4d7de9239 100644 --- a/packages/server/src/models/BillPayment.Settings.ts +++ b/packages/server/src/models/BillPayment.Settings.ts @@ -82,16 +82,25 @@ export default { name: 'bill_payment.field.payment_number', type: 'text', }, - paymentAccountId: { + paymentAccount: { name: 'bill_payment.field.payment_account', - type: 'relation', + 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.statement', + name: 'bill_payment.field.note', type: 'text', }, reference: { @@ -131,7 +140,7 @@ export default { fieldType: 'number', }, statement: { - name: 'bill_payment.field.statement', + name: 'bill_payment.field.note', fieldType: 'text', }, reference: { diff --git a/packages/server/src/models/Item.Settings.ts b/packages/server/src/models/Item.Settings.ts index c471fbd3c..9c8a50ce8 100644 --- a/packages/server/src/models/Item.Settings.ts +++ b/packages/server/src/models/Item.Settings.ts @@ -161,16 +161,19 @@ export default { 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: { @@ -196,6 +199,7 @@ export default { category: { name: 'item.field.category', type: 'text', + accessor: 'category.name', exportable: true, }, active: { diff --git a/packages/server/src/models/ItemCategory.Settings.ts b/packages/server/src/models/ItemCategory.Settings.ts index e2d056cc2..66b7af28f 100644 --- a/packages/server/src/models/ItemCategory.Settings.ts +++ b/packages/server/src/models/ItemCategory.Settings.ts @@ -33,22 +33,18 @@ export default { name: { name: 'item_category.field.name', type: 'text', - exportable: true, }, description: { name: 'item_category.field.description', type: 'text', - exportable: true, }, count: { name: 'item_category.field.count', type: 'text', - exportable: true, }, createdAt: { name: 'item_category.field.created_at', type: 'text', - exportable: true, }, }, fields2: { diff --git a/packages/server/src/models/PaymentReceive.Settings.ts b/packages/server/src/models/PaymentReceive.Settings.ts index 6e559e7e3..663b5884d 100644 --- a/packages/server/src/models/PaymentReceive.Settings.ts +++ b/packages/server/src/models/PaymentReceive.Settings.ts @@ -61,43 +61,37 @@ export default { columns: { customer: { name: 'payment_receive.field.customer', + accessor: 'customer.displayName', type: 'text', - importable: true, }, paymentDate: { name: 'payment_receive.field.payment_date', type: 'date', - importable: true, }, amount: { name: 'payment_receive.field.amount', type: 'number', - importable: true, }, referenceNo: { name: 'payment_receive.field.reference_no', type: 'text', - importable: true, }, depositAccount: { name: 'payment_receive.field.deposit_account', + accessor: 'depositAccount.name', type: 'text', - importable: true, }, paymentReceiveNo: { name: 'payment_receive.field.payment_receive_no', type: 'text', - importable: true, }, statement: { name: 'payment_receive.field.statement', type: 'text', - importable: true, }, created_at: { name: 'payment_receive.field.created_at', type: 'date', - importable: true, }, }, fields2: { diff --git a/packages/server/src/models/SaleEstimate.Settings.ts b/packages/server/src/models/SaleEstimate.Settings.ts index 46e0bfa9a..378fa73fa 100644 --- a/packages/server/src/models/SaleEstimate.Settings.ts +++ b/packages/server/src/models/SaleEstimate.Settings.ts @@ -101,6 +101,11 @@ export default { type: 'text', exportable: true, }, + amount: { + name: 'Amount', + accessor: 'formattedAmount', + type: 'text', + }, exchangeRate: { name: 'Exchange Rate', type: 'number', diff --git a/packages/server/src/models/SaleInvoice.Settings.ts b/packages/server/src/models/SaleInvoice.Settings.ts index d8479a004..d632af617 100644 --- a/packages/server/src/models/SaleInvoice.Settings.ts +++ b/packages/server/src/models/SaleInvoice.Settings.ts @@ -92,52 +92,56 @@ export default { invoiceDate: { name: 'invoice.field.invoice_date', type: 'date', - exportable: true, }, dueDate: { name: 'invoice.field.due_date', type: 'date', - exportable: true, }, referenceNo: { name: 'invoice.field.reference_no', type: 'text', - exportable: true, }, invoiceNo: { name: 'invoice.field.invoice_no', type: 'text', - exportable: true, }, customer: { name: 'invoice.field.customer', type: 'text', - exportable: true, + accessor: 'customer.displayName', + }, + amount: { + name: 'invoice.field.amount', + type: 'text', + accessor: 'balanceAmountFormatted', }, exchangeRate: { name: 'invoice.field.exchange_rate', type: 'number', - exportable: true, }, currencyCode: { name: 'invoice.field.currency', type: 'text', - exportable: true, + }, + paidAmount: { + name: 'Paid Amount', + accessor: 'paymentAmountFormatted' + }, + dueAmount: { + name: 'Due Amount', + accessor: 'dueAmountFormatted', }, invoiceMessage: { name: 'invoice.field.invoice_message', type: 'text', - exportable: true, }, termsConditions: { name: 'invoice.field.terms_conditions', type: 'text', - exportable: true, }, delivered: { name: 'invoice.field.delivered', type: 'boolean', - exportable: true, }, }, fields2: { diff --git a/packages/server/src/models/SaleReceipt.Settings.ts b/packages/server/src/models/SaleReceipt.Settings.ts index 90c6bd5f7..ce26bbfff 100644 --- a/packages/server/src/models/SaleReceipt.Settings.ts +++ b/packages/server/src/models/SaleReceipt.Settings.ts @@ -83,31 +83,24 @@ export default { name: 'receipt.field.amount', column: 'amount', type: 'number', - exportable: true, }, depositAccount: { - column: 'deposit_account_id', name: 'receipt.field.deposit_account', - type: 'relation', - exportable: true, + type: 'text', + accessor: 'depositAccount.name', }, customer: { name: 'receipt.field.customer', - column: 'customer_id', - type: 'relation', - exportable: true, + type: 'text', + accessor: 'customer.displayName', }, receiptDate: { name: 'receipt.field.receipt_date', - column: 'receipt_date', type: 'date', - exportable: true, }, receiptNumber: { name: 'receipt.field.receipt_number', - column: 'receipt_number', type: 'text', - exportable: true, }, referenceNo: { name: 'receipt.field.reference_no', @@ -119,19 +112,14 @@ export default { name: 'receipt.field.receipt_message', column: 'receipt_message', type: 'text', - exportable: true, }, statement: { name: 'receipt.field.statement', - column: 'statement', type: 'text', - exportable: true, }, createdAt: { name: 'receipt.field.created_at', - column: 'created_at', type: 'date', - exportable: true, }, status: { name: 'receipt.field.status', diff --git a/packages/server/src/models/Vendor.Settings.ts b/packages/server/src/models/Vendor.Settings.ts index 553a1b625..2fa93bf6d 100644 --- a/packages/server/src/models/Vendor.Settings.ts +++ b/packages/server/src/models/Vendor.Settings.ts @@ -33,7 +33,7 @@ export default { fieldType: 'text', }, personal_phone: { - name: 'vendor.field.personal_pone', + name: 'vendor.field.personal_phone', column: 'personal_phone', fieldType: 'text', }, @@ -118,7 +118,7 @@ export default { exportable: true, }, personalPhone: { - name: 'vendor.field.personal_pone', + name: 'vendor.field.personal_phone', type: 'text', exportable: true, }, diff --git a/packages/server/src/services/Export/ExportService.ts b/packages/server/src/services/Export/ExportService.ts index fe69eb234..111b5587a 100644 --- a/packages/server/src/services/Export/ExportService.ts +++ b/packages/server/src/services/Export/ExportService.ts @@ -1,5 +1,6 @@ import { Inject, Service } from 'typedi'; import xlsx from 'xlsx'; +import { get } from 'lodash'; import { sanitizeResourceName } from '../Import/_utils'; import ResourceService from '../Resource/ResourceService'; import { ExportableResources } from './ExportResources'; @@ -92,7 +93,7 @@ export class ExportResourceService { private createWorkbook(data: any[], exportableColumns: any[]) { const workbook = xlsx.utils.book_new(); const worksheetData = data.map((item) => - exportableColumns.map((col) => item[col.accessor]) + exportableColumns.map((col) => get(item, col.accessor)) ); worksheetData.unshift(exportableColumns.map((col) => col.name)); From 83a5010dc59075d0e82fc3c5603f61556af8ff8a Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 2 May 2024 15:38:57 +0200 Subject: [PATCH 11/27] feat: flatten the nested columns of exported data --- packages/server/resources/locales/en.json | 1 + packages/server/src/interfaces/Model.ts | 12 +++- packages/server/src/models/Bill.Settings.ts | 30 ++++++++- packages/server/src/models/CreditNote.Meta.ts | 29 ++++++++ .../server/src/models/Customer.Settings.ts | 41 +++--------- .../server/src/models/Expense.Settings.ts | 25 ++++--- .../src/models/ManualJournal.Settings.ts | 54 +++++++++++---- .../src/models/SaleEstimate.Settings.ts | 31 ++++++++- .../server/src/models/SaleInvoice.Settings.ts | 31 ++++++++- .../server/src/models/SaleReceipt.Settings.ts | 37 ++++++++-- packages/server/src/models/Vendor.Settings.ts | 29 +++----- .../server/src/models/VendorCredit.Meta.ts | 45 +++++++++++++ .../services/CreditNotes/ListCreditNotes.ts | 2 +- .../src/services/Export/ExportService.ts | 67 +++++++++++++++---- packages/server/src/services/Export/utils.ts | 27 ++++++++ .../src/services/Purchases/Bills/GetBills.ts | 1 + .../VendorCredits/VendorCreditTransformer.ts | 12 ++++ .../src/services/Resource/ResourceService.ts | 7 +- .../Sales/Estimates/GetSaleEstimates.ts | 1 + .../Sales/Invoices/GetSaleInvoices.ts | 2 +- .../Sales/Receipts/GetSaleReceipts.ts | 5 +- 21 files changed, 392 insertions(+), 97 deletions(-) create mode 100644 packages/server/src/services/Export/utils.ts diff --git a/packages/server/resources/locales/en.json b/packages/server/resources/locales/en.json index 7ba5673b9..ba4c158b8 100644 --- a/packages/server/resources/locales/en.json +++ b/packages/server/resources/locales/en.json @@ -432,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/interfaces/Model.ts b/packages/server/src/interfaces/Model.ts index 3fc4e2e6c..93bb1f7fc 100644 --- a/packages/server/src/interfaces/Model.ts +++ b/packages/server/src/interfaces/Model.ts @@ -126,9 +126,10 @@ export interface IModelMeta { defaultFilterField: string; defaultSort: IModelMetaDefaultSort; - importable?: boolean; exportable?: boolean; + exportFlattenOn?: string; + importable?: boolean; importAggregator?: string; importAggregateOn?: string; importAggregateBy?: string; @@ -174,4 +175,11 @@ interface IModelMetaColumnText { type: 'text;'; } -export type IModelMetaColumn = ImodelMetaColumnMeta & IModelMetaColumnText; +interface IModelMetaColumnCollection { + type: 'collection'; + collectionOf: 'object'; + columns: { [key: string]: ImodelMetaColumnMeta & IModelMetaColumnText }; +} + +export type IModelMetaColumn = ImodelMetaColumnMeta & + (IModelMetaColumnText | IModelMetaColumnCollection); diff --git a/packages/server/src/models/Bill.Settings.ts b/packages/server/src/models/Bill.Settings.ts index 1c2f681ed..890a9635a 100644 --- a/packages/server/src/models/Bill.Settings.ts +++ b/packages/server/src/models/Bill.Settings.ts @@ -5,6 +5,7 @@ export default { sortField: 'bill_date', }, importable: true, + exportFlattenOn: 'entries', exportable: true, importAggregator: 'group', importAggregateOn: 'entries', @@ -121,7 +122,7 @@ export default { }, paidAmount: { name: 'Paid Amount', - accessor: 'formattedPaymentAmount' + accessor: 'formattedPaymentAmount', }, note: { name: 'Note', @@ -131,6 +132,33 @@ export default { 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: { diff --git a/packages/server/src/models/CreditNote.Meta.ts b/packages/server/src/models/CreditNote.Meta.ts index 3a2633359..5da0c1d9e 100644 --- a/packages/server/src/models/CreditNote.Meta.ts +++ b/packages/server/src/models/CreditNote.Meta.ts @@ -13,10 +13,13 @@ export default { sortField: 'name', }, exportable: true, + exportFlattenOn: 'entries', + importable: true, importAggregator: 'group', importAggregateOn: 'entries', importAggregateBy: 'creditNoteNumber', + fields: { customer: { name: 'credit_note.field.customer', @@ -116,6 +119,32 @@ export default { 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: { diff --git a/packages/server/src/models/Customer.Settings.ts b/packages/server/src/models/Customer.Settings.ts index 08605c891..71f631032 100644 --- a/packages/server/src/models/Customer.Settings.ts +++ b/packages/server/src/models/Customer.Settings.ts @@ -95,157 +95,132 @@ export default { firstName: { name: 'vendor.field.first_name', type: 'text', - exportable: true, }, lastName: { name: 'vendor.field.last_name', type: 'text', - exportable: true, }, displayName: { name: 'vendor.field.display_name', type: 'text', - exportable: true, }, email: { name: 'vendor.field.email', type: 'text', - exportable: true, }, workPhone: { name: 'vendor.field.work_phone', type: 'text', - exportable: true, }, personalPhone: { - name: 'vendor.field.personal_pone', + name: 'vendor.field.personal_phone', type: 'text', - exportable: true, }, companyName: { name: 'vendor.field.company_name', type: 'text', - exportable: true, }, website: { name: 'vendor.field.website', type: 'text', - exportable: true, - }, - createdAt: { - name: 'vendor.field.created_at', - type: 'date', - exportable: true, }, balance: { name: 'vendor.field.balance', type: 'number', - exportable: true, }, openingBalance: { name: 'vendor.field.opening_balance', type: 'number', - exportable: true, }, openingBalanceAt: { name: 'vendor.field.opening_balance_at', type: 'date', - exportable: true, }, currencyCode: { name: 'vendor.field.currency', type: 'text', - exportable: true, }, status: { name: 'vendor.field.status', - exportable: true, + }, + note: { + name: 'vendor.field.note', }, // 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, }, shippingAddressPhone: { name: 'Shipping Address Phone', column: 'shipping_address_phone', type: 'text', - exportable: true, }, shippingAddressState: { name: 'Shipping Address State', column: 'shipping_address_state', type: 'text', - exportable: true, + }, + createdAt: { + name: 'vendor.field.created_at', + type: 'date', }, }, fields2: { diff --git a/packages/server/src/models/Expense.Settings.ts b/packages/server/src/models/Expense.Settings.ts index a071b49f3..12c539782 100644 --- a/packages/server/src/models/Expense.Settings.ts +++ b/packages/server/src/models/Expense.Settings.ts @@ -8,6 +8,7 @@ export default { sortField: 'name', }, importable: true, + exportFlattenOn: 'categories', exportable: true, fields: { payment_date: { @@ -66,42 +67,50 @@ export default { paymentReceive: { name: 'expense.field.payment_account', type: 'text', - exportable: true, + accessor: 'paymentAccount.name' }, referenceNo: { name: 'expense.field.reference_no', type: 'text', - exportable: true, }, paymentDate: { name: 'expense.field.payment_date', type: 'date', - exportable: true, }, currencyCode: { name: 'expense.field.currency_code', type: 'text', - exportable: true, }, exchangeRate: { name: 'expense.field.exchange_rate', type: 'number', - exportable: true, }, description: { name: 'expense.field.description', type: 'text', - exportable: true, }, categories: { name: 'expense.field.categories', type: 'collection', - exportable: true, + 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', - exportable: true, }, }, fields2: { diff --git a/packages/server/src/models/ManualJournal.Settings.ts b/packages/server/src/models/ManualJournal.Settings.ts index 9349744ee..db2712220 100644 --- a/packages/server/src/models/ManualJournal.Settings.ts +++ b/packages/server/src/models/ManualJournal.Settings.ts @@ -5,6 +5,8 @@ export default { sortField: 'name', }, importable: true, + exportFlattenOn: 'entries', + exportable: true, importAggregator: 'group', importAggregateOn: 'entries', @@ -61,42 +63,70 @@ export default { date: { name: 'manual_journal.field.date', type: 'date', - exportable: true, }, journalNumber: { name: 'manual_journal.field.journal_number', type: 'text', - exportable: true, }, reference: { name: 'manual_journal.field.reference', type: 'text', - exportable: true, }, journalType: { name: 'manual_journal.field.journal_type', type: 'text', - exportable: true, + }, + amount: { + name: 'Amount', + accessor: 'formattedAmount', }, currencyCode: { name: 'manual_journal.field.currency', type: 'text', - exportable: true, }, - exchange_rate: { + exchangeRate: { name: 'manual_journal.field.exchange_rate', type: 'number', - exportable: true, }, description: { name: 'manual_journal.field.description', type: 'text', - exportable: true, }, - publish: { - name: 'Publish', - type: 'boolean', - exportable: true, + 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: { diff --git a/packages/server/src/models/SaleEstimate.Settings.ts b/packages/server/src/models/SaleEstimate.Settings.ts index 378fa73fa..a9577b4f4 100644 --- a/packages/server/src/models/SaleEstimate.Settings.ts +++ b/packages/server/src/models/SaleEstimate.Settings.ts @@ -5,6 +5,8 @@ export default { sortField: 'estimate_date', }, exportable: true, + exportFlattenOn: 'entries', + importable: true, importAggregator: 'group', importAggregateOn: 'entries', @@ -131,6 +133,33 @@ export default { 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: { @@ -191,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 d632af617..24728522e 100644 --- a/packages/server/src/models/SaleInvoice.Settings.ts +++ b/packages/server/src/models/SaleInvoice.Settings.ts @@ -5,6 +5,8 @@ export default { sortField: 'created_at', }, exportable: true, + exportFlattenOn: 'entries', + importable: true, importAggregator: 'group', importAggregateOn: 'entries', @@ -125,7 +127,7 @@ export default { }, paidAmount: { name: 'Paid Amount', - accessor: 'paymentAmountFormatted' + accessor: 'paymentAmountFormatted', }, dueAmount: { name: 'Due Amount', @@ -143,6 +145,33 @@ export default { 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: { diff --git a/packages/server/src/models/SaleReceipt.Settings.ts b/packages/server/src/models/SaleReceipt.Settings.ts index ce26bbfff..3fecd0480 100644 --- a/packages/server/src/models/SaleReceipt.Settings.ts +++ b/packages/server/src/models/SaleReceipt.Settings.ts @@ -5,6 +5,8 @@ export default { sortField: 'created_at', }, exportable: true, + exportFlattenOn: 'entries', + importable: true, importAggregator: 'group', importAggregateOn: 'entries', @@ -117,10 +119,6 @@ export default { name: 'receipt.field.statement', type: 'text', }, - createdAt: { - name: 'receipt.field.created_at', - type: 'date', - }, status: { name: 'receipt.field.status', type: 'enumeration', @@ -130,6 +128,37 @@ export default { ], 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: { diff --git a/packages/server/src/models/Vendor.Settings.ts b/packages/server/src/models/Vendor.Settings.ts index 2fa93bf6d..7681dfa10 100644 --- a/packages/server/src/models/Vendor.Settings.ts +++ b/packages/server/src/models/Vendor.Settings.ts @@ -95,71 +95,57 @@ export default { firstName: { name: 'vendor.field.first_name', type: 'text', - exportable: true, }, lastName: { name: 'vendor.field.last_name', type: 'text', - exportable: true, }, displayName: { name: 'vendor.field.display_name', type: 'text', - exportable: true, }, email: { name: 'vendor.field.email', type: 'text', - exportable: true, }, workPhone: { name: 'vendor.field.work_phone', type: 'text', - exportable: true, }, personalPhone: { name: 'vendor.field.personal_phone', type: 'text', - exportable: true, }, companyName: { name: 'vendor.field.company_name', type: 'text', - exportable: true, }, website: { name: 'vendor.field.website', type: 'text', - exportable: true, - }, - createdAt: { - name: 'vendor.field.created_at', - type: 'date', - exportable: true, }, balance: { name: 'vendor.field.balance', type: 'number', - exportable: true, }, openingBalance: { name: 'vendor.field.opening_balance', type: 'number', - exportable: true, }, openingBalanceAt: { name: 'vendor.field.opening_balance_at', type: 'date', - exportable: true, }, currencyCode: { name: 'vendor.field.currency', type: 'text', - exportable: true, }, status: { name: 'vendor.field.status', - exportable: true, + }, + note: { + name: 'vendor.field.note', + type: 'text', }, // Billing Address billingAddress1: { @@ -246,7 +232,12 @@ export default { column: 'shipping_address_phone', type: 'text', exportable: true, - } + }, + createdAt: { + name: 'vendor.field.created_at', + type: 'date', + exportable: true, + }, }, fields2: { firstName: { diff --git a/packages/server/src/models/VendorCredit.Meta.ts b/packages/server/src/models/VendorCredit.Meta.ts index d1ce500b7..b57cc275c 100644 --- a/packages/server/src/models/VendorCredit.Meta.ts +++ b/packages/server/src/models/VendorCredit.Meta.ts @@ -13,10 +13,13 @@ export default { sortField: 'name', }, exportable: true, + exportFlattenOn: 'entries', + importable: true, importAggregator: 'group', importAggregateOn: 'entries', importAggregateBy: 'vendorCreditNumber', + fields: { vendor: { name: 'vendor_credit.field.vendor', @@ -99,6 +102,22 @@ export default { 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', @@ -107,6 +126,32 @@ export default { 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: { 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/Export/ExportService.ts b/packages/server/src/services/Export/ExportService.ts index 111b5587a..c9c3dc432 100644 --- a/packages/server/src/services/Export/ExportService.ts +++ b/packages/server/src/services/Export/ExportService.ts @@ -1,12 +1,14 @@ 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 } from '@/interfaces'; +import { IModelMeta, IModelMetaColumn } from '@/interfaces'; +import { flatDataCollections, getDataAccessor } from './utils'; @Service() export class ExportResourceService { @@ -22,16 +24,16 @@ export class ExportResourceService { * @param {string} resourceName - Resource name. * @param {string} format - File format. */ - async export(tenantId: number, resourceName: string, format: string = 'csv') { + 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(data, exportableColumns); + const workbook = this.createWorkbook(transformed, exportableColumns); return this.exportWorkbook(workbook, format); } @@ -57,6 +59,29 @@ export class ExportResourceService { } } + /** + * 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. @@ -75,13 +100,29 @@ export class ExportResourceService { * @returns An array of exportable columns. */ private getExportableColumns(resourceMeta: IModelMeta) { - return Object.entries(resourceMeta.columns) - .filter(([_, value]) => value.exportable !== false) - .map(([key, value]) => ({ - name: value.name, - type: value.type, - accessor: value.accessor || key, - })); + 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); } /** @@ -93,12 +134,14 @@ export class ExportResourceService { private createWorkbook(data: any[], exportableColumns: any[]) { const workbook = xlsx.utils.book_new(); const worksheetData = data.map((item) => - exportableColumns.map((col) => get(item, col.accessor)) + 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; } 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/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/Resource/ResourceService.ts b/packages/server/src/services/Resource/ResourceService.ts index 61914157a..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'], @@ -114,6 +118,7 @@ export default class ResourceService { ['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/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/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); }) From f6a0476fb42fcd34caf917feb7c3d95d7726222d Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Fri, 3 May 2024 11:28:11 +0200 Subject: [PATCH 12/27] feat: bump CHANGELOG --- CHANGELOG.md | 58 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 58 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index ae75e5f45..16567afb2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,64 @@ All notable changes to Bigcapital server-side will be in this file. +## [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 From cb88c234d1aec8da8d10262ac01485ff32a0273d Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Fri, 3 May 2024 16:00:31 +0200 Subject: [PATCH 13/27] feat: sync the isVerified state of authed user --- .../server/src/system/models/SystemUser.ts | 2 +- packages/webapp/src/components/App.tsx | 25 +++++++----- .../components/Dashboard/DashboardBoot.tsx | 10 ++++- .../Guards/EnsureAuthNotAuthenticated.tsx | 14 +++++-- ...ivateRoute.tsx => EnsureAuthenticated.tsx} | 13 ++++--- .../Guards/EnsureUserEmailVerified.tsx | 4 +- .../Authentication/Authentication.tsx | 1 - .../Authentication/EmailConfirmation.tsx | 39 ++++++++++++++----- .../Authentication/RegisterVerify.tsx | 8 +--- .../webapp/src/hooks/query/authentication.tsx | 21 +++++----- packages/webapp/src/hooks/query/users.tsx | 7 +++- .../webapp/src/hooks/state/authentication.tsx | 21 ++++++++-- .../authentication/authentication.actions.tsx | 6 ++- .../authentication/authentication.reducer.tsx | 13 ++++++- .../authentication/authentication.types.tsx | 1 + 15 files changed, 133 insertions(+), 52 deletions(-) rename packages/webapp/src/components/Guards/{PrivateRoute.tsx => EnsureAuthenticated.tsx} (52%) diff --git a/packages/server/src/system/models/SystemUser.ts b/packages/server/src/system/models/SystemUser.ts index 627caaeb6..ce17186df 100644 --- a/packages/server/src/system/models/SystemUser.ts +++ b/packages/server/src/system/models/SystemUser.ts @@ -35,7 +35,7 @@ export default class SystemUser extends SystemModel { * Virtual attributes. */ static get virtualAttributes() { - return ['fullName', 'isDeleted', 'isInviteAccepted']; + return ['fullName', 'isDeleted', 'isInviteAccepted', 'isVerified']; } /** diff --git a/packages/webapp/src/components/App.tsx b/packages/webapp/src/components/App.tsx index 57effb72a..856f83686 100644 --- a/packages/webapp/src/components/App.tsx +++ b/packages/webapp/src/components/App.tsx @@ -9,7 +9,7 @@ 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'; @@ -20,6 +20,9 @@ import { queryConfig } from '../hooks/query/base'; import { EnsureUserEmailVerified } from './Guards/EnsureUserEmailVerified'; import { EnsureAuthNotAuthenticated } from './Guards/EnsureAuthNotAuthenticated'; +const EmailConfirmation = LazyLoader({ + loader: () => import('@/containers/Authentication/EmailConfirmation'), +}); const RegisterVerify = LazyLoader({ loader: () => import('@/containers/Authentication/RegisterVerify'), }); @@ -33,24 +36,28 @@ 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/Guards/EnsureAuthNotAuthenticated.tsx b/packages/webapp/src/components/Guards/EnsureAuthNotAuthenticated.tsx index e849a572c..97539a32d 100644 --- a/packages/webapp/src/components/Guards/EnsureAuthNotAuthenticated.tsx +++ b/packages/webapp/src/components/Guards/EnsureAuthNotAuthenticated.tsx @@ -3,12 +3,20 @@ import React from 'react'; import { Redirect } from 'react-router-dom'; import { useIsAuthenticated } from '@/hooks/state'; -interface PrivateRouteProps { +interface EnsureAuthNotAuthenticatedProps { children: React.ReactNode; + redirectTo?: string; } -export function EnsureAuthNotAuthenticated({ children }: PrivateRouteProps) { +export function EnsureAuthNotAuthenticated({ + children, + redirectTo = '/', +}: EnsureAuthNotAuthenticatedProps) { const isAuthenticated = useIsAuthenticated(); - return !isAuthenticated ? children : ; + return !isAuthenticated ? ( + <>{children} + ) : ( + + ); } diff --git a/packages/webapp/src/components/Guards/PrivateRoute.tsx b/packages/webapp/src/components/Guards/EnsureAuthenticated.tsx similarity index 52% rename from packages/webapp/src/components/Guards/PrivateRoute.tsx rename to packages/webapp/src/components/Guards/EnsureAuthenticated.tsx index a4ef8d3b7..0a223a9ae 100644 --- a/packages/webapp/src/components/Guards/PrivateRoute.tsx +++ b/packages/webapp/src/components/Guards/EnsureAuthenticated.tsx @@ -1,19 +1,22 @@ // @ts-nocheck import React from 'react'; -import BodyClassName from 'react-body-classname'; import { Redirect } from 'react-router-dom'; import { useIsAuthenticated } from '@/hooks/state'; -interface PrivateRouteProps { +interface EnsureAuthenticatedProps { children: React.ReactNode; + redirectTo?: string; } -export default function PrivateRoute({ children }: PrivateRouteProps) { +export function EnsureAuthenticated({ + children, + redirectTo = '/auth/login', +}: EnsureAuthenticatedProps) { const isAuthenticated = useIsAuthenticated(); return isAuthenticated ? ( - children + <>{children} ) : ( - + ); } diff --git a/packages/webapp/src/components/Guards/EnsureUserEmailVerified.tsx b/packages/webapp/src/components/Guards/EnsureUserEmailVerified.tsx index 6fb57c4a5..f24d93533 100644 --- a/packages/webapp/src/components/Guards/EnsureUserEmailVerified.tsx +++ b/packages/webapp/src/components/Guards/EnsureUserEmailVerified.tsx @@ -4,6 +4,7 @@ import { useAuthUserVerified } from '@/hooks/state'; interface EnsureUserEmailVerifiedProps { children: React.ReactNode; + redirectTo?: string; } /** @@ -12,11 +13,12 @@ interface EnsureUserEmailVerifiedProps { */ export function EnsureUserEmailVerified({ children, + redirectTo = '/auth/register/verify', }: EnsureUserEmailVerifiedProps) { const isAuthVerified = useAuthUserVerified(); if (!isAuthVerified) { - return ; + return ; } return <>{children}; } diff --git a/packages/webapp/src/containers/Authentication/Authentication.tsx b/packages/webapp/src/containers/Authentication/Authentication.tsx index 34a99f104..408dc78f0 100644 --- a/packages/webapp/src/containers/Authentication/Authentication.tsx +++ b/packages/webapp/src/containers/Authentication/Authentication.tsx @@ -1,5 +1,4 @@ // @ts-nocheck -import React from 'react'; import { Route, Switch, useLocation } from 'react-router-dom'; import BodyClassName from 'react-body-classname'; import styled from 'styled-components'; diff --git a/packages/webapp/src/containers/Authentication/EmailConfirmation.tsx b/packages/webapp/src/containers/Authentication/EmailConfirmation.tsx index c9aeb985e..e7fe66d5e 100644 --- a/packages/webapp/src/containers/Authentication/EmailConfirmation.tsx +++ b/packages/webapp/src/containers/Authentication/EmailConfirmation.tsx @@ -1,27 +1,46 @@ // @ts-nocheck -import { useEffect } from 'react'; -import { useHistory, useParams } from 'react-router-dom'; +import { useEffect, useMemo } from 'react'; +import { useLocation, useHistory } from 'react-router-dom'; import { useAuthSignUpVerify } from '@/hooks/query'; +import { AppToaster } from '@/components'; +import { Intent } from '@blueprintjs/core'; + +function useQuery() { + const { search } = useLocation(); + return useMemo(() => new URLSearchParams(search), [search]); +} export default function EmailConfirmation() { const { mutateAsync: authSignupVerify } = useAuthSignUpVerify(); - const params = useParams(); const history = useHistory(); + const query = useQuery(); - const token = params.token; - const email = params.email; + const token = query.get('token'); + const email = query.get('email'); useEffect(() => { if (!token || !email) { - history.push('register/email_confirmation'); + history.push('/auth/login'); } }, [history, token, email]); useEffect(() => { - authSignupVerify(token, email) - .then(() => {}) - .catch((error) => {}); - }, [token, email, authSignupVerify]); + authSignupVerify({ token, email }) + .then(() => { + AppToaster.show({ + message: 'Your email has been verified, Congrats!', + intent: Intent.SUCCESS, + }); + history.push('/'); + }) + .catch(() => { + AppToaster.show({ + message: 'Something went wrong', + intent: Intent.DANGER, + }); + history.push('/'); + }); + }, [token, email, authSignupVerify, history]); return null; } diff --git a/packages/webapp/src/containers/Authentication/RegisterVerify.tsx b/packages/webapp/src/containers/Authentication/RegisterVerify.tsx index 2147a8f2e..a707bfdb0 100644 --- a/packages/webapp/src/containers/Authentication/RegisterVerify.tsx +++ b/packages/webapp/src/containers/Authentication/RegisterVerify.tsx @@ -7,10 +7,8 @@ import { AppToaster, Stack } from '@/components'; import { useAuthActions } from '@/hooks/state'; import { useAuthSignUpVerifyResendMail } from '@/hooks/query'; import { AuthContainer } from './AuthContainer'; -import { useHistory } from 'react-router-dom'; export default function RegisterVerify() { - const history = useHistory(); const { setLogout } = useAuthActions(); const { mutateAsync: resendSignUpVerifyMail, isLoading } = useAuthSignUpVerifyResendMail(); @@ -30,8 +28,6 @@ export default function RegisterVerify() { }); }); }; - - // Handle logout link click. const handleSignOutBtnClick = () => { setLogout(); }; @@ -60,11 +56,11 @@ export default function RegisterVerify() { diff --git a/packages/webapp/src/hooks/query/authentication.tsx b/packages/webapp/src/hooks/query/authentication.tsx index d43d244c1..7f48d78b2 100644 --- a/packages/webapp/src/hooks/query/authentication.tsx +++ b/packages/webapp/src/hooks/query/authentication.tsx @@ -78,7 +78,7 @@ export const useAuthResetPassword = (props) => { */ export const useAuthMetadata = (props) => { return useRequestQuery( - [t.AUTH_METADATA_PAGE,], + [t.AUTH_METADATA_PAGE], { method: 'get', url: `auth/meta`, @@ -88,12 +88,11 @@ export const useAuthMetadata = (props) => { defaultData: {}, ...props, }, - ); -} - + ); +}; /** - * + * */ export const useAuthSignUpVerifyResendMail = (props) => { const apiRequest = useApiRequest(); @@ -104,16 +103,20 @@ export const useAuthSignUpVerifyResendMail = (props) => { ); }; - +interface AuthSignUpVerifyValues { + token: string; + email: string; +} /** - * + * */ export const useAuthSignUpVerify = (props) => { const apiRequest = useApiRequest(); return useMutation( - (token: string, email: string) => apiRequest.post('auth/register/verify'), + (values: AuthSignUpVerifyValues) => + apiRequest.post('auth/register/verify', values), props, ); -}; \ No newline at end of file +}; 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 b520e1ac1..73054c663 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'; @@ -66,8 +69,20 @@ export const useAuthOrganizationId = () => { }; /** - * + * Retrieves the user's email verification status. */ export const useAuthUserVerified = () => { - return useSelector(() => false); + 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/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 From f4440c9a03fdae4b63b0f34e444357f2518aca7d Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Fri, 3 May 2024 18:30:19 +0200 Subject: [PATCH 14/27] feat: ability to enable/disable email confirmation from env variables --- .env.example | 3 +++ .../src/api/controllers/Authentication.ts | 5 ++--- packages/server/src/config/index.ts | 7 +++++++ .../services/Authentication/AuthApplication.ts | 18 ++++++------------ .../src/services/Authentication/AuthSignup.ts | 13 ++++++++++--- .../Authentication/AuthSignupConfirm.ts | 2 +- .../Authentication/AuthSignupResend.ts | 13 +++++++------ packages/server/src/utils/index.ts | 6 +++--- 8 files changed, 39 insertions(+), 28 deletions(-) diff --git a/.env.example b/.env.example index 7621a6a09..dc38bf3e3 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/packages/server/src/api/controllers/Authentication.ts b/packages/server/src/api/controllers/Authentication.ts index 87ac2166c..ff6576bec 100644 --- a/packages/server/src/api/controllers/Authentication.ts +++ b/packages/server/src/api/controllers/Authentication.ts @@ -220,11 +220,10 @@ export default class AuthenticationController extends BaseController { } /** - * + * Resends the confirmation email to the user. * @param {Request} req * @param {Response}| res * @param {Function} next - * @returns */ private async registerVerifyResendMail( req: Request, @@ -234,7 +233,7 @@ export default class AuthenticationController extends BaseController { const { user } = req; try { - const data = await this.authApplication.signUpConfirm(user.id); + const data = await this.authApplication.signUpConfirmResend(user.id); return res.status(200).send({ type: 'success', diff --git a/packages/server/src/config/index.ts b/packages/server/src/config/index.ts index 6a61da3ca..36dbfd831 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/services/Authentication/AuthApplication.ts b/packages/server/src/services/Authentication/AuthApplication.ts index 47f2168a3..73f18941f 100644 --- a/packages/server/src/services/Authentication/AuthApplication.ts +++ b/packages/server/src/services/Authentication/AuthApplication.ts @@ -1,4 +1,4 @@ -import { Service, Inject, Container } from 'typedi'; +import { Service, Inject } from 'typedi'; import { IRegisterDTO, ISystemUser, @@ -13,11 +13,6 @@ import { AuthSignupConfirmService } from './AuthSignupConfirm'; import { SystemUser } from '@/system/models'; import { AuthSignupConfirmResend } from './AuthSignupResend'; -interface ISignupConfirmDTO { - token: string; - email: string; -} - @Service() export default class AuthenticationApplication { @Inject() @@ -72,13 +67,12 @@ export default class AuthenticationApplication { } /** - * - * @param {string} email - * @param {string} token - * @returns + * Resends the confirmation email of the given system user. + * @param {number} userId - System user id. + * @returns {Promise} */ - public async signUpConfirmSend(email: string, token: string) { - return this.authSignupConfirmService.signUpConfirm(email, token); + public async signUpConfirmResend(userId: number) { + return this.authSignUpConfirmResendService.signUpConfirmResend(userId); } /** diff --git a/packages/server/src/services/Authentication/AuthSignup.ts b/packages/server/src/services/Authentication/AuthSignup.ts index 2be8c63a2..17c7f574c 100644 --- a/packages/server/src/services/Authentication/AuthSignup.ts +++ b/packages/server/src/services/Authentication/AuthSignup.ts @@ -1,4 +1,4 @@ -import { isEmpty, omit } from 'lodash'; +import { defaultTo, isEmpty, omit } from 'lodash'; import moment from 'moment'; import crypto from 'crypto'; import { ServiceError } from '@/exceptions'; @@ -42,7 +42,13 @@ export class AuthSignupService { await this.validateEmailUniqiness(signupDTO.email); const hashedPassword = await hashPassword(signupDTO.password); - const verifyToken = crypto.randomBytes(64).toString('hex'); + + 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, { @@ -53,10 +59,11 @@ export class AuthSignupService { 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 index 940b6ae7f..b08a4bd2e 100644 --- a/packages/server/src/services/Authentication/AuthSignupConfirm.ts +++ b/packages/server/src/services/Authentication/AuthSignupConfirm.ts @@ -52,6 +52,6 @@ export class AuthSignupConfirmService { userId, } as IAuthSignUpVerifiedEventPayload); - return updatedUser; + return updatedUser as SystemUser; } } diff --git a/packages/server/src/services/Authentication/AuthSignupResend.ts b/packages/server/src/services/Authentication/AuthSignupResend.ts index 5c764e80c..8edb08f35 100644 --- a/packages/server/src/services/Authentication/AuthSignupResend.ts +++ b/packages/server/src/services/Authentication/AuthSignupResend.ts @@ -1,6 +1,6 @@ +import { Inject, Service } from 'typedi'; import { ServiceError } from '@/exceptions'; import { SystemUser } from '@/system/models'; -import { Inject, Service } from 'typedi'; import { ERRORS } from './_constants'; @Service() @@ -9,18 +9,19 @@ export class AuthSignupConfirmResend { private agenda: any; /** - * - * @param {number} tenantId - * @param {string} email + * 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); } - if (user.verifyToken) { + // Throw error if the verification token is not exist. + if (!user.verifyToken) { throw new ServiceError(ERRORS.USER_ALREADY_VERIFIED); } const payload = { 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; From a5bfb0b02bc25b0a02c0affced2d43ec00c8bc31 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 6 May 2024 17:36:36 +0200 Subject: [PATCH 15/27] fix: the email confirmation link on mail message --- .../Authentication/AuthenticationMailMessages.ts | 11 +++++------ ...240425100821_add_confirmation_columns_to_users.js | 12 ++++++++---- 2 files changed, 13 insertions(+), 10 deletions(-) diff --git a/packages/server/src/services/Authentication/AuthenticationMailMessages.ts b/packages/server/src/services/Authentication/AuthenticationMailMessages.ts index e974d22b9..533315e72 100644 --- a/packages/server/src/services/Authentication/AuthenticationMailMessages.ts +++ b/packages/server/src/services/Authentication/AuthenticationMailMessages.ts @@ -44,8 +44,10 @@ export default class AuthenticationMailMesssages { public async sendSignupVerificationMail( email: string, fullName: string, - token: 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') @@ -57,10 +59,7 @@ export default class AuthenticationMailMesssages { cid: 'bigcapital_logo', }, ]) - .setData({ - verifyUrl: `${config.baseURL}/auth/reset_password/${token}`, - fullName, - }) - .send(); + .setData({ verifyUrl, fullName }) + .send(); } } 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 index 125f508f5..fada1380f 100644 --- 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 @@ -1,8 +1,12 @@ exports.up = function (knex) { - return knex.schema.table('users', (table) => { - table.string('verify_token'); - table.boolean('verified').defaultTo(false); - }); + 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) => {}; From dd02ae471e3a3b7f973189c54fe72d106bfe9d9f Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 6 May 2024 17:45:32 +0200 Subject: [PATCH 16/27] feat: redirect to the setup page if user is verified --- packages/webapp/src/components/App.tsx | 5 +++- .../Guards/EnsureUserEmailNotVerified.tsx | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) create mode 100644 packages/webapp/src/components/Guards/EnsureUserEmailNotVerified.tsx diff --git a/packages/webapp/src/components/App.tsx b/packages/webapp/src/components/App.tsx index 856f83686..4f43a8613 100644 --- a/packages/webapp/src/components/App.tsx +++ b/packages/webapp/src/components/App.tsx @@ -19,6 +19,7 @@ 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'), @@ -38,7 +39,9 @@ function AppInsider({ history }) { - + + + 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}; +} From cd046cbe277f463817da6e13a747d63ba8d81bed Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 6 May 2024 17:55:26 +0200 Subject: [PATCH 17/27] hotbug: remove the pnpm-workspace.yaml from Dockerfile --- packages/server/Dockerfile | 1 - packages/webapp/Dockerfile | 1 - 2 files changed, 2 deletions(-) diff --git a/packages/server/Dockerfile b/packages/server/Dockerfile index 16415b9ae..3b0634dc4 100644 --- a/packages/server/Dockerfile +++ b/packages/server/Dockerfile @@ -92,7 +92,6 @@ 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 ./packages/server/package*.json ./packages/server/ diff --git a/packages/webapp/Dockerfile b/packages/webapp/Dockerfile index 41054a525..08f1d6f00 100644 --- a/packages/webapp/Dockerfile +++ b/packages/webapp/Dockerfile @@ -7,7 +7,6 @@ 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 ./packages/webapp/package*.json ./packages/webapp/ From 6eeda235598827d92a69f78ec544a06f17c679e1 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 6 May 2024 20:04:21 +0200 Subject: [PATCH 18/27] Update README.md --- README.md | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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. From d81e544e82a8fb22cb3d55562b6a66d0ded5994b Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 6 May 2024 21:11:39 +0200 Subject: [PATCH 19/27] chore: update pnpm-lock.yaml version --- pnpm-lock.yaml | 86 ++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 66 insertions(+), 20 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3c76abc1e..a636ab8ca 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1873,7 +1873,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 @@ -1889,7 +1889,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 @@ -4136,6 +4136,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 @@ -4769,11 +4770,12 @@ 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 @@ -4782,6 +4784,7 @@ packages: /@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 @@ -5260,7 +5263,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 @@ -6804,7 +6807,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: @@ -6865,7 +6868,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 @@ -7086,6 +7089,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} + requiresBuild: true dev: true /accepts@1.3.8: @@ -7402,6 +7406,7 @@ packages: /ansi-styles@6.2.1: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} + requiresBuild: true dev: true /ansi-wrap@0.1.0: @@ -8731,11 +8736,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 @@ -9441,7 +9447,7 @@ 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 @@ -10944,6 +10950,7 @@ packages: /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} + requiresBuild: true dev: true /ecc-jsbn@0.1.2: @@ -12053,6 +12060,7 @@ packages: /exponential-backoff@3.1.1: resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} + requiresBuild: true dev: true /express-basic-auth@1.2.1: @@ -12563,6 +12571,7 @@ 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 @@ -12626,7 +12635,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) @@ -12767,6 +12776,7 @@ packages: /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 @@ -13098,6 +13108,7 @@ 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 @@ -13839,6 +13850,7 @@ 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 @@ -14781,6 +14793,7 @@ packages: /isexe@3.1.1: resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} engines: {node: '>=16'} + requiresBuild: true dev: true /isobject@2.1.0: @@ -14908,6 +14921,7 @@ packages: /jackspeak@2.3.6: resolution: {integrity: sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==} engines: {node: '>=14'} + requiresBuild: true dependencies: '@isaacs/cliui': 8.0.2 optionalDependencies: @@ -15726,7 +15740,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 @@ -16153,7 +16167,7 @@ packages: 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==} @@ -16661,7 +16675,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 @@ -16670,7 +16684,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 @@ -16923,6 +16937,12 @@ 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 + dev: true + /lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} dependencies: @@ -17016,6 +17036,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 @@ -17383,6 +17404,7 @@ 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 @@ -17412,6 +17434,7 @@ 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 @@ -17419,6 +17442,7 @@ packages: /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 @@ -17480,6 +17504,7 @@ packages: /minipass@7.0.4: resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} engines: {node: '>=16 || 14 >=14.17'} + requiresBuild: true dev: true /minizlib@2.1.2: @@ -17961,11 +17986,12 @@ packages: 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 @@ -18067,6 +18093,7 @@ 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 @@ -18085,7 +18112,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 @@ -18195,7 +18222,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 @@ -19144,8 +19171,9 @@ 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 @@ -20271,6 +20299,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} + requiresBuild: true dev: true /proc-log@4.2.0: @@ -21772,6 +21801,15 @@ 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 + dev: true + /readdirp@2.2.1(supports-color@5.5.0): resolution: {integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==} engines: {node: '>=0.10'} @@ -21800,7 +21838,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: @@ -22851,6 +22889,7 @@ packages: /signal-exit@4.1.0: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} + requiresBuild: true dev: true /sigstore@1.9.0: @@ -23037,6 +23076,7 @@ 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 @@ -23280,6 +23320,7 @@ 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 @@ -23491,6 +23532,7 @@ 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 @@ -24780,6 +24822,7 @@ 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 @@ -24787,6 +24830,7 @@ packages: /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 @@ -25566,6 +25610,7 @@ 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 @@ -25837,6 +25882,7 @@ 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 @@ -25857,7 +25903,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 @@ -25883,7 +25929,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 From 4c0dc276dd0844cc1f054d0ad73879787fded32a Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 6 May 2024 22:04:15 +0200 Subject: [PATCH 20/27] hotfix: rollback the pnpm-workspace.yaml file --- pnpm-workspace.yaml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 pnpm-workspace.yaml diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 000000000..067a01bf0 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +packages: + # all packages in direct subdirs of packages/ + - 'packages/*' From 76bb82f2b443f5a638c993bb6ca9481f115d8d0a Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 6 May 2024 22:25:45 +0200 Subject: [PATCH 21/27] chore: update the pnpm-lock file --- pnpm-lock.yaml | 580 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 496 insertions(+), 84 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a636ab8ca..ac4b280c5 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -221,6 +221,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 @@ -3777,6 +3780,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'} @@ -4087,6 +4100,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 @@ -4144,7 +4176,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==} @@ -4633,6 +4664,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 @@ -4738,6 +4773,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: @@ -4779,7 +4871,6 @@ packages: socks-proxy-agent: 8.0.3 transitivePeerDependencies: - supports-color - dev: true /@npmcli/fs@3.1.0: resolution: {integrity: sha512-7kZUAaLscfgbwBQRbvdMYaZOWyMEcPTH/tJjnyAWJ/dvvs9Ef+CERx/qJb9GExJpl1qipaDGn7KqHnFGGixd0w==} @@ -4787,7 +4878,6 @@ packages: requiresBuild: true dependencies: semver: 7.5.4 - dev: true /@npmcli/git@5.0.6: resolution: {integrity: sha512-4x/182sKXmQkf0EtXxT26GEsaOATpD7WVtza5hrYivWZeo6QefC6xq9KAXrnjtFKBZ4rZwR7aX/zClYYXgtwLw==} @@ -5114,7 +5204,6 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} requiresBuild: true - dev: true optional: true /@pkgr/utils@2.3.1: @@ -5188,6 +5277,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: @@ -5985,6 +6123,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: @@ -6060,6 +6204,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: @@ -6203,6 +6353,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 @@ -6210,6 +6364,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==} @@ -6889,6 +7047,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 @@ -7090,7 +7252,6 @@ packages: resolution: {integrity: sha512-6/mh1E2u2YgEsCHdY0Yx5oW+61gZU+1vXaoiHHrpKeuRNNgFvS+/jrwHiQhB5apAf5oB7UB7E19ol2R2LKH8hQ==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} requiresBuild: true - dev: true /accepts@1.3.8: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} @@ -7125,6 +7286,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: @@ -7244,7 +7413,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 @@ -7252,16 +7421,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: @@ -7407,7 +7575,6 @@ packages: resolution: {integrity: sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==} engines: {node: '>=12'} requiresBuild: true - dev: true /ansi-wrap@0.1.0: resolution: {integrity: sha512-ZyznvL8k/FZeQHr2T6LzcJ/+vBApDnMNZvfVFy3At0knswWd6rJ3/0Hhmpu8oqa6C92npmozs890sX9Dl6q+Qw==} @@ -7843,6 +8010,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: @@ -8750,7 +8927,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 +9171,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 +9226,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 +9335,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==} @@ -9449,7 +9628,6 @@ packages: inherits: 2.0.4 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 +10492,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 +10570,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'} @@ -10951,7 +11123,6 @@ packages: /eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} requiresBuild: true - dev: true /ecc-jsbn@0.1.2: resolution: {integrity: sha512-eh9O+hwRHNbG4BLTjEl3nw044CkGm5X6LoaCf7LPp7UU8Qrt47JYNi6nPX8xjW97TKGKm1ouctg0QSpZe9qrnw==} @@ -11033,7 +11204,6 @@ packages: requiresBuild: true dependencies: iconv-lite: 0.6.3 - dev: true optional: true /end-of-stream@1.4.4: @@ -11115,7 +11285,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==} @@ -11124,7 +11293,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==} @@ -12061,7 +12229,6 @@ packages: /exponential-backoff@3.1.1: resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} requiresBuild: true - dev: true /express-basic-auth@1.2.1: resolution: {integrity: sha512-L6YQ1wQ/mNjVLAmK3AG1RK6VkokA1BIY6wmiH304Xtt/cLTps40EusZsU1Uop+v9lTDPxdtzbFmdXfFO3KEnwA==} @@ -12259,6 +12426,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 @@ -12428,6 +12599,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==} @@ -12544,6 +12719,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: @@ -12575,7 +12760,6 @@ packages: dependencies: cross-spawn: 7.0.3 signal-exit: 4.1.0 - dev: true /forever-agent@0.6.1: resolution: {integrity: sha512-j0KLYPhm6zeac4lz3oJ3o65qvgQCcPubiyotZrXqEaG4hNagNYO8qdlUrX5vwqv9ohqeT/Z3j6+yW067yWWdUw==} @@ -12657,7 +12841,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==} @@ -12756,6 +12939,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'} @@ -12771,7 +12963,6 @@ packages: engines: {node: '>= 8'} dependencies: minipass: 3.3.6 - dev: true /fs-minipass@3.0.3: resolution: {integrity: sha512-XUBA9XClHbnJWSfBzjkm6RvPsyg3sryZt06BEQoXcF7EK/xpGaQYJgQKDJSUH5SGZ76Y7pFx1QBnXz09rU5Fbw==} @@ -12779,7 +12970,6 @@ packages: requiresBuild: true dependencies: minipass: 7.0.4 - dev: true /fs-mkdirp-stream@1.0.0: resolution: {integrity: sha512-+vSd9frUnapVC2RZYfL3FCB2p3g4TBhaUmrsWlSudsGdnxIuUvBB2QM1VZeBtc49QFwrp+wQLrDs3+xxDgI5gQ==} @@ -12927,6 +13117,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'} @@ -13115,7 +13310,6 @@ packages: 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==} @@ -13564,7 +13758,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==} @@ -13781,9 +13974,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==} @@ -13842,7 +14044,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 @@ -13853,10 +14055,9 @@ packages: 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==} @@ -13901,6 +14102,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'} @@ -13929,7 +14136,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 @@ -13938,10 +14145,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==} @@ -14076,6 +14282,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'} @@ -14493,6 +14708,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'} @@ -14502,7 +14722,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==} @@ -14794,7 +15013,6 @@ packages: resolution: {integrity: sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==} engines: {node: '>=16'} requiresBuild: true - dev: true /isobject@2.1.0: resolution: {integrity: sha512-+OUdGJlgjOBZDfxnDjYYG6zp487z0JGNQq3cYQYg5f5hKR+syHMsaztzGeml/4kGG55CSpKSpWTY+jYGgsHLgA==} @@ -14926,7 +15144,6 @@ packages: '@isaacs/cliui': 8.0.2 optionalDependencies: '@pkgjs/parseargs': 0.11.0 - dev: true /jake@10.8.5: resolution: {integrity: sha512-sVpxYeuAhWt0OTWITwT98oyV0GsXyMlXCF+3L1SuafBVUIr/uILGRB+NqwkzhgXKvoJpDIpQvqkUALgdmQsQxw==} @@ -16120,6 +16337,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==} @@ -16162,6 +16385,12 @@ 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: @@ -16187,6 +16416,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'} @@ -16765,7 +16998,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==} @@ -16883,6 +17115,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: @@ -16908,6 +17153,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 @@ -16941,7 +17190,6 @@ packages: resolution: {integrity: sha512-tS24spDe/zXhWbNPErCHs/AGOzbKGHT+ybSBqmdLm8WZ1xXLWvH8Qn71QPAlqVhd0qUTWjy+Kl9JmISgDdEjsA==} engines: {node: 14 || >=16.14} requiresBuild: true - dev: true /lru-cache@4.1.5: resolution: {integrity: sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==} @@ -17051,7 +17299,6 @@ packages: ssri: 10.0.5 transitivePeerDependencies: - supports-color - dev: true /make-iterator@1.0.1: resolution: {integrity: sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==} @@ -17407,7 +17654,6 @@ packages: requiresBuild: true dependencies: brace-expansion: 2.0.1 - dev: true /minimist-options@4.1.0: resolution: {integrity: sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==} @@ -17437,7 +17683,6 @@ packages: requiresBuild: true dependencies: minipass: 7.0.4 - dev: true /minipass-fetch@3.0.4: resolution: {integrity: sha512-jHAqnA728uUpIaFm7NWsCnqKT6UqZz7GcI/bDpPATuwYyKwJwW0remxSCxUlKiEty+eopHGa3oc8WxgQ1FFJqg==} @@ -17449,14 +17694,12 @@ packages: 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==} @@ -17470,26 +17713,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==} @@ -17505,7 +17744,6 @@ packages: resolution: {integrity: sha512-jYofLM5Dam9279rdkWzqHozUo4ybjdZmCsDHePy5V/PbBcVMiSZR97gmAy45aqi8CK1lG2ECd356FU86avfwUQ==} engines: {node: '>=16 || 14 >=14.17'} requiresBuild: true - dev: true /minizlib@2.1.2: resolution: {integrity: sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==} @@ -17513,7 +17751,6 @@ packages: dependencies: minipass: 3.3.6 yallist: 4.0.0 - dev: true /mixin-deep@1.3.2: resolution: {integrity: sha512-WRoDn//mXBiJ1H40rqa3vH0toePwSsGb45iInWlTySa+Uu4k3tYUSxa2v1KqAiLtvlrSzaExqS1gtk96A9zvEA==} @@ -17523,6 +17760,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.) @@ -17541,7 +17784,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==} @@ -17566,6 +17808,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: @@ -17842,6 +18088,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: @@ -17911,6 +18163,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 @@ -17940,6 +18222,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'} @@ -17982,6 +18273,13 @@ 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} @@ -18000,7 +18298,6 @@ packages: which: 4.0.0 transitivePeerDependencies: - supports-color - dev: true /node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} @@ -18096,7 +18393,6 @@ packages: requiresBuild: true dependencies: abbrev: 2.0.0 - dev: true /normalize-package-data@2.5.0: resolution: {integrity: sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==} @@ -18308,6 +18604,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'} @@ -18892,7 +19197,6 @@ packages: engines: {node: '>=10'} dependencies: aggregate-error: 3.1.0 - dev: true /p-pipe@3.1.0: resolution: {integrity: sha512-08pj8ATpzMR0Y80x50yJHn37NF6vjrqHutASaX5LiH5npS9XPvrUmscd9MF5R4fuYRHOxQR1FfMIlF7AzwoPqw==} @@ -19022,6 +19326,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'} @@ -19175,7 +19483,6 @@ packages: dependencies: lru-cache: 10.2.1 minipass: 7.0.4 - dev: true /path-to-regexp@0.1.7: resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} @@ -20199,6 +20506,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'} @@ -20300,7 +20621,6 @@ packages: resolution: {integrity: sha512-++Vn7NS4Xf9NacaU9Xq3URUuqZETPsf8L4j5/ckhaRYsfPeRyzGw+iDjFhV/Jr3uNmTvvddEJFWh5R1gRgUH8A==} engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} requiresBuild: true - dev: true /proc-log@4.2.0: resolution: {integrity: sha512-g8+OnU/L2v+wyiVK+D5fA34J7EH8jZ8DDlvwhRCMxmMj7UCBvxiO1mGeN+36JXIKF4zevU4kRBd8lVgG9vLelA==} @@ -20350,7 +20670,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==} @@ -20551,6 +20870,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 @@ -21808,7 +22146,6 @@ packages: inherits: 2.0.4 string_decoder: 1.3.0 util-deprecate: 1.0.2 - dev: true /readdirp@2.2.1(supports-color@5.5.0): resolution: {integrity: sha512-1JU/8q+VgFZyxwrJ+SVIOsh+KywWGpds3NTqikiKpDMZWScmAYyKIgqkO+ARvNWJfXeXR1zxz7aHF4u4CyH6vQ==} @@ -22125,6 +22462,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'} @@ -22341,7 +22682,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==} @@ -22352,6 +22692,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 @@ -22372,6 +22716,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: @@ -22890,7 +23238,6 @@ packages: resolution: {integrity: sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==} engines: {node: '>=14'} requiresBuild: true - dev: true /sigstore@1.9.0: resolution: {integrity: sha512-0Zjz0oe37d08VeOtBIuB6cRriqXse2e8w+7yIy2XSXjshRKxbc2KkhXjL229jXSxEm7UbcjS76wcJDGQddVI9A==} @@ -23067,7 +23414,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 @@ -23079,11 +23426,10 @@ packages: 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==} @@ -23323,7 +23669,6 @@ packages: requiresBuild: true dependencies: minipass: 7.0.4 - dev: true /ssri@9.0.1: resolution: {integrity: sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==} @@ -23450,6 +23795,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'} @@ -23537,7 +23893,10 @@ packages: 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==} @@ -23909,6 +24268,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 @@ -23975,6 +24349,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'} @@ -24007,7 +24392,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==} @@ -24112,6 +24496,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'} @@ -24536,7 +24937,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 @@ -24547,7 +24948,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 @@ -24782,6 +25183,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'} @@ -24825,7 +25239,6 @@ packages: requiresBuild: true dependencies: unique-slug: 4.0.0 - dev: true /unique-slug@4.0.0: resolution: {integrity: sha512-WrcA6AyEfqDX5bWige/4NQfPZMtASNVxdmWR76WESYQVAACSgWcR6e9i0mofqqBxYFtL4oAxPIptY73/0YE1DQ==} @@ -24833,7 +25246,6 @@ packages: requiresBuild: true dependencies: imurmurhash: 0.1.4 - dev: true /unique-stream@2.3.1: resolution: {integrity: sha512-2nY4TnBE70yoxHkDli7DMazpWiP7xMdCYqU2nBRO0UB+ZpEkGsSija7MvmvnZFUeC+mrgiUfcHSr3LmRFIg4+A==} @@ -24860,6 +25272,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'} @@ -25049,7 +25466,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==} @@ -25613,7 +26029,6 @@ packages: requiresBuild: true dependencies: isexe: 3.1.1 - dev: true /wide-align@1.1.5: resolution: {integrity: sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==} @@ -25887,7 +26302,6 @@ packages: 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==} @@ -26104,7 +26518,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==} @@ -26163,7 +26576,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==} From 8c0ef61038368d7b020c9c620c9c730505abcfaa Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 6 May 2024 22:28:13 +0200 Subject: [PATCH 22/27] hotfix: add the pnpm-workspace.yaml file to Dockerfile --- packages/server/Dockerfile | 1 + packages/webapp/Dockerfile | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/server/Dockerfile b/packages/server/Dockerfile index 3b0634dc4..be4426405 100644 --- a/packages/server/Dockerfile +++ b/packages/server/Dockerfile @@ -93,6 +93,7 @@ RUN npm install -g pnpm COPY ./package*.json ./ COPY ./pnpm-lock.yaml ./pnpm-lock.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/webapp/Dockerfile b/packages/webapp/Dockerfile index 08f1d6f00..72db05769 100644 --- a/packages/webapp/Dockerfile +++ b/packages/webapp/Dockerfile @@ -8,6 +8,7 @@ WORKDIR /app COPY ./package*.json ./ COPY ./pnpm-lock.yaml ./pnpm-lock.yaml COPY ./lerna.json ./lerna.json +COPY ./pnpm-workspace.yaml ./pnpm-workspace.yaml COPY ./packages/webapp/package*.json ./packages/webapp/ # Install application dependencies @@ -23,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 From 7f31a48755bff9a307af796ecddf6ff333f4960f Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Mon, 6 May 2024 23:38:11 +0200 Subject: [PATCH 23/27] chore: dump the CHANGELOG.md file --- CHANGELOG.md | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 16567afb2..eaaa61f99 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,18 @@ 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 From 3020295841b3a39f432dbea282fdb6f37511ed86 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 9 May 2024 15:37:07 +0200 Subject: [PATCH 24/27] feat: Create a manifest list for `webapp` Docker image and push it to DockerHub. (#436) --- .github/workflows/build-deploy-container.yml | 55 +++++++++++++++----- 1 file changed, 42 insertions(+), 13 deletions(-) diff --git a/.github/workflows/build-deploy-container.yml b/.github/workflows/build-deploy-container.yml index 44ff44438..7661be470 100644 --- a/.github/workflows/build-deploy-container.yml +++ b/.github/workflows/build-deploy-container.yml @@ -6,9 +6,8 @@ on: workflow_dispatch: env: - REGISTRY: ghcr.io - WEBAPP_IMAGE_NAME: bigcapital/bigcapital-webapp - SERVER_IMAGE_NAME: bigcapital/bigcapital-server + WEBAPP_IMAGE_NAME: bigcapitalhq/webapp + SERVER_IMAGE_NAME: bigcapitalhq/server jobs: build-publish-webapp: @@ -40,15 +39,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 @@ -59,8 +57,8 @@ jobs: file: ./packages/webapp/Dockerfile platforms: ${{ matrix.platform }} push: true - tags: ghcr.io/bigcapitalhq/webapp:latest, ghcr.io/bigcapitalhq/webapp:${{github.ref_name}} labels: ${{ steps.meta.outputs.labels }} + outputs: type=image,name=bigcapitalhq/webapp,push-by-digest=true,name-canonical=true,push=true - name: Export digest run: | @@ -103,9 +101,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 @@ -116,7 +113,7 @@ jobs: file: ./packages/server/Dockerfile platforms: ${{ matrix.platform }} push: true - tags: ghcr.io/bigcapitalhq/server:latest, ghcr.io/bigcapitalhq/server:${{github.ref_name}} + tags: bigcapitalhq/server:latest, bigcapitalhq/server:${{github.ref_name}} labels: ${{ steps.meta.outputs.labels }} - name: Export digest @@ -137,4 +134,36 @@ jobs: - name: Slack Notification built and published server container successfully. uses: rtCamp/action-slack-notify@v2 env: - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} \ No newline at end of file + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + + merge: + runs-on: ubuntu-latest + needs: + - build-publish-webapp + steps: + - name: Download digests + uses: actions/download-artifact@v4 + with: + path: /tmp/digests + pattern: digests-* + merge-multiple: true + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Docker meta + id: meta + uses: docker/metadata-action@v5 + with: + images: ${{ env.WEBAPP_IMAGE_NAME }} + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} + - name: Create manifest list and push + working-directory: /tmp/digests + run: | + docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ + $(printf '${{ env.WEBAPP_IMAGE_NAME }}@sha256:%s ' *) + - name: Inspect image + run: | + docker buildx imagetools inspect ${{ env.WEBAPP_IMAGE_NAME }}:${{ steps.meta.outputs.version }} From 8aefa7709cc559b9fea1499a8a4131bf3b4dcb55 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 9 May 2024 19:25:51 +0200 Subject: [PATCH 25/27] feat: Combine arm64 and amd64 in one Github action runner (#437) --- .github/workflows/build-deploy-container.yml | 54 +++----------------- docker-compose.prod.yml | 4 +- 2 files changed, 8 insertions(+), 50 deletions(-) diff --git a/.github/workflows/build-deploy-container.yml b/.github/workflows/build-deploy-container.yml index 7661be470..0da29c037 100644 --- a/.github/workflows/build-deploy-container.yml +++ b/.github/workflows/build-deploy-container.yml @@ -13,10 +13,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 @@ -29,9 +25,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 @@ -55,10 +48,10 @@ jobs: with: context: ./ file: ./packages/webapp/Dockerfile - platforms: ${{ matrix.platform }} + platforms: linux/amd64,linux/arm64 push: true labels: ${{ steps.meta.outputs.labels }} - outputs: type=image,name=bigcapitalhq/webapp,push-by-digest=true,name-canonical=true,push=true + tags: bigcapitalhq/webapp:latest, bigcapitalhq/webapp:${{github.ref_name}} - name: Export digest run: | @@ -69,7 +62,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 @@ -91,9 +84,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 @@ -111,7 +101,7 @@ jobs: with: context: ./ file: ./packages/server/Dockerfile - platforms: ${{ matrix.platform }} + platforms: linux/amd64,linux/arm64 push: true tags: bigcapitalhq/server:latest, bigcapitalhq/server:${{github.ref_name}} labels: ${{ steps.meta.outputs.labels }} @@ -125,45 +115,13 @@ 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 env: SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - - merge: - runs-on: ubuntu-latest - needs: - - build-publish-webapp - steps: - - name: Download digests - uses: actions/download-artifact@v4 - with: - path: /tmp/digests - pattern: digests-* - merge-multiple: true - - name: Set up Docker Buildx - uses: docker/setup-buildx-action@v3 - - name: Docker meta - id: meta - uses: docker/metadata-action@v5 - with: - images: ${{ env.WEBAPP_IMAGE_NAME }} - - name: Login to Docker Hub - uses: docker/login-action@v3 - with: - username: ${{ secrets.DOCKER_USERNAME }} - password: ${{ secrets.DOCKER_PASSWORD }} - - name: Create manifest list and push - working-directory: /tmp/digests - run: | - docker buildx imagetools create $(jq -cr '.tags | map("-t " + .) | join(" ")' <<< "$DOCKER_METADATA_OUTPUT_JSON") \ - $(printf '${{ env.WEBAPP_IMAGE_NAME }}@sha256:%s ' *) - - name: Inspect image - run: | - docker buildx imagetools inspect ${{ env.WEBAPP_IMAGE_NAME }}:${{ steps.meta.outputs.version }} diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 9620598e4..5a71de09f 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: From 92e3d31360b04fe4bbdffae895e745b8323148e6 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Thu, 9 May 2024 20:10:49 +0200 Subject: [PATCH 26/27] fix: use server container from Docker registry instead --- docker/migration/Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docker/migration/Dockerfile b/docker/migration/Dockerfile index 190c1b389..69ea51793 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= \ From 23d27cafc1ecd7adeb8adc83239912f4c2ae87d3 Mon Sep 17 00:00:00 2001 From: Ahmed Bouhuolia Date: Fri, 10 May 2024 11:36:01 +0200 Subject: [PATCH 27/27] fix: Update Dockerfile --- docker/migration/Dockerfile | 3 --- 1 file changed, 3 deletions(-) diff --git a/docker/migration/Dockerfile b/docker/migration/Dockerfile index 69ea51793..d61ef0679 100644 --- a/docker/migration/Dockerfile +++ b/docker/migration/Dockerfile @@ -34,8 +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 - # 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"