Update workspace entities to make all TEXT nullable (#16144)
Follow up on #15926 --------- Co-authored-by: Etienne <45695613+etiennejouan@users.noreply.github.com> Co-authored-by: guillim <guigloo@msn.com>
This commit is contained in:
+5
@@ -1,6 +1,7 @@
|
||||
import { InjectRepository } from '@nestjs/typeorm';
|
||||
|
||||
import { Command } from 'nest-commander';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ActiveOrSuspendedWorkspacesMigrationCommandRunner } from 'src/database/commands/command-runners/active-or-suspended-workspaces-migration.command-runner';
|
||||
@@ -62,6 +63,10 @@ export class MigrateAttachmentTypeToFileCategoryCommand extends ActiveOrSuspende
|
||||
for (const attachment of attachments) {
|
||||
const { id, type } = attachment;
|
||||
|
||||
if (!isDefined(type)) {
|
||||
throw new Error(`Attachment ${id} has no type`);
|
||||
}
|
||||
|
||||
const fileCategory =
|
||||
TYPE_TO_FILE_CATEGORY_MAPPING[type] ||
|
||||
TYPE_TO_FILE_CATEGORY_MAPPING.Other;
|
||||
|
||||
+6
-2
@@ -7,10 +7,10 @@ import { msg } from '@lingui/core/macro';
|
||||
import { render } from '@react-email/render';
|
||||
import { SendApprovedAccessDomainValidation } from 'twenty-emails';
|
||||
import { SettingsPath } from 'twenty-shared/types';
|
||||
import { getSettingsPath } from 'twenty-shared/utils';
|
||||
import { getSettingsPath, isDefined } from 'twenty-shared/utils';
|
||||
import { Repository } from 'typeorm';
|
||||
|
||||
import { ApprovedAccessDomainEntity as ApprovedAccessDomainEntity } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.entity';
|
||||
import { ApprovedAccessDomainEntity } from 'src/engine/core-modules/approved-access-domain/approved-access-domain.entity';
|
||||
import {
|
||||
ApprovedAccessDomainException,
|
||||
ApprovedAccessDomainExceptionCode,
|
||||
@@ -70,6 +70,10 @@ export class ApprovedAccessDomainService {
|
||||
},
|
||||
});
|
||||
|
||||
if (!isDefined(sender.userEmail)) {
|
||||
throw new Error(`Sender ${sender.id} has an empty userEmail`);
|
||||
}
|
||||
|
||||
const emailTemplate = SendApprovedAccessDomainValidation({
|
||||
link: link.toString(),
|
||||
workspace: {
|
||||
|
||||
+5
-3
@@ -112,7 +112,7 @@ export class TimelineCalendarEventService {
|
||||
participant.person?.avatarUrl ||
|
||||
participant.workspaceMember?.avatarUrl ||
|
||||
'',
|
||||
handle: participant.handle,
|
||||
handle: participant.handle ?? '',
|
||||
}),
|
||||
);
|
||||
|
||||
@@ -139,15 +139,17 @@ export class TimelineCalendarEventService {
|
||||
title:
|
||||
visibility === CalendarChannelVisibility.METADATA
|
||||
? FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED
|
||||
: event.title,
|
||||
: (event.title ?? ''),
|
||||
description:
|
||||
visibility === CalendarChannelVisibility.METADATA
|
||||
? FIELD_RESTRICTED_ADDITIONAL_PERMISSIONS_REQUIRED
|
||||
: event.description,
|
||||
: (event.description ?? ''),
|
||||
startsAt: event.startsAt as unknown as Date,
|
||||
endsAt: event.endsAt as unknown as Date,
|
||||
participants,
|
||||
visibility,
|
||||
location: event.location ?? '',
|
||||
conferenceSolution: event.conferenceSolution ?? '',
|
||||
};
|
||||
});
|
||||
|
||||
|
||||
+1
-1
@@ -31,7 +31,7 @@ export class FileAttachmentListener {
|
||||
FileDeletionJob.name,
|
||||
{
|
||||
workspaceId: payload.workspaceId,
|
||||
fullPath: event.properties.before.fullPath,
|
||||
fullPath: event.properties.before.fullPath ?? '',
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
+1
-1
@@ -35,7 +35,7 @@ export class FileWorkspaceMemberListener {
|
||||
|
||||
this.messageQueueService.add<FileDeletionJobData>(FileDeletionJob.name, {
|
||||
workspaceId: payload.workspaceId,
|
||||
fullPath: event.properties.before.avatarUrl,
|
||||
fullPath: event.properties.before.avatarUrl ?? '',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
+2
-1
@@ -8,6 +8,7 @@ import {
|
||||
import { Args, Mutation, Query, Resolver } from '@nestjs/graphql';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { UUIDScalarType } from 'src/engine/api/graphql/workspace-schema-builder/graphql-types/scalars';
|
||||
import { AuthGraphqlApiExceptionFilter } from 'src/engine/core-modules/auth/filters/auth-graphql-api-exception.filter';
|
||||
@@ -59,7 +60,7 @@ export class ImapSmtpCaldavResolver {
|
||||
where: { id, provider: ConnectedAccountProvider.IMAP_SMTP_CALDAV },
|
||||
});
|
||||
|
||||
if (!connectedAccount) {
|
||||
if (!isDefined(connectedAccount) || !isDefined(connectedAccount?.handle)) {
|
||||
throw new UserInputError(
|
||||
`Connected mail account with ID ${id} not found`,
|
||||
);
|
||||
|
||||
+2
-2
@@ -75,8 +75,8 @@ export class TimelineMessagingService {
|
||||
|
||||
return {
|
||||
id: messageThread.id,
|
||||
subject: firstMessage.subject,
|
||||
lastMessageBody: lastMessage.text,
|
||||
subject: firstMessage.subject ?? '',
|
||||
lastMessageBody: lastMessage.text ?? '',
|
||||
lastMessageReceivedAt: lastMessage.receivedAt ?? new Date(),
|
||||
numberOfMessagesInThread: messageThread.messages.length,
|
||||
};
|
||||
|
||||
+35
-25
@@ -1,30 +1,40 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type TimelineThreadParticipantDTO } from 'src/engine/core-modules/messaging/dtos/timeline-thread-participant.dto';
|
||||
import { type MessageParticipantWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-participant.workspace-entity';
|
||||
|
||||
export const formatThreadParticipant = (
|
||||
threadParticipant: MessageParticipantWorkspaceEntity,
|
||||
): TimelineThreadParticipantDTO => ({
|
||||
personId: threadParticipant.personId,
|
||||
workspaceMemberId: threadParticipant.workspaceMemberId,
|
||||
firstName:
|
||||
threadParticipant.person?.name?.firstName ||
|
||||
threadParticipant.workspaceMember?.name.firstName ||
|
||||
'',
|
||||
lastName:
|
||||
threadParticipant.person?.name?.lastName ||
|
||||
threadParticipant.workspaceMember?.name.lastName ||
|
||||
'',
|
||||
displayName:
|
||||
threadParticipant.person?.name?.firstName ||
|
||||
threadParticipant.person?.name?.lastName ||
|
||||
threadParticipant.workspaceMember?.name.firstName ||
|
||||
threadParticipant.workspaceMember?.name.lastName ||
|
||||
threadParticipant.displayName ||
|
||||
threadParticipant.handle ||
|
||||
'',
|
||||
avatarUrl:
|
||||
threadParticipant.person?.avatarUrl ||
|
||||
threadParticipant.workspaceMember?.avatarUrl ||
|
||||
'',
|
||||
handle: threadParticipant.handle,
|
||||
});
|
||||
): TimelineThreadParticipantDTO => {
|
||||
if (!isDefined(threadParticipant.handle)) {
|
||||
throw new Error(
|
||||
`Thread participant ${threadParticipant.id} has an empty handle`,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
personId: threadParticipant.personId,
|
||||
workspaceMemberId: threadParticipant.workspaceMemberId,
|
||||
firstName:
|
||||
threadParticipant.person?.name?.firstName ||
|
||||
threadParticipant.workspaceMember?.name.firstName ||
|
||||
'',
|
||||
lastName:
|
||||
threadParticipant.person?.name?.lastName ||
|
||||
threadParticipant.workspaceMember?.name.lastName ||
|
||||
'',
|
||||
displayName:
|
||||
threadParticipant.person?.name?.firstName ||
|
||||
threadParticipant.person?.name?.lastName ||
|
||||
threadParticipant.workspaceMember?.name.firstName ||
|
||||
threadParticipant.workspaceMember?.name.lastName ||
|
||||
threadParticipant.displayName ||
|
||||
threadParticipant.handle ||
|
||||
'',
|
||||
avatarUrl:
|
||||
threadParticipant.person?.avatarUrl ||
|
||||
threadParticipant.workspaceMember?.avatarUrl ||
|
||||
'',
|
||||
handle: threadParticipant.handle,
|
||||
};
|
||||
};
|
||||
|
||||
+8
@@ -75,6 +75,10 @@ export class WorkspaceMemberTranspiler {
|
||||
|
||||
const roles = fromRoleEntitiesToRoleDtos(userWorkspaceRoles);
|
||||
|
||||
if (!isDefined(userEmail)) {
|
||||
throw new Error(`Workspace member ${id} has no userEmail`);
|
||||
}
|
||||
|
||||
return {
|
||||
id,
|
||||
name,
|
||||
@@ -111,6 +115,10 @@ export class WorkspaceMemberTranspiler {
|
||||
userEmail,
|
||||
} = workspaceMember;
|
||||
|
||||
if (!isDefined(userEmail)) {
|
||||
throw new Error(`Workspace member ${id} has no userEmail`);
|
||||
}
|
||||
|
||||
const avatarUrl = userWorkspaceId
|
||||
? this.generateSignedAvatarUrl({
|
||||
workspaceId: userWorkspaceId,
|
||||
|
||||
+1
-1
@@ -4,6 +4,7 @@ import { Args, Mutation, Resolver } from '@nestjs/graphql';
|
||||
import { PreventNestToAutoLogGraphqlErrorsFilter } from 'src/engine/core-modules/graphql/filters/prevent-nest-to-auto-log-graphql-errors.filter';
|
||||
import { ResolverValidationPipe } from 'src/engine/core-modules/graphql/pipes/resolver-validation.pipe';
|
||||
import { CreateDraftFromWorkflowVersionInput } from 'src/engine/core-modules/workflow/dtos/create-draft-from-workflow-version-input.dto';
|
||||
import { DuplicateWorkflowInput } from 'src/engine/core-modules/workflow/dtos/duplicate-workflow-input.dto';
|
||||
import { UpdateWorkflowVersionPositionsInput } from 'src/engine/core-modules/workflow/dtos/update-workflow-version-positions-input.dto';
|
||||
import { WorkflowVersionDTO } from 'src/engine/core-modules/workflow/dtos/workflow-version.dto';
|
||||
import { WorkspaceEntity } from 'src/engine/core-modules/workspace/workspace.entity';
|
||||
@@ -14,7 +15,6 @@ import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { PermissionFlagType } from 'src/engine/metadata-modules/permissions/constants/permission-flag-type.constants';
|
||||
import { PermissionsGraphqlApiExceptionFilter } from 'src/engine/metadata-modules/permissions/utils/permissions-graphql-api-exception.filter';
|
||||
import { WorkflowVersionWorkspaceService } from 'src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service';
|
||||
import { DuplicateWorkflowInput } from 'src/engine/core-modules/workflow/dtos/duplicate-workflow-input.dto';
|
||||
|
||||
@Resolver()
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
|
||||
+7
@@ -306,6 +306,13 @@ export class WorkspaceInvitationService {
|
||||
: {},
|
||||
});
|
||||
|
||||
if (!isDefined(sender.userEmail)) {
|
||||
throw new WorkspaceInvitationException(
|
||||
'Sender email is missing',
|
||||
WorkspaceInvitationExceptionCode.EMAIL_MISSING,
|
||||
);
|
||||
}
|
||||
|
||||
const emailData = {
|
||||
link: link.toString(),
|
||||
workspace: {
|
||||
|
||||
+5
-2
@@ -7,8 +7,11 @@ export const transformStandardAgentDefinitionToFlatAgent = (
|
||||
standardAgentDefinition: StandardAgentDefinition,
|
||||
workspaceId: string,
|
||||
): FlatAgent => {
|
||||
const { standardRoleId: _standardRoleId, ...agentData } =
|
||||
standardAgentDefinition;
|
||||
const {
|
||||
standardRoleId: _standardRoleId,
|
||||
outputStrategy: _outputStrategy,
|
||||
...agentData
|
||||
} = standardAgentDefinition;
|
||||
|
||||
return {
|
||||
...agentData,
|
||||
|
||||
@@ -7,9 +7,9 @@ import {
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types';
|
||||
import { DEFAULT_LABEL_IDENTIFIER_FIELD_NAME } from 'src/engine/metadata-modules/object-metadata/constants/object-metadata.constants';
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceCustomEntity } from 'src/engine/twenty-orm/decorators/workspace-custom-entity.decorator';
|
||||
import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator';
|
||||
@@ -44,7 +44,8 @@ export class CustomWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
type: FieldMetadataType.TEXT,
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
name: string;
|
||||
@WorkspaceIsNullable()
|
||||
name: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: CUSTOM_OBJECT_STANDARD_FIELD_IDS.position,
|
||||
|
||||
+8
@@ -133,6 +133,10 @@ export class CleanerWorkspaceService {
|
||||
const i18n = this.i18nService.getI18nInstance(workspaceMember.locale);
|
||||
const subject = i18n._(workspaceDeletionMsg);
|
||||
|
||||
if (!isDefined(workspaceMember.userEmail)) {
|
||||
throw new Error('Workspace member email is missing');
|
||||
}
|
||||
|
||||
this.emailService.send({
|
||||
to: workspaceMember.userEmail,
|
||||
from: `${this.twentyConfigService.get(
|
||||
@@ -207,6 +211,10 @@ export class CleanerWorkspaceService {
|
||||
const html = await render(emailTemplate, { pretty: true });
|
||||
const text = await render(emailTemplate, { plainText: true });
|
||||
|
||||
if (!isDefined(workspaceMember.userEmail)) {
|
||||
throw new Error('Workspace member email is missing');
|
||||
}
|
||||
|
||||
this.emailService.send({
|
||||
to: workspaceMember.userEmail,
|
||||
from: `${this.twentyConfigService.get(
|
||||
|
||||
+2
-3
@@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { type WorkspaceSyncContext } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/workspace-sync-context.interface';
|
||||
|
||||
import { type FlatRole } from 'src/engine/metadata-modules/flat-role/types/flat-role.type';
|
||||
import { fromStandardRoleDefinitionToFlatRole } from 'src/engine/metadata-modules/flat-role/utils/from-standard-role-definition-to-flat-role.util';
|
||||
import { type RoleEntity } from 'src/engine/metadata-modules/role/role.entity';
|
||||
import { type StandardRoleDefinition } from 'src/engine/workspace-manager/workspace-sync-metadata/standard-roles/types/standard-role-definition.interface';
|
||||
@@ -13,8 +12,8 @@ export class StandardRoleFactory {
|
||||
roleDefinitions: StandardRoleDefinition[],
|
||||
context: WorkspaceSyncContext,
|
||||
existingRoles: RoleEntity[],
|
||||
): FlatRole[] {
|
||||
const computedRoles: FlatRole[] = [];
|
||||
): Partial<RoleEntity>[] {
|
||||
const computedRoles: Partial<RoleEntity>[] = [];
|
||||
|
||||
for (const roleDefinition of roleDefinitions) {
|
||||
const existingRole = existingRoles.find(
|
||||
|
||||
+1
-1
@@ -49,7 +49,7 @@ export class WorkspaceSyncRoleService {
|
||||
|
||||
const roleComparatorResults = this.workspaceRoleComparator.compare({
|
||||
fromFlatRoles: existingStandardRoleEntities.map(fromRoleEntityToFlatRole),
|
||||
toFlatRoles: targetStandardRoles,
|
||||
toFlatRoles: targetStandardRoles.map(fromRoleEntityToFlatRole),
|
||||
});
|
||||
|
||||
for (const roleComparatorResult of roleComparatorResults) {
|
||||
|
||||
+6
-4
@@ -1,10 +1,10 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
ActorMetadata,
|
||||
FieldMetadataType,
|
||||
RelationOnDeleteAction,
|
||||
} from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
@@ -49,7 +49,8 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Attachment name`,
|
||||
icon: 'IconFileUpload',
|
||||
})
|
||||
name: string;
|
||||
@WorkspaceIsNullable()
|
||||
name: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: ATTACHMENT_STANDARD_FIELD_IDS.fullPath,
|
||||
@@ -58,7 +59,8 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Attachment full path`,
|
||||
icon: 'IconLink',
|
||||
})
|
||||
fullPath: string;
|
||||
@WorkspaceIsNullable()
|
||||
fullPath: string | null;
|
||||
|
||||
// Deprecated: Use fileCategory instead
|
||||
@WorkspaceField({
|
||||
@@ -69,7 +71,7 @@ export class AttachmentWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconList',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
type: string;
|
||||
type: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: ATTACHMENT_STANDARD_FIELD_IDS.fileCategory,
|
||||
|
||||
+4
-2
@@ -1,6 +1,6 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
@@ -8,6 +8,7 @@ import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/i
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
|
||||
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator';
|
||||
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
|
||||
@@ -34,7 +35,8 @@ export class BlocklistWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Handle`,
|
||||
icon: 'IconAt',
|
||||
})
|
||||
handle: string;
|
||||
@WorkspaceIsNullable()
|
||||
handle: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: BLOCKLIST_STANDARD_FIELD_IDS.workspaceMember,
|
||||
|
||||
+5
@@ -1,5 +1,6 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { And, Any, ILike, In, Not, Or } from 'typeorm';
|
||||
|
||||
import { type ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
|
||||
@@ -54,6 +55,10 @@ export class BlocklistItemDeleteCalendarEventsJob {
|
||||
acc.set(workspaceMemberId, []);
|
||||
}
|
||||
|
||||
if (!isDefined(handle)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc.get(workspaceMemberId)?.push(handle);
|
||||
|
||||
return acc;
|
||||
|
||||
+4
-1
@@ -1,5 +1,7 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { CalDAVClient } from 'src/modules/calendar/calendar-event-import-manager/drivers/caldav/lib/caldav.client';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
@@ -13,7 +15,8 @@ export class CalDavClientProvider {
|
||||
): Promise<CalDAVClient> {
|
||||
if (
|
||||
!connectedAccount.connectionParameters?.CALDAV?.password ||
|
||||
!connectedAccount.connectionParameters?.CALDAV?.host
|
||||
!connectedAccount.connectionParameters?.CALDAV?.host ||
|
||||
!isDefined(connectedAccount.handle)
|
||||
) {
|
||||
throw new Error('Missing required CalDAV connection parameters');
|
||||
}
|
||||
|
||||
+2
@@ -9,4 +9,6 @@ export enum CalendarEventImportDriverExceptionCode {
|
||||
SYNC_CURSOR_ERROR = 'SYNC_CURSOR_ERROR',
|
||||
UNKNOWN = 'UNKNOWN',
|
||||
UNKNOWN_NETWORK_ERROR = 'UNKNOWN_NETWORK_ERROR',
|
||||
HANDLE_ALIASES_REQUIRED = 'HANDLE_ALIASES_REQUIRED',
|
||||
CHANNEL_MISCONFIGURED = 'CHANNEL_MISCONFIGURED',
|
||||
}
|
||||
|
||||
+1
@@ -67,6 +67,7 @@ export class CalendarEventImportErrorHandlerService {
|
||||
case CalendarEventImportDriverExceptionCode.SYNC_CURSOR_ERROR:
|
||||
await this.handleSyncCursorErrorException(calendarChannel, workspaceId);
|
||||
break;
|
||||
case CalendarEventImportDriverExceptionCode.CHANNEL_MISCONFIGURED:
|
||||
case CalendarEventImportDriverExceptionCode.UNKNOWN:
|
||||
case CalendarEventImportDriverExceptionCode.UNKNOWN_NETWORK_ERROR:
|
||||
default:
|
||||
|
||||
+16
-1
@@ -1,5 +1,6 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { Any } from 'typeorm';
|
||||
|
||||
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
|
||||
@@ -11,6 +12,10 @@ import { BlocklistRepository } from 'src/modules/blocklist/repositories/blocklis
|
||||
import { BlocklistWorkspaceEntity } from 'src/modules/blocklist/standard-objects/blocklist.workspace-entity';
|
||||
import { CalendarEventCleanerService } from 'src/modules/calendar/calendar-event-cleaner/services/calendar-event-cleaner.service';
|
||||
import { CALENDAR_EVENT_IMPORT_BATCH_SIZE } from 'src/modules/calendar/calendar-event-import-manager/constants/calendar-event-import-batch-size';
|
||||
import {
|
||||
CalendarEventImportDriverException,
|
||||
CalendarEventImportDriverExceptionCode,
|
||||
} from 'src/modules/calendar/calendar-event-import-manager/drivers/exceptions/calendar-event-import-driver.exception';
|
||||
import { MicrosoftCalendarImportEventsService } from 'src/modules/calendar/calendar-event-import-manager/drivers/microsoft-calendar/services/microsoft-calendar-import-events.service';
|
||||
import {
|
||||
CalendarEventImportErrorHandlerService,
|
||||
@@ -92,6 +97,16 @@ export class CalendarEventsImportService {
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (
|
||||
!isDefined(connectedAccount.handleAliases) ||
|
||||
!isDefined(calendarChannel.handle)
|
||||
) {
|
||||
throw new CalendarEventImportDriverException(
|
||||
'Calendar channel handle or Handle aliases are required',
|
||||
CalendarEventImportDriverExceptionCode.CHANNEL_MISCONFIGURED,
|
||||
);
|
||||
}
|
||||
|
||||
const { filteredEvents, cancelledEvents } =
|
||||
filterEventsAndReturnCancelledEvents(
|
||||
[
|
||||
@@ -99,7 +114,7 @@ export class CalendarEventsImportService {
|
||||
...connectedAccount.handleAliases.split(','),
|
||||
],
|
||||
calendarEvents,
|
||||
blocklist.map((blocklist) => blocklist.handle),
|
||||
blocklist.map((blocklist) => blocklist.handle ?? ''),
|
||||
);
|
||||
|
||||
const cancelledEventExternalIds = cancelledEvents.map(
|
||||
|
||||
+9
@@ -1,5 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
|
||||
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||
@@ -58,6 +60,13 @@ export class CalendarFetchEventsService {
|
||||
refreshToken,
|
||||
};
|
||||
|
||||
if (!isDefined(calendarChannel.syncCursor)) {
|
||||
throw new CalendarEventImportDriverException(
|
||||
'Sync cursor is required',
|
||||
CalendarEventImportDriverExceptionCode.SYNC_CURSOR_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
const getCalendarEventsResponse =
|
||||
await this.getCalendarEventsService.getCalendarEvents(
|
||||
connectedAccountWithFreshTokens,
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ export const mapCalendarEventsByICalUID = (
|
||||
): Map<string, string> => {
|
||||
return new Map<string, string>(
|
||||
existingCalendarEvents.map((calendarEvent) => [
|
||||
calendarEvent.iCalUID,
|
||||
calendarEvent.iCalUID ?? '',
|
||||
calendarEvent.id,
|
||||
]),
|
||||
);
|
||||
|
||||
+5
-2
@@ -3,8 +3,8 @@ import { Injectable } from '@nestjs/common';
|
||||
import { isDefined } from 'class-validator';
|
||||
import chunk from 'lodash.chunk';
|
||||
import differenceWith from 'lodash.differencewith';
|
||||
import { Any } from 'typeorm';
|
||||
import { FieldActorSource } from 'twenty-shared/types';
|
||||
import { Any } from 'typeorm';
|
||||
|
||||
import { InjectMessageQueue } from 'src/engine/core-modules/message-queue/decorators/message-queue.decorator';
|
||||
import { MessageQueue } from 'src/engine/core-modules/message-queue/message-queue.constants';
|
||||
@@ -158,7 +158,10 @@ export class CalendarEventParticipantService {
|
||||
{
|
||||
workspaceId,
|
||||
connectedAccount,
|
||||
contactsToCreate: savedParticipants,
|
||||
contactsToCreate: savedParticipants.map((participant) => ({
|
||||
handle: participant.handle ?? '',
|
||||
displayName: participant.displayName ?? participant.handle ?? '',
|
||||
})),
|
||||
source: FieldActorSource.CALENDAR,
|
||||
},
|
||||
);
|
||||
|
||||
+6
-3
@@ -1,6 +1,6 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
@@ -9,6 +9,7 @@ import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceField } from 'src/engine/twenty-orm/decorators/workspace-field.decorator';
|
||||
import { WorkspaceIsNotAuditLogged } from 'src/engine/twenty-orm/decorators/workspace-is-not-audit-logged.decorator';
|
||||
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
|
||||
import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is-system.decorator';
|
||||
import { WorkspaceJoinColumn } from 'src/engine/twenty-orm/decorators/workspace-join-column.decorator';
|
||||
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
|
||||
@@ -42,7 +43,8 @@ export class CalendarChannelEventAssociationWorkspaceEntity extends BaseWorkspac
|
||||
description: msg`Event external ID`,
|
||||
icon: 'IconCalendar',
|
||||
})
|
||||
eventExternalId: string;
|
||||
@WorkspaceIsNullable()
|
||||
eventExternalId: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId:
|
||||
@@ -52,7 +54,8 @@ export class CalendarChannelEventAssociationWorkspaceEntity extends BaseWorkspac
|
||||
description: msg`Recurring Event ID`,
|
||||
icon: 'IconHistory',
|
||||
})
|
||||
recurringEventExternalId: string;
|
||||
@WorkspaceIsNullable()
|
||||
recurringEventExternalId: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId:
|
||||
|
||||
+5
-3
@@ -1,8 +1,8 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
@@ -87,7 +87,8 @@ export class CalendarChannelWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Handle`,
|
||||
icon: 'IconAt',
|
||||
})
|
||||
handle: string;
|
||||
@WorkspaceIsNullable()
|
||||
handle: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: CALENDAR_CHANNEL_STANDARD_FIELD_IDS.syncStatus,
|
||||
@@ -280,7 +281,8 @@ export class CalendarChannelWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Sync Cursor. Used for syncing events from the calendar provider`,
|
||||
icon: 'IconReload',
|
||||
})
|
||||
syncCursor: string;
|
||||
@WorkspaceIsNullable()
|
||||
syncCursor: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: CALENDAR_CHANNEL_STANDARD_FIELD_IDS.syncedAt,
|
||||
|
||||
+5
-3
@@ -1,6 +1,6 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
@@ -47,7 +47,8 @@ export class CalendarEventParticipantWorkspaceEntity extends BaseWorkspaceEntity
|
||||
description: msg`Handle`,
|
||||
icon: 'IconMail',
|
||||
})
|
||||
handle: string;
|
||||
@WorkspaceIsNullable()
|
||||
handle: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: CALENDAR_EVENT_PARTICIPANT_STANDARD_FIELD_IDS.displayName,
|
||||
@@ -56,7 +57,8 @@ export class CalendarEventParticipantWorkspaceEntity extends BaseWorkspaceEntity
|
||||
description: msg`Display Name`,
|
||||
icon: 'IconUser',
|
||||
})
|
||||
displayName: string;
|
||||
@WorkspaceIsNullable()
|
||||
displayName: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: CALENDAR_EVENT_PARTICIPANT_STANDARD_FIELD_IDS.isOrganizer,
|
||||
|
||||
+12
-7
@@ -1,10 +1,10 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
RelationOnDeleteAction,
|
||||
LinksMetadata,
|
||||
RelationOnDeleteAction,
|
||||
} from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
@@ -43,7 +43,8 @@ export class CalendarEventWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Title`,
|
||||
icon: 'IconH1',
|
||||
})
|
||||
title: string;
|
||||
@WorkspaceIsNullable()
|
||||
title: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: CALENDAR_EVENT_STANDARD_FIELD_IDS.isCanceled,
|
||||
@@ -112,7 +113,8 @@ export class CalendarEventWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Description`,
|
||||
icon: 'IconFileDescription',
|
||||
})
|
||||
description: string;
|
||||
@WorkspaceIsNullable()
|
||||
description: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: CALENDAR_EVENT_STANDARD_FIELD_IDS.location,
|
||||
@@ -121,7 +123,8 @@ export class CalendarEventWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Location`,
|
||||
icon: 'IconMapPin',
|
||||
})
|
||||
location: string;
|
||||
@WorkspaceIsNullable()
|
||||
location: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: CALENDAR_EVENT_STANDARD_FIELD_IDS.iCalUID,
|
||||
@@ -130,7 +133,8 @@ export class CalendarEventWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`iCal UID`,
|
||||
icon: 'IconKey',
|
||||
})
|
||||
iCalUID: string;
|
||||
@WorkspaceIsNullable()
|
||||
iCalUID: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: CALENDAR_EVENT_STANDARD_FIELD_IDS.conferenceSolution,
|
||||
@@ -139,7 +143,8 @@ export class CalendarEventWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Conference Solution`,
|
||||
icon: 'IconScreenShare',
|
||||
})
|
||||
conferenceSolution: string;
|
||||
@WorkspaceIsNullable()
|
||||
conferenceSolution: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: CALENDAR_EVENT_STANDARD_FIELD_IDS.conferenceLink,
|
||||
|
||||
+4
-4
@@ -1,4 +1,5 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
ActorMetadata,
|
||||
AddressMetadata,
|
||||
@@ -7,13 +8,12 @@ import {
|
||||
RelationOnDeleteAction,
|
||||
type CurrencyMetadata,
|
||||
} from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types';
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceDuplicateCriteria } from 'src/engine/twenty-orm/decorators/workspace-duplicate-criteria.decorator';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
@@ -72,7 +72,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconBuildingSkyscraper',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
name: string;
|
||||
name: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: COMPANY_STANDARD_FIELD_IDS.domainName,
|
||||
@@ -282,7 +282,7 @@ export class CompanyWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
})
|
||||
@WorkspaceIsDeprecated()
|
||||
@WorkspaceIsNullable()
|
||||
addressOld: string;
|
||||
addressOld: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: COMPANY_STANDARD_FIELD_IDS.searchVector,
|
||||
|
||||
+4
@@ -0,0 +1,4 @@
|
||||
export enum OAuth2ClientManagerExceptionCode {
|
||||
ACCESS_TOKEN_REQUIRED = 'ACCESS_TOKEN_REQUIRED',
|
||||
REFRESH_TOKEN_REQUIRED = 'REFRESH_TOKEN_REQUIRED',
|
||||
}
|
||||
+16
@@ -2,9 +2,11 @@ import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { type Client } from '@microsoft/microsoft-graph-client';
|
||||
import { type Auth } from 'googleapis';
|
||||
import { CustomError, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { GoogleOAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/drivers/google/google-oauth2-client-manager.service';
|
||||
import { MicrosoftOAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/drivers/microsoft/microsoft-oauth2-client-manager.service';
|
||||
import { OAuth2ClientManagerExceptionCode } from 'src/modules/connected-account/oauth2-client-manager/exceptions/oauth2-client-manager.exceptions';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
@Injectable()
|
||||
@@ -20,6 +22,13 @@ export class OAuth2ClientManagerService {
|
||||
'provider' | 'refreshToken'
|
||||
>,
|
||||
): Promise<Auth.OAuth2Client> {
|
||||
if (!isDefined(connectedAccount.refreshToken)) {
|
||||
throw new CustomError(
|
||||
'Refresh token is required',
|
||||
OAuth2ClientManagerExceptionCode.REFRESH_TOKEN_REQUIRED,
|
||||
);
|
||||
}
|
||||
|
||||
return this.googleOAuth2ClientManagerService.getOAuth2Client(
|
||||
connectedAccount.refreshToken,
|
||||
);
|
||||
@@ -31,6 +40,13 @@ export class OAuth2ClientManagerService {
|
||||
'provider' | 'accessToken'
|
||||
>,
|
||||
): Promise<Client> {
|
||||
if (!isDefined(connectedAccount.accessToken)) {
|
||||
throw new CustomError(
|
||||
'Access token is required',
|
||||
OAuth2ClientManagerExceptionCode.ACCESS_TOKEN_REQUIRED,
|
||||
);
|
||||
}
|
||||
|
||||
return this.microsoftOAuth2ClientManagerService.getOAuth2Client(
|
||||
connectedAccount.accessToken,
|
||||
);
|
||||
|
||||
+1
@@ -7,4 +7,5 @@ export enum ConnectedAccountRefreshAccessTokenExceptionCode {
|
||||
INVALID_REFRESH_TOKEN = 'INVALID_REFRESH_TOKEN',
|
||||
PROVIDER_NOT_SUPPORTED = 'PROVIDER_NOT_SUPPORTED',
|
||||
TEMPORARY_NETWORK_ERROR = 'TEMPORARY_NETWORK_ERROR',
|
||||
ACCESS_TOKEN_NOT_FOUND = 'ACCESS_TOKEN_NOT_FOUND',
|
||||
}
|
||||
|
||||
+7
-1
@@ -1,7 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
import { assertUnreachable, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { TwentyORMManager } from 'src/engine/twenty-orm/twenty-orm.manager';
|
||||
import { GoogleAPIRefreshAccessTokenService } from 'src/modules/connected-account/refresh-tokens-manager/drivers/google/services/google-api-refresh-tokens.service';
|
||||
@@ -52,6 +52,12 @@ export class ConnectedAccountRefreshTokensService {
|
||||
this.logger.debug(
|
||||
`Reusing valid access token for connected account ${connectedAccount.id.slice(0, 7)} in workspace ${workspaceId.slice(0, 7)}`,
|
||||
);
|
||||
if (!isDefined(accessToken)) {
|
||||
throw new ConnectedAccountRefreshAccessTokenException(
|
||||
`Access token is required for connected account ${connectedAccount.id} in workspace ${workspaceId}`,
|
||||
ConnectedAccountRefreshAccessTokenExceptionCode.ACCESS_TOKEN_NOT_FOUND,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
accessToken,
|
||||
|
||||
+12
-6
@@ -1,10 +1,10 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
ConnectedAccountProvider,
|
||||
FieldMetadataType,
|
||||
RelationOnDeleteAction,
|
||||
} from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
@@ -42,7 +42,8 @@ export class ConnectedAccountWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`The account handle (email, username, phone number, etc.)`,
|
||||
icon: 'IconMail',
|
||||
})
|
||||
handle: string;
|
||||
@WorkspaceIsNullable()
|
||||
handle: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: CONNECTED_ACCOUNT_STANDARD_FIELD_IDS.provider,
|
||||
@@ -50,6 +51,7 @@ export class ConnectedAccountWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
label: msg`provider`,
|
||||
description: msg`The account provider`,
|
||||
icon: 'IconSettings',
|
||||
defaultValue: `'${ConnectedAccountProvider.GOOGLE}'`,
|
||||
})
|
||||
provider: ConnectedAccountProvider; // field metadata should be a SELECT
|
||||
|
||||
@@ -60,7 +62,8 @@ export class ConnectedAccountWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Messaging provider access token`,
|
||||
icon: 'IconKey',
|
||||
})
|
||||
accessToken: string;
|
||||
@WorkspaceIsNullable()
|
||||
accessToken: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: CONNECTED_ACCOUNT_STANDARD_FIELD_IDS.refreshToken,
|
||||
@@ -69,7 +72,8 @@ export class ConnectedAccountWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Messaging provider refresh token`,
|
||||
icon: 'IconKey',
|
||||
})
|
||||
refreshToken: string;
|
||||
@WorkspaceIsNullable()
|
||||
refreshToken: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: CONNECTED_ACCOUNT_STANDARD_FIELD_IDS.lastCredentialsRefreshedAt,
|
||||
@@ -88,7 +92,8 @@ export class ConnectedAccountWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Last sync history ID`,
|
||||
icon: 'IconHistory',
|
||||
})
|
||||
lastSyncHistoryId: string;
|
||||
@WorkspaceIsNullable()
|
||||
lastSyncHistoryId: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: CONNECTED_ACCOUNT_STANDARD_FIELD_IDS.authFailedAt,
|
||||
@@ -107,7 +112,8 @@ export class ConnectedAccountWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Handle Aliases`,
|
||||
icon: 'IconMail',
|
||||
})
|
||||
handleAliases: string;
|
||||
@WorkspaceIsNullable()
|
||||
handleAliases: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: CONNECTED_ACCOUNT_STANDARD_FIELD_IDS.scopes,
|
||||
|
||||
+5
@@ -1,3 +1,5 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import { type Contact } from 'src/modules/contact-creation-manager/types/contact.type';
|
||||
import { getDomainNameFromHandle } from 'src/modules/contact-creation-manager/utils/get-domain-name-from-handle.util';
|
||||
@@ -9,6 +11,9 @@ export function filterOutContactsThatBelongToSelfOrWorkspaceMembers(
|
||||
connectedAccount: ConnectedAccountWorkspaceEntity,
|
||||
workspaceMembers: WorkspaceMemberWorkspaceEntity[],
|
||||
): Contact[] {
|
||||
if (!isDefined(connectedAccount.handle)) {
|
||||
throw new Error('Connected account handle is missing');
|
||||
}
|
||||
const selfDomainName = getDomainNameFromHandle(
|
||||
connectedAccount.handle,
|
||||
).toLowerCase();
|
||||
|
||||
+4
-2
@@ -1,6 +1,6 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
@@ -12,6 +12,7 @@ import { WorkspaceIsSystem } from 'src/engine/twenty-orm/decorators/workspace-is
|
||||
import { WorkspaceRelation } from 'src/engine/twenty-orm/decorators/workspace-relation.decorator';
|
||||
import { FAVORITE_FOLDER_STANDARD_FIELD_IDS } from 'src/engine/workspace-manager/workspace-sync-metadata/constants/standard-field-ids';
|
||||
import { FavoriteWorkspaceEntity } from 'src/modules/favorite/standard-objects/favorite.workspace-entity';
|
||||
import { WorkspaceIsNullable } from 'src/engine/twenty-orm/decorators/workspace-is-nullable.decorator';
|
||||
|
||||
@WorkspaceEntity({
|
||||
standardId: STANDARD_OBJECT_IDS.favoriteFolder,
|
||||
@@ -42,7 +43,8 @@ export class FavoriteFolderWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Name of the favorite folder`,
|
||||
icon: 'IconText',
|
||||
})
|
||||
name: string;
|
||||
@WorkspaceIsNullable()
|
||||
name: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: FAVORITE_FOLDER_STANDARD_FIELD_IDS.favorites,
|
||||
|
||||
@@ -119,7 +119,7 @@ export class MatchParticipantService<
|
||||
for (const participants of chunkedParticipants) {
|
||||
const uniqueParticipantsHandles = [
|
||||
...new Set(participants.map((participant) => participant.handle)),
|
||||
];
|
||||
].filter(isDefined);
|
||||
|
||||
const queryBuilder = addPersonEmailFiltersToQueryBuilder({
|
||||
queryBuilder: personRepository.createQueryBuilder('person'),
|
||||
@@ -140,6 +140,10 @@ export class MatchParticipantService<
|
||||
);
|
||||
|
||||
const partipantsToBeUpdated = participants
|
||||
.map((participant) => ({
|
||||
...participant,
|
||||
handle: participant.handle ?? '',
|
||||
}))
|
||||
.map((participant) => {
|
||||
const person = findPersonByPrimaryOrAdditionalEmail({
|
||||
people,
|
||||
|
||||
+5
@@ -1,5 +1,6 @@
|
||||
import { Scope } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { And, Any, ILike, In, Not, Or } from 'typeorm';
|
||||
|
||||
import { type ObjectRecordCreateEvent } from 'src/engine/core-modules/event-emitter/types/object-record-create.event';
|
||||
@@ -54,6 +55,10 @@ export class BlocklistItemDeleteMessagesJob {
|
||||
acc.set(workspaceMemberId, []);
|
||||
}
|
||||
|
||||
if (!isDefined(handle)) {
|
||||
return acc;
|
||||
}
|
||||
|
||||
acc.get(workspaceMemberId)?.push(handle);
|
||||
|
||||
return acc;
|
||||
|
||||
+4
-2
@@ -146,7 +146,8 @@ export class MessageChannelWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Handle`,
|
||||
icon: 'IconAt',
|
||||
})
|
||||
handle: string;
|
||||
@WorkspaceIsNullable()
|
||||
handle: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_CHANNEL_STANDARD_FIELD_IDS.type,
|
||||
@@ -304,7 +305,8 @@ export class MessageChannelWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Last sync cursor`,
|
||||
icon: 'IconHistory',
|
||||
})
|
||||
syncCursor: string;
|
||||
@WorkspaceIsNullable()
|
||||
syncCursor: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_CHANNEL_STANDARD_FIELD_IDS.syncedAt,
|
||||
|
||||
+5
-3
@@ -1,9 +1,9 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
import { Relation } from 'typeorm';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
|
||||
@@ -47,7 +47,8 @@ export class MessageFolderWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Folder name`,
|
||||
icon: 'IconFolder',
|
||||
})
|
||||
name: string;
|
||||
@WorkspaceIsNullable()
|
||||
name: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: MESSAGE_FOLDER_STANDARD_FIELD_IDS.messageChannel,
|
||||
@@ -68,7 +69,8 @@ export class MessageFolderWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Sync Cursor`,
|
||||
icon: 'IconHash',
|
||||
})
|
||||
syncCursor: string;
|
||||
@WorkspaceIsNullable()
|
||||
syncCursor: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_FOLDER_STANDARD_FIELD_IDS.isSentFolder,
|
||||
|
||||
+5
-3
@@ -1,6 +1,6 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
@@ -55,7 +55,8 @@ export class MessageParticipantWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Handle`,
|
||||
icon: 'IconAt',
|
||||
})
|
||||
handle: string;
|
||||
@WorkspaceIsNullable()
|
||||
handle: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_PARTICIPANT_STANDARD_FIELD_IDS.displayName,
|
||||
@@ -64,7 +65,8 @@ export class MessageParticipantWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Display Name`,
|
||||
icon: 'IconUser',
|
||||
})
|
||||
displayName: string;
|
||||
@WorkspaceIsNullable()
|
||||
displayName: string | null;
|
||||
|
||||
@WorkspaceRelation({
|
||||
standardId: MESSAGE_PARTICIPANT_STANDARD_FIELD_IDS.message,
|
||||
|
||||
+7
-4
@@ -1,6 +1,6 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
@@ -39,7 +39,8 @@ export class MessageWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Message id from the message header`,
|
||||
icon: 'IconHash',
|
||||
})
|
||||
headerMessageId: string;
|
||||
@WorkspaceIsNullable()
|
||||
headerMessageId: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_STANDARD_FIELD_IDS.subject,
|
||||
@@ -48,7 +49,8 @@ export class MessageWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Subject`,
|
||||
icon: 'IconMessage',
|
||||
})
|
||||
subject: string;
|
||||
@WorkspaceIsNullable()
|
||||
subject: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_STANDARD_FIELD_IDS.text,
|
||||
@@ -57,7 +59,8 @@ export class MessageWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Text`,
|
||||
icon: 'IconMessage',
|
||||
})
|
||||
text: string;
|
||||
@WorkspaceIsNullable()
|
||||
text: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: MESSAGE_STANDARD_FIELD_IDS.receivedAt,
|
||||
|
||||
+2
-2
@@ -218,7 +218,7 @@ export class SyncMessageFoldersService {
|
||||
if (isDefined(existingFolder.externalId)) {
|
||||
existingFolderMap.set(existingFolder.externalId, existingFolder);
|
||||
}
|
||||
existingFolderMap.set(existingFolder.name, existingFolder);
|
||||
existingFolderMap.set(existingFolder.name ?? '', existingFolder);
|
||||
}
|
||||
|
||||
return existingFolderMap;
|
||||
@@ -250,6 +250,6 @@ export class SyncMessageFoldersService {
|
||||
return MessageFolderName.SENT_ITEMS;
|
||||
}
|
||||
|
||||
return folder.name;
|
||||
return folder.name ?? '';
|
||||
}
|
||||
}
|
||||
|
||||
+2
@@ -43,4 +43,6 @@ export enum MessageImportDriverExceptionCode {
|
||||
SYNC_CURSOR_ERROR = 'SYNC_CURSOR_ERROR',
|
||||
PROVIDER_NOT_SUPPORTED = 'PROVIDER_NOT_SUPPORTED',
|
||||
CLIENT_NOT_AVAILABLE = 'CLIENT_NOT_AVAILABLE',
|
||||
ACCESS_TOKEN_MISSING = 'ACCESS_TOKEN_MISSING',
|
||||
CHANNEL_MISCONFIGURED = 'CHANNEL_MISCONFIGURED',
|
||||
}
|
||||
|
||||
+10
@@ -5,6 +5,10 @@ import { type gmail_v1 as gmailV1 } from 'googleapis';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
|
||||
import { GmailFetchByBatchService } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-fetch-by-batch.service';
|
||||
import { GmailMessagesImportErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-messages-import-error-handler.service';
|
||||
import { parseAndFormatGmailMessage } from 'src/modules/messaging/message-import-manager/drivers/gmail/utils/parse-and-format-gmail-message.util';
|
||||
@@ -24,6 +28,12 @@ export class GmailGetMessagesService {
|
||||
'accessToken' | 'id' | 'handle' | 'handleAliases'
|
||||
>,
|
||||
): Promise<MessageWithParticipants[]> {
|
||||
if (!isDefined(connectedAccount.accessToken)) {
|
||||
throw new MessageImportDriverException(
|
||||
'Access token is required',
|
||||
MessageImportDriverExceptionCode.ACCESS_TOKEN_MISSING,
|
||||
);
|
||||
}
|
||||
const { messageIdsByBatch, batchResponses } =
|
||||
await this.fetchByBatchesService.fetchAllByBatches(
|
||||
messageIds,
|
||||
|
||||
+19
-3
@@ -1,12 +1,15 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { ImapFlow } from 'imapflow';
|
||||
import { ConnectedAccountProvider } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { CustomError, isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type ImapSmtpCaldavParams } from 'src/engine/core-modules/imap-smtp-caldav-connection/types/imap-smtp-caldav-connection.type';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
|
||||
|
||||
type ConnectedAccountIdentifier = Pick<
|
||||
ConnectedAccountWorkspaceEntity,
|
||||
@@ -83,13 +86,26 @@ export class ImapClientProvider {
|
||||
let client: ImapFlow | null = null;
|
||||
let timeoutId: NodeJS.Timeout | null = null;
|
||||
|
||||
if (!isDefined(connectionParameters.IMAP?.username)) {
|
||||
throw new MessageImportDriverException(
|
||||
'Username is required',
|
||||
MessageImportDriverExceptionCode.CHANNEL_MISCONFIGURED,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(connectedAccount.handle)) {
|
||||
throw new CustomError(
|
||||
'Handle is required',
|
||||
MessageImportDriverExceptionCode.CHANNEL_MISCONFIGURED,
|
||||
);
|
||||
}
|
||||
try {
|
||||
client = new ImapFlow({
|
||||
host: connectionParameters.IMAP?.host || '',
|
||||
port: connectionParameters.IMAP?.port || 993,
|
||||
secure: connectionParameters.IMAP?.secure,
|
||||
auth: {
|
||||
user: isNonEmptyString(connectionParameters.IMAP?.username)
|
||||
user: isDefined(connectionParameters.IMAP?.username)
|
||||
? connectionParameters.IMAP?.username
|
||||
: connectedAccount.handle,
|
||||
pass: connectionParameters.IMAP?.password || '',
|
||||
|
||||
+15
-3
@@ -1,11 +1,16 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { type ImapFlow } from 'imapflow';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type MessageFolderWorkspaceEntity } from 'src/modules/messaging/common/standard-objects/message-folder.workspace-entity';
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
|
||||
import { ImapClientProvider } from 'src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider';
|
||||
import { ImapMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-list-fetch-error-handler.service';
|
||||
import { ImapIncrementalSyncService } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-incremental-sync.service';
|
||||
import { ImapMessageListFetchErrorHandler } from 'src/modules/messaging/message-import-manager/drivers/imap/services/imap-message-list-fetch-error-handler.service';
|
||||
import { createSyncCursor } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/create-sync-cursor.util';
|
||||
import { extractMailboxState } from 'src/modules/messaging/message-import-manager/drivers/imap/utils/extract-mailbox-state.util';
|
||||
import {
|
||||
@@ -67,7 +72,7 @@ export class ImapGetMessageListService {
|
||||
result.push({
|
||||
messageExternalIds: [],
|
||||
nextSyncCursor: folder.syncCursor || '',
|
||||
previousSyncCursor: folder.syncCursor,
|
||||
previousSyncCursor: folder.syncCursor || '',
|
||||
messageExternalIdsToDelete: [],
|
||||
folderId: folder.id,
|
||||
});
|
||||
@@ -86,7 +91,7 @@ export class ImapGetMessageListService {
|
||||
return messageFolders.map((folder) => ({
|
||||
messageExternalIds: [],
|
||||
nextSyncCursor: folder.syncCursor || '',
|
||||
previousSyncCursor: folder.syncCursor,
|
||||
previousSyncCursor: folder.syncCursor || '',
|
||||
messageExternalIdsToDelete: [],
|
||||
folderId: folder.id,
|
||||
}));
|
||||
@@ -102,6 +107,13 @@ export class ImapGetMessageListService {
|
||||
folder: string,
|
||||
messageFolder: Pick<MessageFolderWorkspaceEntity, 'syncCursor'>,
|
||||
): Promise<GetOneMessageListResponse> {
|
||||
if (!isDefined(messageFolder.syncCursor)) {
|
||||
throw new MessageImportDriverException(
|
||||
'Message folder sync cursor is required',
|
||||
MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
const { messages, messageExternalUidsToDelete, syncCursor } =
|
||||
await this.getMessagesFromFolder(
|
||||
client,
|
||||
|
||||
+8
@@ -6,6 +6,7 @@ import {
|
||||
type PageIteratorCallback,
|
||||
} from '@microsoft/microsoft-graph-client';
|
||||
import { isNonEmptyString } from '@sniptt/guards';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
@@ -115,6 +116,13 @@ export class MicrosoftGetMessageListService {
|
||||
this.microsoftMessageListFetchErrorHandler.handleError(error);
|
||||
});
|
||||
|
||||
if (!isDefined(messageFolder.syncCursor)) {
|
||||
throw new MessageImportDriverException(
|
||||
'Message folder sync cursor is required',
|
||||
MessageImportDriverExceptionCode.SYNC_CURSOR_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
return {
|
||||
messageExternalIds,
|
||||
messageExternalIdsToDelete,
|
||||
|
||||
+9
-4
@@ -1,6 +1,9 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { createTransport, type Transporter } from 'nodemailer';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import type SMTPConnection from 'nodemailer/lib/smtp-connection';
|
||||
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
|
||||
@@ -14,21 +17,23 @@ export class SmtpClientProvider {
|
||||
): Promise<Transporter> {
|
||||
const smtpParams = connectedAccount.connectionParameters?.SMTP;
|
||||
|
||||
if (!smtpParams) {
|
||||
if (!isDefined(smtpParams)) {
|
||||
throw new Error('SMTP settings not configured for this account');
|
||||
}
|
||||
|
||||
const transporter = createTransport({
|
||||
const options: SMTPConnection.Options = {
|
||||
host: smtpParams.host,
|
||||
port: smtpParams.port,
|
||||
auth: {
|
||||
user: smtpParams.username ?? connectedAccount.handle,
|
||||
user: smtpParams.username ?? connectedAccount.handle ?? '',
|
||||
pass: smtpParams.password,
|
||||
},
|
||||
tls: {
|
||||
rejectUnauthorized: false,
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
const transporter = createTransport(options);
|
||||
|
||||
return transporter;
|
||||
}
|
||||
|
||||
+2
@@ -87,6 +87,8 @@ export class MessageImportExceptionHandlerService {
|
||||
workspaceId,
|
||||
);
|
||||
break;
|
||||
case MessageImportDriverExceptionCode.CHANNEL_MISCONFIGURED:
|
||||
case MessageImportDriverExceptionCode.ACCESS_TOKEN_MISSING:
|
||||
case MessageImportDriverExceptionCode.UNKNOWN:
|
||||
case MessageImportDriverExceptionCode.UNKNOWN_NETWORK_ERROR:
|
||||
default:
|
||||
|
||||
+23
-1
@@ -1,5 +1,7 @@
|
||||
import { Injectable, Logger } from '@nestjs/common';
|
||||
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { InjectCacheStorage } from 'src/engine/core-modules/cache-storage/decorators/cache-storage.decorator';
|
||||
import { CacheStorageService } from 'src/engine/core-modules/cache-storage/services/cache-storage.service';
|
||||
import { CacheStorageNamespace } from 'src/engine/core-modules/cache-storage/types/cache-storage-namespace.enum';
|
||||
@@ -14,6 +16,10 @@ import {
|
||||
MessageChannelSyncStage,
|
||||
type MessageChannelWorkspaceEntity,
|
||||
} from 'src/modules/messaging/common/standard-objects/message-channel.workspace-entity';
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
|
||||
import { MESSAGING_GMAIL_USERS_MESSAGES_GET_BATCH_SIZE } from 'src/modules/messaging/message-import-manager/drivers/gmail/constants/messaging-gmail-users-messages-get-batch-size.constant';
|
||||
import { MessagingAccountAuthenticationService } from 'src/modules/messaging/message-import-manager/services/messaging-account-authentication.service';
|
||||
import { MessagingGetMessagesService } from 'src/modules/messaging/message-import-manager/services/messaging-get-messages.service';
|
||||
@@ -114,11 +120,27 @@ export class MessagingMessagesImportService {
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
if (!isDefined(messageChannel.handle)) {
|
||||
throw new MessageImportDriverException(
|
||||
'Message channel handle is required',
|
||||
MessageImportDriverExceptionCode.CHANNEL_MISCONFIGURED,
|
||||
);
|
||||
}
|
||||
|
||||
if (!isDefined(connectedAccountWithFreshTokens.handleAliases)) {
|
||||
throw new MessageImportDriverException(
|
||||
'Message channel handle is required',
|
||||
MessageImportDriverExceptionCode.CHANNEL_MISCONFIGURED,
|
||||
);
|
||||
}
|
||||
|
||||
const messagesToSave = filterEmails(
|
||||
messageChannel.handle,
|
||||
[...connectedAccountWithFreshTokens.handleAliases.split(',')],
|
||||
allMessages,
|
||||
blocklist.map((blocklistItem) => blocklistItem.handle),
|
||||
blocklist
|
||||
.map((blocklistItem) => blocklistItem.handle)
|
||||
.filter(isDefined),
|
||||
messageChannel.excludeGroupEmails,
|
||||
);
|
||||
|
||||
|
||||
+12
-1
@@ -8,6 +8,10 @@ import { z } from 'zod';
|
||||
|
||||
import { OAuth2ClientManagerService } from 'src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service';
|
||||
import { type ConnectedAccountWorkspaceEntity } from 'src/modules/connected-account/standard-objects/connected-account.workspace-entity';
|
||||
import {
|
||||
MessageImportDriverException,
|
||||
MessageImportDriverExceptionCode,
|
||||
} from 'src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception';
|
||||
import { ImapClientProvider } from 'src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider';
|
||||
import { SmtpClientProvider } from 'src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider';
|
||||
import { mimeEncode } from 'src/modules/messaging/message-import-manager/utils/mime-encode.util';
|
||||
@@ -140,6 +144,13 @@ export class MessagingSendMessageService {
|
||||
const smtpClient =
|
||||
await this.smtpClientProvider.getSmtpClient(connectedAccount);
|
||||
|
||||
if (!isDefined(handle)) {
|
||||
throw new MessageImportDriverException(
|
||||
'Handle is required',
|
||||
MessageImportDriverExceptionCode.CHANNEL_MISCONFIGURED,
|
||||
);
|
||||
}
|
||||
|
||||
const mail = new MailComposer({
|
||||
from: handle,
|
||||
to: sendMessageInput.to,
|
||||
@@ -178,7 +189,7 @@ export class MessagingSendMessageService {
|
||||
(messageFolder) => messageFolder.isSentFolder,
|
||||
);
|
||||
|
||||
if (isDefined(sentFolder)) {
|
||||
if (isDefined(sentFolder) && isDefined(sentFolder.name)) {
|
||||
await imapClient.append(sentFolder.name, messageBuffer);
|
||||
}
|
||||
|
||||
|
||||
+4
-1
@@ -1,3 +1,5 @@
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import { type MessageWithParticipants } from 'src/modules/messaging/message-import-manager/types/message';
|
||||
import { getDomainNameByEmail } from 'src/utils/get-domain-name-by-email';
|
||||
|
||||
@@ -14,9 +16,10 @@ export const filterOutInternals = (
|
||||
|
||||
try {
|
||||
const isAllHandlesFromSameDomain = message.participants
|
||||
.filter((participant) => !!participant.handle)
|
||||
.filter((participant) => isDefined(participant.handle))
|
||||
.every(
|
||||
(participant) =>
|
||||
isDefined(participant.handle) &&
|
||||
getDomainNameByEmail(participant.handle) === primaryHandleDomain,
|
||||
);
|
||||
|
||||
|
||||
+7
-6
@@ -1,4 +1,5 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
ActorMetadata,
|
||||
EmailsMetadata,
|
||||
@@ -8,13 +9,12 @@ import {
|
||||
type FullNameMetadata,
|
||||
type LinksMetadata,
|
||||
} from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types';
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceDuplicateCriteria } from 'src/engine/twenty-orm/decorators/workspace-duplicate-criteria.decorator';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
@@ -127,7 +127,7 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconBriefcase',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
jobTitle: string;
|
||||
jobTitle: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: PERSON_STANDARD_FIELD_IDS.phone,
|
||||
@@ -137,7 +137,8 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconPhone',
|
||||
})
|
||||
@WorkspaceIsDeprecated()
|
||||
phone: string;
|
||||
@WorkspaceIsNullable()
|
||||
phone: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: PERSON_STANDARD_FIELD_IDS.phones,
|
||||
@@ -160,7 +161,7 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconMap',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
city: string;
|
||||
city: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: PERSON_STANDARD_FIELD_IDS.avatarUrl,
|
||||
@@ -171,7 +172,7 @@ export class PersonWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
})
|
||||
@WorkspaceIsSystem()
|
||||
@WorkspaceIsNullable()
|
||||
avatarUrl: string;
|
||||
avatarUrl: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: PERSON_STANDARD_FIELD_IDS.position,
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
ActorMetadata,
|
||||
FieldMetadataType,
|
||||
RelationOnDeleteAction,
|
||||
type RichTextV2Metadata,
|
||||
} from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types';
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator';
|
||||
@@ -75,7 +75,7 @@ export class TaskWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
icon: 'IconNotes',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
title: string;
|
||||
title: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: TASK_STANDARD_FIELD_IDS.bodyV2,
|
||||
|
||||
+4
-2
@@ -58,7 +58,8 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Event name`,
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
name: string;
|
||||
@WorkspaceIsNullable()
|
||||
name: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.properties,
|
||||
@@ -78,7 +79,8 @@ export class TimelineActivityWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Cached record name`,
|
||||
icon: 'IconAbc',
|
||||
})
|
||||
linkedRecordCachedName: string;
|
||||
@WorkspaceIsNullable()
|
||||
linkedRecordCachedName: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: TIMELINE_ACTIVITY_STANDARD_FIELD_IDS.linkedRecordId,
|
||||
|
||||
+4
-3
@@ -1,19 +1,19 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import {
|
||||
ActorMetadata,
|
||||
FieldMetadataType,
|
||||
RelationOnDeleteAction,
|
||||
} from 'twenty-shared/types';
|
||||
import { type WorkflowRunStepInfos } from 'twenty-shared/workflow';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types';
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator';
|
||||
@@ -103,7 +103,8 @@ export class WorkflowRunWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Name of the workflow run`,
|
||||
icon: 'IconSettingsAutomation',
|
||||
})
|
||||
name: string;
|
||||
@WorkspaceIsNullable()
|
||||
name: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: WORKFLOW_RUN_STANDARD_FIELD_IDS.enqueuedAt,
|
||||
|
||||
+4
-3
@@ -1,13 +1,13 @@
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { FieldMetadataType, RelationOnDeleteAction } from 'twenty-shared/types';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
import { type FieldMetadataComplexOption } from 'src/engine/metadata-modules/field-metadata/dtos/options.input';
|
||||
import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types';
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator';
|
||||
@@ -90,7 +90,8 @@ export class WorkflowVersionWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`The workflow version name`,
|
||||
icon: 'IconSettingsAutomation',
|
||||
})
|
||||
name: string;
|
||||
@WorkspaceIsNullable()
|
||||
name: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: WORKFLOW_VERSION_STANDARD_FIELD_IDS.trigger,
|
||||
|
||||
+2
@@ -126,6 +126,7 @@ export class WorkflowVersionWorkspaceService {
|
||||
|
||||
return {
|
||||
...draftWorkflowVersion,
|
||||
name: draftWorkflowVersion.name ?? '',
|
||||
steps: newWorkflowVersionSteps,
|
||||
trigger: newWorkflowVersionTrigger,
|
||||
};
|
||||
@@ -289,6 +290,7 @@ export class WorkflowVersionWorkspaceService {
|
||||
|
||||
return {
|
||||
...newDraftVersion,
|
||||
name: newDraftVersion.name ?? '',
|
||||
steps: remappedSteps,
|
||||
trigger: remappedTrigger ?? null,
|
||||
};
|
||||
|
||||
+2
-2
@@ -1,10 +1,10 @@
|
||||
import { Injectable } from '@nestjs/common';
|
||||
|
||||
import { type ActorMetadata } from 'twenty-shared/types';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
import { StepStatus, type WorkflowRunStepInfo } from 'twenty-shared/workflow';
|
||||
import { type QueryDeepPartialEntity } from 'typeorm/query-builder/QueryPartialEntity';
|
||||
import { v4 } from 'uuid';
|
||||
import { type ActorMetadata } from 'twenty-shared/types';
|
||||
|
||||
import { WithLock } from 'src/engine/core-modules/cache-lock/with-lock.decorator';
|
||||
import { MetricsService } from 'src/engine/core-modules/metrics/metrics.service';
|
||||
@@ -109,7 +109,7 @@ export class WorkflowRunWorkspaceService {
|
||||
order: { createdAt: 'desc' },
|
||||
});
|
||||
|
||||
const workflowRunCountMatch = lastWorkflowRun?.name.match(/#(\d+)/);
|
||||
const workflowRunCountMatch = lastWorkflowRun?.name?.match(/#(\d+)/);
|
||||
|
||||
const workflowRunCount = workflowRunCountMatch
|
||||
? parseInt(workflowRunCountMatch[1], 10)
|
||||
|
||||
+7
-5
@@ -1,20 +1,20 @@
|
||||
import { registerEnumType } from '@nestjs/graphql';
|
||||
|
||||
import { msg } from '@lingui/core/macro';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
import { type APP_LOCALES, SOURCE_LOCALE } from 'twenty-shared/translations';
|
||||
import {
|
||||
FieldMetadataType,
|
||||
FullNameMetadata,
|
||||
NumberDataType,
|
||||
RelationOnDeleteAction,
|
||||
FullNameMetadata,
|
||||
} from 'twenty-shared/types';
|
||||
import { STANDARD_OBJECT_IDS } from 'twenty-shared/metadata';
|
||||
|
||||
import { RelationType } from 'src/engine/metadata-modules/field-metadata/interfaces/relation-type.interface';
|
||||
import { Relation } from 'src/engine/workspace-manager/workspace-sync-metadata/interfaces/relation.interface';
|
||||
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
import { IndexType } from 'src/engine/metadata-modules/index-metadata/types/indexType.types';
|
||||
import { SEARCH_VECTOR_FIELD } from 'src/engine/metadata-modules/search-field-metadata/constants/search-vector-field.constants';
|
||||
import { BaseWorkspaceEntity } from 'src/engine/twenty-orm/base.workspace-entity';
|
||||
import { WorkspaceEntity } from 'src/engine/twenty-orm/decorators/workspace-entity.decorator';
|
||||
import { WorkspaceFieldIndex } from 'src/engine/twenty-orm/decorators/workspace-field-index.decorator';
|
||||
@@ -148,8 +148,9 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Workspace member avatar`,
|
||||
icon: 'IconFileUpload',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
avatarUrl: string;
|
||||
avatarUrl: string | null;
|
||||
|
||||
@WorkspaceIsUnique()
|
||||
@WorkspaceField({
|
||||
@@ -159,8 +160,9 @@ export class WorkspaceMemberWorkspaceEntity extends BaseWorkspaceEntity {
|
||||
description: msg`Related user email address`,
|
||||
icon: 'IconMail',
|
||||
})
|
||||
@WorkspaceIsNullable()
|
||||
@WorkspaceIsSystem()
|
||||
userEmail: string;
|
||||
userEmail: string | null;
|
||||
|
||||
@WorkspaceField({
|
||||
standardId: WORKSPACE_MEMBER_STANDARD_FIELD_IDS.calendarStartDay,
|
||||
|
||||
Reference in New Issue
Block a user