mirror of
https://github.com/lingble/twenty.git
synced 2025-11-02 13:47:55 +00:00
feat: created by email calendar (#6536)
This PR is a followup of #6324 to add support of EMAIL and CALENDAR source for the created by composite field.
This commit is contained in:
@@ -192,7 +192,7 @@
|
|||||||
"tslib": "^2.3.0",
|
"tslib": "^2.3.0",
|
||||||
"tsup": "^8.0.1",
|
"tsup": "^8.0.1",
|
||||||
"type-fest": "4.10.1",
|
"type-fest": "4.10.1",
|
||||||
"typeorm": "^0.3.20",
|
"typeorm": "patch:typeorm@0.3.20#./packages/twenty-server/patches/typeorm+0.3.20.patch",
|
||||||
"typescript": "5.3.3",
|
"typescript": "5.3.3",
|
||||||
"use-context-selector": "^2.0.0",
|
"use-context-selector": "^2.0.0",
|
||||||
"use-debounce": "^10.0.0",
|
"use-debounce": "^10.0.0",
|
||||||
|
|||||||
14
packages/twenty-server/patches/typeorm+0.3.20.patch
Normal file
14
packages/twenty-server/patches/typeorm+0.3.20.patch
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
diff --git a/node_modules/typeorm/common/PickKeysByType.d.ts b/node_modules/typeorm/common/PickKeysByType.d.ts
|
||||||
|
index 55ad347..1a8a184 100644
|
||||||
|
--- a/common/PickKeysByType.d.ts
|
||||||
|
+++ b/common/PickKeysByType.d.ts
|
||||||
|
@@ -1,6 +1,6 @@
|
||||||
|
/**
|
||||||
|
* Pick only the keys that match the Type `U`
|
||||||
|
*/
|
||||||
|
-export type PickKeysByType<T, U> = string & keyof {
|
||||||
|
- [P in keyof T as T[P] extends U ? P : never]: T[P];
|
||||||
|
-};
|
||||||
|
+export type PickKeysByType<T, U> = string & {
|
||||||
|
+ [P in keyof T]: Exclude<T[P], null> extends U ? P : never;
|
||||||
|
+}[keyof T];
|
||||||
@@ -1,12 +1,10 @@
|
|||||||
import { BlocklistRepository } from 'src/modules/blocklist/repositories/blocklist.repository';
|
import { BlocklistRepository } from 'src/modules/blocklist/repositories/blocklist.repository';
|
||||||
import { CompanyRepository } from 'src/modules/company/repositories/company.repository';
|
|
||||||
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
|
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
|
||||||
import { MessageChannelMessageAssociationRepository } from 'src/modules/messaging/common/repositories/message-channel-message-association.repository';
|
import { MessageChannelMessageAssociationRepository } from 'src/modules/messaging/common/repositories/message-channel-message-association.repository';
|
||||||
import { MessageChannelRepository } from 'src/modules/messaging/common/repositories/message-channel.repository';
|
import { MessageChannelRepository } from 'src/modules/messaging/common/repositories/message-channel.repository';
|
||||||
import { MessageParticipantRepository } from 'src/modules/messaging/common/repositories/message-participant.repository';
|
import { MessageParticipantRepository } from 'src/modules/messaging/common/repositories/message-participant.repository';
|
||||||
import { MessageThreadRepository } from 'src/modules/messaging/common/repositories/message-thread.repository';
|
import { MessageThreadRepository } from 'src/modules/messaging/common/repositories/message-thread.repository';
|
||||||
import { MessageRepository } from 'src/modules/messaging/common/repositories/message.repository';
|
import { MessageRepository } from 'src/modules/messaging/common/repositories/message.repository';
|
||||||
import { PersonRepository } from 'src/modules/person/repositories/person.repository';
|
|
||||||
import { AuditLogRepository } from 'src/modules/timeline/repositiories/audit-log.repository';
|
import { AuditLogRepository } from 'src/modules/timeline/repositiories/audit-log.repository';
|
||||||
import { TimelineActivityRepository } from 'src/modules/timeline/repositiories/timeline-activity.repository';
|
import { TimelineActivityRepository } from 'src/modules/timeline/repositiories/timeline-activity.repository';
|
||||||
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
|
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
|
||||||
@@ -14,7 +12,6 @@ import { WorkspaceMemberRepository } from 'src/modules/workspace-member/reposito
|
|||||||
export const metadataToRepositoryMapping = {
|
export const metadataToRepositoryMapping = {
|
||||||
AuditLogWorkspaceEntity: AuditLogRepository,
|
AuditLogWorkspaceEntity: AuditLogRepository,
|
||||||
BlocklistWorkspaceEntity: BlocklistRepository,
|
BlocklistWorkspaceEntity: BlocklistRepository,
|
||||||
CompanyWorkspaceEntity: CompanyRepository,
|
|
||||||
ConnectedAccountWorkspaceEntity: ConnectedAccountRepository,
|
ConnectedAccountWorkspaceEntity: ConnectedAccountRepository,
|
||||||
MessageChannelMessageAssociationWorkspaceEntity:
|
MessageChannelMessageAssociationWorkspaceEntity:
|
||||||
MessageChannelMessageAssociationRepository,
|
MessageChannelMessageAssociationRepository,
|
||||||
@@ -22,7 +19,6 @@ export const metadataToRepositoryMapping = {
|
|||||||
MessageWorkspaceEntity: MessageRepository,
|
MessageWorkspaceEntity: MessageRepository,
|
||||||
MessageParticipantWorkspaceEntity: MessageParticipantRepository,
|
MessageParticipantWorkspaceEntity: MessageParticipantRepository,
|
||||||
MessageThreadWorkspaceEntity: MessageThreadRepository,
|
MessageThreadWorkspaceEntity: MessageThreadRepository,
|
||||||
PersonWorkspaceEntity: PersonRepository,
|
|
||||||
TimelineActivityWorkspaceEntity: TimelineActivityRepository,
|
TimelineActivityWorkspaceEntity: TimelineActivityRepository,
|
||||||
WorkspaceMemberWorkspaceEntity: WorkspaceMemberRepository,
|
WorkspaceMemberWorkspaceEntity: WorkspaceMemberRepository,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -18,9 +18,9 @@ import {
|
|||||||
SaveOptions,
|
SaveOptions,
|
||||||
UpdateResult,
|
UpdateResult,
|
||||||
} from 'typeorm';
|
} from 'typeorm';
|
||||||
import { PickKeysByType } from 'typeorm/common/PickKeysByType';
|
|
||||||
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
import { QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||||
import { UpsertOptions } from 'typeorm/repository/UpsertOptions';
|
import { UpsertOptions } from 'typeorm/repository/UpsertOptions';
|
||||||
|
import { PickKeysByType } from 'typeorm/common/PickKeysByType';
|
||||||
|
|
||||||
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
import { WorkspaceInternalContext } from 'src/engine/twenty-orm/interfaces/workspace-internal-context.interface';
|
||||||
|
|
||||||
|
|||||||
@@ -27,7 +27,6 @@ import { CalendarEventWorkspaceEntity } from 'src/modules/calendar/common/standa
|
|||||||
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
|
import { ConnectedAccountModule } from 'src/modules/connected-account/connected-account.module';
|
||||||
import { RefreshAccessTokenManagerModule } from 'src/modules/connected-account/refresh-access-token-manager/refresh-access-token-manager.module';
|
import { RefreshAccessTokenManagerModule } from 'src/modules/connected-account/refresh-access-token-manager/refresh-access-token-manager.module';
|
||||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
@@ -41,7 +40,6 @@ import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/sta
|
|||||||
ObjectMetadataRepositoryModule.forFeature([
|
ObjectMetadataRepositoryModule.forFeature([
|
||||||
ConnectedAccountWorkspaceEntity,
|
ConnectedAccountWorkspaceEntity,
|
||||||
BlocklistWorkspaceEntity,
|
BlocklistWorkspaceEntity,
|
||||||
PersonWorkspaceEntity,
|
|
||||||
WorkspaceMemberWorkspaceEntity,
|
WorkspaceMemberWorkspaceEntity,
|
||||||
]),
|
]),
|
||||||
CalendarEventParticipantManagerModule,
|
CalendarEventParticipantManagerModule,
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import {
|
|||||||
CreateCompanyAndContactJob,
|
CreateCompanyAndContactJob,
|
||||||
CreateCompanyAndContactJobData,
|
CreateCompanyAndContactJobData,
|
||||||
} from 'src/modules/contact-creation-manager/jobs/create-company-and-contact.job';
|
} from 'src/modules/contact-creation-manager/jobs/create-company-and-contact.job';
|
||||||
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CalendarSaveEventsService {
|
export class CalendarSaveEventsService {
|
||||||
@@ -153,6 +154,7 @@ export class CalendarSaveEventsService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
connectedAccount,
|
connectedAccount,
|
||||||
contactsToCreate: participantsToSave,
|
contactsToCreate: participantsToSave,
|
||||||
|
source: FieldActorSource.CALENDAR,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
|||||||
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
import { CalendarChannelWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity';
|
||||||
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity';
|
import { CalendarEventParticipantWorkspaceEntity } from 'src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity';
|
||||||
import { CreateCompanyAndContactService } from 'src/modules/contact-creation-manager/services/create-company-and-contact.service';
|
import { CreateCompanyAndContactService } from 'src/modules/contact-creation-manager/services/create-company-and-contact.service';
|
||||||
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
|
|
||||||
export type CalendarCreateCompanyAndContactAfterSyncJobData = {
|
export type CalendarCreateCompanyAndContactAfterSyncJobData = {
|
||||||
workspaceId: string;
|
workspaceId: string;
|
||||||
@@ -96,6 +97,7 @@ export class CalendarCreateCompanyAndContactAfterSyncJob {
|
|||||||
connectedAccount,
|
connectedAccount,
|
||||||
calendarEventParticipantsWithoutPersonIdAndWorkspaceMemberId,
|
calendarEventParticipantsWithoutPersonIdAndWorkspaceMemberId,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
FieldActorSource.CALENDAR,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
|
|||||||
@@ -1,85 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { EntityManager } from 'typeorm';
|
|
||||||
|
|
||||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
|
||||||
|
|
||||||
export type CompanyToCreate = {
|
|
||||||
id: string;
|
|
||||||
domainName: string;
|
|
||||||
name?: string;
|
|
||||||
city?: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class CompanyRepository {
|
|
||||||
constructor(
|
|
||||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
public async getExistingCompaniesByDomainNames(
|
|
||||||
domainNames: string[],
|
|
||||||
workspaceId: string,
|
|
||||||
companyDomainNameColumnName: string,
|
|
||||||
transactionManager?: EntityManager,
|
|
||||||
): Promise<{ id: string; domainName: string }[]> {
|
|
||||||
const dataSourceSchema =
|
|
||||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
|
||||||
|
|
||||||
const existingCompanies =
|
|
||||||
await this.workspaceDataSourceService.executeRawQuery(
|
|
||||||
`SELECT id, "${companyDomainNameColumnName}" AS "domainName" FROM ${dataSourceSchema}.company WHERE REGEXP_REPLACE("${companyDomainNameColumnName}", '^https?://', '') = ANY($1)`,
|
|
||||||
[domainNames],
|
|
||||||
workspaceId,
|
|
||||||
transactionManager,
|
|
||||||
);
|
|
||||||
|
|
||||||
return existingCompanies;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async getLastCompanyPosition(
|
|
||||||
workspaceId: string,
|
|
||||||
transactionManager?: EntityManager,
|
|
||||||
): Promise<number> {
|
|
||||||
const dataSourceSchema =
|
|
||||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
|
||||||
|
|
||||||
const result = await this.workspaceDataSourceService.executeRawQuery(
|
|
||||||
`SELECT MAX(position) FROM ${dataSourceSchema}.company`,
|
|
||||||
[],
|
|
||||||
workspaceId,
|
|
||||||
transactionManager,
|
|
||||||
);
|
|
||||||
|
|
||||||
return result[0].max ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
public async createCompany(
|
|
||||||
workspaceId: string,
|
|
||||||
companyToCreate: CompanyToCreate,
|
|
||||||
companyDomainNameColumnName: string,
|
|
||||||
transactionManager?: EntityManager,
|
|
||||||
): Promise<void> {
|
|
||||||
const dataSourceSchema =
|
|
||||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
|
||||||
|
|
||||||
const lastCompanyPosition = await this.getLastCompanyPosition(
|
|
||||||
workspaceId,
|
|
||||||
transactionManager,
|
|
||||||
);
|
|
||||||
|
|
||||||
await this.workspaceDataSourceService.executeRawQuery(
|
|
||||||
`INSERT INTO ${dataSourceSchema}.company (id, "${companyDomainNameColumnName}", name, "addressAddressCity", position)
|
|
||||||
VALUES ($1, $2, $3, $4, $5)`,
|
|
||||||
[
|
|
||||||
companyToCreate.id,
|
|
||||||
'https://' + companyToCreate.domainName,
|
|
||||||
companyToCreate.name ?? '',
|
|
||||||
companyToCreate.city ?? '',
|
|
||||||
lastCompanyPosition + 1,
|
|
||||||
],
|
|
||||||
workspaceId,
|
|
||||||
transactionManager,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,5 +1,3 @@
|
|||||||
import { Address } from 'nodemailer/lib/mailer';
|
|
||||||
|
|
||||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
@@ -32,6 +30,7 @@ import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/perso
|
|||||||
import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity';
|
import { TaskTargetWorkspaceEntity } from 'src/modules/task/standard-objects/task-target.workspace-entity';
|
||||||
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
import { TimelineActivityWorkspaceEntity } from 'src/modules/timeline/standard-objects/timeline-activity.workspace-entity';
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
|
import { AddressMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/address.composite-type';
|
||||||
|
|
||||||
@WorkspaceEntity({
|
@WorkspaceEntity({
|
||||||
standardId: STANDARD_OBJECT_IDS.company,
|
standardId: STANDARD_OBJECT_IDS.company,
|
||||||
@@ -60,7 +59,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
|
|||||||
'The company website URL. We use this url to fetch the company icon',
|
'The company website URL. We use this url to fetch the company icon',
|
||||||
icon: 'IconLink',
|
icon: 'IconLink',
|
||||||
})
|
})
|
||||||
domainName?: string;
|
domainName?: LinksMetadata;
|
||||||
|
|
||||||
@WorkspaceField({
|
@WorkspaceField({
|
||||||
standardId: COMPANY_STANDARD_FIELD_IDS.employees,
|
standardId: COMPANY_STANDARD_FIELD_IDS.employees,
|
||||||
@@ -111,7 +110,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
|
|||||||
icon: 'IconMap',
|
icon: 'IconMap',
|
||||||
})
|
})
|
||||||
@WorkspaceIsNullable()
|
@WorkspaceIsNullable()
|
||||||
address: Address;
|
address: AddressMetadata;
|
||||||
|
|
||||||
@WorkspaceField({
|
@WorkspaceField({
|
||||||
standardId: COMPANY_STANDARD_FIELD_IDS.idealCustomerProfile,
|
standardId: COMPANY_STANDARD_FIELD_IDS.idealCustomerProfile,
|
||||||
|
|||||||
@@ -2,30 +2,22 @@ import { Module } from '@nestjs/common';
|
|||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
|
||||||
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
import { FeatureFlagEntity } from 'src/engine/core-modules/feature-flag/feature-flag.entity';
|
||||||
import { FieldMetadataEntity } from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
import { ObjectMetadataRepositoryModule } from 'src/engine/object-metadata-repository/object-metadata-repository.module';
|
||||||
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
import { WorkspaceDataSourceModule } from 'src/engine/workspace-datasource/workspace-datasource.module';
|
||||||
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
|
|
||||||
import { AutoCompaniesAndContactsCreationCalendarChannelListener } from 'src/modules/contact-creation-manager/listeners/auto-companies-and-contacts-creation-calendar-channel.listener';
|
import { AutoCompaniesAndContactsCreationCalendarChannelListener } from 'src/modules/contact-creation-manager/listeners/auto-companies-and-contacts-creation-calendar-channel.listener';
|
||||||
import { AutoCompaniesAndContactsCreationMessageChannelListener } from 'src/modules/contact-creation-manager/listeners/auto-companies-and-contacts-creation-message-channel.listener';
|
import { AutoCompaniesAndContactsCreationMessageChannelListener } from 'src/modules/contact-creation-manager/listeners/auto-companies-and-contacts-creation-message-channel.listener';
|
||||||
import { CreateCompanyAndContactService } from 'src/modules/contact-creation-manager/services/create-company-and-contact.service';
|
import { CreateCompanyAndContactService } from 'src/modules/contact-creation-manager/services/create-company-and-contact.service';
|
||||||
import { CreateCompanyService } from 'src/modules/contact-creation-manager/services/create-company.service';
|
import { CreateCompanyService } from 'src/modules/contact-creation-manager/services/create-company.service';
|
||||||
import { CreateContactService } from 'src/modules/contact-creation-manager/services/create-contact.service';
|
import { CreateContactService } from 'src/modules/contact-creation-manager/services/create-contact.service';
|
||||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ObjectMetadataRepositoryModule.forFeature([
|
ObjectMetadataRepositoryModule.forFeature([WorkspaceMemberWorkspaceEntity]),
|
||||||
PersonWorkspaceEntity,
|
|
||||||
WorkspaceMemberWorkspaceEntity,
|
|
||||||
CompanyWorkspaceEntity,
|
|
||||||
]),
|
|
||||||
WorkspaceDataSourceModule,
|
WorkspaceDataSourceModule,
|
||||||
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
|
TypeOrmModule.forFeature([FeatureFlagEntity], 'core'),
|
||||||
TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
|
TypeOrmModule.forFeature([ObjectMetadataEntity], 'metadata'),
|
||||||
TypeOrmModule.forFeature([FieldMetadataEntity], 'metadata'),
|
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
CreateCompanyService,
|
CreateCompanyService,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
||||||
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
||||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||||
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||||
import { CreateCompanyAndContactService } from 'src/modules/contact-creation-manager/services/create-company-and-contact.service';
|
import { CreateCompanyAndContactService } from 'src/modules/contact-creation-manager/services/create-company-and-contact.service';
|
||||||
|
|
||||||
@@ -11,6 +12,7 @@ export type CreateCompanyAndContactJobData = {
|
|||||||
displayName: string;
|
displayName: string;
|
||||||
handle: string;
|
handle: string;
|
||||||
}[];
|
}[];
|
||||||
|
source: FieldActorSource;
|
||||||
};
|
};
|
||||||
|
|
||||||
@Processor(MessageQueue.contactCreationQueue)
|
@Processor(MessageQueue.contactCreationQueue)
|
||||||
@@ -21,7 +23,7 @@ export class CreateCompanyAndContactJob {
|
|||||||
|
|
||||||
@Process(CreateCompanyAndContactJob.name)
|
@Process(CreateCompanyAndContactJob.name)
|
||||||
async handle(data: CreateCompanyAndContactJobData): Promise<void> {
|
async handle(data: CreateCompanyAndContactJobData): Promise<void> {
|
||||||
const { workspaceId, connectedAccount, contactsToCreate } = data;
|
const { workspaceId, connectedAccount, contactsToCreate, source } = data;
|
||||||
|
|
||||||
await this.createCompanyAndContactService.createCompaniesAndContactsAndUpdateParticipants(
|
await this.createCompanyAndContactService.createCompaniesAndContactsAndUpdateParticipants(
|
||||||
connectedAccount,
|
connectedAccount,
|
||||||
@@ -30,6 +32,7 @@ export class CreateCompanyAndContactJob {
|
|||||||
displayName: contact.displayName,
|
displayName: contact.displayName,
|
||||||
})),
|
})),
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
source,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,16 +4,11 @@ import { InjectRepository } from '@nestjs/typeorm';
|
|||||||
|
|
||||||
import chunk from 'lodash.chunk';
|
import chunk from 'lodash.chunk';
|
||||||
import compact from 'lodash.compact';
|
import compact from 'lodash.compact';
|
||||||
import { EntityManager, Repository } from 'typeorm';
|
import { Any, EntityManager, Repository } from 'typeorm';
|
||||||
|
|
||||||
import { ObjectRecordCreateEvent } from 'src/engine/integrations/event-emitter/types/object-record-create.event';
|
import { ObjectRecordCreateEvent } from 'src/engine/integrations/event-emitter/types/object-record-create.event';
|
||||||
import {
|
|
||||||
FieldMetadataEntity,
|
|
||||||
FieldMetadataType,
|
|
||||||
} from 'src/engine/metadata-modules/field-metadata/field-metadata.entity';
|
|
||||||
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
import { ObjectMetadataEntity } from 'src/engine/metadata-modules/object-metadata/object-metadata.entity';
|
||||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||||
import { COMPANY_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
|
||||||
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
import { STANDARD_OBJECT_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-object-ids';
|
||||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||||
import { CONTACTS_CREATION_BATCH_SIZE } from 'src/modules/contact-creation-manager/constants/contacts-creation-batch-size.constant';
|
import { CONTACTS_CREATION_BATCH_SIZE } from 'src/modules/contact-creation-manager/constants/contacts-creation-batch-size.constant';
|
||||||
@@ -23,39 +18,43 @@ import { Contact } from 'src/modules/contact-creation-manager/types/contact.type
|
|||||||
import { filterOutSelfAndContactsFromCompanyOrWorkspace } from 'src/modules/contact-creation-manager/utils/filter-out-contacts-from-company-or-workspace.util';
|
import { filterOutSelfAndContactsFromCompanyOrWorkspace } from 'src/modules/contact-creation-manager/utils/filter-out-contacts-from-company-or-workspace.util';
|
||||||
import { getDomainNameFromHandle } from 'src/modules/contact-creation-manager/utils/get-domain-name-from-handle.util';
|
import { getDomainNameFromHandle } from 'src/modules/contact-creation-manager/utils/get-domain-name-from-handle.util';
|
||||||
import { getUniqueContactsAndHandles } from 'src/modules/contact-creation-manager/utils/get-unique-contacts-and-handles.util';
|
import { getUniqueContactsAndHandles } from 'src/modules/contact-creation-manager/utils/get-unique-contacts-and-handles.util';
|
||||||
import { PersonRepository } from 'src/modules/person/repositories/person.repository';
|
|
||||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
||||||
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
|
import { WorkspaceMemberRepository } from 'src/modules/workspace-member/repositories/workspace-member.repository';
|
||||||
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
import { isWorkEmail } from 'src/utils/is-work-email';
|
import { isWorkEmail } from 'src/utils/is-work-email';
|
||||||
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CreateCompanyAndContactService {
|
export class CreateCompanyAndContactService {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly createContactService: CreateContactService,
|
private readonly createContactService: CreateContactService,
|
||||||
private readonly createCompaniesService: CreateCompanyService,
|
private readonly createCompaniesService: CreateCompanyService,
|
||||||
@InjectObjectMetadataRepository(PersonWorkspaceEntity)
|
|
||||||
private readonly personRepository: PersonRepository,
|
|
||||||
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity)
|
@InjectObjectMetadataRepository(WorkspaceMemberWorkspaceEntity)
|
||||||
private readonly workspaceMemberRepository: WorkspaceMemberRepository,
|
private readonly workspaceMemberRepository: WorkspaceMemberRepository,
|
||||||
private readonly eventEmitter: EventEmitter2,
|
private readonly eventEmitter: EventEmitter2,
|
||||||
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
@InjectRepository(ObjectMetadataEntity, 'metadata')
|
||||||
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
private readonly objectMetadataRepository: Repository<ObjectMetadataEntity>,
|
||||||
@InjectRepository(FieldMetadataEntity, 'metadata')
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly fieldMetadataRepository: Repository<FieldMetadataEntity>,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private async createCompaniesAndPeople(
|
private async createCompaniesAndPeople(
|
||||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||||
contactsToCreate: Contact[],
|
contactsToCreate: Contact[],
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
companyDomainNameColumnName: string,
|
source: FieldActorSource,
|
||||||
transactionManager?: EntityManager,
|
transactionManager?: EntityManager,
|
||||||
): Promise<PersonWorkspaceEntity[]> {
|
): Promise<DeepPartial<PersonWorkspaceEntity>[]> {
|
||||||
if (!contactsToCreate || contactsToCreate.length === 0) {
|
if (!contactsToCreate || contactsToCreate.length === 0) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const personRepository =
|
||||||
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
|
workspaceId,
|
||||||
|
PersonWorkspaceEntity,
|
||||||
|
);
|
||||||
|
|
||||||
const workspaceMembers =
|
const workspaceMembers =
|
||||||
await this.workspaceMemberRepository.getAllByWorkspaceId(
|
await this.workspaceMemberRepository.getAllByWorkspaceId(
|
||||||
workspaceId,
|
workspaceId,
|
||||||
@@ -77,11 +76,11 @@ export class CreateCompanyAndContactService {
|
|||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
|
|
||||||
const alreadyCreatedContacts = await this.personRepository.getByEmails(
|
const alreadyCreatedContacts = await personRepository.find({
|
||||||
uniqueHandles,
|
where: {
|
||||||
workspaceId,
|
email: Any(uniqueHandles),
|
||||||
transactionManager,
|
},
|
||||||
);
|
});
|
||||||
|
|
||||||
const alreadyCreatedContactEmails: string[] = alreadyCreatedContacts?.map(
|
const alreadyCreatedContactEmails: string[] = alreadyCreatedContacts?.map(
|
||||||
({ email }) => email,
|
({ email }) => email,
|
||||||
@@ -103,15 +102,18 @@ export class CreateCompanyAndContactService {
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
const domainNamesToCreate = compact(
|
const domainNamesToCreate = compact(
|
||||||
filteredContactsToCreateWithCompanyDomainNames.map(
|
filteredContactsToCreateWithCompanyDomainNames
|
||||||
(participant) => participant.companyDomainName,
|
.filter((participant) => participant.companyDomainName)
|
||||||
),
|
.map((participant) => ({
|
||||||
|
domainName: participant.companyDomainName!,
|
||||||
|
createdBySource: source,
|
||||||
|
createdByWorkspaceMember: connectedAccount.accountOwner,
|
||||||
|
})),
|
||||||
);
|
);
|
||||||
|
|
||||||
const companiesObject = await this.createCompaniesService.createCompanies(
|
const companiesObject = await this.createCompaniesService.createCompanies(
|
||||||
domainNamesToCreate,
|
domainNamesToCreate,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
companyDomainNameColumnName,
|
|
||||||
transactionManager,
|
transactionManager,
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -123,9 +125,11 @@ export class CreateCompanyAndContactService {
|
|||||||
contact.companyDomainName && contact.companyDomainName !== ''
|
contact.companyDomainName && contact.companyDomainName !== ''
|
||||||
? companiesObject[contact.companyDomainName]
|
? companiesObject[contact.companyDomainName]
|
||||||
: undefined,
|
: undefined,
|
||||||
|
createdBySource: source,
|
||||||
|
createdByWorkspaceMember: connectedAccount.accountOwner,
|
||||||
}));
|
}));
|
||||||
|
|
||||||
return await this.createContactService.createPeople(
|
return this.createContactService.createPeople(
|
||||||
formattedContactsToCreate,
|
formattedContactsToCreate,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
transactionManager,
|
transactionManager,
|
||||||
@@ -136,6 +140,7 @@ export class CreateCompanyAndContactService {
|
|||||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||||
contactsToCreate: Contact[],
|
contactsToCreate: Contact[],
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
|
source: FieldActorSource,
|
||||||
) {
|
) {
|
||||||
const contactsBatches = chunk(
|
const contactsBatches = chunk(
|
||||||
contactsToCreate,
|
contactsToCreate,
|
||||||
@@ -155,31 +160,43 @@ export class CreateCompanyAndContactService {
|
|||||||
throw new Error('Object metadata not found');
|
throw new Error('Object metadata not found');
|
||||||
}
|
}
|
||||||
|
|
||||||
const domainNameFieldMetadata = await this.fieldMetadataRepository.findOne({
|
// In some jobs the accountOwner is not populated
|
||||||
|
if (!connectedAccount.accountOwner) {
|
||||||
|
const workspaceMemberRepository =
|
||||||
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
|
workspaceId,
|
||||||
|
WorkspaceMemberWorkspaceEntity,
|
||||||
|
);
|
||||||
|
|
||||||
|
const workspaceMember = await workspaceMemberRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
workspaceId: workspaceId,
|
id: connectedAccount.accountOwnerId,
|
||||||
standardId: COMPANY_STANDARD_FIELD_IDS.domainName,
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
const companyDomainNameColumnName =
|
if (!workspaceMember) {
|
||||||
domainNameFieldMetadata?.type === FieldMetadataType.LINKS
|
throw new Error(
|
||||||
? 'domainNamePrimaryLinkUrl'
|
`Workspace member with id ${connectedAccount.accountOwnerId} not found in workspace ${workspaceId}`,
|
||||||
: 'domainName';
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
connectedAccount.accountOwner = workspaceMember;
|
||||||
|
}
|
||||||
|
|
||||||
for (const contactsBatch of contactsBatches) {
|
for (const contactsBatch of contactsBatches) {
|
||||||
const createdPeople = await this.createCompaniesAndPeople(
|
const createdPeople = await this.createCompaniesAndPeople(
|
||||||
connectedAccount,
|
connectedAccount,
|
||||||
contactsBatch,
|
contactsBatch,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
companyDomainNameColumnName,
|
source,
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const createdPerson of createdPeople) {
|
for (const createdPerson of createdPeople) {
|
||||||
this.eventEmitter.emit('person.created', {
|
this.eventEmitter.emit('person.created', {
|
||||||
name: 'person.created',
|
name: 'person.created',
|
||||||
workspaceId,
|
workspaceId,
|
||||||
recordId: createdPerson.id,
|
// FixMe: TypeORM typing issue... id is always returned when using save
|
||||||
|
recordId: createdPerson.id!,
|
||||||
objectMetadata,
|
objectMetadata,
|
||||||
properties: {
|
properties: {
|
||||||
after: createdPerson,
|
after: createdPerson,
|
||||||
|
|||||||
@@ -1,108 +1,189 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
import { Injectable } from '@nestjs/common';
|
||||||
|
|
||||||
import axios, { AxiosInstance } from 'axios';
|
import axios, { AxiosInstance } from 'axios';
|
||||||
import { EntityManager } from 'typeorm';
|
import { EntityManager, ILike } from 'typeorm';
|
||||||
import { v4 } from 'uuid';
|
import uniqBy from 'lodash.uniqby';
|
||||||
|
|
||||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
|
||||||
import { CompanyRepository } from 'src/modules/company/repositories/company.repository';
|
|
||||||
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
|
import { CompanyWorkspaceEntity } from 'src/modules/company/standard-objects/company.workspace-entity';
|
||||||
import { extractDomainFromLink } from 'src/modules/contact-creation-manager/utils/extract-domain-from-link.util';
|
import { extractDomainFromLink } from 'src/modules/contact-creation-manager/utils/extract-domain-from-link.util';
|
||||||
import { getCompanyNameFromDomainName } from 'src/modules/contact-creation-manager/utils/get-company-name-from-domain-name.util';
|
import { getCompanyNameFromDomainName } from 'src/modules/contact-creation-manager/utils/get-company-name-from-domain-name.util';
|
||||||
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
import { computeDisplayName } from 'src/utils/compute-display-name';
|
||||||
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
|
|
||||||
|
type CompanyToCreate = {
|
||||||
|
domainName: string;
|
||||||
|
createdBySource: FieldActorSource;
|
||||||
|
createdByWorkspaceMember?: WorkspaceMemberWorkspaceEntity | null;
|
||||||
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CreateCompanyService {
|
export class CreateCompanyService {
|
||||||
private readonly httpService: AxiosInstance;
|
private readonly httpService: AxiosInstance;
|
||||||
|
|
||||||
constructor(
|
constructor(private readonly twentyORMGlobalManager: TwentyORMGlobalManager) {
|
||||||
@InjectObjectMetadataRepository(CompanyWorkspaceEntity)
|
|
||||||
private readonly companyRepository: CompanyRepository,
|
|
||||||
) {
|
|
||||||
this.httpService = axios.create({
|
this.httpService = axios.create({
|
||||||
baseURL: 'https://companies.twenty.com',
|
baseURL: 'https://companies.twenty.com',
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async createCompanies(
|
async createCompanies(
|
||||||
domainNames: string[],
|
companies: CompanyToCreate[],
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
companyDomainNameColumnName: string,
|
|
||||||
transactionManager?: EntityManager,
|
transactionManager?: EntityManager,
|
||||||
): Promise<{
|
): Promise<{
|
||||||
[domainName: string]: string;
|
[domainName: string]: string;
|
||||||
}> {
|
}> {
|
||||||
if (domainNames.length === 0) {
|
if (companies.length === 0) {
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
||||||
const uniqueDomainNames = [...new Set(domainNames)];
|
const companyRepository =
|
||||||
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
const existingCompanies =
|
|
||||||
await this.companyRepository.getExistingCompaniesByDomainNames(
|
|
||||||
uniqueDomainNames,
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
companyDomainNameColumnName,
|
CompanyWorkspaceEntity,
|
||||||
|
);
|
||||||
|
|
||||||
|
// Avoid creating duplicate companies
|
||||||
|
const uniqueCompanies = uniqBy(companies, 'domainName');
|
||||||
|
const conditions = uniqueCompanies.map((companyToCreate) => ({
|
||||||
|
domainName: {
|
||||||
|
primaryLinkUrl: ILike(`%${companyToCreate.domainName}%`),
|
||||||
|
},
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Find existing companies
|
||||||
|
const existingCompanies = await companyRepository.find(
|
||||||
|
{
|
||||||
|
where: conditions,
|
||||||
|
},
|
||||||
transactionManager,
|
transactionManager,
|
||||||
);
|
);
|
||||||
|
const existingCompanyIdsMap = this.createCompanyMap(existingCompanies);
|
||||||
|
|
||||||
const companiesObject = existingCompanies.reduce(
|
// Filter out companies that already exist
|
||||||
(
|
const newCompaniesToCreate = uniqueCompanies.filter(
|
||||||
acc: {
|
(company) =>
|
||||||
[domainName: string]: string;
|
|
||||||
},
|
|
||||||
company: {
|
|
||||||
domainName: string;
|
|
||||||
id: string;
|
|
||||||
},
|
|
||||||
) => ({
|
|
||||||
...acc,
|
|
||||||
[extractDomainFromLink(company.domainName)]: company.id,
|
|
||||||
}),
|
|
||||||
{},
|
|
||||||
);
|
|
||||||
|
|
||||||
const filteredDomainNames = uniqueDomainNames.filter(
|
|
||||||
(domainName) =>
|
|
||||||
!existingCompanies.some(
|
!existingCompanies.some(
|
||||||
(company: { domainName: string }) =>
|
(existingCompany) =>
|
||||||
extractDomainFromLink(company.domainName) === domainName,
|
existingCompany.domainName &&
|
||||||
|
extractDomainFromLink(existingCompany.domainName.primaryLinkUrl) ===
|
||||||
|
company.domainName,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
for (const domainName of filteredDomainNames) {
|
if (newCompaniesToCreate.length === 0) {
|
||||||
companiesObject[domainName] = await this.createCompany(
|
return existingCompanyIdsMap;
|
||||||
domainName,
|
}
|
||||||
workspaceId,
|
|
||||||
companyDomainNameColumnName,
|
// Retrieve the last company position
|
||||||
|
let lastCompanyPosition = await this.getLastCompanyPosition(
|
||||||
|
companyRepository,
|
||||||
transactionManager,
|
transactionManager,
|
||||||
);
|
);
|
||||||
|
const newCompaniesData = await Promise.all(
|
||||||
|
newCompaniesToCreate.map((company) =>
|
||||||
|
this.prepareCompanyData(company, ++lastCompanyPosition),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create new companies
|
||||||
|
const createdCompanies = await companyRepository.save(
|
||||||
|
newCompaniesData,
|
||||||
|
undefined,
|
||||||
|
transactionManager,
|
||||||
|
);
|
||||||
|
const createdCompanyIdsMap = this.createCompanyMap(createdCompanies);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...existingCompanyIdsMap,
|
||||||
|
...createdCompanyIdsMap,
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
return companiesObject;
|
async createCompany(
|
||||||
}
|
company: CompanyToCreate,
|
||||||
|
|
||||||
private async createCompany(
|
|
||||||
domainName: string,
|
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
companyDomainNameColumnName,
|
|
||||||
transactionManager?: EntityManager,
|
transactionManager?: EntityManager,
|
||||||
): Promise<string> {
|
): Promise<string> {
|
||||||
const companyId = v4();
|
const companyRepository =
|
||||||
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
const { name, city } = await this.getCompanyInfoFromDomainName(domainName);
|
|
||||||
|
|
||||||
await this.companyRepository.createCompany(
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
{
|
CompanyWorkspaceEntity,
|
||||||
id: companyId,
|
);
|
||||||
domainName,
|
let lastCompanyPosition = await this.getLastCompanyPosition(
|
||||||
name,
|
companyRepository,
|
||||||
city,
|
|
||||||
},
|
|
||||||
companyDomainNameColumnName,
|
|
||||||
transactionManager,
|
transactionManager,
|
||||||
);
|
);
|
||||||
|
|
||||||
return companyId;
|
const data = await this.prepareCompanyData(company, ++lastCompanyPosition);
|
||||||
|
|
||||||
|
const createdCompany = await companyRepository.save(
|
||||||
|
data,
|
||||||
|
undefined,
|
||||||
|
transactionManager,
|
||||||
|
);
|
||||||
|
|
||||||
|
return createdCompany.id;
|
||||||
|
}
|
||||||
|
|
||||||
|
private async prepareCompanyData(
|
||||||
|
company: CompanyToCreate,
|
||||||
|
position: number,
|
||||||
|
): Promise<DeepPartial<CompanyWorkspaceEntity>> {
|
||||||
|
const { name, city } = await this.getCompanyInfoFromDomainName(
|
||||||
|
company.domainName,
|
||||||
|
);
|
||||||
|
const createdByName = computeDisplayName(
|
||||||
|
company.createdByWorkspaceMember?.name,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
domainName: {
|
||||||
|
primaryLinkUrl: 'https://' + company.domainName,
|
||||||
|
},
|
||||||
|
name,
|
||||||
|
createdBy: {
|
||||||
|
source: company.createdBySource,
|
||||||
|
workspaceMemberId: company.createdByWorkspaceMember?.id,
|
||||||
|
name: createdByName,
|
||||||
|
},
|
||||||
|
address: {
|
||||||
|
addressCity: city,
|
||||||
|
},
|
||||||
|
position,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
private async createCompanyMap(companies: CompanyWorkspaceEntity[]) {
|
||||||
|
return companies.reduce(
|
||||||
|
(acc, company) => {
|
||||||
|
if (!company.domainName) {
|
||||||
|
return acc;
|
||||||
|
}
|
||||||
|
const key = extractDomainFromLink(company.domainName.primaryLinkUrl);
|
||||||
|
|
||||||
|
acc[key] = company.id;
|
||||||
|
|
||||||
|
return acc;
|
||||||
|
},
|
||||||
|
{} as { [domainName: string]: string },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private async getLastCompanyPosition(
|
||||||
|
companyRepository: WorkspaceRepository<CompanyWorkspaceEntity>,
|
||||||
|
transactionManager?: EntityManager,
|
||||||
|
): Promise<number> {
|
||||||
|
const lastCompanyPosition = await companyRepository.maximum(
|
||||||
|
'position',
|
||||||
|
undefined,
|
||||||
|
transactionManager,
|
||||||
|
);
|
||||||
|
|
||||||
|
return lastCompanyPosition ?? 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
private async getCompanyInfoFromDomainName(domainName: string): Promise<{
|
private async getCompanyInfoFromDomainName(domainName: string): Promise<{
|
||||||
|
|||||||
@@ -3,49 +3,61 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { EntityManager } from 'typeorm';
|
import { EntityManager } from 'typeorm';
|
||||||
import { v4 } from 'uuid';
|
import { v4 } from 'uuid';
|
||||||
|
|
||||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
|
||||||
import { getFirstNameAndLastNameFromHandleAndDisplayName } from 'src/modules/contact-creation-manager/utils/get-first-name-and-last-name-from-handle-and-display-name.util';
|
import { getFirstNameAndLastNameFromHandleAndDisplayName } from 'src/modules/contact-creation-manager/utils/get-first-name-and-last-name-from-handle-and-display-name.util';
|
||||||
import { PersonRepository } from 'src/modules/person/repositories/person.repository';
|
|
||||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
||||||
|
import { TwentyORMGlobalManager } from 'src/engine/twenty-orm/twenty-orm-global.manager';
|
||||||
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
|
import { WorkspaceMemberWorkspaceEntity } from 'src/modules/workspace-member/standard-objects/workspace-member.workspace-entity';
|
||||||
|
import { computeDisplayName } from 'src/utils/compute-display-name';
|
||||||
|
import { WorkspaceRepository } from 'src/engine/twenty-orm/repository/workspace.repository';
|
||||||
|
|
||||||
type ContactToCreate = {
|
type ContactToCreate = {
|
||||||
handle: string;
|
handle: string;
|
||||||
displayName: string;
|
displayName: string;
|
||||||
companyId?: string;
|
companyId?: string;
|
||||||
};
|
createdBySource: FieldActorSource;
|
||||||
|
createdByWorkspaceMember?: WorkspaceMemberWorkspaceEntity | null;
|
||||||
type FormattedContactToCreate = {
|
|
||||||
id: string;
|
|
||||||
handle: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
companyId?: string;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class CreateContactService {
|
export class CreateContactService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectObjectMetadataRepository(PersonWorkspaceEntity)
|
private readonly twentyORMGlobalManager: TwentyORMGlobalManager,
|
||||||
private readonly personRepository: PersonRepository,
|
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
private formatContacts(
|
private formatContacts(
|
||||||
contactsToCreate: ContactToCreate[],
|
contactsToCreate: ContactToCreate[],
|
||||||
): FormattedContactToCreate[] {
|
lastPersonPosition: number,
|
||||||
|
): DeepPartial<PersonWorkspaceEntity>[] {
|
||||||
return contactsToCreate.map((contact) => {
|
return contactsToCreate.map((contact) => {
|
||||||
const id = v4();
|
const id = v4();
|
||||||
|
|
||||||
const { handle, displayName, companyId } = contact;
|
const {
|
||||||
|
handle,
|
||||||
|
displayName,
|
||||||
|
companyId,
|
||||||
|
createdBySource,
|
||||||
|
createdByWorkspaceMember,
|
||||||
|
} = contact;
|
||||||
|
|
||||||
const { firstName, lastName } =
|
const { firstName, lastName } =
|
||||||
getFirstNameAndLastNameFromHandleAndDisplayName(handle, displayName);
|
getFirstNameAndLastNameFromHandleAndDisplayName(handle, displayName);
|
||||||
|
const createdByName = computeDisplayName(createdByWorkspaceMember?.name);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id,
|
id,
|
||||||
handle,
|
email: handle,
|
||||||
|
name: {
|
||||||
firstName,
|
firstName,
|
||||||
lastName,
|
lastName,
|
||||||
|
},
|
||||||
companyId,
|
companyId,
|
||||||
|
createdBy: {
|
||||||
|
source: createdBySource,
|
||||||
|
workspaceMemberId: contact.createdByWorkspaceMember?.id,
|
||||||
|
name: createdByName,
|
||||||
|
},
|
||||||
|
position: ++lastPersonPosition,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -54,15 +66,42 @@ export class CreateContactService {
|
|||||||
contactsToCreate: ContactToCreate[],
|
contactsToCreate: ContactToCreate[],
|
||||||
workspaceId: string,
|
workspaceId: string,
|
||||||
transactionManager?: EntityManager,
|
transactionManager?: EntityManager,
|
||||||
): Promise<PersonWorkspaceEntity[]> {
|
): Promise<DeepPartial<PersonWorkspaceEntity>[]> {
|
||||||
if (contactsToCreate.length === 0) return [];
|
if (contactsToCreate.length === 0) return [];
|
||||||
|
|
||||||
const formattedContacts = this.formatContacts(contactsToCreate);
|
const personRepository =
|
||||||
|
await this.twentyORMGlobalManager.getRepositoryForWorkspace(
|
||||||
return await this.personRepository.createPeople(
|
|
||||||
formattedContacts,
|
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
PersonWorkspaceEntity,
|
||||||
|
);
|
||||||
|
|
||||||
|
const lastPersonPosition = await this.getLastPersonPosition(
|
||||||
|
personRepository,
|
||||||
|
transactionManager,
|
||||||
|
);
|
||||||
|
|
||||||
|
const formattedContacts = this.formatContacts(
|
||||||
|
contactsToCreate,
|
||||||
|
lastPersonPosition,
|
||||||
|
);
|
||||||
|
|
||||||
|
return personRepository.save(
|
||||||
|
formattedContacts,
|
||||||
|
undefined,
|
||||||
transactionManager,
|
transactionManager,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private async getLastPersonPosition(
|
||||||
|
personRepository: WorkspaceRepository<PersonWorkspaceEntity>,
|
||||||
|
transactionManager?: EntityManager,
|
||||||
|
): Promise<number> {
|
||||||
|
const lastPersonPosition = await personRepository.maximum(
|
||||||
|
'position',
|
||||||
|
undefined,
|
||||||
|
transactionManager,
|
||||||
|
);
|
||||||
|
|
||||||
|
return lastPersonPosition ?? 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,13 +9,11 @@ import { MessagingChannelSyncStatusService } from 'src/modules/messaging/common/
|
|||||||
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
|
import { MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
|
||||||
import { MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity';
|
import { MessageThreadWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-thread.workspace-entity';
|
||||||
import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity';
|
import { MessageWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message.workspace-entity';
|
||||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
WorkspaceDataSourceModule,
|
WorkspaceDataSourceModule,
|
||||||
ObjectMetadataRepositoryModule.forFeature([
|
ObjectMetadataRepositoryModule.forFeature([
|
||||||
PersonWorkspaceEntity,
|
|
||||||
MessageParticipantWorkspaceEntity,
|
MessageParticipantWorkspaceEntity,
|
||||||
MessageWorkspaceEntity,
|
MessageWorkspaceEntity,
|
||||||
MessageThreadWorkspaceEntity,
|
MessageThreadWorkspaceEntity,
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import { MessagingMessageService } from 'src/modules/messaging/message-import-ma
|
|||||||
import { MessagingMessageParticipantService } from 'src/modules/messaging/message-participant-manager/services/messaging-message-participant.service';
|
import { MessagingMessageParticipantService } from 'src/modules/messaging/message-participant-manager/services/messaging-message-participant.service';
|
||||||
import { isGroupEmail } from 'src/utils/is-group-email';
|
import { isGroupEmail } from 'src/utils/is-group-email';
|
||||||
import { isWorkEmail } from 'src/utils/is-work-email';
|
import { isWorkEmail } from 'src/utils/is-work-email';
|
||||||
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class MessagingSaveMessagesAndEnqueueContactCreationService {
|
export class MessagingSaveMessagesAndEnqueueContactCreationService {
|
||||||
@@ -121,6 +122,7 @@ export class MessagingSaveMessagesAndEnqueueContactCreationService {
|
|||||||
workspaceId,
|
workspaceId,
|
||||||
connectedAccount,
|
connectedAccount,
|
||||||
contactsToCreate,
|
contactsToCreate,
|
||||||
|
source: FieldActorSource.EMAIL,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import { Logger } from '@nestjs/common';
|
|||||||
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
import { Process } from 'src/engine/integrations/message-queue/decorators/process.decorator';
|
||||||
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
import { Processor } from 'src/engine/integrations/message-queue/decorators/processor.decorator';
|
||||||
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
import { MessageQueue } from 'src/engine/integrations/message-queue/message-queue.constants';
|
||||||
|
import { FieldActorSource } from 'src/engine/metadata-modules/field-metadata/composite-types/actor.composite-type';
|
||||||
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
import { InjectObjectMetadataRepository } from 'src/engine/object-metadata-repository/object-metadata-repository.decorator';
|
||||||
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
|
import { ConnectedAccountRepository } from 'src/modules/connected-account/repositories/connected-account.repository';
|
||||||
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
import { ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||||
@@ -84,6 +85,7 @@ export class MessagingCreateCompanyAndContactAfterSyncJob {
|
|||||||
connectedAccount,
|
connectedAccount,
|
||||||
contactsToCreate,
|
contactsToCreate,
|
||||||
workspaceId,
|
workspaceId,
|
||||||
|
FieldActorSource.EMAIL,
|
||||||
);
|
);
|
||||||
|
|
||||||
this.logger.log(
|
this.logger.log(
|
||||||
|
|||||||
@@ -1,89 +0,0 @@
|
|||||||
import { Injectable } from '@nestjs/common';
|
|
||||||
|
|
||||||
import { EntityManager } from 'typeorm';
|
|
||||||
|
|
||||||
import { WorkspaceDataSourceService } from 'src/engine/workspace-datasource/workspace-datasource.service';
|
|
||||||
import { PersonWorkspaceEntity } from 'src/modules/person/standard-objects/person.workspace-entity';
|
|
||||||
import { getFlattenedValuesAndValuesStringForBatchRawQuery } from 'src/modules/calendar/calendar-event-import-manager/utils/get-flattened-values-and-values-string-for-batch-raw-query.util';
|
|
||||||
|
|
||||||
@Injectable()
|
|
||||||
export class PersonRepository {
|
|
||||||
constructor(
|
|
||||||
private readonly workspaceDataSourceService: WorkspaceDataSourceService,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async getByEmails(
|
|
||||||
emails: string[],
|
|
||||||
workspaceId: string,
|
|
||||||
transactionManager?: EntityManager,
|
|
||||||
): Promise<PersonWorkspaceEntity[]> {
|
|
||||||
const dataSourceSchema =
|
|
||||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
|
||||||
|
|
||||||
return await this.workspaceDataSourceService.executeRawQuery(
|
|
||||||
`SELECT * FROM ${dataSourceSchema}.person WHERE email = ANY($1)`,
|
|
||||||
[emails],
|
|
||||||
workspaceId,
|
|
||||||
transactionManager,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
async getLastPersonPosition(
|
|
||||||
workspaceId: string,
|
|
||||||
transactionManager?: EntityManager,
|
|
||||||
): Promise<number> {
|
|
||||||
const dataSourceSchema =
|
|
||||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
|
||||||
|
|
||||||
const result = await this.workspaceDataSourceService.executeRawQuery(
|
|
||||||
`SELECT MAX(position) FROM ${dataSourceSchema}.person`,
|
|
||||||
[],
|
|
||||||
workspaceId,
|
|
||||||
transactionManager,
|
|
||||||
);
|
|
||||||
|
|
||||||
return result[0].max ?? 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
async createPeople(
|
|
||||||
peopleToCreate: {
|
|
||||||
id: string;
|
|
||||||
handle: string;
|
|
||||||
firstName: string;
|
|
||||||
lastName: string;
|
|
||||||
companyId?: string;
|
|
||||||
}[],
|
|
||||||
workspaceId: string,
|
|
||||||
transactionManager?: EntityManager,
|
|
||||||
): Promise<PersonWorkspaceEntity[]> {
|
|
||||||
const dataSourceSchema =
|
|
||||||
this.workspaceDataSourceService.getSchemaName(workspaceId);
|
|
||||||
|
|
||||||
const lastPersonPosition = await this.getLastPersonPosition(
|
|
||||||
workspaceId,
|
|
||||||
transactionManager,
|
|
||||||
);
|
|
||||||
|
|
||||||
peopleToCreate = peopleToCreate.map((contact, index) => ({
|
|
||||||
...contact,
|
|
||||||
position: lastPersonPosition + index + 1,
|
|
||||||
}));
|
|
||||||
|
|
||||||
const { flattenedValues, valuesString } =
|
|
||||||
getFlattenedValuesAndValuesStringForBatchRawQuery(peopleToCreate, {
|
|
||||||
id: 'uuid',
|
|
||||||
handle: 'text',
|
|
||||||
firstName: 'text',
|
|
||||||
lastName: 'text',
|
|
||||||
companyId: 'uuid',
|
|
||||||
position: 'double precision',
|
|
||||||
});
|
|
||||||
|
|
||||||
return await this.workspaceDataSourceService.executeRawQuery(
|
|
||||||
`INSERT INTO ${dataSourceSchema}.person (id, email, "nameFirstName", "nameLastName", "companyId", "position") VALUES ${valuesString} RETURNING *`,
|
|
||||||
flattenedValues,
|
|
||||||
workspaceId,
|
|
||||||
transactionManager,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
12
packages/twenty-server/src/utils/compute-display-name.ts
Normal file
12
packages/twenty-server/src/utils/compute-display-name.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import { FullNameMetadata } from 'src/engine/metadata-modules/field-metadata/composite-types/full-name.composite-type';
|
||||||
|
import { isDefined } from 'src/utils/is-defined';
|
||||||
|
|
||||||
|
export const computeDisplayName = (
|
||||||
|
name: FullNameMetadata | null | undefined,
|
||||||
|
) => {
|
||||||
|
if (!name) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
return Object.values(name).filter(isDefined).join(' ');
|
||||||
|
};
|
||||||
84
yarn.lock
84
yarn.lock
@@ -49531,7 +49531,7 @@ __metadata:
|
|||||||
tsup: "npm:^8.0.1"
|
tsup: "npm:^8.0.1"
|
||||||
tsx: "npm:^4.7.2"
|
tsx: "npm:^4.7.2"
|
||||||
type-fest: "npm:4.10.1"
|
type-fest: "npm:4.10.1"
|
||||||
typeorm: "npm:^0.3.20"
|
typeorm: "patch:typeorm@0.3.20#./packages/twenty-server/patches/typeorm+0.3.20.patch"
|
||||||
typescript: "npm:5.3.3"
|
typescript: "npm:5.3.3"
|
||||||
use-context-selector: "npm:^2.0.0"
|
use-context-selector: "npm:^2.0.0"
|
||||||
use-debounce: "npm:^10.0.0"
|
use-debounce: "npm:^10.0.0"
|
||||||
@@ -49657,7 +49657,7 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
"typeorm@npm:^0.3.20":
|
"typeorm@npm:0.3.20":
|
||||||
version: 0.3.20
|
version: 0.3.20
|
||||||
resolution: "typeorm@npm:0.3.20"
|
resolution: "typeorm@npm:0.3.20"
|
||||||
dependencies:
|
dependencies:
|
||||||
@@ -49737,6 +49737,86 @@ __metadata:
|
|||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"typeorm@patch:typeorm@0.3.20#./packages/twenty-server/patches/typeorm+0.3.20.patch::locator=twenty%40workspace%3A.":
|
||||||
|
version: 0.3.20
|
||||||
|
resolution: "typeorm@patch:typeorm@npm%3A0.3.20#./packages/twenty-server/patches/typeorm+0.3.20.patch::version=0.3.20&hash=9584e4&locator=twenty%40workspace%3A."
|
||||||
|
dependencies:
|
||||||
|
"@sqltools/formatter": "npm:^1.2.5"
|
||||||
|
app-root-path: "npm:^3.1.0"
|
||||||
|
buffer: "npm:^6.0.3"
|
||||||
|
chalk: "npm:^4.1.2"
|
||||||
|
cli-highlight: "npm:^2.1.11"
|
||||||
|
dayjs: "npm:^1.11.9"
|
||||||
|
debug: "npm:^4.3.4"
|
||||||
|
dotenv: "npm:^16.0.3"
|
||||||
|
glob: "npm:^10.3.10"
|
||||||
|
mkdirp: "npm:^2.1.3"
|
||||||
|
reflect-metadata: "npm:^0.2.1"
|
||||||
|
sha.js: "npm:^2.4.11"
|
||||||
|
tslib: "npm:^2.5.0"
|
||||||
|
uuid: "npm:^9.0.0"
|
||||||
|
yargs: "npm:^17.6.2"
|
||||||
|
peerDependencies:
|
||||||
|
"@google-cloud/spanner": ^5.18.0
|
||||||
|
"@sap/hana-client": ^2.12.25
|
||||||
|
better-sqlite3: ^7.1.2 || ^8.0.0 || ^9.0.0
|
||||||
|
hdb-pool: ^0.1.6
|
||||||
|
ioredis: ^5.0.4
|
||||||
|
mongodb: ^5.8.0
|
||||||
|
mssql: ^9.1.1 || ^10.0.1
|
||||||
|
mysql2: ^2.2.5 || ^3.0.1
|
||||||
|
oracledb: ^6.3.0
|
||||||
|
pg: ^8.5.1
|
||||||
|
pg-native: ^3.0.0
|
||||||
|
pg-query-stream: ^4.0.0
|
||||||
|
redis: ^3.1.1 || ^4.0.0
|
||||||
|
sql.js: ^1.4.0
|
||||||
|
sqlite3: ^5.0.3
|
||||||
|
ts-node: ^10.7.0
|
||||||
|
typeorm-aurora-data-api-driver: ^2.0.0
|
||||||
|
peerDependenciesMeta:
|
||||||
|
"@google-cloud/spanner":
|
||||||
|
optional: true
|
||||||
|
"@sap/hana-client":
|
||||||
|
optional: true
|
||||||
|
better-sqlite3:
|
||||||
|
optional: true
|
||||||
|
hdb-pool:
|
||||||
|
optional: true
|
||||||
|
ioredis:
|
||||||
|
optional: true
|
||||||
|
mongodb:
|
||||||
|
optional: true
|
||||||
|
mssql:
|
||||||
|
optional: true
|
||||||
|
mysql2:
|
||||||
|
optional: true
|
||||||
|
oracledb:
|
||||||
|
optional: true
|
||||||
|
pg:
|
||||||
|
optional: true
|
||||||
|
pg-native:
|
||||||
|
optional: true
|
||||||
|
pg-query-stream:
|
||||||
|
optional: true
|
||||||
|
redis:
|
||||||
|
optional: true
|
||||||
|
sql.js:
|
||||||
|
optional: true
|
||||||
|
sqlite3:
|
||||||
|
optional: true
|
||||||
|
ts-node:
|
||||||
|
optional: true
|
||||||
|
typeorm-aurora-data-api-driver:
|
||||||
|
optional: true
|
||||||
|
bin:
|
||||||
|
typeorm: cli.js
|
||||||
|
typeorm-ts-node-commonjs: cli-ts-node-commonjs.js
|
||||||
|
typeorm-ts-node-esm: cli-ts-node-esm.js
|
||||||
|
checksum: 10c0/f817ca0a7a6c2d4d242fd64ffc1e5337b2db4b03bc87004daf307a9ad77cf79d7050d461a0103a2674df2fba09cc2e95275b6bdef9eb6287d2c779c6ec673676
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"typescript@npm:5.3.3":
|
"typescript@npm:5.3.3":
|
||||||
version: 5.3.3
|
version: 5.3.3
|
||||||
resolution: "typescript@npm:5.3.3"
|
resolution: "typescript@npm:5.3.3"
|
||||||
|
|||||||
Reference in New Issue
Block a user