Skip to content

Commit

Permalink
Bubble.io Destination Integration (#535)
Browse files Browse the repository at this point in the history
  • Loading branch information
chavda-bhavik authored Apr 2, 2024
2 parents d92a507 + 13b596a commit 33b47d6
Show file tree
Hide file tree
Showing 81 changed files with 8,443 additions and 7,002 deletions.
27 changes: 7 additions & 20 deletions apps/api/src/app/column/usecases/add-column/add-column.usecase.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Injectable } from '@nestjs/common';
import { createRecordFormat, updateCombinedFormat } from '@impler/shared';
import { ColumnRepository, CustomizationRepository, CustomizationEntity } from '@impler/dal';
import { ColumnRepository } from '@impler/dal';
import { AddColumnCommand } from '../../commands/add-column.command';
import { SaveSampleFile } from '@shared/usecases/save-sample-file/save-sample-file.usecase';
import { UpdateCustomization } from 'app/template/usecases';

@Injectable()
export class AddColumn {
constructor(
private saveSampleFile: SaveSampleFile,
private columnRepository: ColumnRepository,
private customizationRepository: CustomizationRepository
private updateCustomization: UpdateCustomization
) {}

async execute(command: AddColumnCommand, _templateId: string) {
Expand All @@ -22,25 +22,12 @@ export class AddColumn {
const variables = columns.map((columnItem) => columnItem.key);
variables.push(column.key);

await this.updateCustomization(_templateId, variables);
await this.updateCustomization.execute(_templateId, {
recordVariables: variables,
internal: true,
});
await this.saveSampleFile.execute([...columns, column], _templateId);

return column;
}

async updateCustomization(_templateId: string, variables: string[]) {
const customization = await this.customizationRepository.findOne({
_templateId,
});
const updateData: Partial<CustomizationEntity> = {
recordVariables: variables,
};
if (!customization.isRecordFormatUpdated) {
updateData.recordFormat = createRecordFormat(variables);
}
if (!customization.isCombinedFormatUpdated) {
updateData.combinedFormat = updateCombinedFormat(customization.combinedFormat, variables);
}
await this.customizationRepository.update({ _templateId }, updateData);
}
}
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
import { Injectable } from '@nestjs/common';
import { createRecordFormat, updateCombinedFormat } from '@impler/shared';
import { ColumnRepository, ColumnEntity, CustomizationRepository } from '@impler/dal';
import { ColumnRepository } from '@impler/dal';
import { DocumentNotFoundException } from '@shared/exceptions/document-not-found.exception';
import { SaveSampleFile } from '@shared/usecases/save-sample-file/save-sample-file.usecase';
import { UpdateCustomization } from 'app/template/usecases';

@Injectable()
export class DeleteColumn {
constructor(
private saveSampleFile: SaveSampleFile,
private columnRepository: ColumnRepository,
private customizationRepository: CustomizationRepository,
private saveSampleFile: SaveSampleFile
private updateCustomization: UpdateCustomization
) {}

async execute(_id: string) {
Expand All @@ -18,26 +18,14 @@ export class DeleteColumn {
throw new DocumentNotFoundException('Column', _id);
}
await this.columnRepository.delete({ _id });
await this.updateCustomization(column._templateId, column);

const columns = await this.columnRepository.find({ _templateId: column._templateId });
await this.updateCustomization.execute(column._templateId, {
recordVariables: columns.map((columnItem) => columnItem.key).filter((variable) => variable !== column.key),
internal: true,
});
await this.saveSampleFile.execute(columns, column._templateId);

return column;
}

async updateCustomization(_templateId: string, column: ColumnEntity) {
const customization = await this.customizationRepository.findOne({
_templateId,
});
customization.recordVariables = customization.recordVariables.filter((variable) => variable !== column.key);

if (!customization.isRecordFormatUpdated) {
customization.recordFormat = createRecordFormat(customization.recordVariables);
}
if (!customization.isCombinedFormatUpdated) {
customization.combinedFormat = updateCombinedFormat(customization.combinedFormat, customization.recordVariables);
}
await this.customizationRepository.update({ _templateId }, customization);
}
}
2 changes: 2 additions & 0 deletions apps/api/src/app/column/usecases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,14 @@ import { AddColumn } from './add-column/add-column.usecase';
import { UpdateColumn } from './update-column/update-column.usecase';
import { DeleteColumn } from './delete-column/delete-column.usecase';
import { SaveSampleFile } from '@shared/usecases/save-sample-file/save-sample-file.usecase';
import { UpdateCustomization } from 'app/template/usecases';

export const USE_CASES = [
AddColumn,
DeleteColumn,
UpdateColumn,
SaveSampleFile,
UpdateCustomization,
//
];

Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
import { Injectable } from '@nestjs/common';
import { createRecordFormat, updateCombinedFormat } from '@impler/shared';
import { UpdateColumnCommand } from '../../commands/update-column.command';
import { ColumnRepository } from '@impler/dal';
import { DocumentNotFoundException } from '@shared/exceptions/document-not-found.exception';
import { ColumnRepository, CustomizationRepository } from '@impler/dal';
import { SaveSampleFile } from '@shared/usecases/save-sample-file/save-sample-file.usecase';
import { UpdateCustomization } from 'app/template/usecases';
import { UpdateColumnCommand } from '../../commands/update-column.command';

@Injectable()
export class UpdateColumn {
constructor(
private saveSampleFile: SaveSampleFile,
private columnRepository: ColumnRepository,
private customizationRepository: CustomizationRepository
private updateCustomization: UpdateCustomization
) {}

async execute(command: UpdateColumnCommand, _id: string) {
Expand All @@ -36,23 +36,12 @@ export class UpdateColumn {

if (isKeyUpdated) {
const variables = columns.map((columnItem) => columnItem.key);
await this.updateCustomization(column._templateId, variables);
await this.updateCustomization.execute(column._templateId, {
recordVariables: variables,
internal: true,
});
}

return column;
}

async updateCustomization(_templateId: string, variables: string[]) {
const customization = await this.customizationRepository.findOne({
_templateId,
});
customization.recordVariables = variables;
if (!customization.isRecordFormatUpdated) {
customization.recordFormat = createRecordFormat(variables);
}
if (!customization.isCombinedFormatUpdated) {
customization.combinedFormat = updateCombinedFormat(customization.combinedFormat, variables);
}
await this.customizationRepository.update({ _templateId }, customization);
}
}
3 changes: 2 additions & 1 deletion apps/api/src/app/project/usecases/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { DeleteProject } from './delete-project/delete-project.usecase';
import { GetTemplates } from './get-templates/get-templates.usecase';
import { GetEnvironment } from './get-environment/get-environment.usecase';

import { CreateTemplate, UpdateTemplateColumns } from 'app/template/usecases';
import { CreateTemplate, UpdateTemplateColumns, UpdateCustomization } from 'app/template/usecases';
import { SaveSampleFile } from '@shared/usecases/save-sample-file/save-sample-file.usecase';

export const USE_CASES = [
Expand All @@ -21,6 +21,7 @@ export const USE_CASES = [
GetEnvironment,
CreateTemplate,
SaveSampleFile,
UpdateCustomization,
UpdateTemplateColumns,
//
];
Expand Down
Original file line number Diff line number Diff line change
@@ -1,16 +1,17 @@
import { Injectable } from '@nestjs/common';
import { TemplateEntity, UploadEntity, UploadRepository } from '@impler/dal';
import { QueuesEnum, UploadStatusEnum } from '@impler/shared';
import { QueueService } from '@shared/services/queue.service';
import { TemplateEntity, UploadEntity, UploadRepository } from '@impler/dal';

@Injectable()
export class StartProcess {
constructor(private uploadRepository: UploadRepository, private queueService: QueueService) {}

async execute(_uploadId: string): Promise<UploadEntity> {
let upload = await this.uploadRepository.getUploadWithTemplate(_uploadId, ['callbackUrl']);
// if template has callbackUrl then start sending data to the callbackUrl
if ((upload._templateId as unknown as TemplateEntity)?.callbackUrl) {
let upload = await this.uploadRepository.getUploadWithTemplate(_uploadId, ['destination']);
const destination = (upload._templateId as unknown as TemplateEntity)?.destination;
// if template destination has callbackUrl then start sending data to the callbackUrl
if (destination) {
upload = await this.uploadRepository.findOneAndUpdate(
{ _id: _uploadId },
{ status: UploadStatusEnum.PROCESSING }
Expand All @@ -21,7 +22,7 @@ export class StartProcess {
}
this.queueService.publishToQueue(QueuesEnum.END_IMPORT, {
uploadId: _uploadId,
processFile: !!(upload._templateId as unknown as TemplateEntity)?.callbackUrl,
destination: destination,
});

return upload;
Expand Down
3 changes: 3 additions & 0 deletions apps/api/src/app/shared/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ export const CONSTANTS = {
"fileName": "{{fileName}}",
"extra": "{{extra}}"
}`,
BUBBLEIO_PROPS: {
user: '{{extra.userId}}',
},
};

export const COOKIE_CONFIG: CookieOptions = {
Expand Down
89 changes: 89 additions & 0 deletions apps/api/src/app/shared/services/bubble-io.service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import axios, { AxiosError } from 'axios';
import { Injectable } from '@nestjs/common';
import { BubbleDestinationEntity } from '@impler/dal';
import { BubbleBaseService, ColumnTypesEnum, IColumn } from '@impler/shared';

interface IThingsResponse {
response: {
cursor: number;
count: number;
remaining: number;
results: Record<string, string | number>[];
};
}

@Injectable()
export class BubbleIoService extends BubbleBaseService {
async getDatatypeData(data: Omit<BubbleDestinationEntity, '_id' | '_templateId'>) {
try {
const url = this.createBubbleIoUrl(data);
const response = await axios.get<IThingsResponse>(url, {
headers: {
Authorization: `Bearer ${data.apiPrivateKey}`,
},
});
if (!response.data.response.results.length)
throw new Error('Datatype is empty. Please add at least one entry to the datatype');

return response.data.response.results;
} catch (error: unknown) {
this.throwRequestError(error as AxiosError);
}
}

createColumns(data: Record<string, string | number>[], _templateId: string) {
const bubbleIoDefaultColumns = ['Modified Date', 'Created Date', 'Created By', '_id', 'Slug'];
const columns: Partial<IColumn>[] = [];
const takenCols = new Set();
for (const record of data) {
for (const colKey of Object.keys(record)) {
if (!bubbleIoDefaultColumns.includes(colKey) && !takenCols.has(colKey)) {
const columnType = this.assumeValueType(record[colKey]);
columns.push({
name: colKey,
type: columnType.type,
key: colKey,
isRequired: false,
isUnique: false,
_templateId,
selectValues: columnType.selectValues,
dateFormats: columnType.dateFormats,
});
takenCols.add(colKey);
}
}
}

return columns;
}

assumeValueType(value: any) {
const obj = {
selectValues: [],
dateFormats: ['MM/DD/YYYY', 'MM/DD/YY', 'M/D/YY', 'MM/D/YY', 'M/DD/YY', 'M/D/YYYY', 'MM/D/YYYY', 'M/DD/YYYY'],
type: ColumnTypesEnum.STRING,
};
// Check if the value is an email
if (/^\w+([\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/.test(value)) {
obj.type = ColumnTypesEnum.EMAIL;
}

if (!isNaN(parseFloat(value)) && isFinite(value)) {
obj.type = ColumnTypesEnum.NUMBER;
}

// Check if the value is a date
if (/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/.test(value)) {
obj.type = ColumnTypesEnum.DATE;
}

// Check if the value is a boolean
if (value === true || value === false) {
obj.type = ColumnTypesEnum.SELECT;
obj.selectValues = [true, false];
}

// Default case: generic string
return obj;
}
}
4 changes: 2 additions & 2 deletions apps/api/src/app/shared/services/queue.service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Injectable, Logger } from '@nestjs/common';
import amqp from 'amqp-connection-manager';
import { EndImportData, ProcessFileData, PublishToQueueData, QueuesEnum } from '@impler/shared';
import { EndImportData, SendWebhookData, PublishToQueueData, QueuesEnum } from '@impler/shared';

@Injectable()
export class QueueService {
Expand Down Expand Up @@ -29,7 +29,7 @@ export class QueueService {
}

publishToQueue(queueName: QueuesEnum.END_IMPORT, data: EndImportData): void;
publishToQueue(queueName: QueuesEnum.PROCESS_FILE, data: ProcessFileData): void;
publishToQueue(queueName: QueuesEnum.SEND_WEBHOOK_DATA, data: SendWebhookData): void;
async publishToQueue(queueName: QueuesEnum, data: PublishToQueueData) {
if (this.connection.isConnected()) {
await this.chanelWrapper.sendToQueue(queueName, data, { durable: false });
Expand Down
4 changes: 4 additions & 0 deletions apps/api/src/app/shared/shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
EnvironmentRepository,
CustomizationRepository,
ValidatorRepository,
WebhookDestinationRepository,
BubbleDestinationRepository,
} from '@impler/dal';
import { FileNameService } from '@impler/shared';
import { S3StorageService, StorageService } from '@impler/shared/dist/services/storage';
Expand All @@ -31,6 +33,8 @@ const DAL_MODELS = [
EnvironmentRepository,
CustomizationRepository,
ValidatorRepository,
WebhookDestinationRepository,
BubbleDestinationRepository,
];
const FILE_SERVICES = [CSVFileService2, FileNameService, ExcelFileService];

Expand Down
Loading

0 comments on commit 33b47d6

Please sign in to comment.