refactor(nestjs): import module

This commit is contained in:
Ahmed Bouhuolia
2025-04-09 10:39:08 +02:00
parent e8f1fedf35
commit d851e5b646
18 changed files with 192 additions and 133 deletions

View File

@@ -164,6 +164,7 @@ export interface IModelMetaRelationField2 {
}
export type IModelMetaField2 = IModelMetaFieldCommon2 &
IModelMetaFieldWithFields &
(
| IModelMetaFieldText
| IModelMetaFieldNumber

View File

@@ -59,8 +59,8 @@ export class BillPaymentsApplication {
/**
* Retrieves bill payments list.
*/
public getBillPayments(filterDTO: IBillPaymentsFilter) {
return this.getBillPaymentsService.getBillPayments(filterDTO);
public getBillPayments() {
// return this.getBillPaymentsService.getBillPayments(filterDTO);
}
/**

View File

@@ -19,7 +19,7 @@ export class BillPaymentsExportable extends Exportable {
* @param {number} tenantId
* @returns
*/
public exportable(query: any) {
public async exportable(query: any) {
const filterQuery = (builder) => {
builder.withGraphFetched('entries.bill');
builder.withGraphFetched('branch');

View File

@@ -1,46 +1,46 @@
// import { Inject, Service } from 'typedi';
// import { Knex } from 'knex';
// import { Importable } from '@/services/Import/Importable';
// import { CreateBill } from './CreateBill.service';
// import { IBillDTO } from '@/interfaces';
// import { BillsSampleData } from '../Bills.constants';
import { Knex } from 'knex';
import { CreateBill } from './CreateBill.service';
import { BillsSampleData } from '../Bills.constants';
import { Injectable } from '@nestjs/common';
import { Importable } from '@/modules/Import/Importable';
import { CreateBillDto } from '../dtos/Bill.dto';
// @Service()
// export class BillsImportable extends Importable {
// @Inject()
// private createBillService: CreateBill;
@Injectable()
export class BillsImportable extends Importable {
constructor(
private readonly createBillService: CreateBill,
) {
super();
}
// /**
// * Importing to account service.
// * @param {number} tenantId
// * @param {IAccountCreateDTO} createAccountDTO
// * @returns
// */
// public importable(
// tenantId: number,
// createAccountDTO: IBillDTO,
// trx?: Knex.Transaction
// ) {
// return this.createBillService.createBill(
// tenantId,
// createAccountDTO,
// {},
// trx
// );
// }
/**
* Importing to account service.
* @param {number} tenantId
* @param {IAccountCreateDTO} createAccountDTO
* @returns
*/
public importable(
createBillDto: CreateBillDto,
trx?: Knex.Transaction
) {
return this.createBillService.createBill(
createBillDto,
trx
);
}
// /**
// * Concurrrency controlling of the importing process.
// * @returns {number}
// */
// public get concurrency() {
// return 1;
// }
/**
* Concurrrency controlling of the importing process.
* @returns {number}
*/
public get concurrency() {
return 1;
}
// /**
// * Retrieves the sample data that used to download accounts sample sheet.
// */
// public sampleData(): any[] {
// return BillsSampleData;
// }
// }
/**
* Retrieves the sample data that used to download accounts sample sheet.
*/
public sampleData(): any[] {
return BillsSampleData;
}
}

View File

@@ -15,7 +15,7 @@ export class ExportAls {
* @returns The result of the callback function.
*/
public run<T>(callback: () => T): T {
return this.als.run<T>(new Map(), () => {
return this.als.run<T, []>(new Map(), () => {
this.markAsExport();
return callback();

View File

@@ -1,16 +1,15 @@
// @ts-nocheck
import { Injectable } from '@nestjs/common';
import xlsx from 'xlsx';
import * as R from 'ramda';
import { get } from 'lodash';
import { sanitizeResourceName } from '../Import/_utils';
import { ExportableResources } from './ExportResources';
import { ServiceError } from '@/exceptions';
import { Errors, ExportFormat } from './common';
import { IModelMeta, IModelMetaColumn } from '@/interfaces';
import { flatDataCollections, getDataAccessor } from './utils';
import { ExportPdf } from './ExportPdf';
import { ExportAls } from './ExportAls';
import { Injectable } from '@nestjs/common';
import { IModelMeta, IModelMetaColumn } from '@/interfaces/Model';
import { ServiceError } from '../Items/ServiceError';
@Injectable()
export class ExportResourceService {

View File

@@ -1,8 +1,34 @@
import { Module } from '@nestjs/common';
import { ImportAls } from './ImportALS';
import { ImportSampleService } from './ImportSample';
import { ImportResourceApplication } from './ImportResourceApplication';
import { ImportDeleteExpiredFiles } from './ImportRemoveExpiredFiles';
import { ImportFileUploadService } from './ImportFileUpload';
import { ImportFileProcessCommit } from './ImportFileProcessCommit';
import { ImportFileProcess } from './ImportFileProcess';
import { ImportFilePreview } from './ImportFilePreview';
import { ImportFileMeta } from './ImportFileMeta';
import { ImportFileMapping } from './ImportFileMapping';
import { ImportFileDataValidator } from './ImportFileDataValidator';
import { ImportFileDataTransformer } from './ImportFileDataTransformer';
import { ImportFileCommon } from './ImportFileCommon';
@Module({
providers: [ImportAls],
providers: [
ImportAls,
ImportSampleService,
ImportResourceApplication,
ImportDeleteExpiredFiles,
ImportFileUploadService,
ImportFileProcessCommit,
ImportFileProcess,
ImportFilePreview,
ImportFileMeta,
ImportFileMapping,
ImportFileDataValidator,
ImportFileDataTransformer,
ImportFileCommon
],
exports: [ImportAls],
})
export class ImportModule {}

View File

@@ -12,9 +12,9 @@ import {
import { getUniqueImportableValue, trimObject } from './_utils';
import { ImportableResources } from './ImportableResources';
import { ResourceService } from '../Resource/ResourceService';
import { Import } from '@/system/models';
import { Injectable } from '@nestjs/common';
import { ServiceError } from '../Items/ServiceError';
import { ImportModelShape } from './models/Import';
@Injectable()
export class ImportFileCommon {
@@ -32,7 +32,7 @@ export class ImportFileCommon {
* @returns {Promise<[ImportOperSuccess[], ImportOperError[]]>}
*/
public async import(
importFile: Import,
importFile: ImportModelShape,
parsedData: Record<string, any>[],
trx?: Knex.Transaction,
): Promise<[ImportOperSuccess[], ImportOperError[]]> {
@@ -68,7 +68,6 @@ export class ImportFileCommon {
try {
// Run the importable function and listen to the errors.
const data = await importable.importable(
tenantId,
transformedDTO,
trx,
);
@@ -135,14 +134,13 @@ export class ImportFileCommon {
* @param {Record<string, any>} params
*/
public async validateParams(
tenantId: number,
resourceName: string,
params: Record<string, any>,
) {
const ImportableRegistry = this.importable.registry;
const importable = ImportableRegistry.getImportable(resourceName);
await importable.validateParams(tenantId, params);
await importable.validateParams(params);
}
/**

View File

@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { fromPairs, isUndefined } from 'lodash';
import {
ImportDateFormats,
@@ -8,15 +8,19 @@ import {
import { ResourceService } from '../Resource/ResourceService';
import { ServiceError } from '../Items/ServiceError';
import { ERRORS } from './_utils';
import { Import } from './models/Import';
import { ImportModel } from './models/Import';
@Injectable()
export class ImportFileMapping {
constructor(private readonly resource: ResourceService) {}
constructor(
private readonly resource: ResourceService,
@Inject(ImportModel.name)
private readonly importModel: () => typeof ImportModel,
) {}
/**
* Mapping the excel sheet columns with resource columns.
* @param {number} tenantId
* @param {number} importId
* @param {ImportMappingAttr} maps
*/
@@ -24,7 +28,8 @@ export class ImportFileMapping {
importId: string,
maps: ImportMappingAttr[],
): Promise<ImportFileMapPOJO> {
const importFile = await Import.query()
const importFile = await this.importModel()
.query()
.findOne('filename', importId)
.throwIfNotFound();
@@ -41,7 +46,7 @@ export class ImportFileMapping {
const mappingStringified = JSON.stringify(maps);
await Import.query().findById(importFile.id).patch({
await this.importModel().query().findById(importFile.id).patch({
mapping: mappingStringified,
});
return {
@@ -54,7 +59,6 @@ export class ImportFileMapping {
/**
* Validate the mapping attributes.
* @param {number} tenantId -
* @param {} importFile -
* @param {ImportMappingAttr[]} maps
* @throws {ServiceError(ERRORS.INVALID_MAP_ATTRS)}
@@ -116,7 +120,6 @@ export class ImportFileMapping {
/**
* Validates the date format mapping.
* @param {number} tenantId
* @param {string} resource
* @param {ImportMappingAttr[]} maps
*/

View File

@@ -1,20 +1,29 @@
import { Import } from './models/Import';
import { ImportModel } from './models/Import';
import { ImportFileMetaTransformer } from './ImportFileMetaTransformer';
import { Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { TransformerInjectable } from '../Transformer/TransformerInjectable.service';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Injectable()
export class ImportFileMeta {
constructor(private readonly transformer: TransformerInjectable) {}
constructor(
private readonly transformer: TransformerInjectable,
private readonly tenancyContext: TenancyContext,
@Inject(ImportModel.name)
private readonly importModel: () => typeof ImportModel,
) {}
/**
* Retrieves the import meta of the given import model id.
* @param {number} tenantId
* @param {number} importId
* @returns {}
*/
async getImportMeta(importId: string) {
const importFile = await Import.query()
const tenant = await this.tenancyContext.getTenant();
const tenantId = tenant.id;
const importFile = await this.importModel()
.query()
.where('tenantId', tenantId)
.findOne('importId', importId);

View File

@@ -1,13 +1,18 @@
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { ImportFilePreviewPOJO } from './interfaces';
import { ImportFileProcess } from './ImportFileProcess';
import { ImportAls } from './ImportALS';
import { Injectable } from '@nestjs/common';
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
@Injectable()
export class ImportFilePreview {
constructor(
private readonly importFile: ImportFileProcess,
private readonly importAls: ImportAls,
@Inject(TENANCY_DB_CONNECTION)
private readonly tenantKnex: () => Knex
) {}
/**
@@ -27,8 +32,7 @@ export class ImportFilePreview {
* @returns {Promise<ImportFilePreviewPOJO>}
*/
public async previewAlsRun(importId: string): Promise<ImportFilePreviewPOJO> {
const knex = this.tenancy.knex(tenantId);
const trx = await knex.transaction({ isolationLevel: 'read uncommitted' });
const trx = await this.tenantKnex().transaction({ isolationLevel: 'read uncommitted' });
const meta = await this.importFile.import(importId, trx);

View File

@@ -1,14 +1,16 @@
import { chain } from 'lodash';
import { Knex } from 'knex';
import { Inject, Injectable } from '@nestjs/common';
import { ERRORS, getUnmappedSheetColumns, readImportFile } from './_utils';
import { ImportFileCommon } from './ImportFileCommon';
import { ImportFileDataTransformer } from './ImportFileDataTransformer';
import { ImportFilePreviewPOJO } from './interfaces';
import { parseSheetData } from './sheet_utils';
import { Injectable } from '@nestjs/common';
import { ResourceService } from '../Resource/ResourceService';
import { UnitOfWork } from '../Tenancy/TenancyDB/UnitOfWork.service';
import { ServiceError } from '../Items/ServiceError';
import { ImportModel } from './models/Import';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Injectable()
export class ImportFileProcess {
@@ -17,6 +19,10 @@ export class ImportFileProcess {
private readonly importCommon: ImportFileCommon,
private readonly importParser: ImportFileDataTransformer,
private readonly uow: UnitOfWork,
private readonly tenancyContext: TenancyContext,
@Inject(ImportModel.name)
private readonly importModel: typeof ImportModel,
) {}
/**
@@ -29,7 +35,11 @@ export class ImportFileProcess {
importId: string,
trx?: Knex.Transaction,
): Promise<ImportFilePreviewPOJO> {
const importFile = await Import.query()
const tenant = await this.tenancyContext.getTenant();
const tenantId = tenant.id;
const importFile = await this.importModel
.query()
.findOne('importId', importId)
.where('tenantId', tenantId)
.throwIfNotFound();
@@ -48,28 +58,21 @@ export class ImportFileProcess {
// Runs the importing operation with ability to return errors that will happen.
const [successedImport, failedImport, allData] =
await this.uow.withTransaction(
tenantId,
async (trx: Knex.Transaction) => {
// Prases the sheet json data.
const parsedData = await this.importParser.parseSheetData(
tenantId,
importFile,
resourceFields,
sheetData,
trx,
);
const [successedImport, failedImport] =
await this.importCommon.import(
tenantId,
importFile,
parsedData,
trx,
);
return [successedImport, failedImport, parsedData];
},
trx,
);
await this.uow.withTransaction(async (trx: Knex.Transaction) => {
// Prases the sheet json data.
const parsedData = await this.importParser.parseSheetData(
importFile,
resourceFields,
sheetData,
trx,
);
const [successedImport, failedImport] = await this.importCommon.import(
importFile,
parsedData,
trx,
);
return [successedImport, failedImport, parsedData];
}, trx);
const mapping = importFile.mappingParsed;
const errors = chain(failedImport)
.map((oper) => oper.error)

View File

@@ -1,9 +1,11 @@
import { ImportFilePreviewPOJO } from './interfaces';
import { Knex } from 'knex';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { IImportFileCommitedEventPayload, ImportFilePreviewPOJO } from './interfaces';
import { ImportFileProcess } from './ImportFileProcess';
import { ImportAls } from './ImportALS';
import { Injectable } from '@nestjs/common';
import { EventEmitter2 } from '@nestjs/event-emitter';
import { Inject, Injectable } from '@nestjs/common';
import { events } from '@/common/events/events';
import { TENANCY_DB_CONNECTION } from '../Tenancy/TenancyDB/TenancyDB.constants';
@Injectable()
export class ImportFileProcessCommit {
@@ -11,6 +13,9 @@ export class ImportFileProcessCommit {
private readonly importFile: ImportFileProcess,
private readonly importAls: ImportAls,
private readonly eventEmitter: EventEmitter2,
@Inject(TENANCY_DB_CONNECTION)
private readonly tenantKnex: () => Knex,
) {}
/**
@@ -30,8 +35,9 @@ export class ImportFileProcessCommit {
* @returns {Promise<ImportFilePreviewPOJO>}
*/
public async commitAlsRun(importId: string): Promise<ImportFilePreviewPOJO> {
const trx = await knex.transaction({ isolationLevel: 'read uncommitted' });
const trx = await this.tenantKnex().transaction({
isolationLevel: 'read uncommitted',
});
const meta = await this.importFile.import(importId, trx);
// Commit the successed transaction.

View File

@@ -9,9 +9,10 @@ import { ResourceService } from '../Resource/ResourceService';
import { ImportFileCommon } from './ImportFileCommon';
import { ImportFileDataValidator } from './ImportFileDataValidator';
import { ImportFileUploadPOJO } from './interfaces';
import { Import } from '@/system/models';
import { parseSheetData } from './sheet_utils';
import { Injectable } from '@nestjs/common';
import { Inject, Injectable } from '@nestjs/common';
import { ImportModel } from './models/Import';
import { TenancyContext } from '../Tenancy/TenancyContext.service';
@Injectable()
export class ImportFileUploadService {
@@ -19,6 +20,10 @@ export class ImportFileUploadService {
private resourceService: ResourceService,
private importFileCommon: ImportFileCommon,
private importValidator: ImportFileDataValidator,
private tenancyContext: TenancyContext,
@Inject(ImportModel.name)
private readonly importModel: typeof ImportModel,
) {}
/**
@@ -36,12 +41,7 @@ export class ImportFileUploadService {
params: Record<string, number | string>,
): Promise<ImportFileUploadPOJO> {
try {
return await this.importUnhandled(
tenantId,
resourceName,
filename,
params,
);
return await this.importUnhandled(resourceName, filename, params);
} catch (err) {
deleteImportFile(filename);
throw err;
@@ -81,15 +81,18 @@ export class ImportFileUploadService {
await this.importFileCommon.validateParamsSchema(resource, params);
// Validates importable params asyncly.
await this.importFileCommon.validateParams(tenantId, resource, params);
await this.importFileCommon.validateParams(resource, params);
} catch (error) {
throw error;
}
const _params = this.importFileCommon.transformParams(resource, params);
const paramsStringified = JSON.stringify(_params);
const tenant = await this.tenancyContext.getTenant();
const tenantId = tenant.id;
// Store the import model with related metadata.
const importFile = await Import.query().insert({
const importFile = await this.importModel.query().insert({
filename,
resource,
tenantId,
@@ -97,10 +100,8 @@ export class ImportFileUploadService {
columns: coumnsStringified,
params: paramsStringified,
});
const resourceColumnsMap = this.resourceService.getResourceFields2(
tenantId,
resource,
);
const resourceColumnsMap =
this.resourceService.getResourceFields2(resource);
const resourceColumns = getResourceColumns(resourceColumnsMap);
return {

View File

@@ -1,34 +1,36 @@
import * as moment from 'moment';
import bluebird from 'bluebird';
import { deleteImportFile } from './_utils';
import { Injectable } from '@nestjs/common';
import { Import } from './models/Import';
import { Inject, Injectable } from '@nestjs/common';
import { ImportModel } from './models/Import';
@Injectable()
export class ImportDeleteExpiredFiles {
constructor(
@Inject(ImportModel.name)
private readonly importModel: typeof ImportModel,
) {}
/**
* Delete expired files.
*/
async deleteExpiredFiles() {
const yesterday = moment().subtract(1, 'hour').format('YYYY-MM-DD HH:mm');
const expiredImports = await Import.query().where(
'createdAt',
'<',
yesterday
);
const expiredImports = await this.importModel
.query()
.where('createdAt', '<', yesterday);
await bluebird.map(
expiredImports,
async (expiredImport) => {
await deleteImportFile(expiredImport.filename);
},
{ concurrency: 10 }
{ concurrency: 10 },
);
const expiredImportsIds = expiredImports.map(
(expiredImport) => expiredImport.id
(expiredImport) => expiredImport.id,
);
if (expiredImportsIds.length > 0) {
await Import.query().whereIn('id', expiredImportsIds).delete();
await this.importModel.query().whereIn('id', expiredImportsIds).delete();
}
}
}

View File

@@ -111,6 +111,7 @@ export const convertFieldsToYupValidation = (fields: ResourceMetaFieldsMap) => {
} else if (field.fieldType === 'url') {
fieldSchema = fieldSchema.url();
} else if (field.fieldType === 'collection') {
// @ts-expect-error
const nestedFieldShema = convertFieldsToYupValidation(field.fields);
fieldSchema = Yup.array().label(field.name);

View File

@@ -1,5 +1,5 @@
import { IModelMetaField2 } from "@/interfaces/Model";
import { Import } from "./models/Import";
import { ImportModelShape } from "./models/Import";
export interface ImportMappingAttr {
from: string;
@@ -65,7 +65,7 @@ export interface ImportOperError {
}
export interface ImportableContext {
import: Import;
import: ImportModelShape;
rowIndex: number;
}
@@ -75,3 +75,9 @@ export const ImportDateFormats = [
'MM/dd/yy',
'dd/MMM/yyyy',
];
export interface IImportFileCommitedEventPayload {
importId: string;
meta: ImportFilePreviewPOJO;
}

View File

@@ -1,10 +1,10 @@
import { Model, ModelObject } from 'objection';
// import SystemModel from './SystemModel';
import { BaseModel } from '@/models/Model';
export class Import extends BaseModel {
resource: string;
tenantId: number;
export class ImportModel extends BaseModel {
resource!: string;
tenantId!: number;
filename!: string;
mapping!: string;
columns!: string;
params!: string;
@@ -85,4 +85,4 @@ export class Import extends BaseModel {
}
}
export type ImportShape = ModelObject<Import>;
export type ImportModelShape = ModelObject<ImportModel>;