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:
@ -3,7 +3,7 @@ import styled from '@emotion/styled';
|
||||
|
||||
import { MessageChannel } from '@/accounts/types/MessageChannel';
|
||||
import { SettingsAccountsInboxSettingsCardMedia } from '@/settings/accounts/components/SettingsAccountsInboxSettingsCardMedia';
|
||||
import { IconSend } from '@/ui/display/icon';
|
||||
import { IconUser } from '@/ui/display/icon';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
import { Toggle } from '@/ui/input/components/Toggle';
|
||||
import { Card } from '@/ui/layout/card/components/Card';
|
||||
@ -43,7 +43,7 @@ export const SettingsAccountsInboxSettingsContactAutoCreateSection = ({
|
||||
<Card>
|
||||
<StyledCardContent>
|
||||
<SettingsAccountsInboxSettingsCardMedia>
|
||||
<IconSend size={theme.icon.size.sm} stroke={theme.icon.stroke.lg} />
|
||||
<IconUser size={theme.icon.size.sm} stroke={theme.icon.stroke.lg} />
|
||||
</SettingsAccountsInboxSettingsCardMedia>
|
||||
<StyledTitle>Auto-creation</StyledTitle>
|
||||
<Toggle
|
||||
|
||||
@ -1,5 +1,4 @@
|
||||
import styled from '@emotion/styled';
|
||||
import { SoonPill } from 'tsup.ui.index';
|
||||
|
||||
import { SettingsAccountsInboxSettingsCardMedia } from '@/settings/accounts/components/SettingsAccountsInboxSettingsCardMedia';
|
||||
import { H2Title } from '@/ui/display/typography/components/H2Title';
|
||||
@ -23,7 +22,11 @@ const StyledCardContent = styled(CardContent)`
|
||||
align-items: center;
|
||||
display: flex;
|
||||
gap: ${({ theme }) => theme.spacing(4)};
|
||||
opacity: 0.56;
|
||||
cursor: pointer;
|
||||
|
||||
&:hover {
|
||||
background: ${({ theme }) => theme.background.transparent.lighter};
|
||||
}
|
||||
`;
|
||||
|
||||
const StyledCardMedia = styled(SettingsAccountsInboxSettingsCardMedia)`
|
||||
@ -61,16 +64,6 @@ const StyledRadio = styled(Radio)`
|
||||
margin-left: auto;
|
||||
`;
|
||||
|
||||
const StyledSoonPill = styled(SoonPill)`
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
`;
|
||||
|
||||
const StyledSection = styled(Section)`
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const inboxSettingsVisibilityOptions = [
|
||||
{
|
||||
title: 'Everything',
|
||||
@ -108,12 +101,11 @@ export const SettingsAccountsInboxSettingsVisibilitySection = ({
|
||||
onChange,
|
||||
value = InboxSettingsVisibilityValue.Everything,
|
||||
}: SettingsAccountsInboxSettingsVisibilitySectionProps) => (
|
||||
<StyledSection>
|
||||
<Section>
|
||||
<H2Title
|
||||
title="Email visibility"
|
||||
description="Define what will be visible to other users in your workspace"
|
||||
/>
|
||||
<StyledSoonPill />
|
||||
<Card>
|
||||
{inboxSettingsVisibilityOptions.map(
|
||||
(
|
||||
@ -123,6 +115,7 @@ export const SettingsAccountsInboxSettingsVisibilitySection = ({
|
||||
<StyledCardContent
|
||||
key={optionValue}
|
||||
divider={index < inboxSettingsVisibilityOptions.length - 1}
|
||||
onClick={() => onChange(optionValue)}
|
||||
>
|
||||
<StyledCardMedia>
|
||||
<StyledMetadataSkeleton isActive={visibleElements.metadata} />
|
||||
@ -137,11 +130,10 @@ export const SettingsAccountsInboxSettingsVisibilitySection = ({
|
||||
value={optionValue}
|
||||
onCheckedChange={() => onChange(optionValue)}
|
||||
checked={value === optionValue}
|
||||
disabled={true}
|
||||
/>
|
||||
</StyledCardContent>
|
||||
),
|
||||
)}
|
||||
</Card>
|
||||
</StyledSection>
|
||||
</Section>
|
||||
);
|
||||
|
||||
@ -4,6 +4,7 @@ import { useNavigate, useParams } from 'react-router-dom';
|
||||
import { MessageChannel } from '@/accounts/types/MessageChannel';
|
||||
import { useFindOneRecord } from '@/object-record/hooks/useFindOneRecord';
|
||||
import { useUpdateOneRecord } from '@/object-record/hooks/useUpdateOneRecord';
|
||||
import { SettingsAccountsInboxSettingsContactAutoCreateSection } from '@/settings/accounts/components/SettingsAccountsInboxSettingsContactAutoCreationSection';
|
||||
import {
|
||||
InboxSettingsVisibilityValue,
|
||||
SettingsAccountsInboxSettingsVisibilitySection,
|
||||
@ -36,6 +37,15 @@ export const SettingsAccountsEmailsInboxSettings = () => {
|
||||
});
|
||||
};
|
||||
|
||||
const handleContactAutoCreationToggle = (value: boolean) => {
|
||||
updateOneRecord({
|
||||
idToUpdate: messageChannelId,
|
||||
updateOneRecordInput: {
|
||||
isContactAutoCreationEnabled: value,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
if (!loading && !messageChannel) navigate(AppPath.NotFound);
|
||||
}, [loading, messageChannel, navigate]);
|
||||
@ -61,11 +71,10 @@ export const SettingsAccountsEmailsInboxSettings = () => {
|
||||
value={messageChannel?.visibility}
|
||||
onChange={handleVisibilityChange}
|
||||
/>
|
||||
{/* TODO : Add this section when the backend will be ready to auto create contacts */}
|
||||
{/* <SettingsAccountsInboxSettingsContactAutoCreateSection
|
||||
<SettingsAccountsInboxSettingsContactAutoCreateSection
|
||||
messageChannel={messageChannel}
|
||||
onToggle={handleContactAutoCreationToggle}
|
||||
/> */}
|
||||
/>
|
||||
</SettingsPageContainer>
|
||||
</SubMenuTopBarContainer>
|
||||
);
|
||||
|
||||
@ -67,8 +67,8 @@ export class GoogleGmailService {
|
||||
);
|
||||
|
||||
await manager.query(
|
||||
`INSERT INTO ${dataSourceMetadata.schema}."messageChannel" ("visibility", "handle", "connectedAccountId", "type") VALUES ($1, $2, $3, $4)`,
|
||||
['share_everything', handle, connectedAccountId, 'email'],
|
||||
`INSERT INTO ${dataSourceMetadata.schema}."messageChannel" ("visibility", "handle", "connectedAccountId", "type", "isContactAutoCreationEnabled") VALUES ($1, $2, $3, $4, $5)`,
|
||||
['share_everything', handle, connectedAccountId, 'email', true],
|
||||
);
|
||||
});
|
||||
|
||||
|
||||
@ -20,6 +20,9 @@ import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
|
||||
import { FetchAllWorkspacesMessagesJob } from 'src/workspace/messaging/commands/crons/fetch-all-workspaces-messages.job';
|
||||
import { ConnectedAccountModule } from 'src/workspace/messaging/repositories/connected-account/connected-account.module';
|
||||
import { MatchMessageParticipantJob } from 'src/workspace/messaging/jobs/match-message-participant.job';
|
||||
import { CreateCompaniesAndContactsAfterSyncJob } from 'src/workspace/messaging/jobs/create-companies-and-contacts-after-sync.job';
|
||||
import { CreateCompaniesAndContactsModule } from 'src/workspace/messaging/services/create-companies-and-contacts/create-companies-and-contacts.module';
|
||||
import { MessageChannelModule } from 'src/workspace/messaging/repositories/message-channel/message-channel.module';
|
||||
import { MessageParticipantModule } from 'src/workspace/messaging/repositories/message-participant/message-participant.module';
|
||||
|
||||
@Module({
|
||||
@ -36,6 +39,8 @@ import { MessageParticipantModule } from 'src/workspace/messaging/repositories/m
|
||||
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
|
||||
ConnectedAccountModule,
|
||||
MessageParticipantModule,
|
||||
CreateCompaniesAndContactsModule,
|
||||
MessageChannelModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
@ -67,6 +72,10 @@ import { MessageParticipantModule } from 'src/workspace/messaging/repositories/m
|
||||
provide: MatchMessageParticipantJob.name,
|
||||
useClass: MatchMessageParticipantJob,
|
||||
},
|
||||
{
|
||||
provide: CreateCompaniesAndContactsAfterSyncJob.name,
|
||||
useClass: CreateCompaniesAndContactsAfterSyncJob,
|
||||
},
|
||||
],
|
||||
})
|
||||
export class JobsModule {
|
||||
|
||||
@ -0,0 +1,67 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { CreateContactsAndCompaniesAfterSyncJobData } from 'packages/twenty-server/dist/src/workspace/messaging/jobs/create-contacts-and-companies-after-sync.job';
|
||||
|
||||
import { MessageQueueJob } from 'src/integrations/message-queue/interfaces/message-queue-job.interface';
|
||||
|
||||
import { CreateCompaniesAndContactsService } from 'src/workspace/messaging/services/create-companies-and-contacts/create-companies-and-contacts.service';
|
||||
import { MessageChannelService } from 'src/workspace/messaging/repositories/message-channel/message-channel.service';
|
||||
import { MessageParticipantService } from 'src/workspace/messaging/repositories/message-participant/message-participant.service';
|
||||
|
||||
export type CreateCompaniesAndContactsAfterSyncJobData = {
|
||||
workspaceId: string;
|
||||
messageChannelId: string;
|
||||
};
|
||||
|
||||
@Injectable()
|
||||
export class CreateCompaniesAndContactsAfterSyncJob
|
||||
implements MessageQueueJob<CreateCompaniesAndContactsAfterSyncJobData>
|
||||
{
|
||||
private readonly logger = new Logger(
|
||||
CreateCompaniesAndContactsAfterSyncJob.name,
|
||||
);
|
||||
constructor(
|
||||
private readonly createCompaniesAndContactsService: CreateCompaniesAndContactsService,
|
||||
private readonly messageChannelService: MessageChannelService,
|
||||
private readonly messageParticipantService: MessageParticipantService,
|
||||
) {}
|
||||
|
||||
async handle(
|
||||
data: CreateContactsAndCompaniesAfterSyncJobData,
|
||||
): Promise<void> {
|
||||
this.logger.log(
|
||||
`create contacts and companies after sync for workspace ${data.workspaceId} and messageChannel ${data.messageChannelId}`,
|
||||
);
|
||||
const { workspaceId, messageChannelId } = data;
|
||||
|
||||
const isContactAutoCreationEnabled =
|
||||
await this.messageChannelService.getIsContactAutoCreationEnabledByMessageChannelId(
|
||||
messageChannelId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!isContactAutoCreationEnabled) {
|
||||
return;
|
||||
}
|
||||
|
||||
const messageParticipantsWithoutPersonIdAndWorkspaceMemberId =
|
||||
await this.messageParticipantService.getByMessageChannelIdWithoutPersonIdAndWorkspaceMemberId(
|
||||
messageChannelId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.createCompaniesAndContactsService.createCompaniesAndContacts(
|
||||
messageParticipantsWithoutPersonIdAndWorkspaceMemberId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
await this.messageParticipantService.updateMessageParticipantsAfterPeopleCreation(
|
||||
messageParticipantsWithoutPersonIdAndWorkspaceMemberId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
this.logger.log(
|
||||
`create contacts and companies after sync for workspace ${data.workspaceId} and messageChannel ${data.messageChannelId} done`,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,40 @@
|
||||
import { Injectable, Inject } from '@nestjs/common';
|
||||
import { OnEvent } from '@nestjs/event-emitter';
|
||||
|
||||
import { CreateContactsAndCompaniesAfterSyncJobData } from 'packages/twenty-server/dist/src/workspace/messaging/jobs/create-contacts-and-companies-after-sync.job';
|
||||
|
||||
import { ObjectRecordUpdateEvent } from 'src/integrations/event-emitter/types/object-record-update.event';
|
||||
import { MessageQueue } from 'src/integrations/message-queue/message-queue.constants';
|
||||
import { MessageQueueService } from 'src/integrations/message-queue/services/message-queue.service';
|
||||
import { MessageChannelObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-channel.object-metadata';
|
||||
import { objectRecordChangedProperties as objectRecordUpdateEventChangedProperties } from 'src/integrations/event-emitter/utils/object-record-changed-properties.util';
|
||||
import { CreateCompaniesAndContactsAfterSyncJob } from 'src/workspace/messaging/jobs/create-companies-and-contacts-after-sync.job';
|
||||
|
||||
@Injectable()
|
||||
export class IsContactAutoCreationEnabledListener {
|
||||
constructor(
|
||||
@Inject(MessageQueue.messagingQueue)
|
||||
private readonly messageQueueService: MessageQueueService,
|
||||
) {}
|
||||
|
||||
@OnEvent('messageChannel.updated')
|
||||
handleUpdatedEvent(
|
||||
payload: ObjectRecordUpdateEvent<MessageChannelObjectMetadata>,
|
||||
) {
|
||||
if (
|
||||
objectRecordUpdateEventChangedProperties(
|
||||
payload.previousRecord,
|
||||
payload.updatedRecord,
|
||||
).includes('isContactAutoCreationEnabled') &&
|
||||
payload.updatedRecord.isContactAutoCreationEnabled
|
||||
) {
|
||||
this.messageQueueService.add<CreateContactsAndCompaniesAfterSyncJobData>(
|
||||
CreateCompaniesAndContactsAfterSyncJob.name,
|
||||
{
|
||||
workspaceId: payload.workspaceId,
|
||||
messageChannelId: payload.updatedRecord.id,
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -18,10 +18,14 @@ import { GmailRefreshAccessTokenService } from 'src/workspace/messaging/services
|
||||
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
|
||||
import { MessageParticipantModule } from 'src/workspace/messaging/repositories/message-participant/message-participant.module';
|
||||
import { MessagingWorkspaceMemberListener } from 'src/workspace/messaging/listeners/messaging-workspace-member.listener';
|
||||
import { IsContactAutoCreationEnabledListener } from 'src/workspace/messaging/listeners/is-contact-auto-creation-enabled-listener';
|
||||
import { MessagingMessageChannelListener } from 'src/workspace/messaging/listeners/messaging-message-channel.listener';
|
||||
import { MessageService } from 'src/workspace/messaging/repositories/message/message.service';
|
||||
import { WorkspaceMemberModule } from 'src/workspace/messaging/repositories/workspace-member/workspace-member.module';
|
||||
import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
|
||||
import { CreateCompaniesAndContactsModule } from 'src/workspace/messaging/services/create-companies-and-contacts/create-companies-and-contacts.module';
|
||||
import { CompanyModule } from 'src/workspace/messaging/repositories/company/company.module';
|
||||
import { PersonModule } from 'src/workspace/messaging/repositories/person/person.module';
|
||||
@Module({
|
||||
imports: [
|
||||
EnvironmentModule,
|
||||
@ -32,8 +36,11 @@ import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
|
||||
MessageModule,
|
||||
MessageThreadModule,
|
||||
MessageParticipantModule,
|
||||
CreateCompaniesAndContactsModule,
|
||||
WorkspaceMemberModule,
|
||||
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
|
||||
CompanyModule,
|
||||
PersonModule,
|
||||
],
|
||||
providers: [
|
||||
GmailFullSyncService,
|
||||
@ -45,6 +52,7 @@ import { FeatureFlagEntity } from 'src/core/feature-flag/feature-flag.entity';
|
||||
CreateCompanyService,
|
||||
MessagingPersonListener,
|
||||
MessagingWorkspaceMemberListener,
|
||||
IsContactAutoCreationEnabledListener,
|
||||
MessagingMessageChannelListener,
|
||||
MessageService,
|
||||
],
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { CompanyService } from 'src/workspace/messaging/repositories/company/company.service';
|
||||
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
|
||||
|
||||
// TODO: Move outside of the messaging module
|
||||
@Module({
|
||||
imports: [WorkspaceDataSourceModule],
|
||||
providers: [CompanyService],
|
||||
exports: [CompanyService],
|
||||
})
|
||||
export class CompanyModule {}
|
||||
@ -0,0 +1,52 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
||||
|
||||
// TODO: Move outside of the messaging module
|
||||
@Injectable()
|
||||
export class CompanyService {
|
||||
constructor(
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
) {}
|
||||
|
||||
public async getExistingCompaniesByDomainNames(
|
||||
domainNames: string[],
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<{ id: string; domainName: string }[]> {
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
const existingCompanies =
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`SELECT id, "domainName" FROM ${dataSourceSchema}.company WHERE "domainName" = ANY($1)`,
|
||||
[domainNames],
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
return existingCompanies;
|
||||
}
|
||||
|
||||
public async createCompany(
|
||||
id: string,
|
||||
name: string,
|
||||
domainName: string,
|
||||
city: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<void> {
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`INSERT INTO ${dataSourceSchema}.company (id, name, "domainName", address)
|
||||
VALUES ($1, $2, $3, $4)`,
|
||||
[id, name, domainName, city],
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -44,6 +44,35 @@ export class MessageChannelService {
|
||||
return messageChannels[0];
|
||||
}
|
||||
|
||||
public async getIsContactAutoCreationEnabledByConnectedAccountIdOrFail(
|
||||
connectedAccountId: string,
|
||||
workspaceId: string,
|
||||
): Promise<boolean> {
|
||||
const messageChannel = await this.getFirstByConnectedAccountIdOrFail(
|
||||
connectedAccountId,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return messageChannel.isContactAutoCreationEnabled;
|
||||
}
|
||||
|
||||
public async getIsContactAutoCreationEnabledByMessageChannelId(
|
||||
messageChannelId: string,
|
||||
workspaceId: string,
|
||||
): Promise<boolean> {
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
const messageChannels =
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`SELECT * FROM ${dataSourceSchema}."messageChannel" WHERE "id" = $1 LIMIT 1`,
|
||||
[messageChannelId],
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return messageChannels[0]?.isContactAutoCreationEnabled;
|
||||
}
|
||||
|
||||
public async getByIds(
|
||||
ids: string[],
|
||||
workspaceId: string,
|
||||
|
||||
@ -1,16 +1,11 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
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 { CreateCompaniesAndContactsModule } from 'src/workspace/messaging/services/create-companies-and-contacts/create-companies-and-contacts.module';
|
||||
import { MessageParticipantService } from 'src/workspace/messaging/repositories/message-participant/message-participant.service';
|
||||
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
WorkspaceDataSourceModule,
|
||||
CreateContactModule,
|
||||
CreateCompanyModule,
|
||||
],
|
||||
imports: [WorkspaceDataSourceModule, CreateCompaniesAndContactsModule],
|
||||
providers: [MessageParticipantService],
|
||||
exports: [MessageParticipantService],
|
||||
})
|
||||
|
||||
@ -5,17 +5,15 @@ import { EntityManager } from 'typeorm';
|
||||
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
||||
import { MessageParticipantObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/message-participant.object-metadata';
|
||||
import { ObjectRecord } from 'src/workspace/workspace-sync-metadata/types/object-record';
|
||||
import { DataSourceEntity } from 'src/metadata/data-source/data-source.entity';
|
||||
import { Participant } from 'src/workspace/messaging/types/gmail-message';
|
||||
import { CreateContactService } from 'src/workspace/messaging/services/create-contact/create-contact.service';
|
||||
import { CreateCompanyService } from 'src/workspace/messaging/services/create-company/create-company.service';
|
||||
import {
|
||||
ParticipantWithId,
|
||||
Participant,
|
||||
} from 'src/workspace/messaging/types/gmail-message';
|
||||
|
||||
@Injectable()
|
||||
export class MessageParticipantService {
|
||||
constructor(
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
private readonly createContactService: CreateContactService,
|
||||
private readonly createCompaniesService: CreateCompanyService,
|
||||
) {}
|
||||
|
||||
public async getByHandles(
|
||||
@ -68,79 +66,71 @@ export class MessageParticipantService {
|
||||
);
|
||||
}
|
||||
|
||||
public async getByMessageChannelIdWithoutPersonIdAndWorkspaceMemberId(
|
||||
messageChannelId: string,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<ParticipantWithId[]> {
|
||||
if (!messageChannelId || !workspaceId) {
|
||||
return [];
|
||||
}
|
||||
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
const messageParticipants: ParticipantWithId[] =
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`SELECT "messageParticipant".id,
|
||||
"messageParticipant"."role",
|
||||
"messageParticipant"."handle",
|
||||
"messageParticipant"."displayName",
|
||||
"messageParticipant"."personId",
|
||||
"messageParticipant"."workspaceMemberId",
|
||||
"messageParticipant"."messageId"
|
||||
FROM ${dataSourceSchema}."messageParticipant" "messageParticipant"
|
||||
LEFT JOIN ${dataSourceSchema}."message" ON "messageParticipant"."messageId" = ${dataSourceSchema}."message"."id"
|
||||
LEFT JOIN ${dataSourceSchema}."messageChannelMessageAssociation" ON ${dataSourceSchema}."messageChannelMessageAssociation"."messageId" = ${dataSourceSchema}."message"."id"
|
||||
WHERE ${dataSourceSchema}."messageChannelMessageAssociation"."messageChannelId" = $1
|
||||
AND "messageParticipant"."personId" IS NULL
|
||||
AND "messageParticipant"."workspaceMemberId" IS NULL`,
|
||||
[messageChannelId],
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
return messageParticipants;
|
||||
}
|
||||
|
||||
public async saveMessageParticipants(
|
||||
participants: Participant[],
|
||||
messageId: string,
|
||||
dataSourceMetadata: DataSourceEntity,
|
||||
manager: EntityManager,
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<void> {
|
||||
if (!participants) return;
|
||||
|
||||
const alreadyCreatedContacts = await manager.query(
|
||||
`SELECT email FROM ${dataSourceMetadata.schema}."person" WHERE "email" = ANY($1)`,
|
||||
[participants.map((participant) => participant.handle)],
|
||||
);
|
||||
|
||||
const alreadyCreatedContactEmails: string[] = alreadyCreatedContacts?.map(
|
||||
({ email }) => email,
|
||||
);
|
||||
|
||||
const filteredParticipants = participants.filter(
|
||||
(participant) =>
|
||||
!alreadyCreatedContactEmails.includes(participant.handle) &&
|
||||
participant.handle.includes('@'),
|
||||
);
|
||||
|
||||
const filteredParticipantsWihCompanyDomainNames = filteredParticipants?.map(
|
||||
(participant) => ({
|
||||
handle: participant.handle,
|
||||
displayName: participant.displayName,
|
||||
companyDomainName: participant.handle
|
||||
.split('@')?.[1]
|
||||
.split('.')
|
||||
.slice(-2)
|
||||
.join('.')
|
||||
.toLowerCase(),
|
||||
}),
|
||||
);
|
||||
|
||||
const domainNamesToCreate = filteredParticipantsWihCompanyDomainNames.map(
|
||||
(participant) => participant.companyDomainName,
|
||||
);
|
||||
|
||||
const companiesObject = await this.createCompaniesService.createCompanies(
|
||||
domainNamesToCreate,
|
||||
dataSourceMetadata,
|
||||
manager,
|
||||
);
|
||||
|
||||
const contactsToCreate = filteredParticipantsWihCompanyDomainNames.map(
|
||||
(participant) => ({
|
||||
handle: participant.handle,
|
||||
displayName: participant.displayName,
|
||||
companyId: companiesObject[participant.companyDomainName],
|
||||
}),
|
||||
);
|
||||
|
||||
await this.createContactService.createContacts(
|
||||
contactsToCreate,
|
||||
dataSourceMetadata,
|
||||
manager,
|
||||
);
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
const handles = participants.map((participant) => participant.handle);
|
||||
|
||||
const participantPersonIds = await manager.query(
|
||||
`SELECT id, email FROM ${dataSourceMetadata.schema}."person" WHERE "email" = ANY($1)`,
|
||||
[handles],
|
||||
);
|
||||
const participantPersonIds =
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`SELECT id, email FROM ${dataSourceSchema}."person" WHERE "email" = ANY($1)`,
|
||||
[handles],
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
const participantWorkspaceMemberIds = await manager.query(
|
||||
`SELECT "workspaceMember"."id", "connectedAccount"."handle" AS email FROM ${dataSourceMetadata.schema}."workspaceMember"
|
||||
JOIN ${dataSourceMetadata.schema}."connectedAccount" ON ${dataSourceMetadata.schema}."workspaceMember"."id" = ${dataSourceMetadata.schema}."connectedAccount"."accountOwnerId"
|
||||
WHERE ${dataSourceMetadata.schema}."connectedAccount"."handle" = ANY($1)`,
|
||||
[handles],
|
||||
);
|
||||
const participantWorkspaceMemberIds =
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`SELECT "workspaceMember"."id", "connectedAccount"."handle" AS email FROM ${dataSourceSchema}."workspaceMember"
|
||||
JOIN ${dataSourceSchema}."connectedAccount" ON ${dataSourceSchema}."workspaceMember"."id" = ${dataSourceSchema}."connectedAccount"."accountOwnerId"
|
||||
WHERE ${dataSourceSchema}."connectedAccount"."handle" = ANY($1)`,
|
||||
[handles],
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
const messageParticipantsToSave = participants.map((participant) => [
|
||||
messageId,
|
||||
@ -163,9 +153,54 @@ export class MessageParticipantService {
|
||||
|
||||
if (messageParticipantsToSave.length === 0) return;
|
||||
|
||||
await manager.query(
|
||||
`INSERT INTO ${dataSourceMetadata.schema}."messageParticipant" ("messageId", "role", "handle", "displayName", "personId", "workspaceMemberId") VALUES ${valuesString}`,
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`INSERT INTO ${dataSourceSchema}."messageParticipant" ("messageId", "role", "handle", "displayName", "personId", "workspaceMemberId") VALUES ${valuesString}`,
|
||||
messageParticipantsToSave.flat(),
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
|
||||
public async updateMessageParticipantsAfterPeopleCreation(
|
||||
participants: ParticipantWithId[],
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<void> {
|
||||
if (!participants) return;
|
||||
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
const handles = participants.map((participant) => participant.handle);
|
||||
|
||||
const participantPersonIds =
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`SELECT id, email FROM ${dataSourceSchema}."person" WHERE "email" = ANY($1)`,
|
||||
[handles],
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
|
||||
const messageParticipantsToUpdate = participants.map((participant) => [
|
||||
participant.id,
|
||||
participantPersonIds.find(
|
||||
(e: { id: string; email: string }) => e.email === participant.handle,
|
||||
)?.id,
|
||||
]);
|
||||
|
||||
if (messageParticipantsToUpdate.length === 0) return;
|
||||
|
||||
const valuesString = messageParticipantsToUpdate
|
||||
.map((_, index) => `($${index * 2 + 1}::uuid, $${index * 2 + 2}::uuid)`)
|
||||
.join(', ');
|
||||
|
||||
await this.workspaceDataSourceService.executeRawQuery(
|
||||
`UPDATE ${dataSourceSchema}."messageParticipant" AS "messageParticipant" SET "personId" = "data"."personId"
|
||||
FROM (VALUES ${valuesString}) AS "data"("id", "personId")
|
||||
WHERE "messageParticipant"."id" = "data"."id"`,
|
||||
messageParticipantsToUpdate.flat(),
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,10 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { MessageChannelModule } from 'src/workspace/messaging/repositories/message-channel/message-channel.module';
|
||||
import { MessageChannelMessageAssociationModule } from 'src/workspace/messaging/repositories/message-channel-message-association/message-channel-message-assocation.module';
|
||||
import { MessageParticipantModule } from 'src/workspace/messaging/repositories/message-participant/message-participant.module';
|
||||
import { MessageThreadModule } from 'src/workspace/messaging/repositories/message-thread/message-thread.module';
|
||||
import { MessageService } from 'src/workspace/messaging/repositories/message/message.service';
|
||||
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
|
||||
import { CreateCompaniesAndContactsModule } from 'src/workspace/messaging/services/create-companies-and-contacts/create-companies-and-contacts.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -12,6 +14,8 @@ import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/wo
|
||||
MessageThreadModule,
|
||||
MessageParticipantModule,
|
||||
MessageChannelMessageAssociationModule,
|
||||
MessageChannelModule,
|
||||
CreateCompaniesAndContactsModule,
|
||||
],
|
||||
providers: [MessageService],
|
||||
exports: [MessageService],
|
||||
|
||||
@ -9,10 +9,12 @@ import { ObjectRecord } from 'src/workspace/workspace-sync-metadata/types/object
|
||||
import { DataSourceEntity } from 'src/metadata/data-source/data-source.entity';
|
||||
import { GmailMessage } from 'src/workspace/messaging/types/gmail-message';
|
||||
import { ConnectedAccountObjectMetadata } from 'src/workspace/workspace-sync-metadata/standard-objects/connected-account.object-metadata';
|
||||
import { MessageChannelService } from 'src/workspace/messaging/repositories/message-channel/message-channel.service';
|
||||
import { MessageChannelMessageAssociationService } from 'src/workspace/messaging/repositories/message-channel-message-association/message-channel-message-association.service';
|
||||
import { MessageParticipantService } from 'src/workspace/messaging/repositories/message-participant/message-participant.service';
|
||||
import { MessageThreadService } from 'src/workspace/messaging/repositories/message-thread/message-thread.service';
|
||||
import { isPersonEmail } from 'src/workspace/messaging/utils/is-person-email.util';
|
||||
import { CreateCompaniesAndContactsService } from 'src/workspace/messaging/services/create-companies-and-contacts/create-companies-and-contacts.service';
|
||||
@Injectable()
|
||||
export class MessageService {
|
||||
constructor(
|
||||
@ -20,6 +22,8 @@ export class MessageService {
|
||||
private readonly messageChannelMessageAssociationService: MessageChannelMessageAssociationService,
|
||||
private readonly messageThreadService: MessageThreadService,
|
||||
private readonly messageParticipantService: MessageParticipantService,
|
||||
private readonly messageChannelService: MessageChannelService,
|
||||
private readonly createCompaniesAndContactsService: CreateCompaniesAndContactsService,
|
||||
) {}
|
||||
|
||||
public async getFirstByHeaderMessageId(
|
||||
@ -193,11 +197,24 @@ export class MessageService {
|
||||
],
|
||||
);
|
||||
|
||||
const isContactAutoCreationEnabled =
|
||||
await this.messageChannelService.getIsContactAutoCreationEnabledByConnectedAccountIdOrFail(
|
||||
connectedAccount.id,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (isContactAutoCreationEnabled) {
|
||||
await this.createCompaniesAndContactsService.createCompaniesAndContacts(
|
||||
message.participants,
|
||||
workspaceId,
|
||||
manager,
|
||||
);
|
||||
}
|
||||
|
||||
await this.messageParticipantService.saveMessageParticipants(
|
||||
message.participants,
|
||||
newMessageId,
|
||||
dataSourceMetadata,
|
||||
manager,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
return Promise.resolve(newMessageId);
|
||||
|
||||
@ -0,0 +1,12 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
|
||||
import { PersonService } from 'src/workspace/messaging/repositories/person/person.service';
|
||||
import { WorkspaceDataSourceModule } from 'src/workspace/workspace-datasource/workspace-datasource.module';
|
||||
|
||||
// TODO: Move outside of the messaging module
|
||||
@Module({
|
||||
imports: [WorkspaceDataSourceModule],
|
||||
providers: [PersonService],
|
||||
exports: [PersonService],
|
||||
})
|
||||
export class PersonModule {}
|
||||
@ -0,0 +1,52 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { EntityManager } from 'typeorm';
|
||||
|
||||
import { WorkspaceDataSourceService } from 'src/workspace/workspace-datasource/workspace-datasource.service';
|
||||
|
||||
// TODO: Move outside of the messaging module
|
||||
@Injectable()
|
||||
export class PersonService {
|
||||
constructor(
|
||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
||||
) {}
|
||||
|
||||
async createPeople(
|
||||
peopleToCreate: {
|
||||
id: string;
|
||||
handle: string;
|
||||
firstName: string;
|
||||
lastName: string;
|
||||
companyId: string;
|
||||
}[],
|
||||
workspaceId: string,
|
||||
transactionManager?: EntityManager,
|
||||
): Promise<void> {
|
||||
const dataSourceSchema =
|
||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
||||
|
||||
const valuesString = peopleToCreate
|
||||
.map(
|
||||
(_, index) =>
|
||||
`($${index * 5 + 1}, $${index * 5 + 2}, $${index * 5 + 3}, $${
|
||||
index * 5 + 4
|
||||
}, $${index * 5 + 5})`,
|
||||
)
|
||||
.join(', ');
|
||||
|
||||
return await this.workspaceDataSourceService.executeRawQuery(
|
||||
`INSERT INTO ${dataSourceSchema}.person (id, email, "nameFirstName", "nameLastName", "companyId") VALUES ${valuesString}`,
|
||||
peopleToCreate
|
||||
.map((contact) => [
|
||||
contact.id,
|
||||
contact.handle,
|
||||
contact.firstName,
|
||||
contact.lastName,
|
||||
contact.companyId,
|
||||
])
|
||||
.flat(),
|
||||
workspaceId,
|
||||
transactionManager,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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 {}
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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],
|
||||
})
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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],
|
||||
})
|
||||
|
||||
@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -20,3 +20,7 @@ export type Participant = {
|
||||
handle: string;
|
||||
displayName: string;
|
||||
};
|
||||
|
||||
export type ParticipantWithId = Participant & {
|
||||
id: string;
|
||||
};
|
||||
|
||||
@ -0,0 +1,3 @@
|
||||
export function getDomainNameFromHandle(handle: string): string {
|
||||
return handle.split('@')?.[1].split('.').slice(-2).join('.').toLowerCase();
|
||||
}
|
||||
@ -0,0 +1,18 @@
|
||||
import { capitalize } from 'src/utils/capitalize';
|
||||
|
||||
export function getFirstNameAndLastNameFromHandleAndDisplayName(
|
||||
handle: string,
|
||||
displayName: string,
|
||||
): { firstName: string; lastName: string } {
|
||||
const firstName = displayName.split(' ')[0];
|
||||
const lastName = displayName.split(' ')[1];
|
||||
|
||||
const contactFullNameFromHandle = handle.split('@')[0];
|
||||
const firstNameFromHandle = contactFullNameFromHandle.split('.')[0];
|
||||
const lastNameFromHandle = contactFullNameFromHandle.split('.')[1];
|
||||
|
||||
return {
|
||||
firstName: capitalize(firstName || firstNameFromHandle || ''),
|
||||
lastName: capitalize(lastName || lastNameFromHandle || ''),
|
||||
};
|
||||
}
|
||||
@ -71,6 +71,14 @@ export class MessageChannelObjectMetadata extends BaseObjectMetadata {
|
||||
})
|
||||
type: string;
|
||||
|
||||
@FieldMetadata({
|
||||
type: FieldMetadataType.BOOLEAN,
|
||||
label: 'Is Contact Auto Creation Enabled',
|
||||
description: 'Is Contact Auto Creation Enabled',
|
||||
icon: 'IconUserCircle',
|
||||
})
|
||||
isContactAutoCreationEnabled: boolean;
|
||||
|
||||
@FieldMetadata({
|
||||
type: FieldMetadataType.RELATION,
|
||||
label: 'Message Channel Association',
|
||||
|
||||
Reference in New Issue
Block a user