3889 activate settingsaccountsemailsinboxsettings (#3962)

* update email visibility in settings

* improve styling

* Add contact auto creation toggle to inbox settings

* re
move soonpill

* update Icon

* create job

* Add logic to create contacts and companies for message participants without personId and workspaceMemberId

* add listener

* wip

* wip

* refactoring

* improve structure

* Add isContactAutoCreationEnabled method to MessageChannelService

* wip

* wip

* clean

* add job

* fix bug

* contact creation is working

* wip

* working

* improve code

* improve typing

* resolve conflicts

* fix

* create company repository

* move util

* wip

* fix
This commit is contained in:
bosiraphael
2024-02-14 17:30:17 +01:00
committed by GitHub
parent 0b2ffb0ee6
commit 94ad0e33ec
27 changed files with 607 additions and 164 deletions

View File

@ -0,0 +1,17 @@
import { Module } from '@nestjs/common';
import { CreateCompaniesAndContactsService } from 'src/workspace/messaging/services/create-companies-and-contacts/create-companies-and-contacts.service';
import { CreateCompanyModule } from 'src/workspace/messaging/services/create-company/create-company.module';
import { CreateContactModule } from 'src/workspace/messaging/services/create-contact/create-contact.module';
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
@Module({
imports: [
WorkspaceDataSourceModule,
CreateContactModule,
CreateCompanyModule,
],
providers: [CreateCompaniesAndContactsService],
exports: [CreateCompaniesAndContactsService],
})
export class CreateCompaniesAndContactsModule {}

View File

@ -0,0 +1,76 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
import { Participant } from 'src/workspace/messaging/types/gmail-message';
import { getDomainNameFromHandle } from 'src/workspace/messaging/utils/get-domain-name-from-handle.util';
import { CreateCompanyService } from 'src/workspace/messaging/services/create-company/create-company.service';
import { CreateContactService } from 'src/workspace/messaging/services/create-contact/create-contact.service';
@Injectable()
export class CreateCompaniesAndContactsService {
constructor(
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
private readonly createContactService: CreateContactService,
private readonly createCompaniesService: CreateCompanyService,
) {}
async createCompaniesAndContacts(
participants: Participant[],
workspaceId: string,
transactionManager?: EntityManager,
) {
const dataSourceSchema =
this.workspaceDataSourceService.getSchemaName(workspaceId);
const alreadyCreatedContacts =
await this.workspaceDataSourceService.executeRawQuery(
`SELECT email FROM ${dataSourceSchema}."person" WHERE "email" = ANY($1)`,
[participants.map((participant) => participant.handle)],
workspaceId,
transactionManager,
);
const alreadyCreatedContactEmails: string[] = alreadyCreatedContacts?.map(
({ email }) => email,
);
const filteredParticipants = participants.filter(
(participant) =>
!alreadyCreatedContactEmails.includes(participant.handle) &&
participant.handle.includes('@'),
);
const filteredParticipantsWithCompanyDomainNames =
filteredParticipants?.map((participant) => ({
handle: participant.handle,
displayName: participant.displayName,
companyDomainName: getDomainNameFromHandle(participant.handle),
}));
const domainNamesToCreate = filteredParticipantsWithCompanyDomainNames.map(
(participant) => participant.companyDomainName,
);
const companiesObject = await this.createCompaniesService.createCompanies(
domainNamesToCreate,
workspaceId,
transactionManager,
);
const contactsToCreate = filteredParticipantsWithCompanyDomainNames.map(
(participant) => ({
handle: participant.handle,
displayName: participant.displayName,
companyId: companiesObject[participant.companyDomainName],
}),
);
await this.createContactService.createContacts(
contactsToCreate,
workspaceId,
transactionManager,
);
}
}

View File

@ -1,9 +1,11 @@
import { Module } from '@nestjs/common';
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
import { CreateCompanyService } from 'src/workspace/messaging/services/create-company/create-company.service';
import { CompanyModule } from 'src/workspace/messaging/repositories/company/company.module';
@Module({
imports: [],
imports: [WorkspaceDataSourceModule, CompanyModule],
providers: [CreateCompanyService],
exports: [CreateCompanyService],
})

View File

@ -1,16 +1,16 @@
import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import axios, { AxiosInstance } from 'axios';
import { v4 } from 'uuid';
import axios, { AxiosInstance } from 'axios';
import { DataSourceEntity } from 'src/metadata/data-source/data-source.entity';
import { CompanyService } from 'src/workspace/messaging/repositories/company/company.service';
import { capitalize } from 'src/utils/capitalize';
@Injectable()
export class CreateCompanyService {
private readonly httpService: AxiosInstance;
constructor() {
constructor(private readonly companyService: CompanyService) {
this.httpService = axios.create({
baseURL: 'https://companies.twenty.com',
});
@ -18,17 +18,19 @@ export class CreateCompanyService {
async createCompanies(
domainNames: string[],
dataSourceMetadata: DataSourceEntity,
manager: EntityManager,
workspaceId: string,
transactionManager?: EntityManager,
): Promise<{
[domainName: string]: string;
}> {
const uniqueDomainNames = [...new Set(domainNames)];
const existingCompanies = await manager.query(
`SELECT id, "domainName" FROM ${dataSourceMetadata.schema}.company WHERE "domainName" = ANY($1)`,
[uniqueDomainNames],
);
const existingCompanies =
await this.companyService.getExistingCompaniesByDomainNames(
uniqueDomainNames,
workspaceId,
transactionManager,
);
const companiesObject = existingCompanies.reduce(
(
@ -57,8 +59,8 @@ export class CreateCompanyService {
for (const domainName of filteredDomainNames) {
companiesObject[domainName] = await this.createCompany(
domainName,
dataSourceMetadata,
manager,
workspaceId,
transactionManager,
);
}
@ -67,17 +69,20 @@ export class CreateCompanyService {
async createCompany(
domainName: string,
dataSourceMetadata: DataSourceEntity,
manager: EntityManager,
workspaceId: string,
transactionManager?: EntityManager,
): Promise<string> {
const companyId = v4();
const { name, city } = await this.getCompanyInfoFromDomainName(domainName);
await manager.query(
`INSERT INTO ${dataSourceMetadata.schema}.company (id, name, "domainName", address)
VALUES ($1, $2, $3, $4)`,
[companyId, name, domainName, city],
this.companyService.createCompany(
companyId,
name,
domainName,
city,
workspaceId,
transactionManager,
);
return companyId;

View File

@ -1,9 +1,11 @@
import { Module } from '@nestjs/common';
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
import { CreateContactService } from 'src/workspace/messaging/services/create-contact/create-contact.service';
import { PersonModule } from 'src/workspace/messaging/repositories/person/person.module';
@Module({
imports: [],
imports: [WorkspaceDataSourceModule, PersonModule],
providers: [CreateContactService],
exports: [CreateContactService],
})

View File

@ -3,8 +3,8 @@ import { Injectable } from '@nestjs/common';
import { EntityManager } from 'typeorm';
import { v4 } from 'uuid';
import { DataSourceEntity } from 'src/metadata/data-source/data-source.entity';
import { capitalize } from 'src/utils/capitalize';
import { PersonService } from 'src/workspace/messaging/repositories/person/person.service';
import { getFirstNameAndLastNameFromHandleAndDisplayName } from 'src/workspace/messaging/utils/get-first-name-and-last-name-from-handle-and-display-name.util';
type ContactToCreate = {
handle: string;
@ -22,67 +22,42 @@ type FormattedContactToCreate = {
@Injectable()
export class CreateContactService {
constructor() {}
constructor(private readonly personService: PersonService) {}
formatContacts(
public formatContacts(
contactsToCreate: ContactToCreate[],
): FormattedContactToCreate[] {
return contactsToCreate.map((contact) => {
const id = v4();
const { handle, displayName, companyId } = contact;
const contactFirstName = displayName.split(' ')[0];
const contactLastName = displayName.split(' ')[1];
const contactFullNameFromHandle = handle.split('@')[0];
const contactFirstNameFromHandle =
contactFullNameFromHandle.split('.')[0];
const contactLastNameFromHandle = contactFullNameFromHandle.split('.')[1];
const id = v4();
const { firstName, lastName } =
getFirstNameAndLastNameFromHandleAndDisplayName(handle, displayName);
return {
id,
handle,
firstName: capitalize(
contactFirstName || contactFirstNameFromHandle || '',
),
lastName: capitalize(
contactLastName || contactLastNameFromHandle || '',
),
firstName,
lastName,
companyId,
};
});
}
async createContacts(
public async createContacts(
contactsToCreate: ContactToCreate[],
dataSourceMetadata: DataSourceEntity,
manager: EntityManager,
workspaceId: string,
transactionManager?: EntityManager,
): Promise<void> {
if (contactsToCreate.length === 0) return;
const formattedContacts = this.formatContacts(contactsToCreate);
const valuesString = formattedContacts
.map(
(_, index) =>
`($${index * 5 + 1}, $${index * 5 + 2}, $${index * 5 + 3}, $${
index * 5 + 4
}, $${index * 5 + 5})`,
)
.join(', ');
await manager.query(
`INSERT INTO ${dataSourceMetadata.schema}.person (id, email, "nameFirstName", "nameLastName", "companyId") VALUES ${valuesString}`,
formattedContacts
.map((contact) => [
contact.id,
contact.handle,
contact.firstName,
contact.lastName,
contact.companyId,
])
.flat(),
await this.personService.createPeople(
formattedContacts,
workspaceId,
transactionManager,
);
}
}