From 95002f5f9a012f1f3ee43a192c563b550c75e1db Mon Sep 17 00:00:00 2001 From: bosiraphael <71827178+bosiraphael@users.noreply.github.com> Date: Tue, 12 Dec 2023 11:09:20 +0100 Subject: [PATCH] Migrate connected account model (#2944) * migrate-connectedAccount-model * update accountOwerId * prevent user from connecting multiple times with the same account * Delete .yarn/releases/yarn-1.22.21.cjs * Delete .yarnrc * modified according to comments * updates --- .../google-gmail-auth.controller.ts | 9 +- .../core/auth/dto/save-connected-account.ts | 9 +- .../auth/services/google-gmail.service.ts | 24 ++++- .../standard-objects/connected-account.ts | 87 ------------------- .../connected-account.object-metadata.ts | 59 +++++++++++++ .../standard-objects/index.ts | 2 + .../workspace-member.object-metadata.ts | 15 ++++ .../utils/metadata.parser.ts | 4 + 8 files changed, 113 insertions(+), 96 deletions(-) delete mode 100644 packages/twenty-server/src/workspace/workspace-manager/standard-objects/connected-account.ts create mode 100644 packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/connected-account.object-metadata.ts diff --git a/packages/twenty-server/src/core/auth/controllers/google-gmail-auth.controller.ts b/packages/twenty-server/src/core/auth/controllers/google-gmail-auth.controller.ts index 222bfdcb4..56ce61161 100644 --- a/packages/twenty-server/src/core/auth/controllers/google-gmail-auth.controller.ts +++ b/packages/twenty-server/src/core/auth/controllers/google-gmail-auth.controller.ts @@ -28,21 +28,22 @@ export class GoogleGmailAuthController { @Req() req: GoogleGmailRequest, @Res() res: Response, ) { - const { user: gmailUser } = req; + const { user } = req; - const { accessToken, refreshToken, transientToken } = gmailUser; + const { email, accessToken, refreshToken, transientToken } = user; const { workspaceMemberId, workspaceId } = await this.tokenService.verifyTransientToken(transientToken); this.googleGmailService.saveConnectedAccount({ + email, workspaceMemberId: workspaceMemberId, workspaceId: workspaceId, - type: 'gmail', + provider: 'gmail', accessToken, refreshToken, }); - return res.redirect('http://localhost:3001'); + return res.redirect('http://localhost:3001/settings/accounts'); } } diff --git a/packages/twenty-server/src/core/auth/dto/save-connected-account.ts b/packages/twenty-server/src/core/auth/dto/save-connected-account.ts index 10a91ef22..32b53313f 100644 --- a/packages/twenty-server/src/core/auth/dto/save-connected-account.ts +++ b/packages/twenty-server/src/core/auth/dto/save-connected-account.ts @@ -1,9 +1,14 @@ import { ArgsType, Field } from '@nestjs/graphql'; -import { IsNotEmpty, IsString } from 'class-validator'; +import { IsEmail, IsNotEmpty, IsString } from 'class-validator'; @ArgsType() export class SaveConnectedAccountInput { + @Field(() => String) + @IsNotEmpty() + @IsEmail() + email: string; + @Field(() => String) @IsNotEmpty() @IsString() @@ -17,7 +22,7 @@ export class SaveConnectedAccountInput { @Field(() => String) @IsNotEmpty() @IsString() - type: string; + provider: string; @Field(() => String) @IsNotEmpty() diff --git a/packages/twenty-server/src/core/auth/services/google-gmail.service.ts b/packages/twenty-server/src/core/auth/services/google-gmail.service.ts index 896555275..3fd3d5265 100644 --- a/packages/twenty-server/src/core/auth/services/google-gmail.service.ts +++ b/packages/twenty-server/src/core/auth/services/google-gmail.service.ts @@ -14,8 +14,14 @@ export class GoogleGmailService { async saveConnectedAccount( saveConnectedAccountInput: SaveConnectedAccountInput, ) { - const { workspaceId, type, accessToken, refreshToken } = - saveConnectedAccountInput; + const { + email, + workspaceId, + provider, + accessToken, + refreshToken, + workspaceMemberId, + } = saveConnectedAccountInput; const dataSourceMetadata = await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( @@ -26,8 +32,20 @@ export class GoogleGmailService { dataSourceMetadata, ); + const connectedAccount = await workspaceDataSource?.query( + `SELECT * FROM ${dataSourceMetadata.schema}."connectedAccount" WHERE "email" = $1 AND "provider" = $2 AND "accountOwnerId" = $3`, + [email, provider, workspaceMemberId], + ); + + if (connectedAccount.length > 0) { + console.log('This account is already connected to your workspace.'); + + return; + } + await workspaceDataSource?.query( - `INSERT INTO ${dataSourceMetadata.schema}."connectedAccount" ("type", "accessToken", "refreshToken") VALUES ('${type}', '${accessToken}', '${refreshToken}')`, + `INSERT INTO ${dataSourceMetadata.schema}."connectedAccount" ("email", "provider", "accessToken", "refreshToken", "accountOwnerId") VALUES ($1, $2, $3, $4, $5)`, + [email, provider, accessToken, refreshToken, workspaceMemberId], ); return; diff --git a/packages/twenty-server/src/workspace/workspace-manager/standard-objects/connected-account.ts b/packages/twenty-server/src/workspace/workspace-manager/standard-objects/connected-account.ts deleted file mode 100644 index a4a38d5fe..000000000 --- a/packages/twenty-server/src/workspace/workspace-manager/standard-objects/connected-account.ts +++ /dev/null @@ -1,87 +0,0 @@ -import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; - -const connectedAccountMetadata = { - nameSingular: 'connectedAccount', - namePlural: 'connectedAccounts', - labelSingular: 'Connected Account', - labelPlural: 'Connected Accounts', - targetTableName: 'connectedAccount', - description: 'A connected account', - icon: 'IconBuildingSkyscraper', - isActive: true, - isSystem: false, - fields: [ - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'type', - label: 'type', - targetColumnMap: { - value: 'type', - }, - description: 'The account type', - icon: 'IconSettings', - isNullable: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'accessToken', - label: 'accessToken', - targetColumnMap: { - value: 'accessToken', - }, - description: 'Messaging provider access token', - icon: 'IconKey', - isNullable: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, - name: 'refreshToken', - label: 'refreshToken', - targetColumnMap: { - value: 'refreshToken', - }, - description: 'Messaging provider refresh token', - icon: 'IconKey', - isNullable: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.TEXT, // Should be an array of strings - name: 'externalScopes', - label: 'externalScopes', - targetColumnMap: { - value: 'externalScopes', - }, - description: 'External scopes', - icon: 'IconCircle', - isNullable: false, - defaultValue: { value: '' }, - }, - { - isCustom: false, - isActive: true, - type: FieldMetadataType.BOOLEAN, // Should be an array of strings - name: 'hasEmailScope', - label: 'hasEmailScope', - targetColumnMap: { - value: 'hasEmailScope', - }, - description: 'Has email scope', - icon: 'IconMail', - isNullable: false, - defaultValue: { value: '' }, - }, - ], -}; - -export default connectedAccountMetadata; diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/connected-account.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/connected-account.object-metadata.ts new file mode 100644 index 000000000..3e22239db --- /dev/null +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/connected-account.object-metadata.ts @@ -0,0 +1,59 @@ +import { FieldMetadataType } from 'src/metadata/field-metadata/field-metadata.entity'; +import { + ObjectMetadata, + IsSystem, + FieldMetadata, +} from 'src/workspace/workspace-sync-metadata/decorators/metadata.decorator'; +import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; +import { WorkspaceMemberObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata'; + +@ObjectMetadata({ + namePlural: 'connectedAccounts', + labelSingular: 'Connected Account', + labelPlural: 'Connected Accounts', + description: 'A connected account', + icon: 'IconAt', +}) +@IsSystem() +export class ConnectedAccountObjectMetadata extends BaseObjectMetadata { + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'email', + description: 'The account email', + icon: 'IconMail', + }) + email: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'provider', + description: 'The account provider', + icon: 'IconSettings', + }) + provider: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'accessToken', + description: 'Messaging provider access token', + icon: 'IconKey', + }) + accessToken: string; + + @FieldMetadata({ + type: FieldMetadataType.TEXT, + label: 'refreshToken', + description: 'Messaging provider refresh token', + icon: 'IconKey', + }) + refreshToken: string; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Account Owner', + description: 'Account Owner', + icon: 'IconUserCircle', + joinColumn: 'accountOwnerId', + }) + accountOwner: WorkspaceMemberObjectMetadata; +} diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts index 944afae8e..56ab4f5b3 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/index.ts @@ -4,6 +4,7 @@ import { ApiKeyObjectMetadata } from 'src/workspace/workspace-sync-metadata/stan import { AttachmentObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/attachment.object-metadata'; import { CommentObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/comment.object-metadata'; import { CompanyObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/company.object-metadata'; +import { ConnectedAccountObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/connected-account.object-metadata'; import { FavoriteObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/favorite.object-metadata'; import { OpportunityObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/opportunity.object-metadata'; import { PersonObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata'; @@ -22,6 +23,7 @@ export const standardObjectMetadata = [ AttachmentObjectMetadata, CommentObjectMetadata, CompanyObjectMetadata, + ConnectedAccountObjectMetadata, FavoriteObjectMetadata, OpportunityObjectMetadata, PersonObjectMetadata, diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata.ts index 577c9b5e7..1f9a64387 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/standard-objects/workspace-member.object-metadata.ts @@ -13,6 +13,7 @@ import { AttachmentObjectMetadata } from 'src/workspace/workspace-sync-metadata/ import { BaseObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/base.object-metadata'; import { CommentObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/comment.object-metadata'; import { CompanyObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/company.object-metadata'; +import { ConnectedAccountObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/connected-account.object-metadata'; import { FavoriteObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/favorite.object-metadata'; @ObjectMetadata({ @@ -149,4 +150,18 @@ export class WorkspaceMemberObjectMetadata extends BaseObjectMetadata { }) @IsNullable() authoredComments: CommentObjectMetadata[]; + + @FieldMetadata({ + type: FieldMetadataType.RELATION, + label: 'Connected accounts', + description: 'Connected accounts', + icon: 'IconAt', + }) + @RelationMetadata({ + type: RelationMetadataType.ONE_TO_MANY, + objectName: 'connectedAccount', + inverseSideFieldName: 'accountOwner', + }) + @IsNullable() + connectedAccounts: ConnectedAccountObjectMetadata[]; } diff --git a/packages/twenty-server/src/workspace/workspace-sync-metadata/utils/metadata.parser.ts b/packages/twenty-server/src/workspace/workspace-sync-metadata/utils/metadata.parser.ts index 6cd1c14c6..4684c2e27 100644 --- a/packages/twenty-server/src/workspace/workspace-sync-metadata/utils/metadata.parser.ts +++ b/packages/twenty-server/src/workspace/workspace-sync-metadata/utils/metadata.parser.ts @@ -56,6 +56,7 @@ export class MetadataParser { return relationMetadata.map((relation) => { const fromObjectMetadata = objectMetadataFromDB[relation.fromObjectNameSingular]; + assert( fromObjectMetadata, `Object ${relation.fromObjectNameSingular} not found in DB @@ -64,6 +65,7 @@ export class MetadataParser { const toObjectMetadata = objectMetadataFromDB[relation.toObjectNameSingular]; + assert( toObjectMetadata, `Object ${relation.toObjectNameSingular} not found in DB @@ -72,6 +74,7 @@ export class MetadataParser { const fromFieldMetadata = fromObjectMetadata?.fields[relation.fromFieldMetadataName]; + assert( fromFieldMetadata, `Field ${relation.fromFieldMetadataName} not found in object ${relation.fromObjectNameSingular} @@ -80,6 +83,7 @@ export class MetadataParser { const toFieldMetadata = toObjectMetadata?.fields[relation.toFieldMetadataName]; + assert( toFieldMetadata, `Field ${relation.toFieldMetadataName} not found in object ${relation.toObjectNameSingular}