1
0

Merge pull request #1076 from bigcapitalhq/feat/financial-audit-trail

feat(ee): audit logs domain level
This commit is contained in:
Ahmed Bouhuolia
2026-05-17 21:18:03 +02:00
committed by GitHub
62 changed files with 4494 additions and 196 deletions
+5 -5
View File
@@ -36,15 +36,15 @@
"@aws-sdk/s3-request-presigner": "^3.583.0",
"@bigcapital/email-components": "workspace:*",
"@bigcapital/pdf-templates": "workspace:*",
"@bigcapital/sdk-ts": "workspace:*",
"@bigcapital/utils": "workspace:*",
"@bigcapital/sdk-ts": "workspace:*",
"@bull-board/api": "^5.22.0",
"@bull-board/express": "^5.22.0",
"@bull-board/nestjs": "^5.22.0",
"@casl/ability": "^5.4.3",
"@lemonsqueezy/lemonsqueezy.js": "^2.2.0",
"@liaoliaots/nestjs-redis": "^10.0.0",
"@nest-lab/throttler-storage-redis": "^1.1.0",
"@bull-board/api": "^5.22.0",
"@bull-board/express": "^5.22.0",
"@bull-board/nestjs": "^5.22.0",
"@nestjs/bull": "^10.2.1",
"@nestjs/bullmq": "^10.2.2",
"@nestjs/cache-manager": "^2.2.2",
@@ -71,7 +71,7 @@
"async": "^3.2.0",
"async-mutex": "^0.5.0",
"axios": "^1.6.0",
"bcrypt": "^5.1.1",
"bcrypt": "5.1.1",
"bcryptjs": "^2.4.3",
"bluebird": "^3.7.2",
"bull": "^4.16.3",
@@ -95,6 +95,14 @@ export const events = {
onActivated: 'onAccountActivated',
},
/**
* Contacts service.
*/
contacts: {
onActivated: 'onContactActivated',
onInactivated: 'onContactInactivated',
},
/**
* Manual journals service.
*/
@@ -0,0 +1,17 @@
exports.up = (knex) => {
return knex.schema.createTable('audit_logs', (table) => {
table.increments('id').primary();
table.integer('user_id').unsigned().nullable().index();
table.string('action', 64).notNullable();
table.string('subject', 64).notNullable();
table.integer('subject_id').unsigned().nullable();
table.json('metadata').nullable();
table.string('ip', 64).nullable();
table.timestamp('created_at').notNullable().defaultTo(knex.fn.now());
table.index(['subject', 'subject_id']);
table.index(['created_at']);
});
};
exports.down = (knex) => knex.schema.dropTableIfExists('audit_logs');
+116
View File
@@ -0,0 +1,116 @@
{
"subject.SaleInvoice": "Sale Invoice",
"subject.SaleEstimate": "Sale Estimate",
"subject.SaleReceipt": "Sale Receipt",
"subject.PaymentReceive": "Payment Received",
"subject.PaymentMade": "Payment Made",
"subject.CreditNote": "Credit Note",
"subject.VendorCredit": "Vendor Credit",
"subject.ManualJournal": "Manual Journal",
"subject.InventoryAdjustment": "Inventory Adjustment",
"subject.WarehouseTransfer": "Warehouse Transfer",
"subject.ItemCategory": "Item Category",
"subject.BankRule": "Bank Rule",
"subject.TransactionsLocking": "Transactions Locking",
"subject.CreditNoteRefund": "Credit Note Refund",
"subject.VendorCreditRefund": "Vendor Credit Refund",
"subject.Cashflow": "Cashflow",
"subject.TaxRate": "Tax Rate",
"subject.UncategorizedTransaction": "Uncategorized Transaction",
"subject.PlaidTransactions": "Plaid Transactions",
"subject.BankTransaction": "Bank Transaction",
"subject.Bill": "Bill",
"subject.Expense": "Expense",
"subject.Account": "Account",
"subject.Item": "Item",
"subject.Customer": "Customer",
"subject.Vendor": "Vendor",
"subject.Role": "Role",
"subject.Warehouse": "Warehouse",
"subject.Branch": "Branch",
"action.created": "Created",
"action.edited": "Edited",
"action.deleted": "Deleted",
"action.opened": "Opened",
"action.delivered": "Delivered",
"action.writtenoff": "Written Off",
"action.writtenoff_canceled": "Write-off Canceled",
"action.published": "Published",
"action.refund_created": "Refund Created",
"action.categorized": "Categorized",
"action.activated": "Activated",
"action.initiated": "Initiated",
"action.transferred": "Transferred",
"action.locking_changed": "Locking Changed",
"metadata.bill_with_amount": "Bill {billNumber} - {amount} {currencyCode}",
"metadata.bill": "Bill {billNumber}",
"metadata.invoice_with_balance": "Invoice {invoiceNumber} - Balance: {balance} {currencyCode}",
"metadata.invoice": "Invoice {invoiceNumber}",
"metadata.receipt_with_amount": "Receipt {receiptNumber} - {amount} {currencyCode}",
"metadata.receipt": "Receipt {receiptNumber}",
"metadata.estimate_with_total": "Estimate {estimateNumber} - {total} {currencyCode}",
"metadata.estimate": "Estimate {estimateNumber}",
"metadata.payment_receive_with_amount": "Payment {paymentReceiveNo} - {amount} {currencyCode}",
"metadata.payment_receive": "Payment {paymentReceiveNo}",
"metadata.payment_made_with_amount": "Payment {paymentNumber} - {amount} {currencyCode}",
"metadata.payment_made": "Payment {paymentNumber}",
"metadata.expense_with_currency": "Expense - {amount} {currencyCode}",
"metadata.expense": "Expense - {amount}",
"metadata.expense_plain": "Expense",
"metadata.credit_note_with_amount": "Credit Note {creditNoteNumber} - {amount} {currencyCode}",
"metadata.credit_note": "Credit Note {creditNoteNumber}",
"metadata.vendor_credit_with_total": "Vendor Credit {vendorCreditNumber} - {total} {currencyCode}",
"metadata.vendor_credit": "Vendor Credit {vendorCreditNumber}",
"metadata.journal_with_amount": "Journal {journalNumber} - {amount} {currencyCode}",
"metadata.journal": "Journal {journalNumber}",
"metadata.cashflow_with_currency": "Cashflow - {amount} {currencyCode}",
"metadata.cashflow": "Cashflow - {amount}",
"metadata.cashflow_plain": "Cashflow",
"metadata.account_with_code": "Account: {name} ({code})",
"metadata.account": "Account: {name}",
"metadata.account_plain": "Account",
"metadata.adjustment": "Adjustment: {reason}",
"metadata.adjustment_plain": "Inventory Adjustment",
"metadata.transfer": "Transfer: {transactionNumber}",
"metadata.transfer_plain": "Warehouse Transfer",
"metadata.item_with_code": "{name} ({code})",
"metadata.item": "{name}",
"metadata.item_plain": "Item",
"metadata.customer_with_email": "{displayName} ({email})",
"metadata.customer": "{displayName}",
"metadata.customer_plain": "Customer",
"metadata.vendor_with_email": "{displayName} ({email})",
"metadata.vendor": "{displayName}",
"metadata.vendor_plain": "Vendor",
"metadata.role_with_old": "Role: {roleName} (was: {oldRoleName})",
"metadata.role": "Role: {roleName}",
"metadata.role_plain": "Role",
"metadata.tax_rate": "{name} - {rate}%",
"metadata.tax_rate_name": "{name}",
"metadata.tax_rate_plain": "Tax Rate",
"metadata.warehouse": "Warehouse: {code}",
"metadata.warehouse_plain": "Warehouse",
"metadata.branch_with_code": "{name} ({code})",
"metadata.branch": "{name}",
"metadata.branch_plain": "Branch",
"metadata.item_category": "Category: {name}",
"metadata.item_category_plain": "Item Category",
"metadata.bank_rule": "Rule: {name}",
"metadata.bank_rule_plain": "Bank Rule",
"metadata.locking_with_date": "Module: {module} locked to {lockToDate}",
"metadata.locking_module": "Module: {module}",
"metadata.locking_plain": "Transactions Locking",
"metadata.refund_amount": "Refund - {amount}",
"metadata.refund_plain": "Refund",
"metadata.imported_with_payee": "Imported - {payee}: {amount} {currencyCode}",
"metadata.imported": "Imported - {amount} {currencyCode}",
"metadata.imported_plain": "Imported Transaction",
"metadata.plaid_with_batch": "Plaid Sync - Account {plaidAccountId} (Batch: {batch})",
"metadata.plaid": "Plaid Sync - Account {plaidAccountId}",
"metadata.plaid_plain": "Plaid Sync",
"metadata.bank_with_payee": "{payee}: {amount} {currencyCode}",
"metadata.bank": "{amount} {currencyCode}",
"metadata.bank_plain": "Bank Transaction"
}
@@ -147,6 +147,8 @@ export interface IAccountEventDeletePayload {
export interface IAccountEventActivatedPayload {
tenantId: number;
accountId: number;
activate: boolean;
account: IAccount;
trx: Knex.Transaction;
}
+12
View File
@@ -155,6 +155,18 @@ export interface IItemEventDeletedPayload {
trx: Knex.Transaction;
}
export interface IItemEventActivatedPayload {
item: Item;
itemId: number;
trx: Knex.Transaction;
}
export interface IItemEventInactivatedPayload {
item: Item;
itemId: number;
trx: Knex.Transaction;
}
export enum ItemAction {
CREATE = 'Create',
EDIT = 'Edit',
@@ -67,6 +67,8 @@ export interface IAccountEventDeletePayload {
export interface IAccountEventActivatedPayload {
accountId: number;
activate: boolean;
account: Account;
trx: Knex.Transaction;
}
@@ -53,6 +53,8 @@ export class ActivateAccount {
// Triggers `onAccountActivated` event.
this.eventEmitter.emitAsync(events.accounts.onActivated, {
accountId,
activate,
account: oldAccount,
trx,
} as IAccountEventActivatedPayload);
});
@@ -103,6 +103,7 @@ import { ExchangeRatesModule } from '../ExchangeRates/ExchangeRates.module';
import { TenantModelsInitializeModule } from '../Tenancy/TenantModelsInitialize.module';
import { BillLandedCostsModule } from '../BillLandedCosts/BillLandedCosts.module';
import { SocketModule } from '../Socket/Socket.module';
import { EEModule } from '../EE/EE.module';
import { ThrottlerGuard } from '@nestjs/throttler';
import { AppThrottleModule } from './AppThrottle.module';
@@ -254,6 +255,7 @@ import { AppThrottleModule } from './AppThrottle.module';
UsersModule,
ContactsModule,
SocketModule,
EEModule,
ExchangeRatesModule,
],
controllers: [AppController],
@@ -0,0 +1,71 @@
import { Inject, Injectable } from '@nestjs/common';
import { ClsService } from 'nestjs-cls';
import { Knex } from 'knex';
import * as moment from 'moment';
import '@/utils/moment-mysql';
import { AuditLog } from './models/AuditLog.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants';
const METADATA_JSON_MAX = 8000;
export interface RecordAuditLogParams {
/** When set, the row is written in the same DB transaction as the business change. */
trx?: Knex.Transaction;
action: string;
subject: string;
subjectId?: number | null;
metadata?: Record<string, unknown> | null;
}
@Injectable()
export class AuditLogService {
constructor(
private readonly cls: ClsService,
@Inject(AuditLog.name)
private readonly auditLogModel: TenantModelProxy<typeof AuditLog>,
@Inject(TENANCY_DB_CONNECTION)
private readonly tenantKnex: () => Knex,
) {}
/**
* Persists one audit row. Prefer always passing `trx` from domain event payloads so the
* audit row rolls back with failed business transactions. If `trx` is omitted, the insert
* runs on a separate connection/transaction (only use after the business change committed).
*/
async record(params: RecordAuditLogParams): Promise<void> {
const userId = this.cls.get<number>('userId') ?? null;
const ip = (this.cls.get<string>('ip') as string) ?? null;
const executor = params.trx ?? this.tenantKnex();
const metadata = this.normalizeMetadata(params.metadata);
await this.auditLogModel()
.query(executor)
.insert({
userId,
action: params.action,
subject: params.subject,
subjectId: params.subjectId ?? null,
metadata,
ip,
// MySQL DATETIME expects `YYYY-MM-DD HH:mm:ss`, not ISO-8601 with `T`/`Z`.
createdAt: moment().toMySqlDateTime(),
});
}
private normalizeMetadata(
metadata: Record<string, unknown> | null | undefined,
): Record<string, unknown> | null {
if (metadata == null) return null;
try {
const s = JSON.stringify(metadata);
if (s.length <= METADATA_JSON_MAX) return metadata;
return {
_truncated: true,
summary: s.slice(0, METADATA_JSON_MAX),
};
} catch {
return { _error: 'metadata_not_serializable' };
}
}
}
@@ -0,0 +1,25 @@
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
import { ApiOperation, ApiTags } from '@nestjs/swagger';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { GetAuditLogsQueryDto } from './dtos/GetAuditLogsQuery.dto';
import { GetAuditLogsService } from './queries/GetAuditLogs.service';
import { AuditLogAction } from './types/AuditLogs.types';
@Controller('audit-logs')
@ApiTags('Audit logs')
@ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class AuditLogsController {
constructor(private readonly getAuditLogsService: GetAuditLogsService) {}
@Get()
@RequirePermission(AuditLogAction.View, AbilitySubject.AuditLog)
@ApiOperation({ summary: 'List financial audit log entries for the tenant.' })
getAuditLogs(@Query() query: GetAuditLogsQueryDto) {
return this.getAuditLogsService.getAuditLogs(query);
}
}
@@ -0,0 +1,20 @@
import { Module } from '@nestjs/common';
import { AuditLogsController } from './AuditLogs.controller';
import { AuditLogService } from './AuditLog.service';
import { GetAuditLogsService } from './queries/GetAuditLogs.service';
import { FinancialAuditLogSubscriber } from './subscribers/FinancialAuditLog.subscriber';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
@Module({
controllers: [AuditLogsController],
providers: [
AuditLogService,
GetAuditLogsService,
FinancialAuditLogSubscriber,
AuthorizationGuard,
PermissionGuard,
],
exports: [AuditLogService],
})
export class AuditLogsModule {}
@@ -0,0 +1,75 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import {
ArrayMaxSize,
IsArray,
IsInt,
IsOptional,
IsString,
Max,
Min,
} from 'class-validator';
import { ToNumber } from '@/common/decorators/Validators';
function toOptionalStringArray(value: unknown): string[] | undefined {
if (value === undefined || value === null || value === '') {
return undefined;
}
const raw = Array.isArray(value) ? value : [value];
const filtered = raw
.map((v) => (v == null ? '' : String(v).trim()))
.filter((v) => v.length > 0);
if (!filtered.length) {
return undefined;
}
return [...new Set(filtered)];
}
export class GetAuditLogsQueryDto {
@ApiPropertyOptional({ minimum: 1, default: 1 })
@IsOptional()
@ToNumber()
@IsInt()
@Min(1)
page?: number;
@ApiPropertyOptional({ minimum: 1, maximum: 100, default: 20 })
@IsOptional()
@ToNumber()
@IsInt()
@Min(1)
@Max(100)
pageSize?: number;
@ApiPropertyOptional({ type: [String], isArray: true })
@Transform(({ value }) => toOptionalStringArray(value))
@IsOptional()
@IsArray()
@ArrayMaxSize(50)
@IsString({ each: true })
subject?: string[];
@ApiPropertyOptional({ type: [String], isArray: true })
@Transform(({ value }) => toOptionalStringArray(value))
@IsOptional()
@IsArray()
@ArrayMaxSize(50)
@IsString({ each: true })
action?: string[];
@ApiPropertyOptional({ description: 'System user id' })
@IsOptional()
@ToNumber()
@IsInt()
userId?: number;
@ApiPropertyOptional({ description: 'ISO date (inclusive), start of day' })
@IsOptional()
@IsString()
from?: string;
@ApiPropertyOptional({ description: 'ISO date (inclusive), end of day' })
@IsOptional()
@IsString()
to?: string;
}
@@ -0,0 +1,63 @@
import { Model } from 'objection';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { TenantUser } from '@/modules/Tenancy/TenancyModels/models/TenantUser.model';
export class AuditLog extends TenantBaseModel {
public tenantUser?: TenantUser;
public id!: number;
/** System user id (matches CLS `userId` / `users.system_user_id` in tenant DB). */
public userId!: number | null;
public action!: string;
public subject!: string;
public subjectId!: number | null;
public metadata!: Record<string, unknown> | null;
public ip!: string | null;
public createdAt!: Date | string;
static get tableName() {
return 'audit_logs';
}
static get jsonAttributes() {
return ['metadata'];
}
/**
* No `updated_at`; `created_at` is set in AuditLogService.
*/
get timestamps() {
return [];
}
static get relationMappings() {
return {
tenantUser: {
relation: Model.BelongsToOneRelation,
modelClass: TenantUser,
join: {
from: 'audit_logs.userId',
to: 'users.systemUserId',
},
},
};
}
static get jsonSchema() {
return {
type: 'object',
required: ['action', 'subject'],
properties: {
id: { type: 'integer' },
userId: { type: ['integer', 'null'] },
action: { type: 'string', maxLength: 64 },
subject: { type: 'string', maxLength: 64 },
subjectId: { type: ['integer', 'null'] },
metadata: { type: ['object', 'null'] },
ip: { type: ['string', 'null'], maxLength: 64 },
// Stored as MySQL `YYYY-MM-DD HH:mm:ss` (see AuditLogService), not strict ISO-8601.
createdAt: { type: 'string' },
},
};
}
}
@@ -0,0 +1,75 @@
import * as moment from 'moment';
import { Transformer } from '@/modules/Transformer/Transformer';
import {
formatAction,
formatMetadataSummary,
formatSubject,
} from './GetAuditLogList.transformer.utils';
export class GetAuditLogListTransformer extends Transformer {
public excludeAttributes = (): string[] => {
return ['*'];
};
public includeAttributes = (): string[] => {
return [
'id',
'userId',
'userName',
'userEmail',
'action',
'subject',
'subjectId',
'metadata',
'summary',
'ip',
'createdAt',
'createdAtFormatted',
];
};
protected userName = (item: Record<string, any>): string | null => {
if (!item.tenantUser) return null;
const u = item.tenantUser as Record<string, string>;
const firstName = u.firstName || u.first_name || '';
const lastName = u.lastName || u.last_name || '';
const fullName = u.fullName || u.full_name || '';
const name = fullName || `${firstName} ${lastName}`.trim();
return name || null;
};
protected userEmail = (item: Record<string, any>): string | null => {
if (!item.tenantUser) return null;
const u = item.tenantUser as Record<string, string>;
const email =
u.email || u.emailAddress || u.email_address || '';
return email || null;
};
protected action = (item: Record<string, any>): string => {
return formatAction(item.action, this.context.i18n.t.bind(this.context.i18n));
};
protected subject = (item: Record<string, any>): string => {
return formatSubject(item.subject, this.context.i18n.t.bind(this.context.i18n));
};
protected summary = (item: Record<string, any>): string => {
return formatMetadataSummary(
item.metadata,
item.subject,
this.context.i18n.t.bind(this.context.i18n),
);
};
protected createdAt = (item: Record<string, any>): string => {
const raw = item.createdAt;
if (typeof raw === 'string') return raw;
return (raw as Date)?.toISOString?.() ?? String(raw);
};
protected createdAtFormatted = (item: Record<string, any>): string => {
const createdAtStr = this.createdAt(item);
return moment(createdAtStr).format('YYYY-MM-DD HH:mm:ss');
};
}
@@ -0,0 +1,374 @@
export type TranslateFn = (key: string, options?: { args?: Record<string, any> }) => string;
const defaultT: TranslateFn = (key) => key;
/**
* Format camelCase subject to readable text using i18n.
*/
export function formatSubject(subject: string, t: TranslateFn = defaultT): string {
return t(`audit_log.subject.${subject}`);
}
/**
* Format action to capitalized text using i18n.
*/
export function formatAction(action: string, t: TranslateFn = defaultT): string {
if (!action) return '';
return t(`audit_log.action.${action}`);
}
/**
* Format metadata into a human-readable summary based on subject type.
*/
export function formatMetadataSummary(
metadata: Record<string, unknown> | null,
subject: string,
t: TranslateFn = defaultT,
): string {
if (metadata == null) return '';
const formatters: Record<string, (m: Record<string, unknown>) => string> = {
Bill: (m) => {
if (m.billNumber) {
return m.amount
? t('audit_log.metadata.bill_with_amount', {
args: { billNumber: String(m.billNumber), amount: String(m.amount), currencyCode: String(m.currencyCode || '') },
})
: t('audit_log.metadata.bill', {
args: { billNumber: String(m.billNumber) },
});
}
return String(m.billNumber || '');
},
SaleInvoice: (m) => {
if (m.invoiceNumber) {
return m.balance
? t('audit_log.metadata.invoice_with_balance', {
args: { invoiceNumber: String(m.invoiceNumber), balance: String(m.balance), currencyCode: String(m.currencyCode || '') },
})
: t('audit_log.metadata.invoice', {
args: { invoiceNumber: String(m.invoiceNumber) },
});
}
return String(m.invoiceNumber || '');
},
SaleReceipt: (m) => {
if (m.receiptNumber) {
return m.amount
? t('audit_log.metadata.receipt_with_amount', {
args: { receiptNumber: String(m.receiptNumber), amount: String(m.amount), currencyCode: String(m.currencyCode || '') },
})
: t('audit_log.metadata.receipt', {
args: { receiptNumber: String(m.receiptNumber) },
});
}
return String(m.receiptNumber || '');
},
SaleEstimate: (m) => {
if (m.estimateNumber) {
return m.total
? t('audit_log.metadata.estimate_with_total', {
args: { estimateNumber: String(m.estimateNumber), total: String(m.total), currencyCode: String(m.currencyCode || '') },
})
: t('audit_log.metadata.estimate', {
args: { estimateNumber: String(m.estimateNumber) },
});
}
return String(m.estimateNumber || '');
},
PaymentReceive: (m) => {
if (m.paymentReceiveNo) {
return m.amount
? t('audit_log.metadata.payment_receive_with_amount', {
args: { paymentReceiveNo: String(m.paymentReceiveNo), amount: String(m.amount), currencyCode: String(m.currencyCode || '') },
})
: t('audit_log.metadata.payment_receive', {
args: { paymentReceiveNo: String(m.paymentReceiveNo) },
});
}
return String(m.paymentReceiveNo || '');
},
PaymentMade: (m) => {
if (m.paymentNumber) {
return m.amount
? t('audit_log.metadata.payment_made_with_amount', {
args: { paymentNumber: String(m.paymentNumber), amount: String(m.amount), currencyCode: String(m.currencyCode || '') },
})
: t('audit_log.metadata.payment_made', {
args: { paymentNumber: String(m.paymentNumber) },
});
}
return String(m.paymentNumber || '');
},
Expense: (m) => {
if (m.amount) {
return m.currencyCode
? t('audit_log.metadata.expense_with_currency', {
args: { amount: String(m.amount), currencyCode: String(m.currencyCode) },
})
: t('audit_log.metadata.expense', {
args: { amount: String(m.amount) },
});
}
return t('audit_log.metadata.expense_plain');
},
CreditNote: (m) => {
if (m.creditNoteNumber) {
return m.amount
? t('audit_log.metadata.credit_note_with_amount', {
args: { creditNoteNumber: String(m.creditNoteNumber), amount: String(m.amount), currencyCode: String(m.currencyCode || '') },
})
: t('audit_log.metadata.credit_note', {
args: { creditNoteNumber: String(m.creditNoteNumber) },
});
}
return String(m.creditNoteNumber || '');
},
VendorCredit: (m) => {
if (m.vendorCreditNumber) {
return m.total
? t('audit_log.metadata.vendor_credit_with_total', {
args: { vendorCreditNumber: String(m.vendorCreditNumber), total: String(m.total), currencyCode: String(m.currencyCode || '') },
})
: t('audit_log.metadata.vendor_credit', {
args: { vendorCreditNumber: String(m.vendorCreditNumber) },
});
}
return String(m.vendorCreditNumber || '');
},
ManualJournal: (m) => {
if (m.journalNumber) {
return m.amount
? t('audit_log.metadata.journal_with_amount', {
args: { journalNumber: String(m.journalNumber), amount: String(m.amount), currencyCode: String(m.currencyCode || '') },
})
: t('audit_log.metadata.journal', {
args: { journalNumber: String(m.journalNumber) },
});
}
return String(m.journalNumber || '');
},
Cashflow: (m) => {
if (m.amount) {
return m.currencyCode
? t('audit_log.metadata.cashflow_with_currency', {
args: { amount: String(m.amount), currencyCode: String(m.currencyCode) },
})
: t('audit_log.metadata.cashflow', {
args: { amount: String(m.amount) },
});
}
return t('audit_log.metadata.cashflow_plain');
},
Account: (m) => {
if (m.name) {
return m.code
? t('audit_log.metadata.account_with_code', {
args: { name: String(m.name), code: String(m.code) },
})
: t('audit_log.metadata.account', {
args: { name: String(m.name) },
});
}
return t('audit_log.metadata.account_plain');
},
InventoryAdjustment: (m) => {
return m.reason
? t('audit_log.metadata.adjustment', {
args: { reason: String(m.reason) },
})
: t('audit_log.metadata.adjustment_plain');
},
WarehouseTransfer: (m) => {
if (m.transactionNumber) {
return t('audit_log.metadata.transfer', {
args: { transactionNumber: String(m.transactionNumber) },
});
}
return t('audit_log.metadata.transfer_plain');
},
Item: (m) => {
if (m.name) {
return m.code
? t('audit_log.metadata.item_with_code', {
args: { name: String(m.name), code: String(m.code) },
})
: t('audit_log.metadata.item', {
args: { name: String(m.name) },
});
}
return t('audit_log.metadata.item_plain');
},
Customer: (m) => {
if (m.displayName) {
return m.email
? t('audit_log.metadata.customer_with_email', {
args: { displayName: String(m.displayName), email: String(m.email) },
})
: t('audit_log.metadata.customer', {
args: { displayName: String(m.displayName) },
});
}
return t('audit_log.metadata.customer_plain');
},
Vendor: (m) => {
if (m.displayName) {
return m.email
? t('audit_log.metadata.vendor_with_email', {
args: { displayName: String(m.displayName), email: String(m.email) },
})
: t('audit_log.metadata.vendor', {
args: { displayName: String(m.displayName) },
});
}
return t('audit_log.metadata.vendor_plain');
},
Role: (m) => {
if (m.roleName) {
return m.oldRoleName
? t('audit_log.metadata.role_with_old', {
args: { roleName: String(m.roleName), oldRoleName: String(m.oldRoleName) },
})
: t('audit_log.metadata.role', {
args: { roleName: String(m.roleName) },
});
}
return t('audit_log.metadata.role_plain');
},
TaxRate: (m) => {
if (m.name) {
return m.rate !== undefined
? t('audit_log.metadata.tax_rate', {
args: { name: String(m.name), rate: String(m.rate) },
})
: t('audit_log.metadata.tax_rate_name', {
args: { name: String(m.name) },
});
}
return t('audit_log.metadata.tax_rate_plain');
},
Warehouse: (m) => {
return m.code
? t('audit_log.metadata.warehouse', {
args: { code: String(m.code) },
})
: t('audit_log.metadata.warehouse_plain');
},
Branch: (m) => {
if (m.name) {
return m.code
? t('audit_log.metadata.branch_with_code', {
args: { name: String(m.name), code: String(m.code) },
})
: t('audit_log.metadata.branch', {
args: { name: String(m.name) },
});
}
return t('audit_log.metadata.branch_plain');
},
ItemCategory: (m) => {
return m.name
? t('audit_log.metadata.item_category', {
args: { name: String(m.name) },
})
: t('audit_log.metadata.item_category_plain');
},
BankRule: (m) => {
return m.name
? t('audit_log.metadata.bank_rule', {
args: { name: String(m.name) },
})
: t('audit_log.metadata.bank_rule_plain');
},
TransactionsLocking: (m) => {
if (m.module) {
return m.lockToDate
? t('audit_log.metadata.locking_with_date', {
args: { module: String(m.module), lockToDate: String(m.lockToDate) },
})
: t('audit_log.metadata.locking_module', {
args: { module: String(m.module) },
});
}
return t('audit_log.metadata.locking_plain');
},
CreditNoteRefund: (m) => {
if (m.amount) {
return t('audit_log.metadata.refund_amount', {
args: { amount: String(m.amount) },
});
}
return t('audit_log.metadata.refund_plain');
},
VendorCreditRefund: (m) => {
if (m.amount) {
return t('audit_log.metadata.refund_amount', {
args: { amount: String(m.amount) },
});
}
return t('audit_log.metadata.refund_plain');
},
UncategorizedTransaction: (m) => {
if (m.amount) {
return m.payee
? t('audit_log.metadata.imported_with_payee', {
args: { payee: String(m.payee), amount: String(m.amount), currencyCode: String(m.currencyCode || '') },
})
: t('audit_log.metadata.imported', {
args: { amount: String(m.amount), currencyCode: String(m.currencyCode || '') },
});
}
return t('audit_log.metadata.imported_plain');
},
PlaidTransactions: (m) => {
if (m.plaidAccountId) {
return m.batch
? t('audit_log.metadata.plaid_with_batch', {
args: { plaidAccountId: String(m.plaidAccountId), batch: String(m.batch) },
})
: t('audit_log.metadata.plaid', {
args: { plaidAccountId: String(m.plaidAccountId) },
});
}
return t('audit_log.metadata.plaid_plain');
},
BankTransaction: (m) => {
if (m.amount) {
return m.payee
? t('audit_log.metadata.bank_with_payee', {
args: { payee: String(m.payee), amount: String(m.amount), currencyCode: String(m.currencyCode || '') },
})
: t('audit_log.metadata.bank', {
args: { amount: String(m.amount), currencyCode: String(m.currencyCode || '') },
});
}
return t('audit_log.metadata.bank_plain');
},
};
const formatter = formatters[subject];
if (formatter) {
try {
return formatter(metadata);
} catch (e) {
// Fallback to default below
}
}
const entries = Object.entries(metadata).filter(
([, value]) => value !== null && value !== undefined && value !== '',
);
if (entries.length === 0) return '';
return entries
.slice(0, 3)
.map(([key, value]) => {
const displayKey = key
.replace(/([A-Z])/g, ' $1')
.replace(/^./, (str) => str.toUpperCase())
.trim();
return `${displayKey}: ${value}`;
})
.join(', ');
}
@@ -0,0 +1,79 @@
import { Inject, Injectable } from '@nestjs/common';
import * as moment from 'moment';
import { AuditLog } from '../models/AuditLog.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { GetAuditLogsQueryDto } from '../dtos/GetAuditLogsQuery.dto';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { GetAuditLogListTransformer } from './GetAuditLogList.transformer';
export interface AuditLogListItem {
id: number;
userId: number | null;
userName: string | null;
userEmail: string | null;
action: string;
subject: string;
subjectId: number | null;
metadata: Record<string, unknown> | null;
summary: string;
ip: string | null;
createdAt: string;
createdAtFormatted: string;
}
@Injectable()
export class GetAuditLogsService {
constructor(
@Inject(AuditLog.name)
private readonly auditLogModel: TenantModelProxy<typeof AuditLog>,
private readonly transformer: TransformerInjectable,
) {}
async getAuditLogs(query: GetAuditLogsQueryDto): Promise<{
data: AuditLogListItem[];
pagination: { total: number; page: number; pageSize: number };
}> {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const pageIndex = Math.max(0, page - 1);
let q = this.auditLogModel()
.query()
.withGraphFetched('tenantUser')
.orderBy('createdAt', 'desc');
if (query.subject?.length) {
q = q.whereIn('subject', query.subject);
}
if (query.action?.length) {
q = q.whereIn('action', query.action);
}
if (query.userId != null) {
q = q.where('userId', query.userId);
}
if (query.from) {
const from = moment(query.from).startOf('day').format('YYYY-MM-DD HH:mm:ss');
q = q.where('createdAt', '>=', from);
}
if (query.to) {
const to = moment(query.to).endOf('day').format('YYYY-MM-DD HH:mm:ss');
q = q.where('createdAt', '<=', to);
}
const result = await q.page(pageIndex, pageSize);
const data = (await this.transformer.transform(
result.results,
new GetAuditLogListTransformer(),
)) as AuditLogListItem[];
return {
data,
pagination: {
total: result.total,
page,
pageSize,
},
};
}
}
@@ -0,0 +1,807 @@
import { Injectable } from '@nestjs/common';
import { OnEvent } from '@nestjs/event-emitter';
import { Knex } from 'knex';
import { events } from '@/common/events/events';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { AuditLogService } from '../AuditLog.service';
import {
IBillCreatedPayload,
IBillEditedPayload,
IBIllEventDeletedPayload,
IBillOpenedPayload,
} from '@/modules/Bills/Bills.types';
import {
ISaleInvoiceCreatedPayload,
ISaleInvoiceEditedPayload,
ISaleInvoiceDeletedPayload,
ISaleInvoiceEventDeliveredPayload,
ISaleInvoiceWrittenOffCanceledPayload,
ISaleInvoiceWriteoffCreatePayload,
} from '@/modules/SaleInvoices/SaleInvoice.types';
import {
ISaleReceiptCreatedPayload,
ISaleReceiptEditedPayload,
ISaleReceiptEventDeletedPayload,
} from '@/modules/SaleReceipts/types/SaleReceipts.types';
import {
IPaymentReceivedCreatedPayload,
IPaymentReceivedEditedPayload,
IPaymentReceivedDeletedPayload,
} from '@/modules/PaymentReceived/types/PaymentReceived.types';
import {
IBillPaymentEventCreatedPayload,
IBillPaymentEventEditedPayload,
IBillPaymentEventDeletedPayload,
} from '@/modules/BillPayments/types/BillPayments.types';
import {
IExpenseCreatedPayload,
IExpenseEventEditPayload,
IExpenseEventDeletePayload,
IExpenseEventPublishedPayload,
} from '@/modules/Expenses/Expenses.types';
import {
ICreditNoteCreatedPayload,
ICreditNoteEditedPayload,
ICreditNoteDeletedPayload,
ICreditNoteOpenedPayload,
} from '@/modules/CreditNotes/types/CreditNotes.types';
import {
IVendorCreditCreatedPayload,
IVendorCreditEditedPayload,
IVendorCreditDeletedPayload,
IVendorCreditOpenedPayload,
} from '@/modules/VendorCredit/types/VendorCredit.types';
import {
IManualJournalEventCreatedPayload,
IManualJournalEventEditedPayload,
IManualJournalEventDeletedPayload,
IManualJournalEventPublishedPayload,
} from '@/modules/ManualJournals/types/ManualJournals.types';
import {
ICommandCashflowCreatedPayload,
ICommandCashflowDeletedPayload,
ICashflowTransactionCategorizedPayload,
} from '@/modules/BankingTransactions/types/BankingTransactions.types';
import {
IAccountEventCreatedPayload,
IAccountEventDeletedPayload,
IAccountEventActivatedPayload,
} from '@/interfaces/Account';
import {
IInventoryAdjustmentEventCreatedPayload,
IInventoryAdjustmentEventPublishedPayload,
IInventoryAdjustmentEventDeletedPayload,
} from '@/modules/InventoryAdjutments/types/InventoryAdjustments.types';
import {
IWarehouseTransferCreated,
IWarehouseTransferEditedPayload,
IWarehouseTransferDeletedPayload,
IWarehouseTransferInitiatedPayload,
IWarehouseTransferTransferredPayload,
} from '@/modules/Warehouses/Warehouse.types';
import {
ITransactionsLockingPartialUnlocked,
ITransactionsLockingCanceled,
} from '@/modules/TransactionsLocking/types/TransactionsLocking.types';
import {
ISaleEstimateCreatedPayload,
ISaleEstimateEditedPayload,
ISaleEstimateDeletedPayload,
} from '@/modules/SaleEstimates/types/SaleEstimates.types';
import { IRefundCreditNoteCreatedPayload } from '@/modules/CreditNoteRefunds/types/CreditNoteRefunds.types';
import { IRefundVendorCreditCreatedPayload } from '@/modules/VendorCreditsRefund/types/VendorCreditRefund.types';
@Injectable()
export class FinancialAuditLogSubscriber {
constructor(private readonly auditLog: AuditLogService) {}
private async write(
trx: Knex.Transaction | undefined,
action: string,
subject: string,
subjectId: number | null,
metadata: Record<string, unknown>,
) {
await this.auditLog.record({ trx, action, subject, subjectId, metadata });
}
// --- Bills ---
@OnEvent(events.bill.onCreated)
async onBillCreated({ bill, trx }: IBillCreatedPayload) {
await this.write(trx, 'created', AbilitySubject.Bill, bill.id, {
billNumber: bill.billNumber,
amount: bill.amount,
currencyCode: bill.currencyCode,
});
}
@OnEvent(events.bill.onEdited)
async onBillEdited({ bill, trx }: IBillEditedPayload) {
await this.write(trx, 'edited', AbilitySubject.Bill, bill.id, {
billNumber: bill.billNumber,
amount: bill.amount,
currencyCode: bill.currencyCode,
});
}
@OnEvent(events.bill.onDeleted)
async onBillDeleted({ billId, oldBill, trx }: IBIllEventDeletedPayload) {
await this.write(trx, 'deleted', AbilitySubject.Bill, billId, {
billNumber: oldBill.billNumber,
});
}
@OnEvent(events.bill.onOpened)
async onBillOpened({ bill, trx }: IBillOpenedPayload) {
await this.write(trx, 'opened', AbilitySubject.Bill, bill.id, {
billNumber: bill.billNumber,
});
}
// --- Sale invoices ---
@OnEvent(events.saleInvoice.onCreated)
async onSaleInvoiceCreated({
saleInvoice,
trx,
}: ISaleInvoiceCreatedPayload) {
await this.write(trx, 'created', AbilitySubject.SaleInvoice, saleInvoice.id, {
invoiceNumber: saleInvoice.invoiceNo,
balance: saleInvoice.balance,
currencyCode: saleInvoice.currencyCode,
});
}
@OnEvent(events.saleInvoice.onEdited)
async onSaleInvoiceEdited({ saleInvoice, trx }: ISaleInvoiceEditedPayload) {
await this.write(trx, 'edited', AbilitySubject.SaleInvoice, saleInvoice.id, {
invoiceNumber: saleInvoice.invoiceNo,
balance: saleInvoice.balance,
currencyCode: saleInvoice.currencyCode,
});
}
@OnEvent(events.saleInvoice.onDeleted)
async onSaleInvoiceDeleted({
saleInvoiceId,
oldSaleInvoice,
trx,
}: ISaleInvoiceDeletedPayload) {
await this.write(
trx,
'deleted',
AbilitySubject.SaleInvoice,
saleInvoiceId,
{ invoiceNumber: oldSaleInvoice.invoiceNo },
);
}
@OnEvent(events.saleInvoice.onDelivered)
async onSaleInvoiceDelivered({
saleInvoice,
trx,
}: ISaleInvoiceEventDeliveredPayload) {
await this.write(trx, 'delivered', AbilitySubject.SaleInvoice, saleInvoice.id, {
invoiceNumber: saleInvoice.invoiceNo,
});
}
@OnEvent(events.saleInvoice.onWrittenoff)
async onSaleInvoiceWrittenoff({
saleInvoice,
trx,
}: ISaleInvoiceWriteoffCreatePayload) {
await this.write(trx, 'writtenoff', AbilitySubject.SaleInvoice, saleInvoice.id, {
invoiceNumber: saleInvoice.invoiceNo,
});
}
@OnEvent(events.saleInvoice.onWrittenoffCanceled)
async onSaleInvoiceWrittenoffCanceled({
saleInvoice,
trx,
}: ISaleInvoiceWrittenOffCanceledPayload) {
await this.write(
trx,
'writtenoff_canceled',
AbilitySubject.SaleInvoice,
saleInvoice.id,
{ invoiceNumber: saleInvoice.invoiceNo },
);
}
// --- Sale receipts ---
@OnEvent(events.saleReceipt.onCreated)
async onSaleReceiptCreated({ saleReceipt, trx }: ISaleReceiptCreatedPayload) {
await this.write(trx, 'created', AbilitySubject.SaleReceipt, saleReceipt.id, {
receiptNumber: saleReceipt.receiptNumber,
amount: saleReceipt.total,
currencyCode: saleReceipt.currencyCode,
});
}
@OnEvent(events.saleReceipt.onEdited)
async onSaleReceiptEdited({ saleReceipt, trx }: ISaleReceiptEditedPayload) {
await this.write(trx, 'edited', AbilitySubject.SaleReceipt, saleReceipt.id, {
receiptNumber: saleReceipt.receiptNumber,
amount: saleReceipt.total,
currencyCode: saleReceipt.currencyCode,
});
}
@OnEvent(events.saleReceipt.onDeleted)
async onSaleReceiptDeleted({
saleReceiptId,
oldSaleReceipt,
trx,
}: ISaleReceiptEventDeletedPayload) {
await this.write(
trx,
'deleted',
AbilitySubject.SaleReceipt,
saleReceiptId,
{ receiptNumber: oldSaleReceipt.receiptNumber },
);
}
// --- Payments received ---
@OnEvent(events.paymentReceive.onCreated)
async onPaymentReceivedCreated({
paymentReceive,
trx,
}: IPaymentReceivedCreatedPayload) {
await this.write(
trx,
'created',
AbilitySubject.PaymentReceive,
paymentReceive.id,
{
paymentReceiveNo: paymentReceive.paymentReceiveNo,
amount: paymentReceive.amount,
currencyCode: paymentReceive.currencyCode,
},
);
}
@OnEvent(events.paymentReceive.onEdited)
async onPaymentReceivedEdited({
paymentReceive,
trx,
}: IPaymentReceivedEditedPayload) {
await this.write(
trx,
'edited',
AbilitySubject.PaymentReceive,
paymentReceive.id,
{
paymentReceiveNo: paymentReceive.paymentReceiveNo,
amount: paymentReceive.amount,
},
);
}
@OnEvent(events.paymentReceive.onDeleted)
async onPaymentReceivedDeleted({
paymentReceiveId,
oldPaymentReceive,
trx,
}: IPaymentReceivedDeletedPayload) {
await this.write(
trx,
'deleted',
AbilitySubject.PaymentReceive,
paymentReceiveId,
{ paymentReceiveNo: oldPaymentReceive.paymentReceiveNo },
);
}
// --- Bill payments (payments made) ---
@OnEvent(events.billPayment.onCreated)
async onBillPaymentCreated({
billPayment,
trx,
}: IBillPaymentEventCreatedPayload) {
await this.write(
trx,
'created',
AbilitySubject.PaymentMade,
billPayment.id,
{
paymentNumber: billPayment.paymentNumber,
amount: billPayment.amount,
currencyCode: billPayment.currencyCode,
},
);
}
@OnEvent(events.billPayment.onEdited)
async onBillPaymentEdited({
billPayment,
trx,
}: IBillPaymentEventEditedPayload) {
await this.write(
trx,
'edited',
AbilitySubject.PaymentMade,
billPayment.id,
{
paymentNumber: billPayment.paymentNumber,
amount: billPayment.amount,
},
);
}
@OnEvent(events.billPayment.onDeleted)
async onBillPaymentDeleted({
billPaymentId,
oldBillPayment,
trx,
}: IBillPaymentEventDeletedPayload) {
await this.write(
trx,
'deleted',
AbilitySubject.PaymentMade,
billPaymentId,
{ paymentNumber: oldBillPayment.paymentNumber },
);
}
// --- Expenses ---
@OnEvent(events.expenses.onCreated)
async onExpenseCreated({ expense, expenseId, trx }: IExpenseCreatedPayload) {
await this.write(trx, 'created', AbilitySubject.Expense, expenseId, {
amount: expense.totalAmount,
currencyCode: expense.currencyCode,
});
}
@OnEvent(events.expenses.onEdited)
async onExpenseEdited({ expense, expenseId, trx }: IExpenseEventEditPayload) {
await this.write(trx, 'edited', AbilitySubject.Expense, expenseId, {
amount: expense.totalAmount,
currencyCode: expense.currencyCode,
});
}
@OnEvent(events.expenses.onDeleted)
async onExpenseDeleted({ expenseId, oldExpense, trx }: IExpenseEventDeletePayload) {
await this.write(trx, 'deleted', AbilitySubject.Expense, expenseId, {
amount: oldExpense.totalAmount,
});
}
@OnEvent(events.expenses.onPublished)
async onExpensePublished({ expense, expenseId, trx }: IExpenseEventPublishedPayload) {
await this.write(trx, 'published', AbilitySubject.Expense, expenseId, {
amount: expense.totalAmount,
currencyCode: expense.currencyCode,
});
}
// --- Credit notes ---
@OnEvent(events.creditNote.onCreated)
async onCreditNoteCreated({ creditNote, trx }: ICreditNoteCreatedPayload) {
await this.write(trx, 'created', AbilitySubject.CreditNote, creditNote.id, {
creditNoteNumber: creditNote.creditNoteNumber,
amount: creditNote.total,
currencyCode: creditNote.currencyCode,
});
}
@OnEvent(events.creditNote.onEdited)
async onCreditNoteEdited({ creditNote, trx }: ICreditNoteEditedPayload) {
await this.write(trx, 'edited', AbilitySubject.CreditNote, creditNote.id, {
creditNoteNumber: creditNote.creditNoteNumber,
amount: creditNote.total,
});
}
@OnEvent(events.creditNote.onDeleted)
async onCreditNoteDeleted({
creditNoteId,
oldCreditNote,
trx,
}: ICreditNoteDeletedPayload) {
await this.write(
trx,
'deleted',
AbilitySubject.CreditNote,
creditNoteId,
{ creditNoteNumber: oldCreditNote.creditNoteNumber },
);
}
@OnEvent(events.creditNote.onOpened)
async onCreditNoteOpened({ creditNote, trx }: ICreditNoteOpenedPayload) {
await this.write(trx, 'opened', AbilitySubject.CreditNote, creditNote.id, {
creditNoteNumber: creditNote.creditNoteNumber,
});
}
@OnEvent(events.creditNote.onRefundCreated)
async onCreditNoteRefundCreated({
trx,
refundCreditNote,
creditNote,
}: IRefundCreditNoteCreatedPayload) {
await this.write(trx, 'refund_created', 'CreditNoteRefund', refundCreditNote.id, {
creditNoteId: creditNote.id,
amount: refundCreditNote.amount,
});
}
// --- Vendor credits ---
@OnEvent(events.vendorCredit.onCreated)
async onVendorCreditCreated({ vendorCredit, trx }: IVendorCreditCreatedPayload) {
await this.write(
trx,
'created',
AbilitySubject.VendorCredit,
vendorCredit.id,
{
vendorCreditNumber: vendorCredit.vendorCreditNumber,
total: vendorCredit.total,
currencyCode: vendorCredit.currencyCode,
},
);
}
@OnEvent(events.vendorCredit.onEdited)
async onVendorCreditEdited({ vendorCredit, trx }: IVendorCreditEditedPayload) {
await this.write(
trx,
'edited',
AbilitySubject.VendorCredit,
vendorCredit.id,
{
vendorCreditNumber: vendorCredit.vendorCreditNumber,
total: vendorCredit.total,
},
);
}
@OnEvent(events.vendorCredit.onDeleted)
async onVendorCreditDeleted({
vendorCreditId,
oldVendorCredit,
trx,
}: IVendorCreditDeletedPayload) {
await this.write(
trx,
'deleted',
AbilitySubject.VendorCredit,
vendorCreditId,
{ vendorCreditNumber: oldVendorCredit.vendorCreditNumber },
);
}
@OnEvent(events.vendorCredit.onOpened)
async onVendorCreditOpened({
vendorCredit,
vendorCreditId,
trx,
}: IVendorCreditOpenedPayload) {
await this.write(trx, 'opened', AbilitySubject.VendorCredit, vendorCreditId, {
vendorCreditNumber: vendorCredit.vendorCreditNumber,
});
}
@OnEvent(events.vendorCredit.onRefundCreated)
async onVendorCreditRefundCreated({
trx,
refundVendorCredit,
vendorCredit,
}: IRefundVendorCreditCreatedPayload) {
await this.write(
trx,
'refund_created',
'VendorCreditRefund',
refundVendorCredit.id,
{ vendorCreditId: vendorCredit.id, amount: refundVendorCredit.amount },
);
}
// --- Manual journals ---
@OnEvent(events.manualJournals.onCreated)
async onManualJournalCreated({
manualJournal,
trx,
}: IManualJournalEventCreatedPayload) {
await this.write(
trx,
'created',
AbilitySubject.ManualJournal,
manualJournal.id,
{
journalNumber: manualJournal.journalNumber,
amount: manualJournal.amount,
currencyCode: manualJournal.currencyCode,
},
);
}
@OnEvent(events.manualJournals.onEdited)
async onManualJournalEdited({
manualJournal,
trx,
}: IManualJournalEventEditedPayload) {
await this.write(
trx,
'edited',
AbilitySubject.ManualJournal,
manualJournal.id,
{
journalNumber: manualJournal.journalNumber,
amount: manualJournal.amount,
},
);
}
@OnEvent(events.manualJournals.onDeleted)
async onManualJournalDeleted({
manualJournalId,
trx,
}: IManualJournalEventDeletedPayload) {
await this.write(trx, 'deleted', AbilitySubject.ManualJournal, manualJournalId, {});
}
@OnEvent(events.manualJournals.onPublished)
async onManualJournalPublished({
manualJournal,
trx,
}: IManualJournalEventPublishedPayload) {
await this.write(
trx,
'published',
AbilitySubject.ManualJournal,
manualJournal.id,
{ journalNumber: manualJournal.journalNumber },
);
}
// --- Cashflow ---
@OnEvent(events.cashflow.onTransactionCreated)
async onCashflowCreated({
cashflowTransaction,
trx,
}: ICommandCashflowCreatedPayload) {
await this.write(trx, 'created', AbilitySubject.Cashflow, cashflowTransaction.id, {
amount: cashflowTransaction.amount,
currencyCode: cashflowTransaction.currencyCode,
});
}
@OnEvent(events.cashflow.onTransactionDeleted)
async onCashflowDeleted({
cashflowTransactionId,
trx,
}: ICommandCashflowDeletedPayload) {
await this.write(trx, 'deleted', AbilitySubject.Cashflow, cashflowTransactionId, {});
}
@OnEvent(events.cashflow.onTransactionCategorized)
async onCashflowCategorized({
cashflowTransaction,
trx,
}: ICashflowTransactionCategorizedPayload) {
await this.write(
trx,
'categorized',
AbilitySubject.Cashflow,
cashflowTransaction.id,
{ amount: cashflowTransaction.amount },
);
}
// --- GL accounts ---
@OnEvent(events.accounts.onCreated)
async onAccountCreated({ account, accountId, trx }: IAccountEventCreatedPayload) {
await this.write(trx, 'created', AbilitySubject.Account, accountId, {
name: account.name,
code: account.code,
});
}
@OnEvent(events.accounts.onDeleted)
async onAccountDeleted({ accountId, oldAccount, trx }: IAccountEventDeletedPayload) {
await this.write(trx, 'deleted', AbilitySubject.Account, accountId, {
name: oldAccount.name,
code: oldAccount.code,
});
}
@OnEvent(events.accounts.onActivated)
async onAccountActivated({ accountId, trx }: IAccountEventActivatedPayload) {
await this.write(trx, 'activated', AbilitySubject.Account, accountId, {});
}
// --- Inventory adjustments ---
@OnEvent(events.inventoryAdjustment.onQuickCreated)
async onInventoryAdjustmentCreated({
inventoryAdjustment,
inventoryAdjustmentId,
trx,
}: IInventoryAdjustmentEventCreatedPayload) {
await this.write(
trx,
'created',
AbilitySubject.InventoryAdjustment,
inventoryAdjustmentId,
{ reason: inventoryAdjustment.reason },
);
}
@OnEvent(events.inventoryAdjustment.onPublished)
async onInventoryAdjustmentPublished({
inventoryAdjustment,
inventoryAdjustmentId,
trx,
}: IInventoryAdjustmentEventPublishedPayload) {
await this.write(
trx,
'published',
AbilitySubject.InventoryAdjustment,
inventoryAdjustmentId,
{ reason: inventoryAdjustment.reason },
);
}
@OnEvent(events.inventoryAdjustment.onDeleted)
async onInventoryAdjustmentDeleted({
inventoryAdjustmentId,
trx,
}: IInventoryAdjustmentEventDeletedPayload) {
await this.write(
trx,
'deleted',
AbilitySubject.InventoryAdjustment,
inventoryAdjustmentId,
{},
);
}
// --- Warehouse transfers ---
@OnEvent(events.warehouseTransfer.onCreated)
async onWarehouseTransferCreated({
warehouseTransfer,
trx,
}: IWarehouseTransferCreated) {
await this.write(
trx,
'created',
'WarehouseTransfer',
warehouseTransfer.id,
{
transactionNumber: (warehouseTransfer as { transactionNumber?: string })
.transactionNumber,
},
);
}
@OnEvent(events.warehouseTransfer.onEdited)
async onWarehouseTransferEdited({
warehouseTransfer,
trx,
}: IWarehouseTransferEditedPayload) {
await this.write(
trx,
'edited',
'WarehouseTransfer',
warehouseTransfer.id,
{
transactionNumber: (warehouseTransfer as { transactionNumber?: string })
.transactionNumber,
},
);
}
@OnEvent(events.warehouseTransfer.onDeleted)
async onWarehouseTransferDeleted({
oldWarehouseTransfer,
trx,
}: IWarehouseTransferDeletedPayload) {
await this.write(
trx,
'deleted',
'WarehouseTransfer',
oldWarehouseTransfer.id,
{},
);
}
@OnEvent(events.warehouseTransfer.onInitiated)
async onWarehouseTransferInitiated({
warehouseTransfer,
trx,
}: IWarehouseTransferInitiatedPayload) {
await this.write(
trx,
'initiated',
'WarehouseTransfer',
warehouseTransfer.id,
{
transactionNumber: (warehouseTransfer as { transactionNumber?: string })
.transactionNumber,
},
);
}
@OnEvent(events.warehouseTransfer.onTransferred)
async onWarehouseTransferTransferred({
warehouseTransfer,
trx,
}: IWarehouseTransferTransferredPayload) {
await this.write(
trx,
'transferred',
'WarehouseTransfer',
warehouseTransfer.id,
{
transactionNumber: (warehouseTransfer as { transactionNumber?: string })
.transactionNumber,
},
);
}
// --- Transactions locking (settings change; no trx on payload) ---
@OnEvent(events.transactionsLocking.partialUnlocked)
async onTransactionsLockingChanged(
payload: ITransactionsLockingPartialUnlocked | ITransactionsLockingCanceled,
) {
const meta: Record<string, unknown> = { module: payload.module };
if ('transactionLockingDTO' in payload && payload.transactionLockingDTO) {
meta.lockToDate = (payload.transactionLockingDTO as { lockToDate?: Date })
.lockToDate;
}
if ('cancelLockingDTO' in payload && payload.cancelLockingDTO) {
meta.cancelReason = (payload.cancelLockingDTO as { reason?: string }).reason;
}
await this.write(undefined, 'locking_changed', 'TransactionsLocking', null, meta);
}
// --- Sale estimates ---
@OnEvent(events.saleEstimate.onCreated)
async onSaleEstimateCreated({
saleEstimate,
saleEstimateId,
trx,
}: ISaleEstimateCreatedPayload) {
await this.write(
trx,
'created',
AbilitySubject.SaleEstimate,
saleEstimate?.id ?? saleEstimateId,
{
estimateNumber: saleEstimate.estimateNumber,
total: saleEstimate.total,
currencyCode: saleEstimate.currencyCode,
},
);
}
@OnEvent(events.saleEstimate.onEdited)
async onSaleEstimateEdited({
saleEstimate,
estimateId,
trx,
}: ISaleEstimateEditedPayload) {
await this.write(trx, 'edited', AbilitySubject.SaleEstimate, estimateId, {
estimateNumber: saleEstimate.estimateNumber,
total: saleEstimate.total,
});
}
@OnEvent(events.saleEstimate.onDeleted)
async onSaleEstimateDeleted({
saleEstimateId,
oldSaleEstimate,
trx,
}: ISaleEstimateDeletedPayload) {
await this.write(
trx,
'deleted',
AbilitySubject.SaleEstimate,
saleEstimateId,
{ estimateNumber: oldSaleEstimate.estimateNumber },
);
}
}
@@ -0,0 +1,3 @@
export enum AuditLogAction {
View = 'View',
}
@@ -20,6 +20,7 @@ export class UncategorizedBankTransaction extends TenantBaseModel {
readonly pending: boolean;
readonly categorizeRefId!: number;
readonly categorizeRefType!: string;
readonly currencyCode!: string;
/**
* Table name.
@@ -47,6 +47,7 @@ export class ExcludeBankTransactionService {
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
await this.eventEmitter.emitAsync(events.bankTransactions.onExcluding, {
uncategorizedTransactionId,
uncategorizedTransaction: oldUncategorizedTransaction,
trx,
} as IBankTransactionUnexcludingEventPayload);
@@ -59,6 +60,7 @@ export class ExcludeBankTransactionService {
await this.eventEmitter.emitAsync(events.bankTransactions.onExcluded, {
uncategorizedTransactionId,
uncategorizedTransaction: oldUncategorizedTransaction,
trx,
} as IBankTransactionUnexcludedEventPayload);
});
@@ -50,6 +50,7 @@ export class UnexcludeBankTransactionService {
return this.uow.withTransaction(async (trx: Knex.Transaction) => {
await this.eventEmitter.emitAsync(events.bankTransactions.onUnexcluding, {
uncategorizedTransactionId,
uncategorizedTransaction: oldUncategorizedTransaction,
trx,
} as IBankTransactionUnexcludingEventPayload);
@@ -62,6 +63,7 @@ export class UnexcludeBankTransactionService {
await this.eventEmitter.emitAsync(events.bankTransactions.onUnexcluded, {
uncategorizedTransactionId,
uncategorizedTransaction: oldUncategorizedTransaction,
trx,
} as IBankTransactionUnexcludedEventPayload);
});
@@ -1,4 +1,5 @@
import { Knex } from "knex";
import { UncategorizedBankTransaction } from "@/modules/BankingTransactions/models/UncategorizedBankTransaction";
export interface ExcludedBankTransactionsQuery {
page?: number;
@@ -17,14 +18,17 @@ export interface IBankTransactionUnexcludingEventPayload {
export interface IBankTransactionUnexcludedEventPayload {
uncategorizedTransactionId: number;
uncategorizedTransaction?: UncategorizedBankTransaction;
trx?: Knex.Transaction
}
export interface IBankTransactionExcludingEventPayload {
uncategorizedTransactionId: number;
uncategorizedTransaction?: UncategorizedBankTransaction;
trx?: Knex.Transaction
}
export interface IBankTransactionExcludedEventPayload {
uncategorizedTransactionId: number;
uncategorizedTransaction?: UncategorizedBankTransaction;
trx?: Knex.Transaction
}
@@ -2,13 +2,17 @@ import { ServiceError } from '@/modules/Items/ServiceError';
import { Contact } from '../models/Contact';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { Inject, Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { ERRORS } from '../Contacts.constants';
import { events } from '@/common/events/events';
@Injectable()
export class ActivateContactService {
constructor(
@Inject(Contact.name)
private readonly contactModel: TenantModelProxy<typeof Contact>,
private readonly eventEmitter: EventEmitter2,
) {}
async activateContact(contactId: number) {
@@ -24,5 +28,11 @@ export class ActivateContactService {
.query()
.findById(contactId)
.update({ active: true });
// Triggers `onContactActivated` event.
await this.eventEmitter.emitAsync(events.contacts.onActivated, {
contactId,
contact,
});
}
}
@@ -3,12 +3,16 @@ import { ServiceError } from '@/modules/Items/ServiceError';
import { Contact } from '../models/Contact';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { ERRORS } from '../Contacts.constants';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { events } from '@/common/events/events';
@Injectable()
export class InactivateContactService {
constructor(
@Inject(Contact.name)
private readonly contactModel: TenantModelProxy<typeof Contact>,
private readonly eventEmitter: EventEmitter2,
) {}
async inactivateContact(contactId: number) {
@@ -24,5 +28,11 @@ export class InactivateContactService {
.query()
.findById(contactId)
.update({ active: false });
// Triggers `onContactInactivated` event.
await this.eventEmitter.emitAsync(events.contacts.onInactivated, {
contactId,
contact,
});
}
}
@@ -0,0 +1,71 @@
import { Inject, Injectable } from '@nestjs/common';
import { ClsService } from 'nestjs-cls';
import { Knex } from 'knex';
import * as moment from 'moment';
import '@/utils/moment-mysql';
import { AuditLog } from './models/AuditLog.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { TENANCY_DB_CONNECTION } from '@/modules/Tenancy/TenancyDB/TenancyDB.constants';
const METADATA_JSON_MAX = 8000;
export interface RecordAuditLogParams {
/** When set, the row is written in the same DB transaction as the business change. */
trx?: Knex.Transaction;
action: string;
subject: string;
subjectId?: number | null;
metadata?: Record<string, unknown> | null;
}
@Injectable()
export class AuditLogService {
constructor(
private readonly cls: ClsService,
@Inject(AuditLog.name)
private readonly auditLogModel: TenantModelProxy<typeof AuditLog>,
@Inject(TENANCY_DB_CONNECTION)
private readonly tenantKnex: () => Knex,
) {}
/**
* Persists one audit row. Prefer always passing `trx` from domain event payloads so the
* audit row rolls back with failed business transactions. If `trx` is omitted, the insert
* runs on a separate connection/transaction (only use after the business change committed).
*/
async record(params: RecordAuditLogParams): Promise<void> {
const userId = this.cls.get<number>('userId') ?? null;
const ip = (this.cls.get<string>('ip') as string) ?? null;
const executor = params.trx ?? this.tenantKnex();
const metadata = this.normalizeMetadata(params.metadata);
await this.auditLogModel()
.query(executor)
.insert({
userId,
action: params.action,
subject: params.subject,
subjectId: params.subjectId ?? null,
metadata,
ip,
// MySQL DATETIME expects `YYYY-MM-DD HH:mm:ss`, not ISO-8601 with `T`/`Z`.
createdAt: moment().toMySqlDateTime(),
});
}
private normalizeMetadata(
metadata: Record<string, unknown> | null | undefined,
): Record<string, unknown> | null {
if (metadata == null) return null;
try {
const s = JSON.stringify(metadata);
if (s.length <= METADATA_JSON_MAX) return metadata;
return {
_truncated: true,
summary: s.slice(0, METADATA_JSON_MAX),
};
} catch {
return { _error: 'metadata_not_serializable' };
}
}
}
@@ -0,0 +1,42 @@
import { Controller, Get, Query, UseGuards } from '@nestjs/common';
import { ApiOkResponse, ApiOperation, ApiTags } from '@nestjs/swagger';
import { ApiCommonHeaders } from '@/common/decorators/ApiCommonHeaders';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { RequirePermission } from '@/modules/Roles/RequirePermission.decorator';
import { AbilitySubject } from '@/modules/Roles/Roles.types';
import { GetAuditLogsQueryDto } from './dtos/GetAuditLogsQuery.dto';
import { GetAuditLogsResponseDto } from './dtos/GetAuditLogsResponse.dto';
import { GetAuditLogFilterOptionsResponseDto } from './dtos/GetAuditLogFilterOptionsResponse.dto';
import { GetAuditLogsService } from './queries/GetAuditLogs.service';
import { GetAuditLogFilterOptionsService } from './queries/GetAuditLogFilterOptions.service';
import { AuditLogAction } from './types/AuditLogs.types';
@Controller('audit-logs')
@ApiTags('Audit logs')
@ApiCommonHeaders()
@UseGuards(AuthorizationGuard, PermissionGuard)
export class AuditLogsController {
constructor(
private readonly getAuditLogsService: GetAuditLogsService,
private readonly getAuditLogFilterOptionsService: GetAuditLogFilterOptionsService,
) {}
@Get('filter-options')
@RequirePermission(AuditLogAction.View, AbilitySubject.AuditLog)
@ApiOperation({
summary: 'Distinct subject and action values for audit log filters.',
})
@ApiOkResponse({ type: GetAuditLogFilterOptionsResponseDto })
getAuditLogFilterOptions() {
return this.getAuditLogFilterOptionsService.getFilterOptions();
}
@Get()
@RequirePermission(AuditLogAction.View, AbilitySubject.AuditLog)
@ApiOperation({ summary: 'List financial audit log entries for the tenant.' })
@ApiOkResponse({ type: GetAuditLogsResponseDto })
getAuditLogs(@Query() query: GetAuditLogsQueryDto) {
return this.getAuditLogsService.getAuditLogs(query);
}
}
@@ -0,0 +1,28 @@
import { Module } from '@nestjs/common';
import { AuditLogsController } from './AuditLogs.controller';
import { AuditLogService } from './AuditLog.service';
import { GetAuditLogsService } from './queries/GetAuditLogs.service';
import { GetAuditLogFilterOptionsService } from './queries/GetAuditLogFilterOptions.service';
import { FinancialAuditLogSubscriber } from './subscribers/FinancialAuditLog.subscriber';
import { AuthorizationGuard } from '@/modules/Roles/Authorization.guard';
import { PermissionGuard } from '@/modules/Roles/Permission.guard';
import { RegisterTenancyModel } from '@/modules/Tenancy/TenancyModels/Tenancy.module';
import { AuditLog } from './models/AuditLog.model';
const models = [
RegisterTenancyModel(AuditLog)
];
@Module({
imports: [...models],
controllers: [AuditLogsController],
providers: [
AuditLogService,
GetAuditLogsService,
GetAuditLogFilterOptionsService,
FinancialAuditLogSubscriber,
AuthorizationGuard,
PermissionGuard,
],
exports: [AuditLogService, ...models],
})
export class AuditLogsModule {}
@@ -0,0 +1,17 @@
import { ApiProperty } from '@nestjs/swagger';
class AuditLogFilterOptionDto {
@ApiProperty({ example: 'SaleInvoice' })
key: string;
@ApiProperty({ example: 'Sale Invoice' })
label: string;
}
export class GetAuditLogFilterOptionsResponseDto {
@ApiProperty({ type: [AuditLogFilterOptionDto] })
subjects: AuditLogFilterOptionDto[];
@ApiProperty({ type: [AuditLogFilterOptionDto] })
actions: AuditLogFilterOptionDto[];
}
@@ -0,0 +1,75 @@
import { ApiPropertyOptional } from '@nestjs/swagger';
import { Transform } from 'class-transformer';
import {
ArrayMaxSize,
IsArray,
IsInt,
IsOptional,
IsString,
Max,
Min,
} from 'class-validator';
import { ToNumber } from '@/common/decorators/Validators';
function toOptionalStringArray(value: unknown): string[] | undefined {
if (value === undefined || value === null || value === '') {
return undefined;
}
const raw = Array.isArray(value) ? value : [value];
const filtered = raw
.map((v) => (v == null ? '' : String(v).trim()))
.filter((v) => v.length > 0);
if (!filtered.length) {
return undefined;
}
return [...new Set(filtered)];
}
export class GetAuditLogsQueryDto {
@ApiPropertyOptional({ minimum: 1, default: 1 })
@IsOptional()
@ToNumber()
@IsInt()
@Min(1)
page?: number;
@ApiPropertyOptional({ minimum: 1, maximum: 100, default: 20 })
@IsOptional()
@ToNumber()
@IsInt()
@Min(1)
@Max(100)
pageSize?: number;
@ApiPropertyOptional({ type: [String], isArray: true })
@Transform(({ value }) => toOptionalStringArray(value))
@IsOptional()
@IsArray()
@ArrayMaxSize(50)
@IsString({ each: true })
subject?: string[];
@ApiPropertyOptional({ type: [String], isArray: true })
@Transform(({ value }) => toOptionalStringArray(value))
@IsOptional()
@IsArray()
@ArrayMaxSize(50)
@IsString({ each: true })
action?: string[];
@ApiPropertyOptional({ description: 'System user id' })
@IsOptional()
@ToNumber()
@IsInt()
userId?: number;
@ApiPropertyOptional({ description: 'ISO date (inclusive), start of day' })
@IsOptional()
@IsString()
from?: string;
@ApiPropertyOptional({ description: 'ISO date (inclusive), end of day' })
@IsOptional()
@IsString()
to?: string;
}
@@ -0,0 +1,62 @@
import { ApiProperty } from '@nestjs/swagger';
class PaginationMetaDto {
@ApiProperty({ example: 100 })
total: number;
@ApiProperty({ example: 1 })
page: number;
@ApiProperty({ example: 20 })
pageSize: number;
}
export class AuditLogListItemDto {
@ApiProperty({ example: 1 })
id: number;
@ApiProperty({ example: 5, required: false, nullable: true })
userId: number | null;
@ApiProperty({ example: 'John Doe', required: false, nullable: true })
userName: string | null;
@ApiProperty({ example: 'john@example.com', required: false, nullable: true })
userEmail: string | null;
@ApiProperty({ example: 'created' })
action: string;
@ApiProperty({ example: 'sale_invoice' })
subject: string;
@ApiProperty({ example: 42, required: false, nullable: true })
subjectId: number | null;
@ApiProperty({
required: false,
nullable: true,
example: { invoiceNumber: 'INV-001' },
})
metadata: Record<string, unknown> | null;
@ApiProperty({ example: 'Invoice INV-001 was created for $500.00' })
summary: string;
@ApiProperty({ example: '192.168.1.1', required: false, nullable: true })
ip: string | null;
@ApiProperty({ example: '2025-04-12T18:30:00.000Z' })
createdAt: string;
@ApiProperty({ example: 'Apr 12, 2025 at 06:30 PM' })
createdAtFormatted: string;
}
export class GetAuditLogsResponseDto {
@ApiProperty({ type: [AuditLogListItemDto] })
data: AuditLogListItemDto[];
@ApiProperty({ type: PaginationMetaDto })
pagination: PaginationMetaDto;
}
@@ -0,0 +1,63 @@
import { Model } from 'objection';
import { TenantBaseModel } from '@/modules/System/models/TenantBaseModel';
import { TenantUser } from '@/modules/Tenancy/TenancyModels/models/TenantUser.model';
export class AuditLog extends TenantBaseModel {
public tenantUser?: TenantUser;
public id!: number;
/** System user id (matches CLS `userId` / `users.system_user_id` in tenant DB). */
public userId!: number | null;
public action!: string;
public subject!: string;
public subjectId!: number | null;
public metadata!: Record<string, unknown> | null;
public ip!: string | null;
public createdAt!: Date | string;
static get tableName() {
return 'audit_logs';
}
static get jsonAttributes() {
return ['metadata'];
}
/**
* No `updated_at`; `created_at` is set in AuditLogService.
*/
get timestamps() {
return [];
}
static get relationMappings() {
return {
tenantUser: {
relation: Model.BelongsToOneRelation,
modelClass: TenantUser,
join: {
from: 'audit_logs.userId',
to: 'users.systemUserId',
},
},
};
}
static get jsonSchema() {
return {
type: 'object',
required: ['action', 'subject'],
properties: {
id: { type: 'integer' },
userId: { type: ['integer', 'null'] },
action: { type: 'string', maxLength: 64 },
subject: { type: 'string', maxLength: 64 },
subjectId: { type: ['integer', 'null'] },
metadata: { type: ['object', 'null'] },
ip: { type: ['string', 'null'], maxLength: 64 },
// Stored as MySQL `YYYY-MM-DD HH:mm:ss` (see AuditLogService), not strict ISO-8601.
createdAt: { type: 'string' },
},
};
}
}
@@ -0,0 +1,48 @@
import { Inject, Injectable } from '@nestjs/common';
import { I18nService } from 'nestjs-i18n';
import { AuditLog } from '../models/AuditLog.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
export interface AuditLogFilterOption {
key: string;
label: string;
}
export interface AuditLogFilterOptions {
subjects: AuditLogFilterOption[];
actions: AuditLogFilterOption[];
}
@Injectable()
export class GetAuditLogFilterOptionsService {
constructor(
@Inject(AuditLog.name)
private readonly auditLogModel: TenantModelProxy<typeof AuditLog>,
private readonly i18n: I18nService,
) {}
async getFilterOptions(): Promise<AuditLogFilterOptions> {
const subjectRows = await this.auditLogModel()
.query()
.select('subject')
.groupBy('subject')
.orderBy('subject', 'asc');
const actionRows = await this.auditLogModel()
.query()
.select('action')
.groupBy('action')
.orderBy('action', 'asc');
return {
subjects: subjectRows
.map((r) => r.subject)
.filter(Boolean)
.map((key) => ({ key, label: this.i18n.t(`audit_log.subject.${key}`) })),
actions: actionRows
.map((r) => r.action)
.filter(Boolean)
.map((key) => ({ key, label: this.i18n.t(`audit_log.action.${key}`) })),
};
}
}
@@ -0,0 +1,79 @@
import { Inject, Injectable } from '@nestjs/common';
import * as moment from 'moment';
import { AuditLog } from '../models/AuditLog.model';
import { TenantModelProxy } from '@/modules/System/models/TenantBaseModel';
import { GetAuditLogsQueryDto } from '../dtos/GetAuditLogsQuery.dto';
import { TransformerInjectable } from '@/modules/Transformer/TransformerInjectable.service';
import { GetAuditLogListTransformer } from '@/modules/AuditLogs/queries/GetAuditLogList.transformer';
export interface AuditLogListItem {
id: number;
userId: number | null;
userName: string | null;
userEmail: string | null;
action: string;
subject: string;
subjectId: number | null;
metadata: Record<string, unknown> | null;
summary: string;
ip: string | null;
createdAt: string;
createdAtFormatted: string;
}
@Injectable()
export class GetAuditLogsService {
constructor(
@Inject(AuditLog.name)
private readonly auditLogModel: TenantModelProxy<typeof AuditLog>,
private readonly transformer: TransformerInjectable,
) {}
async getAuditLogs(query: GetAuditLogsQueryDto): Promise<{
data: AuditLogListItem[];
pagination: { total: number; page: number; pageSize: number };
}> {
const page = query.page ?? 1;
const pageSize = query.pageSize ?? 20;
const pageIndex = Math.max(0, page - 1);
let q = this.auditLogModel()
.query()
.withGraphFetched('tenantUser')
.orderBy('createdAt', 'desc');
if (query.subject?.length) {
q = q.whereIn('subject', query.subject);
}
if (query.action?.length) {
q = q.whereIn('action', query.action);
}
if (query.userId != null) {
q = q.where('userId', query.userId);
}
if (query.from) {
const from = moment(query.from).startOf('day').format('YYYY-MM-DD HH:mm:ss');
q = q.where('createdAt', '>=', from);
}
if (query.to) {
const to = moment(query.to).endOf('day').format('YYYY-MM-DD HH:mm:ss');
q = q.where('createdAt', '<=', to);
}
const result = await q.page(pageIndex, pageSize);
const data = (await this.transformer.transform(
result.results,
new GetAuditLogListTransformer(),
)) as AuditLogListItem[];
return {
data,
pagination: {
total: result.total,
page,
pageSize,
},
};
}
}
@@ -0,0 +1,3 @@
export enum AuditLogAction {
View = 'View',
}
@@ -0,0 +1,8 @@
import { Module } from '@nestjs/common';
import { AuditLogsModule } from './AuditLogs/AuditLogs.module';
@Module({
imports: [AuditLogsModule],
exports: [AuditLogsModule],
})
export class EEModule {}
@@ -4,6 +4,7 @@ import { Inject, Injectable } from '@nestjs/common';
import { Item } from './models/Item';
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
import { events } from '@/common/events/events';
import { IItemEventActivatedPayload } from '@/interfaces/Item';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@Injectable()
@@ -39,7 +40,11 @@ export class ActivateItemService {
.patch({ active: true });
// Triggers `onItemActivated` event.
await this.eventEmitter.emitAsync(events.item.onActivated, {});
await this.eventEmitter.emitAsync(events.item.onActivated, {
itemId,
item: oldItem,
trx,
} as IItemEventActivatedPayload);
}, trx);
}
}
@@ -3,6 +3,7 @@ import { Knex } from 'knex';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Item } from './models/Item';
import { events } from '@/common/events/events';
import { IItemEventInactivatedPayload } from '@/interfaces/Item';
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
import { TenantModelProxy } from '../System/models/TenantBaseModel';
@@ -38,7 +39,11 @@ export class InactivateItem {
.patch({ active: false });
// Triggers `onItemInactivated` event.
await this.eventEmitter.emitAsync(events.item.onInactivated, { trx });
await this.eventEmitter.emitAsync(events.item.onInactivated, {
itemId,
item: oldItem,
trx,
} as IItemEventInactivatedPayload);
}, trx);
}
}
@@ -16,6 +16,7 @@ import { BillAction } from "../Bills/Bills.types";
import { AbilitySubject, ISubjectAbilitiesSchema, ISubjectAbilitySchema } from "./Roles.types";
import { PaymentReceiveAction } from "../PaymentReceived/types/PaymentReceived.types";
import { PreferencesAction } from "../Settings/Settings.types";
import { AuditLogAction } from "../EE/AuditLogs/types/AuditLogs.types";
import { AttachmentAction } from "../Attachments/Attachments.types";
export const AbilitySchema: ISubjectAbilitiesSchema[] = [
@@ -306,6 +307,13 @@ export const AbilitySchema: ISubjectAbilitiesSchema[] = [
},
],
},
{
subject: AbilitySubject.AuditLog,
subjectLabel: 'ability.audit_log',
abilities: [
{ key: AuditLogAction.View, label: 'ability.view' },
],
},
{
subject: AbilitySubject.Attachment,
subjectLabel: 'ability.attachments',
@@ -6,30 +6,24 @@ import {
} from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { Request } from 'express';
import { REQUIRED_PERMISSION_KEY, RequiredPermission } from './RequirePermission.decorator';
import { AbilitySubject } from './Roles.types';
import {
REQUIRED_PERMISSION_KEY,
RequiredPermission,
} from './RequirePermission.decorator';
/**
* Guard that checks if the user has the required permission to access a route.
* Uses CASL ability instance attached to the request by AuthorizationGuard.
* Guard that checks CASL `ability` on the request (attached by AuthorizationGuard).
*/
@Injectable()
export class PermissionGuard implements CanActivate {
constructor(private readonly reflector: Reflector) {}
/**
* Checks if the user has the required permission to access the route.
* @param context - The execution context
* @returns A boolean indicating if the user can access the route
* @throws ForbiddenException if the user doesn't have the required permission
*/
canActivate(context: ExecutionContext): boolean {
const requiredPermission = this.reflector.getAllAndOverride<RequiredPermission>(
REQUIRED_PERMISSION_KEY,
[context.getHandler(), context.getClass()],
);
// If no permission is required, allow access
if (!requiredPermission) {
return true;
}
@@ -37,17 +31,15 @@ export class PermissionGuard implements CanActivate {
const request = context.switchToHttp().getRequest<Request>();
const ability = (request as any).ability;
// If no ability instance is attached to the request, deny access
if (!ability) {
throw new ForbiddenException('Ability instance not found. Ensure AuthorizationGuard is applied.');
throw new ForbiddenException(
'Ability instance not found. Ensure AuthorizationGuard is applied.',
);
}
const { ability: action, subject } = requiredPermission;
// Check if the user has the required permission using CASL ability
const hasPermission = ability.can(action, subject);
if (!hasPermission) {
if (!ability.can(action, subject)) {
throw new ForbiddenException(
`You do not have permission to ${action} ${subject}`,
);
@@ -55,13 +47,4 @@ export class PermissionGuard implements CanActivate {
return true;
}
/**
* Helper method to check if a subject value is a valid AbilitySubject.
* @param subject - The subject value to check
* @returns True if the subject is a valid AbilitySubject enum value
*/
private isValidSubject(subject: string): subject is AbilitySubject {
return Object.values(AbilitySubject).includes(subject as AbilitySubject);
}
}
@@ -61,6 +61,10 @@ export enum AbilitySubject {
VendorCredit = 'VendorCredit',
Project = 'Project',
TaxRate = 'TaxRate',
AuditLog = 'AuditLog',
Role = 'Role',
Warehouse = 'Warehouse',
Branch = 'Branch',
Attachment = 'Attachment',
}
@@ -44,6 +44,7 @@ export function FinancialSheet({
() => getBasisLabel(basis),
[getBasisLabel, basis],
);
const hasHead = companyName || sheetType || dateText;
return (
<FinancialSheetRoot
@@ -51,10 +52,13 @@ export function FinancialSheet({
fullWidth={fullWidth}
className={className}
>
{hasHead && (
<div>
{companyName && <FinancialSheetTitle>{companyName}</FinancialSheetTitle>}
{sheetType && <FinancialSheetType>{sheetType}</FinancialSheetType>}
{dateText && <FinancialSheetDate>{dateText}</FinancialSheetDate>}
</div>
)}
<FinancialSheetTable>{children}</FinancialSheetTable>
<FinancialSheetAccountingBasis>
@@ -12,6 +12,7 @@ export const FinancialSheetRoot = styled.div`
min-height: 400px;
display: flex;
flex-direction: column;
gap: 24px;
${(props) =>
props.fullWidth &&
@@ -73,9 +74,7 @@ export const FinancialSheetFooter = styled.div`
padding-left: 10px;
}
`;
export const FinancialSheetTable = styled.div`
margin-top: 24px;
`;
export const FinancialSheetTable = styled.div``;
export const FinancialSheetFooterBasis = styled.span``;
export const FinancialSheetFooterCurrentTime = styled.span``;
@@ -23,6 +23,7 @@ export const AbilitySubject = {
Project: 'Project',
TaxRate: 'TaxRate',
BankRule: 'BankRule',
AuditLog: 'AuditLog',
};
export const ItemAction = {
@@ -202,3 +203,7 @@ export const BankRuleAction = {
Edit: 'Edit',
Delete: 'Delete',
};
export const AuditLogAction = {
View: 'View',
};
@@ -1,7 +1,7 @@
// @ts-nocheck
import React from 'react';
import { FormattedMessage as T } from '@/components';
import { ReportsAction, AbilitySubject } from '@/constants/abilityOption';
import { ReportsAction, AbilitySubject, AuditLogAction } from '@/constants/abilityOption';
export const financialReportMenus = [
{
@@ -194,4 +194,16 @@ export const financialReportMenus = [
},
],
},
{
sectionTitle: <T id={'system_reports'} />,
reports: [
{
title: <T id={'audit_log_report'} />,
desc: <T id={'audit_log_report_desc'} />,
link: '/financial-reports/audit-log',
subject: AbilitySubject.AuditLog,
ability: AuditLogAction.View,
},
],
},
];
@@ -0,0 +1,47 @@
// @ts-nocheck
import React from 'react';
import { Button, Classes, NavbarGroup, NavbarDivider } from '@blueprintjs/core';
import classNames from 'classnames';
import { DashboardActionsBar, Icon } from '@/components';
import { useAuditLogContext } from './AuditLogProvider';
/**
* Audit Log Actions Bar
*/
function AuditLogActionsBar({
isFilterDrawerOpen,
toggleFilterDrawer,
}) {
const { sheetRefresh } = useAuditLogContext();
const handleCustomizeClick = () => {
toggleFilterDrawer();
};
const handleRecalcReport = () => {
sheetRefresh();
};
return (
<DashboardActionsBar>
<NavbarGroup>
<Button
className={classNames(Classes.MINIMAL)}
text={"Reload"}
onClick={handleRecalcReport}
icon={<Icon icon="refresh-16" iconSize={16} />}
/>
<NavbarDivider />
<Button
className={classNames(Classes.MINIMAL)}
icon={<Icon icon="cog-16" iconSize={16} />}
text={"Filter"}
onClick={handleCustomizeClick}
active={isFilterDrawerOpen}
/>
</NavbarGroup>
</DashboardActionsBar>
);
}
export default AuditLogActionsBar;
@@ -0,0 +1,27 @@
// @ts-nocheck
import React from 'react';
import { Spinner } from '@blueprintjs/core';
import { FinancialReportBody } from '../FinancialReportPage';
import { useAuditLogContext } from './AuditLogProvider';
import AuditLogTable from './AuditLogTable';
/**
* Audit Log Body
*/
function AuditLogBody() {
const { isLoading } = useAuditLogContext();
return (
<FinancialReportBody>
{isLoading ? (
<div style={{ padding: 20 }}>
<Spinner size={24} />
</div>
) : (
<AuditLogTable />
)}
</FinancialReportBody>
);
}
export { AuditLogBody };
@@ -0,0 +1,218 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import intl from 'react-intl-universal';
import moment from 'moment';
import { Button, Tabs, Tab, DrawerSize, Position } from '@blueprintjs/core';
import styled from 'styled-components';
import { Formik, Form } from 'formik';
import {
FormattedMessage as T,
FFormGroup,
FDateInput,
} from '@/components';
import { FMultiSelect } from '@/components/Forms';
import { useAuditLogFilterOptionsQuery } from '@/hooks/query';
import { saveInvoke, transformToForm } from '@/utils';
import FinancialStatementHeader from '../FinancialStatementHeader';
import { getDefaultAuditLogQuery, getAuditLogQuerySchema } from './common';
function normalizeStringListField(value) {
return Array.isArray(value) ? value : value ? [value] : [];
}
const auditLogSelectItemPredicate = (query, item) => {
const q = (query || '').toLowerCase();
const name = (item?.name ?? '').toLowerCase();
return name.includes(q);
};
const AuditLogDrawerHeader = styled(FinancialStatementHeader)`
.bp4-drawer {
max-height: 350px;
}
`;
/**
* Audit Log Header - Filter drawer
*/
function AuditLogHeader({ onSubmitFilter, pageFilter, isFilterDrawerOpen, toggleFilterDrawer }) {
const { data: filterOptions, isLoading: isFilterOptionsLoading } =
useAuditLogFilterOptionsQuery({
enabled: isFilterDrawerOpen,
});
const subjectSelectItems = useMemo(() => {
const byValue = new Map();
for (const s of filterOptions.subjects ?? []) {
byValue.set(s.key, { value: s.key, name: s.label });
}
for (const s of normalizeStringListField(pageFilter.subject)) {
if (s && !byValue.has(s)) {
byValue.set(s, { value: s, name: s });
}
}
return Array.from(byValue.values()).sort((a, b) =>
a.name.localeCompare(b.name),
);
}, [filterOptions.subjects, pageFilter.subject]);
const actionSelectItems = useMemo(() => {
const byValue = new Map();
for (const a of filterOptions.actions ?? []) {
byValue.set(a.key, { value: a.key, name: a.label });
}
for (const act of normalizeStringListField(pageFilter.action)) {
if (act && !byValue.has(act)) {
byValue.set(act, { value: act, name: act });
}
}
return Array.from(byValue.values()).sort((a, b) =>
a.name.localeCompare(b.name),
);
}, [filterOptions.actions, pageFilter.action]);
const defaultValues = getDefaultAuditLogQuery();
const initialValues = transformToForm(
{
...defaultValues,
...pageFilter,
fromDate: pageFilter.fromDate ? moment(pageFilter.fromDate).toDate() : '',
toDate: pageFilter.toDate ? moment(pageFilter.toDate).toDate() : '',
},
defaultValues
);
const validationSchema = getAuditLogQuerySchema();
const handleSubmit = (values, { setSubmitting }) => {
const parsedFilter = {
...values,
subject: normalizeStringListField(values.subject),
action: normalizeStringListField(values.action),
fromDate: values.fromDate ? moment(values.fromDate).format('YYYY-MM-DD') : '',
toDate: values.toDate ? moment(values.toDate).format('YYYY-MM-DD') : '',
};
saveInvoke(onSubmitFilter, parsedFilter);
toggleFilterDrawer(false);
setSubmitting(false);
};
const handleCancelClick = () => {
toggleFilterDrawer(false);
};
const handleDrawerClose = () => {
toggleFilterDrawer(false);
};
return (
<AuditLogDrawerHeader
isOpen={isFilterDrawerOpen}
drawerProps={{ onClose: handleDrawerClose }}
>
<Formik
validationSchema={validationSchema}
initialValues={initialValues}
onSubmit={handleSubmit}
>
<Form>
<Tabs animate={true} vertical={true} renderActiveTabPanelOnly={true}>
<Tab
id="general"
title={<T id={'general'} />}
panel={
<div style={{ maxWidth: '400px' }}>
<FFormGroup
name="subject"
label={intl.get('audit_log.filter_subject')}
fastField
>
<FMultiSelect
name="subject"
items={subjectSelectItems}
valueAccessor="value"
textAccessor="name"
tagAccessor="name"
itemPredicate={auditLogSelectItemPredicate}
placeholder={intl.get('all')}
popoverProps={{ minimal: true }}
disabled={isFilterOptionsLoading}
fill
resetOnSelect
fastField
/>
</FFormGroup>
<FFormGroup
name="action"
label={intl.get('audit_log.filter_action')}
fastField
>
<FMultiSelect
name="action"
items={actionSelectItems}
valueAccessor="value"
textAccessor="name"
tagAccessor="name"
itemPredicate={auditLogSelectItemPredicate}
placeholder={intl.get('all')}
popoverProps={{ minimal: true }}
disabled={isFilterOptionsLoading}
fill
resetOnSelect
fastField
/>
</FFormGroup>
<FFormGroup
name="fromDate"
label={intl.get('audit_log.filter_from')}
fastField
>
<FDateInput
name="fromDate"
popoverProps={{ position: Position.BOTTOM, minimal: true }}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
inputProps={{ fill: true }}
fastField
/>
</FFormGroup>
<FFormGroup
name="toDate"
label={intl.get('audit_log.filter_to')}
fill
fastField
>
<FDateInput
name="toDate"
type="date"
popoverProps={{ position: Position.BOTTOM, minimal: true }}
formatDate={(date) => date.toLocaleDateString()}
parseDate={(str) => new Date(str)}
inputProps={{ fill: true }}
fastField
/>
</FFormGroup>
</div>
}
/>
</Tabs>
<div className="financial-header-drawer__footer">
<Button className={'mr1'} intent="primary" type="submit">
<T id={'calculate_report'} />
</Button>
<Button onClick={handleCancelClick} minimal={true}>
<T id={'cancel'} />
</Button>
</div>
</Form>
</Formik>
</AuditLogDrawerHeader>
);
}
export default AuditLogHeader;
@@ -0,0 +1,82 @@
// @ts-nocheck
import React, { createContext, useCallback, useContext, useMemo } from 'react';
import { flatten, map } from 'lodash';
import { useAuditLogsInfinityQuery } from '@/hooks/query';
import { IntersectionObserver } from '@/components';
function flattenInfinityPagesData(data) {
return flatten(map(data.pages, (page) => page.data));
}
// Context for Audit Log
const AuditLogContext = React.createContext();
const useAuditLogContext = () => useContext(AuditLogContext);
/**
* Audit Log Provider
*/
function toHttpStringList(value) {
if (value == null || value === '') return undefined;
if (Array.isArray(value)) return value.length ? value : undefined;
return [value];
}
function AuditLogProvider({ query, children }) {
const httpQuery = useMemo(() => {
return {
pageSize: 20,
subject: toHttpStringList(query.subject),
action: toHttpStringList(query.action),
from: query.fromDate || undefined,
to: query.toDate || undefined,
};
}, [query]);
const {
data: auditLogsPages,
isLoading,
isFetching,
isFetchingNextPage,
fetchNextPage,
hasNextPage,
refetch,
} = useAuditLogsInfinityQuery(httpQuery);
const auditLogs = useMemo(
() =>
auditLogsPages
? flattenInfinityPagesData(auditLogsPages)
: [],
[auditLogsPages],
);
const handleObserverInteract = useCallback(() => {
if (!isFetching && hasNextPage) {
fetchNextPage();
}
}, [isFetching, hasNextPage, fetchNextPage]);
const provider = {
auditLogs,
isLoading,
isFetching,
isFetchingNextPage,
hasNextPage,
fetchNextPage,
handleObserverInteract,
sheetRefresh: refetch,
httpQuery,
};
return (
<AuditLogContext.Provider value={provider}>
{children}
<IntersectionObserver
onIntersect={handleObserverInteract}
/>
</AuditLogContext.Provider>
);
}
export { AuditLogProvider, useAuditLogContext };
@@ -0,0 +1,89 @@
// @ts-nocheck
import React, { useCallback, useEffect, useState } from 'react';
import intl from 'react-intl-universal';
import { NonIdealState } from '@blueprintjs/core';
import {
Card,
Can,
DashboardPageContent,
FinancialStatement,
} from '@/components';
import { AbilitySubject, AuditLogAction } from '@/constants/abilityOption';
import { AuditLogProvider } from './AuditLogProvider';
import AuditLogHeader from './AuditLogHeader';
import AuditLogActionsBar from './AuditLogActionsBar';
import { AuditLogLoadingBar } from './components';
import { AuditLogBody } from './AuditLogBody';
import { useAuditLogQuery } from './common';
/**
* Audit Log Report Content
*/
function AuditLogReportContent() {
const { query, setLocationQuery } = useAuditLogQuery();
const [isFilterDrawerOpen, setIsFilterDrawerOpen] = useState(false);
const handleFilterSubmit = useCallback(
(filter) => {
setLocationQuery(filter);
},
[setLocationQuery]
);
const toggleFilterDrawer = useCallback((toggle) => {
setIsFilterDrawerOpen((prev) =>
typeof toggle !== 'undefined' ? toggle : !prev
);
}, []);
// Hide filter drawer on unmount
useEffect(() => {
return () => setIsFilterDrawerOpen(false);
}, []);
return (
<AuditLogProvider query={query}>
<AuditLogActionsBar
isFilterDrawerOpen={isFilterDrawerOpen}
toggleFilterDrawer={toggleFilterDrawer}
/>
<DashboardPageContent>
<FinancialStatement>
<AuditLogHeader
pageFilter={query}
onSubmitFilter={handleFilterSubmit}
isFilterDrawerOpen={isFilterDrawerOpen}
toggleFilterDrawer={toggleFilterDrawer}
/>
<AuditLogLoadingBar />
<AuditLogBody />
</FinancialStatement>
</DashboardPageContent>
</AuditLogProvider>
);
}
/**
* Audit Log Report page (in Financial Reports section).
*/
function AuditLogReport() {
return (
<>
<Can I={AuditLogAction.View} a={AbilitySubject.AuditLog}>
<AuditLogReportContent />
</Can>
<Can not I={AuditLogAction.View} a={AbilitySubject.AuditLog}>
<DashboardPageContent>
<Card style={{ padding: 20 }}>
<NonIdealState title={intl.get('audit_log.no_access')} />
</Card>
</DashboardPageContent>
</Can>
</>
);
}
export default AuditLogReport;
@@ -0,0 +1,129 @@
// @ts-nocheck
import React, { useMemo } from 'react';
import intl from 'react-intl-universal';
import { Spinner } from '@blueprintjs/core';
import styled from 'styled-components';
import {
FinancialSheet,
ReportDataTable,
TableFastCell,
TableVirtualizedListRows,
IntersectionObserver,
} from '@/components';
import { TableStyle } from '@/constants';
import { useAuditLogContext } from './AuditLogProvider';
// Dynamic columns for audit log
const useAuditLogTableColumns = () => {
return useMemo(
() => [
{
Header: intl.get('audit_log.col_time'),
accessor: 'created_at_formatted',
width: 180,
textOverview: true,
},
{
Header: intl.get('audit_log.col_user'),
accessor: 'user_name',
width: 150,
textOverview: true,
},
{
Header: intl.get('audit_log.col_action'),
accessor: 'action',
width: 100,
textOverview: true,
},
{
Header: intl.get('audit_log.col_subject'),
accessor: 'subject',
width: 120,
textOverview: true,
},
{
Header: intl.get('audit_log.col_summary'),
accessor: 'summary',
width: 350,
textOverview: true,
Cell: ({ value }) => (
<div
style={{
maxWidth: 330,
overflow: 'hidden',
textOverflow: 'ellipsis',
whiteSpace: 'nowrap',
}}
title={value || ''}
>
{value || ''}
</div>
),
},
{
Header: intl.get('audit_log.col_ip'),
accessor: 'ip',
width: 120,
textOverview: true,
Cell: ({ value }) => value || '—',
},
],
[]
);
};
const AuditLogDataTable = styled(ReportDataTable)`
--color-table-text-color: #252a31;
--color-table-border-color: #ececec;
.bp4-dark & {
--color-table-text-color: var(--color-light-gray1);
--color-table-border-color: var(--color-dark-gray4);
}
.tbody {
.tr .td {
padding-top: 0.2rem;
padding-bottom: 0.2rem;
}
.tr:not(.no-results) .td:not(:first-of-type) {
border-left: 1px solid var(--color-table-border-color);
}
.tr:last-child .td {
border-bottom: 1px solid var(--color-table-border-color);
}
}
`;
/**
* Audit Log Table
*/
function AuditLogTable() {
const { auditLogs, isLoading, isFetchingNextPage, handleObserverInteract } = useAuditLogContext();
const columns = useAuditLogTableColumns();
return (
<FinancialSheet
loading={isLoading}
fullWidth={true}
currentDate={false}
>
<AuditLogDataTable
noResults={intl.get('audit_log.empty')}
columns={columns}
data={auditLogs}
virtualizedRows={true}
fixedItemSize={30}
fixedSizeHeight={1000}
sticky={true}
TableRowsRenderer={TableVirtualizedListRows}
vListrowHeight={28}
vListOverscanRowCount={2}
TableCellRenderer={TableFastCell}
styleName={TableStyle.Constrant}
/>
</FinancialSheet>
);
}
export default AuditLogTable;
@@ -0,0 +1,41 @@
// @ts-nocheck
import React, { useMemo, useState } from 'react';
import * as Yup from 'yup';
import { transformToForm } from '@/utils';
// Default query for audit log
export const getDefaultAuditLogQuery = () => ({
subject: [],
action: [],
fromDate: '',
toDate: '',
});
// Validation schema
export const getAuditLogQuerySchema = () => {
return Yup.object().shape({
fromDate: Yup.date().optional(),
toDate: Yup.date().min(Yup.ref('fromDate')).optional(),
});
};
// Parse query from URL
const parseAuditLogQuery = (locationQuery) => {
const defaultQuery = getDefaultAuditLogQuery();
return {
...defaultQuery,
...transformToForm(locationQuery, defaultQuery),
};
};
// Hook for managing query state
export const useAuditLogQuery = () => {
const [locationQuery, setLocationQuery] = useState({});
const query = useMemo(
() => parseAuditLogQuery(locationQuery),
[locationQuery]
);
return { query, setLocationQuery };
};
@@ -0,0 +1,18 @@
// @ts-nocheck
import React from 'react';
import { useAuditLogContext } from './AuditLogProvider';
import FinancialLoadingBar from '../FinancialLoadingBar';
/**
* Audit Log Loading Bar
*/
export function AuditLogLoadingBar() {
const { isFetching, isFetchingNextPage } = useAuditLogContext();
if (!isFetching || isFetchingNextPage) return null;
return (
<div className={'financial-progressbar'}>
<FinancialLoadingBar />
</div>
);
}
@@ -0,0 +1,103 @@
// @ts-nocheck
import * as qs from 'qs';
import { useInfiniteQuery } from 'react-query';
import { useRequestQuery } from '../useQueryRequest';
import useApiRequest from '../useRequest';
import { normalizeApiPath } from '@/utils';
import t from './types';
const qsArrayOptions = { skipNulls: true, arrayFormat: 'repeat' as const };
/** Normalize subject/action to a non-empty string[] or omit from query. */
function auditLogStringListParam(value) {
if (value == null || value === '') return undefined;
if (Array.isArray(value)) return value.length ? value : undefined;
return [value];
}
/**
* Paginated audit log list (financial domain events).
*/
export function useAuditLogsQuery(filters, props) {
const query = qs.stringify(
{
page: filters.page ?? 1,
pageSize: filters.pageSize ?? 20,
subject: auditLogStringListParam(filters.subject),
action: auditLogStringListParam(filters.action),
userId: filters.userId || undefined,
from: filters.from || undefined,
to: filters.to || undefined,
},
qsArrayOptions,
);
return useRequestQuery(
[t.AUDIT_LOGS, filters],
{ method: 'get', url: `audit-logs?${query}` },
{
select: (res) => res.data,
keepPreviousData: true,
...props,
},
);
}
/**
* Distinct subject/action values for audit log filter dropdowns.
*/
export function useAuditLogFilterOptionsQuery(props) {
return useRequestQuery(
[t.AUDIT_LOG_FILTER_OPTIONS],
{ method: 'get', url: 'audit-logs/filter-options' },
{
defaultData: { subjects: [], actions: [] },
select: (res) => ({
subjects: res.data?.subjects ?? [],
actions: res.data?.actions ?? [],
}),
staleTime: 5 * 60 * 1000,
...props,
},
);
}
/**
* Infinite audit log list with page-based pagination.
*/
export function useAuditLogsInfinityQuery(filters, infinityProps) {
const apiRequest = useApiRequest();
return useInfiniteQuery(
[t.AUDIT_LOGS, filters],
async ({ pageParam = 1 }) => {
const query = qs.stringify(
{
page: pageParam,
pageSize: filters.pageSize ?? 20,
subject: auditLogStringListParam(filters.subject),
action: auditLogStringListParam(filters.action),
userId: filters.userId || undefined,
from: filters.from || undefined,
to: filters.to || undefined,
},
qsArrayOptions,
);
const response = await apiRequest.http({
method: 'get',
url: `/api/${normalizeApiPath(`audit-logs?${query}`)}`,
});
return response.data;
},
{
getNextPageParam: (lastPage) => {
const { pagination } = lastPage;
return pagination.total > pagination.page_size * pagination.page
? pagination.page + 1
: undefined;
},
...infinityProps,
},
);
}
@@ -39,3 +39,4 @@ export * from './warehousesTransfers';
export * from './plaid';
export * from './FinancialReports';
export * from './apiKeys';
export * from './auditLogs';
+2 -2
View File
@@ -124,7 +124,7 @@ export function useActivateItem(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation((id) => apiRequest.post(`items/${id}/activate`), {
return useMutation((id) => apiRequest.patch(`items/${id}/activate`), {
onSuccess: (res, id) => {
// Invalidate specific item.
queryClient.invalidateQueries([t.ITEM, id]);
@@ -143,7 +143,7 @@ export function useInactivateItem(props) {
const queryClient = useQueryClient();
const apiRequest = useApiRequest();
return useMutation((id) => apiRequest.post(`items/${id}/inactivate`), {
return useMutation((id) => apiRequest.patch(`items/${id}/inactivate`), {
onSuccess: (res, id) => {
// Invalidate specific item.
queryClient.invalidateQueries([t.ITEM, id]);
+10
View File
@@ -245,6 +245,14 @@ export const API_KEYS = {
API_KEYS: 'API_KEYS',
};
const AUDIT_LOGS = {
AUDIT_LOGS: 'AUDIT_LOGS',
};
const AUDIT_LOG_FILTER_OPTIONS = {
AUDIT_LOG_FILTER_OPTIONS: 'AUDIT_LOG_FILTER_OPTIONS',
};
export default {
...Authentication,
...ACCOUNTS,
@@ -281,4 +289,6 @@ export default {
...TAX_RATES,
...EXCHANGE_RATE,
...API_KEYS,
...AUDIT_LOGS,
...AUDIT_LOG_FILTER_OPTIONS,
};
+20
View File
@@ -242,6 +242,26 @@
"new_expenses": "New Expenses",
"preferences": "Preferences",
"auditing_system": "Auditing System",
"audit_log.no_access": "You do not have permission to view the audit log.",
"audit_log.empty": "No audit entries yet.",
"audit_log.filter_subject": "Subject",
"audit_log.filter_action": "Action",
"audit_log.filter_from": "From",
"audit_log.filter_to": "To",
"audit_log.apply_filters": "Apply",
"audit_log.col_time": "Time",
"audit_log.col_user": "User",
"audit_log.col_action": "Action",
"audit_log.col_subject": "Subject",
"audit_log.col_id": "ID",
"audit_log.col_summary": "Summary",
"audit_log.col_ip": "IP",
"audit_log.pagination": "Page {page} of {pages} ({total} total)",
"audit_log.prev": "Previous",
"audit_log.next": "Next",
"audit_log_report": "Audit Log",
"audit_log_report_desc": "View system audit log entries for financial transactions and configuration changes",
"system_reports": "System Reports",
"all": "All",
"organization": "Organization.",
"check_your_email_for_a_link_to_reset": "Check your email for a link to reset your password. If it doesnt appear within a few minutes, check your spam folder.",
+11
View File
@@ -496,6 +496,17 @@ export const getDashboardRoutes = () => [
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: `/financial-reports/audit-log`,
component: lazy(
() => import('@/containers/FinancialStatements/AuditLog/AuditLogReport'),
),
breadcrumb: intl.get('audit_log_report'),
pageTitle: intl.get('audit_log_report'),
backLink: true,
sidebarExpand: false,
subscriptionActive: [SUBSCRIPTION_TYPE.MAIN],
},
{
path: '/financial-reports',
component: lazy(
+170 -153
View File
@@ -65,7 +65,7 @@ importers:
version: 5.23.0
'@bull-board/nestjs':
specifier: ^5.22.0
version: 5.23.0(@bull-board/api@5.23.0(@bull-board/ui@5.23.0))(@bull-board/express@5.23.0)(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)
version: 5.23.0(@bull-board/api@5.23.0(@bull-board/ui@5.23.0))(@bull-board/express@5.23.0)(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@casl/ability':
specifier: ^5.4.3
version: 5.4.4
@@ -74,19 +74,19 @@ importers:
version: 2.2.0
'@liaoliaots/nestjs-redis':
specifier: ^10.0.0
version: 10.0.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(ioredis@5.6.0)
version: 10.0.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(ioredis@5.6.0)
'@nest-lab/throttler-storage-redis':
specifier: ^1.1.0
version: 1.1.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/throttler@6.2.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2))(ioredis@5.6.0)(reflect-metadata@0.2.2)
version: 1.1.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/throttler@6.2.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(reflect-metadata@0.2.2))(ioredis@5.6.0)(reflect-metadata@0.2.2)
'@nestjs/bull':
specifier: ^10.2.1
version: 10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(bull@4.16.4)
version: 10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(bull@4.16.4)
'@nestjs/bullmq':
specifier: ^10.2.2
version: 10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(bullmq@5.25.6)
version: 10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(bullmq@5.25.6)
'@nestjs/cache-manager':
specifier: ^2.2.2
version: 2.3.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(cache-manager@6.1.3)(rxjs@7.8.1)
version: 2.3.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(cache-manager@6.1.3)(rxjs@7.8.1)
'@nestjs/common':
specifier: ^10.0.0
version: 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
@@ -98,7 +98,7 @@ importers:
version: 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/event-emitter':
specifier: ^2.0.4
version: 2.1.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))
version: 2.1.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)
'@nestjs/jwt':
specifier: ^10.2.0
version: 10.2.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))
@@ -113,16 +113,16 @@ importers:
version: 10.4.20(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/websockets@10.4.20)(rxjs@7.8.1)
'@nestjs/schedule':
specifier: ^4.1.2
version: 4.1.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))
version: 4.1.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)
'@nestjs/serve-static':
specifier: ^5.0.3
version: 5.0.3(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))
version: 5.0.3(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)
'@nestjs/swagger':
specifier: ^7.4.2
version: 7.4.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)
version: 7.4.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)
'@nestjs/throttler':
specifier: ^6.2.1
version: 6.2.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)
version: 6.2.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)
'@nestjs/websockets':
specifier: ^10.0.0
version: 10.4.20(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-socket.io@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.1)
@@ -157,7 +157,7 @@ importers:
specifier: ^1.6.0
version: 1.7.2
bcrypt:
specifier: ^5.1.1
specifier: 5.1.1
version: 5.1.1(encoding@0.1.13)
bcryptjs:
specifier: ^2.4.3
@@ -245,16 +245,16 @@ importers:
version: 3.11.4
nest-commander:
specifier: ^3.20.1
version: 3.20.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@types/inquirer@8.2.12)(@types/node@20.5.1)(typescript@5.6.3)
version: 3.20.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@types/inquirer@8.2.12)(@types/node@20.5.1)(typescript@5.6.3)
nestjs-cls:
specifier: ^5.2.0
version: 5.2.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)
version: 5.2.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1)
nestjs-i18n:
specifier: ^10.4.9
version: 10.5.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-validator@0.14.1)(rxjs@7.8.1)
version: 10.5.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(class-validator@0.14.1)(rxjs@7.8.1)
nestjs-redis:
specifier: ^1.3.3
version: 1.3.3(@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7))(@nestjs/websockets@10.4.20(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-socket.io@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.1))(cache-manager@6.1.3)(class-transformer@0.5.1)(class-validator@0.14.1)(encoding@0.1.13)
version: 1.3.3(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(cache-manager@6.1.3)(class-transformer@0.5.1)(class-validator@0.14.1)(encoding@0.1.13)
nodemailer:
specifier: ^6.3.0
version: 6.9.13
@@ -342,7 +342,7 @@ importers:
version: 10.2.3(chokidar@3.6.0)(typescript@5.6.3)
'@nestjs/testing':
specifier: ^10.0.0
version: 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7))
version: 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-express@10.4.7)
'@types/express':
specifier: ^5.0.0
version: 5.0.0
@@ -423,10 +423,10 @@ importers:
version: link:../../shared/bigcapital-utils
'@blueprintjs-formik/core':
specifier: ^0.3.7
version: 0.3.7(@babel/core@7.26.0)(@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/select@4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(formik@2.4.6(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
version: 0.3.7(@babel/core@7.28.5)(@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/select@4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(formik@2.4.6(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
'@blueprintjs-formik/datetime':
specifier: ^0.4.0
version: 0.4.0(@babel/core@7.26.0)(@blueprintjs-formik/core@0.3.7(@babel/core@7.26.0)(@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/select@4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(formik@2.4.6(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/datetime2@0.9.37(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/datetime@4.4.37(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/select@4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(formik@2.4.6(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
version: 0.4.0(@babel/core@7.28.5)(@blueprintjs-formik/core@0.3.7(@babel/core@7.28.5)(@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/select@4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(formik@2.4.6(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/datetime2@0.9.37(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/datetime@4.4.37(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/select@4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(formik@2.4.6(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
'@blueprintjs-formik/select':
specifier: ^0.4.5
version: 0.4.5(@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/select@4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(formik@2.4.6(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -765,7 +765,7 @@ importers:
version: 0.23.1
styled-components:
specifier: ^5.3.1
version: 5.3.11(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
version: 5.3.11(@babel/core@7.28.5)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
stylis-rtlcss:
specifier: ^2.1.1
version: 2.1.1
@@ -787,7 +787,7 @@ importers:
version: 4.7.0(vite@5.4.10(@types/node@20.19.25)(less@4.2.0)(sass@1.77.2)(terser@5.31.0))
eslint-config-react-app:
specifier: ^7.0.1
version: 7.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.26.0))(eslint@8.57.0)(typescript@4.9.5)
version: 7.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.28.5))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.5))(eslint@8.57.0)(typescript@4.9.5)
vite:
specifier: ^5.1.6
version: 5.4.10(@types/node@20.19.25)(less@4.2.0)(sass@1.77.2)(terser@5.31.0)
@@ -913,13 +913,13 @@ importers:
version: 2.5.1
css-loader:
specifier: ^6.x
version: 6.11.0(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))
version: 6.11.0(webpack@5.91.0)
declaration-bundler-webpack-plugin:
specifier: ^1.0.3
version: 1.0.3
fork-ts-checker-webpack-plugin:
specifier: ^9.0.2
version: 9.0.2(typescript@5.6.3)(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))
version: 9.0.2(typescript@5.6.3)(webpack@5.91.0)
lodash:
specifier: ^4.17.15
version: 4.17.21
@@ -931,16 +931,16 @@ importers:
version: 18.3.1(react@18.3.1)
style-loader:
specifier: ^3.x
version: 3.3.4(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))
version: 3.3.4(webpack@5.91.0)
tailwindcss:
specifier: ^3.4.14
version: 3.4.14(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.6.3))
version: 3.4.14(ts-node@10.9.2(@types/node@20.19.25)(typescript@5.6.3))
ts-loader:
specifier: ^9.x
version: 9.5.1(typescript@5.6.3)(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))
version: 9.5.1(typescript@5.6.3)(webpack@5.91.0)
webpack:
specifier: ^5.x
version: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
version: 5.91.0(esbuild@0.23.1)(webpack-cli@5.1.4)
webpack-cli:
specifier: ^5.x
version: 5.1.4(webpack@5.91.0)
@@ -959,7 +959,7 @@ importers:
version: 7.2.2(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@storybook/addon-styling':
specifier: 1.3.6
version: 1.3.6(@types/react-dom@18.3.0)(@types/react@18.3.4)(encoding@0.1.13)(less@4.2.0)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.2)(typescript@5.6.3)(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))
version: 1.3.6(@types/react-dom@18.3.0)(@types/react@18.3.4)(encoding@0.1.13)(less@4.2.0)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.2)(typescript@5.6.3)(webpack@5.91.0)
'@storybook/blocks':
specifier: 7.2.2
version: 7.2.2(@types/react-dom@18.3.0)(@types/react@18.3.4)(encoding@0.1.13)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -1011,7 +1011,7 @@ importers:
version: 7.13.0(typescript@5.6.3)
tsup:
specifier: ^8.3.0
version: 8.3.0(@microsoft/api-extractor@7.47.11(@types/node@20.5.1))(jiti@1.21.0)(postcss@8.4.47)(typescript@5.6.3)(yaml@2.4.2)
version: 8.3.0(@microsoft/api-extractor@7.47.11(@types/node@20.19.25))(jiti@1.21.0)(postcss@8.4.47)(typescript@5.6.3)(yaml@2.4.2)
typescript:
specifier: ^5.1.3
version: 5.6.3
@@ -3600,24 +3600,28 @@ packages:
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@nx/nx-linux-arm64-musl@19.0.7':
resolution: {integrity: sha512-d0a3iIobeYJY4b8HdwsohGt1d7TMKQJM0nWI3xcbqotLSovFoL6CqNn3g7uZhZPhNwQXNtSEwaXfQRZcH9Nr1g==}
engines: {node: '>= 10'}
cpu: [arm64]
os: [linux]
libc: [musl]
'@nx/nx-linux-x64-gnu@19.0.7':
resolution: {integrity: sha512-C75zX747Fwc/oH8Xk6+U4xBG97BYka5hGU57034cQVMHKaDfivVHKfwBuROVkj3Mg96QbAS3rAAfMtaCthMLTA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [glibc]
'@nx/nx-linux-x64-musl@19.0.7':
resolution: {integrity: sha512-OzPtwGp4ENsRI5J35kbjXE0hDbTNF1QwnK3O7R7H1Ew9WJjZi3tEm1cTsq0/SssI6YbZqRhzrNBd/N7Mkjd2dA==}
engines: {node: '>= 10'}
cpu: [x64]
os: [linux]
libc: [musl]
'@nx/nx-win32-arm64-msvc@19.0.7':
resolution: {integrity: sha512-G17tdQEjE6eAWRO4XFGpoFHZ55M1AVmj+CJwaTowzXDSk26y3waoDUGOy75ft1LLoz5i8Q9CWFG4Fnyno4Bv/g==}
@@ -4339,46 +4343,55 @@ packages:
resolution: {integrity: sha512-0KXvIJQMOImLCVCz9uvvdPgfyWo93aHHp8ui3FrtOP57svqrF/roSSR5pjqL2hcMp0ljeGlU4q9o/rQaAQ3AYA==}
cpu: [arm]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.24.0':
resolution: {integrity: sha512-it2BW6kKFVh8xk/BnHfakEeoLPv8STIISekpoF+nBgWM4d55CZKc7T4Dx1pEbTnYm/xEKMgy1MNtYuoA8RFIWw==}
cpu: [arm]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.24.0':
resolution: {integrity: sha512-i0xTLXjqap2eRfulFVlSnM5dEbTVque/3Pi4g2y7cxrs7+a9De42z4XxKLYJ7+OhE3IgxvfQM7vQc43bwTgPwA==}
cpu: [arm64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.24.0':
resolution: {integrity: sha512-9E6MKUJhDuDh604Qco5yP/3qn3y7SLXYuiC0Rpr89aMScS2UAmK1wHP2b7KAa1nSjWJc/f/Lc0Wl1L47qjiyQw==}
cpu: [arm64]
os: [linux]
libc: [musl]
'@rollup/rollup-linux-powerpc64le-gnu@4.24.0':
resolution: {integrity: sha512-2XFFPJ2XMEiF5Zi2EBf4h73oR1V/lycirxZxHZNc93SqDN/IWhYYSYj8I9381ikUFXZrz2v7r2tOVk2NBwxrWw==}
cpu: [ppc64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-riscv64-gnu@4.24.0':
resolution: {integrity: sha512-M3Dg4hlwuntUCdzU7KjYqbbd+BLq3JMAOhCKdBE3TcMGMZbKkDdJ5ivNdehOssMCIokNHFOsv7DO4rlEOfyKpg==}
cpu: [riscv64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-s390x-gnu@4.24.0':
resolution: {integrity: sha512-mjBaoo4ocxJppTorZVKWFpy1bfFj9FeCMJqzlMQGjpNPY9JwQi7OuS1axzNIk0nMX6jSgy6ZURDZ2w0QW6D56g==}
cpu: [s390x]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.24.0':
resolution: {integrity: sha512-ZXFk7M72R0YYFN5q13niV0B7G8/5dcQ9JDp8keJSfr3GoZeXEoMHP/HlvqROA3OMbMdfr19IjCeNAnPUG93b6A==}
cpu: [x64]
os: [linux]
libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.24.0':
resolution: {integrity: sha512-w1i+L7kAXZNdYl+vFvzSZy8Y1arS7vMgIy8wusXJzRrPyof5LAb02KGr1PD2EkRcl73kHulIID0M501lN+vobQ==}
cpu: [x64]
os: [linux]
libc: [musl]
'@rollup/rollup-win32-arm64-msvc@4.24.0':
resolution: {integrity: sha512-VXBrnPWgBpVDCVY6XF3LEW0pOU51KbaHhccHw6AS6vBWIC60eqsH19DAeeObl+g8nKAz04QFdl/Cefta0xQtUQ==}
@@ -14598,11 +14611,6 @@ snapshots:
'@babel/core': 7.28.5
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.26.0)':
dependencies:
'@babel/core': 7.26.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-jsx@7.24.1(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -14613,6 +14621,11 @@ snapshots:
'@babel/core': 7.26.0
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
@@ -15292,6 +15305,17 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.5)':
dependencies:
'@babel/core': 7.28.5
'@babel/helper-annotate-as-pure': 7.27.3
'@babel/helper-module-imports': 7.27.1
'@babel/helper-plugin-utils': 7.27.1
'@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.5)
'@babel/types': 7.28.5
transitivePeerDependencies:
- supports-color
'@babel/plugin-transform-react-pure-annotations@7.27.1(@babel/core@7.26.0)':
dependencies:
'@babel/core': 7.26.0
@@ -15773,7 +15797,7 @@ snapshots:
'@bcoe/v8-coverage@0.2.3': {}
'@blueprintjs-formik/core@0.3.7(@babel/core@7.26.0)(@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/select@4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(formik@2.4.6(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)':
'@blueprintjs-formik/core@0.3.7(@babel/core@7.28.5)(@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/select@4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(formik@2.4.6(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)':
dependencies:
'@blueprintjs/core': 4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@blueprintjs/select': 4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -15782,15 +15806,15 @@ snapshots:
lodash.keyby: 4.6.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
styled-components: 5.3.11(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
styled-components: 5.3.11(@babel/core@7.28.5)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
web-vitals: 2.1.4
transitivePeerDependencies:
- '@babel/core'
- react-is
'@blueprintjs-formik/datetime@0.4.0(@babel/core@7.26.0)(@blueprintjs-formik/core@0.3.7(@babel/core@7.26.0)(@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/select@4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(formik@2.4.6(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/datetime2@0.9.37(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/datetime@4.4.37(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/select@4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(formik@2.4.6(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)':
'@blueprintjs-formik/datetime@0.4.0(@babel/core@7.28.5)(@blueprintjs-formik/core@0.3.7(@babel/core@7.28.5)(@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/select@4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(formik@2.4.6(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/datetime2@0.9.37(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/datetime@4.4.37(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/select@4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(formik@2.4.6(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)':
dependencies:
'@blueprintjs-formik/core': 0.3.7(@babel/core@7.26.0)(@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/select@4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(formik@2.4.6(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
'@blueprintjs-formik/core': 0.3.7(@babel/core@7.28.5)(@blueprintjs/core@4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(@blueprintjs/select@4.9.24(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1))(formik@2.4.6(react@18.3.1))(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
'@blueprintjs/core': 4.20.2(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@blueprintjs/datetime': 4.4.37(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@blueprintjs/datetime2': 0.9.37(@types/react@18.3.4)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
@@ -15800,7 +15824,7 @@ snapshots:
lodash.keyby: 4.6.0
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
styled-components: 5.3.11(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
styled-components: 5.3.11(@babel/core@7.28.5)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
web-vitals: 2.1.4
transitivePeerDependencies:
- '@babel/core'
@@ -15939,11 +15963,11 @@ snapshots:
transitivePeerDependencies:
- supports-color
'@bull-board/nestjs@5.23.0(@bull-board/api@5.23.0(@bull-board/ui@5.23.0))(@bull-board/express@5.23.0)(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1)':
'@bull-board/nestjs@5.23.0(@bull-board/api@5.23.0(@bull-board/ui@5.23.0))(@bull-board/express@5.23.0)(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1)':
dependencies:
'@bull-board/api': 5.23.0(@bull-board/ui@5.23.0)
'@bull-board/express': 5.23.0
'@nestjs/bull-shared': 10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))
'@nestjs/bull-shared': 10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
reflect-metadata: 0.2.2
@@ -16562,7 +16586,7 @@ snapshots:
'@formatjs/intl-utils@2.3.0': {}
'@golevelup/nestjs-discovery@5.0.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))':
'@golevelup/nestjs-discovery@5.0.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)':
dependencies:
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
@@ -16967,7 +16991,7 @@ snapshots:
dependencies:
npmlog: 6.0.2
'@liaoliaots/nestjs-redis@10.0.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(ioredis@5.6.0)':
'@liaoliaots/nestjs-redis@10.0.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(ioredis@5.6.0)':
dependencies:
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
@@ -17088,38 +17112,38 @@ snapshots:
pump: 3.0.0
tar-fs: 2.1.1
'@nest-lab/throttler-storage-redis@1.1.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/throttler@6.2.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2))(ioredis@5.6.0)(reflect-metadata@0.2.2)':
'@nest-lab/throttler-storage-redis@1.1.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/throttler@6.2.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(reflect-metadata@0.2.2))(ioredis@5.6.0)(reflect-metadata@0.2.2)':
dependencies:
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/throttler': 6.2.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)
'@nestjs/throttler': 6.2.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)
ioredis: 5.6.0
reflect-metadata: 0.2.2
tslib: 2.8.1
'@nestjs/bull-shared@10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))':
'@nestjs/bull-shared@10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)':
dependencies:
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
tslib: 2.8.0
'@nestjs/bull@10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(bull@4.16.4)':
'@nestjs/bull@10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(bull@4.16.4)':
dependencies:
'@nestjs/bull-shared': 10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))
'@nestjs/bull-shared': 10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
bull: 4.16.4
tslib: 2.8.0
'@nestjs/bullmq@10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(bullmq@5.25.6)':
'@nestjs/bullmq@10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(bullmq@5.25.6)':
dependencies:
'@nestjs/bull-shared': 10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))
'@nestjs/bull-shared': 10.2.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
bullmq: 5.25.6
tslib: 2.8.0
'@nestjs/cache-manager@2.3.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(cache-manager@6.1.3)(rxjs@7.8.1)':
'@nestjs/cache-manager@2.3.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(cache-manager@6.1.3)(rxjs@7.8.1)':
dependencies:
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
@@ -17218,7 +17242,7 @@ snapshots:
transitivePeerDependencies:
- encoding
'@nestjs/core@7.6.18(@nestjs/common@7.6.18(cache-manager@6.1.3)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@6.6.7))(@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7))(@nestjs/websockets@10.4.20(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-socket.io@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.1))(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@6.6.7)':
'@nestjs/core@7.6.18(@nestjs/common@7.6.18(cache-manager@6.1.3)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@6.6.7))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@6.6.7)':
dependencies:
'@nestjs/common': 7.6.18(cache-manager@6.1.3)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@6.6.7)
'@nuxtjs/opencollective': 0.3.2(encoding@0.1.13)
@@ -17236,7 +17260,7 @@ snapshots:
transitivePeerDependencies:
- encoding
'@nestjs/event-emitter@2.1.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))':
'@nestjs/event-emitter@2.1.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)':
dependencies:
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
@@ -17285,7 +17309,7 @@ snapshots:
- supports-color
- utf-8-validate
'@nestjs/schedule@4.1.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))':
'@nestjs/schedule@4.1.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)':
dependencies:
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
@@ -17303,13 +17327,13 @@ snapshots:
transitivePeerDependencies:
- chokidar
'@nestjs/serve-static@5.0.3(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))':
'@nestjs/serve-static@5.0.3(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)':
dependencies:
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
path-to-regexp: 8.2.0
'@nestjs/swagger@7.4.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)':
'@nestjs/swagger@7.4.2(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)':
dependencies:
'@microsoft/tsdoc': 0.15.0
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
@@ -17324,7 +17348,7 @@ snapshots:
class-transformer: 0.5.1
class-validator: 0.14.1
'@nestjs/testing@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7))':
'@nestjs/testing@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-express@10.4.7)':
dependencies:
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
@@ -17332,7 +17356,7 @@ snapshots:
optionalDependencies:
'@nestjs/platform-express': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)
'@nestjs/throttler@6.2.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)':
'@nestjs/throttler@6.2.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)':
dependencies:
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
@@ -18848,7 +18872,7 @@ snapshots:
- '@types/react'
- '@types/react-dom'
'@storybook/addon-styling@1.3.6(@types/react-dom@18.3.0)(@types/react@18.3.4)(encoding@0.1.13)(less@4.2.0)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.2)(typescript@5.6.3)(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))':
'@storybook/addon-styling@1.3.6(@types/react-dom@18.3.0)(@types/react@18.3.4)(encoding@0.1.13)(less@4.2.0)(postcss@8.4.47)(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(sass@1.77.2)(typescript@5.6.3)(webpack@5.91.0)':
dependencies:
'@babel/template': 7.25.9
'@babel/types': 7.26.0
@@ -18861,19 +18885,19 @@ snapshots:
'@storybook/preview-api': 7.6.20
'@storybook/theming': 7.6.20(react-dom@18.3.1(react@18.3.1))(react@18.3.1)
'@storybook/types': 7.6.20
css-loader: 6.11.0(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))
less-loader: 11.1.4(less@4.2.0)(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))
postcss-loader: 7.3.4(postcss@8.4.47)(typescript@5.6.3)(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))
css-loader: 6.11.0(webpack@5.91.0)
less-loader: 11.1.4(less@4.2.0)(webpack@5.91.0)
postcss-loader: 7.3.4(postcss@8.4.47)(typescript@5.6.3)(webpack@5.91.0)
prettier: 2.8.8
resolve-url-loader: 5.0.0
sass-loader: 13.3.3(sass@1.77.2)(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))
style-loader: 3.3.4(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))
sass-loader: 13.3.3(sass@1.77.2)(webpack@5.91.0)
style-loader: 3.3.4(webpack@5.91.0)
optionalDependencies:
less: 4.2.0
postcss: 8.4.47
react: 18.3.1
react-dom: 18.3.1(react@18.3.1)
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@5.1.4)
transitivePeerDependencies:
- '@rspack/core'
- '@types/react'
@@ -20844,19 +20868,19 @@ snapshots:
'@webassemblyjs/ast': 1.12.1
'@xtuc/long': 4.2.2
'@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4(webpack@5.91.0))(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))':
'@webpack-cli/configtest@2.1.1(webpack-cli@5.1.4)(webpack@5.91.0)':
dependencies:
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@5.1.4)
webpack-cli: 5.1.4(webpack@5.91.0)
'@webpack-cli/info@2.0.2(webpack-cli@5.1.4(webpack@5.91.0))(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))':
'@webpack-cli/info@2.0.2(webpack-cli@5.1.4)(webpack@5.91.0)':
dependencies:
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@5.1.4)
webpack-cli: 5.1.4(webpack@5.91.0)
'@webpack-cli/serve@2.0.5(webpack-cli@5.1.4(webpack@5.91.0))(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))':
'@webpack-cli/serve@2.0.5(webpack-cli@5.1.4)(webpack@5.91.0)':
dependencies:
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@5.1.4)
webpack-cli: 5.1.4(webpack@5.91.0)
'@welldone-software/why-did-you-render@6.2.3(react@18.3.1)':
@@ -21420,14 +21444,14 @@ snapshots:
transitivePeerDependencies:
- supports-color
babel-plugin-styled-components@2.1.4(@babel/core@7.26.0)(styled-components@5.3.11(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(supports-color@5.5.0):
babel-plugin-styled-components@2.1.4(@babel/core@7.28.5)(styled-components@5.3.11(@babel/core@7.28.5)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(supports-color@5.5.0):
dependencies:
'@babel/helper-annotate-as-pure': 7.22.5
'@babel/helper-module-imports': 7.25.9(supports-color@5.5.0)
'@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.26.0)
'@babel/plugin-syntax-jsx': 7.24.1(@babel/core@7.28.5)
lodash: 4.17.21
picomatch: 2.3.1
styled-components: 5.3.11(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
styled-components: 5.3.11(@babel/core@7.28.5)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1)
transitivePeerDependencies:
- '@babel/core'
- supports-color
@@ -22319,7 +22343,7 @@ snapshots:
dependencies:
hyphenate-style-name: 1.0.5
css-loader@6.11.0(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)):
css-loader@6.11.0(webpack@5.91.0):
dependencies:
icss-utils: 5.1.0(postcss@8.4.47)
postcss: 8.4.47
@@ -22330,7 +22354,7 @@ snapshots:
postcss-value-parser: 4.2.0
semver: 7.6.3
optionalDependencies:
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@5.1.4)
css-loader@6.11.0(webpack@5.96.1(esbuild@0.18.20)):
dependencies:
@@ -23035,7 +23059,7 @@ snapshots:
dependencies:
eslint: 9.13.0(jiti@1.21.0)
eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.26.0))(eslint@8.57.0)(typescript@4.9.5):
eslint-config-react-app@7.0.1(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.28.5))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.5))(eslint@8.57.0)(typescript@4.9.5):
dependencies:
'@babel/core': 7.26.0
'@babel/eslint-parser': 7.24.5(@babel/core@7.26.0)(eslint@8.57.0)
@@ -23045,7 +23069,7 @@ snapshots:
babel-preset-react-app: 10.0.1
confusing-browser-globals: 1.0.11
eslint: 8.57.0
eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.26.0))(eslint@8.57.0)
eslint-plugin-flowtype: 8.0.3(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.28.5))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.5))(eslint@8.57.0)
eslint-plugin-import: 2.29.1(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)
eslint-plugin-jest: 25.7.0(@typescript-eslint/eslint-plugin@5.62.0(@typescript-eslint/parser@5.62.0(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5))(eslint@8.57.0)(typescript@4.9.5)
eslint-plugin-jsx-a11y: 6.8.0(eslint@8.57.0)
@@ -23080,10 +23104,10 @@ snapshots:
transitivePeerDependencies:
- supports-color
eslint-plugin-flowtype@8.0.3(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.26.0))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.26.0))(eslint@8.57.0):
eslint-plugin-flowtype@8.0.3(@babel/plugin-syntax-flow@7.26.0(@babel/core@7.28.5))(@babel/plugin-transform-react-jsx@7.27.1(@babel/core@7.28.5))(eslint@8.57.0):
dependencies:
'@babel/plugin-syntax-flow': 7.26.0(@babel/core@7.26.0)
'@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.26.0)
'@babel/plugin-syntax-flow': 7.26.0(@babel/core@7.28.5)
'@babel/plugin-transform-react-jsx': 7.27.1(@babel/core@7.28.5)
eslint: 8.57.0
lodash: 4.17.21
string-natural-compare: 3.0.1
@@ -23666,7 +23690,7 @@ snapshots:
cross-spawn: 7.0.3
signal-exit: 4.1.0
fork-ts-checker-webpack-plugin@9.0.2(typescript@5.6.3)(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)):
fork-ts-checker-webpack-plugin@9.0.2(typescript@5.6.3)(webpack@5.91.0):
dependencies:
'@babel/code-frame': 7.26.0
chalk: 4.1.2
@@ -23681,7 +23705,7 @@ snapshots:
semver: 7.6.2
tapable: 2.2.1
typescript: 5.6.3
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@5.1.4)
fork-ts-checker-webpack-plugin@9.0.2(typescript@5.6.3)(webpack@5.96.1(esbuild@0.23.1)):
dependencies:
@@ -25326,10 +25350,10 @@ snapshots:
- encoding
- supports-color
less-loader@11.1.4(less@4.2.0)(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)):
less-loader@11.1.4(less@4.2.0)(webpack@5.91.0):
dependencies:
less: 4.2.0
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@5.1.4)
less-loader@11.1.4(less@4.2.0)(webpack@5.96.1(esbuild@0.18.20)):
dependencies:
@@ -25973,10 +25997,10 @@ snapshots:
neo-async@2.6.2: {}
nest-commander@3.20.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@types/inquirer@8.2.12)(@types/node@20.5.1)(typescript@5.6.3):
nest-commander@3.20.1(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@types/inquirer@8.2.12)(@types/node@20.5.1)(typescript@5.6.3):
dependencies:
'@fig/complete-commander': 3.2.0(commander@11.1.0)
'@golevelup/nestjs-discovery': 5.0.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))
'@golevelup/nestjs-discovery': 5.0.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@types/inquirer': 8.2.12
@@ -25987,14 +26011,14 @@ snapshots:
- '@types/node'
- typescript
nestjs-cls@5.2.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(reflect-metadata@0.2.2)(rxjs@7.8.1):
nestjs-cls@5.2.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(reflect-metadata@0.2.2)(rxjs@7.8.1):
dependencies:
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
reflect-metadata: 0.2.2
rxjs: 7.8.1
nestjs-i18n@10.5.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(class-validator@0.14.1)(rxjs@7.8.1):
nestjs-i18n@10.5.0(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(class-validator@0.14.1)(rxjs@7.8.1):
dependencies:
'@nestjs/common': 10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1)
'@nestjs/core': 10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)
@@ -26007,10 +26031,10 @@ snapshots:
rxjs: 7.8.1
string-format: 2.0.0
nestjs-redis@1.3.3(@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7))(@nestjs/websockets@10.4.20(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-socket.io@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.1))(cache-manager@6.1.3)(class-transformer@0.5.1)(class-validator@0.14.1)(encoding@0.1.13):
nestjs-redis@1.3.3(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(cache-manager@6.1.3)(class-transformer@0.5.1)(class-validator@0.14.1)(encoding@0.1.13):
dependencies:
'@nestjs/common': 7.6.18(cache-manager@6.1.3)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@6.6.7)
'@nestjs/core': 7.6.18(@nestjs/common@7.6.18(cache-manager@6.1.3)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@6.6.7))(@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7))(@nestjs/websockets@10.4.20(@nestjs/common@10.4.7(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)(@nestjs/platform-socket.io@10.4.20)(reflect-metadata@0.2.2)(rxjs@7.8.1))(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@6.6.7)
'@nestjs/core': 7.6.18(@nestjs/common@7.6.18(cache-manager@6.1.3)(class-transformer@0.5.1)(class-validator@0.14.1)(reflect-metadata@0.2.2)(rxjs@6.6.7))(@nestjs/platform-express@10.4.7)(@nestjs/websockets@10.4.20)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@6.6.7)
'@types/ioredis': 5.0.0
'@types/uuid': 10.0.0
ioredis: 4.29.1
@@ -26767,14 +26791,6 @@ snapshots:
postcss: 8.4.47
ts-node: 10.9.2(@types/node@20.19.25)(typescript@5.6.3)
postcss-load-config@4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.6.3)):
dependencies:
lilconfig: 3.1.1
yaml: 2.4.2
optionalDependencies:
postcss: 8.4.47
ts-node: 10.9.2(@types/node@20.5.1)(typescript@5.6.3)
postcss-load-config@6.0.1(jiti@1.21.0)(postcss@8.4.47)(yaml@2.4.2):
dependencies:
lilconfig: 3.1.1
@@ -26783,13 +26799,13 @@ snapshots:
postcss: 8.4.47
yaml: 2.4.2
postcss-loader@7.3.4(postcss@8.4.47)(typescript@5.6.3)(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)):
postcss-loader@7.3.4(postcss@8.4.47)(typescript@5.6.3)(webpack@5.91.0):
dependencies:
cosmiconfig: 8.3.6(typescript@5.6.3)
jiti: 1.21.0
postcss: 8.4.47
semver: 7.6.3
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@5.1.4)
transitivePeerDependencies:
- typescript
@@ -28028,10 +28044,10 @@ snapshots:
safer-buffer@2.1.2: {}
sass-loader@13.3.3(sass@1.77.2)(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)):
sass-loader@13.3.3(sass@1.77.2)(webpack@5.91.0):
dependencies:
neo-async: 2.6.2
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@5.1.4)
optionalDependencies:
sass: 1.77.2
@@ -28557,22 +28573,22 @@ snapshots:
loader-utils: 1.4.2
schema-utils: 1.0.0
style-loader@3.3.4(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)):
style-loader@3.3.4(webpack@5.91.0):
dependencies:
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@5.1.4)
style-loader@3.3.4(webpack@5.96.1(esbuild@0.18.20)):
dependencies:
webpack: 5.96.1(esbuild@0.18.20)
styled-components@5.3.11(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1):
styled-components@5.3.11(@babel/core@7.28.5)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1):
dependencies:
'@babel/helper-module-imports': 7.24.3
'@babel/traverse': 7.24.5(supports-color@5.5.0)
'@emotion/is-prop-valid': 1.2.2
'@emotion/stylis': 0.8.5
'@emotion/unitless': 0.7.5
babel-plugin-styled-components: 2.1.4(@babel/core@7.26.0)(styled-components@5.3.11(@babel/core@7.26.0)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(supports-color@5.5.0)
babel-plugin-styled-components: 2.1.4(@babel/core@7.28.5)(styled-components@5.3.11(@babel/core@7.28.5)(react-dom@18.3.1(react@18.3.1))(react-is@18.3.1)(react@18.3.1))(supports-color@5.5.0)
css-to-react-native: 3.2.0
hoist-non-react-statics: 3.3.2
react: 18.3.1
@@ -28696,33 +28712,6 @@ snapshots:
transitivePeerDependencies:
- ts-node
tailwindcss@3.4.14(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.6.3)):
dependencies:
'@alloc/quick-lru': 5.2.0
arg: 5.0.2
chokidar: 3.6.0
didyoumean: 1.2.2
dlv: 1.1.3
fast-glob: 3.3.2
glob-parent: 6.0.2
is-glob: 4.0.3
jiti: 1.21.0
lilconfig: 2.1.0
micromatch: 4.0.7
normalize-path: 3.0.0
object-hash: 3.0.0
picocolors: 1.1.1
postcss: 8.4.47
postcss-import: 15.1.0(postcss@8.4.47)
postcss-js: 4.0.1(postcss@8.4.47)
postcss-load-config: 4.0.2(postcss@8.4.47)(ts-node@10.9.2(@types/node@20.5.1)(typescript@5.6.3))
postcss-nested: 6.0.1(postcss@8.4.47)
postcss-selector-parser: 6.1.0
resolve: 1.22.8
sucrase: 3.35.0
transitivePeerDependencies:
- ts-node
tapable@2.2.1: {}
tar-fs@2.1.1:
@@ -28771,17 +28760,6 @@ snapshots:
type-fest: 0.16.0
unique-string: 2.0.0
terser-webpack-plugin@5.3.10(esbuild@0.18.20)(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.2
terser: 5.31.0
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
optionalDependencies:
esbuild: 0.18.20
terser-webpack-plugin@5.3.10(esbuild@0.18.20)(webpack@5.96.1(esbuild@0.18.20)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
@@ -28793,6 +28771,17 @@ snapshots:
optionalDependencies:
esbuild: 0.18.20
terser-webpack-plugin@5.3.10(esbuild@0.23.1)(webpack@5.91.0):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
jest-worker: 27.5.1
schema-utils: 3.3.0
serialize-javascript: 6.0.2
terser: 5.31.0
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@5.1.4)
optionalDependencies:
esbuild: 0.23.1
terser-webpack-plugin@5.3.10(esbuild@0.23.1)(webpack@5.96.1(esbuild@0.23.1)):
dependencies:
'@jridgewell/trace-mapping': 0.3.31
@@ -28949,7 +28938,7 @@ snapshots:
babel-jest: 29.7.0(@babel/core@7.28.5)
esbuild: 0.23.1
ts-loader@9.5.1(typescript@5.6.3)(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)):
ts-loader@9.5.1(typescript@5.6.3)(webpack@5.91.0):
dependencies:
chalk: 4.1.2
enhanced-resolve: 5.17.1
@@ -28957,7 +28946,7 @@ snapshots:
semver: 7.6.3
source-map: 0.7.4
typescript: 5.6.3
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@5.1.4)
ts-loader@9.5.1(typescript@5.6.3)(webpack@5.96.1(esbuild@0.23.1)):
dependencies:
@@ -29045,6 +29034,34 @@ snapshots:
tslib@2.8.1: {}
tsup@8.3.0(@microsoft/api-extractor@7.47.11(@types/node@20.19.25))(jiti@1.21.0)(postcss@8.4.47)(typescript@5.6.3)(yaml@2.4.2):
dependencies:
bundle-require: 5.0.0(esbuild@0.23.1)
cac: 6.7.14
chokidar: 3.6.0
consola: 3.2.3
debug: 4.3.7(supports-color@5.5.0)
esbuild: 0.23.1
execa: 5.1.1
joycon: 3.1.1
picocolors: 1.0.1
postcss-load-config: 6.0.1(jiti@1.21.0)(postcss@8.4.47)(yaml@2.4.2)
resolve-from: 5.0.0
rollup: 4.24.0
source-map: 0.8.0-beta.0
sucrase: 3.35.0
tinyglobby: 0.2.9
tree-kill: 1.2.2
optionalDependencies:
'@microsoft/api-extractor': 7.47.11(@types/node@20.19.25)
postcss: 8.4.47
typescript: 5.6.3
transitivePeerDependencies:
- jiti
- supports-color
- tsx
- yaml
tsup@8.3.0(@microsoft/api-extractor@7.47.11(@types/node@20.5.1))(jiti@1.21.0)(postcss@8.4.47)(typescript@5.6.3)(yaml@2.4.2):
dependencies:
bundle-require: 5.0.0(esbuild@0.23.1)
@@ -29497,9 +29514,9 @@ snapshots:
webpack-cli@5.1.4(webpack@5.91.0):
dependencies:
'@discoveryjs/json-ext': 0.5.7
'@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4(webpack@5.91.0))(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))
'@webpack-cli/info': 2.0.2(webpack-cli@5.1.4(webpack@5.91.0))(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))
'@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4(webpack@5.91.0))(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))
'@webpack-cli/configtest': 2.1.1(webpack-cli@5.1.4)(webpack@5.91.0)
'@webpack-cli/info': 2.0.2(webpack-cli@5.1.4)(webpack@5.91.0)
'@webpack-cli/serve': 2.0.5(webpack-cli@5.1.4)(webpack@5.91.0)
colorette: 2.0.20
commander: 10.0.1
cross-spawn: 7.0.3
@@ -29508,7 +29525,7 @@ snapshots:
import-local: 3.1.0
interpret: 3.1.1
rechoir: 0.8.0
webpack: 5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4)
webpack: 5.91.0(esbuild@0.23.1)(webpack-cli@5.1.4)
webpack-merge: 5.10.0
webpack-merge@5.10.0:
@@ -29523,7 +29540,7 @@ snapshots:
webpack-virtual-modules@0.6.2: {}
webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4):
webpack@5.91.0(esbuild@0.23.1)(webpack-cli@5.1.4):
dependencies:
'@types/eslint-scope': 3.7.7
'@types/estree': 1.0.5
@@ -29546,7 +29563,7 @@ snapshots:
neo-async: 2.6.2
schema-utils: 3.3.0
tapable: 2.2.1
terser-webpack-plugin: 5.3.10(esbuild@0.18.20)(webpack@5.91.0(esbuild@0.18.20)(webpack-cli@5.1.4))
terser-webpack-plugin: 5.3.10(esbuild@0.23.1)(webpack@5.91.0)
watchpack: 2.4.1
webpack-sources: 3.2.3
optionalDependencies:
+2
View File
@@ -2,3 +2,5 @@ packages:
# all packages in direct subdirs of packages/
- 'packages/*'
- 'shared/*'
allowBuilds:
bcrypt: true