Build stripe integration on backend side (#5246)
Adding stripe integration by making the server logic independent of the input fields: - query factories (remote server, foreign data wrapper, foreign table) to loop on fields and values without hardcoding the names of the fields - adding stripe input and type - add the logic to handle static schema. Simply creating a big object to store into the server Additional work: - rename username field to user. This is the input intended for postgres user mapping and we now need a matching by name --------- Co-authored-by: Thomas Trompette <thomast@twenty.com>
This commit is contained in:
@ -11,6 +11,7 @@ import {
|
||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
||||
import { DistantTableColumn } from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/types/distant-table-column';
|
||||
import { DistantTables } from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/types/distant-table';
|
||||
import { STRIPE_DISTANT_TABLES } from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/util/stripe-distant-tables.util';
|
||||
|
||||
@Injectable()
|
||||
export class DistantTableService {
|
||||
@ -27,7 +28,9 @@ export class DistantTableService {
|
||||
tableName: string,
|
||||
): Promise<DistantTableColumn[]> {
|
||||
if (!remoteServer.availableTables) {
|
||||
throw new Error('Remote server available tables are not defined');
|
||||
throw new BadRequestException(
|
||||
'Remote server available tables are not defined',
|
||||
);
|
||||
}
|
||||
|
||||
return remoteServer.availableTables[tableName];
|
||||
@ -47,6 +50,20 @@ export class DistantTableService {
|
||||
private async createAvailableTables(
|
||||
remoteServer: RemoteServerEntity<RemoteServerType>,
|
||||
workspaceId: string,
|
||||
): Promise<DistantTables> {
|
||||
if (remoteServer.schema) {
|
||||
return this.createAvailableTablesFromDynamicSchema(
|
||||
remoteServer,
|
||||
workspaceId,
|
||||
);
|
||||
}
|
||||
|
||||
return this.createAvailableTablesFromStaticSchema(remoteServer);
|
||||
}
|
||||
|
||||
private async createAvailableTablesFromDynamicSchema(
|
||||
remoteServer: RemoteServerEntity<RemoteServerType>,
|
||||
workspaceId: string,
|
||||
): Promise<DistantTables> {
|
||||
if (!remoteServer.schema) {
|
||||
throw new BadRequestException('Remote server schema is not defined');
|
||||
@ -99,4 +116,21 @@ export class DistantTableService {
|
||||
|
||||
return availableTables;
|
||||
}
|
||||
|
||||
private async createAvailableTablesFromStaticSchema(
|
||||
remoteServer: RemoteServerEntity<RemoteServerType>,
|
||||
): Promise<DistantTables> {
|
||||
switch (remoteServer.foreignDataWrapperType) {
|
||||
case RemoteServerType.STRIPE_FDW:
|
||||
this.remoteServerRepository.update(remoteServer.id, {
|
||||
availableTables: STRIPE_DISTANT_TABLES,
|
||||
});
|
||||
|
||||
return STRIPE_DISTANT_TABLES;
|
||||
default:
|
||||
throw new BadRequestException(
|
||||
`Type ${remoteServer.foreignDataWrapperType} does not have a static schema.`,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,91 @@
|
||||
import { DistantTables } from 'src/engine/metadata-modules/remote-server/remote-table/distant-table/types/distant-table';
|
||||
|
||||
export const STRIPE_DISTANT_TABLES: DistantTables = {
|
||||
balance_transactions: [
|
||||
{ columnName: 'id', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'amount', dataType: 'bigint', udtName: 'int8' },
|
||||
{ columnName: 'currency', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'description', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'fee', dataType: 'bigint', udtName: 'int8' },
|
||||
{ columnName: 'net', dataType: 'bigint', udtName: 'int8' },
|
||||
{ columnName: 'status', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'type', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'created', dataType: 'timestamp', udtName: 'timestamp' },
|
||||
],
|
||||
customers: [
|
||||
{ columnName: 'id', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'email', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'name', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'description', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'created', dataType: 'timestamp', udtName: 'timestamp' },
|
||||
],
|
||||
disputes: [
|
||||
{ columnName: 'id', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'amount', dataType: 'bigint', udtName: 'int8' },
|
||||
{ columnName: 'currency', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'charge', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'payment_intent', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'reason', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'status', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'created', dataType: 'timestamp', udtName: 'timestamp' },
|
||||
],
|
||||
files: [
|
||||
{ columnName: 'id', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'filename', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'purpose', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'title', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'size', dataType: 'bigint', udtName: 'int8' },
|
||||
{ columnName: 'type', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'url', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'created', dataType: 'timestamp', udtName: 'timestamp' },
|
||||
{ columnName: 'expires_at', dataType: 'timestamp', udtName: 'timestamp' },
|
||||
],
|
||||
file_links: [
|
||||
{ columnName: 'id', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'file', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'url', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'created', dataType: 'timestamp', udtName: 'timestamp' },
|
||||
{ columnName: 'expired', dataType: 'bool', udtName: 'boolean' },
|
||||
{ columnName: 'expires_at', dataType: 'timestamp', udtName: 'timestamp' },
|
||||
],
|
||||
mandates: [
|
||||
{ columnName: 'id', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'payment_method', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'status', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'type', dataType: 'text', udtName: 'text' },
|
||||
],
|
||||
payouts: [
|
||||
{ columnName: 'id', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'amount', dataType: 'bigint', udtName: 'int8' },
|
||||
{ columnName: 'currency', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'description', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'status', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'created', dataType: 'timestamp', udtName: 'timestamp' },
|
||||
],
|
||||
refunds: [
|
||||
{ columnName: 'id', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'amount', dataType: 'bigint', udtName: 'int8' },
|
||||
{ columnName: 'currency', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'charge', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'payment_intent', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'reason', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'status', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'created', dataType: 'timestamp', udtName: 'timestamp' },
|
||||
],
|
||||
topups: [
|
||||
{ columnName: 'id', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'amount', dataType: 'bigint', udtName: 'int8' },
|
||||
{ columnName: 'currency', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'description', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'status', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'created', dataType: 'timestamp', udtName: 'timestamp' },
|
||||
],
|
||||
transfers: [
|
||||
{ columnName: 'id', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'amount', dataType: 'bigint', udtName: 'int8' },
|
||||
{ columnName: 'currency', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'description', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'destination', dataType: 'text', udtName: 'text' },
|
||||
{ columnName: 'created', dataType: 'timestamp', udtName: 'timestamp' },
|
||||
],
|
||||
};
|
||||
@ -1,7 +1,7 @@
|
||||
import { ObjectType, Field, registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { IDField } from '@ptc-org/nestjs-query-graphql';
|
||||
import { IsEnum } from 'class-validator';
|
||||
import { IsEnum, IsOptional } from 'class-validator';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
|
||||
@ -27,6 +27,7 @@ export class RemoteTableDTO {
|
||||
@Field(() => RemoteTableStatus)
|
||||
status: RemoteTableStatus;
|
||||
|
||||
@Field(() => String)
|
||||
@IsOptional()
|
||||
@Field(() => String, { nullable: true })
|
||||
schema?: string;
|
||||
}
|
||||
|
||||
@ -26,6 +26,7 @@ import { WorkspaceMigrationService } from 'src/engine/metadata-modules/workspace
|
||||
import { WorkspaceMigrationRunnerService } from 'src/engine/workspace-manager/workspace-migration-runner/workspace-migration-runner.service';
|
||||
import { generateMigrationName } from 'src/engine/metadata-modules/workspace-migration/utils/generate-migration-name.util';
|
||||
import {
|
||||
ReferencedTable,
|
||||
WorkspaceMigrationForeignColumnDefinition,
|
||||
WorkspaceMigrationForeignTable,
|
||||
WorkspaceMigrationTableActionType,
|
||||
@ -338,6 +339,11 @@ export class RemoteTableService {
|
||||
remoteServer: RemoteServerEntity<RemoteServerType>,
|
||||
distantTableColumns: DistantTableColumn[],
|
||||
) {
|
||||
const referencedTable: ReferencedTable = this.buildReferencedTable(
|
||||
remoteServer,
|
||||
remoteTableInput,
|
||||
);
|
||||
|
||||
const workspaceMigration =
|
||||
await this.workspaceMigrationService.createCustomMigration(
|
||||
generateMigrationName(`create-foreign-table-${localTableName}`),
|
||||
@ -355,8 +361,7 @@ export class RemoteTableService {
|
||||
distantColumnName: column.columnName,
|
||||
}) satisfies WorkspaceMigrationForeignColumnDefinition,
|
||||
),
|
||||
referencedTableName: remoteTableInput.name,
|
||||
referencedTableSchema: remoteServer.schema,
|
||||
referencedTable,
|
||||
foreignDataWrapperId: remoteServer.foreignDataWrapperId,
|
||||
} satisfies WorkspaceMigrationForeignTable,
|
||||
},
|
||||
@ -430,4 +435,21 @@ export class RemoteTableService {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private buildReferencedTable(
|
||||
remoteServer: RemoteServerEntity<RemoteServerType>,
|
||||
remoteTableInput: RemoteTableInput,
|
||||
): ReferencedTable {
|
||||
switch (remoteServer.foreignDataWrapperType) {
|
||||
case RemoteServerType.POSTGRES_FDW:
|
||||
return {
|
||||
table_name: remoteTableInput.name,
|
||||
schema_name: remoteServer.schema,
|
||||
};
|
||||
case RemoteServerType.STRIPE_FDW:
|
||||
return { object: remoteTableInput.name };
|
||||
default:
|
||||
throw new BadRequestException('Foreign data wrapper not supported');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -7,6 +7,9 @@ export const mapUdtNameToFieldType = (udtName: string): FieldMetadataType => {
|
||||
case 'uuid':
|
||||
return FieldMetadataType.UUID;
|
||||
case 'varchar':
|
||||
case 'text':
|
||||
case 'bigint':
|
||||
case 'int8':
|
||||
return FieldMetadataType.TEXT;
|
||||
case 'bool':
|
||||
return FieldMetadataType.BOOLEAN;
|
||||
|
||||
Reference in New Issue
Block a user