Skip to content

Commit

Permalink
Feat/overriding schema (#365)
Browse files Browse the repository at this point in the history
  • Loading branch information
chavda-bhavik committed Sep 13, 2023
2 parents 7b98fbb + 4468b1e commit 3ca6c37
Show file tree
Hide file tree
Showing 23 changed files with 195 additions and 175 deletions.
10 changes: 5 additions & 5 deletions apps/api/src/app/column/dtos/column-response.dto.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ export class ColumnResponseDto {
description: 'Alternative possible keys of the column',
type: Array<string>,
})
alternateKeys: string[];
alternateKeys?: string[];

@ApiPropertyOptional({
description: 'While true, it Indicates column value should exists in data',
Expand All @@ -38,20 +38,20 @@ export class ColumnResponseDto {
@ApiPropertyOptional({
description: 'Regex if type is Regex',
})
regex: string;
regex?: string;

@ApiPropertyOptional({
description: 'Description of Regex',
})
regexDescription: string;
regexDescription?: string;

@ApiPropertyOptional({
description: 'List of possible values for column if type is Select',
})
selectValues: string[];
selectValues?: string[];

@ApiProperty({
description: 'Sequence of column',
})
sequence: number;
sequence?: number;
}
13 changes: 9 additions & 4 deletions apps/api/src/app/mapping/dtos/update-columns.dto.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,18 @@
import { ApiProperty } from '@nestjs/swagger';
import { IsDefined, IsString, IsMongoId, IsOptional } from 'class-validator';
import { IsDefined, IsString, IsOptional } from 'class-validator';

export class UpdateMappingDto {
@ApiProperty({
description: 'Id of the column',
description: 'key of the column',
})
@IsMongoId()
@IsDefined()
_columnId: string;
key: string;

@ApiProperty({
description: 'Name of the column',
})
@IsOptional()
name: string;

@ApiProperty({
description: 'Selected Heading for column',
Expand Down
55 changes: 29 additions & 26 deletions apps/api/src/app/mapping/mapping.controller.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
import { Body, Controller, Get, Param, ParseArrayPipe, Post, UseGuards } from '@nestjs/common';
import { ApiTags, ApiSecurity, ApiOperation, ApiBody } from '@nestjs/swagger';
import { ACCESS_KEY_NAME, Defaults, UploadStatusEnum } from '@impler/shared';
import { MappingEntity } from '@impler/dal';
import { ACCESS_KEY_NAME, Defaults, ITemplateSchemaItem, UploadStatusEnum } from '@impler/shared';

import { JwtAuthGuard } from '@shared/framework/auth.gaurd';
import { validateNotFound } from '@shared/helpers/common.helper';
Expand All @@ -11,14 +10,15 @@ import { ValidateMongoId } from '@shared/validations/valid-mongo-id.validation';
import { GetUploadCommand } from '@shared/usecases/get-upload/get-upload.command';

import { UpdateMappingDto } from './dtos/update-columns.dto';
import { DoMapping } from './usecases/do-mapping/do-mapping.usecase';
import { GetMappings } from './usecases/get-mappings/get-mappings.usecase';
import { DoMappingCommand } from './usecases/do-mapping/do-mapping.command';
import { UpdateMappings } from './usecases/update-mappings/update-mappings.usecase';
import { FinalizeUpload } from './usecases/finalize-upload/finalize-upload.usecase';
import { ValidateMapping } from './usecases/validate-mapping/validate-mapping.usecase';
import { UpdateMappingCommand } from './usecases/update-mappings/update-mappings.command';
import { ReanameFileHeadings } from './usecases/rename-file-headings/rename-file-headings.usecase';
import {
DoMapping,
GetMappings,
FinalizeUpload,
ReanameFileHeadings,
DoMappingCommand,
UpdateMappings,
ValidateMapping,
} from './usecases';

@Controller('/mapping')
@ApiTags('Mappings')
Expand All @@ -29,8 +29,8 @@ export class MappingController {
private getUpload: GetUpload,
private doMapping: DoMapping,
private getMappings: GetMappings,
private updateMappings: UpdateMappings,
private finalizeUpload: FinalizeUpload,
private updateMappings: UpdateMappings,
private validateMapping: ValidateMapping,
private renameFileHeadings: ReanameFileHeadings
) {}
Expand All @@ -39,7 +39,9 @@ export class MappingController {
@ApiOperation({
summary: 'Get mapping information for uploaded file',
})
async getMappingInformation(@Param('uploadId', ValidateMongoId) uploadId: string): Promise<Partial<MappingEntity>[]> {
async getMappingInformation(
@Param('uploadId', ValidateMongoId) uploadId: string
): Promise<Partial<ITemplateSchemaItem>[]> {
const uploadInformation = await this.getUpload.execute(
GetUploadCommand.create({
uploadId,
Expand Down Expand Up @@ -81,7 +83,7 @@ export class MappingController {
const uploadInformation = await this.getUpload.execute(
GetUploadCommand.create({
uploadId: _uploadId,
select: 'status',
select: 'status customSchema headings',
})
);

Expand All @@ -92,22 +94,23 @@ export class MappingController {
validateUploadStatus(uploadInformation.status as UploadStatusEnum, [UploadStatusEnum.MAPPING]);

// validate mapping data
await this.validateMapping.execute(body, _uploadId);
this.validateMapping.execute(
body,
_uploadId,
JSON.parse(uploadInformation.customSchema),
uploadInformation.headings
);

// save mapping
if (Array.isArray(body) && body.length > Defaults.ZERO) {
await this.updateMappings.execute(
body
.filter((columnDataItem) => !!columnDataItem.columnHeading)
.map((updateColumnData) =>
UpdateMappingCommand.create({
_columnId: updateColumnData._columnId,
_uploadId,
columnHeading: updateColumnData.columnHeading,
})
),
_uploadId
);
const templateSchema = await this.getMappings.execute(_uploadId);
templateSchema.forEach((schemaItem: ITemplateSchemaItem, index: number) => {
const foundColumn = body.find((item) => item.key === schemaItem.key);
if (foundColumn) {
templateSchema[index].columnHeading = foundColumn.columnHeading;
}
});
await this.updateMappings.execute(templateSchema, _uploadId);
}

const { headings } = await this.renameFileHeadings.execute(_uploadId);
Expand Down
33 changes: 12 additions & 21 deletions apps/api/src/app/mapping/usecases/do-mapping/do-mapping.usecase.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,32 @@
import { Injectable } from '@nestjs/common';
import { Defaults, UploadStatusEnum } from '@impler/shared';
import { ColumnEntity, MappingEntity, MappingRepository, UploadRepository } from '@impler/dal';
import { UploadRepository } from '@impler/dal';
import { Defaults, ITemplateSchemaItem, UploadStatusEnum } from '@impler/shared';
import { DoMappingCommand } from './do-mapping.command';

@Injectable()
export class DoMapping {
constructor(private mappingRepository: MappingRepository, private uploadRepository: UploadRepository) {}
constructor(private uploadRepository: UploadRepository) {}

async execute(command: DoMappingCommand) {
const uploadInfo = await this.uploadRepository.findById(command._uploadId, 'customSchema');
const mapping = this.buildMapping(JSON.parse(uploadInfo.customSchema), command.headings, command._uploadId);
const createdHeadings = await this.mappingRepository.createMany(mapping);
await this.uploadRepository.update({ _id: command._uploadId }, { status: UploadStatusEnum.MAPPING });
const updatedTemplateSchema = this.buildMapping(JSON.parse(uploadInfo.customSchema), command.headings);
await this.uploadRepository.update(
{ _id: command._uploadId },
{ status: UploadStatusEnum.MAPPING, customSchema: JSON.stringify(updatedTemplateSchema) }
);

return createdHeadings;
return updatedTemplateSchema;
}

private buildMapping(columns: ColumnEntity[], headings: string[], _uploadId: string) {
const mappings: MappingEntity[] = [];
private buildMapping(columns: ITemplateSchemaItem[], headings: string[]): ITemplateSchemaItem[] {
for (const column of columns) {
const heading = this.findBestMatchingHeading(headings, column.key, column.alternateKeys);
if (heading) {
mappings.push(this.buildMappingItem(column._id, _uploadId, heading));
} else {
mappings.push(this.buildMappingItem(column._id, _uploadId));
column.columnHeading = heading;
}
}

return mappings;
return columns;
}

private findBestMatchingHeading(headings: string[], key: string, alternateKeys: string[]): string | null {
Expand All @@ -50,12 +49,4 @@ export class DoMapping {
private checkStringEqual(a: string, b: string): boolean {
return String(a).localeCompare(String(b), undefined, { sensitivity: 'accent' }) === Defaults.ZERO;
}

private buildMappingItem(columnId: string, uploadId: string, heading?: string): MappingEntity {
return {
_columnId: columnId,
_uploadId: uploadId,
columnHeading: heading || null,
};
}
}
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import { Injectable } from '@nestjs/common';
import { MappingRepository } from '@impler/dal';
import { UploadRepository } from '@impler/dal';
import { ITemplateSchemaItem } from '@impler/shared';

@Injectable()
export class GetMappings {
constructor(private mappingRepository: MappingRepository) {}
constructor(private uploadRepository: UploadRepository) {}

async execute(_uploadId: string) {
return await this.mappingRepository.getMappingInfo(_uploadId);
async execute(_uploadId: string): Promise<ITemplateSchemaItem[]> {
const uploadInfo = await this.uploadRepository.findById(_uploadId, 'customSchema');

return JSON.parse(uploadInfo.customSchema);
}
}
9 changes: 8 additions & 1 deletion apps/api/src/app/mapping/usecases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,12 @@ import { DoMapping } from './do-mapping/do-mapping.usecase';
import { GetMappings } from './get-mappings/get-mappings.usecase';
import { UpdateMappings } from './update-mappings/update-mappings.usecase';
import { FinalizeUpload } from './finalize-upload/finalize-upload.usecase';
import { GetUpload } from '@shared/usecases/get-upload/get-upload.usecase';
import { ValidateMapping } from './validate-mapping/validate-mapping.usecase';
import { ReanameFileHeadings } from './rename-file-headings/rename-file-headings.usecase';
import { GetUpload } from '@shared/usecases/get-upload/get-upload.usecase';

import { DoMappingCommand } from './do-mapping/do-mapping.command';
import { ValidateMappingCommand } from './validate-mapping/validate-mapping.command';

export const USE_CASES = [
DoMapping,
Expand All @@ -16,3 +19,7 @@ export const USE_CASES = [
GetUpload,
//
];

export { DoMapping, ValidateMapping, GetMappings, UpdateMappings, FinalizeUpload, ReanameFileHeadings, GetUpload };

export { DoMappingCommand, ValidateMappingCommand };
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
import { Injectable } from '@nestjs/common';
import { MappingRepository, UploadRepository } from '@impler/dal';
import { UploadRepository } from '@impler/dal';
import { ITemplateSchemaItem } from '@impler/shared';

@Injectable()
export class ReanameFileHeadings {
constructor(private uploadRepository: UploadRepository, private mappingRepository: MappingRepository) {}
constructor(private uploadRepository: UploadRepository) {}

async execute(_uploadId: string): Promise<{ headings: string[] }> {
return new Promise(async (resolve, reject) => {
try {
const uploadInfo = await this.uploadRepository.findById(_uploadId, 'headings _uploadedFileId');
const mappingInfo = await this.mappingRepository.getMappingWithColumnInfo(_uploadId);
const uploadInfo = await this.uploadRepository.findById(_uploadId, 'headings _uploadedFileId customSchema');
const templateColumnItems = JSON.parse(uploadInfo.customSchema) as ITemplateSchemaItem[];

const newHeadings = uploadInfo.headings.reduce((headingsArr, heading) => {
const foundMapping = mappingInfo.find((mapping) => mapping.columnHeading === heading);
if (foundMapping) headingsArr.push(foundMapping.column.key);
const foundMapping = templateColumnItems.find((mapping) => mapping.columnHeading === heading);
if (foundMapping) headingsArr.push(foundMapping.key);
else headingsArr.push(heading);

return headingsArr;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,17 @@
import { Injectable } from '@nestjs/common';
import { MappingRepository } from '@impler/dal';
import { UpdateMappingCommand } from './update-mappings.command';
import { UploadRepository } from '@impler/dal';
import { ITemplateSchemaItem } from '@impler/shared';

@Injectable()
export class UpdateMappings {
constructor(private mappingRepository: MappingRepository) {}
constructor(private uploadRepository: UploadRepository) {}

async execute(command: UpdateMappingCommand[], _uploadId: string) {
await this.mappingRepository.deleteMany({ _uploadId });

return this.mappingRepository.createMany(command);
async execute(templateSchema: ITemplateSchemaItem[], _uploadId: string) {
await this.uploadRepository.update(
{
_id: _uploadId,
},
{ customSchema: JSON.stringify(templateSchema) }
);
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
import { IsString, IsDefined, IsMongoId } from 'class-validator';
import { IsString, IsDefined } from 'class-validator';
import { BaseCommand } from '@shared/commands/base.command';

export class ValidateMappingCommand extends BaseCommand {
@IsDefined()
@IsMongoId()
_columnId: string;
@IsString()
key: string;

@IsDefined()
@IsString()
name: string;

@IsDefined()
@IsString()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,39 +1,24 @@
import { BadRequestException, Injectable } from '@nestjs/common';
import { ColumnRepository, UploadRepository } from '@impler/dal';
import { ValidateMappingCommand } from './validate-mapping.command';

import { ITemplateSchemaItem } from '@impler/shared';

@Injectable()
export class ValidateMapping {
constructor(private columnRepository: ColumnRepository, private uploadRepository: UploadRepository) {}

async execute(command: ValidateMappingCommand[], _uploadId: string) {
execute(command: ValidateMappingCommand[], _uploadId: string, columns: ITemplateSchemaItem[], headings: string[]) {
// check if mapping data contains duplicates
const mappings = [...new Map(command.map((item) => [item._columnId, item])).values()];
const mappings = [...new Map(command.map((item) => [item.key, item])).values()];
if (mappings.length !== command.length) throw new BadRequestException('Mapping data contains duplicates');

// Check if mapping data _columnIds are valid
const columnIds = command.map((mapping) => ({
_id: mapping._columnId,
}));
const columnEntities = await this.columnRepository.find(
{
$or: [...columnIds],
},
'name _id isRequired'
);
if (columnEntities.length !== command.length)
throw new BadRequestException(`Mapping data contains invalid _columnId(s)`);

// check if mapping data headings are valid
const columnHeadings = command.map((mapping) => mapping.columnHeading).filter((heading) => !!heading);
const uploadInfo = await this.uploadRepository.findById(_uploadId, 'headings');
const isAllHeadingsAreValid = columnHeadings.every((heading) => uploadInfo.headings.includes(heading));
const isAllHeadingsAreValid = columnHeadings.every((heading) => headings.includes(heading));
if (!isAllHeadingsAreValid) throw new BadRequestException(`Mapping data contains invalid columnHeading values`);

// check if mapping data has required columns
const columnEntityIds = command.filter((item) => !!item.columnHeading).map((item) => item._columnId);
for (const columnEntity of columnEntities) {
if (columnEntity.isRequired && !columnEntityIds.includes(columnEntity._id)) {
const providedColumnKeys = command.filter((item) => !!item.columnHeading).map((item) => item.key);
for (const columnEntity of columns) {
if (columnEntity.isRequired && !providedColumnKeys.includes(columnEntity.key)) {
throw new BadRequestException(`${columnEntity.name} is required`);
}
}
Expand Down
Loading

0 comments on commit 3ca6c37

Please sign in to comment.