diff --git a/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-migrate-attachment-type-to-file-category.command.ts b/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-migrate-attachment-type-to-file-category.command.ts index ea339e94e4..c95658a328 100644 --- a/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-migrate-attachment-type-to-file-category.command.ts +++ b/packages/twenty-server/src/database/commands/upgrade-version-command/1-10/1-10-migrate-attachment-type-to-file-category.command.ts @@ -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; diff --git a/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.service.ts b/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.service.ts index 83d2185b25..ce5555646c 100644 --- a/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.service.ts +++ b/packages/twenty-server/src/engine/core-modules/approved-access-domain/services/approved-access-domain.service.ts @@ -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: { diff --git a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts index c976148d9b..620109ca71 100644 --- a/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts +++ b/packages/twenty-server/src/engine/core-modules/calendar/timeline-calendar-event.service.ts @@ -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 ?? '', }; }); diff --git a/packages/twenty-server/src/engine/core-modules/file/listeners/file-attachment.listener.ts b/packages/twenty-server/src/engine/core-modules/file/listeners/file-attachment.listener.ts index 05e7a641d2..d7454a8cd4 100644 --- a/packages/twenty-server/src/engine/core-modules/file/listeners/file-attachment.listener.ts +++ b/packages/twenty-server/src/engine/core-modules/file/listeners/file-attachment.listener.ts @@ -31,7 +31,7 @@ export class FileAttachmentListener { FileDeletionJob.name, { workspaceId: payload.workspaceId, - fullPath: event.properties.before.fullPath, + fullPath: event.properties.before.fullPath ?? '', }, ); } diff --git a/packages/twenty-server/src/engine/core-modules/file/listeners/file-workspace-member.listener.ts b/packages/twenty-server/src/engine/core-modules/file/listeners/file-workspace-member.listener.ts index 8e29515a35..69d335fe7e 100644 --- a/packages/twenty-server/src/engine/core-modules/file/listeners/file-workspace-member.listener.ts +++ b/packages/twenty-server/src/engine/core-modules/file/listeners/file-workspace-member.listener.ts @@ -35,7 +35,7 @@ export class FileWorkspaceMemberListener { this.messageQueueService.add(FileDeletionJob.name, { workspaceId: payload.workspaceId, - fullPath: event.properties.before.avatarUrl, + fullPath: event.properties.before.avatarUrl ?? '', }); } } diff --git a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts index e6a33e8a6e..64dd6ad979 100644 --- a/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/imap-smtp-caldav-connection/imap-smtp-caldav-connection.resolver.ts @@ -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`, ); diff --git a/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts b/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts index cffd6ac27f..b73fe09e9c 100644 --- a/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts +++ b/packages/twenty-server/src/engine/core-modules/messaging/services/timeline-messaging.service.ts @@ -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, }; diff --git a/packages/twenty-server/src/engine/core-modules/messaging/utils/format-thread-participant.util.ts b/packages/twenty-server/src/engine/core-modules/messaging/utils/format-thread-participant.util.ts index bdfccd8fcb..b5420b2567 100644 --- a/packages/twenty-server/src/engine/core-modules/messaging/utils/format-thread-participant.util.ts +++ b/packages/twenty-server/src/engine/core-modules/messaging/utils/format-thread-participant.util.ts @@ -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, + }; +}; diff --git a/packages/twenty-server/src/engine/core-modules/user/services/workspace-member-transpiler.service.ts b/packages/twenty-server/src/engine/core-modules/user/services/workspace-member-transpiler.service.ts index e339099d4b..01b306901c 100644 --- a/packages/twenty-server/src/engine/core-modules/user/services/workspace-member-transpiler.service.ts +++ b/packages/twenty-server/src/engine/core-modules/user/services/workspace-member-transpiler.service.ts @@ -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, diff --git a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version.resolver.ts b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version.resolver.ts index 6dac344d81..3dbc5d65fa 100644 --- a/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version.resolver.ts +++ b/packages/twenty-server/src/engine/core-modules/workflow/resolvers/workflow-version.resolver.ts @@ -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) diff --git a/packages/twenty-server/src/engine/core-modules/workspace-invitation/services/workspace-invitation.service.ts b/packages/twenty-server/src/engine/core-modules/workspace-invitation/services/workspace-invitation.service.ts index 3ea59910bf..df6932a71d 100644 --- a/packages/twenty-server/src/engine/core-modules/workspace-invitation/services/workspace-invitation.service.ts +++ b/packages/twenty-server/src/engine/core-modules/workspace-invitation/services/workspace-invitation.service.ts @@ -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: { diff --git a/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-standard-agent-definition-to-flat-agent.util.ts b/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-standard-agent-definition-to-flat-agent.util.ts index 693f0fedf9..b8488d4eb4 100644 --- a/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-standard-agent-definition-to-flat-agent.util.ts +++ b/packages/twenty-server/src/engine/metadata-modules/flat-agent/utils/transform-standard-agent-definition-to-flat-agent.util.ts @@ -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, diff --git a/packages/twenty-server/src/engine/twenty-orm/custom.workspace-entity.ts b/packages/twenty-server/src/engine/twenty-orm/custom.workspace-entity.ts index a0be74e85b..5f45542446 100644 --- a/packages/twenty-server/src/engine/twenty-orm/custom.workspace-entity.ts +++ b/packages/twenty-server/src/engine/twenty-orm/custom.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/services/cleaner.workspace-service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/services/cleaner.workspace-service.ts index 0398cf2b51..27ac58837e 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/services/cleaner.workspace-service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-cleaner/services/cleaner.workspace-service.ts @@ -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( diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-role.factory.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-role.factory.ts index 20e1a24a94..e03355b929 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-role.factory.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/factories/standard-role.factory.ts @@ -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[] { + const computedRoles: Partial[] = []; for (const roleDefinition of roleDefinitions) { const existingRole = existingRoles.find( diff --git a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-role.service.ts b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-role.service.ts index 3252a16bec..6b039bf8ec 100644 --- a/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-role.service.ts +++ b/packages/twenty-server/src/engine/workspace-manager/workspace-sync-metadata/services/workspace-sync-role.service.ts @@ -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) { diff --git a/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts b/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts index 1f2647da4f..7eddc62e4b 100644 --- a/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts +++ b/packages/twenty-server/src/modules/attachment/standard-objects/attachment.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/blocklist/standard-objects/blocklist.workspace-entity.ts b/packages/twenty-server/src/modules/blocklist/standard-objects/blocklist.workspace-entity.ts index 3738f497ce..0508cee2b3 100644 --- a/packages/twenty-server/src/modules/blocklist/standard-objects/blocklist.workspace-entity.ts +++ b/packages/twenty-server/src/modules/blocklist/standard-objects/blocklist.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-item-delete-calendar-events.job.ts b/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-item-delete-calendar-events.job.ts index ccf4c283dc..9d9bda7260 100644 --- a/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-item-delete-calendar-events.job.ts +++ b/packages/twenty-server/src/modules/calendar/blocklist-manager/jobs/blocklist-item-delete-calendar-events.job.ts @@ -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; diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/providers/caldav.provider.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/providers/caldav.provider.ts index 3015f416d5..db34fed972 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/providers/caldav.provider.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/caldav/providers/caldav.provider.ts @@ -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 { if ( !connectedAccount.connectionParameters?.CALDAV?.password || - !connectedAccount.connectionParameters?.CALDAV?.host + !connectedAccount.connectionParameters?.CALDAV?.host || + !isDefined(connectedAccount.handle) ) { throw new Error('Missing required CalDAV connection parameters'); } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/exceptions/calendar-event-import-driver.exception.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/exceptions/calendar-event-import-driver.exception.ts index b805e07f55..a7b3b3c742 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/exceptions/calendar-event-import-driver.exception.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/drivers/exceptions/calendar-event-import-driver.exception.ts @@ -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', } diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts index b479c0d072..fb5c1717b3 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-event-import-exception-handler.service.ts @@ -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: diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service.ts index 3ac53cb8ef..6894f4ded1 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-events-import.service.ts @@ -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( diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service.ts index 2bb64f24f9..7e2803c5b6 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/services/calendar-fetch-events.service.ts @@ -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, diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/utils/calendar-event-mapper.util.ts b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/utils/calendar-event-mapper.util.ts index d5d1662813..a098150532 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/utils/calendar-event-mapper.util.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-import-manager/utils/calendar-event-mapper.util.ts @@ -5,7 +5,7 @@ export const mapCalendarEventsByICalUID = ( ): Map => { return new Map( existingCalendarEvents.map((calendarEvent) => [ - calendarEvent.iCalUID, + calendarEvent.iCalUID ?? '', calendarEvent.id, ]), ); diff --git a/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service.ts b/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service.ts index 0fbcfe107b..128de9668c 100644 --- a/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service.ts +++ b/packages/twenty-server/src/modules/calendar/calendar-event-participant-manager/services/calendar-event-participant.service.ts @@ -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, }, ); diff --git a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity.ts b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity.ts index 149516fcd0..093df891e8 100644 --- a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity.ts +++ b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel-event-association.workspace-entity.ts @@ -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: diff --git a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity.ts b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity.ts index d9d809d48c..73ddfff6b3 100644 --- a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity.ts +++ b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-channel.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity.ts b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity.ts index bbd841d987..035a6cca0b 100644 --- a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity.ts +++ b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event-participant.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event.workspace-entity.ts b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event.workspace-entity.ts index 66c2abc7ee..3ff9042db8 100644 --- a/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event.workspace-entity.ts +++ b/packages/twenty-server/src/modules/calendar/common/standard-objects/calendar-event.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/company/standard-objects/company.workspace-entity.ts b/packages/twenty-server/src/modules/company/standard-objects/company.workspace-entity.ts index 508259fe88..8890f8b268 100644 --- a/packages/twenty-server/src/modules/company/standard-objects/company.workspace-entity.ts +++ b/packages/twenty-server/src/modules/company/standard-objects/company.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/connected-account/oauth2-client-manager/exceptions/oauth2-client-manager.exceptions.ts b/packages/twenty-server/src/modules/connected-account/oauth2-client-manager/exceptions/oauth2-client-manager.exceptions.ts new file mode 100644 index 0000000000..af88b440b2 --- /dev/null +++ b/packages/twenty-server/src/modules/connected-account/oauth2-client-manager/exceptions/oauth2-client-manager.exceptions.ts @@ -0,0 +1,4 @@ +export enum OAuth2ClientManagerExceptionCode { + ACCESS_TOKEN_REQUIRED = 'ACCESS_TOKEN_REQUIRED', + REFRESH_TOKEN_REQUIRED = 'REFRESH_TOKEN_REQUIRED', +} diff --git a/packages/twenty-server/src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service.ts b/packages/twenty-server/src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service.ts index abd1344074..5f85698e07 100644 --- a/packages/twenty-server/src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service.ts +++ b/packages/twenty-server/src/modules/connected-account/oauth2-client-manager/services/oauth2-client-manager.service.ts @@ -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 { + 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 { + if (!isDefined(connectedAccount.accessToken)) { + throw new CustomError( + 'Access token is required', + OAuth2ClientManagerExceptionCode.ACCESS_TOKEN_REQUIRED, + ); + } + return this.microsoftOAuth2ClientManagerService.getOAuth2Client( connectedAccount.accessToken, ); diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception.ts index 908e20b646..25f7c73213 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/exceptions/connected-account-refresh-tokens.exception.ts @@ -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', } diff --git a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts index 5f9bc8d9b5..be7f4dfe80 100644 --- a/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts +++ b/packages/twenty-server/src/modules/connected-account/refresh-tokens-manager/services/connected-account-refresh-tokens.service.ts @@ -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, diff --git a/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts b/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts index c05ff4d3ee..edef1d09bf 100644 --- a/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts +++ b/packages/twenty-server/src/modules/connected-account/standard-objects/connected-account.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/contact-creation-manager/utils/filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts b/packages/twenty-server/src/modules/contact-creation-manager/utils/filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts index 7db5d9b4da..58ab555016 100644 --- a/packages/twenty-server/src/modules/contact-creation-manager/utils/filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts +++ b/packages/twenty-server/src/modules/contact-creation-manager/utils/filter-out-contacts-that-belong-to-self-or-workspace-members.util.ts @@ -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(); diff --git a/packages/twenty-server/src/modules/favorite-folder/standard-objects/favorite-folder.workspace-entity.ts b/packages/twenty-server/src/modules/favorite-folder/standard-objects/favorite-folder.workspace-entity.ts index 0b77dc5038..a812cf90bb 100644 --- a/packages/twenty-server/src/modules/favorite-folder/standard-objects/favorite-folder.workspace-entity.ts +++ b/packages/twenty-server/src/modules/favorite-folder/standard-objects/favorite-folder.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/match-participant/match-participant.service.ts b/packages/twenty-server/src/modules/match-participant/match-participant.service.ts index 8737ef50ba..781a568503 100644 --- a/packages/twenty-server/src/modules/match-participant/match-participant.service.ts +++ b/packages/twenty-server/src/modules/match-participant/match-participant.service.ts @@ -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, diff --git a/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts b/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts index 21718b351b..98e07e0d2d 100644 --- a/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts +++ b/packages/twenty-server/src/modules/messaging/blocklist-manager/jobs/messaging-blocklist-item-delete-messages.job.ts @@ -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; diff --git a/packages/twenty-server/src/modules/messaging/common/standard-objects/message-channel.workspace-entity.ts b/packages/twenty-server/src/modules/messaging/common/standard-objects/message-channel.workspace-entity.ts index 2c82068735..33f17e8538 100644 --- a/packages/twenty-server/src/modules/messaging/common/standard-objects/message-channel.workspace-entity.ts +++ b/packages/twenty-server/src/modules/messaging/common/standard-objects/message-channel.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/messaging/common/standard-objects/message-folder.workspace-entity.ts b/packages/twenty-server/src/modules/messaging/common/standard-objects/message-folder.workspace-entity.ts index eefeec5816..3cc848eed1 100644 --- a/packages/twenty-server/src/modules/messaging/common/standard-objects/message-folder.workspace-entity.ts +++ b/packages/twenty-server/src/modules/messaging/common/standard-objects/message-folder.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/messaging/common/standard-objects/message-participant.workspace-entity.ts b/packages/twenty-server/src/modules/messaging/common/standard-objects/message-participant.workspace-entity.ts index cf1638bfbb..76e1701ff2 100644 --- a/packages/twenty-server/src/modules/messaging/common/standard-objects/message-participant.workspace-entity.ts +++ b/packages/twenty-server/src/modules/messaging/common/standard-objects/message-participant.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/messaging/common/standard-objects/message.workspace-entity.ts b/packages/twenty-server/src/modules/messaging/common/standard-objects/message.workspace-entity.ts index e8a8b5932d..f34996a6af 100644 --- a/packages/twenty-server/src/modules/messaging/common/standard-objects/message.workspace-entity.ts +++ b/packages/twenty-server/src/modules/messaging/common/standard-objects/message.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts b/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts index 1efd1816a6..2a879824db 100644 --- a/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-folder-manager/services/sync-message-folders.service.ts @@ -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 ?? ''; } } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception.ts index 2685ca6cdb..f3f073e832 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/exceptions/message-import-driver.exception.ts @@ -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', } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-messages.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-messages.service.ts index dc64c21a85..9ea95fee42 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-messages.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/gmail/services/gmail-get-messages.service.ts @@ -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 { + if (!isDefined(connectedAccount.accessToken)) { + throw new MessageImportDriverException( + 'Access token is required', + MessageImportDriverExceptionCode.ACCESS_TOKEN_MISSING, + ); + } const { messageIdsByBatch, batchResponses } = await this.fetchByBatchesService.fetchAllByBatches( messageIds, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider.ts index 3e391d9a14..b32e9c6dba 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/providers/imap-client.provider.ts @@ -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 || '', diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.ts index 1d00e769dd..26e00dff03 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/imap/services/imap-get-message-list.service.ts @@ -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, ): Promise { + 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, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts index 119218770a..7c27d67297 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/microsoft/services/microsoft-get-message-list.service.ts @@ -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, diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider.ts index 34d881c103..44fbd9753d 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/drivers/smtp/providers/smtp-client.provider.ts @@ -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 { 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; } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts index 34e9a6f81a..1952c16e15 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-import-exception-handler.service.ts @@ -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: diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts index 73e448df7c..7189a7901c 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-messages-import.service.ts @@ -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, ); diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-send-message.service.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-send-message.service.ts index 97add291e7..6d825bce1e 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-send-message.service.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/services/messaging-send-message.service.ts @@ -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); } diff --git a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-internals.util.ts b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-internals.util.ts index 7b6a28576e..c4a24e38c8 100644 --- a/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-internals.util.ts +++ b/packages/twenty-server/src/modules/messaging/message-import-manager/utils/filter-out-internals.util.ts @@ -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, ); diff --git a/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts b/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts index ef34e21321..f6c156307e 100644 --- a/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts +++ b/packages/twenty-server/src/modules/person/standard-objects/person.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts b/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts index 877f314292..0fa9ea4753 100644 --- a/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts +++ b/packages/twenty-server/src/modules/task/standard-objects/task.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts b/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts index 77ae7eccae..9b84bec784 100644 --- a/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts +++ b/packages/twenty-server/src/modules/timeline/standard-objects/timeline-activity.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts index 0d945c1ca4..2345a3388c 100644 --- a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-run.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts index 9c68461d06..e91c330cd3 100644 --- a/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workflow/common/standard-objects/workflow-version.workspace-entity.ts @@ -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, diff --git a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts index 160cad1c75..7b8f779511 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-builder/workflow-version/workflow-version.workspace-service.ts @@ -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, }; diff --git a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts index e351020e49..cb92d0d308 100644 --- a/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts +++ b/packages/twenty-server/src/modules/workflow/workflow-runner/workflow-run/workflow-run.workspace-service.ts @@ -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) diff --git a/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts b/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts index 0cfb07d901..294ec08943 100644 --- a/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts +++ b/packages/twenty-server/src/modules/workspace-member/standard-objects/workspace-member.workspace-entity.ts @@ -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,