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
This commit is contained in:
bosiraphael
2023-12-12 11:09:20 +01:00
committed by GitHub
parent 3f422f9640
commit 95002f5f9a
8 changed files with 113 additions and 96 deletions

View File

@ -28,21 +28,22 @@ export class GoogleGmailAuthController {
@Req() req: GoogleGmailRequest, @Req() req: GoogleGmailRequest,
@Res() res: Response, @Res() res: Response,
) { ) {
const { user: gmailUser } = req; const { user } = req;
const { accessToken, refreshToken, transientToken } = gmailUser; const { email, accessToken, refreshToken, transientToken } = user;
const { workspaceMemberId, workspaceId } = const { workspaceMemberId, workspaceId } =
await this.tokenService.verifyTransientToken(transientToken); await this.tokenService.verifyTransientToken(transientToken);
this.googleGmailService.saveConnectedAccount({ this.googleGmailService.saveConnectedAccount({
email,
workspaceMemberId: workspaceMemberId, workspaceMemberId: workspaceMemberId,
workspaceId: workspaceId, workspaceId: workspaceId,
type: 'gmail', provider: 'gmail',
accessToken, accessToken,
refreshToken, refreshToken,
}); });
return res.redirect('http://localhost:3001'); return res.redirect('http://localhost:3001/settings/accounts');
} }
} }

View File

@ -1,9 +1,14 @@
import { ArgsType, Field } from '@nestjs/graphql'; import { ArgsType, Field } from '@nestjs/graphql';
import { IsNotEmpty, IsString } from 'class-validator'; import { IsEmail, IsNotEmpty, IsString } from 'class-validator';
@ArgsType() @ArgsType()
export class SaveConnectedAccountInput { export class SaveConnectedAccountInput {
@Field(() => String)
@IsNotEmpty()
@IsEmail()
email: string;
@Field(() => String) @Field(() => String)
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
@ -17,7 +22,7 @@ export class SaveConnectedAccountInput {
@Field(() => String) @Field(() => String)
@IsNotEmpty() @IsNotEmpty()
@IsString() @IsString()
type: string; provider: string;
@Field(() => String) @Field(() => String)
@IsNotEmpty() @IsNotEmpty()

View File

@ -14,8 +14,14 @@ export class GoogleGmailService {
async saveConnectedAccount( async saveConnectedAccount(
saveConnectedAccountInput: SaveConnectedAccountInput, saveConnectedAccountInput: SaveConnectedAccountInput,
) { ) {
const { workspaceId, type, accessToken, refreshToken } = const {
saveConnectedAccountInput; email,
workspaceId,
provider,
accessToken,
refreshToken,
workspaceMemberId,
} = saveConnectedAccountInput;
const dataSourceMetadata = const dataSourceMetadata =
await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail( await this.dataSourceService.getLastDataSourceMetadataFromWorkspaceIdOrFail(
@ -26,8 +32,20 @@ export class GoogleGmailService {
dataSourceMetadata, 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( 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; return;

View File

@ -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;

View File

@ -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;
}

View File

@ -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 { 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 { 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 { 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 { 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 { 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'; import { PersonObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/person.object-metadata';
@ -22,6 +23,7 @@ export const standardObjectMetadata = [
AttachmentObjectMetadata, AttachmentObjectMetadata,
CommentObjectMetadata, CommentObjectMetadata,
CompanyObjectMetadata, CompanyObjectMetadata,
ConnectedAccountObjectMetadata,
FavoriteObjectMetadata, FavoriteObjectMetadata,
OpportunityObjectMetadata, OpportunityObjectMetadata,
PersonObjectMetadata, PersonObjectMetadata,

View File

@ -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 { 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 { 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 { 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 { FavoriteObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/favorite.object-metadata';
@ObjectMetadata({ @ObjectMetadata({
@ -149,4 +150,18 @@ export class WorkspaceMemberObjectMetadata extends BaseObjectMetadata {
}) })
@IsNullable() @IsNullable()
authoredComments: CommentObjectMetadata[]; 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[];
} }

View File

@ -56,6 +56,7 @@ export class MetadataParser {
return relationMetadata.map((relation) => { return relationMetadata.map((relation) => {
const fromObjectMetadata = const fromObjectMetadata =
objectMetadataFromDB[relation.fromObjectNameSingular]; objectMetadataFromDB[relation.fromObjectNameSingular];
assert( assert(
fromObjectMetadata, fromObjectMetadata,
`Object ${relation.fromObjectNameSingular} not found in DB `Object ${relation.fromObjectNameSingular} not found in DB
@ -64,6 +65,7 @@ export class MetadataParser {
const toObjectMetadata = const toObjectMetadata =
objectMetadataFromDB[relation.toObjectNameSingular]; objectMetadataFromDB[relation.toObjectNameSingular];
assert( assert(
toObjectMetadata, toObjectMetadata,
`Object ${relation.toObjectNameSingular} not found in DB `Object ${relation.toObjectNameSingular} not found in DB
@ -72,6 +74,7 @@ export class MetadataParser {
const fromFieldMetadata = const fromFieldMetadata =
fromObjectMetadata?.fields[relation.fromFieldMetadataName]; fromObjectMetadata?.fields[relation.fromFieldMetadataName];
assert( assert(
fromFieldMetadata, fromFieldMetadata,
`Field ${relation.fromFieldMetadataName} not found in object ${relation.fromObjectNameSingular} `Field ${relation.fromFieldMetadataName} not found in object ${relation.fromObjectNameSingular}
@ -80,6 +83,7 @@ export class MetadataParser {
const toFieldMetadata = const toFieldMetadata =
toObjectMetadata?.fields[relation.toFieldMetadataName]; toObjectMetadata?.fields[relation.toFieldMetadataName];
assert( assert(
toFieldMetadata, toFieldMetadata,
`Field ${relation.toFieldMetadataName} not found in object ${relation.toObjectNameSingular} `Field ${relation.toFieldMetadataName} not found in object ${relation.toObjectNameSingular}