mirror of
https://github.com/bigcapitalhq/bigcapital.git
synced 2026-02-15 12:20:31 +00:00
refactor(nestjs): import module
This commit is contained in:
@@ -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 {}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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>;
|
||||
|
||||
Reference in New Issue
Block a user